From 335be8299c7fc5dd8dd3fe59e478cd445b36250d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20=C3=87erri?= <64997243+deniscerri@users.noreply.github.com> Date: Wed, 18 Jan 2023 19:52:27 +0100 Subject: [PATCH] created download worker --- .../1.json | 254 ------------------ app/src/main/AndroidManifest.xml | 2 +- .../deniscerri/ytdlnis/DownloaderService.kt | 2 + .../deniscerri/ytdlnis/database/DBManager.kt | 5 +- .../database/dao/CommandTemplateDao.kt | 30 +++ .../ytdlnis/database/dao/DownloadDao.kt | 7 +- .../database/models/CommandTemplate.kt | 13 + .../ytdlnis/database/models/DownloadItem.kt | 19 +- .../ytdlnis/database/models/HistoryItem.kt | 8 +- .../database/repository/DownloadRepository.kt | 10 +- .../CancelDownloadNotificationReceiver.kt | 14 + .../ytdlnis/receiver/NotificationReceiver.kt | 53 ---- .../ytdlnis/util/NotificationUtil.kt | 8 +- .../ytdlnis/work/DownloadWorker.java | 36 --- .../deniscerri/ytdlnis/work/DownloadWorker.kt | 253 +++++++++++++++++ .../ytdlnis/work/FileTransferWorker.kt | 2 + app/src/main/res/layout/downloads_card.xml | 20 +- app/src/main/res/layout/result_card.xml | 4 +- 18 files changed, 367 insertions(+), 373 deletions(-) delete mode 100644 app/schemas/com.deniscerri.ytdlnis.database.DBManager/1.json create mode 100644 app/src/main/java/com/deniscerri/ytdlnis/database/dao/CommandTemplateDao.kt create mode 100644 app/src/main/java/com/deniscerri/ytdlnis/database/models/CommandTemplate.kt create mode 100644 app/src/main/java/com/deniscerri/ytdlnis/receiver/CancelDownloadNotificationReceiver.kt delete mode 100644 app/src/main/java/com/deniscerri/ytdlnis/receiver/NotificationReceiver.kt delete mode 100644 app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.java create mode 100644 app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt diff --git a/app/schemas/com.deniscerri.ytdlnis.database.DBManager/1.json b/app/schemas/com.deniscerri.ytdlnis.database.DBManager/1.json deleted file mode 100644 index 599d0a37..00000000 --- a/app/schemas/com.deniscerri.ytdlnis.database.DBManager/1.json +++ /dev/null @@ -1,254 +0,0 @@ -{ - "formatVersion": 1, - "database": { - "version": 1, - "identityHash": "e2280d10a40623d3a51ecf5205500351", - "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)", - "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 - } - ], - "primaryKey": { - "columnNames": [ - "id" - ], - "autoGenerate": true - }, - "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)", - "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 - } - ], - "primaryKey": { - "columnNames": [ - "id" - ], - "autoGenerate": true - }, - "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, `startTime` TEXT NOT NULL, `endTime` TEXT NOT NULL, `thumb` TEXT NOT NULL, `formatId` INTEGER NOT NULL, `formatDesc` TEXT NOT NULL, `downloadPath` TEXT NOT NULL, `website` TEXT NOT NULL, `downloadSize` TEXT NOT NULL, `progress` REAL NOT NULL, `custom` INTEGER NOT NULL, `customTemplateId` 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": "startTime", - "columnName": "startTime", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "endTime", - "columnName": "endTime", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "thumb", - "columnName": "thumb", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "formatId", - "columnName": "formatId", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "formatDesc", - "columnName": "formatDesc", - "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": "progress", - "columnName": "progress", - "affinity": "REAL", - "notNull": true - }, - { - "fieldPath": "custom", - "columnName": "custom", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "customTemplateId", - "columnName": "customTemplateId", - "affinity": "INTEGER", - "notNull": true - } - ], - "primaryKey": { - "columnNames": [ - "id" - ], - "autoGenerate": true - }, - "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, 'e2280d10a40623d3a51ecf5205500351')" - ] - } -} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 886e4b13..7ed8199e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -67,7 +67,7 @@ android:name=".DownloaderService" android:enabled="true" android:exported="false" /> - + + + @Query("SELECT * FROM commandTemplates WHERE id=:id LIMIT 1") + fun getTemplateById(id: Int) : CommandTemplate + + @Insert(onConflict = OnConflictStrategy.IGNORE) + suspend fun insert(item: CommandTemplate) + + @Query("DELETE FROM commandTemplates") + suspend fun deleteAll() + + @Delete + suspend fun delete(item: CommandTemplate) + + @Update(onConflict = OnConflictStrategy.REPLACE) + suspend fun update(item: CommandTemplate) +} \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/DownloadDao.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/DownloadDao.kt index 285799bb..f2c72d17 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/DownloadDao.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/DownloadDao.kt @@ -10,10 +10,13 @@ import com.deniscerri.ytdlnis.util.FileUtil interface DownloadDao { @Query("SELECT * FROM downloads") - fun getAllDownloads() : LiveData> + fun getAllDownloads() : List + + @Query("SELECT * FROM downloads WHERE status='Queued'") + fun getQueuedDownloads() : List @Query("SELECT * FROM downloads WHERE id=:id LIMIT 1") - suspend fun getDownloadById(id: Int) : DownloadItem + fun getDownloadById(id: Int) : DownloadItem @Insert(onConflict = OnConflictStrategy.IGNORE) suspend fun insert(item: DownloadItem) diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/models/CommandTemplate.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/models/CommandTemplate.kt new file mode 100644 index 00000000..96024e4a --- /dev/null +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/models/CommandTemplate.kt @@ -0,0 +1,13 @@ +package com.deniscerri.ytdlnis.database.models + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "downloads") +data class CommandTemplate( + @PrimaryKey(autoGenerate = true) + val id: Int, + val title: String, + val content: String +) \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/models/DownloadItem.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/models/DownloadItem.kt index b63bc725..7b9c3058 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/models/DownloadItem.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/models/DownloadItem.kt @@ -1,5 +1,6 @@ package com.deniscerri.ytdlnis.database.models +import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey @@ -10,15 +11,21 @@ data class DownloadItem( val url: String, val title: String, val author: String, - val startTime: String, - val endTime: String, val thumb: String, - val formatId: Int, + val duration: String, + val type: String, + val audioQualityId: String, + val videoQualityId: String, + val customTemplateId: Int, + val audioFormat: String, + val videoFormat: String, val formatDesc: String, + @ColumnInfo(defaultValue = "0") + val removeAudio: Boolean, val downloadPath: String, val website: String, val downloadSize: String, - val progress: Float, - val custom: Boolean, - val customTemplateId: Int + val playlistTitle: String, + @ColumnInfo(defaultValue = "Queued") + var status: String ) \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/models/HistoryItem.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/models/HistoryItem.kt index 3a841663..5783fc93 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/models/HistoryItem.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/models/HistoryItem.kt @@ -5,8 +5,6 @@ import androidx.room.PrimaryKey @Entity(tableName = "history") data class HistoryItem( - @PrimaryKey(autoGenerate = true) - var id: Int, val url: String, val title: String, val author: String, @@ -16,5 +14,7 @@ data class HistoryItem( val time: Long, val downloadPath: String, val website: String - //val isQueuedDownload: Int -) \ No newline at end of file +){ + @PrimaryKey(autoGenerate = true) + var id: Int? = null +} \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/DownloadRepository.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/DownloadRepository.kt index e9538190..852055b3 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/DownloadRepository.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/DownloadRepository.kt @@ -5,7 +5,6 @@ import com.deniscerri.ytdlnis.database.dao.DownloadDao import com.deniscerri.ytdlnis.database.models.DownloadItem class DownloadRepository(private val downloadDao: DownloadDao) { - val allDownloads : LiveData> = downloadDao.getAllDownloads() suspend fun insert(item: DownloadItem){ downloadDao.insert(item) @@ -19,5 +18,14 @@ class DownloadRepository(private val downloadDao: DownloadDao) { downloadDao.update(item) } + suspend fun getQueuedDownloads() : List{ + var items = downloadDao.getQueuedDownloads(); + for (i in items){ + i.status = "Downloading" + downloadDao.update(i) + } + return items + } + } \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdlnis/receiver/CancelDownloadNotificationReceiver.kt b/app/src/main/java/com/deniscerri/ytdlnis/receiver/CancelDownloadNotificationReceiver.kt new file mode 100644 index 00000000..64f09ae5 --- /dev/null +++ b/app/src/main/java/com/deniscerri/ytdlnis/receiver/CancelDownloadNotificationReceiver.kt @@ -0,0 +1,14 @@ +package com.deniscerri.ytdlnis.receiver + +import android.content.* +import androidx.work.WorkManager + +class CancelDownloadNotificationReceiver : BroadcastReceiver() { + override fun onReceive(c: Context, intent: Intent) { + val message = intent.getStringExtra("cancel") + val id = intent.getIntExtra("workID", 0) + if (message != null) { + WorkManager.getInstance(c).cancelAllWorkByTag(id.toString()) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdlnis/receiver/NotificationReceiver.kt b/app/src/main/java/com/deniscerri/ytdlnis/receiver/NotificationReceiver.kt deleted file mode 100644 index d3e1f54c..00000000 --- a/app/src/main/java/com/deniscerri/ytdlnis/receiver/NotificationReceiver.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.deniscerri.ytdlnis.receiver - -import android.content.* -import android.os.IBinder -import com.deniscerri.ytdlnis.DownloaderService -import com.deniscerri.ytdlnis.DownloaderService.LocalBinder -import com.deniscerri.ytdlnis.service.IDownloaderService - -class NotificationReceiver : BroadcastReceiver() { - var downloaderService: DownloaderService? = null - private var iDownloaderService: IDownloaderService? = null - private var context: Context? = null - private val serviceConnection: ServiceConnection = object : ServiceConnection { - override fun onServiceConnected(className: ComponentName, service: IBinder) { - downloaderService = (service as LocalBinder).service - iDownloaderService = service - cancelDownload() - } - - override fun onServiceDisconnected(componentName: ComponentName) { - downloaderService = null - iDownloaderService = null - } - } - - override fun onReceive(c: Context, intent: Intent) { - context = c - val message = intent.getStringExtra("cancel") - if (message != null) { - val serviceIntent = Intent(context!!.applicationContext, DownloaderService::class.java) - serviceIntent.putExtra("rebind", true) - context!!.applicationContext.bindService( - serviceIntent, - serviceConnection, - Context.BIND_AUTO_CREATE - ) - } - } - - private fun cancelDownload() { - try { - iDownloaderService!!.cancelDownload(true) - context!!.applicationContext.unbindService(serviceConnection) - context!!.applicationContext.stopService( - Intent( - context!!.applicationContext, - DownloaderService::class.java - ) - ) - } catch (ignored: Exception) { - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdlnis/util/NotificationUtil.kt b/app/src/main/java/com/deniscerri/ytdlnis/util/NotificationUtil.kt index 6a005eea..5a3856dd 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/util/NotificationUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/util/NotificationUtil.kt @@ -10,7 +10,7 @@ import android.graphics.BitmapFactory import android.os.Build import androidx.core.app.NotificationCompat import com.deniscerri.ytdlnis.R -import com.deniscerri.ytdlnis.receiver.NotificationReceiver +import com.deniscerri.ytdlnis.receiver.CancelDownloadNotificationReceiver class NotificationUtil(var context: Context) { private val downloadNotificationBuilder: NotificationCompat.Builder = NotificationCompat.Builder(context, DOWNLOAD_SERVICE_CHANNEL_ID) @@ -53,7 +53,7 @@ class NotificationUtil(var context: Context) { pendingIntent: PendingIntent?, title: String?, ) : Notification { - val intent = Intent(context, NotificationReceiver::class.java) + val intent = Intent(context, CancelDownloadNotificationReceiver::class.java) return fileTransferNotificationBuilder .setContentTitle(title) @@ -92,12 +92,14 @@ class NotificationUtil(var context: Context) { fun createDownloadServiceNotification( pendingIntent: PendingIntent?, title: String?, + workID: Int, channel: String ): Notification { val notificationBuilder = getBuilder(channel) - val intent = Intent(context, NotificationReceiver::class.java) + val intent = Intent(context, CancelDownloadNotificationReceiver::class.java) intent.putExtra("cancel", "") + intent.putExtra("workID", workID) val cancelNotificationPendingIntent = PendingIntent.getBroadcast( context, 0, diff --git a/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.java b/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.java deleted file mode 100644 index e843f580..00000000 --- a/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.deniscerri.ytdlnis.work; - -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; - -import androidx.annotation.NonNull; -import androidx.work.ForegroundInfo; -import androidx.work.Worker; -import androidx.work.WorkerParameters; - -import com.deniscerri.ytdlnis.MainActivity; -import com.deniscerri.ytdlnis.database.Video; - -import java.util.ArrayList; - -import kotlin.random.Random; - -public class DownloadWorker extends Worker { - private Context context; - private WorkerParameters workerParams; - public DownloadWorker( - @NonNull Context appContext, - @NonNull WorkerParameters workerParams) { - super(appContext, workerParams); - this.context = appContext; - this.workerParams = workerParams; - } - - @NonNull - @Override - public Result doWork() { - //TODO - return Result.success(); - } -} diff --git a/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt b/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt new file mode 100644 index 00000000..7e4d43d1 --- /dev/null +++ b/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt @@ -0,0 +1,253 @@ +package com.deniscerri.ytdlnis.work + +import android.app.PendingIntent +import android.app.Service +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.SystemClock +import android.provider.DocumentsContract +import android.util.Log +import android.widget.Toast +import androidx.work.CoroutineWorker +import androidx.work.ForegroundInfo +import androidx.work.WorkerParameters +import androidx.work.workDataOf +import com.deniscerri.ytdlnis.BuildConfig +import com.deniscerri.ytdlnis.MainActivity +import com.deniscerri.ytdlnis.R +import com.deniscerri.ytdlnis.database.DBManager +import com.deniscerri.ytdlnis.database.models.DownloadItem +import com.deniscerri.ytdlnis.database.models.HistoryItem +import com.deniscerri.ytdlnis.database.repository.DownloadRepository +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 java.io.File +import java.util.regex.Pattern + + +class DownloadWorker( + private val context: Context, + workerParams: WorkerParameters +) : CoroutineWorker(context, workerParams) { + override suspend fun doWork(): Result { + workID = inputData.getInt("workID", SystemClock.uptimeMillis().toInt()) + val notificationUtil = NotificationUtil(context) + val dbManager = DBManager.getInstance(context) + val dao = dbManager.downloadDao + val repository = DownloadRepository(dao) + val commandTemplateDao = dbManager.commandTemplateDao + val historyDao = dbManager.historyDao + + queue = repository.getQueuedDownloads() as MutableList + + val intent = Intent(context, MainActivity::class.java) + val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) + val notification = notificationUtil.createDownloadServiceNotification(pendingIntent, queue[0].title, workID, NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID) + val foregroundInfo = ForegroundInfo(workID, notification) + setForeground(foregroundInfo) + + while (queue.isNotEmpty()){ + val item = dao.getDownloadById(queue[0].id) + queue.removeFirst() + val url = item.url + val request = YoutubeDLRequest(url) + val type = item.type + val downloadLocation = item.downloadPath + + val tempFolder = StringBuilder(context.cacheDir.absolutePath + """/${item.title}##${item.type}""") + when(type){ + "audio" -> tempFolder.append("##${item.audioQualityId}") + "video" -> tempFolder.append("##${item.videoQualityId}") + "command" -> tempFolder.append("##${item.customTemplateId}") + } + var tempFileDir = File(tempFolder.toString()) + tempFileDir.delete() + tempFileDir.mkdir() + + val sharedPreferences = context.getSharedPreferences("root_preferences", + Service.MODE_PRIVATE + ) + val aria2 = sharedPreferences.getBoolean("aria2", false) + if (aria2) { + request.addOption("--downloader", "libaria2c.so") + request.addOption("--external-downloader-args", "aria2c:\"--summary-interval=1\"") + } else { + val concurrentFragments = sharedPreferences.getInt("concurrent_fragments", 1) + if (concurrentFragments > 1) request.addOption("-N", concurrentFragments) + } + val limitRate = sharedPreferences.getString("limit_rate", "") + if (limitRate != "") request.addOption("-r", limitRate!!) + val writeThumbnail = sharedPreferences.getBoolean("write_thumbnail", false) + if (writeThumbnail) { + request.addOption("--write-thumbnail") + request.addOption("--convert-thumbnails", "png") + } + request.addOption("--no-mtime") + val sponsorBlockFilters = sharedPreferences.getStringSet("sponsorblock_filters", emptySet()) + if (sponsorBlockFilters!!.isNotEmpty()) { + val filters = java.lang.String.join(",", sponsorBlockFilters) + request.addOption("--sponsorblock-remove", filters) + } + + when(type){ + "audio" -> { + request.addOption("-x") + var audioQualityId : String = item.audioQualityId + if (audioQualityId == "0") audioQualityId = "ba" + var format = item.audioFormat + if (format.isEmpty()){ + format = sharedPreferences.getString("audio_format", "")!! + } + request.addOption("-f", audioQualityId) + request.addOption("--audio-format", format) + request.addOption("--embed-metadata") + + val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false) + if (embedThumb) { + request.addOption("--embed-thumbnail") + request.addOption("--convert-thumbnails", "png") + try { + val config = File(context.cacheDir, "config" + item.title + "##" + item.audioQualityId + ".txt") + val configData = + "--ppa \"ffmpeg: -c:v png -vf crop=\\\"'if(gt(ih,iw),iw,ih)':'if(gt(iw,ih),ih,iw)'\\\"\"" + config.writeText(configData) + request.addOption("--config", config.absolutePath) + } catch (ignored: Exception) {} + } + request.addOption("--parse-metadata", "%(release_year,upload_date)s:%(meta_date)s") + request.addCommands(listOf("--replace-in-metadata", "title", ".*.", item.title)) + request.addCommands(listOf("--replace-in-metadata", "uploader", ".*.", item.author)) + + if (item.playlistTitle.isNotEmpty()) { + request.addOption("--parse-metadata", "%(album,playlist,title)s:%(meta_album)s") + request.addOption("--parse-metadata", "%(track_number,playlist_index)d:%(meta_track)s") + } else { + request.addOption("--parse-metadata", "%(album,title)s:%(meta_album)s") + } + request.addOption("-o", tempFileDir.absolutePath + "/%(uploader)s - %(title)s.%(ext)s") + } + "video" -> { + if (downloadLocation == context.getString(R.string.video_path)){ + tempFileDir = File(context.getString(R.string.video_path)) + } + + val addChapters = sharedPreferences.getBoolean("add_chapters", false) + if (addChapters) { + request.addOption("--sponsorblock-mark", "all") + } + val embedSubs = sharedPreferences.getBoolean("embed_subtitles", false) + if (embedSubs) { + request.addOption("--embed-subs", "") + } + var videoQualityId = item.videoQualityId + val audioQualityId = item.audioQualityId + if (videoQualityId.isEmpty()) videoQualityId = "bestvideo" + val formatArgument = StringBuilder(videoQualityId) + if (videoQualityId != "worst"){ + if (audioQualityId != "0") formatArgument.append("+", audioQualityId, "/best") + } + request.addOption("-f", formatArgument.toString()) + var format = item.videoFormat + if (format.isNotEmpty()) { + format = sharedPreferences.getString("video_format", "")!! + if (format != "DEFAULT") request.addOption("--merge-output-format", format) + } + + if (format != "webm") { + val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false) + if (embedThumb) { + request.addOption("--embed-thumbnail") + } + } + request.addOption("-o", tempFileDir.absolutePath + "/%(uploader)s - %(title)s.%(ext)s") + } + "command" -> { + val commandRegex = "\"([^\"]*)\"|(\\S+)" + val command = commandTemplateDao.getTemplateById(item.customTemplateId) + val m = Pattern.compile(commandRegex).matcher(command.content) + while (m.find()) { + if (m.group(1) != null) { + request.addOption(m.group(1)!!) + } else { + request.addOption(m.group(2)!!) + } + } + } + } + + runCatching { + YoutubeDL.getInstance().execute(request, item.id.toString()){ progress, _, line -> + setProgressAsync(workDataOf("progress" to progress.toInt())) + var title: String? = context.getString(R.string.download_start) + if (queue.isNotEmpty()) { + title = queue[0].title + } + notificationUtil.updateDownloadNotification( + workID, + line, progress.toInt(), queue.size, title, + NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID + ) + } + }.onSuccess { + //move file from internal to set download directory + moveFile(tempFileDir.absoluteFile, downloadLocation){ progress -> + setProgressAsync(workDataOf("progress" to progress)) + } + //put download in history + val incognito = sharedPreferences.getBoolean("incognito", false) + if (!incognito) { + val unixtime = System.currentTimeMillis() / 1000 + val historyItem = HistoryItem(item.url, item.title, item.author, item.duration, item.thumb, item.type, unixtime, item.downloadPath, item.website) + historyDao.insert(historyItem) + } + + }.onFailure { + tempFileDir.delete() + if (BuildConfig.DEBUG) { + Toast.makeText(context, it.message, Toast.LENGTH_LONG).show() + Log.e(DownloadWorker.TAG, context.getString(R.string.failed_download), it) + } + notificationUtil.updateDownloadNotification( + workID, + context.getString(R.string.failed_download), 0, 0, item.title, + NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID + ) + return Result.failure() + } + } + return Result.success() + } + + private fun getDownloadLocation(type: String, context: Context): String? { + val sharedPreferences = context.getSharedPreferences("root_preferences", + Service.MODE_PRIVATE + ) + val downloadsDir: String? = if (type == "audio") { + sharedPreferences.getString("music_path", context.getString(R.string.music_path)) + } else { + sharedPreferences.getString("video_path", context.getString(R.string.video_path)) + } + return downloadsDir + } + + + private fun moveFile(originDir: File, downLocation: String, progress: (progress: Int) -> Unit){ + val destDir = Uri.parse(downLocation).run { + DocumentsContract.buildChildDocumentsUriUsingTree(this, DocumentsContract.getTreeDocumentId(this)) + } + val fileUtil = FileUtil() + fileUtil.moveFile(originDir, context, destDir){ p -> + progress(p) + } + } + + companion object { + var workID: Int = 0 + const val TAG = "DownloadWorker" + lateinit var queue: MutableList + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdlnis/work/FileTransferWorker.kt b/app/src/main/java/com/deniscerri/ytdlnis/work/FileTransferWorker.kt index 9e2cf182..e79d7760 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/work/FileTransferWorker.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/work/FileTransferWorker.kt @@ -9,6 +9,7 @@ import android.provider.DocumentsContract import androidx.work.CoroutineWorker import androidx.work.ForegroundInfo import androidx.work.WorkerParameters +import androidx.work.workDataOf import com.deniscerri.ytdlnis.MainActivity import com.deniscerri.ytdlnis.util.FileUtil import com.deniscerri.ytdlnis.util.NotificationUtil @@ -39,6 +40,7 @@ class FileTransferWorker( setForeground(foregroundInfo) fileUtil.moveFile(originDir, context, destDir){ progress -> + setProgressAsync(workDataOf("progress" to progress)) notificationUtil.updateFileTransferNotification(id, progress) } diff --git a/app/src/main/res/layout/downloads_card.xml b/app/src/main/res/layout/downloads_card.xml index 7a1e4d6c..373e39c1 100644 --- a/app/src/main/res/layout/downloads_card.xml +++ b/app/src/main/res/layout/downloads_card.xml @@ -49,11 +49,11 @@ android:id="@+id/downloads_title" android:layout_width="300dp" android:layout_height="100dp" - android:paddingTop="15dp" - android:paddingRight="20dp" - android:paddingBottom="20dp" - android:paddingLeft="20dp" - android:textSize="16sp" + android:paddingTop="10dp" + android:paddingEnd="10dp" + android:paddingBottom="10dp" + android:paddingStart="10dp" + android:textSize="15sp" android:textColor="@color/white" android:textStyle="bold" android:shadowRadius="2" @@ -67,9 +67,9 @@ android:layout_height="match_parent" android:gravity="bottom" android:paddingTop="10dp" - android:paddingRight="20dp" + android:paddingRight="10dp" android:paddingBottom="10dp" - android:paddingLeft="20dp" + android:paddingLeft="10dp" android:textSize="12sp" android:textColor="@color/white" android:textStyle="bold" @@ -82,11 +82,11 @@ android:id="@+id/downloads_info_time" android:layout_width="match_parent" android:layout_height="match_parent" - android:gravity="bottom|right" + android:gravity="bottom|end" android:paddingTop="10dp" - android:paddingRight="20dp" + android:paddingRight="10dp" android:paddingBottom="10dp" - android:paddingLeft="20dp" + android:paddingLeft="10dp" android:textSize="12sp" android:textColor="@color/white" android:textStyle="bold" diff --git a/app/src/main/res/layout/result_card.xml b/app/src/main/res/layout/result_card.xml index af7dde22..8fe15e8f 100644 --- a/app/src/main/res/layout/result_card.xml +++ b/app/src/main/res/layout/result_card.xml @@ -59,7 +59,7 @@ android:paddingTop="10dp" android:paddingEnd="10dp" android:paddingStart="10dp" - android:textSize="15sp" + android:textSize="17sp" android:textColor="@color/white" android:textStyle="bold" android:shadowRadius="2" @@ -75,7 +75,7 @@ android:paddingEnd="10dp" android:paddingBottom="5dp" android:paddingStart="10dp" - android:textSize="10sp" + android:textSize="12sp" android:textColor="@color/white" android:textStyle="bold" android:shadowRadius="1.5"