From 444e188549acd352d6da82c620bf964e7ef0b10c Mon Sep 17 00:00:00 2001 From: deniscerri <64997243+deniscerri@users.noreply.github.com> Date: Sun, 23 Mar 2025 20:37:07 +0100 Subject: [PATCH] hmm --- .../ytdl/database/dao/DownloadDao.kt | 8 +- .../database/repository/DownloadRepository.kt | 2 +- .../database/viewmodel/DownloadViewModel.kt | 54 +- .../java/com/deniscerri/ytdl/di/AppModule.kt | 5 - .../ytdl/receiver/ScheduleAlarmReceiver.kt | 2 +- .../downloadcard/ResultCardDetailsDialog.kt | 4 +- .../ui/downloads/ActiveDownloadsFragment.kt | 4 +- .../more/downloadLogs/DownloadLogFragment.kt | 5 +- .../more/settings/DownloadSettingsFragment.kt | 2 +- .../terminal/TerminalDownloadsListFragment.kt | 5 +- .../deniscerri/ytdl/work/DownloadWorker.kt | 441 ++++++++++++++++ .../ytdl/work/TerminalDownloadWorker.kt | 6 +- .../ytdl/work/downloader/DownloadManager.kt | 479 ------------------ .../ytdl/work/downloader/DownloadWorker.kt | 104 ---- 14 files changed, 475 insertions(+), 646 deletions(-) delete mode 100644 app/src/main/java/com/deniscerri/ytdl/di/AppModule.kt create mode 100644 app/src/main/java/com/deniscerri/ytdl/work/DownloadWorker.kt delete mode 100644 app/src/main/java/com/deniscerri/ytdl/work/downloader/DownloadManager.kt delete mode 100644 app/src/main/java/com/deniscerri/ytdl/work/downloader/DownloadWorker.kt diff --git a/app/src/main/java/com/deniscerri/ytdl/database/dao/DownloadDao.kt b/app/src/main/java/com/deniscerri/ytdl/database/dao/DownloadDao.kt index 9278bc19..b2e85401 100644 --- a/app/src/main/java/com/deniscerri/ytdl/database/dao/DownloadDao.kt +++ b/app/src/main/java/com/deniscerri/ytdl/database/dao/DownloadDao.kt @@ -107,15 +107,15 @@ interface DownloadDao { @Query(""" SELECT * FROM downloads - WHERE status in ('Active', 'Queued', 'Scheduled') AND downloadStartTime <= :currentTime + WHERE status in ('Queued', 'Scheduled') AND downloadStartTime <= :currentTime ORDER BY downloadStartTime, id LIMIT 10 """) - fun getActiveQueuedScheduledDownloadsUntil(currentTime: Long) : Flow> + fun getQueuedScheduledDownloadsUntil(currentTime: Long) : Flow> @Query(""" SELECT * FROM downloads - WHERE status in ('Active', 'Queued', 'Scheduled') AND downloadStartTime <= :currentTime + WHERE status in ('Queued', 'Scheduled') AND downloadStartTime <= :currentTime ORDER BY CASE WHEN id in (:priorityItems) THEN 0 @@ -124,7 +124,7 @@ interface DownloadDao { downloadStartTime, id LIMIT 10 """) - fun getActiveQueuedScheduledDownloadsUntilWithPriority(currentTime: Long, priorityItems: List) : Flow> + fun getQueuedScheduledDownloadsUntilWithPriority(currentTime: Long, priorityItems: List) : Flow> @Query("SELECT * FROM downloads WHERE status='Queued' ORDER BY downloadStartTime, id") fun getQueuedDownloadsList() : List diff --git a/app/src/main/java/com/deniscerri/ytdl/database/repository/DownloadRepository.kt b/app/src/main/java/com/deniscerri/ytdl/database/repository/DownloadRepository.kt index 3b16b9f9..5894c125 100644 --- a/app/src/main/java/com/deniscerri/ytdl/database/repository/DownloadRepository.kt +++ b/app/src/main/java/com/deniscerri/ytdl/database/repository/DownloadRepository.kt @@ -22,7 +22,7 @@ import com.deniscerri.ytdl.database.models.DownloadItemSimple import com.deniscerri.ytdl.util.Extensions.toListString import com.deniscerri.ytdl.util.FileUtil import com.deniscerri.ytdl.work.AlarmScheduler -import com.deniscerri.ytdl.work.downloader.DownloadWorker +import com.deniscerri.ytdl.work.DownloadWorker import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged import java.io.File diff --git a/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/DownloadViewModel.kt b/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/DownloadViewModel.kt index e1f7090e..80a297d9 100644 --- a/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/DownloadViewModel.kt +++ b/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/DownloadViewModel.kt @@ -10,13 +10,9 @@ import android.util.DisplayMetrics import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.MediatorLiveData -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope -import androidx.media3.common.Player.Command import androidx.paging.PagingData -import androidx.paging.filter -import androidx.paging.map import androidx.preference.PreferenceManager import androidx.work.Data import androidx.work.ExistingWorkPolicy @@ -49,7 +45,6 @@ import com.deniscerri.ytdl.util.extractors.YTDLPUtil import com.deniscerri.ytdl.work.AlarmScheduler import com.deniscerri.ytdl.work.UpdateMultipleDownloadsDataWorker import com.deniscerri.ytdl.work.UpdateMultipleDownloadsFormatsWorker -import com.deniscerri.ytdl.work.downloader.DownloadManager import com.google.gson.Gson import com.yausername.youtubedl_android.YoutubeDL import kotlinx.coroutines.CancellationException @@ -58,11 +53,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext @@ -99,9 +91,6 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel val erroredDownloadsCount : Flow val savedDownloadsCount : Flow val scheduledDownloadsCount : Flow - - val downloadManager: DownloadManager - val pausedAllDownloads = MediatorLiveData(PausedAllDownloadsState.HIDDEN) private val pausedAllDownloadsFlow : Flow private var isPausingResuming = false @@ -148,7 +137,6 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel resultRepository = ResultRepository(dbManager.resultDao, commandTemplateDao, application) sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application) ytdlpUtil = YTDLPUtil(application, commandTemplateDao) - downloadManager = DownloadManager.getInstance() activeDownloadsCount = repository.activeDownloadsCount activePausedDownloadsCount = repository.activePausedDownloadsCount @@ -1296,38 +1284,39 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel } fun cancelDownloadOnly(id : Long) { - downloadManager.cancelDownload(id) + YoutubeDL.getInstance().destroyProcessById(id.toString()) + notificationUtil.cancelDownloadNotification(id.toInt()) } suspend fun cancelDownload(id: Long) { - downloadManager.cancelDownload(id) - val item = getItemByID(id) - item.status = DownloadRepository.Status.Cancelled.toString() - updateDownload(item) + cancelDownloadOnly(id) + updateToStatus(id, DownloadRepository.Status.Cancelled) } suspend fun pauseDownload(id: Long) { - downloadManager.cancelDownload(id) + cancelDownloadOnly(id) updateToStatus(id, DownloadRepository.Status.Paused) } suspend fun pauseAllDownloads() { pausedAllDownloads.value = PausedAllDownloadsState.PROCESSING isPausingResuming = true - - withContext(Dispatchers.IO){ - downloadManager.cancelAll() - } - WorkManager.getInstance(application).cancelAllWorkByTag("download") val activeDownloadsList = withContext(Dispatchers.IO){ getActiveDownloads() } - delay(1000) - isPausingResuming = false + + activeDownloadsList.forEach { + cancelDownloadOnly(it.id) + } + withContext(Dispatchers.IO) { repository.setDownloadStatusMultiple(activeDownloadsList.map { it.id }, DownloadRepository.Status.Paused) } + delay(1500) + isPausingResuming = false + + withContext(Dispatchers.Main) { pausedAllDownloads.value = PausedAllDownloadsState.RESUME } @@ -1336,12 +1325,6 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel suspend fun resumeAllDownloads() { pausedAllDownloads.value = PausedAllDownloadsState.PROCESSING isPausingResuming = true - WorkManager.getInstance(application).cancelAllWorkByTag("download") - - withContext(Dispatchers.IO){ - downloadManager.cancelAll() - } - val paused = withContext(Dispatchers.IO) { dao.getPausedDownloadsList() } @@ -1350,7 +1333,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel dbManager.downloadDao.resetPausedToQueued() repository.startDownloadWorker(paused, application) } - delay(1000) + delay(1500) isPausingResuming = false withContext(Dispatchers.Main) { pausedAllDownloads.value = PausedAllDownloadsState.PAUSE @@ -1366,12 +1349,9 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel cancelAllDownloadsImpl() } - private suspend fun cancelAllDownloadsImpl() { - cancelActiveQueued() - withContext(Dispatchers.IO) { - downloadManager.cancelAll() - } + private fun cancelAllDownloadsImpl() { WorkManager.getInstance(application).cancelAllWorkByTag("download") + cancelActiveQueued() } fun resumeDownload(itemID: Long) = viewModelScope.launch { diff --git a/app/src/main/java/com/deniscerri/ytdl/di/AppModule.kt b/app/src/main/java/com/deniscerri/ytdl/di/AppModule.kt deleted file mode 100644 index 0e1f5381..00000000 --- a/app/src/main/java/com/deniscerri/ytdl/di/AppModule.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.deniscerri.ytdl.di - -class AppModule { - -} \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdl/receiver/ScheduleAlarmReceiver.kt b/app/src/main/java/com/deniscerri/ytdl/receiver/ScheduleAlarmReceiver.kt index 7cc6d8b4..01e45b4f 100644 --- a/app/src/main/java/com/deniscerri/ytdl/receiver/ScheduleAlarmReceiver.kt +++ b/app/src/main/java/com/deniscerri/ytdl/receiver/ScheduleAlarmReceiver.kt @@ -9,7 +9,7 @@ import androidx.work.ExistingWorkPolicy import androidx.work.NetworkType import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkManager -import com.deniscerri.ytdl.work.downloader.DownloadWorker +import com.deniscerri.ytdl.work.DownloadWorker import java.util.concurrent.TimeUnit class ScheduleAlarmReceiver : BroadcastReceiver() { diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/ResultCardDetailsDialog.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/ResultCardDetailsDialog.kt index 12258e64..7000ea70 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/ResultCardDetailsDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/ResultCardDetailsDialog.kt @@ -48,7 +48,7 @@ import com.deniscerri.ytdl.util.Extensions.setFullScreen import com.deniscerri.ytdl.util.NotificationUtil import com.deniscerri.ytdl.util.UiUtil import com.deniscerri.ytdl.util.VideoPlayerUtil -import com.deniscerri.ytdl.work.downloader.DownloadWorker +import com.deniscerri.ytdl.work.DownloadWorker import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment @@ -502,7 +502,7 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap //dont remove @Subscribe(threadMode = ThreadMode.MAIN) - fun onDownloadProgressEvent(event: com.deniscerri.ytdl.work.downloader.DownloadManager.DownloadProgress) { + fun onDownloadProgressEvent(event: DownloadWorker.WorkerProgress) { val progressBar = requireView().findViewWithTag("${event.downloadItemID}##progress") val outputText = requireView().findViewWithTag("${event.downloadItemID}##output") diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/ActiveDownloadsFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/ActiveDownloadsFragment.kt index 5878bb33..5487be73 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/ActiveDownloadsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/ActiveDownloadsFragment.kt @@ -28,7 +28,7 @@ import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel import com.deniscerri.ytdl.ui.adapter.ActiveDownloadAdapter import com.deniscerri.ytdl.util.Extensions.forceFastScrollMode import com.deniscerri.ytdl.util.NotificationUtil -import com.deniscerri.ytdl.work.downloader.DownloadManager +import com.deniscerri.ytdl.work.DownloadWorker import com.google.android.material.badge.ExperimentalBadgeUtils import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton import com.google.android.material.progressindicator.LinearProgressIndicator @@ -149,7 +149,7 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis //dont remove @Subscribe(threadMode = ThreadMode.MAIN) - fun onDownloadProgressEvent(event: DownloadManager.DownloadProgress) { + fun onDownloadProgressEvent(event: DownloadWorker.WorkerProgress) { val progressBar = requireView().findViewWithTag("${event.downloadItemID}##progress") val outputText = requireView().findViewWithTag("${event.downloadItemID}##output") diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/more/downloadLogs/DownloadLogFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/more/downloadLogs/DownloadLogFragment.kt index b8b47f61..3c4432da 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/more/downloadLogs/DownloadLogFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/more/downloadLogs/DownloadLogFragment.kt @@ -30,8 +30,7 @@ import com.deniscerri.ytdl.util.Extensions.enableFastScroll import com.deniscerri.ytdl.util.Extensions.enableTextHighlight import com.deniscerri.ytdl.util.Extensions.setCustomTextSize import com.deniscerri.ytdl.util.FileUtil -import com.deniscerri.ytdl.work.downloader.DownloadManager -import com.deniscerri.ytdl.work.downloader.DownloadWorker +import com.deniscerri.ytdl.work.DownloadWorker import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.bottomappbar.BottomAppBar import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton @@ -266,7 +265,7 @@ class DownloadLogFragment : Fragment() { //dont remove @Subscribe(threadMode = ThreadMode.MAIN) - fun onDownloadProgressEvent(event: DownloadManager.DownloadProgress) { + fun onDownloadProgressEvent(event: DownloadWorker.WorkerProgress) { val progressBar = requireView().findViewById(R.id.progress) if (event.logItemID == logID) { progressBar.isVisible = event.progress < 100 diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/DownloadSettingsFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/DownloadSettingsFragment.kt index 17b5e607..7adccfab 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/DownloadSettingsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/DownloadSettingsFragment.kt @@ -22,7 +22,7 @@ import com.deniscerri.ytdl.util.FileUtil import com.deniscerri.ytdl.util.UiUtil import com.deniscerri.ytdl.work.AlarmScheduler import com.deniscerri.ytdl.work.CleanUpLeftoverDownloads -import com.deniscerri.ytdl.work.downloader.DownloadWorker +import com.deniscerri.ytdl.work.DownloadWorker import java.util.Calendar import java.util.concurrent.TimeUnit diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/more/terminal/TerminalDownloadsListFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/more/terminal/TerminalDownloadsListFragment.kt index 0faa0102..d759960b 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/more/terminal/TerminalDownloadsListFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/more/terminal/TerminalDownloadsListFragment.kt @@ -20,8 +20,7 @@ import com.deniscerri.ytdl.database.models.TerminalItem import com.deniscerri.ytdl.database.viewmodel.TerminalViewModel import com.deniscerri.ytdl.ui.adapter.TerminalDownloadsAdapter import com.deniscerri.ytdl.util.Extensions.enableFastScroll -import com.deniscerri.ytdl.work.downloader.DownloadManager -import com.deniscerri.ytdl.work.downloader.DownloadWorker +import com.deniscerri.ytdl.work.DownloadWorker import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.progressindicator.LinearProgressIndicator import kotlinx.coroutines.flow.collectLatest @@ -85,7 +84,7 @@ class TerminalDownloadsListFragment : Fragment(), TerminalDownloadsAdapter.OnIte } @Subscribe(threadMode = ThreadMode.MAIN) - fun onDownloadProgressEvent(event: DownloadManager.DownloadProgress) { + fun onDownloadProgressEvent(event: DownloadWorker.WorkerProgress) { val progressBar = requireView().findViewWithTag("${event.downloadItemID}##progress") val outputText = requireView().findViewWithTag("${event.downloadItemID}##output") diff --git a/app/src/main/java/com/deniscerri/ytdl/work/DownloadWorker.kt b/app/src/main/java/com/deniscerri/ytdl/work/DownloadWorker.kt new file mode 100644 index 00000000..4922361b --- /dev/null +++ b/app/src/main/java/com/deniscerri/ytdl/work/DownloadWorker.kt @@ -0,0 +1,441 @@ +package com.deniscerri.ytdl.work + +import android.annotation.SuppressLint +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.content.pm.ServiceInfo +import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE +import android.content.res.Configuration +import android.content.res.Resources +import android.os.Build +import android.os.Handler +import android.os.Looper +import android.util.DisplayMetrics +import android.util.Log +import android.widget.Toast +import androidx.preference.PreferenceManager +import androidx.work.CoroutineWorker +import androidx.work.ForegroundInfo +import androidx.work.WorkManager +import androidx.work.WorkerParameters +import com.afollestad.materialdialogs.utils.MDUtil.getStringArray +import com.deniscerri.ytdl.App +import com.deniscerri.ytdl.MainActivity +import com.deniscerri.ytdl.R +import com.deniscerri.ytdl.database.DBManager +import com.deniscerri.ytdl.database.models.HistoryItem +import com.deniscerri.ytdl.database.models.LogItem +import com.deniscerri.ytdl.database.repository.DownloadRepository +import com.deniscerri.ytdl.database.repository.LogRepository +import com.deniscerri.ytdl.database.repository.ResultRepository +import com.deniscerri.ytdl.util.Extensions.getMediaDuration +import com.deniscerri.ytdl.util.Extensions.toStringDuration +import com.deniscerri.ytdl.util.FileUtil +import com.deniscerri.ytdl.util.NotificationUtil +import com.deniscerri.ytdl.util.extractors.YTDLPUtil +import com.yausername.youtubedl_android.YoutubeDL +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.supervisorScope +import kotlinx.coroutines.withContext +import org.greenrobot.eventbus.EventBus +import java.io.File +import java.security.MessageDigest +import java.util.Locale + + +class DownloadWorker( + private val context: Context, + workerParams: WorkerParameters +) : CoroutineWorker(context, workerParams) { + + override suspend fun getForegroundInfo(): ForegroundInfo { + val workNotif = NotificationUtil(App.instance).createDefaultWorkerNotification() + + return ForegroundInfo( + 1000000000, + workNotif, + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC + } else { + 0 + }, + ) + } + + + + @OptIn(ExperimentalStdlibApi::class) + @SuppressLint("RestrictedApi") + override suspend fun doWork(): Result { + val workManager = WorkManager.getInstance(context) + if (workManager.isRunning("download") || isStopped) return Result.Failure() + + setForegroundSafely() + + val notificationUtil = NotificationUtil(App.instance) + val dbManager = DBManager.getInstance(context) + val dao = dbManager.downloadDao + val historyDao = dbManager.historyDao + val commandTemplateDao = dbManager.commandTemplateDao + val logRepo = LogRepository(dbManager.logDao) + val resultRepo = ResultRepository(dbManager.resultDao, commandTemplateDao, context) + val ytdlpUtil = YTDLPUtil(context, commandTemplateDao) + val handler = Handler(Looper.getMainLooper()) + val alarmScheduler = AlarmScheduler(context) + val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) + val time = System.currentTimeMillis() + 6000 + val priorityItemIDs = (inputData.getLongArray("priority_item_ids") ?: longArrayOf()).toMutableList() + val continueAfterPriorityIds = inputData.getBoolean("continue_after_priority_ids", true) + val queuedItems = if (priorityItemIDs.isEmpty()) { + dao.getQueuedScheduledDownloadsUntil(time) + }else { + dao.getQueuedScheduledDownloadsUntilWithPriority(time, priorityItemIDs) + } + + // this is needed for observe sources call, so it wont create result items + // [removed] + //val createResultItem = inputData.getBoolean("createResultItem", true) + + val confTmp = Configuration(context.resources.configuration) + val locale = if (Build.VERSION.SDK_INT < 33) { + sharedPreferences.getString("app_language", "")!!.ifEmpty { Locale.getDefault().language } + }else{ + Locale.getDefault().language + }.run { + split("-") + }.run { + if (this.size == 1) Locale(this[0]) else Locale(this[0], this[1]) + } + confTmp.setLocale(locale) + val metrics = DisplayMetrics() + val resources = Resources(context.assets, metrics, confTmp) + + val openQueueIntent = Intent(context, MainActivity::class.java) + openQueueIntent.setAction(Intent.ACTION_VIEW) + openQueueIntent.putExtra("destination", "Queue") + val openDownloadQueue = PendingIntent.getActivity( + context, + 1000000000, + openQueueIntent, + PendingIntent.FLAG_IMMUTABLE + ) + + queuedItems.collectLatest { items -> + if (this@DownloadWorker.isStopped) return@collectLatest + + runningYTDLInstances.clear() + val activeDownloads = dao.getActiveDownloadsList() + activeDownloads.forEach { + runningYTDLInstances.add(it.id) + } + + val running = ArrayList(runningYTDLInstances) + val useScheduler = sharedPreferences.getBoolean("use_scheduler", false) + if (items.isEmpty() && running.isEmpty()) { + WorkManager.getInstance(context).cancelWorkById(this@DownloadWorker.id) + return@collectLatest + } + + if (useScheduler){ + if (items.none{it.downloadStartTime > 0L} && running.isEmpty() && !alarmScheduler.isDuringTheScheduledTime()) { + WorkManager.getInstance(context).cancelWorkById(this@DownloadWorker.id) + return@collectLatest + } + } + + if (priorityItemIDs.isEmpty() && !continueAfterPriorityIds) { + WorkManager.getInstance(context).cancelWorkById(this@DownloadWorker.id) + return@collectLatest + } + + val concurrentDownloads = sharedPreferences.getInt("concurrent_downloads", 1) - running.size + val eligibleDownloads = if (priorityItemIDs.isNotEmpty()) { + val tmp = priorityItemIDs.take(concurrentDownloads) + items.filter { it.id !in running && tmp.contains(it.id) } + }else{ + items.take(concurrentDownloads).filter { it.id !in running } + } + + eligibleDownloads.forEach{downloadItem -> + val notification = notificationUtil.createDownloadServiceNotification(openDownloadQueue, downloadItem.title.ifEmpty { downloadItem.url }) + notificationUtil.notify(downloadItem.id.toInt(), notification) + + CoroutineScope(Dispatchers.IO).launch { + val writtenPath = downloadItem.format.format_note.contains("-P ") + val noCache = writtenPath || (!sharedPreferences.getBoolean("cache_downloads", true) && File(FileUtil.formatPath(downloadItem.downloadPath)).canWrite()) + + val request = ytdlpUtil.buildYoutubeDLRequest(downloadItem) + downloadItem.status = DownloadRepository.Status.Active.toString() + CoroutineScope(Dispatchers.IO).launch { + delay(1500) + //update item if its incomplete + resultRepo.updateDownloadItem(downloadItem)?.apply { + val status = dao.checkStatus(this.id) + if (status == DownloadRepository.Status.Active){ + dao.updateWithoutUpsert(this) + } + } + } + + val cacheDir = FileUtil.getCachePath(context) + val tempFileDir = File(cacheDir, downloadItem.id.toString()) + tempFileDir.delete() + tempFileDir.mkdirs() + + val downloadLocation = downloadItem.downloadPath + val keepCache = sharedPreferences.getBoolean("keep_cache", false) + val logDownloads = sharedPreferences.getBoolean("log_downloads", false) && !downloadItem.incognito + + + val commandString = ytdlpUtil.parseYTDLRequestString(request) + val initialLogDetails = "Downloading:\n" + + "Title: ${downloadItem.title}\n" + + "URL: ${downloadItem.url}\n" + + "Type: ${downloadItem.type}\n" + + "Command:\n$commandString \n\n" + val logString = StringBuilder(initialLogDetails) + val logItem = LogItem( + 0, + downloadItem.title.ifBlank { downloadItem.url }, + logString.toString(), + downloadItem.format, + downloadItem.type, + System.currentTimeMillis(), + ) + + + runBlocking { + if (logDownloads) logItem.id = logRepo.insert(logItem) + downloadItem.logID = logItem.id + dao.update(downloadItem) + } + + val eventBus = EventBus.getDefault() + + runCatching { + YoutubeDL.getInstance().destroyProcessById(downloadItem.id.toString()) + YoutubeDL.getInstance().execute(request, downloadItem.id.toString()){ progress, _, line -> + eventBus.post(WorkerProgress(progress.toInt(), line, downloadItem.id, downloadItem.logID)) + val title: String = downloadItem.title.ifEmpty { downloadItem.url } + notificationUtil.updateDownloadNotification( + downloadItem.id.toInt(), + line, progress.toInt(), 0, title, + NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID + ) + CoroutineScope(Dispatchers.IO).launch { + if (logDownloads) { + logRepo.update(line, logItem.id) + logString.append("$line\n") + } + } + } + }.onSuccess { + resultRepo.updateDownloadItem(downloadItem)?.apply { + dao.updateWithoutUpsert(this) + } + //val wasQuickDownloaded = resultDao.getCountInt() == 0 + runBlocking { + var finalPaths = mutableListOf() + + if (noCache){ + eventBus.post(WorkerProgress(100, "Scanning Files", downloadItem.id, downloadItem.logID)) + val outputSequence = it.out.split("\n") + finalPaths = + outputSequence.asSequence() + .filter { it.startsWith("'/storage") } + .map { it.removeSuffix("\n") } + .map { it.removeSurrounding("'", "'") } + .toMutableList() + + finalPaths.addAll( + outputSequence.asSequence() + .filter { it.startsWith("[SplitChapters]") && it.contains("Destination: ") } + .map { it.split("Destination: ")[1] } + .map { it.removeSuffix("\n") } + .toList() + ) + + finalPaths.sortBy { File(it).lastModified() } + finalPaths = finalPaths.distinct().toMutableList() + FileUtil.scanMedia(finalPaths, context) + }else{ + //move file from internal to set download directory + eventBus.post(WorkerProgress(100, "Moving file to ${FileUtil.formatPath(downloadLocation)}", downloadItem.id, downloadItem.logID)) + try { + finalPaths = withContext(Dispatchers.IO){ + FileUtil.moveFile(tempFileDir.absoluteFile,context, downloadLocation, keepCache){ p -> + eventBus.post(WorkerProgress(p, "Moving file to ${FileUtil.formatPath(downloadLocation)}", downloadItem.id, downloadItem.logID)) + } + }.filter { !it.matches("\\.(description)|(txt)\$".toRegex()) }.toMutableList() + + if (finalPaths.isNotEmpty()){ + eventBus.post(WorkerProgress(100, "Moved file to ${FileUtil.formatPath(downloadLocation)}", downloadItem.id, downloadItem.logID)) + } + }catch (e: Exception){ + e.printStackTrace() + handler.postDelayed({ + Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show() + }, 1000) + } + } + + + val nonMediaExtensions = mutableListOf().apply { + addAll(context.getStringArray(R.array.thumbnail_containers_values)) + addAll(context.getStringArray(R.array.sub_formats_values).filter { it.isNotBlank() }) + add("description") + add("txt") + } + finalPaths = finalPaths.filter { path -> !nonMediaExtensions.any { path.endsWith(it) } }.toMutableList() + FileUtil.deleteConfigFiles(request) + + //put download in history + if (!downloadItem.incognito) { + if (request.hasOption("--download-archive") && finalPaths.isEmpty()) { + handler.postDelayed({ + Toast.makeText(context, resources.getString(R.string.download_already_exists), Toast.LENGTH_LONG).show() + }, 100) + }else{ + if (finalPaths.isNotEmpty()) { + val unixTime = System.currentTimeMillis() / 1000 + finalPaths.first().apply { + val file = File(this) + var duration = downloadItem.duration + val d = file.getMediaDuration(context) + if (d > 0) duration = d.toStringDuration(Locale.US) + + downloadItem.format.filesize = file.length() + downloadItem.format.container = file.extension + downloadItem.duration = duration + } + + val historyItem = HistoryItem(0, + downloadItem.url, + downloadItem.title, + downloadItem.author, + downloadItem.duration, + downloadItem.thumb, + downloadItem.type, + unixTime, + finalPaths, + downloadItem.website, + downloadItem.format, + downloadItem.format.filesize, + downloadItem.id, + commandString) + historyDao.insert(historyItem) + } + } + } + + withContext(Dispatchers.Main) { + notificationUtil.cancelDownloadNotification(downloadItem.id.toInt()) + notificationUtil.createDownloadFinished( + downloadItem.id, downloadItem.title, downloadItem.type, if (finalPaths.isEmpty()) null else finalPaths, resources + ) + } + +// if (wasQuickDownloaded && createResultItem){ +// runCatching { +// eventBus.post(WorkerProgress(100, "Creating Result Items", downloadItem.id)) +// runBlocking { +// infoUtil.getFromYTDL(downloadItem.url).forEach { res -> +// if (res != null) { +// resultDao.insert(res) +// } +// } +// } +// } +// } + + dao.delete(downloadItem.id) + + if (logDownloads){ + logRepo.update(initialLogDetails + it.out, logItem.id, true) + } + } + + }.onFailure { + FileUtil.deleteConfigFiles(request) + withContext(Dispatchers.Main){ + notificationUtil.cancelDownloadNotification(downloadItem.id.toInt()) + } + if (this@DownloadWorker.isStopped) return@onFailure + if (it is YoutubeDL.CanceledException) return@onFailure + if (it.message?.contains("JSONDecodeError") == true) { + val cachePath = "${FileUtil.getCachePath(context)}infojsons" + val infoJsonName = MessageDigest.getInstance("MD5").digest(downloadItem.url.toByteArray()).toHexString() + FileUtil.deleteFile("${cachePath}/${infoJsonName}.info.json") + } + + if(it.message != null){ + if (logDownloads){ + logRepo.update(it.message!!, logItem.id) + }else{ + logString.append("${it.message}\n") + logItem.content = logString.toString() + val logID = logRepo.insert(logItem) + downloadItem.logID = logID + } + } + + tempFileDir.delete() + handler.postDelayed({ + Toast.makeText(context, it.message, Toast.LENGTH_LONG).show() + }, 1000) + + Log.e(TAG, context.getString(R.string.failed_download), it) + notificationUtil.cancelDownloadNotification(downloadItem.id.toInt()) + + downloadItem.status = DownloadRepository.Status.Error.toString() + runBlocking { + dao.update(downloadItem) + } + + notificationUtil.createDownloadErrored( + downloadItem.id, + downloadItem.title.ifEmpty { downloadItem.url }, + it.message, + downloadItem.logID, + resources + ) + + eventBus.post(WorkerProgress(100, it.toString(), downloadItem.id, downloadItem.logID)) + } + } + } + + if (eligibleDownloads.isNotEmpty()){ + eligibleDownloads.forEach { + it.status = DownloadRepository.Status.Active.toString() + priorityItemIDs.remove(it.id) + } + dao.updateMultiple(eligibleDownloads) + } + } + + return Result.success() + } + + + + companion object { + val runningYTDLInstances: MutableList = mutableListOf() + const val TAG = "DownloadWorker" + } + + class WorkerProgress( + val progress: Int, + val output: String, + val downloadItemID: Long, + val logItemID: Long? + ) + +} \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdl/work/TerminalDownloadWorker.kt b/app/src/main/java/com/deniscerri/ytdl/work/TerminalDownloadWorker.kt index 16b7f536..1fe1b8a2 100644 --- a/app/src/main/java/com/deniscerri/ytdl/work/TerminalDownloadWorker.kt +++ b/app/src/main/java/com/deniscerri/ytdl/work/TerminalDownloadWorker.kt @@ -22,8 +22,6 @@ import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel import com.deniscerri.ytdl.ui.more.terminal.TerminalActivity import com.deniscerri.ytdl.util.FileUtil import com.deniscerri.ytdl.util.NotificationUtil -import com.deniscerri.ytdl.work.downloader.DownloadManager -import com.deniscerri.ytdl.work.downloader.DownloadWorker import com.yausername.youtubedl_android.YoutubeDL import com.yausername.youtubedl_android.YoutubeDLRequest import kotlinx.coroutines.CoroutineScope @@ -124,7 +122,7 @@ class TerminalDownloadWorker( YoutubeDL.getInstance().execute(request, itemId.toString()){ progress, _, line -> runBlocking { - eventBus.post(DownloadManager.DownloadProgress(progress.toInt(), line, itemId.toLong(), logItem.id)) + eventBus.post(DownloadWorker.WorkerProgress(progress.toInt(), line, itemId.toLong(), logItem.id)) } val title: String = command.take(65) @@ -144,7 +142,7 @@ class TerminalDownloadWorker( //move file from internal to set download directory try { FileUtil.moveFile(File(FileUtil.getCachePath(context) + "/TERMINAL/" + itemId),context, downloadLocation!!, false){ p -> - eventBus.post(DownloadManager.DownloadProgress(p, "", itemId.toLong(), logItem.id)) + eventBus.post(DownloadWorker.WorkerProgress(p, "", itemId.toLong(), logItem.id)) } }catch (e: Exception){ e.printStackTrace() diff --git a/app/src/main/java/com/deniscerri/ytdl/work/downloader/DownloadManager.kt b/app/src/main/java/com/deniscerri/ytdl/work/downloader/DownloadManager.kt deleted file mode 100644 index 6f2798c7..00000000 --- a/app/src/main/java/com/deniscerri/ytdl/work/downloader/DownloadManager.kt +++ /dev/null @@ -1,479 +0,0 @@ -package com.deniscerri.ytdl.work.downloader - -import android.app.PendingIntent -import android.content.Context -import android.content.Intent -import android.content.SharedPreferences -import android.content.res.Configuration -import android.content.res.Resources -import android.os.Build -import android.os.Handler -import android.os.Looper -import android.util.DisplayMetrics -import android.util.Log -import android.widget.Toast -import androidx.preference.PreferenceManager -import com.afollestad.materialdialogs.utils.MDUtil.getStringArray -import com.deniscerri.ytdl.App -import com.deniscerri.ytdl.MainActivity -import com.deniscerri.ytdl.R -import com.deniscerri.ytdl.database.DBManager -import com.deniscerri.ytdl.database.dao.CommandTemplateDao -import com.deniscerri.ytdl.database.dao.DownloadDao -import com.deniscerri.ytdl.database.dao.HistoryDao -import com.deniscerri.ytdl.database.models.DownloadItem -import com.deniscerri.ytdl.database.models.HistoryItem -import com.deniscerri.ytdl.database.models.LogItem -import com.deniscerri.ytdl.database.repository.DownloadRepository -import com.deniscerri.ytdl.database.repository.LogRepository -import com.deniscerri.ytdl.database.repository.ResultRepository -import com.deniscerri.ytdl.util.Extensions.getMediaDuration -import com.deniscerri.ytdl.util.Extensions.toStringDuration -import com.deniscerri.ytdl.util.FileUtil -import com.deniscerri.ytdl.util.NotificationUtil -import com.deniscerri.ytdl.util.extractors.YTDLPUtil -import com.deniscerri.ytdl.work.AlarmScheduler -import com.yausername.youtubedl_android.YoutubeDL -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.isActive -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.supervisorScope -import kotlinx.coroutines.withContext -import org.greenrobot.eventbus.EventBus -import java.io.File -import java.security.MessageDigest -import java.util.Locale - -class DownloadManager private constructor() { - private var notificationUtil: NotificationUtil = NotificationUtil(App.instance) - private var sharedPreferences : SharedPreferences = PreferenceManager.getDefaultSharedPreferences(App.instance) - private var dbManager: DBManager = DBManager.getInstance(App.instance) - private var dao : DownloadDao = dbManager.downloadDao - - private lateinit var historyDao: HistoryDao - private lateinit var commandTemplateDao: CommandTemplateDao - private lateinit var logRepo: LogRepository - private lateinit var resultRepo: ResultRepository - private lateinit var ytdlpUtil: YTDLPUtil - private lateinit var alarmScheduler : AlarmScheduler - private lateinit var eventBus: EventBus - private lateinit var resources: Resources - - private lateinit var openDownloadQueue: PendingIntent - - private lateinit var queueState : Flow> - private var observeJob : Job? = null - private val handler = Handler(Looper.getMainLooper()) - - val isRunning get() = observeJob?.isActive == true - - fun cancelDownload(id: Long) { - YoutubeDL.getInstance().destroyProcessById(id.toString()) - notificationUtil.cancelDownloadNotification(id.toInt()) - } - - fun cancelAll() { - observeJob?.cancel() - observeJob = null - val activeDownloads = dao.getActiveDownloadsList() - activeDownloads.forEach { - cancelDownload(it.id) - } - } - - //only call from download worker - fun startDownload( - context: Context, //worker context - priorityItems: List = listOf(), - continueAfterPriorityIds : Boolean = true - ) { - val priorityItemIDs = priorityItems.toMutableList() - - //init - notificationUtil = NotificationUtil(context) - dbManager = DBManager.getInstance(context) - dao = dbManager.downloadDao - historyDao = dbManager.historyDao - commandTemplateDao = dbManager.commandTemplateDao - logRepo = LogRepository(dbManager.logDao) - resultRepo = ResultRepository(dbManager.resultDao, commandTemplateDao, context) - ytdlpUtil = YTDLPUtil(context, commandTemplateDao) - alarmScheduler = AlarmScheduler(context) - sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) - eventBus = EventBus.getDefault() - - sharedPreferences.edit().putStringSet("ytdlp_active_downloads", setOf()).apply() - - val confTmp = Configuration(context.resources.configuration) - val locale = if (Build.VERSION.SDK_INT < 33) { - sharedPreferences.getString("app_language", "")!!.ifEmpty { Locale.getDefault().language } - }else{ - Locale.getDefault().language - }.run { - split("-") - }.run { - if (this.size == 1) Locale(this[0]) else Locale(this[0], this[1]) - } - confTmp.setLocale(locale) - val metrics = DisplayMetrics() - resources = Resources(context.assets, metrics, confTmp) - - openDownloadQueue = PendingIntent.getActivity( - context, - 1000000000, - Intent(context, MainActivity::class.java).run { - action = Intent.ACTION_VIEW - putExtra("destination", "Queue") - }, - PendingIntent.FLAG_IMMUTABLE - ) - - val time = System.currentTimeMillis() + 6000 - queueState = if (priorityItemIDs.isEmpty()) { - dao.getActiveQueuedScheduledDownloadsUntil(time) - }else { - dao.getActiveQueuedScheduledDownloadsUntilWithPriority(time, priorityItemIDs) - } - - observeJob = CoroutineScope(SupervisorJob() + Dispatchers.IO).launch { - supervisorScope { - queueState.distinctUntilChanged().collectLatest { queue -> - val activeDownloads = queue - .asSequence() - .filter { it.status == DownloadRepository.Status.Active.toString() } - .map { it.id } - - val activeDownloadsCount = activeDownloads.count() - - val queueItems = queue - .asSequence() - .filter { it.status != DownloadRepository.Status.Active.toString() } - - val queueCount = queueItems.count() - if (queueCount == 0 && activeDownloadsCount == 0) { - observeJob?.cancel() - return@collectLatest - } - - if (!isRunning) { - observeJob?.cancel() - return@collectLatest - } - - val useScheduler = sharedPreferences.getBoolean("use_scheduler", false) - if (useScheduler){ - if (queueItems.none{ it.downloadStartTime > 0L } && activeDownloadsCount == 0 && !alarmScheduler.isDuringTheScheduledTime()) { - observeJob?.cancel() - return@collectLatest - } - } - - val concurrentDownloads = sharedPreferences.getInt("concurrent_downloads", 1) - activeDownloadsCount - if (concurrentDownloads > 0) { - - supervisorScope { - //free spots are open - val queuedDownloads = if (priorityItemIDs.isNotEmpty() && !continueAfterPriorityIds) { - queueItems - .filter { priorityItemIDs.contains(it.id) } - .toList() - }else{ - queueItems.toList() - }.take(concurrentDownloads) - - - priorityItemIDs.removeAll(queuedDownloads.map { it.id }) - val downloadsToStart = queuedDownloads.filter { it.id !in activeDownloads } - downloadsToStart.forEach { downloadItem -> - val notification = notificationUtil.createDownloadServiceNotification(openDownloadQueue, downloadItem.title.ifEmpty { downloadItem.url }) - notificationUtil.notify(downloadItem.id.toInt(), notification) - launchDownloadJob(context, downloadItem) - } - } - } - - if (priorityItemIDs.isEmpty() && !continueAfterPriorityIds) { - observeJob?.cancel() - return@collectLatest - } - - } - } - } - } - - - @OptIn(ExperimentalStdlibApi::class) - private fun launchDownloadJob(context: Context, downloadItem: DownloadItem) = CoroutineScope(Dispatchers.IO).launch { - val writtenPath = downloadItem.format.format_note.contains("-P ") - val noCache = writtenPath || (!sharedPreferences.getBoolean("cache_downloads", true) && File( - FileUtil.formatPath(downloadItem.downloadPath)).canWrite()) - - val request = ytdlpUtil.buildYoutubeDLRequest(downloadItem) - - val cacheDir = FileUtil.getCachePath(context) - val tempFileDir = File(cacheDir, downloadItem.id.toString()) - tempFileDir.delete() - tempFileDir.mkdirs() - - val downloadLocation = downloadItem.downloadPath - val keepCache = sharedPreferences.getBoolean("keep_cache", false) - val logDownloads = sharedPreferences.getBoolean("log_downloads", false) && !downloadItem.incognito - - val commandString = ytdlpUtil.parseYTDLRequestString(request) - val initialLogDetails = "Downloading:\n" + - "Title: ${downloadItem.title}\n" + - "URL: ${downloadItem.url}\n" + - "Type: ${downloadItem.type}\n" + - "Command:\n$commandString \n\n" - val logString = StringBuilder(initialLogDetails) - val logItem = LogItem( - 0, - downloadItem.title.ifBlank { downloadItem.url }, - logString.toString(), - downloadItem.format, - downloadItem.type, - System.currentTimeMillis(), - ) - - - runBlocking { - if (logDownloads) logItem.id = logRepo.insert(logItem) - downloadItem.logID = logItem.id - downloadItem.status = DownloadRepository.Status.Active.toString() - dao.update(downloadItem) - - CoroutineScope(Dispatchers.IO).launch { - delay(1500) - //update item if its incomplete - resultRepo.updateDownloadItem(downloadItem)?.apply { - val status = dao.checkStatus(id) - if (status == DownloadRepository.Status.Active){ - dao.updateWithoutUpsert(this) - } - } - } - } - - runCatching { - YoutubeDL.getInstance().destroyProcessById(downloadItem.id.toString()) - YoutubeDL.getInstance().execute(request, downloadItem.id.toString()){ progress, _, line -> - eventBus.post(DownloadProgress(progress.toInt(), line, downloadItem.id, downloadItem.logID)) - val title: String = downloadItem.title.ifEmpty { downloadItem.url } - notificationUtil.updateDownloadNotification( - downloadItem.id.toInt(), - line, progress.toInt(), 0, title, - NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID - ) - CoroutineScope(Dispatchers.IO).launch { - if (logDownloads) { - logRepo.update(line, logItem.id) - logString.append("$line\n") - } - } - } - }.onSuccess { - resultRepo.updateDownloadItem(downloadItem)?.apply { - dao.updateWithoutUpsert(this) - } - //val wasQuickDownloaded = resultDao.getCountInt() == 0 - runBlocking { - var finalPaths = mutableListOf() - - if (noCache){ - eventBus.post(DownloadProgress(100, "Scanning Files", downloadItem.id, downloadItem.logID)) - val outputSequence = it.out.split("\n") - finalPaths = - outputSequence.asSequence() - .filter { it.startsWith("'/storage") } - .map { it.removeSuffix("\n") } - .map { it.removeSurrounding("'", "'") } - .toMutableList() - - finalPaths.addAll( - outputSequence.asSequence() - .filter { it.startsWith("[SplitChapters]") && it.contains("Destination: ") } - .map { it.split("Destination: ")[1] } - .map { it.removeSuffix("\n") } - .toList() - ) - - finalPaths.sortBy { File(it).lastModified() } - finalPaths = finalPaths.distinct().toMutableList() - FileUtil.scanMedia(finalPaths, context) - }else{ - //move file from internal to set download directory - eventBus.post(DownloadProgress(100, "Moving file to ${FileUtil.formatPath(downloadLocation)}", downloadItem.id, downloadItem.logID)) - try { - finalPaths = withContext(Dispatchers.IO){ - FileUtil.moveFile(tempFileDir.absoluteFile, - context, downloadLocation, keepCache){ p -> - eventBus.post(DownloadProgress(p, "Moving file to ${FileUtil.formatPath(downloadLocation)}", downloadItem.id, downloadItem.logID)) - } - }.filter { !it.matches("\\.(description)|(txt)\$".toRegex()) }.toMutableList() - - if (finalPaths.isNotEmpty()){ - eventBus.post(DownloadProgress(100, "Moved file to ${FileUtil.formatPath(downloadLocation)}", downloadItem.id, downloadItem.logID)) - } - }catch (e: Exception){ - e.printStackTrace() - handler.postDelayed({ - Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show() - }, 1000) - } - } - - - val nonMediaExtensions = mutableListOf().apply { - addAll(context.getStringArray(R.array.thumbnail_containers_values)) - addAll(context.getStringArray(R.array.sub_formats_values).filter { it.isNotBlank() }) - add("description") - add("txt") - } - finalPaths = finalPaths.filter { path -> !nonMediaExtensions.any { path.endsWith(it) } }.toMutableList() - FileUtil.deleteConfigFiles(request) - - //put download in history - if (!downloadItem.incognito) { - if (request.hasOption("--download-archive") && finalPaths.isEmpty()) { - handler.postDelayed({ - Toast.makeText(context, resources.getString(R.string.download_already_exists), Toast.LENGTH_LONG).show() - }, 100) - }else{ - if (finalPaths.isNotEmpty()) { - val unixTime = System.currentTimeMillis() / 1000 - finalPaths.first().apply { - val file = File(this) - var duration = downloadItem.duration - val d = file.getMediaDuration(context) - if (d > 0) duration = d.toStringDuration(Locale.US) - - downloadItem.format.filesize = file.length() - downloadItem.format.container = file.extension - downloadItem.duration = duration - } - - val historyItem = HistoryItem(0, - downloadItem.url, - downloadItem.title, - downloadItem.author, - downloadItem.duration, - downloadItem.thumb, - downloadItem.type, - unixTime, - finalPaths, - downloadItem.website, - downloadItem.format, - downloadItem.format.filesize, - downloadItem.id, - commandString) - historyDao.insert(historyItem) - } - } - } - - withContext(Dispatchers.Main) { - notificationUtil.cancelDownloadNotification(downloadItem.id.toInt()) - notificationUtil.createDownloadFinished( - downloadItem.id, downloadItem.title, downloadItem.type, if (finalPaths.isEmpty()) null else finalPaths, resources - ) - } - -// if (wasQuickDownloaded && createResultItem){ -// runCatching { -// eventBus.post(WorkerProgress(100, "Creating Result Items", downloadItem.id)) -// runBlocking { -// infoUtil.getFromYTDL(downloadItem.url).forEach { res -> -// if (res != null) { -// resultDao.insert(res) -// } -// } -// } -// } -// } - - dao.delete(downloadItem.id) - - if (logDownloads){ - logRepo.update(initialLogDetails + it.out, logItem.id, true) - } - } - - }.onFailure { - FileUtil.deleteConfigFiles(request) - withContext(Dispatchers.Main){ - notificationUtil.cancelDownloadNotification(downloadItem.id.toInt()) - } - if (!this.isActive) return@onFailure - if (it is YoutubeDL.CanceledException) return@onFailure - if (it.message?.contains("JSONDecodeError") == true) { - val cachePath = "${FileUtil.getCachePath(context)}infojsons" - val infoJsonName = MessageDigest.getInstance("MD5").digest(downloadItem.url.toByteArray()).toHexString() - FileUtil.deleteFile("${cachePath}/${infoJsonName}.info.json") - } - - if(it.message != null){ - if (logDownloads){ - logRepo.update(it.message!!, logItem.id) - }else{ - logString.append("${it.message}\n") - logItem.content = logString.toString() - val logID = logRepo.insert(logItem) - downloadItem.logID = logID - } - } - - tempFileDir.delete() - handler.postDelayed({ - Toast.makeText(context, it.message, Toast.LENGTH_LONG).show() - }, 1000) - - Log.e(TAG, context.getString(R.string.failed_download), it) - notificationUtil.cancelDownloadNotification(downloadItem.id.toInt()) - - downloadItem.status = DownloadRepository.Status.Error.toString() - runBlocking { - dao.update(downloadItem) - } - - notificationUtil.createDownloadErrored( - downloadItem.id, - downloadItem.title.ifEmpty { downloadItem.url }, - it.message, - downloadItem.logID, - resources - ) - - eventBus.post(DownloadProgress(100, it.toString(), downloadItem.id, downloadItem.logID)) - } - } - - companion object { - const val TAG = "DownloadManager" - @Volatile - private var instance: DownloadManager? = null - - fun getInstance() : DownloadManager { - return instance ?: synchronized(this) { - instance ?: DownloadManager().also { - instance = it - } - } - } - } - - class DownloadProgress( - val progress: Int, - val output: String, - val downloadItemID: Long, - val logItemID: Long? - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdl/work/downloader/DownloadWorker.kt b/app/src/main/java/com/deniscerri/ytdl/work/downloader/DownloadWorker.kt deleted file mode 100644 index e7cab134..00000000 --- a/app/src/main/java/com/deniscerri/ytdl/work/downloader/DownloadWorker.kt +++ /dev/null @@ -1,104 +0,0 @@ -package com.deniscerri.ytdl.work.downloader - -import android.annotation.SuppressLint -import android.app.PendingIntent -import android.content.Context -import android.content.Intent -import android.content.SharedPreferences -import android.content.pm.ServiceInfo -import android.content.res.Configuration -import android.content.res.Resources -import android.os.Build -import android.os.Handler -import android.os.Looper -import android.util.DisplayMetrics -import android.util.Log -import android.widget.Toast -import androidx.preference.PreferenceManager -import androidx.work.CoroutineWorker -import androidx.work.ForegroundInfo -import androidx.work.WorkManager -import androidx.work.WorkerParameters -import com.afollestad.materialdialogs.utils.MDUtil.getStringArray -import com.deniscerri.ytdl.App -import com.deniscerri.ytdl.MainActivity -import com.deniscerri.ytdl.R -import com.deniscerri.ytdl.database.DBManager -import com.deniscerri.ytdl.database.dao.CommandTemplateDao -import com.deniscerri.ytdl.database.dao.DownloadDao -import com.deniscerri.ytdl.database.dao.HistoryDao -import com.deniscerri.ytdl.database.models.DownloadItem -import com.deniscerri.ytdl.database.models.HistoryItem -import com.deniscerri.ytdl.database.models.LogItem -import com.deniscerri.ytdl.database.repository.DownloadRepository -import com.deniscerri.ytdl.database.repository.LogRepository -import com.deniscerri.ytdl.database.repository.ResultRepository -import com.deniscerri.ytdl.util.Extensions.getMediaDuration -import com.deniscerri.ytdl.util.Extensions.toStringDuration -import com.deniscerri.ytdl.util.FileUtil -import com.deniscerri.ytdl.util.NotificationUtil -import com.deniscerri.ytdl.util.extractors.YTDLPUtil -import com.deniscerri.ytdl.work.AlarmScheduler -import com.deniscerri.ytdl.work.isRunning -import com.deniscerri.ytdl.work.setForegroundSafely -import com.yausername.youtubedl_android.YoutubeDL -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.supervisorScope -import kotlinx.coroutines.withContext -import org.greenrobot.eventbus.EventBus -import java.io.File -import java.security.MessageDigest -import java.util.Locale - - -class DownloadWorker(private val context: Context,workerParams: WorkerParameters) : CoroutineWorker(context, workerParams) { - private lateinit var workManager: WorkManager - private lateinit var downloadManager: DownloadManager - - override suspend fun getForegroundInfo(): ForegroundInfo { - val workNotif = NotificationUtil(App.instance).createDefaultWorkerNotification() - - return ForegroundInfo( - 1000000000, - workNotif, - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC - } else { - 0 - }, - ) - } - - @SuppressLint("RestrictedApi") - override suspend fun doWork(): Result { - workManager = WorkManager.getInstance(context) - if (workManager.isRunning("download")) return Result.Failure() - - setForegroundSafely() - downloadManager = DownloadManager.getInstance() - - val priorityItemIDs = (inputData.getLongArray("priority_item_ids") ?: longArrayOf()).toMutableList() - val continueAfterPriorityIds = inputData.getBoolean("continue_after_priority_ids", true) - downloadManager.startDownload(context, priorityItemIDs, continueAfterPriorityIds) - - var active = downloadManager.isRunning - while (active) { - active = !isStopped && downloadManager.isRunning - } - - return Result.Success() - } - - companion object { - const val TAG = "DownloadWorker" - } - -} \ No newline at end of file