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, "formatVersion": 1,
"database": { "database": {
"version": 21, "version": 21,
"identityHash": "3be2d00eeed0d077b84c352d10f6628f", "identityHash": "60367016912c1948d70c556647e93114",
"entities": [ "entities": [
{ {
"tableName": "results", "tableName": "results",
@ -384,7 +384,7 @@
}, },
{ {
"tableName": "commandTemplates", "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": [ "fields": [
{ {
"fieldPath": "id", "fieldPath": "id",
@ -431,6 +431,20 @@
"affinity": "INTEGER", "affinity": "INTEGER",
"notNull": true, "notNull": true,
"defaultValue": "0" "defaultValue": "0"
},
{
"fieldPath": "preferredCommandTemplate",
"columnName": "preferredCommandTemplate",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "urlRegex",
"columnName": "urlRegex",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "'[]'"
} }
], ],
"primaryKey": { "primaryKey": {
@ -747,7 +761,7 @@
"views": [], "views": [],
"setupQueries": [ "setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", "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, TerminalItem::class,
ObserveSourcesItem::class ObserveSourcesItem::class
], ],
version = 21, version = 22,
autoMigrations = [ autoMigrations = [
AutoMigration (from = 1, to = 2), AutoMigration (from = 1, to = 2),
AutoMigration (from = 2, to = 3), AutoMigration (from = 2, to = 3),
@ -63,6 +63,7 @@ import com.deniscerri.ytdl.database.models.TerminalItem
AutoMigration (from = 18, to = 19), AutoMigration (from = 18, to = 19),
AutoMigration (from = 19, to = 20), AutoMigration (from = 19, to = 20),
//AutoMigration (from = 20, to = 21) MANUALLY HANDLED //AutoMigration (from = 20, to = 21) MANUALLY HANDLED
//AutoMigration(from = 21, to = 22) MANUALLY HANDLED
] ]
) )
abstract class DBManager : RoomDatabase(){ abstract class DBManager : RoomDatabase(){

View file

@ -42,6 +42,15 @@ object Migrations {
database.execSQL("UPDATE history SET filesize = ${parsed.filesize} WHERE id = $id") 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") @Query("SELECT content FROM commandTemplates WHERE useAsExtraCommand is 1")
fun getAllTemplatesAsExtraCommands() : List<String> fun getAllTemplatesAsExtraCommands() : List<String>
@Query("SELECT content FROM commandTemplates WHERE useAsExtraCommand is 1 AND useAsExtraCommandAudio = 1") @Query("SELECT * FROM commandTemplates WHERE useAsExtraCommandDataFetching is 1")
fun getAllTemplatesAsExtraCommandsForAudio() : List<String> fun getAllTemplatesAsDataFetchingExtraCommands() : List<CommandTemplate>
@Query("SELECT content FROM commandTemplates WHERE useAsExtraCommand is 1 AND useAsExtraCommandVideo = 1") @Query("SELECT * FROM commandTemplates WHERE useAsExtraCommand is 1 AND useAsExtraCommandAudio = 1")
fun getAllTemplatesAsExtraCommandsForVideo() : List<String> 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") @Query("SELECT * FROM templateShortcuts ORDER BY id DESC")
fun getAllShortcutsFlow() : Flow<List<TemplateShortcut>> fun getAllShortcutsFlow() : Flow<List<TemplateShortcut>>
@ -65,6 +68,9 @@ interface CommandTemplateDao {
@Query("SELECT * FROM commandTemplates ORDER BY id DESC LIMIT 1") @Query("SELECT * FROM commandTemplates ORDER BY id DESC LIMIT 1")
fun getFirst() : CommandTemplate? fun getFirst() : CommandTemplate?
@Query("SELECT * FROM commandTemplates WHERE preferredCommandTemplate is 1")
fun getPreferredCommandTemplates() : List<CommandTemplate>
@Insert(onConflict = OnConflictStrategy.IGNORE) @Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(item: CommandTemplate) suspend fun insert(item: CommandTemplate)

View file

@ -19,5 +19,8 @@ data class CommandTemplate(
@ColumnInfo(defaultValue = "1") @ColumnInfo(defaultValue = "1")
var useAsExtraCommandVideo: Boolean, var useAsExtraCommandVideo: Boolean,
@ColumnInfo(defaultValue = "0") @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 playerClient: String,
var poTokens: MutableList<YoutubePoTokenItem>, var poTokens: MutableList<YoutubePoTokenItem>,
var enabled: Boolean = true, var enabled: Boolean = true,
var useOnlyPoToken: Boolean = false var useOnlyPoToken: Boolean = false,
) var urlRegex: MutableList<String> = mutableListOf()
)
data class YoutubePoTokenItem( data class YoutubePoTokenItem(
var context: String, var context: String,

View file

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

View file

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

View file

@ -71,8 +71,10 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
private val sharedPreferences: SharedPreferences private val sharedPreferences: SharedPreferences
init { init {
dao = DBManager.getInstance(application).resultDao val dbManager = DBManager.getInstance(application)
repository = ResultRepository(dao, getApplication<Application>().applicationContext) dao = dbManager.resultDao
val commandTemplateDao = dbManager.commandTemplateDao
repository = ResultRepository(dao, commandTemplateDao, getApplication<Application>().applicationContext)
searchHistoryRepository = SearchHistoryRepository(DBManager.getInstance(application).searchHistoryDao) searchHistoryRepository = SearchHistoryRepository(DBManager.getInstance(application).searchHistoryDao)
items = repository.allResults.asLiveData() 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 { card.findViewById<TextView>(R.id.preferredTemplate).apply {
val preferred = sharedPreferences.getString("preferred_command_template", "") isVisible = item.preferredCommandTemplate
isVisible = preferred == item.content
} }
if (checkedItems.contains(item.id)) { if (checkedItems.contains(item.id)) {
@ -170,7 +169,9 @@ class TemplatesAdapter(onItemClickListener: OnItemClickListener, activity: Activ
oldItem.useAsExtraCommand == newItem.useAsExtraCommand && oldItem.useAsExtraCommand == newItem.useAsExtraCommand &&
oldItem.useAsExtraCommandAudio == newItem.useAsExtraCommandAudio && oldItem.useAsExtraCommandAudio == newItem.useAsExtraCommandAudio &&
oldItem.useAsExtraCommandVideo == newItem.useAsExtraCommandVideo && 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 { override fun areContentsTheSame(oldItem: YoutubePlayerClientItem, newItem: YoutubePlayerClientItem): Boolean {
return oldItem.playerClient == newItem.playerClient && oldItem.enabled == newItem.enabled && oldItem.poTokens.joinToString( return oldItem.playerClient == newItem.playerClient &&
"\n" oldItem.enabled == newItem.enabled &&
) { it.token } == newItem.poTokens.joinToString( oldItem.poTokens.joinToString("\n") { it.token } == newItem.poTokens.joinToString("\n") { it.token } &&
"\n" oldItem.urlRegex.joinToString("\n") == newItem.urlRegex.joinToString("\n")
) { it.token }
} }
} }
} }

View file

@ -19,6 +19,7 @@ import androidx.preference.PreferenceManager
import com.deniscerri.ytdl.R import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.models.DownloadItem import com.deniscerri.ytdl.database.models.DownloadItem
import com.deniscerri.ytdl.database.viewmodel.CommandTemplateViewModel 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.Extensions.enableTextHighlight
import com.deniscerri.ytdl.util.UiUtil import com.deniscerri.ytdl.util.UiUtil
import com.deniscerri.ytdl.util.extractors.YTDLPUtil 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() { 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 sharedPreferences: SharedPreferences
private lateinit var commandTemplateViewModel: CommandTemplateViewModel private lateinit var commandTemplateViewModel: CommandTemplateViewModel
private lateinit var ytdlpViewModel: YTDLPViewModel
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
ytdlpUtil = YTDLPUtil(requireActivity().applicationContext)
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
commandTemplateViewModel = ViewModelProvider(this)[CommandTemplateViewModel::class.java] commandTemplateViewModel = ViewModelProvider(this)[CommandTemplateViewModel::class.java]
ytdlpViewModel = ViewModelProvider(this)[YTDLPViewModel::class.java]
} }
override fun onCreateView( override fun onCreateView(
@ -86,7 +87,7 @@ class AddExtraCommandsDialog(private val item: DownloadItem? = null, private val
val currentText = view.findViewById<TextView>(R.id.currentText) val currentText = view.findViewById<TextView>(R.id.currentText)
if (item != null){ if (item != null){
val currentCommand = ytdlpUtil.parseYTDLRequestString(ytdlpUtil.buildYoutubeDLRequest(item)) val currentCommand = ytdlpViewModel.parseYTDLRequestString(item)
currentText?.text = currentCommand currentText?.text = currentCommand
}else{ }else{
view.findViewById<View>(R.id.current).visibility = View.GONE 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 url = url.editText?.text?.isNotBlank() == true
val category = ObserveSourcesRepository.EveryCategory.values()[categoryAdapter.getPosition(everyCat.text.toString())] 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 everyTime = everyTime.editText?.text?.isNotBlank() == true
val every = when(category){ val every = when(category){
ObserveSourcesRepository.EveryCategory.HOUR -> { ObserveSourcesRepository.EveryCategory.HOUR -> {
everyNr everyNrBool
} }
else -> { 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.repository.DownloadRepository
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdl.database.viewmodel.ResultViewModel 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.ActiveDownloadMinifiedAdapter
import com.deniscerri.ytdl.ui.adapter.GenericDownloadAdapter import com.deniscerri.ytdl.ui.adapter.GenericDownloadAdapter
import com.deniscerri.ytdl.util.Extensions.setFullScreen import com.deniscerri.ytdl.util.Extensions.setFullScreen
@ -79,6 +80,7 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap
private lateinit var videoView: PlayerView private lateinit var videoView: PlayerView
private lateinit var downloadViewModel: DownloadViewModel private lateinit var downloadViewModel: DownloadViewModel
private lateinit var resultViewModel: ResultViewModel private lateinit var resultViewModel: ResultViewModel
private lateinit var ytdlpViewModel: YTDLPViewModel
private lateinit var activeAdapter: ActiveDownloadMinifiedAdapter private lateinit var activeAdapter: ActiveDownloadMinifiedAdapter
private lateinit var queuedAdapter: GenericDownloadAdapter private lateinit var queuedAdapter: GenericDownloadAdapter
@ -94,6 +96,7 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap
notificationUtil = NotificationUtil(requireActivity()) notificationUtil = NotificationUtil(requireActivity())
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java] resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
ytdlpViewModel = ViewModelProvider(this)[YTDLPViewModel::class.java]
downloadManager = requireContext().getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager downloadManager = requireContext().getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
} }
@ -446,6 +449,7 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap
item, item,
requireActivity(), requireActivity(),
DownloadRepository.Status.valueOf(item.status), DownloadRepository.Status.valueOf(item.status),
ytdlpViewModel,
removeItem = { it: DownloadItem, sheet: BottomSheetDialog -> removeItem = { it: DownloadItem, sheet: BottomSheetDialog ->
sheet.hide() sheet.hide()
removeQueuedItem(itemID) 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.models.DownloadItem
import com.deniscerri.ytdl.database.repository.DownloadRepository import com.deniscerri.ytdl.database.repository.DownloadRepository
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel 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.GenericDownloadAdapter
import com.deniscerri.ytdl.util.Extensions.enableFastScroll import com.deniscerri.ytdl.util.Extensions.enableFastScroll
import com.deniscerri.ytdl.util.Extensions.forceFastScrollMode import com.deniscerri.ytdl.util.Extensions.forceFastScrollMode
@ -60,6 +61,7 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
private var fragmentView: View? = null private var fragmentView: View? = null
private var activity: Activity? = null private var activity: Activity? = null
private lateinit var downloadViewModel : DownloadViewModel private lateinit var downloadViewModel : DownloadViewModel
private lateinit var ytdlpViewModel : YTDLPViewModel
private lateinit var cancelledRecyclerView : RecyclerView private lateinit var cancelledRecyclerView : RecyclerView
private lateinit var preferences : SharedPreferences private lateinit var preferences : SharedPreferences
private lateinit var adapter : GenericDownloadAdapter private lateinit var adapter : GenericDownloadAdapter
@ -79,6 +81,7 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
fragmentView = inflater.inflate(R.layout.generic_list, container, false) fragmentView = inflater.inflate(R.layout.generic_list, container, false)
activity = getActivity() activity = getActivity()
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java] downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
ytdlpViewModel = ViewModelProvider(requireActivity())[YTDLPViewModel::class.java]
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
return fragmentView return fragmentView
} }
@ -174,6 +177,7 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
item, item,
requireActivity(), requireActivity(),
DownloadRepository.Status.valueOf(item.status), DownloadRepository.Status.valueOf(item.status),
ytdlpViewModel,
removeItem = { it: DownloadItem, sheet: BottomSheetDialog -> removeItem = { it: DownloadItem, sheet: BottomSheetDialog ->
removeItem(it, sheet) 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.models.DownloadItem
import com.deniscerri.ytdl.database.repository.DownloadRepository import com.deniscerri.ytdl.database.repository.DownloadRepository
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel 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.GenericDownloadAdapter
import com.deniscerri.ytdl.util.Extensions.enableFastScroll import com.deniscerri.ytdl.util.Extensions.enableFastScroll
import com.deniscerri.ytdl.util.Extensions.forceFastScrollMode import com.deniscerri.ytdl.util.Extensions.forceFastScrollMode
@ -58,6 +59,7 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
private var fragmentView: View? = null private var fragmentView: View? = null
private var activity: Activity? = null private var activity: Activity? = null
private lateinit var downloadViewModel : DownloadViewModel private lateinit var downloadViewModel : DownloadViewModel
private lateinit var ytdlpViewModel : YTDLPViewModel
private lateinit var erroredRecyclerView : RecyclerView private lateinit var erroredRecyclerView : RecyclerView
private lateinit var preferences : SharedPreferences private lateinit var preferences : SharedPreferences
private lateinit var noResults : RelativeLayout private lateinit var noResults : RelativeLayout
@ -77,6 +79,7 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
fragmentView = inflater.inflate(R.layout.generic_list, container, false) fragmentView = inflater.inflate(R.layout.generic_list, container, false)
activity = getActivity() activity = getActivity()
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java] downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
ytdlpViewModel = ViewModelProvider(requireActivity())[YTDLPViewModel::class.java]
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
return fragmentView return fragmentView
} }
@ -174,6 +177,7 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
item, item,
requireActivity(), requireActivity(),
DownloadRepository.Status.valueOf(item.status), DownloadRepository.Status.valueOf(item.status),
ytdlpViewModel,
removeItem = { it: DownloadItem, sheet: BottomSheetDialog -> removeItem = { it: DownloadItem, sheet: BottomSheetDialog ->
removeItem(it, sheet) 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.models.DownloadItem
import com.deniscerri.ytdl.database.repository.DownloadRepository import com.deniscerri.ytdl.database.repository.DownloadRepository
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel 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.ui.adapter.QueuedDownloadAdapter
import com.deniscerri.ytdl.util.Extensions.enableFastScroll import com.deniscerri.ytdl.util.Extensions.enableFastScroll
import com.deniscerri.ytdl.util.Extensions.forceFastScrollMode import com.deniscerri.ytdl.util.Extensions.forceFastScrollMode
@ -61,6 +62,7 @@ class QueuedDownloadsFragment : Fragment(), QueuedDownloadAdapter.OnItemClickLis
private var fragmentView: View? = null private var fragmentView: View? = null
private var activity: Activity? = null private var activity: Activity? = null
private lateinit var downloadViewModel : DownloadViewModel private lateinit var downloadViewModel : DownloadViewModel
private lateinit var ytdlpViewModel : YTDLPViewModel
private lateinit var queuedRecyclerView : RecyclerView private lateinit var queuedRecyclerView : RecyclerView
private lateinit var adapter : QueuedDownloadAdapter private lateinit var adapter : QueuedDownloadAdapter
private lateinit var noResults : RelativeLayout private lateinit var noResults : RelativeLayout
@ -80,6 +82,7 @@ class QueuedDownloadsFragment : Fragment(), QueuedDownloadAdapter.OnItemClickLis
activity = getActivity() activity = getActivity()
notificationUtil = NotificationUtil(requireContext()) notificationUtil = NotificationUtil(requireContext())
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
ytdlpViewModel = ViewModelProvider(this)[YTDLPViewModel::class.java]
return fragmentView return fragmentView
} }
@ -450,6 +453,7 @@ class QueuedDownloadsFragment : Fragment(), QueuedDownloadAdapter.OnItemClickLis
item, item,
requireActivity(), requireActivity(),
DownloadRepository.Status.valueOf(item.status), DownloadRepository.Status.valueOf(item.status),
ytdlpViewModel,
removeItem = { it: DownloadItem, sheet: BottomSheetDialog -> removeItem = { it: DownloadItem, sheet: BottomSheetDialog ->
sheet.hide() sheet.hide()
removeItem(it.id) 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.models.DownloadItem
import com.deniscerri.ytdl.database.repository.DownloadRepository import com.deniscerri.ytdl.database.repository.DownloadRepository
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel 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.GenericDownloadAdapter
import com.deniscerri.ytdl.util.Extensions.enableFastScroll import com.deniscerri.ytdl.util.Extensions.enableFastScroll
import com.deniscerri.ytdl.util.Extensions.forceFastScrollMode import com.deniscerri.ytdl.util.Extensions.forceFastScrollMode
@ -57,6 +58,7 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
private var fragmentView: View? = null private var fragmentView: View? = null
private var activity: Activity? = null private var activity: Activity? = null
private lateinit var downloadViewModel : DownloadViewModel private lateinit var downloadViewModel : DownloadViewModel
private lateinit var ytdlpViewModel : YTDLPViewModel
private lateinit var savedRecyclerView: RecyclerView private lateinit var savedRecyclerView: RecyclerView
private lateinit var adapter: GenericDownloadAdapter private lateinit var adapter: GenericDownloadAdapter
private lateinit var preferences: SharedPreferences private lateinit var preferences: SharedPreferences
@ -76,6 +78,7 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
fragmentView = inflater.inflate(R.layout.generic_list, container, false) fragmentView = inflater.inflate(R.layout.generic_list, container, false)
activity = getActivity() activity = getActivity()
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java] downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
ytdlpViewModel = ViewModelProvider(requireActivity())[YTDLPViewModel::class.java]
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
return fragmentView return fragmentView
} }
@ -183,6 +186,7 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
item, item,
requireActivity(), requireActivity(),
DownloadRepository.Status.valueOf(item.status), DownloadRepository.Status.valueOf(item.status),
ytdlpViewModel,
removeItem = { it: DownloadItem, sheet: BottomSheetDialog -> removeItem = { it: DownloadItem, sheet: BottomSheetDialog ->
removeItem(it, sheet) 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.models.DownloadItem
import com.deniscerri.ytdl.database.repository.DownloadRepository import com.deniscerri.ytdl.database.repository.DownloadRepository
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel 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.GenericDownloadAdapter
import com.deniscerri.ytdl.ui.adapter.ScheduledDownloadAdapter import com.deniscerri.ytdl.ui.adapter.ScheduledDownloadAdapter
import com.deniscerri.ytdl.util.Extensions.enableFastScroll import com.deniscerri.ytdl.util.Extensions.enableFastScroll
@ -58,6 +59,7 @@ class ScheduledDownloadsFragment : Fragment(), ScheduledDownloadAdapter.OnItemCl
private var fragmentView: View? = null private var fragmentView: View? = null
private var activity: Activity? = null private var activity: Activity? = null
private lateinit var downloadViewModel : DownloadViewModel private lateinit var downloadViewModel : DownloadViewModel
private lateinit var ytdlpViewModel : YTDLPViewModel
private lateinit var scheduledRecyclerView : RecyclerView private lateinit var scheduledRecyclerView : RecyclerView
private lateinit var preferences : SharedPreferences private lateinit var preferences : SharedPreferences
private lateinit var adapter : ScheduledDownloadAdapter private lateinit var adapter : ScheduledDownloadAdapter
@ -77,6 +79,7 @@ class ScheduledDownloadsFragment : Fragment(), ScheduledDownloadAdapter.OnItemCl
fragmentView = inflater.inflate(R.layout.generic_list, container, false) fragmentView = inflater.inflate(R.layout.generic_list, container, false)
activity = getActivity() activity = getActivity()
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
ytdlpViewModel = ViewModelProvider(this)[YTDLPViewModel::class.java]
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
return fragmentView return fragmentView
} }
@ -179,6 +182,7 @@ class ScheduledDownloadsFragment : Fragment(), ScheduledDownloadAdapter.OnItemCl
item, item,
requireActivity(), requireActivity(),
DownloadRepository.Status.valueOf(item.status), DownloadRepository.Status.valueOf(item.status),
ytdlpViewModel,
removeItem = { it: DownloadItem, sheet: BottomSheetDialog -> removeItem = { it: DownloadItem, sheet: BottomSheetDialog ->
sheet.hide() sheet.hide()
removeItem(it, sheet) 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.enableFastScroll
import com.deniscerri.ytdl.util.Extensions.enableTextHighlight import com.deniscerri.ytdl.util.Extensions.enableTextHighlight
import com.deniscerri.ytdl.util.Extensions.setCustomTextSize 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.appbar.MaterialToolbar
import com.google.android.material.bottomappbar.BottomAppBar import com.google.android.material.bottomappbar.BottomAppBar
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton 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.slider.Slider
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
class DownloadLogFragment : Fragment() { class DownloadLogFragment : Fragment() {
@ -48,6 +53,7 @@ class DownloadLogFragment : Fragment() {
private lateinit var mainActivity: MainActivity private lateinit var mainActivity: MainActivity
private lateinit var logViewModel: LogViewModel private lateinit var logViewModel: LogViewModel
private lateinit var sharedPreferences: SharedPreferences private lateinit var sharedPreferences: SharedPreferences
private var logID: Long? = null
private var autoScroll : Boolean = true private var autoScroll : Boolean = true
private var scrollDownBtn : MenuItem? = null private var scrollDownBtn : MenuItem? = null
@ -95,8 +101,8 @@ class DownloadLogFragment : Fragment() {
.show() .show()
} }
val id = arguments?.getLong("logID") logID = arguments?.getLong("logID")
if (id == null || id == 0L) { if (logID == null || logID == 0L) {
mainActivity.onBackPressedDispatcher.onBackPressed() mainActivity.onBackPressedDispatcher.onBackPressed()
} }
@ -105,7 +111,7 @@ class DownloadLogFragment : Fragment() {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
runCatching { runCatching {
val logItem = logViewModel.getItemById(id!!) val logItem = logViewModel.getItemById(logID!!)
withContext(Dispatchers.Main){ withContext(Dispatchers.Main){
topAppBar.title = logItem.title 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 { kotlin.runCatching {
requireActivity().runOnUiThread{ requireActivity().runOnUiThread{
if (logItem != null){ if (logItem != null){
@ -227,6 +233,26 @@ class DownloadLogFragment : Fragment() {
scrollDownBtn?.isVisible = canVerticallyScroll 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 { companion object {
private const val TAG = "DownloadLogActivity" private const val TAG = "DownloadLogActivity"
} }

View file

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

View file

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

View file

@ -12,6 +12,7 @@ import androidx.preference.PreferenceManager
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
import com.anggrayudi.storage.extension.count import com.anggrayudi.storage.extension.count
import com.deniscerri.ytdl.R 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.ChapterItem
import com.deniscerri.ytdl.database.models.DownloadItem import com.deniscerri.ytdl.database.models.DownloadItem
import com.deniscerri.ytdl.database.models.Format 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.database.viewmodel.ResultViewModel
import com.deniscerri.ytdl.util.Extensions.getIntByAny import com.deniscerri.ytdl.util.Extensions.getIntByAny
import com.deniscerri.ytdl.util.Extensions.getStringByAny 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.isYoutubeURL
import com.deniscerri.ytdl.util.Extensions.isYoutubeWatchVideosURL import com.deniscerri.ytdl.util.Extensions.isYoutubeWatchVideosURL
import com.deniscerri.ytdl.util.Extensions.toStringDuration import com.deniscerri.ytdl.util.Extensions.toStringDuration
@ -42,12 +44,12 @@ import java.util.Locale
import java.util.StringJoiner import java.util.StringJoiner
import java.util.UUID 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 var sharedPreferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
private val formatUtil = FormatUtil(context) private val formatUtil = FormatUtil(context)
private val handler = Handler(Looper.getMainLooper()) private val handler = Handler(Looper.getMainLooper())
private fun YoutubeDLRequest.applyDefaultOptionsForFetchingData() { private fun YoutubeDLRequest.applyDefaultOptionsForFetchingData(url: String?) {
addOption("--skip-download") addOption("--skip-download")
addOption("-R", "1") addOption("-R", "1")
addOption("--compat-options", "manifest-filesize-approx") addOption("--compat-options", "manifest-filesize-approx")
@ -80,10 +82,17 @@ class YTDLPUtil(private val context: Context) {
addOption("--no-check-certificates") addOption("--no-check-certificates")
} }
val extraCommands = sharedPreferences.getString("data_fetching_extra_commands", "")!! var extraCommands = commandTemplateDao.getAllTemplatesAsDataFetchingExtraCommands()
if (extraCommands.isNotBlank()){
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")) //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()) { if (searchEngine == "ytsearch" || query.isYoutubeURL()) {
request.setYoutubeExtractorArgs() request.setYoutubeExtractorArgs(query)
} }
request.addOption("--flat-playlist") request.addOption("--flat-playlist")
request.addOption(if (singleItem) "-J" else "-j") request.addOption(if (singleItem) "-J" else "-j")
request.applyDefaultOptionsForFetchingData() request.applyDefaultOptionsForFetchingData(if (query.isURL()) query else null)
println(parseYTDLRequestString(request)) println(parseYTDLRequestString(request))
val youtubeDLResponse = YoutubeDL.getInstance().execute(request) val youtubeDLResponse = YoutubeDL.getInstance().execute(request)
@ -249,15 +258,15 @@ class YTDLPUtil(private val context: Context) {
items.add(res) items.add(res)
} }
return items; return items
} }
fun getYoutubeWatchLater() : ArrayList<ResultItem> { fun getYoutubeWatchLater() : ArrayList<ResultItem> {
val request = YoutubeDLRequest(listOf()) val request = YoutubeDLRequest(listOf())
request.setYoutubeExtractorArgs() request.setYoutubeExtractorArgs(null)
request.addOption( "-j") request.addOption( "-j")
request.addOption("--flat-playlist") request.addOption("--flat-playlist")
request.applyDefaultOptionsForFetchingData() request.applyDefaultOptionsForFetchingData(null)
request.addOption(":ytwatchlater") request.addOption(":ytwatchlater")
val youtubeDLResponse = YoutubeDL.getInstance().execute(request) val youtubeDLResponse = YoutubeDL.getInstance().execute(request)
val results: List<String?> = try { val results: List<String?> = try {
@ -273,10 +282,10 @@ class YTDLPUtil(private val context: Context) {
fun getYoutubeRecommendations() : ArrayList<ResultItem> { fun getYoutubeRecommendations() : ArrayList<ResultItem> {
val request = YoutubeDLRequest(listOf()) val request = YoutubeDLRequest(listOf())
request.setYoutubeExtractorArgs() request.setYoutubeExtractorArgs(null)
request.addOption( "-j") request.addOption( "-j")
request.addOption("--flat-playlist") request.addOption("--flat-playlist")
request.applyDefaultOptionsForFetchingData() request.applyDefaultOptionsForFetchingData(null)
request.addOption(":ytrec") request.addOption(":ytrec")
val youtubeDLResponse = YoutubeDL.getInstance().execute(request) val youtubeDLResponse = YoutubeDL.getInstance().execute(request)
val results: List<String?> = try { val results: List<String?> = try {
@ -292,10 +301,10 @@ class YTDLPUtil(private val context: Context) {
fun getYoutubeLikedVideos() : ArrayList<ResultItem> { fun getYoutubeLikedVideos() : ArrayList<ResultItem> {
val request = YoutubeDLRequest(listOf()) val request = YoutubeDLRequest(listOf())
request.setYoutubeExtractorArgs() request.setYoutubeExtractorArgs(null)
request.addOption( "-j") request.addOption( "-j")
request.addOption("--flat-playlist") request.addOption("--flat-playlist")
request.applyDefaultOptionsForFetchingData() request.applyDefaultOptionsForFetchingData(null)
request.addOption(":ytfav") request.addOption(":ytfav")
val youtubeDLResponse = YoutubeDL.getInstance().execute(request) val youtubeDLResponse = YoutubeDL.getInstance().execute(request)
val results: List<String?> = try { val results: List<String?> = try {
@ -311,10 +320,10 @@ class YTDLPUtil(private val context: Context) {
fun getYoutubeWatchHistory() : ArrayList<ResultItem> { fun getYoutubeWatchHistory() : ArrayList<ResultItem> {
val request = YoutubeDLRequest(listOf()) val request = YoutubeDLRequest(listOf())
request.setYoutubeExtractorArgs() request.setYoutubeExtractorArgs(null)
request.addOption( "-j") request.addOption( "-j")
request.addOption("--flat-playlist") request.addOption("--flat-playlist")
request.applyDefaultOptionsForFetchingData() request.applyDefaultOptionsForFetchingData(null)
request.addOption(":ythis") request.addOption(":ythis")
val youtubeDLResponse = YoutubeDL.getInstance().execute(request) val youtubeDLResponse = YoutubeDL.getInstance().execute(request)
val results: List<String?> = try { val results: List<String?> = try {
@ -344,9 +353,9 @@ class YTDLPUtil(private val context: Context) {
val request = YoutubeDLRequest(emptyList()) val request = YoutubeDLRequest(emptyList())
request.addOption("--print", "formats") request.addOption("--print", "formats")
request.addOption("-a", urlsFile.absolutePath) request.addOption("-a", urlsFile.absolutePath)
request.applyDefaultOptionsForFetchingData() request.applyDefaultOptionsForFetchingData(urls.firstOrNull { it.isURL() })
if (urls.all { it.isYoutubeURL() }) { if (urls.all { it.isYoutubeURL() }) {
request.setYoutubeExtractorArgs() request.setYoutubeExtractorArgs(urls[0])
} }
val txt = parseYTDLRequestString(request) val txt = parseYTDLRequestString(request)
@ -416,9 +425,9 @@ class YTDLPUtil(private val context: Context) {
val request = YoutubeDLRequest(url) val request = YoutubeDLRequest(url)
request.addOption("--print", "%(formats)s") request.addOption("--print", "%(formats)s")
request.addOption("--print", "%(duration)s") request.addOption("--print", "%(duration)s")
request.applyDefaultOptionsForFetchingData() request.applyDefaultOptionsForFetchingData(url)
if (url.isYoutubeURL()) { if (url.isYoutubeURL()) {
request.setYoutubeExtractorArgs() request.setYoutubeExtractorArgs(url)
} }
val res = YoutubeDL.getInstance().execute(request) val res = YoutubeDL.getInstance().execute(request)
@ -501,9 +510,9 @@ class YTDLPUtil(private val context: Context) {
val request = YoutubeDLRequest(url) val request = YoutubeDLRequest(url)
request.addOption("--get-url") request.addOption("--get-url")
request.addOption("--print", "%(.{urls,chapters})s") request.addOption("--print", "%(.{urls,chapters})s")
request.applyDefaultOptionsForFetchingData() request.applyDefaultOptionsForFetchingData(url)
if (url.isYoutubeURL()) { if (url.isYoutubeURL()) {
request.setYoutubeExtractorArgs() request.setYoutubeExtractorArgs(url)
} }
val youtubeDLResponse = YoutubeDL.getInstance().execute(request) val youtubeDLResponse = YoutubeDL.getInstance().execute(request)
@ -752,7 +761,7 @@ class YTDLPUtil(private val context: Context) {
} }
if (downloadItem.url.isYoutubeURL()) { if (downloadItem.url.isYoutubeURL()) {
ytDlRequest.setYoutubeExtractorArgs() ytDlRequest.setYoutubeExtractorArgs(downloadItem.url)
} }
//TODO REVIEW TO ADD THIS AGAIN LATER? //TODO REVIEW TO ADD THIS AGAIN LATER?
@ -1242,7 +1251,7 @@ class YTDLPUtil(private val context: Context) {
return YoutubeDL.getInstance().execute(req).out.trim() return YoutubeDL.getInstance().execute(req).out.trim()
} }
private fun YoutubeDLRequest.setYoutubeExtractorArgs() { private fun YoutubeDLRequest.setYoutubeExtractorArgs(url: String?) {
val extractorArgs = mutableListOf<String>() val extractorArgs = mutableListOf<String>()
val playerClients = mutableListOf<String>() val playerClients = mutableListOf<String>()
val poTokens = mutableListOf<String>() val poTokens = mutableListOf<String>()
@ -1256,8 +1265,15 @@ class YTDLPUtil(private val context: Context) {
playerClients.add(value.playerClient) playerClients.add(value.playerClient)
} }
value.poTokens.forEach { pt -> var canUsePoToken = true
poTokens.add("${value.playerClient}.${pt.context}+${pt.token}") 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 notificationUtil = NotificationUtil(App.instance)
val ytdlpUtil = YTDLPUtil(context)
val dbManager = DBManager.getInstance(context) val dbManager = DBManager.getInstance(context)
val dao = dbManager.downloadDao val dao = dbManager.downloadDao
val historyDao = dbManager.historyDao val historyDao = dbManager.historyDao
val commandTemplateDao = dbManager.commandTemplateDao
val logRepo = LogRepository(dbManager.logDao) 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 handler = Handler(Looper.getMainLooper())
val alarmScheduler = AlarmScheduler(context) val alarmScheduler = AlarmScheduler(context)
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
@ -187,7 +188,7 @@ class DownloadWorker(
"Title: ${downloadItem.title}\n" + "Title: ${downloadItem.title}\n" +
"URL: ${downloadItem.url}\n" + "URL: ${downloadItem.url}\n" +
"Type: ${downloadItem.type}\n" + "Type: ${downloadItem.type}\n" +
"Command: \n $commandString \n\n" "Command:\n$commandString \n\n"
val logString = StringBuilder(initialLogDetails) val logString = StringBuilder(initialLogDetails)
val logItem = LogItem( val logItem = LogItem(
0, 0,
@ -210,7 +211,7 @@ class DownloadWorker(
runCatching { runCatching {
YoutubeDL.getInstance().destroyProcessById(downloadItem.id.toString()) YoutubeDL.getInstance().destroyProcessById(downloadItem.id.toString())
YoutubeDL.getInstance().execute(request, downloadItem.id.toString()){ progress, _, line -> 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 } val title: String = downloadItem.title.ifEmpty { downloadItem.url }
notificationUtil.updateDownloadNotification( notificationUtil.updateDownloadNotification(
downloadItem.id.toInt(), downloadItem.id.toInt(),
@ -233,7 +234,7 @@ class DownloadWorker(
var finalPaths = mutableListOf<String>() var finalPaths = mutableListOf<String>()
if (noCache){ 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") val outputSequence = it.out.split("\n")
finalPaths = finalPaths =
outputSequence.asSequence() outputSequence.asSequence()
@ -255,16 +256,16 @@ class DownloadWorker(
FileUtil.scanMedia(finalPaths, context) FileUtil.scanMedia(finalPaths, context)
}else{ }else{
//move file from internal to set download directory //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 { try {
finalPaths = withContext(Dispatchers.IO){ finalPaths = withContext(Dispatchers.IO){
FileUtil.moveFile(tempFileDir.absoluteFile,context, downloadLocation, keepCache){ p -> 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() }.filter { !it.matches("\\.(description)|(txt)\$".toRegex()) }.toMutableList()
if (finalPaths.isNotEmpty()){ 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){ }catch (e: Exception){
e.printStackTrace() e.printStackTrace()
@ -346,7 +347,7 @@ class DownloadWorker(
dao.delete(downloadItem.id) dao.delete(downloadItem.id)
if (logDownloads){ 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 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( class WorkerProgress(
val progress: Int, val progress: Int,
val output: String, 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() if (sourceID == 0L) return Result.failure()
val dbManager = DBManager.getInstance(context) val dbManager = DBManager.getInstance(context)
val repo = ObserveSourcesRepository(dbManager.observeSourcesDao) val repo = ObserveSourcesRepository(dbManager.observeSourcesDao)
val resultsRepo = ResultRepository(dbManager.resultDao, App.instance)
val historyRepo = HistoryRepository(dbManager.historyDao) val historyRepo = HistoryRepository(dbManager.historyDao)
val downloadRepo = DownloadRepository(dbManager.downloadDao) val downloadRepo = DownloadRepository(dbManager.downloadDao)
val ytdlpUtil = YTDLPUtil(context) val commandTemplateDao = dbManager.commandTemplateDao
val ytdlpUtil = YTDLPUtil(context, commandTemplateDao)
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
val item = repo.getByID(sourceID) val item = repo.getByID(sourceID)
@ -193,16 +193,9 @@ class ObserveSourceWorker(
item.runCount += 1 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() 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 item.status = ObserveSourcesRepository.SourceStatus.STOPPED
withContext(Dispatchers.IO){ withContext(Dispatchers.IO){
repo.update(item) repo.update(item)

View file

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

View file

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

View file

@ -22,7 +22,8 @@ class UpdateMultipleDownloadsFormatsWorker(
val dbManager = DBManager.getInstance(context) val dbManager = DBManager.getInstance(context)
val dao = dbManager.downloadDao val dao = dbManager.downloadDao
val resDao = dbManager.resultDao 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 vm = DownloadViewModel(App.instance)
val notificationUtil = NotificationUtil(context) val notificationUtil = NotificationUtil(context)
val ids = inputData.getLongArray("ids")!!.toMutableList() val ids = inputData.getLongArray("ids")!!.toMutableList()

View file

@ -3,6 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"> android:orientation="vertical">
@ -172,6 +173,35 @@
app:layout_constraintEnd_toEndOf="parent" /> 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 <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">

View file

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

View file

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

View file

@ -82,6 +82,13 @@
</FrameLayout> </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 <androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/coordinatorLayout3" android:id="@+id/coordinatorLayout3"
android:layout_width="0dp" android:layout_width="0dp"

View file

@ -488,34 +488,141 @@
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.materialswitch.MaterialSwitch <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/get_new_uploads"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_marginTop="20dp" android:layout_marginTop="20dp"
android:layout_height="wrap_content" android:layout_height="wrap_content">
android:checked="false"
android:text="@string/get_new_uploads"/>
<com.google.android.material.materialswitch.MaterialSwitch <TextView
android:id="@+id/sync_with_source" android:id="@+id/textView9"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:checked="false" android:textStyle="bold"
android:text="@string/sync_with_source"/> 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 <com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/retry_missing_downloads" android:id="@+id/get_new_uploads"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:checked="false" android:checked="false"
android:text="@string/retry_missing_downloads"/> app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.materialswitch.MaterialSwitch </androidx.constraintlayout.widget.ConstraintLayout>
android:id="@+id/reset_processed_links"
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_marginTop="10dp"
android:checked="false" android:layout_height="wrap_content">
android:text="@string/reset_processed_links"/>
<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> </LinearLayout>

View file

@ -170,6 +170,34 @@
</com.google.android.material.textfield.TextInputLayout> </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 <com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/use_only_po_token" android:id="@+id/use_only_po_token"
android:layout_width="match_parent" android:layout_width="match_parent"

View file

@ -357,6 +357,7 @@
<string name="week">Week</string> <string name="week">Week</string>
<string name="month">Month</string> <string name="month">Month</string>
<string name="retry_missing_downloads">Retry Missing Downloads</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="bitrate">Bitrate</string>
<string name="save_auto_subs">Save Automatic Subtitles</string> <string name="save_auto_subs">Save Automatic Subtitles</string>
<string name="not_deleted">Not Deleted</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="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="show_download_count">Show counts in the download queue screen</string>
<string name="thumbnail_format">Thumbnail Format</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="also_download_audio">Also Download as Audio</string>
<string name="wrap_text">Wrap Text</string> <string name="wrap_text">Wrap Text</string>
<string name="scroll_down">Scroll to the Bottom</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="all_queries_finished">All Queries have been processed</string>
<string name="select_between">Select Items Between</string> <string name="select_between">Select Items Between</string>
<string name="reset_processed_links">Reset Already Processed Links</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">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="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> <string name="my_filename_templates">My Filename Templates</string>
@ -405,6 +407,7 @@
<string name="label_visibility">Label Visibility</string> <string name="label_visibility">Label Visibility</string>
<string name="always">Always</string> <string name="always">Always</string>
<string name="sync_with_source">Sync With Source</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="queue">Queue</string>
<string name="reset">Reset</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> <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>