few changes

-made the download logs not freeze the app
-download logs will work on quick downloaded items that were later updated
-added app arch and build type in the about section
-added "force-keyframes-at-cuts" preference
This commit is contained in:
deniscerri 2023-07-10 22:30:59 +02:00
parent 1783b40608
commit d659686e01
No known key found for this signature in database
GPG key ID: 95C43D517D830350
26 changed files with 869 additions and 280 deletions

View file

@ -10,11 +10,11 @@ def properties = new Properties()
def versionMajor = 1
def versionMinor = 6
def versionPatch = 3
def versionBuild = 2 // bump for dogfood builds, public betas, etc.
def versionBuild = 4 // bump for dogfood builds, public betas, etc.
def versionExt = ""
if (versionBuild > 0){
versionExt = ".${versionBuild}-beta"
versionExt = ".${versionBuild}"
}
android {
@ -72,7 +72,8 @@ android {
beta {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
debuggable false
signingConfig signingConfigs.debug
}

View file

@ -0,0 +1,486 @@
{
"formatVersion": 1,
"database": {
"version": 8,
"identityHash": "b21f2aa737d29eef2c1bf6418b314726",
"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)",
"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"
}
],
"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, `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": "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)",
"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
}
],
"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)",
"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
}
],
"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, 'b21f2aa737d29eef2c1bf6418b314726')"
]
}
}

View file

@ -84,6 +84,7 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
if (item.author.isNotEmpty()) info += ""
info += item.duration
}
author.text = info
val type = card.findViewById<MaterialButton>(R.id.download_type)
when(item.type){
@ -93,7 +94,7 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
}
val formatDetailsChip = card.findViewById<Chip>(R.id.format_note)
val formatDetailsText = StringBuilder(item.format.format_note.uppercase()+"\t")
val formatDetailsText = StringBuilder(item.format.format_note.uppercase())
val codecText =
if (item.format.encoding != "") {
@ -104,11 +105,11 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
item.format.acodec.uppercase()
}
if (codecText != "" && codecText != "none"){
formatDetailsText.append("\t|\t$codecText")
formatDetailsText.append(" \t\t $codecText")
}
val fileSize = FileUtil.convertFileSize(item.format.filesize)
if (fileSize != "?") formatDetailsText.append("\t|\t$fileSize")
if (fileSize != "?") formatDetailsText.append(" \t\t $fileSize")
formatDetailsChip.text = formatDetailsText

View file

