1.6.8.2-beta
This commit is contained in:
parent
a922633567
commit
559efca908
46 changed files with 1269 additions and 474 deletions
|
|
@ -9,8 +9,8 @@ plugins {
|
|||
def properties = new Properties()
|
||||
def versionMajor = 1
|
||||
def versionMinor = 6
|
||||
def versionPatch = 7
|
||||
def versionBuild = 0 // bump for dogfood builds, public betas, etc.
|
||||
def versionPatch = 8
|
||||
def versionBuild = 2 // bump for dogfood builds, public betas, etc.
|
||||
def versionExt = ""
|
||||
|
||||
if (versionBuild > 0){
|
||||
|
|
@ -136,6 +136,7 @@ dependencies {
|
|||
implementation "androidx.navigation:navigation-ui-ktx:$navVer"
|
||||
implementation 'androidx.core:core-ktx:1.10.1'
|
||||
implementation 'androidx.test.ext:junit-ktx:1.1.5'
|
||||
implementation 'androidx.compose.ui:ui-android:1.5.3'
|
||||
testImplementation "junit:junit:$junitVer"
|
||||
androidTestImplementation "junit:junit:$junitVer"
|
||||
androidTestImplementation "androidx.test.ext:junit:$androidJunitVer"
|
||||
|
|
@ -186,4 +187,6 @@ dependencies {
|
|||
|
||||
implementation "com.anggrayudi:storage:1.5.5"
|
||||
implementation 'me.zhanghai.android.fastscroll:library:1.3.0'
|
||||
implementation 'com.google.accompanist:accompanist-webview:0.30.1'
|
||||
implementation 'androidx.compose.material3:material3-android:1.2.0-alpha08'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 10,
|
||||
"identityHash": "6b5efd73749a7138035e1f00349715c8",
|
||||
"identityHash": "0647c4b527efea884e1dc26157cd4189",
|
||||
"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, `creationTime` INTEGER NOT NULL)",
|
||||
"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 DEFAULT '', `website` TEXT NOT NULL, `playlistTitle` TEXT NOT NULL, `formats` TEXT NOT NULL, `urls` TEXT NOT NULL DEFAULT '', `chapters` TEXT, `creationTime` INTEGER NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
|
|
@ -42,7 +42,8 @@
|
|||
"fieldPath": "thumb",
|
||||
"columnName": "thumb",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
"notNull": true,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "website",
|
||||
|
|
@ -93,7 +94,7 @@
|
|||
},
|
||||
{
|
||||
"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, `downloadId` INTEGER NOT NULL DEFAULT 0)",
|
||||
"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, `downloadId` INTEGER NOT NULL DEFAULT 0, `command` TEXT NOT NULL DEFAULT '')",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
|
|
@ -167,6 +168,13 @@
|
|||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "command",
|
||||
"columnName": "command",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true,
|
||||
"defaultValue": "''"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
|
|
@ -538,7 +546,7 @@
|
|||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6b5efd73749a7138035e1f00349715c8')"
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '0647c4b527efea884e1dc26157cd4189')"
|
||||
]
|
||||
}
|
||||
}
|
||||
558
app/schemas/com.deniscerri.ytdlnis.database.DBManager/11.json
Normal file
558
app/schemas/com.deniscerri.ytdlnis.database.DBManager/11.json
Normal file
|
|
@ -0,0 +1,558 @@
|
|||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 11,
|
||||
"identityHash": "09798517d2b7ce96999c879bcd381c7f",
|
||||
"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, `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": "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, `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": "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)",
|
||||
"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
|
||||
}
|
||||
],
|
||||
"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)",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"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": []
|
||||
}
|
||||
],
|
||||
"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, '09798517d2b7ce96999c879bcd381c7f')"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -194,6 +194,7 @@
|
|||
</activity>
|
||||
|
||||
<receiver android:name=".receiver.CancelDownloadNotificationReceiver" />
|
||||
<receiver android:name=".receiver.CancelWorkReceiver" />
|
||||
<receiver android:name=".receiver.PauseDownloadNotificationReceiver" />
|
||||
<receiver android:name=".receiver.AlarmStartReceiver" />
|
||||
<receiver android:name=".receiver.AlarmStopReceiver" />
|
||||
|
|
|
|||
|
|
@ -5,19 +5,7 @@ import android.os.Looper
|
|||
import android.widget.Toast
|
||||
import androidx.core.content.edit
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.work.Configuration
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.ExistingPeriodicWorkPolicy
|
||||
import androidx.work.NetworkType
|
||||
import androidx.work.PeriodicWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import com.deniscerri.ytdlnis.database.DBManager
|
||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||
import com.deniscerri.ytdlnis.util.UpdateUtil
|
||||
import com.deniscerri.ytdlnis.work.UpdateYTDLWorker
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.yausername.aria2c.Aria2c
|
||||
import com.yausername.ffmpeg.FFmpeg
|
||||
import com.yausername.youtubedl_android.YoutubeDL
|
||||
|
|
@ -27,8 +15,6 @@ import kotlinx.coroutines.Dispatchers
|
|||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.*
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
||||
class App : Application() {
|
||||
|
|
|
|||
|
|
@ -58,8 +58,8 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener
|
|||
|
||||
// THUMBNAIL ----------------------------------
|
||||
if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("home")){
|
||||
val imageURL = item!!.thumb
|
||||
if (imageURL.isNotEmpty()) {
|
||||
val imageURL = item?.thumb
|
||||
if (!imageURL.isNullOrBlank()) {
|
||||
uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) }
|
||||
} else {
|
||||
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import kotlinx.coroutines.flow.Flow
|
|||
LogItem::class,
|
||||
TerminalItem::class
|
||||
],
|
||||
version = 10,
|
||||
version = 11,
|
||||
autoMigrations = [
|
||||
AutoMigration (from = 1, to = 2),
|
||||
AutoMigration (from = 2, to = 3),
|
||||
|
|
@ -33,7 +33,8 @@ import kotlinx.coroutines.flow.Flow
|
|||
AutoMigration (from = 6, to = 7),
|
||||
AutoMigration (from = 7, to = 8),
|
||||
AutoMigration (from = 8, to = 9),
|
||||
AutoMigration (from = 9, to = 10)
|
||||
AutoMigration (from = 9, to = 10),
|
||||
AutoMigration (from = 10, to = 11)
|
||||
]
|
||||
)
|
||||
abstract class DBManager : RoomDatabase(){
|
||||
|
|
|
|||
|
|
@ -34,6 +34,9 @@ interface DownloadDao {
|
|||
@Query("SELECT * FROM downloads WHERE status='Active' or status='Queued' or status='QueuedPaused' or status='Paused'")
|
||||
fun getActiveAndQueuedDownloadsList() : List<DownloadItem>
|
||||
|
||||
@Query("SELECT id FROM downloads WHERE status='Active' or status='Queued' or status='QueuedPaused' or status='Paused'")
|
||||
fun getActiveAndQueuedDownloadIDs() : List<Long>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status='Active' or status='Queued' or status='QueuedPaused' or status='Paused'")
|
||||
fun getActiveAndQueuedDownloads() : Flow<List<DownloadItem>>
|
||||
|
||||
|
|
@ -122,8 +125,8 @@ interface DownloadDao {
|
|||
@Query("DELETE FROM downloads WHERE id in (:list)")
|
||||
suspend fun deleteAllWithIDs(list: List<Long>)
|
||||
|
||||
@Query("UPDATE downloads SET status='Cancelled' WHERE status='Queued' or status='QueuedPaused'")
|
||||
suspend fun cancelQueued()
|
||||
@Query("UPDATE downloads SET status='Cancelled' WHERE status='Queued' or status='QueuedPaused' or status='Active' or status='Paused'")
|
||||
suspend fun cancelActiveQueued()
|
||||
|
||||
@Query("DELETE FROM downloads WHERE status='Processing' AND id=:id")
|
||||
suspend fun deleteSingleProcessing(id: Long)
|
||||
|
|
|
|||
|
|
@ -35,6 +35,9 @@ interface HistoryDao {
|
|||
@Query("SELECT * FROM history WHERE id=:id LIMIT 1")
|
||||
suspend fun getHistoryItem(id: Int) : HistoryItem
|
||||
|
||||
@Query("SELECT * FROM history WHERE url=:url")
|
||||
fun getAllHistoryByURL(url: String) : List<HistoryItem>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insert(item: HistoryItem)
|
||||
|
||||
|
|
|
|||
|
|
@ -11,15 +11,38 @@ import kotlinx.coroutines.flow.Flow
|
|||
@Dao
|
||||
interface TerminalDao {
|
||||
|
||||
@Query("SELECT * FROM terminalDownloads ORDER BY id")
|
||||
@Query("SELECT id,command FROM terminalDownloads ORDER BY id")
|
||||
fun getActiveTerminalDownloads() : List<TerminalItem>
|
||||
|
||||
@Query("SELECT * FROM terminalDownloads ORDER BY id")
|
||||
@Query("SELECT id,command FROM terminalDownloads ORDER BY id")
|
||||
fun getActiveTerminalDownloadsFlow() : Flow<List<TerminalItem>>
|
||||
|
||||
@Query("SELECT * from terminalDownloads where id=:id")
|
||||
fun getActiveTerminalFlow(id: Long) : Flow<TerminalItem>
|
||||
|
||||
@Query("SELECT COUNT(*) FROM terminalDownloads")
|
||||
fun getActiveTerminalsCount() : Int
|
||||
|
||||
@Query("UPDATE terminalDownloads set log=:l where id=:id")
|
||||
fun updateTerminalLog(l: String, id: Long)
|
||||
|
||||
@Query("SELECT * FROM terminalDownloads WHERE id=:id LIMIT 1")
|
||||
fun getTerminalById(id: Long) : TerminalItem?
|
||||
@Transaction
|
||||
fun updateLog(line: String, id: Long){
|
||||
val t = getTerminalById(id) ?: return
|
||||
val log = t.log ?: ""
|
||||
val lines = log.split("\n")
|
||||
//clean dublicate progress + add newline
|
||||
val l = if (listOf(line, lines.last()).all { it.contains("[download")}){
|
||||
lines.dropLast(1).joinToString("\n")
|
||||
}else{
|
||||
log
|
||||
} + "\n${line}"
|
||||
|
||||
updateTerminalLog(l, id)
|
||||
}
|
||||
|
||||
@Insert
|
||||
suspend fun insert(item: TerminalItem) : Long
|
||||
|
||||
|
|
|
|||
|
|
@ -20,5 +20,7 @@ data class HistoryItem(
|
|||
val website: String,
|
||||
val format: Format,
|
||||
@ColumnInfo(defaultValue = "0")
|
||||
val downloadId: Long
|
||||
val downloadId: Long,
|
||||
@ColumnInfo(defaultValue = "")
|
||||
val command: String = ""
|
||||
)
|
||||
|
|
@ -9,5 +9,7 @@ import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
|||
data class TerminalItem(
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
var id: Long = 0,
|
||||
var command: String
|
||||
var command: String,
|
||||
@ColumnInfo(defaultValue = "")
|
||||
var log: String? = "",
|
||||
)
|
||||
|
|
@ -83,6 +83,10 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
|||
return downloadDao.getActiveAndQueuedDownloadsList()
|
||||
}
|
||||
|
||||
fun getActiveAndQueuedDownloadIDs() : List<Long> {
|
||||
return downloadDao.getActiveAndQueuedDownloadIDs()
|
||||
}
|
||||
|
||||
fun getQueuedDownloads() : List<DownloadItem> {
|
||||
return downloadDao.getQueuedDownloadsList()
|
||||
}
|
||||
|
|
@ -120,8 +124,8 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
|||
|
||||
}
|
||||
|
||||
suspend fun cancelQueued(){
|
||||
downloadDao.cancelQueued()
|
||||
suspend fun cancelActiveQueued(){
|
||||
downloadDao.cancelActiveQueued()
|
||||
}
|
||||
|
||||
fun pauseDownloads(){
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.deniscerri.ytdlnis.database.viewmodel
|
|||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
|
|
@ -28,6 +29,7 @@ import com.deniscerri.ytdlnis.R
|
|||
import com.deniscerri.ytdlnis.database.DBManager
|
||||
import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao
|
||||
import com.deniscerri.ytdlnis.database.dao.DownloadDao
|
||||
import com.deniscerri.ytdlnis.database.dao.HistoryDao
|
||||
import com.deniscerri.ytdlnis.database.dao.ResultDao
|
||||
import com.deniscerri.ytdlnis.database.models.AudioPreferences
|
||||
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
||||
|
|
@ -38,6 +40,7 @@ import com.deniscerri.ytdlnis.database.models.HistoryItem
|
|||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||
import com.deniscerri.ytdlnis.database.models.VideoPreferences
|
||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||
import com.deniscerri.ytdlnis.ui.ErrorDialogActivity
|
||||
import com.deniscerri.ytdlnis.util.FileUtil
|
||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||
import com.deniscerri.ytdlnis.work.AlarmScheduler
|
||||
|
|
@ -82,6 +85,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
private var videoCodec: String?
|
||||
private var audioCodec: String?
|
||||
private val dao: DownloadDao
|
||||
private val historyDao: HistoryDao
|
||||
|
||||
enum class Type {
|
||||
auto, audio, video, command
|
||||
|
|
@ -96,6 +100,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
init {
|
||||
dbManager = DBManager.getInstance(application)
|
||||
dao = dbManager.downloadDao
|
||||
historyDao = dbManager.historyDao
|
||||
repository = DownloadRepository(dao)
|
||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application)
|
||||
commandTemplateDao = DBManager.getInstance(application).commandTemplateDao
|
||||
|
|
@ -173,8 +178,20 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
return repository.getItemByID(id)
|
||||
}
|
||||
|
||||
fun getDownloadType(t: Type, url: String) : Type {
|
||||
return when(t){
|
||||
fun getDownloadType(t: Type? = null, url: String) : Type {
|
||||
var type = t
|
||||
|
||||
if (type == null){
|
||||
val preferredDownloadType = sharedPreferences.getString("preferred_download_type", Type.auto.toString())
|
||||
type = if (sharedPreferences.getBoolean("remember_download_type", false)){
|
||||
Type.valueOf(sharedPreferences.getString("last_used_download_type",
|
||||
preferredDownloadType)!!)
|
||||
}else{
|
||||
Type.valueOf(preferredDownloadType!!)
|
||||
}
|
||||
}
|
||||
|
||||
return when(type){
|
||||
Type.auto -> {
|
||||
if (urlsForAudioType.any { url.contains(it) }){
|
||||
Type.audio
|
||||
|
|
@ -182,7 +199,8 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
Type.video
|
||||
}
|
||||
}
|
||||
else -> t
|
||||
|
||||
else -> type
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -448,10 +466,11 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
|
||||
fun turnResultItemsToDownloadItems(items: List<ResultItem?>) : List<DownloadItem> {
|
||||
val list : MutableList<DownloadItem> = mutableListOf()
|
||||
var preferredType = sharedPreferences.getString("preferred_download_type", "video")
|
||||
items.forEach {
|
||||
preferredType = getDownloadType(Type.valueOf(preferredType!!), it!!.url).toString()
|
||||
list.add(createDownloadItemFromResult(result = it, givenType = Type.valueOf(preferredType!!)))
|
||||
val preferredType = getDownloadType(url = it!!.url).toString()
|
||||
list.add(createDownloadItemFromResult(result = it, givenType = Type.valueOf(
|
||||
preferredType
|
||||
)))
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
|
@ -487,12 +506,11 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
}
|
||||
|
||||
fun deleteAllWithID(ids: List<Long>) = viewModelScope.launch(Dispatchers.IO){
|
||||
Log.e("Aa", ids.size.toString())
|
||||
repository.deleteAllWithIDs(ids)
|
||||
}
|
||||
|
||||
fun cancelQueued() = viewModelScope.launch(Dispatchers.IO) {
|
||||
repository.cancelQueued()
|
||||
fun cancelActiveQueued() = viewModelScope.launch(Dispatchers.IO) {
|
||||
repository.cancelActiveQueued()
|
||||
}
|
||||
|
||||
fun getQueued() : List<DownloadItem> {
|
||||
|
|
@ -528,6 +546,10 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
return repository.getActiveAndQueuedDownloads()
|
||||
}
|
||||
|
||||
fun getActiveAndQueuedDownloadIDs() : List<Long>{
|
||||
return repository.getActiveAndQueuedDownloadIDs()
|
||||
}
|
||||
|
||||
suspend fun resetScheduleTimeForItemsAndStartDownload(items: List<Long>) = CoroutineScope(Dispatchers.IO).launch {
|
||||
dbManager.downloadDao.resetScheduleTimeForItems(items)
|
||||
startDownloadWorker(emptyList())
|
||||
|
|
@ -548,7 +570,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
val alarmScheduler = AlarmScheduler(context)
|
||||
val activeAndQueuedDownloads = repository.getActiveAndQueuedDownloads()
|
||||
val queuedItems = mutableListOf<DownloadItem>()
|
||||
var exists = false
|
||||
val existing = mutableListOf<DownloadItem>()
|
||||
|
||||
//if scheduler is on
|
||||
val useScheduler = sharedPreferences.getBoolean("use_scheduler", false)
|
||||
|
|
@ -556,8 +578,11 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
alarmScheduler.schedule()
|
||||
}
|
||||
|
||||
|
||||
items.forEach {
|
||||
if (it.status != DownloadRepository.Status.Paused.toString()) it.status = DownloadRepository.Status.Queued.toString()
|
||||
val currentCommand = infoUtil.buildYoutubeDLRequest(it)
|
||||
val parsedCurrentCommand = infoUtil.parseYTDLRequestString(currentCommand)
|
||||
if (activeAndQueuedDownloads.firstOrNull{d ->
|
||||
d.id = 0
|
||||
d.logID = null
|
||||
|
|
@ -565,23 +590,33 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
d.status = DownloadRepository.Status.Queued.toString()
|
||||
d.toString() == it.toString()
|
||||
} != null) {
|
||||
exists = true
|
||||
existing.add(it)
|
||||
}else{
|
||||
if (it.id == 0L){
|
||||
val id = runBlocking {repository.insert(it)}
|
||||
it.id = id
|
||||
}else if (it.status == DownloadRepository.Status.Queued.toString()){
|
||||
repository.update(it)
|
||||
}
|
||||
//check if downloaded and file exists
|
||||
val history = historyDao.getAllHistoryByURL(it.url).filter { item -> FileUtil.exists(item.downloadPath) }
|
||||
if (history.any { h -> h.command.replace("-P \"(.*?)\"".toRegex(), "") == parsedCurrentCommand.replace("-P \"(.*?)\"".toRegex(), "") }){
|
||||
existing.add(it)
|
||||
}else{
|
||||
if (it.id == 0L){
|
||||
val id = runBlocking {repository.insert(it)}
|
||||
it.id = id
|
||||
}else if (it.status == DownloadRepository.Status.Queued.toString()){
|
||||
repository.update(it)
|
||||
}
|
||||
|
||||
queuedItems.add(it)
|
||||
queuedItems.add(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (exists){
|
||||
Looper.prepare().run {
|
||||
Toast.makeText(context, context.getString(R.string.download_already_exists), Toast.LENGTH_LONG).show()
|
||||
}
|
||||
if (existing.isNotEmpty()){
|
||||
val intent = Intent(context, ErrorDialogActivity::class.java)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
intent.putExtra("title", context.resources.getString(R.string.download_already_exists))
|
||||
intent.putExtra("message",
|
||||
"${existing.size}/${items.size}\n" +
|
||||
existing.mapIndexed { index, downloadItem -> "${index+1}. ${downloadItem.title}" }.joinToString("\n"))
|
||||
context.startActivity(intent)
|
||||
}
|
||||
|
||||
if (!useScheduler || alarmScheduler.isDuringTheScheduledTime() || items.any { it.downloadStartTime > 0L } ){
|
||||
|
|
|
|||
|
|
@ -71,6 +71,10 @@ class TerminalViewModel(private val application: Application) : AndroidViewModel
|
|||
return dao.getActiveTerminalDownloadsFlow()
|
||||
}
|
||||
|
||||
fun getTerminal(id: Long) : Flow<TerminalItem> {
|
||||
return dao.getActiveTerminalFlow(id)
|
||||
}
|
||||
|
||||
suspend fun insert(item: TerminalItem) : Long {
|
||||
return dao.insert(item)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ class AlarmStopReceiver : BroadcastReceiver() {
|
|||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
runCatching {
|
||||
Log.e("assa", "assssssssssssssssssssssssssssss")
|
||||
val active = dbManager.downloadDao.getActiveDownloadsList()
|
||||
|
||||
val startingTime = preferences.getString("schedule_start", "00:00")!!
|
||||
|
|
|
|||
|
|
@ -16,12 +16,9 @@ import kotlinx.coroutines.runBlocking
|
|||
|
||||
class CancelWorkReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(c: Context, intent: Intent) {
|
||||
val id = intent.getStringExtra("workID")
|
||||
if (!id.isNullOrBlank()) {
|
||||
runCatching {
|
||||
WorkManager.getInstance(c).cancelAllWorkByTag(id)
|
||||
}
|
||||
|
||||
val id = intent.getStringExtra("workTag")
|
||||
if (!id.isNullOrBlank()){
|
||||
WorkManager.getInstance(c).cancelAllWorkByTag(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -108,65 +108,56 @@ class ShareActivity : BaseActivity() {
|
|||
runCatching { supportFragmentManager.popBackStack() }
|
||||
|
||||
quickDownload = intent.getBooleanExtra("quick_download", sharedPreferences.getBoolean("quick_download", false) || sharedPreferences.getString("preferred_download_type", "video") == "command")
|
||||
val intentData = when(action){
|
||||
val url = when(action){
|
||||
Intent.ACTION_SEND -> intent.getStringExtra(Intent.EXTRA_TEXT)!!
|
||||
else -> intent.dataString!!
|
||||
}
|
||||
val matcher = Patterns.WEB_URL.matcher(intentData)
|
||||
val matcher = Patterns.WEB_URL.matcher(url)
|
||||
val inputQuery = if (matcher.find()){
|
||||
matcher.group()
|
||||
}else{
|
||||
intentData
|
||||
url
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
val result = withContext(Dispatchers.IO){
|
||||
resultViewModel.getItemByURL(inputQuery)
|
||||
}
|
||||
if (result == null) throw Exception()
|
||||
showDownloadSheet(result)
|
||||
}catch (e: Exception){
|
||||
resultViewModel.deleteAll()
|
||||
showDownloadSheet(downloadViewModel.createEmptyResultItem(inputQuery))
|
||||
if (matcher.matches()){
|
||||
val type = intent.getStringExtra("TYPE")
|
||||
val background = intent.getBooleanExtra("BACKGROUND", false)
|
||||
val command = intent.getStringExtra("COMMAND") ?: ""
|
||||
|
||||
}else{
|
||||
val res = withContext(Dispatchers.IO){
|
||||
resultViewModel.parseQuery(inputQuery, true)
|
||||
}
|
||||
if (res.isEmpty()) {
|
||||
Toast.makeText(this@ShareActivity, "No Results Found!", Toast.LENGTH_SHORT).show()
|
||||
exit()
|
||||
lifecycleScope.launch {
|
||||
var result = withContext(Dispatchers.IO){
|
||||
resultViewModel.getItemByURL(inputQuery)
|
||||
}
|
||||
if (result == null) {
|
||||
resultViewModel.deleteAll()
|
||||
result = downloadViewModel.createEmptyResultItem(inputQuery)
|
||||
}
|
||||
val downloadType = DownloadViewModel.Type.valueOf(type ?: downloadViewModel.getDownloadType(url = result.url).toString())
|
||||
if (sharedPreferences.getBoolean("download_card", true) && !background){
|
||||
val bottomSheet = DownloadBottomSheetDialog(
|
||||
result = result,
|
||||
type = downloadType)
|
||||
bottomSheet.show(supportFragmentManager, "downloadSingleSheet")
|
||||
}else{
|
||||
lifecycleScope.launch(Dispatchers.IO){
|
||||
val downloadItem = downloadViewModel.createDownloadItemFromResult(
|
||||
result = result,
|
||||
givenType = downloadType)
|
||||
|
||||
if (downloadType == DownloadViewModel.Type.command && command.isNotBlank()){
|
||||
downloadItem.format.format_note = command
|
||||
}else{
|
||||
if (res.size == 1){
|
||||
showDownloadSheet(res[0]!!)
|
||||
}else{
|
||||
showSelectPlaylistItems(res.toList())
|
||||
}
|
||||
downloadItem.extraCommands = downloadItem.extraCommands + " $command"
|
||||
}
|
||||
downloadViewModel.queueDownloads(listOf(downloadItem))
|
||||
}
|
||||
this@ShareActivity.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showDownloadSheet(it: ResultItem){
|
||||
val downloadType = downloadViewModel.getDownloadType(DownloadViewModel.Type.valueOf(sharedPreferences.getString("preferred_download_type", "video")!!), it.url)
|
||||
if (sharedPreferences.getBoolean("download_card", true)){
|
||||
val bottomSheet = DownloadBottomSheetDialog(
|
||||
result = it,
|
||||
type = downloadType)
|
||||
bottomSheet.show(supportFragmentManager, "downloadSingleSheet")
|
||||
}else{
|
||||
lifecycleScope.launch(Dispatchers.IO){
|
||||
val downloadItem = downloadViewModel.createDownloadItemFromResult(
|
||||
result = it,
|
||||
givenType = downloadType)
|
||||
downloadViewModel.queueDownloads(listOf(downloadItem))
|
||||
}
|
||||
this.finish()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun showSelectPlaylistItems(it: List<ResultItem?>){
|
||||
|
|
|
|||
|
|
@ -51,7 +51,9 @@ open class ErrorDialogActivity : BaseActivity() {
|
|||
}.setOnDismissListener {
|
||||
this.finish()
|
||||
}
|
||||
errDialog.show()
|
||||
this.runOnUiThread {
|
||||
errDialog.show()
|
||||
}
|
||||
super.onCreate(savedInstanceState)
|
||||
}
|
||||
}
|
||||
|
|
@ -706,7 +706,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
|
|||
if (sharedPreferences!!.getBoolean("download_card", true) && selectedObjects!!.size == 1) {
|
||||
showSingleDownloadSheet(
|
||||
selectedObjects!![0],
|
||||
DownloadViewModel.Type.valueOf(sharedPreferences!!.getString("preferred_download_type", "video")!!)
|
||||
downloadViewModel.getDownloadType(url = selectedObjects!![0].url)
|
||||
)
|
||||
}else{
|
||||
val downloadList = withContext(Dispatchers.IO){
|
||||
|
|
@ -714,15 +714,8 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
|
|||
}
|
||||
|
||||
if (sharedPreferences!!.getBoolean("download_card", true)) {
|
||||
if (selectedObjects!!.size == 1){
|
||||
showSingleDownloadSheet(
|
||||
selectedObjects!![0],
|
||||
DownloadViewModel.Type.valueOf(sharedPreferences!!.getString("preferred_download_type", "video")!!)
|
||||
)
|
||||
}else{
|
||||
val bottomSheet = DownloadMultipleBottomSheetDialog(downloadList.toMutableList())
|
||||
bottomSheet.show(parentFragmentManager, "downloadMultipleSheet")
|
||||
}
|
||||
val bottomSheet = DownloadMultipleBottomSheetDialog(downloadList.toMutableList())
|
||||
bottomSheet.show(parentFragmentManager, "downloadMultipleSheet")
|
||||
} else {
|
||||
downloadViewModel.queueDownloads(downloadList)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.deniscerri.ytdlnis.ui.downloadcard
|
|||
import android.annotation.SuppressLint
|
||||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.LayoutInflater
|
||||
|
|
@ -10,9 +11,11 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import android.widget.Button
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.edit
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import com.deniscerri.ytdlnis.R
|
||||
|
|
@ -41,6 +44,7 @@ class ConfigureDownloadBottomSheetDialog(private var result: ResultItem, private
|
|||
private lateinit var commandTemplateDao: CommandTemplateDao
|
||||
private lateinit var behavior: BottomSheetBehavior<View>
|
||||
private lateinit var onDownloadItemUpdateListener: OnDownloadItemUpdateListener
|
||||
private lateinit var sharedPreferences : SharedPreferences
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
|
@ -48,6 +52,7 @@ class ConfigureDownloadBottomSheetDialog(private var result: ResultItem, private
|
|||
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
||||
commandTemplateDao = DBManager.getInstance(requireContext()).commandTemplateDao
|
||||
onDownloadItemUpdateListener = listener
|
||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
|
|
@ -153,6 +158,10 @@ class ConfigureDownloadBottomSheetDialog(private var result: ResultItem, private
|
|||
override fun onPageSelected(position: Int) {
|
||||
tabLayout.selectTab(tabLayout.getTabAt(position))
|
||||
runCatching {
|
||||
sharedPreferences.edit(commit = true) {
|
||||
putString("last_used_download_type",
|
||||
listOf(Type.audio, Type.video, Type.command)[position].toString())
|
||||
}
|
||||
updateTitleAuthorWhenSwitching()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ import android.widget.AdapterView
|
|||
import android.widget.ArrayAdapter
|
||||
import android.widget.AutoCompleteTextView
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.core.view.isEmpty
|
||||
import androidx.fragment.app.Fragment
|
||||
|
|
@ -140,17 +142,7 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
|
|||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
||||
|
||||
val callback = FileUtil.getURIFromResult(this@DownloadAudioFragment, requireActivity()){result ->
|
||||
downloadItem.downloadPath = result
|
||||
//downloadViewModel.updateDownload(downloadItem)
|
||||
saveDir.editText?.setText(FileUtil.formatPath(result), TextView.BufferType.EDITABLE)
|
||||
|
||||
val free = FileUtil.convertFileSize(
|
||||
File(FileUtil.formatPath(downloadItem.downloadPath)).freeSpace)
|
||||
freeSpace.text = String.format( getString(R.string.freespace) + ": " + free)
|
||||
if (free == "?") freeSpace.visibility = View.GONE
|
||||
}
|
||||
callback.launch(intent)
|
||||
pathResultLauncher.launch(intent)
|
||||
}
|
||||
freeSpace = view.findViewById(R.id.freespace)
|
||||
val free = FileUtil.convertFileSize(
|
||||
|
|
@ -296,4 +288,28 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
|
|||
state.putBoolean("updated", true)
|
||||
onViewCreated(requireView(),savedInstanceState = state)
|
||||
}
|
||||
|
||||
private var pathResultLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
result.data?.data?.let {
|
||||
activity?.contentResolver?.takePersistableUriPermission(
|
||||
it,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
)
|
||||
}
|
||||
|
||||
downloadItem.downloadPath = result.data?.data.toString()
|
||||
//downloadViewModel.updateDownload(downloadItem)
|
||||
saveDir.editText?.setText(FileUtil.formatPath(result.data?.data.toString()), TextView.BufferType.EDITABLE)
|
||||
|
||||
val free = FileUtil.convertFileSize(
|
||||
File(FileUtil.formatPath(downloadItem.downloadPath)).freeSpace)
|
||||
freeSpace.text = String.format( getString(R.string.freespace) + ": " + free)
|
||||
if (free == "?") freeSpace.visibility = View.GONE
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,15 @@
|
|||
package com.deniscerri.ytdlnis.ui.downloadcard
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.ActivityManager
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import android.os.Looper
|
||||
import android.text.format.DateFormat
|
||||
import android.util.DisplayMetrics
|
||||
import android.util.Patterns
|
||||
|
|
@ -16,6 +19,7 @@ import android.view.ViewGroup
|
|||
import android.widget.Button
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.edit
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
|
|
@ -158,6 +162,11 @@ class DownloadBottomSheetDialog(private var result: ResultItem, private val type
|
|||
}
|
||||
}
|
||||
|
||||
sharedPreferences.edit(commit = true) {
|
||||
putString("last_used_download_type",
|
||||
type.toString())
|
||||
}
|
||||
|
||||
|
||||
tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
|
||||
override fun onTabSelected(tab: TabLayout.Tab?) {
|
||||
|
|
@ -184,6 +193,10 @@ class DownloadBottomSheetDialog(private var result: ResultItem, private val type
|
|||
override fun onPageSelected(position: Int) {
|
||||
tabLayout.selectTab(tabLayout.getTabAt(position))
|
||||
runCatching {
|
||||
sharedPreferences.edit(commit = true) {
|
||||
putString("last_used_download_type",
|
||||
listOf(Type.audio, Type.video, Type.command)[position].toString())
|
||||
}
|
||||
updateTitleAuthorWhenSwitching()
|
||||
}
|
||||
}
|
||||
|
|
@ -326,13 +339,18 @@ class DownloadBottomSheetDialog(private var result: ResultItem, private val type
|
|||
|
||||
if (result.url.isBlank()){
|
||||
withContext(Dispatchers.Main){dismiss()}
|
||||
return@launch
|
||||
}
|
||||
|
||||
val result = resultViewModel.parseQueries(listOf(result.url))
|
||||
val result = resultViewModel.parseQuery(result.url, true)
|
||||
if (result.isEmpty()){
|
||||
withContext(Dispatchers.Main){
|
||||
Looper.prepare().run {
|
||||
Toast.makeText(requireContext(), "No Results Found!", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
return@launch
|
||||
}
|
||||
|
||||
if (result.size == 1 && result[0] != null){
|
||||
|
|
|
|||
|
|
@ -55,7 +55,9 @@ import com.google.android.material.color.MaterialColors
|
|||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
|
|
@ -132,7 +134,10 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
|
|||
item.downloadStartTime = it.timeInMillis
|
||||
}
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(items)
|
||||
val chunks = items.chunked(10)
|
||||
for (c in chunks) {
|
||||
downloadViewModel.queueDownloads(c)
|
||||
}
|
||||
val first = items.first()
|
||||
val date = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(first.downloadStartTime)
|
||||
Toast.makeText(context, getString(R.string.download_rescheduled_to) + " " + date, Toast.LENGTH_LONG).show()
|
||||
|
|
@ -145,7 +150,11 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
|
|||
scheduleBtn.isEnabled = false
|
||||
download.isEnabled = false
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(items)
|
||||
val chunks = items.chunked(10)
|
||||
for (c in chunks) {
|
||||
downloadViewModel.queueDownloads(c)
|
||||
}
|
||||
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
|
|
@ -206,13 +215,13 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
|
|||
}
|
||||
|
||||
|
||||
override fun onContinueOnBackground(items: List<DownloadItem?>) {
|
||||
override fun onContinueOnBackground() {
|
||||
requireActivity().lifecycleScope.launch {
|
||||
val ids = mutableListOf<Long>()
|
||||
runBlocking {
|
||||
items.map { it!!.status = DownloadRepository.Status.Saved.toString() }
|
||||
items.map { it.status = DownloadRepository.Status.Saved.toString() }
|
||||
items.forEach {
|
||||
ids.add(downloadViewModel.repository.insert(it!!))
|
||||
ids.add(downloadViewModel.repository.insert(it))
|
||||
}
|
||||
}
|
||||
val id = System.currentTimeMillis().toInt()
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import android.widget.AdapterView
|
|||
import android.widget.ArrayAdapter
|
||||
import android.widget.AutoCompleteTextView
|
||||
import android.widget.TextView
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
|
|
@ -130,16 +131,7 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
|
|||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
||||
val callback = FileUtil.getURIFromResult(this@DownloadVideoFragment, requireActivity()){ result ->
|
||||
downloadItem.downloadPath = result
|
||||
saveDir.editText?.setText(FileUtil.formatPath(result), TextView.BufferType.EDITABLE)
|
||||
|
||||
val free = FileUtil.convertFileSize(
|
||||
File(FileUtil.formatPath(downloadItem.downloadPath)).freeSpace)
|
||||
freeSpace.text = String.format( getString(R.string.freespace) + ": " + free)
|
||||
if (free == "?") freeSpace.visibility = View.GONE
|
||||
}
|
||||
callback.launch(intent)
|
||||
pathResultLauncher.launch(intent)
|
||||
}
|
||||
|
||||
freeSpace = view.findViewById(R.id.freespace)
|
||||
|
|
@ -311,5 +303,27 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
|
|||
onViewCreated(requireView(),savedInstanceState = state)
|
||||
}
|
||||
|
||||
private var pathResultLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
result.data?.data?.let {
|
||||
activity?.contentResolver?.takePersistableUriPermission(
|
||||
it,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
)
|
||||
}
|
||||
|
||||
downloadItem.downloadPath = result.data?.data.toString()
|
||||
saveDir.editText?.setText(FileUtil.formatPath(result.data?.data.toString()), TextView.BufferType.EDITABLE)
|
||||
|
||||
val free = FileUtil.convertFileSize(
|
||||
File(FileUtil.formatPath(downloadItem.downloadPath)).freeSpace)
|
||||
freeSpace.text = String.format( getString(R.string.freespace) + ": " + free)
|
||||
if (free == "?") freeSpace.visibility = View.GONE
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -130,7 +130,7 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
|||
if (items.size > 10){
|
||||
continueInBackgroundSnackBar = Snackbar.make(view, R.string.update_formats_background, Snackbar.LENGTH_LONG)
|
||||
continueInBackgroundSnackBar.setAction(R.string.ok) {
|
||||
listener.onContinueOnBackground(items)
|
||||
listener.onContinueOnBackground()
|
||||
this@FormatSelectionBottomSheetDialog.dismiss()
|
||||
}
|
||||
continueInBackgroundSnackBar.show()
|
||||
|
|
@ -369,7 +369,7 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
|||
|
||||
interface OnFormatClickListener{
|
||||
fun onFormatClick(allFormats: List<List<Format>>, item: List<FormatTuple>)
|
||||
fun onContinueOnBackground(items : List<DownloadItem?>) {}
|
||||
fun onContinueOnBackground() {}
|
||||
}
|
||||
|
||||
class FormatTuple internal constructor(
|
||||
|
|
|
|||
|
|
@ -89,7 +89,6 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
|
|||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
infoUtil = InfoUtil(requireActivity())
|
||||
notificationUtil = NotificationUtil(requireActivity().applicationContext)
|
||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
||||
downloadManager = requireContext().getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||
|
|
|
|||
|
|
@ -160,14 +160,12 @@ class DownloadQueueMainFragment : Fragment(){
|
|||
lifecycleScope.launch {
|
||||
val notificationUtil = NotificationUtil(requireContext())
|
||||
val activeAndQueued = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getActiveAndQueuedDownloads()
|
||||
downloadViewModel.getActiveAndQueuedDownloadIDs()
|
||||
}
|
||||
activeAndQueued.forEach {
|
||||
it.status = DownloadRepository.Status.Cancelled.toString()
|
||||
downloadViewModel.updateDownload(it)
|
||||
val id = it.id.toInt()
|
||||
downloadViewModel.cancelActiveQueued()
|
||||
activeAndQueued.forEach { id ->
|
||||
YoutubeDL.getInstance().destroyProcessById(id.toString())
|
||||
notificationUtil.cancelDownloadNotification(id)
|
||||
notificationUtil.cancelDownloadNotification(id.toInt())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -106,7 +106,15 @@ class CookiesFragment : Fragment(), CookieAdapter.OnItemClickListener {
|
|||
newCookie.isEnabled = useCookiesPref
|
||||
}
|
||||
|
||||
private fun initChips() {
|
||||
val new = view?.findViewById<Chip>(R.id.newCookie)
|
||||
new?.setOnClickListener {
|
||||
showDialog("")
|
||||
}
|
||||
}
|
||||
|
||||
private fun initMenu() {
|
||||
topAppBar.menu.getItem(1).isChecked = preferences.getBoolean("use_header", false)
|
||||
topAppBar.setOnMenuItemClickListener { m: MenuItem ->
|
||||
when (m.itemId) {
|
||||
R.id.delete_cookies -> {
|
||||
|
|
@ -131,18 +139,15 @@ class CookiesFragment : Fragment(), CookieAdapter.OnItemClickListener {
|
|||
cookiesViewModel.exportToClipboard()
|
||||
Snackbar.make(recyclerView, getString(R.string.copied_to_clipboard), Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
R.id.use_header -> {
|
||||
m.isChecked = !m.isChecked
|
||||
preferences.edit().putBoolean("use_header", m.isChecked).apply()
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun initChips() {
|
||||
val new = view?.findViewById<Chip>(R.id.newCookie)
|
||||
new?.setOnClickListener {
|
||||
showDialog("")
|
||||
}
|
||||
}
|
||||
|
||||
private fun showDialog(url: String){
|
||||
val builder = MaterialAlertDialogBuilder(requireContext())
|
||||
builder.setTitle(getString(R.string.cookies))
|
||||
|
|
|
|||
|
|
@ -1,18 +1,29 @@
|
|||
package com.deniscerri.ytdlnis.ui.more
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.webkit.CookieManager
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.models.CookieItem
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.CookieViewModel
|
||||
import com.deniscerri.ytdlnis.ui.BaseActivity
|
||||
import com.google.accompanist.web.AccompanistWebChromeClient
|
||||
import com.google.accompanist.web.AccompanistWebViewClient
|
||||
import com.google.accompanist.web.WebView
|
||||
import com.google.accompanist.web.rememberWebViewState
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
import com.google.android.material.button.MaterialButton
|
||||
|
|
@ -24,12 +35,14 @@ import kotlinx.coroutines.withContext
|
|||
class WebViewActivity : BaseActivity() {
|
||||
private lateinit var cookiesViewModel: CookieViewModel
|
||||
private lateinit var webView: WebView
|
||||
private lateinit var webViewCompose: ComposeView
|
||||
private lateinit var toolbar: MaterialToolbar
|
||||
private lateinit var generateBtn: MaterialButton
|
||||
private lateinit var cookieManager: CookieManager
|
||||
private lateinit var url: String
|
||||
private lateinit var cookies: String
|
||||
|
||||
private lateinit var webViewClient: AccompanistWebViewClient
|
||||
private lateinit var preferences: SharedPreferences
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
public override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
|
@ -40,13 +53,20 @@ class WebViewActivity : BaseActivity() {
|
|||
val appbar = findViewById<AppBarLayout>(R.id.webview_appbarlayout)
|
||||
toolbar = appbar.findViewById(R.id.webviewToolbar)
|
||||
generateBtn = toolbar.findViewById(R.id.generate)
|
||||
webViewCompose = findViewById(R.id.webview_compose)
|
||||
cookieManager = CookieManager.getInstance()
|
||||
cookieManager.removeAllCookies(null)
|
||||
preferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
|
||||
webView = findViewById(R.id.webview)
|
||||
webView.settings.javaScriptEnabled = true
|
||||
webView.settings.domStorageEnabled = true
|
||||
webView.settings.javaScriptCanOpenWindowsAutomatically = true
|
||||
webViewClient = object : AccompanistWebViewClient() {
|
||||
override fun onPageFinished(view: WebView?, url: String?) {
|
||||
super.onPageFinished(view, url)
|
||||
kotlin.runCatching {
|
||||
toolbar.title = view?.title ?: ""
|
||||
cookies = CookieManager.getInstance().getCookie(view?.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cookieManager = CookieManager.getInstance()
|
||||
|
||||
|
|
@ -55,21 +75,8 @@ class WebViewActivity : BaseActivity() {
|
|||
onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
|
||||
val webViewClient: WebViewClient = object : WebViewClient() {
|
||||
|
||||
override fun onPageFinished(view: WebView, url: String) {
|
||||
super.onPageFinished(view, url)
|
||||
kotlin.runCatching {
|
||||
toolbar.title = view.title
|
||||
cookies = CookieManager.getInstance().getCookie(view.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
webView.webViewClient = webViewClient
|
||||
|
||||
generateBtn.setOnClickListener {
|
||||
cookiesViewModel.getCookiesFromDB().getOrNull()?.let {
|
||||
Log.e("asas", it)
|
||||
kotlin.runCatching {
|
||||
lifecycleScope.launch {
|
||||
withContext(Dispatchers.IO){
|
||||
|
|
@ -91,8 +98,40 @@ class WebViewActivity : BaseActivity() {
|
|||
onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
|
||||
webView.loadUrl(url)
|
||||
webViewCompose.apply {
|
||||
setContent { WebViewView() }
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
@Composable
|
||||
fun WebViewView() {
|
||||
val webViewChromeClient = remember {
|
||||
object : AccompanistWebChromeClient() {
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(modifier = Modifier.fillMaxSize()) { paddingValues ->
|
||||
WebView(
|
||||
state = rememberWebViewState(url), client = webViewClient, chromeClient = webViewChromeClient,
|
||||
modifier = Modifier
|
||||
.padding(paddingValues)
|
||||
.fillMaxSize(),
|
||||
captureBackPresses = false, factory = { context ->
|
||||
WebView(context).apply {
|
||||
settings.run {
|
||||
javaScriptEnabled = true
|
||||
domStorageEnabled = true
|
||||
javaScriptCanOpenWindowsAutomatically = true
|
||||
preferences.edit().putString("useragent_header", userAgentString).apply()
|
||||
}
|
||||
cookieManager.setAcceptThirdPartyCookies(this, true)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
package com.deniscerri.ytdlnis.ui.more.settings
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.receiver.AlarmStartReceiver
|
||||
import com.deniscerri.ytdlnis.receiver.AlarmStopReceiver
|
||||
import com.deniscerri.ytdlnis.util.UiUtil
|
||||
import com.deniscerri.ytdlnis.work.AlarmScheduler
|
||||
import java.util.Calendar
|
||||
|
|
@ -21,6 +20,14 @@ class DownloadSettingsFragment : BaseSettingsFragment() {
|
|||
setPreferencesFromResource(R.xml.downloading_preferences, rootKey)
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
|
||||
val rememberDownloadType = findPreference<SwitchPreferenceCompat>("remember_download_type")
|
||||
val downloadType = findPreference<ListPreference>("preferred_download_type")
|
||||
downloadType?.isEnabled = rememberDownloadType?.isChecked == false
|
||||
rememberDownloadType?.setOnPreferenceClickListener {
|
||||
downloadType?.isEnabled = !rememberDownloadType.isChecked
|
||||
true
|
||||
}
|
||||
|
||||
val useScheduler = findPreference<SwitchPreferenceCompat>("use_scheduler")
|
||||
val scheduleStart = findPreference<Preference>("schedule_start")
|
||||
scheduleStart?.summary = preferences.getString("schedule_start", "00:00")
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import android.widget.HorizontalScrollView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.RelativeLayout
|
||||
import android.widget.TextView
|
||||
import androidx.activity.addCallback
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
|
|
@ -39,6 +40,7 @@ import kotlinx.coroutines.launch
|
|||
|
||||
class TerminalDownloadsListFragment : Fragment(), TerminalDownloadsAdapter.OnItemClickListener {
|
||||
private var topAppBar: MaterialToolbar? = null
|
||||
private lateinit var noResults: RelativeLayout
|
||||
private lateinit var terminalViewModel: TerminalViewModel
|
||||
|
||||
override fun onCreateView(
|
||||
|
|
@ -52,6 +54,7 @@ class TerminalDownloadsListFragment : Fragment(), TerminalDownloadsAdapter.OnIte
|
|||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
lifecycleScope.launch {
|
||||
noResults = view.findViewById(R.id.no_results)
|
||||
val recycler = view.findViewById<RecyclerView>(R.id.terminal_recycler)
|
||||
val adapter = TerminalDownloadsAdapter(this@TerminalDownloadsListFragment, requireActivity())
|
||||
recycler.adapter = adapter
|
||||
|
|
@ -73,9 +76,7 @@ class TerminalDownloadsListFragment : Fragment(), TerminalDownloadsAdapter.OnIte
|
|||
|
||||
terminalViewModel.getTerminals().collectLatest {
|
||||
adapter.submitList(it)
|
||||
if (it.isEmpty()){
|
||||
findNavController().navigate(R.id.terminalFragment)
|
||||
}
|
||||
noResults.visibility = if (it.isEmpty()) View.VISIBLE else View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -48,9 +48,12 @@ import com.google.android.material.appbar.MaterialToolbar
|
|||
import com.google.android.material.bottomappbar.BottomAppBar
|
||||
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
import com.yausername.youtubedl_android.YoutubeDL
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.lang.StringBuilder
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
|
||||
|
|
@ -102,11 +105,13 @@ class TerminalFragment : Fragment() {
|
|||
topAppBar!!.setNavigationOnClickListener { requireActivity().finish() }
|
||||
|
||||
input = view.findViewById(R.id.command_edittext)
|
||||
fab = view.findViewById(R.id.command_fab)
|
||||
|
||||
if (arguments?.getLong("id") != null){
|
||||
downloadID = requireArguments().getLong("id")
|
||||
if(downloadID != 0L){
|
||||
input!!.visibility = View.GONE
|
||||
showCancelFab()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -145,6 +150,7 @@ class TerminalFragment : Fragment() {
|
|||
}else{
|
||||
bottomAppBar.menu.getItem(1).icon?.alpha = 255
|
||||
}
|
||||
|
||||
}
|
||||
bottomAppBar.setOnMenuItemClickListener {
|
||||
when(it.itemId){
|
||||
|
|
@ -195,7 +201,6 @@ class TerminalFragment : Fragment() {
|
|||
output!!.setTextIsSelectable(true)
|
||||
output!!.layoutParams!!.width = LayoutParams.WRAP_CONTENT
|
||||
input!!.requestFocus()
|
||||
fab = view.findViewById(R.id.command_fab)
|
||||
fab!!.setOnClickListener {
|
||||
if (fab!!.text == getString(R.string.run_command)){
|
||||
input!!.visibility = View.GONE
|
||||
|
|
@ -205,7 +210,7 @@ class TerminalFragment : Fragment() {
|
|||
lifecycleScope.launch {
|
||||
val command = input!!.text.toString().replace("yt-dlp", "")
|
||||
downloadID = withContext(Dispatchers.IO){
|
||||
terminalViewModel.insert(TerminalItem(command = command))
|
||||
terminalViewModel.insert(TerminalItem(command = command, log = output!!.text.toString()))
|
||||
}
|
||||
terminalViewModel.startTerminalDownloadWorker(TerminalItem(downloadID, command))
|
||||
runWorkerListener()
|
||||
|
|
@ -293,6 +298,22 @@ class TerminalFragment : Fragment() {
|
|||
}
|
||||
|
||||
private fun runWorkerListener(){
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
terminalViewModel.getTerminal(downloadID).collectLatest {
|
||||
kotlin.runCatching {
|
||||
requireActivity().runOnUiThread{
|
||||
if (it != null){
|
||||
if (!it.log.isNullOrBlank()) {
|
||||
output?.text = it.log
|
||||
}
|
||||
output?.scrollTo(0, output!!.height)
|
||||
scrollView?.fullScroll(View.FOCUS_DOWN)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
WorkManager.getInstance(requireContext())
|
||||
.getWorkInfosByTagLiveData(downloadID.toString())
|
||||
|
|
@ -310,25 +331,9 @@ class TerminalFragment : Fragment() {
|
|||
}
|
||||
return@forEach
|
||||
}
|
||||
val outputData = work.progress.getString("output")
|
||||
if (!outputData.isNullOrBlank()){
|
||||
val pieces = output!!.text.split("\n").toMutableList()
|
||||
val lastLine = pieces.last()
|
||||
requireActivity().runOnUiThread{
|
||||
kotlin.runCatching {
|
||||
showCancelFab()
|
||||
if (listOf(lastLine, outputData).all { it.contains("[download]") }){
|
||||
output!!.text = pieces.dropLast(1).joinToString("\n")
|
||||
}
|
||||
output!!.append("\n$outputData")
|
||||
output!!.scrollTo(0, output!!.height)
|
||||
scrollView!!.fullScroll(View.FOCUS_DOWN)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -309,19 +309,4 @@ object FileUtil {
|
|||
return "${DecimalFormat("#,##0.#", symbols).format(s / 1024.0.pow(digitGroups.toDouble()))} ${units[digitGroups]}"
|
||||
}
|
||||
|
||||
fun getURIFromResult(f: Fragment, a: FragmentActivity?, callback: (data: String) -> Unit) = f.registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
result.data?.data?.let {
|
||||
a?.contentResolver?.takePersistableUriPermission(
|
||||
it,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
)
|
||||
}
|
||||
|
||||
callback(result.data?.data.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,49 +1,26 @@
|
|||
package com.deniscerri.ytdlnis.util
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.DownloadManager
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.Resources
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.os.Looper
|
||||
import android.text.Html
|
||||
import android.util.Log
|
||||
import android.util.Patterns
|
||||
import android.util.TypedValue
|
||||
import android.view.WindowManager
|
||||
import android.widget.Toast
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.preference.AndroidResources
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.deniscerri.ytdlnis.App
|
||||
import com.deniscerri.ytdlnis.MainActivity
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.DBManager
|
||||
import com.deniscerri.ytdlnis.database.models.ChapterItem
|
||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||
import com.deniscerri.ytdlnis.database.models.Format
|
||||
import com.deniscerri.ytdlnis.database.models.LogItem
|
||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdlnis.ui.ErrorDialogActivity
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.yausername.youtubedl_android.YoutubeDL
|
||||
import com.yausername.youtubedl_android.YoutubeDLRequest
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
|
|
@ -475,6 +452,12 @@ class InfoUtil(private val context: Context) {
|
|||
FileUtil.getCookieFile(context){
|
||||
request.addOption("--cookies", it)
|
||||
}
|
||||
|
||||
val useHeader = sharedPreferences.getBoolean("use_header", false)
|
||||
val header = sharedPreferences.getString("useragent_header", "")
|
||||
if (useHeader && !header.isNullOrBlank()){
|
||||
request.addOption("--add-header","User-Agent:${header}")
|
||||
}
|
||||
}
|
||||
|
||||
val proxy = sharedPreferences.getString("proxy", "")
|
||||
|
|
@ -482,6 +465,8 @@ class InfoUtil(private val context: Context) {
|
|||
request.addOption("--proxy", proxy)
|
||||
}
|
||||
|
||||
|
||||
|
||||
val res = YoutubeDL.getInstance().execute(request)
|
||||
val results: Array<String?> = try {
|
||||
val lineSeparator = System.getProperty("line.separator")
|
||||
|
|
@ -528,6 +513,12 @@ class InfoUtil(private val context: Context) {
|
|||
FileUtil.getCookieFile(context){
|
||||
request.addOption("--cookies", it)
|
||||
}
|
||||
|
||||
val useHeader = sharedPreferences.getBoolean("use_header", false)
|
||||
val header = sharedPreferences.getString("useragent_header", "")
|
||||
if (useHeader && !header.isNullOrBlank()){
|
||||
request.addOption("--add-header","User-Agent:${header}")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -536,6 +527,8 @@ class InfoUtil(private val context: Context) {
|
|||
request.addOption("--proxy", proxy)
|
||||
}
|
||||
|
||||
|
||||
|
||||
YoutubeDL.getInstance().execute(request){ progress, _, line ->
|
||||
try{
|
||||
if (line.isNotBlank()){
|
||||
|
|
@ -592,6 +585,12 @@ class InfoUtil(private val context: Context) {
|
|||
FileUtil.getCookieFile(context){
|
||||
request.addOption("--cookies", it)
|
||||
}
|
||||
|
||||
val useHeader = sharedPreferences.getBoolean("use_header", false)
|
||||
val header = sharedPreferences.getString("useragent_header", "")
|
||||
if (useHeader && !header.isNullOrBlank()){
|
||||
request.addOption("--add-header","User-Agent:${header}")
|
||||
}
|
||||
}
|
||||
|
||||
val proxy = sharedPreferences.getString("proxy", "")
|
||||
|
|
@ -599,6 +598,8 @@ class InfoUtil(private val context: Context) {
|
|||
request.addOption("--proxy", proxy)
|
||||
}
|
||||
|
||||
|
||||
|
||||
val youtubeDLResponse = YoutubeDL.getInstance().execute(request)
|
||||
val results: List<String?> = try {
|
||||
val lineSeparator = System.getProperty("line.separator")
|
||||
|
|
@ -759,6 +760,12 @@ class InfoUtil(private val context: Context) {
|
|||
FileUtil.getCookieFile(context){
|
||||
request.addOption("--cookies", it)
|
||||
}
|
||||
|
||||
val useHeader = sharedPreferences.getBoolean("use_header", false)
|
||||
val header = sharedPreferences.getString("useragent_header", "")
|
||||
if (useHeader && !header.isNullOrBlank()){
|
||||
request.addOption("--add-header","User-Agent:${header}")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -768,6 +775,8 @@ class InfoUtil(private val context: Context) {
|
|||
request.addOption("--proxy", proxy)
|
||||
}
|
||||
|
||||
|
||||
|
||||
val youtubeDLResponse = YoutubeDL.getInstance().execute(request)
|
||||
val jsonObject = JSONObject(youtubeDLResponse.out)
|
||||
|
||||
|
|
@ -921,6 +930,12 @@ class InfoUtil(private val context: Context) {
|
|||
FileUtil.getCookieFile(context){
|
||||
request.addOption("--cookies", it)
|
||||
}
|
||||
|
||||
val useHeader = sharedPreferences.getBoolean("use_header", false)
|
||||
val header = sharedPreferences.getString("useragent_header", "")
|
||||
if (useHeader && !header.isNullOrBlank()){
|
||||
request.addOption("--add-header","User-Agent:${header}")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -930,6 +945,8 @@ class InfoUtil(private val context: Context) {
|
|||
request.addOption("--proxy", proxy)
|
||||
}
|
||||
|
||||
|
||||
|
||||
val youtubeDLResponse = YoutubeDL.getInstance().execute(request)
|
||||
val results: Array<String?> = try {
|
||||
val lineSeparator = System.getProperty("line.separator")
|
||||
|
|
@ -958,22 +975,6 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
fun getFilePaths(request: YoutubeDLRequest) : List<String>{
|
||||
return try{
|
||||
request.addOption("--print", "filename")
|
||||
val res = YoutubeDL.getInstance().execute(request)
|
||||
val results: Array<String?> = try {
|
||||
val lineSeparator = System.getProperty("line.separator")
|
||||
res.out.split(lineSeparator!!).toTypedArray()
|
||||
} catch (e: Exception) {
|
||||
arrayOf(res.out)
|
||||
}
|
||||
results.filter { !it.isNullOrBlank() }.map { it!!.substring(0, it.lastIndexOf(".")) }.map { it.substring(it.lastIndexOf("/") + 1, it.length) }
|
||||
}catch (e: Exception){
|
||||
listOf()
|
||||
}
|
||||
}
|
||||
|
||||
fun buildYoutubeDLRequest(downloadItem: DownloadItem) : YoutubeDLRequest{
|
||||
val url = downloadItem.url
|
||||
val request = YoutubeDLRequest(url)
|
||||
|
|
@ -982,6 +983,9 @@ class InfoUtil(private val context: Context) {
|
|||
val downDir : File
|
||||
if (!sharedPreferences.getBoolean("cache_downloads", true) && File(FileUtil.formatPath(downloadItem.downloadPath)).canWrite()){
|
||||
downDir = File(FileUtil.formatPath(downloadItem.downloadPath))
|
||||
request.addOption("--print", "after_video:'%(filename)s'")
|
||||
request.addOption("--progress")
|
||||
request.addOption("-v")
|
||||
}else{
|
||||
val cacheDir = FileUtil.getCachePath(context)
|
||||
downDir = File(cacheDir, downloadItem.id.toString())
|
||||
|
|
@ -1010,6 +1014,27 @@ class InfoUtil(private val context: Context) {
|
|||
val sponsorblockURL = sharedPreferences.getString("sponsorblock_url", "")!!
|
||||
if (sponsorblockURL.isNotBlank()) request.addOption("--sponsorblock-api", sponsorblockURL)
|
||||
|
||||
if (sharedPreferences.getBoolean("restrict_filenames", true)) request.addOption("--restrict-filenames")
|
||||
if (sharedPreferences.getBoolean("use_cookies", false)){
|
||||
FileUtil.getCookieFile(context){
|
||||
request.addOption("--cookies", it)
|
||||
}
|
||||
}
|
||||
|
||||
val proxy = sharedPreferences.getString("proxy", "")
|
||||
if (proxy!!.isNotBlank()){
|
||||
request.addOption("--proxy", proxy)
|
||||
}
|
||||
|
||||
val keepCache = sharedPreferences.getBoolean("keep_cache", false)
|
||||
if(keepCache){
|
||||
request.addOption("--part")
|
||||
request.addOption("--keep-fragments")
|
||||
}
|
||||
|
||||
val preferredAudioCodec = sharedPreferences.getString("audio_codec", "")!!
|
||||
val aCodecPref = "ba[acodec~='^($preferredAudioCodec)']"
|
||||
|
||||
if(downloadItem.type != DownloadViewModel.Type.command){
|
||||
request.addOption("--trim-filenames", downDir.absolutePath.length + 120)
|
||||
|
||||
|
|
@ -1106,34 +1131,19 @@ class InfoUtil(private val context: Context) {
|
|||
downloadItem.customFileNameTemplate = downloadItem.customFileNameTemplate.replace("%(uploader)s", "%(uploader,channel)s")
|
||||
}
|
||||
|
||||
if (sharedPreferences.getBoolean("restrict_filenames", true)) {
|
||||
request.addOption("--restrict-filenames")
|
||||
}
|
||||
|
||||
if (sharedPreferences.getBoolean("use_cookies", false)){
|
||||
FileUtil.getCookieFile(context){
|
||||
request.addOption("--cookies", it)
|
||||
}
|
||||
}
|
||||
|
||||
val proxy = sharedPreferences.getString("proxy", "")
|
||||
if (proxy!!.isNotBlank()){
|
||||
request.addOption("--proxy", proxy)
|
||||
}
|
||||
|
||||
val keepCache = sharedPreferences.getBoolean("keep_cache", false)
|
||||
if(keepCache){
|
||||
request.addOption("--part")
|
||||
request.addOption("--keep-fragments")
|
||||
}
|
||||
|
||||
when(type){
|
||||
DownloadViewModel.Type.audio -> {
|
||||
val supportedContainers = context.resources.getStringArray(R.array.audio_containers)
|
||||
|
||||
var audioQualityId : String = downloadItem.format.format_id
|
||||
if (audioQualityId.isBlank() || audioQualityId == "0" || audioQualityId == context.getString(R.string.best_quality) || audioQualityId == "best") audioQualityId = ""
|
||||
else if (audioQualityId == context.getString(R.string.worst_quality) || audioQualityId == "worst") audioQualityId = "worstaudio"
|
||||
if (audioQualityId.isBlank() || listOf("0", context.getString(R.string.best_quality), "best", "").contains(audioQualityId)){
|
||||
audioQualityId = ""
|
||||
if (preferredAudioCodec.isNotBlank()){
|
||||
audioQualityId = "${aCodecPref}/bestaudio"
|
||||
}
|
||||
}else if (listOf(context.getString(R.string.worst_quality), "worst").contains(audioQualityId)){
|
||||
audioQualityId = "worstaudio"
|
||||
}
|
||||
|
||||
val ext = downloadItem.container
|
||||
if (audioQualityId.isNotBlank()) request.addOption("-f", audioQualityId)
|
||||
|
|
@ -1230,6 +1240,9 @@ class InfoUtil(private val context: Context) {
|
|||
val f = StringBuilder()
|
||||
|
||||
if(!usingGenericFormat){
|
||||
if (preferredAudioCodec.isNotBlank()){
|
||||
f.append("$videof+$aCodecPref/")
|
||||
}
|
||||
val aa = if (audiof.isNotBlank()) "+$audiof" else ""
|
||||
f.append("$videof$aa/$videof/best")
|
||||
|
||||
|
|
@ -1257,7 +1270,12 @@ class InfoUtil(private val context: Context) {
|
|||
.toString()
|
||||
.split(",")
|
||||
.filter { it.isNotEmpty() }
|
||||
.ifEmpty { listOf(audiof) }
|
||||
.ifEmpty {
|
||||
val list = mutableListOf<String>()
|
||||
if (preferredAudioCodec.isNotBlank()) list.add("ba[acodec~='^($preferredAudioCodec)']")
|
||||
list.add(audiof)
|
||||
list
|
||||
}
|
||||
|
||||
val preferredAudioFormats = if (downloadItem.videoPreferences.audioFormatIDs.isEmpty()) {
|
||||
preferredAudioFormatIDs
|
||||
|
|
|
|||
|
|
@ -7,19 +7,26 @@ import android.app.PendingIntent
|
|||
import android.app.TaskStackBuilder
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.graphics.BitmapFactory
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.DisplayMetrics
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.navigation.NavDeepLinkBuilder
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.App.Companion.instance
|
||||
import com.deniscerri.ytdlnis.receiver.CancelDownloadNotificationReceiver
|
||||
import com.deniscerri.ytdlnis.receiver.CancelWorkReceiver
|
||||
import com.deniscerri.ytdlnis.receiver.PauseDownloadNotificationReceiver
|
||||
import com.deniscerri.ytdlnis.receiver.ResumeActivity
|
||||
import com.deniscerri.ytdlnis.receiver.ShareFileActivity
|
||||
import java.io.File
|
||||
import java.util.Locale
|
||||
|
||||
|
||||
class NotificationUtil(var context: Context) {
|
||||
|
|
@ -27,6 +34,15 @@ class NotificationUtil(var context: Context) {
|
|||
private val commandDownloadNotificationBuilder: NotificationCompat.Builder = NotificationCompat.Builder(context, COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID)
|
||||
private val finishedDownloadNotificationBuilder: NotificationCompat.Builder = NotificationCompat.Builder(context, DOWNLOAD_FINISHED_CHANNEL_ID)
|
||||
private val notificationManager: NotificationManager = context.getSystemService(NotificationManager::class.java)
|
||||
private val resources: Resources
|
||||
private var sharedPreferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
|
||||
init {
|
||||
val confTmp = Configuration(context.resources.configuration)
|
||||
confTmp.setLocale(Locale(sharedPreferences.getString("app_language", "en")!!))
|
||||
val metrics = DisplayMetrics()
|
||||
resources = Resources(context.assets, metrics, confTmp)
|
||||
}
|
||||
|
||||
fun createNotificationChannel() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
|
|
@ -35,7 +51,7 @@ class NotificationUtil(var context: Context) {
|
|||
)
|
||||
|
||||
//downloading worker notification
|
||||
var name: CharSequence = context.getString(R.string.downloading)
|
||||
var name: CharSequence = resources.getString(R.string.downloading)
|
||||
var description = "WorkManager Default Notification"
|
||||
val importance = NotificationManager.IMPORTANCE_LOW
|
||||
var channel = NotificationChannel(DOWNLOAD_WORKER_CHANNEL_ID, name, importance)
|
||||
|
|
@ -43,30 +59,30 @@ class NotificationUtil(var context: Context) {
|
|||
notificationManager.createNotificationChannel(channel)
|
||||
|
||||
//gui downloads
|
||||
name = context.getString(R.string.download_notification_channel_name)
|
||||
description = context.getString(R.string.download_notification_channel_description)
|
||||
name = resources.getString(R.string.download_notification_channel_name)
|
||||
description = resources.getString(R.string.download_notification_channel_description)
|
||||
channel = NotificationChannel(DOWNLOAD_SERVICE_CHANNEL_ID, name, importance)
|
||||
channel.description = description
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
|
||||
//command downloads
|
||||
name = context.getString(R.string.command_download_notification_channel_name)
|
||||
name = resources.getString(R.string.command_download_notification_channel_name)
|
||||
description =
|
||||
context.getString(R.string.command_download_notification_channel_description)
|
||||
resources.getString(R.string.command_download_notification_channel_description)
|
||||
channel = NotificationChannel(COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID, name, importance)
|
||||
channel.description = description
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
|
||||
//finished or errored downloads
|
||||
name = context.getString(R.string.finished_download_notification_channel_name)
|
||||
name = resources.getString(R.string.finished_download_notification_channel_name)
|
||||
description =
|
||||
context.getString(R.string.finished_download_notification_channel_description)
|
||||
resources.getString(R.string.finished_download_notification_channel_description)
|
||||
channel = NotificationChannel(DOWNLOAD_FINISHED_CHANNEL_ID, name, NotificationManager.IMPORTANCE_HIGH)
|
||||
channel.description = description
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
|
||||
//misc
|
||||
name = context.getString(R.string.misc)
|
||||
name = resources.getString(R.string.misc)
|
||||
description = ""
|
||||
channel = NotificationChannel(DOWNLOAD_MISC_CHANNEL_ID, name, NotificationManager.IMPORTANCE_HIGH)
|
||||
channel.description = description
|
||||
|
|
@ -87,12 +103,12 @@ class NotificationUtil(var context: Context) {
|
|||
val notificationBuilder = getBuilder(DOWNLOAD_WORKER_CHANNEL_ID)
|
||||
|
||||
return notificationBuilder
|
||||
.setContentTitle(context.getString(R.string.downloading))
|
||||
.setContentTitle(resources.getString(R.string.downloading))
|
||||
.setOngoing(true)
|
||||
.setSmallIcon(R.drawable.ic_launcher_foreground_large)
|
||||
.setLargeIcon(
|
||||
BitmapFactory.decodeResource(
|
||||
context.resources,
|
||||
resources,
|
||||
R.drawable.ic_launcher_foreground_large
|
||||
)
|
||||
)
|
||||
|
|
@ -118,7 +134,7 @@ class NotificationUtil(var context: Context) {
|
|||
.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||
.setLargeIcon(
|
||||
BitmapFactory.decodeResource(
|
||||
context.resources,
|
||||
resources,
|
||||
android.R.drawable.stat_sys_download
|
||||
)
|
||||
)
|
||||
|
|
@ -140,7 +156,7 @@ class NotificationUtil(var context: Context) {
|
|||
.setSmallIcon(R.drawable.ic_launcher_foreground_large)
|
||||
.setLargeIcon(
|
||||
BitmapFactory.decodeResource(
|
||||
context.resources,
|
||||
resources,
|
||||
R.drawable.ic_launcher_foreground_large
|
||||
)
|
||||
)
|
||||
|
|
@ -157,7 +173,7 @@ class NotificationUtil(var context: Context) {
|
|||
PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
notificationBuilder.addAction(0, context.getString(R.string.resume), resumeNotificationPendingIntent)
|
||||
notificationBuilder.addAction(0, resources.getString(R.string.resume), resumeNotificationPendingIntent)
|
||||
notificationManager.notify(DOWNLOAD_RESUME_NOTIFICATION_ID + itemID, notificationBuilder.build())
|
||||
}
|
||||
|
||||
|
|
@ -165,11 +181,11 @@ class NotificationUtil(var context: Context) {
|
|||
val notificationBuilder = getBuilder(channel)
|
||||
|
||||
notificationBuilder
|
||||
.setContentTitle(context.getString(R.string.updating_download_data))
|
||||
.setContentTitle(resources.getString(R.string.updating_download_data))
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||
.setLargeIcon(
|
||||
BitmapFactory.decodeResource(
|
||||
context.resources,
|
||||
resources,
|
||||
android.R.drawable.stat_sys_download
|
||||
)
|
||||
)
|
||||
|
|
@ -185,11 +201,11 @@ class NotificationUtil(var context: Context) {
|
|||
val notificationBuilder = getBuilder(DOWNLOAD_FINISHED_CHANNEL_ID)
|
||||
|
||||
notificationBuilder
|
||||
.setContentTitle("${context.getString(R.string.downloaded)} $title")
|
||||
.setContentTitle("${resources.getString(R.string.downloaded)} $title")
|
||||
.setSmallIcon(R.drawable.ic_launcher_foreground_large)
|
||||
.setLargeIcon(
|
||||
BitmapFactory.decodeResource(
|
||||
context.resources,
|
||||
resources,
|
||||
R.drawable.ic_launcher_foreground_large
|
||||
)
|
||||
)
|
||||
|
|
@ -228,8 +244,8 @@ class NotificationUtil(var context: Context) {
|
|||
PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
notificationBuilder.addAction(0, context.getString(R.string.Open_File), openNotificationPendingIntent)
|
||||
notificationBuilder.addAction(0, context.getString(R.string.share), shareNotificationPendingIntent)
|
||||
notificationBuilder.addAction(0, resources.getString(R.string.Open_File), openNotificationPendingIntent)
|
||||
notificationBuilder.addAction(0, resources.getString(R.string.share), shareNotificationPendingIntent)
|
||||
}catch (_: Exception){}
|
||||
}
|
||||
notificationManager.notify(DOWNLOAD_FINISHED_NOTIFICATION_ID, notificationBuilder.build())
|
||||
|
|
@ -263,12 +279,12 @@ class NotificationUtil(var context: Context) {
|
|||
.createPendingIntent()
|
||||
|
||||
notificationBuilder
|
||||
.setContentTitle("${context.getString(R.string.failed_download)}: $title")
|
||||
.setContentTitle("${resources.getString(R.string.failed_download)}: $title")
|
||||
.setContentText(error)
|
||||
.setSmallIcon(R.drawable.ic_launcher_foreground_large)
|
||||
.setLargeIcon(
|
||||
BitmapFactory.decodeResource(
|
||||
context.resources,
|
||||
resources,
|
||||
R.drawable.ic_launcher_foreground_large
|
||||
)
|
||||
)
|
||||
|
|
@ -278,7 +294,7 @@ class NotificationUtil(var context: Context) {
|
|||
.clearActions()
|
||||
if (logID != null){
|
||||
notificationBuilder.setContentIntent(errorPendingIntent)
|
||||
notificationBuilder.addAction(0, context.getString(R.string.logs), errorPendingIntent)
|
||||
notificationBuilder.addAction(0, resources.getString(R.string.logs), errorPendingIntent)
|
||||
}
|
||||
notificationManager.notify(DOWNLOAD_FINISHED_NOTIFICATION_ID, notificationBuilder.build())
|
||||
}
|
||||
|
|
@ -299,7 +315,7 @@ class NotificationUtil(var context: Context) {
|
|||
|
||||
val notificationBuilder = getBuilder(channel)
|
||||
var contentText = ""
|
||||
if (queue > 1) contentText += """${queue - 1} ${context.getString(R.string.items_left)}""" + "\n"
|
||||
if (queue > 1) contentText += """${queue - 1} ${resources.getString(R.string.items_left)}""" + "\n"
|
||||
contentText += desc.replace("\\[.*?\\] ".toRegex(), "")
|
||||
|
||||
val pauseIntent = Intent(context, PauseDownloadNotificationReceiver::class.java)
|
||||
|
|
@ -326,8 +342,8 @@ class NotificationUtil(var context: Context) {
|
|||
.setContentTitle(title)
|
||||
.setStyle(NotificationCompat.BigTextStyle().bigText(contentText))
|
||||
.clearActions()
|
||||
.addAction(0, context.getString(R.string.pause), pauseNotificationPendingIntent)
|
||||
.addAction(0, context.getString(R.string.cancel), cancelNotificationPendingIntent)
|
||||
.addAction(0, resources.getString(R.string.pause), pauseNotificationPendingIntent)
|
||||
.addAction(0, resources.getString(R.string.cancel), cancelNotificationPendingIntent)
|
||||
notificationManager.notify(id, notificationBuilder.build())
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
|
|
@ -342,13 +358,13 @@ class NotificationUtil(var context: Context) {
|
|||
val notificationBuilder = getBuilder(downloadMiscChannelId)
|
||||
|
||||
return notificationBuilder
|
||||
.setContentTitle(context.getString(R.string.move_temporary_files))
|
||||
.setContentTitle(resources.getString(R.string.move_temporary_files))
|
||||
.setOngoing(true)
|
||||
.setCategory(Notification.CATEGORY_PROGRESS)
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||
.setLargeIcon(
|
||||
BitmapFactory.decodeResource(
|
||||
context.resources,
|
||||
resources,
|
||||
android.R.drawable.stat_sys_download
|
||||
)
|
||||
)
|
||||
|
|
@ -367,7 +383,7 @@ class NotificationUtil(var context: Context) {
|
|||
val contentText = "${progress}/${totalFiles}"
|
||||
try {
|
||||
notificationBuilder.setProgress(100, progress, false)
|
||||
.setContentTitle(context.getString(R.string.move_temporary_files))
|
||||
.setContentTitle(resources.getString(R.string.move_temporary_files))
|
||||
.setStyle(NotificationCompat.BigTextStyle().bigText(contentText))
|
||||
notificationManager.notify(id, notificationBuilder.build())
|
||||
} catch (e: Exception) {
|
||||
|
|
@ -383,7 +399,7 @@ class NotificationUtil(var context: Context) {
|
|||
.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||
.setLargeIcon(
|
||||
BitmapFactory.decodeResource(
|
||||
context.resources,
|
||||
resources,
|
||||
android.R.drawable.stat_sys_download
|
||||
)
|
||||
)
|
||||
|
|
@ -394,26 +410,16 @@ class NotificationUtil(var context: Context) {
|
|||
return notificationBuilder.build()
|
||||
}
|
||||
|
||||
fun createFormatsUpdateNotification(workID: Int): Notification {
|
||||
fun createFormatsUpdateNotification(): Notification {
|
||||
val notificationBuilder = getBuilder(DOWNLOAD_MISC_CHANNEL_ID)
|
||||
|
||||
val cancelIntent = Intent(context, CancelWorkReceiver::class.java)
|
||||
cancelIntent.putExtra("workID", workID)
|
||||
val cancelNotificationPendingIntent = PendingIntent.getBroadcast(
|
||||
context,
|
||||
workID,
|
||||
cancelIntent,
|
||||
PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
return notificationBuilder
|
||||
.setContentTitle(context.getString(R.string.update_formats_background))
|
||||
.setContentTitle(resources.getString(R.string.update_formats_background))
|
||||
.setOngoing(true)
|
||||
.setCategory(Notification.CATEGORY_PROGRESS)
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||
.setLargeIcon(
|
||||
BitmapFactory.decodeResource(
|
||||
context.resources,
|
||||
resources,
|
||||
android.R.drawable.stat_sys_download
|
||||
)
|
||||
)
|
||||
|
|
@ -423,23 +429,37 @@ class NotificationUtil(var context: Context) {
|
|||
.setProgress(PROGRESS_MAX, PROGRESS_CURR, false)
|
||||
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
|
||||
.clearActions()
|
||||
.addAction(0, context.getString(R.string.cancel), cancelNotificationPendingIntent)
|
||||
.build()
|
||||
}
|
||||
|
||||
fun updateFormatUpdateNotification(
|
||||
id: Int,
|
||||
workID: Int,
|
||||
workTag: String,
|
||||
progress: Int,
|
||||
queue: Int,
|
||||
) {
|
||||
|
||||
val notificationBuilder = getBuilder(DOWNLOAD_MISC_CHANNEL_ID)
|
||||
val contentText = """${queue - progress} ${context.getString(R.string.items_left)}"""
|
||||
val contentText = """${queue - progress} ${resources.getString(R.string.items_left)}"""
|
||||
|
||||
|
||||
val cancelIntent = Intent(context, CancelWorkReceiver::class.java)
|
||||
cancelIntent.putExtra("workTag", workTag)
|
||||
val cancelNotificationPendingIntent = PendingIntent.getBroadcast(
|
||||
context,
|
||||
workID,
|
||||
cancelIntent,
|
||||
PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
|
||||
try {
|
||||
notificationBuilder.setProgress(queue, progress, false)
|
||||
.setContentTitle(context.getString(R.string.update_formats_background))
|
||||
.setContentTitle(resources.getString(R.string.update_formats_background))
|
||||
.setStyle(NotificationCompat.BigTextStyle().bigText(contentText))
|
||||
notificationManager.notify(id, notificationBuilder.build())
|
||||
.clearActions()
|
||||
.addAction(0, resources.getString(R.string.cancel), cancelNotificationPendingIntent)
|
||||
notificationManager.notify(workID, notificationBuilder.build())
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
|
@ -460,11 +480,11 @@ class NotificationUtil(var context: Context) {
|
|||
.createPendingIntent()
|
||||
|
||||
notificationBuilder
|
||||
.setContentTitle(context.getString(R.string.finished_download_notification_channel_name))
|
||||
.setContentTitle(resources.getString(R.string.finished_download_notification_channel_name))
|
||||
.setSmallIcon(R.drawable.ic_launcher_foreground_large)
|
||||
.setLargeIcon(
|
||||
BitmapFactory.decodeResource(
|
||||
context.resources,
|
||||
resources,
|
||||
R.drawable.ic_launcher_foreground_large
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import androidx.work.WorkManager
|
|||
import androidx.work.WorkerParameters
|
||||
import androidx.work.await
|
||||
import androidx.work.workDataOf
|
||||
import com.deniscerri.ytdlnis.App
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.DBManager
|
||||
import com.deniscerri.ytdlnis.database.dao.DownloadDao
|
||||
|
|
@ -49,7 +50,7 @@ class DownloadWorker(
|
|||
override suspend fun doWork(): Result {
|
||||
if (isStopped) return Result.success()
|
||||
|
||||
val notificationUtil = NotificationUtil(context)
|
||||
val notificationUtil = NotificationUtil(App.instance)
|
||||
val infoUtil = InfoUtil(context)
|
||||
val dbManager = DBManager.getInstance(context)
|
||||
val dao = dbManager.downloadDao
|
||||
|
|
@ -119,6 +120,8 @@ class DownloadWorker(
|
|||
val keepCache = sharedPreferences.getBoolean("keep_cache", false)
|
||||
val logDownloads = sharedPreferences.getBoolean("log_downloads", false) && !sharedPreferences.getBoolean("incognito", false)
|
||||
|
||||
|
||||
val commandString = infoUtil.parseYTDLRequestString(request)
|
||||
val logItem = LogItem(
|
||||
0,
|
||||
downloadItem.title.ifEmpty { downloadItem.url },
|
||||
|
|
@ -126,7 +129,7 @@ class DownloadWorker(
|
|||
"Title: ${downloadItem.title}\n" +
|
||||
"URL: ${downloadItem.url}\n" +
|
||||
"Type: ${downloadItem.type}\n" +
|
||||
"Command:\n ${infoUtil.parseYTDLRequestString(request)}\n\n",
|
||||
"Command:\n ${commandString}\n\n",
|
||||
downloadItem.format,
|
||||
downloadItem.type,
|
||||
System.currentTimeMillis(),
|
||||
|
|
@ -142,6 +145,8 @@ class DownloadWorker(
|
|||
|
||||
}
|
||||
|
||||
val noCache = !sharedPreferences.getBoolean("cache_downloads", true) && File(FileUtil.formatPath(downloadItem.downloadPath)).canWrite()
|
||||
|
||||
runCatching {
|
||||
YoutubeDL.getInstance().execute(request, downloadItem.id.toString()){ progress, _, line ->
|
||||
setProgressAsync(workDataOf("progress" to progress.toInt(), "output" to line.chunked(5000).first().toString(), "id" to downloadItem.id))
|
||||
|
|
@ -162,16 +167,18 @@ class DownloadWorker(
|
|||
runBlocking {
|
||||
var finalPaths : List<String>?
|
||||
|
||||
//if there was no cache used
|
||||
if (!sharedPreferences.getBoolean("cache_downloads", true) && File(FileUtil.formatPath(downloadItem.downloadPath)).canWrite()){
|
||||
if (noCache){
|
||||
setProgressAsync(workDataOf("progress" to 100, "output" to "Scanning Files", "id" to downloadItem.id))
|
||||
val p = infoUtil.getFilePaths(request)
|
||||
val p = it.out.split("\r")
|
||||
.filter { it.startsWith("'/storage") }
|
||||
.map { it.removePrefix("'") }
|
||||
.map { it.removeSuffix("'\n") }
|
||||
finalPaths = File(FileUtil.formatPath(downloadLocation))
|
||||
.walkTopDown()
|
||||
.filter { it.isFile && p.contains(it.nameWithoutExtension) }
|
||||
.sortedByDescending { it.length() }
|
||||
.map { it.absolutePath }
|
||||
.toList()
|
||||
.walkTopDown()
|
||||
.filter { it.isFile && p.any { f -> f.contains(it.nameWithoutExtension) }}
|
||||
.sortedByDescending { it.length() }
|
||||
.map { it.absolutePath }
|
||||
.toList()
|
||||
FileUtil.scanMedia(finalPaths, context)
|
||||
if (finalPaths.isEmpty()){
|
||||
finalPaths = listOf(context.getString(R.string.unfound_file))
|
||||
|
|
@ -208,7 +215,7 @@ class DownloadWorker(
|
|||
val unixtime = System.currentTimeMillis() / 1000
|
||||
val file = File(finalPaths?.first()!!)
|
||||
downloadItem.format.filesize = file.length()
|
||||
val historyItem = HistoryItem(0, downloadItem.url, downloadItem.title, downloadItem.author, downloadItem.duration, downloadItem.thumb, downloadItem.type, unixtime, finalPaths.first() , downloadItem.website, downloadItem.format, downloadItem.id)
|
||||
val historyItem = HistoryItem(0, downloadItem.url, downloadItem.title, downloadItem.author, downloadItem.duration, downloadItem.thumb, downloadItem.type, unixtime, finalPaths.first() , downloadItem.website, downloadItem.format, downloadItem.id, commandString)
|
||||
historyDao.insert(historyItem)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import androidx.work.ForegroundInfo
|
|||
import androidx.work.Worker
|
||||
import androidx.work.WorkerParameters
|
||||
import androidx.work.workDataOf
|
||||
import com.deniscerri.ytdlnis.App
|
||||
import com.deniscerri.ytdlnis.MainActivity
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.DBManager
|
||||
|
|
@ -43,7 +44,7 @@ class MoveCacheFilesWorker(
|
|||
workerParams: WorkerParameters
|
||||
) : Worker(context, workerParams) {
|
||||
override fun doWork(): Result {
|
||||
val notificationUtil = NotificationUtil(context)
|
||||
val notificationUtil = NotificationUtil(App.instance)
|
||||
val id = System.currentTimeMillis().toInt()
|
||||
|
||||
val downloadFolders = File(context.cacheDir.absolutePath + "/downloads")
|
||||
|
|
|
|||
|
|
@ -72,6 +72,12 @@ class TerminalDownloadWorker(
|
|||
if (cookiesFile.exists()){
|
||||
request.addOption("--cookies", cookiesFile.absolutePath)
|
||||
}
|
||||
|
||||
val useHeader = sharedPreferences.getBoolean("use_header", false)
|
||||
val header = sharedPreferences.getString("useragent_header", "")
|
||||
if (useHeader && !header.isNullOrBlank()){
|
||||
request.addOption("--add-header","User-Agent:${header}")
|
||||
}
|
||||
}
|
||||
|
||||
request.addOption("-P", FileUtil.getDefaultCommandPath() + "/" + itemId)
|
||||
|
|
@ -83,7 +89,7 @@ class TerminalDownloadWorker(
|
|||
"Terminal Download",
|
||||
"Downloading:\n" +
|
||||
"Terminal Download\n" +
|
||||
"Command: ${infoUtil.parseYTDLRequestString(request)}\n\n",
|
||||
"Command: ${command}\n\n",
|
||||
Format(),
|
||||
DownloadViewModel.Type.command,
|
||||
System.currentTimeMillis(),
|
||||
|
|
@ -110,10 +116,9 @@ class TerminalDownloadWorker(
|
|||
line, progress.toInt(), 0, title,
|
||||
NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID
|
||||
)
|
||||
if (logDownloads){
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
logRepo.update(line, logItem.id)
|
||||
}
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
if (logDownloads) logRepo.update(line, logItem.id)
|
||||
dao.updateLog(line, itemId.toLong())
|
||||
}
|
||||
}
|
||||
}.onSuccess {
|
||||
|
|
@ -132,39 +137,30 @@ class TerminalDownloadWorker(
|
|||
}
|
||||
|
||||
return runBlocking {
|
||||
it.out.split("\n").forEach {
|
||||
setProgress(workDataOf("progress" to 100, "output" to it, "id" to itemId, "log" to logDownloads))
|
||||
Thread.sleep(50)
|
||||
}
|
||||
|
||||
if (logDownloads){
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
logRepo.update(it.out, logItem.id)
|
||||
}
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
if (logDownloads) logRepo.update(it.out, logItem.id)
|
||||
dao.updateLog(it.out, itemId.toLong())
|
||||
Thread.sleep(1000)
|
||||
dao.delete(itemId.toLong())
|
||||
}
|
||||
notificationUtil.cancelDownloadNotification(itemId)
|
||||
dao.delete(itemId.toLong())
|
||||
return@runBlocking Result.success()
|
||||
|
||||
Result.success()
|
||||
}
|
||||
}.onFailure {
|
||||
return runBlocking {
|
||||
it.message?.chunked(10000)?.forEach {
|
||||
setProgress(workDataOf("progress" to 100, "output" to it, "id" to itemId, "log" to logDownloads))
|
||||
Thread.sleep(100)
|
||||
}
|
||||
|
||||
if (logDownloads){
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
if (it.message != null){
|
||||
logRepo.update(it.message!!, logItem.id)
|
||||
}
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
if (it.message != null){
|
||||
if (logDownloads) logRepo.update(it.message!!, logItem.id)
|
||||
dao.updateLog(it.message!!, itemId.toLong())
|
||||
Thread.sleep(1000)
|
||||
dao.delete(itemId.toLong())
|
||||
}
|
||||
}
|
||||
notificationUtil.cancelDownloadNotification(itemId)
|
||||
File(FileUtil.getDefaultCommandPath() + "/" + itemId).deleteRecursively()
|
||||
Log.e(TAG, context.getString(R.string.failed_download), it)
|
||||
notificationUtil.cancelDownloadNotification(itemId)
|
||||
dao.delete(itemId.toLong())
|
||||
return@runBlocking Result.failure()
|
||||
Result.failure()
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,50 +1,23 @@
|
|||
package com.deniscerri.ytdlnis.work
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.navigation.NavDeepLinkBuilder
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.Data
|
||||
import androidx.work.ForegroundInfo
|
||||
import androidx.work.Worker
|
||||
import androidx.work.WorkerParameters
|
||||
import androidx.work.workDataOf
|
||||
import com.deniscerri.ytdlnis.App
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.Converters
|
||||
import com.deniscerri.ytdlnis.database.DBManager
|
||||
import com.deniscerri.ytdlnis.database.dao.DownloadDao
|
||||
import com.deniscerri.ytdlnis.database.dao.ResultDao
|
||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
||||
import com.deniscerri.ytdlnis.database.models.LogItem
|
||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||
import com.deniscerri.ytdlnis.database.repository.LogRepository
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdlnis.util.FileUtil
|
||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||
import com.deniscerri.ytdlnis.util.NotificationUtil.Companion.FORMAT_UPDATING_NOTIFICATION_ID
|
||||
import com.deniscerri.ytdlnis.util.UpdateUtil
|
||||
import com.yausername.youtubedl_android.YoutubeDL
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.io.File
|
||||
|
||||
|
||||
class UpdatePlaylistFormatsWorker(
|
||||
private val context: Context,
|
||||
workerParams: WorkerParameters
|
||||
) : CoroutineWorker(context, workerParams) {
|
||||
override suspend fun doWork(): Result {
|
||||
) : Worker(context, workerParams) {
|
||||
override fun doWork(): Result {
|
||||
val dbManager = DBManager.getInstance(context)
|
||||
val dao = dbManager.downloadDao
|
||||
val resDao = dbManager.resultDao
|
||||
|
|
@ -55,47 +28,59 @@ class UpdatePlaylistFormatsWorker(
|
|||
val workID = inputData.getInt("id", 0)
|
||||
if (workID == 0) return Result.failure()
|
||||
|
||||
val notification = notificationUtil.createFormatsUpdateNotification(workID)
|
||||
val foregroundInfo = ForegroundInfo(FORMAT_UPDATING_NOTIFICATION_ID, notification)
|
||||
setForeground(foregroundInfo)
|
||||
val notification = notificationUtil.createFormatsUpdateNotification()
|
||||
val foregroundInfo = ForegroundInfo(workID, notification)
|
||||
setForegroundAsync(foregroundInfo)
|
||||
|
||||
var count = 0
|
||||
ids.forEach {
|
||||
if (!isStopped){
|
||||
val d = dao.getDownloadById(it)
|
||||
val r = resDao.getResultByURL(d.url)
|
||||
|
||||
if (d.allFormats.isNotEmpty()){
|
||||
return try{
|
||||
ids.forEach {
|
||||
if (!isStopped){
|
||||
val d = dao.getDownloadById(it)
|
||||
val r = resDao.getResultByURL(d.url)
|
||||
|
||||
if (d.allFormats.isNotEmpty()){
|
||||
count++
|
||||
return@forEach
|
||||
}
|
||||
|
||||
runCatching {
|
||||
d.allFormats.clear()
|
||||
d.allFormats.addAll(infoUtil.getFormats(d.url))
|
||||
d.format = vm.getFormat(d.allFormats,d.type)
|
||||
|
||||
r.formats.clear()
|
||||
r.formats.addAll(d.allFormats)
|
||||
|
||||
runBlocking {
|
||||
resDao.update(r)
|
||||
dao.delete(d.id)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
count++
|
||||
return@forEach
|
||||
notificationUtil.updateFormatUpdateNotification(workID, UpdatePlaylistFormatsWorker::class.java.name, count, ids.size)
|
||||
}else{
|
||||
throw Exception()
|
||||
}
|
||||
}
|
||||
|
||||
runCatching {
|
||||
d.allFormats.clear()
|
||||
d.allFormats.addAll(infoUtil.getFormats(d.url))
|
||||
d.format = vm.getFormat(d.allFormats,d.type)
|
||||
|
||||
r.formats.clear()
|
||||
r.formats.addAll(d.allFormats)
|
||||
|
||||
dao.update(d)
|
||||
resDao.update(r)
|
||||
}
|
||||
|
||||
|
||||
count++
|
||||
notificationUtil.updateFormatUpdateNotification(FORMAT_UPDATING_NOTIFICATION_ID, count, ids.size)
|
||||
}else{
|
||||
notificationUtil.cancelDownloadNotification(FORMAT_UPDATING_NOTIFICATION_ID)
|
||||
Result.success()
|
||||
}catch (e: Exception){
|
||||
notificationUtil.cancelDownloadNotification(workID)
|
||||
ids.clear()
|
||||
Result.failure()
|
||||
}finally {
|
||||
if (ids.isNotEmpty()){
|
||||
notificationUtil.showFormatsUpdatedNotification(ids)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
runBlocking {
|
||||
notificationUtil.showFormatsUpdatedNotification(ids)
|
||||
}
|
||||
|
||||
return Result.success()
|
||||
override fun onStopped() {
|
||||
Log.e("asd", "Asd")
|
||||
super.onStopped()
|
||||
}
|
||||
}
|
||||
|
|
@ -18,4 +18,7 @@
|
|||
|
||||
</androidx.recyclerview.widget.RecyclerView>
|
||||
|
||||
<include layout="@layout/history_no_results"
|
||||
android:visibility="gone" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -43,14 +43,16 @@
|
|||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/rl"
|
||||
android:layout_width="match_parent"
|
||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<WebView
|
||||
android:id="@+id/webview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
<androidx.compose.ui.platform.ComposeView
|
||||
android:id="@+id/webview_compose"
|
||||
tools:composableName="com.deniscerri.ytdlnis.ui.more.WebViewActivity.WebViewView"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="match_parent"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,12 @@
|
|||
android:title="@string/delete_all_cookies"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/use_header"
|
||||
android:title="@string/user_agent_header"
|
||||
android:checkable="true"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/import_clipboard"
|
||||
android:title="@string/import_from_clipboard"
|
||||
|
|
|
|||
|
|
@ -307,4 +307,7 @@
|
|||
<string name="preferred_audio_codec">Preferred Audio Codec</string>
|
||||
<string name="loading">Loading</string>
|
||||
<string name="sponsorblock_api_url">Sponsorblock API URL</string>
|
||||
<string name="remember_download_type">Remember Download Type</string>
|
||||
<string name="remember_download_type_summary">Select the last opened download type for the current item</string>
|
||||
<string name="user_agent_header">User-Agent header</string>
|
||||
</resources>
|
||||
|
|
@ -53,6 +53,19 @@
|
|||
app:summary="@string/preferred_download_type_summary"
|
||||
app:title="@string/preferred_download_type" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="false"
|
||||
android:icon="@drawable/ic_download_type"
|
||||
android:key="remember_download_type"
|
||||
app:summary="@string/remember_download_type_summary"
|
||||
app:title="@string/remember_download_type" />
|
||||
|
||||
<Preference
|
||||
android:defaultValue="video"
|
||||
android:key="last_used_download_type"
|
||||
app:isPreferenceVisible="false" />
|
||||
|
||||
<SeekBarPreference
|
||||
android:defaultValue="3"
|
||||
android:icon="@drawable/ic_lines"
|
||||
|
|
@ -130,5 +143,15 @@
|
|||
/>
|
||||
</PreferenceCategory>
|
||||
|
||||
<Preference
|
||||
app:defaultValue=""
|
||||
android:key="use_header"
|
||||
app:isPreferenceVisible="false" />
|
||||
|
||||
<Preference
|
||||
app:defaultValue=""
|
||||
android:key="useragent_header"
|
||||
app:isPreferenceVisible="false" />
|
||||
|
||||
|
||||
</PreferenceScreen>
|
||||
|
|
@ -1,38 +1,49 @@
|
|||
download audio/video files from more than 1000 websites
|
||||
process playlists
|
||||
edit every playlist item separately just like in a normal download item.
|
||||
select a common format for all items and/or select multiple audio formats in case you are downloading them as a video
|
||||
select a download path for all items
|
||||
select a filename template for all items
|
||||
batch update download type to audio/video/custom command in one click
|
||||
queue downloads and schedule them by date and time
|
||||
you can also schedule multiple items at the same time
|
||||
download multiple items at the same time
|
||||
use custom commands and templates or go full yt-dlp mode with a built in terminal
|
||||
you can backup and restore templates, so you can share with your buddies
|
||||
COOKIES support. Log in with your accounts and download private/unavailable videos, unlock premium formats etc.
|
||||
cut videos based on timestamps and video chapters (This yt-dlp feature is experimental on the original project)
|
||||
you can make unlimited cuts
|
||||
remove sponsorblock elements from the item
|
||||
embed them as a chapters in your video
|
||||
embed subtitles/metadata/chapters etc
|
||||
modify metadata such as title and author
|
||||
split item into separate files depending on its chapters
|
||||
select different download formats
|
||||
bottom card right from the share menu, no need to open the app
|
||||
you can create a txt file and fill it with links/playlists/search queries separate by a new line and the app will process them
|
||||
search or insert a link from the app
|
||||
you can stack searches so you can process them at the same time
|
||||
log downloads in case of problems
|
||||
re-download cancelled or failed downloads
|
||||
you can use gestures to swipe left to redownload and right to delete
|
||||
you can long click the redownload button in the details sheet to show the download card for more functionality
|
||||
incognito mode when you don't want to save a download history or logs
|
||||
quick download mode
|
||||
download immediately without having to wait for data to process. Turn off the bottom card and it will instantly start
|
||||
open / share downloaded files right from the finished notification
|
||||
most yt-dlp features are implemented, suggestions are welcome
|
||||
Material You interface
|
||||
Theming options
|
||||
Backup and restore features. (Almost everything can be backed up)
|
||||
MVVM Architecture w/ WorkManager
|
||||
<b>YTDLnis</b> lets you download audio/video files from more than 1000 websites, and offers a lot of features. For example:
|
||||
|
||||
<b>process playlists</b>
|
||||
|
||||
- edit every playlist item separately just like in a normal download item.
|
||||
- select a common format for all items and/or select multiple audio formats in case you are downloading them as a video
|
||||
- select a download path for all items
|
||||
- select a filename template for all items
|
||||
- batch update download type to audio/video/custom command in one click
|
||||
- queue downloads and schedule them by date and time
|
||||
- you can also schedule multiple items at the same time
|
||||
- download multiple items at the same time
|
||||
- use custom commands and templates or go full yt-dlp mode with a built in terminal
|
||||
- you can backup and restore templates, so you can share with your buddies
|
||||
|
||||
<b>COOKIES support</b>
|
||||
|
||||
- Log in with your accounts and download private/unavailable videos, unlock premium formats etc.
|
||||
|
||||
<b>Different ways of modifying the file</b>
|
||||
|
||||
- cut videos based on timestamps and video chapters (This yt-dlp feature is experimental on the original project)
|
||||
- you can make unlimited cuts
|
||||
- remove sponsorblock elements from the item
|
||||
- embed them as a chapters in your video
|
||||
- embed subtitles/metadata/chapters etc
|
||||
- modify metadata such as title and author
|
||||
- split item into separate files depending on its chapters
|
||||
- select different download formats
|
||||
|
||||
<b>Other features</b>
|
||||
|
||||
- bottom card right from the share menu, no need to open the app
|
||||
- you can create a txt file and fill it with links/playlists/search queries separate by a new line and the app will process them
|
||||
- search or insert a link from the app
|
||||
- you can stack searches so you can process them at the same time
|
||||
- log downloads in case of problems
|
||||
- re-download cancelled or failed downloads
|
||||
- you can use gestures to swipe left to redownload and right to delete
|
||||
- you can long click the redownload button in the details sheet to show the download card for more functionality
|
||||
- incognito mode when you don't want to save a download history or logs
|
||||
- quick download mode
|
||||
- download immediately without having to wait for data to process. Turn off the bottom card and it will instantly start
|
||||
- open / share downloaded files right from the finished notification
|
||||
- most yt-dlp features are implemented, suggestions are welcome
|
||||
- Material You interface
|
||||
- Theming options
|
||||
- Backup and restore features. (Almost everything can be backed up)
|
||||
- MVVM Architecture w/ WorkManager
|
||||
Loading…
Reference in a new issue