@ -12,9 +12,10 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.LogItem
import java.io.File
class DownloadLogsAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<File?, DownloadLogsAdapter.ViewHolder>(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) {
class DownloadLogsAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<LogItem?, DownloadLogsAdapter.ViewHolder>(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) {
private val onItemClickListener: OnItemClickListener
private val activity: Activity
@ -42,7 +43,7 @@ class DownloadLogsAdapter(onItemClickListener: OnItemClickListener, activity: Ac
val card = holder.item
val title = card.findViewById<TextView>(R.id.title)
title.text = item?.name
title.text = item?.title
val delete = card.findViewById<Button>(R.id.delete)
@ -56,18 +57,18 @@ class DownloadLogsAdapter(onItemClickListener: OnItemClickListener, activity: Ac
}
interface OnItemClickListener {
fun onItemClick(file: File)
fun onDeleteClick(file: File)
fun onItemClick(item: LogItem)
fun onDeleteClick(item: LogItem)
}
companion object {
private val DIFF_CALLBACK: DiffUtil.ItemCallback<File> = object : DiffUtil.ItemCallback<File>() {
override fun areItemsTheSame(oldItem: File, newItem: File): Boolean {
return oldItem == newItem
private val DIFF_CALLBACK: DiffUtil.ItemCallback<LogItem> = object : DiffUtil.ItemCallback<LogItem>() {
override fun areItemsTheSame(oldItem: LogItem, newItem: LogItem): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: File, newItem: File): Boolean {
return oldItem.absolutePath == newItem.absolutePath
override fun areContentsTheSame(oldItem: LogItem, newItem: LogItem): Boolean {
return oldItem.content == newItem.content
}
}
}

View file

@ -105,8 +105,7 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
DownloadRepository.Status.Queued.toString() -> actionButton.setIconResource(R.drawable.ic_baseline_delete_outline_24)
else -> {
actionButton.setIconResource(R.drawable.ic_baseline_file_open_24)
val logFile = FileUtil.checkLogFileExists(activity, item)
if (logFile == null){
if (item.logID == null){
actionButton.visibility = View.GONE
}
}

View file

@ -7,15 +7,16 @@ import com.deniscerri.ytdlnis.database.models.*
@TypeConverters(Converters::class)
@Database(
entities = [ResultItem::class, HistoryItem::class, DownloadItem::class, CommandTemplate::class, SearchHistoryItem::class, TemplateShortcut::class, CookieItem::class],
version = 7,
entities = [ResultItem::class, HistoryItem::class, DownloadItem::class, CommandTemplate::class, SearchHistoryItem::class, TemplateShortcut::class, CookieItem::class, LogItem::class],
version = 8,
autoMigrations = [
AutoMigration (from = 1, to = 2),
AutoMigration (from = 2, to = 3),
AutoMigration (from = 3, to = 4),
AutoMigration (from = 4, to = 5),
AutoMigration (from = 5, to = 6),
AutoMigration (from = 6, to = 7)
AutoMigration (from = 6, to = 7),
AutoMigration (from = 7, to = 8)
]
)
abstract class DBManager : RoomDatabase(){
@ -25,6 +26,7 @@ abstract class DBManager : RoomDatabase(){
abstract val commandTemplateDao : CommandTemplateDao
abstract val searchHistoryDao: SearchHistoryDao
abstract val cookieDao: CookieDao
abstract val logDao: LogDao
companion object {
//prevents multiple instances of db getting created at the same time

View file

@ -0,0 +1,30 @@
package com.deniscerri.ytdlnis.database.dao
import androidx.room.*
import com.deniscerri.ytdlnis.database.models.CookieItem
import com.deniscerri.ytdlnis.database.models.LogItem
import kotlinx.coroutines.flow.Flow
@Dao
interface LogDao {
@Query("SELECT * FROM logs ORDER BY id DESC")
fun getAllLogs() : List<LogItem>
@Query("SELECT * FROM logs ORDER BY id DESC")
fun getAllLogsFlow() : Flow<List<LogItem>>
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(item: LogItem) : Long
@Query("DELETE FROM logs")
suspend fun deleteAll()
@Query("DELETE FROM logs WHERE id=:itemId")
suspend fun delete(itemId: Long)
@Update(onConflict = OnConflictStrategy.REPLACE)
suspend fun update(item: LogItem)
@Query("SELECT * FROM logs WHERE id=:id LIMIT 1")
fun getByID(id: Long) : LogItem
}

View file

@ -32,5 +32,6 @@ data class DownloadItem(
@ColumnInfo(defaultValue = "Queued")
var status: String,
@ColumnInfo(defaultValue = "0")
var downloadStartTime: Long
var downloadStartTime: Long,
var logID: Long?
)

View file

@ -0,0 +1,12 @@
package com.deniscerri.ytdlnis.database.models
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "logs")
data class LogItem(
@PrimaryKey(autoGenerate = true)
var id: Long,
var title: String,
var content: String
)

View file

@ -0,0 +1,39 @@
package com.deniscerri.ytdlnis.database.repository
import com.deniscerri.ytdlnis.database.dao.LogDao
import com.deniscerri.ytdlnis.database.models.CookieItem
import com.deniscerri.ytdlnis.database.models.LogItem
import kotlinx.coroutines.flow.Flow
class LogRepository(private val logDao: LogDao) {
val items : Flow<List<LogItem>> = logDao.getAllLogsFlow()
fun getAll() : List<LogItem> {
return logDao.getAllLogs()
}
suspend fun insert(item: LogItem) : Long{
return logDao.insert(item)
}
suspend fun delete(item: LogItem){
logDao.delete(item.id)
}
suspend fun deleteAll(){
logDao.deleteAll()
}
fun getItem(id: Long) : LogItem{
return logDao.getByID(id);
}
suspend fun update(newLine: String, id: Long){
val item = getItem(id)
item.content += "${newLine}\n"
logDao.update(item)
}
}

View file

@ -180,7 +180,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
sharedPreferences.getString("video_format", "Default")!!,
"",
resultItem.formats,
downloadPath!!, resultItem.website, "", resultItem.playlistTitle, audioPreferences, videoPreferences,customFileNameTemplate!!, saveThumb, DownloadRepository.Status.Processing.toString(), 0
downloadPath!!, resultItem.website, "", resultItem.playlistTitle, audioPreferences, videoPreferences,customFileNameTemplate!!, saveThumb, DownloadRepository.Status.Processing.toString(), 0, null
)
}
@ -302,7 +302,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
container,
"",
ArrayList(),
path, historyItem.website, "", "", audioPreferences, videoPreferences,customFileNameTemplate!!, saveThumb, DownloadRepository.Status.Processing.toString(), 0
path, historyItem.website, "", "", audioPreferences, videoPreferences,customFileNameTemplate!!, saveThumb, DownloadRepository.Status.Processing.toString(), 0, null
)
}

View file

@ -0,0 +1,50 @@
package com.deniscerri.ytdlnis.database.viewmodel
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import com.deniscerri.ytdlnis.database.DBManager
import com.deniscerri.ytdlnis.database.models.LogItem
import com.deniscerri.ytdlnis.database.repository.LogRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class LogViewModel(private val application: Application) : AndroidViewModel(application) {
private val repository: LogRepository
val items: LiveData<List<LogItem>>
init {
val dao = DBManager.getInstance(application).logDao
repository = LogRepository(dao)
items = repository.items.asLiveData()
}
fun getItemById(id: Long): LogItem{
return repository.getItem(id)
}
fun getAll(): List<LogItem> {
return repository.getAll()
}
suspend fun insert(item: LogItem) : Long {
return repository.insert(item)
}
fun delete(item: LogItem) = viewModelScope.launch(Dispatchers.IO) {
repository.delete(item)
}
fun deleteAll() = viewModelScope.launch(Dispatchers.IO) {
repository.deleteAll()
}
fun update(newLine: String, id: Long) = viewModelScope.launch(Dispatchers.IO) {
repository.update(newLine, id)
}
}

View file

@ -18,8 +18,6 @@ import com.deniscerri.ytdlnis.adapter.ActiveDownloadAdapter
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.databinding.FragmentHomeBinding
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.NotificationUtil
import com.google.android.material.progressindicator.LinearProgressIndicator
import com.yausername.youtubedl_android.YoutubeDL
@ -98,10 +96,9 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
}
override fun onOutputClick(item: DownloadItem) {
val logFile = FileUtil.getLogFile(requireContext(), item)
if (logFile.exists()) {
if (item.logID != null) {
val bundle = Bundle()
bundle.putString("logpath", logFile.absolutePath)
bundle.putLong("logID", item.logID!!)
findNavController().navigate(
R.id.downloadLogFragment,
bundle

View file

@ -30,7 +30,6 @@ import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.adapter.GenericDownloadAdapter
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.databinding.FragmentHomeBinding
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.UiUtil
@ -42,7 +41,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
import kotlinx.coroutines.runBlocking
import java.io.File
class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickListener, OnItemClickListener {
@ -93,10 +91,9 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
override fun onActionButtonClick(itemID: Long) {
val item = items.find { it.id == itemID } ?: return
val logFile = FileUtil.getLogFile(requireContext(), item)
if (logFile.exists()) {
if (item.logID != null) {
val bundle = Bundle()
bundle.putString("logpath", logFile.absolutePath)
bundle.putLong("logID", item.logID!!)
findNavController().navigate(
R.id.downloadLogFragment,
bundle

View file

@ -3,7 +3,6 @@ package com.deniscerri.ytdlnis.ui.more.downloadLogs
import android.content.ClipboardManager
import android.content.Context.CLIPBOARD_SERVICE
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.os.FileObserver
import android.view.LayoutInflater
@ -12,14 +11,21 @@ import android.view.ViewGroup
import android.widget.ScrollView
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.map
import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.LogItem
import com.deniscerri.ytdlnis.database.viewmodel.LogViewModel
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import com.neo.highlight.core.Highlight
import com.neo.highlight.util.listener.HighlightTextWatcher
import com.neo.highlight.util.scheme.ColorScheme
import java.io.File
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.util.regex.Pattern
@ -27,9 +33,9 @@ class DownloadLogFragment : Fragment() {
private lateinit var content: TextView
private lateinit var contentScrollView : ScrollView
private lateinit var topAppBar: MaterialToolbar
private lateinit var observer: FileObserver
private lateinit var copyLog : ExtendedFloatingActionButton
private lateinit var mainActivity: MainActivity
private lateinit var logViewModel: LogViewModel
override fun onCreateView(
inflater: LayoutInflater,
@ -61,41 +67,27 @@ class DownloadLogFragment : Fragment() {
clipboard.setText(content.text)
}
val path = arguments?.getString("logpath")
if (path == null) {
val id = arguments?.getLong("logID")
if (id == null) {
mainActivity.onBackPressedDispatcher.onBackPressed()
}else{
arguments?.remove("logpath")
arguments?.remove("logID")
}
val file = File(path!!)
topAppBar.title = file.name
content.text = file.readText()
if(Build.VERSION.SDK_INT < 29){
observer = object : FileObserver(file.absolutePath, MODIFY) {
override fun onEvent(event: Int, p: String?) {
mainActivity.runOnUiThread{
val newText = File(path).readText()
content.text = newText
content.scrollTo(0, content.height)
contentScrollView.fullScroll(View.FOCUS_DOWN)
}
}
logViewModel = ViewModelProvider(this)[LogViewModel::class.java]
logViewModel.items.map { it.find { log -> log.id == id } }.observe(viewLifecycleOwner) { logItem ->
mainActivity.runOnUiThread{
content.text = logItem?.content
content.scrollTo(0, content.height)
contentScrollView.fullScroll(View.FOCUS_DOWN)
}
observer.startWatching();
}else{
observer = object : FileObserver(file, MODIFY) {
override fun onEvent(event: Int, p: String?) {
mainActivity.runOnUiThread{
val newText = File(path).readText()
content.text = newText
content.scrollTo(0, content.height)
contentScrollView.fullScroll(View.FOCUS_DOWN)
}
}
}
observer.startWatching();
}
CoroutineScope(Dispatchers.IO).launch {
val logItem = logViewModel.getItemById(id!!)
topAppBar.title = logItem.title
}
//init syntax highlighter
@ -119,11 +111,6 @@ class DownloadLogFragment : Fragment() {
content.addTextChangedListener(highlightWatcher)
}
override fun onDestroy() {
super.onDestroy()
observer.stopWatching()
}
companion object {
private const val TAG = "DownloadLogActivity"
}

View file

@ -1,9 +1,7 @@
package com.deniscerri.ytdlnis.ui.more.downloadLogs
import android.content.DialogInterface
import android.os.Build
import android.os.Bundle
import android.os.FileObserver
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
@ -11,12 +9,15 @@ import android.view.ViewGroup
import android.widget.RelativeLayout
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.adapter.DownloadLogsAdapter
import com.deniscerri.ytdlnis.database.models.LogItem
import com.deniscerri.ytdlnis.database.viewmodel.LogViewModel
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import java.io.File
@ -28,8 +29,9 @@ class DownloadLogListFragment : Fragment(), DownloadLogsAdapter.OnItemClickListe
private lateinit var noResults: RelativeLayout
private lateinit var fileList: MutableList<File>
private lateinit var topAppBar: MaterialToolbar
private lateinit var logFolder : File
private lateinit var mainActivity: MainActivity
private lateinit var logViewModel: LogViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@ -56,32 +58,17 @@ class DownloadLogListFragment : Fragment(), DownloadLogsAdapter.OnItemClickListe
noResults = view.findViewById(R.id.no_results)
noResults.visibility = View.GONE
logFolder = File(requireContext().filesDir.absolutePath + "/logs")
updateList(logFolder)
if(Build.VERSION.SDK_INT < 29){
val observer: FileObserver = object : FileObserver(logFolder.absolutePath) {
override fun onEvent(event: Int, path: String?) {
when(event) {
CREATE, DELETE -> updateList(logFolder)
}
}
}
observer.startWatching()
}else{
val observer: FileObserver = object : FileObserver(logFolder) {
override fun onEvent(event: Int, path: String?) {
when(event) {
CREATE, DELETE -> updateList(logFolder)
}
}
}
observer.startWatching()
logViewModel = ViewModelProvider(this)[LogViewModel::class.java]
logViewModel.items.observe(viewLifecycleOwner) {
if (it.isEmpty()) noResults.visibility = View.VISIBLE
else noResults.visibility = View.GONE
downloadLogAdapter.submitList(it)
}
initMenu(logFolder)
initMenu()
}
private fun initMenu(logFolder: File) {
private fun initMenu() {
topAppBar.setOnMenuItemClickListener { m: MenuItem ->
val itemId = m.itemId
if (itemId == R.id.remove_logs) {
@ -91,11 +78,7 @@ class DownloadLogListFragment : Fragment(), DownloadLogsAdapter.OnItemClickListe
deleteDialog.setMessage(getString(R.string.confirm_delete_logs_desc))
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
logFolder.listFiles()!!.forEach {
it.delete()
}.run {
updateList(logFolder)
}
logViewModel.deleteAll()
}
deleteDialog.show()
}catch (e: Exception){
@ -106,43 +89,22 @@ class DownloadLogListFragment : Fragment(), DownloadLogsAdapter.OnItemClickListe
}
}
private fun updateList(logFolder: File){
fileList = mutableListOf()
try{
fileList.addAll(logFolder.listFiles()!!)
fileList.sortByDescending { it.lastModified()}
}catch (e: Exception){
e.printStackTrace()
}
downloadLogAdapter.submitList(fileList.toList())
mainActivity.runOnUiThread{
if (fileList.isNotEmpty()) {
noResults.visibility = View.GONE
topAppBar.menu.findItem(R.id.remove_logs).isVisible = true
}else{
topAppBar.menu.findItem(R.id.remove_logs).isVisible = false
noResults.visibility = View.VISIBLE
}
}
}
override fun onItemClick(file: File) {
override fun onItemClick(item: LogItem) {
val bundle = Bundle()
bundle.putString("logpath", file.absolutePath)
bundle.putLong("logID", item.id)
findNavController().navigate(
R.id.downloadLogFragment,
bundle
)
}
override fun onDeleteClick(file: File) {
override fun onDeleteClick(item: LogItem) {
val deleteDialog = MaterialAlertDialogBuilder(requireContext())
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + file.name + "\"!")
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + item.title + "\"!")
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
file.delete().run {
updateList(logFolder)
}
logViewModel.delete(item)
}
deleteDialog.show()
}

View file

@ -34,7 +34,6 @@ import com.deniscerri.ytdlnis.database.viewmodel.CookieViewModel
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.database.viewmodel.HistoryViewModel
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.UpdateUtil
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
@ -65,6 +64,7 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
private lateinit var cookieViewModel: CookieViewModel
private lateinit var commandTemplateViewModel: CommandTemplateViewModel
private var arch: Preference? = null
private var version: Preference? = null
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
@ -200,7 +200,7 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
}
version = findPreference("version")
version!!.summary = BuildConfig.VERSION_NAME
version!!.summary = "${BuildConfig.VERSION_NAME} [${Build.SUPPORTED_ABIS[0]}] ${BuildConfig.BUILD_TYPE}"
version!!.onPreferenceClickListener =
Preference.OnPreferenceClickListener {
@ -223,11 +223,13 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
val prefs = preferences.all
val arr = JsonArray()
prefs.forEach {
val obj = JsonObject()
obj.addProperty("key", it.key)
obj.addProperty("value", it.value.toString())
obj.addProperty("type", it.value!!::class.simpleName)
arr.add(obj)
if (it.key != "app_language"){
val obj = JsonObject()
obj.addProperty("key", it.key)
obj.addProperty("value", it.value.toString())
obj.addProperty("type", it.value!!::class.simpleName)
arr.add(obj)
}
}
return arr
}

View file

@ -152,25 +152,6 @@ object FileUtil {
return listOf(context.getString(R.string.unfound_file))
}
fun getLogFile(context: Context, item: DownloadItem) : File {
val titleRegex = Regex("[^A-Za-z\\d ]")
val title = item.title.ifEmpty { item.url }
return File(context.filesDir.absolutePath + """/logs/${item.id} - ${titleRegex.replace(title, "").take(150)}##${item.type}##${item.format.format_id}.log""")
}
fun checkLogFileExists(context: Context, item: DownloadItem) : File? {
return try {
val dir = File(context.filesDir.absolutePath + "/logs/")
dir.listFiles()?.toList()?.first { it.name.startsWith(item.id.toString()) }
}catch (e: Exception){
null
}
}
fun getLogFileForTerminal(context: Context, command: String) : File {
val titleRegex = Regex("[^A-Za-z\\d ]")
return File(context.filesDir.absolutePath + """/logs/Terminal - ${titleRegex.replace(command.take(30), "")}##terminal.log""")
}
fun getDefaultAudioPath() : String{
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).absolutePath + File.separator + "YTDLnis/Audio"

View file

@ -279,48 +279,51 @@ class InfoUtil(private val context: Context) {
val thumb = "https://i.ytimg.com/vi/$id/hqdefault.jpg"
val url = "https://www.youtube.com/watch?v=$id"
val formats : ArrayList<Format> = ArrayList()
if (obj.has("audioStreams")){
val formatsInJSON = obj.getJSONArray("audioStreams")
for (f in 0 until formatsInJSON.length()){
val format = formatsInJSON.getJSONObject(f)
if (format.getInt("bitrate") == 0) continue
val formatObj = Gson().fromJson(format.toString(), Format::class.java)
try{
formatObj.acodec = format.getString("codec")
formatObj.asr = format.getString("quality")
if (! format.getString("audioTrackName").equals("null", ignoreCase = true)){
formatObj.format_note = format.getString("audioTrackName") + " Audio, " + formatObj.format_note
}else{
formatObj.format_note = formatObj.format_note + " Audio"
if(sharedPreferences.getString("formats_source", "yt-dlp") == "piped"){
if (obj.has("audioStreams")){
val formatsInJSON = obj.getJSONArray("audioStreams")
for (f in 0 until formatsInJSON.length()){
val format = formatsInJSON.getJSONObject(f)
if (format.getInt("bitrate") == 0) continue
val formatObj = Gson().fromJson(format.toString(), Format::class.java)
try{
formatObj.acodec = format.getString("codec")
formatObj.asr = format.getString("quality")
if (! format.getString("audioTrackName").equals("null", ignoreCase = true)){
formatObj.format_note = format.getString("audioTrackName") + " Audio, " + formatObj.format_note
}else{
formatObj.format_note = formatObj.format_note + " Audio"
}
}catch (e: Exception) {
e.printStackTrace()
}
}catch (e: Exception) {
e.printStackTrace()
formats.add(formatObj)
}
formats.add(formatObj)
}
}
if (obj.has("videoStreams")){
val formatsInJSON = obj.getJSONArray("videoStreams")
for (f in 0 until formatsInJSON.length()){
val format = formatsInJSON.getJSONObject(f)
if (format.getInt("bitrate") == 0) continue
val formatObj = Gson().fromJson(format.toString(), Format::class.java)
try{
formatObj.vcodec = format.getString("codec")
}catch (e: Exception) {
e.printStackTrace()
if (obj.has("videoStreams")){
val formatsInJSON = obj.getJSONArray("videoStreams")
for (f in 0 until formatsInJSON.length()){
val format = formatsInJSON.getJSONObject(f)
if (format.getInt("bitrate") == 0) continue
val formatObj = Gson().fromJson(format.toString(), Format::class.java)
try{
formatObj.vcodec = format.getString("codec")
}catch (e: Exception) {
e.printStackTrace()
}
formats.add(formatObj)
}
formats.add(formatObj)
}
}
formats.sortBy { it.filesize }
formats.groupBy { it.format_id }.forEach {
if (it.value.count() > 1) {
it.value.filter { f-> !f.format_note.contains("original", true) }.forEachIndexed { index, format -> format.format_id = format.format_id.split("-")[0] + "-${index}" }
val engDefault = it.value.find { f -> f.format_note.contains("original", true) }
engDefault?.format_id = (engDefault?.format_id?.split("-")?.get(0) ?: "") + "-${it.value.size-1}"
formats.sortBy { it.filesize }
formats.groupBy { it.format_id }.forEach {
if (it.value.count() > 1) {
it.value.filter { f-> !f.format_note.contains("original", true) }.forEachIndexed { index, format -> format.format_id = format.format_id.split("-")[0] + "-${index}" }
val engDefault = it.value.find { f -> f.format_note.contains("original", true) }
engDefault?.format_id = (engDefault?.format_id?.split("-")?.get(0) ?: "") + "-${it.value.size-1}"
}
}
}

View file

@ -19,6 +19,7 @@ import com.deniscerri.ytdlnis.receiver.PauseDownloadNotificationReceiver
import com.deniscerri.ytdlnis.receiver.ResumeActivity
import com.deniscerri.ytdlnis.receiver.ShareFileActivity
import java.io.File
import kotlin.math.log
class NotificationUtil(var context: Context) {
@ -231,13 +232,15 @@ class NotificationUtil(var context: Context) {
fun createDownloadErrored(title: String?,
error: String?,
logFile: File?,
logID: Long?,
channel: String
) {
val notificationBuilder = getBuilder(channel)
val bundle = Bundle()
bundle.putString("logpath", logFile?.absolutePath)
if (logID != null){
bundle.putLong("logID", logID)
}
val errorPendingIntent = NavDeepLinkBuilder(context)
.setGraph(R.navigation.nav_graph)
@ -268,7 +271,7 @@ class NotificationUtil(var context: Context) {
.setPriority(NotificationCompat.PRIORITY_MAX)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.clearActions()
if (logFile != null){
if (logID != null){
notificationBuilder.setContentIntent(errorPendingIntent)
notificationBuilder.addAction(0, context.getString(R.string.logs), errorPendingIntent)
}

View file

@ -1,7 +1,6 @@
package com.deniscerri.ytdlnis.work
import android.content.Context
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.util.Log
@ -19,7 +18,9 @@ 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
@ -48,6 +49,7 @@ class DownloadWorker(
val repository = DownloadRepository(dao)
val historyDao = dbManager.historyDao
val resultDao = dbManager.resultDao
val logRepo = LogRepository(dbManager.logDao)
val handler = Handler(Looper.getMainLooper())
@ -92,16 +94,6 @@ class DownloadWorker(
tempFileDir.delete()
tempFileDir.mkdirs()
var useCacheFolder = true
val downloadLocationFile = File(downloadLocation)
if (downloadLocationFile.canWrite()) {
File(downloadLocation).mkdirs()
if(Build.VERSION.SDK_INT > 23) request.addOption("-P", "temp:" + tempFileDir.absolutePath)
useCacheFolder = false
}
val pathUsed = if (useCacheFolder) tempFileDir.absolutePath else downloadLocation
val aria2 = sharedPreferences.getBoolean("aria2", false)
if (aria2) {
request.addOption("--downloader", "libaria2c.so")
@ -159,6 +151,10 @@ class DownloadWorker(
request.addOption("--download-sections", "*$it")
else
request.addOption("--download-sections", it)
if (sharedPreferences.getBoolean("force_keyframes", false)){
request.addOption("--force-keyframes-at-cuts")
}
}
downloadItem.customFileNameTemplate += " %(section_title)s %(autonumber)s"
request.addOption("--output-na-placeholder", " ")
@ -233,9 +229,9 @@ class DownloadWorker(
if (downloadItem.audioPreferences.splitByChapters && downloadItem.downloadSections.isBlank()){
request.addOption("--split-chapters")
request.addOption("-P", pathUsed)
request.addOption("-P", tempFileDir.absolutePath)
}else{
request.addOption("-o", pathUsed + "/${downloadItem.customFileNameTemplate}.%(ext)s")
request.addOption("-o", tempFileDir.absolutePath + "/${downloadItem.customFileNameTemplate}.%(ext)s")
}
}
@ -262,7 +258,7 @@ class DownloadWorker(
else if (videoFormatID == context.resources.getString(R.string.worst_quality) || videoFormatID == "worst") videoFormatID = "worst"
else if (defaultFormats.contains(videoFormatID)) videoFormatID = "bestvideo[height<="+videoFormatID.substring(0, videoFormatID.length -1)+"]"
formatArgument = if (downloadItem.videoPreferences.audioFormatIDs.isNotEmpty()){
formatArgument = if (downloadItem.videoPreferences.audioFormatIDs.isNotEmpty() && ! downloadItem.videoPreferences.removeAudio){
val audioIds = downloadItem.videoPreferences.audioFormatIDs.joinToString("+")
"$videoFormatID+$audioIds/best/$videoFormatID"
}else{
@ -292,16 +288,15 @@ class DownloadWorker(
}
}
if (downloadItem.videoPreferences.removeAudio &&
(downloadItem.format.acodec.isNotEmpty() && downloadItem.format.acodec != "none")){
if (downloadItem.videoPreferences.removeAudio){
request.addOption("--ppa", "ffmpeg:-an")
}
if (downloadItem.videoPreferences.splitByChapters && downloadItem.downloadSections.isBlank()){
request.addOption("--split-chapters")
request.addOption("-P", pathUsed)
request.addOption("-P", tempFileDir.absolutePath)
}else{
request.addOption("-o", pathUsed + "/${downloadItem.customFileNameTemplate}.%(ext)s")
request.addOption("-o", tempFileDir.absolutePath + "/${downloadItem.customFileNameTemplate}.%(ext)s")
}
}
@ -312,24 +307,32 @@ class DownloadWorker(
writeText(downloadItem.format.format_note)
}.absolutePath
)
request.addOption("-P", pathUsed)
request.addOption("-P", tempFileDir.absolutePath)
}
}
val logDownloads = sharedPreferences.getBoolean("log_downloads", false) && !sharedPreferences.getBoolean("incognito", false)
val logFolder = File(context.filesDir.absolutePath + "/logs")
val logFile = FileUtil.getLogFile(context, downloadItem)
if (logDownloads){
logFolder.mkdirs()
logFile.createNewFile()
logFile.writeText("Downloading:\n" +
val logItem = LogItem(
0,
downloadItem.title,
"Downloading:\n" +
"Title: ${downloadItem.title}\n" +
"URL: ${downloadItem.url}\n" +
"Type: ${downloadItem.type}\n" +
"Format: ${downloadItem.format}\n\n" +
"Command: ${java.lang.String.join(" ", request.buildCommand())}\n\n")
"Command: ${java.lang.String.join(" ", request.buildCommand())}\n\n"
)
if (logDownloads){
runBlocking {
logItem.id = logRepo.insert(logItem)
downloadItem.logID = logItem.id
dao.update(downloadItem)
}
}
runCatching {
YoutubeDL.getInstance().execute(request, downloadItem.id.toString()){ progress, _, line ->
@ -340,48 +343,41 @@ class DownloadWorker(
line, progress.toInt(), 0, title,
NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID
)
if (logDownloads && logFile.exists()){
logFile.appendText("${line}\n")
if (logDownloads){
CoroutineScope(Dispatchers.IO).launch {
logRepo.update(line, logItem.id)
}
}
}
}.onSuccess {
val wasQuickDownloaded = updateDownloadItem(downloadItem, infoUtil, dao, resultDao)
var finalPaths: List<String>
if (useCacheFolder){
//move file from internal to set download directory
setProgressAsync(workDataOf("progress" to 100, "output" to "Moving file to ${FileUtil.formatPath(downloadLocation)}", "id" to downloadItem.id, "log" to logDownloads))
try {
finalPaths = FileUtil.moveFile(tempFileDir.absoluteFile,context, downloadLocation, keepCache){ p ->
setProgressAsync(workDataOf("progress" to p, "output" to "Moving file to ${FileUtil.formatPath(downloadLocation)}", "id" to downloadItem.id, "log" to logDownloads))
}
if (finalPaths.isNotEmpty()){
setProgressAsync(workDataOf("progress" to 100, "output" to "Moved file to $downloadLocation", "id" to downloadItem.id, "log" to logDownloads))
}else{
finalPaths = listOf(context.getString(R.string.unfound_file))
}
}catch (e: Exception){
finalPaths = listOf(context.getString(R.string.unfound_file))
e.printStackTrace()
handler.postDelayed({
Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show()
}, 1000)
}
}else{
val now = System.currentTimeMillis()
finalPaths = downloadLocationFile
.walkTopDown()
.filter { it.isFile && it.absolutePath.contains(downloadItem.title) && it.lastModified() > now - 10000000}
.map { it.absolutePath }
.toList()
if (finalPaths.isEmpty()) finalPaths = listOf(context.getString(R.string.unfound_file))
var finalPaths : List<String>?
//move file from internal to set download directory
setProgressAsync(workDataOf("progress" to 100, "output" to "Moving file to ${FileUtil.formatPath(downloadLocation)}", "id" to downloadItem.id, "log" to logDownloads))
try {
finalPaths = FileUtil.moveFile(tempFileDir.absoluteFile,context, downloadLocation, keepCache){ p ->
setProgressAsync(workDataOf("progress" to p, "output" to "Moving file to ${FileUtil.formatPath(downloadLocation)}", "id" to downloadItem.id, "log" to logDownloads))
}
if (finalPaths.isNotEmpty()){
setProgressAsync(workDataOf("progress" to 100, "output" to "Moved file to $downloadLocation", "id" to downloadItem.id, "log" to logDownloads))
}else{
finalPaths = listOf(context.getString(R.string.unfound_file))
}
}catch (e: Exception){
finalPaths = listOf(context.getString(R.string.unfound_file))
e.printStackTrace()
handler.postDelayed({
Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show()
}, 1000)
}
//put download in history
val incognito = sharedPreferences.getBoolean("incognito", false)
if (!incognito) {
val unixtime = System.currentTimeMillis() / 1000
val file = File(finalPaths.first()!!)
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)
runBlocking {
@ -421,8 +417,12 @@ class DownloadWorker(
Data.Builder().putString("output", "Download has been cancelled!").build()
)
}else{
if (logDownloads && logFile.exists()){
logFile.appendText("${it.message}\n")
if (logDownloads){
CoroutineScope(Dispatchers.IO).launch {
if(it.message != null){
logRepo.update(it.message!!, logItem.id)
}
}
}
tempFileDir.delete()
@ -440,7 +440,7 @@ class DownloadWorker(
notificationUtil.createDownloadErrored(
downloadItem.title, it.message,
if (logDownloads) logFile else null,
if (logDownloads) logItem.id else null,
NotificationUtil.DOWNLOAD_FINISHED_CHANNEL_ID
)

View file

@ -14,12 +14,22 @@ import androidx.work.WorkerParameters
import androidx.work.workDataOf
import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.DBManager
import com.deniscerri.ytdlnis.database.models.LogItem
import com.deniscerri.ytdlnis.database.repository.LogRepository
import com.deniscerri.ytdlnis.ui.more.TerminalActivity
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.NotificationUtil
import com.yausername.youtubedl_android.YoutubeDL
import com.yausername.youtubedl_android.YoutubeDLRequest
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.io.File
import java.time.LocalDate
import java.time.LocalDateTime
import java.util.Calendar
class TerminalDownloadWorker(
@ -32,6 +42,9 @@ class TerminalDownloadWorker(
if (itemId == 0) return Result.failure()
if (command!!.isEmpty()) return Result.failure()
val dbManager = DBManager.getInstance(context)
val logRepo = LogRepository(dbManager.logDao)
val notificationUtil = NotificationUtil(context)
val handler = Handler(Looper.getMainLooper())
@ -67,16 +80,20 @@ class TerminalDownloadWorker(
request.addOption("-P", tempFileDir.absolutePath)
val logDownloads = sharedPreferences.getBoolean("log_downloads", false) && !sharedPreferences.getBoolean("incognito", false)
val logFolder = File(context.filesDir.absolutePath + "/logs")
val logFile = FileUtil.getLogFileForTerminal(context, command)
val logItem = LogItem(
0,
"Terminal Download at " + Calendar.getInstance().time,
"Downloading:\n" +
"Terminal Download\n" +
"Command: ${command}\n\n"
)
runCatching {
if (logDownloads){
logFolder.mkdirs()
logFile.createNewFile()
logFile.writeText("Downloading:\n" +
"Terminal Download\n" +
"Command: ${command}\n\n")
runBlocking {
logItem.id = logRepo.insert(logItem)
}
}
YoutubeDL.getInstance().execute(request, itemId.toString()){ progress, _, line ->
@ -87,8 +104,10 @@ class TerminalDownloadWorker(
line, progress.toInt(), 0, title,
NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID
)
if (logDownloads && logFile.exists()){
logFile.appendText("${line}\n")
if (logDownloads){
CoroutineScope(Dispatchers.IO).launch {
logRepo.update(line, logItem.id)
}
}
}
}.onSuccess {
@ -106,8 +125,10 @@ class TerminalDownloadWorker(
if (it.out.length > 200){
outputFile.appendText("${it.out}\n")
if (logDownloads && logFile.exists()){
logFile.appendText("${it.out}\n")
if (logDownloads){
CoroutineScope(Dispatchers.IO).launch {
logRepo.update(it.out, logItem.id)
}
}
}
notificationUtil.cancelDownloadNotification(itemId)
@ -117,8 +138,12 @@ class TerminalDownloadWorker(
return Result.failure()
}
outputFile.appendText("${it.message}\n")
if (logDownloads && logFile.exists()){
logFile.appendText("${it.message}\n")
if (logDownloads){
CoroutineScope(Dispatchers.IO).launch {
if (it.message != null){
logRepo.update(it.message!!, logItem.id)
}
}
}
tempFileDir.delete()

View file

@ -282,4 +282,6 @@
<string name="retries">Retries</string>
<string name="fragment_retries">Fragment Retries</string>
<string name="format_order">Format Order</string>
<string name="force_keyframes">Force Keyframes At Cuts</string>
<string name="force_keyframes_summary">Accurate Video Cuts. Slower</string>
</resources>

View file

@ -2,7 +2,7 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory android:title="@string/locale">
<PreferenceCategory android:title="@string/appearance">
<ListPreference
android:entries="@array/language_values"
android:entryValues="@array/language_values"
@ -11,18 +11,7 @@
app:summary="en"
app:title="@string/language" />
<ListPreference
android:defaultValue=""
android:entries="@array/countries"
android:entryValues="@array/countries_values"
app:icon="@drawable/ic_language"
app:useSimpleSummaryProvider="true"
app:dependency="home_recommendations"
app:key="locale"
app:title="@string/preferred_locale" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/appearance">
<ListPreference
android:entries="@array/themes"
android:entryValues="@array/themes_values"
@ -60,6 +49,17 @@
app:summary="@string/video_recommendations_summary"
app:title="@string/video_recommendations" />
<ListPreference
android:defaultValue=""
android:entries="@array/countries"
android:entryValues="@array/countries_values"
app:icon="@drawable/ic_language"
app:useSimpleSummaryProvider="true"
app:dependency="home_recommendations"
app:key="locale"
app:title="@string/preferred_locale" />
<ListPreference
android:defaultValue="Home"
android:entries="@array/start_destination"

View file

@ -60,6 +60,14 @@
app:summary="@string/crop_thumb_summary"
app:title="@string/crop_thumb" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="false"
app:icon="@drawable/ic_cut"
app:key="force_keyframes"
app:summary="@string/force_keyframes_summary"
app:title="@string/force_keyframes" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="true"

View file

@ -30,7 +30,7 @@
app:title="@string/update_formats" />
<ListPreference
android:defaultValue="yt-dlp"
android:defaultValue="piped"
android:entries="@array/formats_source"
android:entryValues="@array/formats_source_values"
android:icon="@drawable/baseline_manage_search_24"
@ -49,7 +49,7 @@
<EditTextPreference
app:key="piped_instance"
app:defaultValue="https://pipedapi.kavin.rocks"
app:defaultValue="https://pipedapi.syncpundit.io"
android:summary="@string/piped_instance_summary"
app:title="@string/piped_instance" />