From d860511ce9ae291358e634ff90fcc1ae637b5c0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20=C3=87erri?= <64997243+deniscerri@users.noreply.github.com> Date: Sat, 11 Feb 2023 18:26:13 +0100 Subject: [PATCH] implemented download scheduling --- app/baseline-profiles-rules.pro | 1 + app/build.gradle | 34 ++- .../1.json | 20 +- .../ytdlnis/BaselineProfileGenerator.kt | 18 ++ .../main/java/com/deniscerri/ytdlnis/App.kt | 37 +-- .../com/deniscerri/ytdlnis/MainActivity.kt | 120 +------- .../deniscerri/ytdlnis/database/Converters.kt | 1 + .../ytdlnis/database/dao/DownloadDao.kt | 3 + .../ytdlnis/database/models/DownloadItem.kt | 7 +- .../database/repository/DownloadRepository.kt | 14 +- .../database/viewmodel/DownloadViewModel.kt | 22 +- .../CancelDownloadNotificationReceiver.kt | 2 +- .../deniscerri/ytdlnis/ui/DownloadFragment.kt | 282 ------------------ .../deniscerri/ytdlnis/ui/HistoryFragment.kt | 12 +- .../com/deniscerri/ytdlnis/ui/HomeFragment.kt | 5 +- .../ui/downloadcard/DownloadAudioFragment.kt | 1 - .../downloadcard/DownloadBottomSheetDialog.kt | 70 ++++- .../downloadcard/DownloadCommandFragment.kt | 135 ++++++++- .../DownloadMultipleBottomSheetDialog.kt | 43 +++ .../ui/downloadcard/DownloadVideoFragment.kt | 11 +- .../deniscerri/ytdlnis/work/DownloadWorker.kt | 14 +- .../main/res/layout/download_bottom_sheet.xml | 3 +- .../res/layout/fragment_download_audio.xml | 3 +- .../res/layout/fragment_download_command.xml | 83 ++---- .../res/layout/fragment_download_video.xml | 63 ++-- app/src/main/res/layout/fragment_history.xml | 2 +- app/src/main/res/menu/bottom_nav_menu.xml | 5 +- app/src/main/res/values/strings.xml | 2 +- build.gradle | 4 +- 29 files changed, 420 insertions(+), 597 deletions(-) create mode 100644 app/baseline-profiles-rules.pro create mode 100644 app/src/androidTest/java/com/deniscerri/ytdlnis/BaselineProfileGenerator.kt delete mode 100644 app/src/main/java/com/deniscerri/ytdlnis/ui/DownloadFragment.kt diff --git a/app/baseline-profiles-rules.pro b/app/baseline-profiles-rules.pro new file mode 100644 index 00000000..0674e774 --- /dev/null +++ b/app/baseline-profiles-rules.pro @@ -0,0 +1 @@ +-dontobfuscate \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index f3865898..454182a7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,3 +1,6 @@ +import com.android.build.gradle.internal.dsl.ManagedVirtualDevice + + plugins { id 'com.android.application' id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin' version '2.0.1' @@ -48,6 +51,19 @@ android { androidTest.assets.srcDirs += files("$projectDir/schemas".toString()) } + + testOptions{ + managedDevices { + devices{ + pixel4Api31(com.android.build.api.dsl.ManagedVirtualDevice){ + device = "Pixel 2" + apiLevel = 31 + systemImageSource = "aosp" + } + } + } + } + buildTypes { release { minifyEnabled false @@ -55,6 +71,16 @@ android { debuggable true signingConfig signingConfigs.debug } + + benchmark { + initWith buildTypes.release + proguardFiles 'baseline-profiles-rules.pro' + } + + debug { + initWith buildTypes.release + proguardFiles 'baseline-profiles-rules.pro' + } } buildFeatures { @@ -95,7 +121,7 @@ dependencies { implementation "androidx.appcompat:appcompat:$appCompatVer" implementation "androidx.constraintlayout:constraintlayout:2.1.4" - implementation 'com.google.android.material:material:1.7.0' + implementation 'com.google.android.material:material:1.8.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.core:core:1.9.0' implementation 'androidx.recyclerview:recyclerview:1.2.1' @@ -104,9 +130,15 @@ dependencies { implementation 'androidx.navigation:navigation-ui:2.5.3' implementation 'androidx.core:core-ktx:1.9.0' implementation 'androidx.core:core-ktx:1.9.0' + implementation 'androidx.test.ext:junit-ktx:1.1.5' testImplementation "junit:junit:$junitVer" + androidTestImplementation "junit:junit:$junitVer" androidTestImplementation "androidx.test.ext:junit:$androidJunitVer" androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVer" + androidTestImplementation "androidx.benchmark:benchmark-macro-junit4:1.2.0-alpha09" + androidTestImplementation "androidx.test:runner:1.5.2" + androidTestImplementation "androidx.test:core:1.5.0" + androidTestImplementation "androidx.test:rules:1.5.0" implementation "io.reactivex.rxjava2:rxandroid:2.1.0" implementation "com.devbrackets.android:exomedia:4.3.0" diff --git a/app/schemas/com.deniscerri.ytdlnis.database.DBManager/1.json b/app/schemas/com.deniscerri.ytdlnis.database.DBManager/1.json index d6834da9..5a6a700d 100644 --- a/app/schemas/com.deniscerri.ytdlnis.database.DBManager/1.json +++ b/app/schemas/com.deniscerri.ytdlnis.database.DBManager/1.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 1, - "identityHash": "e53569dbe0c02d62eda598075d73d5b1", + "identityHash": "9197d2ce894417365b9012ace8bcf284", "entities": [ { "tableName": "results", @@ -160,7 +160,7 @@ }, { "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, `removeAudio` INTEGER NOT NULL DEFAULT 0, `downloadPath` TEXT NOT NULL, `website` TEXT NOT NULL, `downloadSize` TEXT NOT NULL, `playlistTitle` TEXT NOT NULL, `embedSubs` INTEGER NOT NULL, `addChapters` INTEGER NOT NULL, `SaveThumb` INTEGER NOT NULL, `status` TEXT NOT NULL DEFAULT 'Queued')", + "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, `downloadPath` TEXT NOT NULL, `website` TEXT NOT NULL, `downloadSize` TEXT NOT NULL, `playlistTitle` TEXT NOT NULL, `embedSubs` INTEGER NOT NULL, `addChapters` INTEGER NOT NULL, `SaveThumb` INTEGER NOT NULL, `status` TEXT NOT NULL DEFAULT 'Queued', `downloadStartTime` INTEGER NOT NULL DEFAULT 0)", "fields": [ { "fieldPath": "id", @@ -210,13 +210,6 @@ "affinity": "TEXT", "notNull": true }, - { - "fieldPath": "removeAudio", - "columnName": "removeAudio", - "affinity": "INTEGER", - "notNull": true, - "defaultValue": "0" - }, { "fieldPath": "downloadPath", "columnName": "downloadPath", @@ -265,6 +258,13 @@ "affinity": "TEXT", "notNull": true, "defaultValue": "'Queued'" + }, + { + "fieldPath": "downloadStartTime", + "columnName": "downloadStartTime", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" } ], "primaryKey": { @@ -312,7 +312,7 @@ "views": [], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'e53569dbe0c02d62eda598075d73d5b1')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '9197d2ce894417365b9012ace8bcf284')" ] } } \ No newline at end of file diff --git a/app/src/androidTest/java/com/deniscerri/ytdlnis/BaselineProfileGenerator.kt b/app/src/androidTest/java/com/deniscerri/ytdlnis/BaselineProfileGenerator.kt new file mode 100644 index 00000000..d7d54dd4 --- /dev/null +++ b/app/src/androidTest/java/com/deniscerri/ytdlnis/BaselineProfileGenerator.kt @@ -0,0 +1,18 @@ +package com.deniscerri.ytdlnis + +import androidx.benchmark.macro.junit4.BaselineProfileRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class BaselineProfileGenerator { + @get:Rule val baselineProfileRule = BaselineProfileRule() + + @Test + fun startup() = + baselineProfileRule.collectBaselineProfile(packageName = "com.deniscerri.ytdl") { + startActivityAndWait() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdlnis/App.kt b/app/src/main/java/com/deniscerri/ytdlnis/App.kt index 708c5679..618353bc 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/App.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/App.kt @@ -48,40 +48,13 @@ class App : Application() { .getInt("concurrent_downloads", 1))) .build()) - configureRxJavaErrorHandler() - Completable.fromAction { initLibraries() }.subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(object : DisposableCompletableObserver() { - override fun onComplete() { - // it worked - } - - override fun onError(e: Throwable) { - if (BuildConfig.DEBUG) Log.e(TAG, "failed to initialize youtubedl-android", e) - Toast.makeText( - applicationContext, - "initialization failed: " + e.localizedMessage, - Toast.LENGTH_SHORT - ).show() - } - }) - } - - private fun configureRxJavaErrorHandler() { - var err = Throwable() - RxJavaPlugins.setErrorHandler { e: Throwable -> - if (e is UndeliverableException) { - // As UndeliverableException is a wrapper, get the cause of it to get the "real" exception - err = e.cause!! - } - if (e is InterruptedException) { - // fine, some blocking code was interrupted by a dispose call - return@setErrorHandler - } - Log.e(TAG, "Undeliverable exception received, not sure what to do", err) + try { + initLibraries() + }catch (e: Exception){ + Toast.makeText(this@App, e.message, Toast.LENGTH_SHORT).show() + e.printStackTrace() } } - @Throws(YoutubeDLException::class) private fun initLibraries() { YoutubeDL.getInstance().init(this) diff --git a/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt b/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt index 53de4ea3..35906773 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt @@ -1,7 +1,6 @@ package com.deniscerri.ytdlnis import android.Manifest -import android.app.ActivityManager import android.content.Context import android.content.DialogInterface import android.content.Intent @@ -21,11 +20,9 @@ import androidx.core.view.WindowInsetsCompat import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.work.WorkManager -import com.deniscerri.ytdlnis.database.models.ResultItem import com.deniscerri.ytdlnis.databinding.ActivityMainBinding import com.deniscerri.ytdlnis.service.IDownloaderListener import com.deniscerri.ytdlnis.service.IDownloaderService -import com.deniscerri.ytdlnis.ui.DownloadFragment import com.deniscerri.ytdlnis.ui.HistoryFragment import com.deniscerri.ytdlnis.ui.HomeFragment import com.deniscerri.ytdlnis.ui.MoreFragment @@ -47,7 +44,6 @@ class MainActivity : AppCompatActivity() { lateinit var context: Context private lateinit var homeFragment: HomeFragment private lateinit var historyFragment: HistoryFragment - private lateinit var downloadFragment: DownloadFragment private lateinit var workManager: WorkManager override fun onCreate(savedInstanceState: Bundle?) { @@ -63,7 +59,6 @@ class MainActivity : AppCompatActivity() { homeFragment = HomeFragment() historyFragment = HistoryFragment() - downloadFragment = DownloadFragment() moreFragment = MoreFragment() initFragments() binding.bottomNavigationView.setOnItemSelectedListener { item: MenuItem -> @@ -79,18 +74,10 @@ class MainActivity : AppCompatActivity() { R.id.history -> { if (lastFragment === historyFragment) { historyFragment.scrollToTop() - } else { - this.title = getString(R.string.history) - } - replaceFragment(historyFragment) - } - R.id.downloads -> { - if (lastFragment === downloadFragment) { - downloadFragment.scrollToTop() } else { this.title = getString(R.string.downloads) } - replaceFragment(downloadFragment) + replaceFragment(historyFragment) } R.id.more -> { if (lastFragment === moreFragment) { @@ -129,11 +116,14 @@ class MainActivity : AppCompatActivity() { Log.e(TAG, action) homeFragment = HomeFragment() historyFragment = HistoryFragment() - downloadFragment = DownloadFragment() moreFragment = MoreFragment() if (type.equals("application/txt", ignoreCase = true)) { try { - val uri = intent.getParcelableExtra(Intent.EXTRA_STREAM) + val uri = if (Build.VERSION.SDK_INT >= 33){ + intent.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java) + }else{ + intent.getParcelableExtra(Intent.EXTRA_STREAM) + } val `is` = contentResolver.openInputStream(uri!!) val textBuilder = StringBuilder() val reader: Reader = BufferedReader( @@ -164,11 +154,9 @@ class MainActivity : AppCompatActivity() { fm.beginTransaction() .replace(R.id.frame_layout, homeFragment) .add(R.id.frame_layout, historyFragment) - .add(R.id.frame_layout, downloadFragment) .add(R.id.frame_layout, moreFragment) .hide(historyFragment) .hide(moreFragment) - .hide(downloadFragment) .commit() lastFragment = homeFragment listeners = ArrayList() @@ -179,106 +167,10 @@ class MainActivity : AppCompatActivity() { lastFragment = f } - fun startDownloadService( - downloadQueue: ArrayList, - awaitingListener: IDownloaderListener - ) { -// addQueueToDownloads(downloadQueue) -// if (isDownloadServiceRunning) { -// iDownloaderService?.updateQueue(downloadQueue) -// return -// } -// if (!listeners.contains(awaitingListener)) listeners.add(awaitingListener) -// val serviceIntent = Intent(context, DownloaderService::class.java) -// serviceIntent.putParcelableArrayListExtra("queue", downloadQueue) -// context.applicationContext.startService(serviceIntent) -// context.applicationContext.bindService(serviceIntent, serviceConnection, BIND_AUTO_CREATE) - - } - - private fun addQueueToDownloads(downloadQueue: ArrayList) { -// try { -// val sharedPreferences = context.getSharedPreferences("root_preferences", MODE_PRIVATE) -// if (!sharedPreferences.getBoolean("incognito", false)) { -// val databaseManager = DatabaseManager(context) -// for (i in downloadQueue.indices.reversed()) { -// val v = downloadQueue[i] -// v.isQueuedDownload = true -// databaseManager.addToHistory(v) -// } -// databaseManager.close() -// //downloadsFragment.initCards() -// } -// } catch (e: Exception) { -// e.printStackTrace() -// } - } - - fun stopDownloadService() { -// if (!isDownloadServiceRunning) return -// try { -// iDownloaderService!!.removeActivity(this) -// context.applicationContext.unbindService(serviceConnection) -// context.applicationContext.stopService( -// Intent( -// context.applicationContext, -// DownloaderService::class.java -// ) -// ) -// } catch (ignored: Exception) { -// } -// isDownloadServiceRunning = false - } - - fun cancelDownloadService() { - if (!isDownloadServiceRunning) return - iDownloaderService!!.cancelDownload(true) - stopDownloadService() - } - fun cancelAllDownloads() { workManager.cancelAllWork(); } - fun removeItemFromDownloadQueue(video: ResultItem?, type: String?) { - iDownloaderService!!.removeItemFromDownloadQueue(video, type) - } - - fun isDownloadServiceRunning(): Boolean { -// val service = getService(DownloaderService::class.java) -// if (service != null) { -// if (service.foreground) { -// isDownloadServiceRunning = true -// return true -// } -// } - return false - } - - private fun reconnectDownloadService() { -// val service = getService(DownloaderService::class.java) -// if (service != null) { -// val serviceIntent = Intent(context.applicationContext, DownloaderService::class.java) -// serviceIntent.putExtra("rebind", true) -// context.applicationContext.bindService( -// serviceIntent, -// serviceConnection, -// BIND_AUTO_CREATE -// ) -// isDownloadServiceRunning = true -// } - } - - private fun getService(className: Class<*>): ActivityManager.RunningServiceInfo? { - val manager = getSystemService(ACTIVITY_SERVICE) as ActivityManager - for (service in manager.getRunningServices(Int.MAX_VALUE)) { - if (className.name == service.service.className) { - return service - } - } - return null - } - private fun checkUpdate() { val preferences = context.getSharedPreferences("root_preferences", MODE_PRIVATE) if (preferences.getBoolean("update_app", false)) { diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/Converters.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/Converters.kt index 2c464728..302079f1 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/Converters.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/Converters.kt @@ -6,6 +6,7 @@ import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.google.gson.Gson import com.google.gson.reflect.TypeToken import java.lang.reflect.Type +import java.util.Calendar class Converters { 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 ce99029a..abccc1f5 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 @@ -49,4 +49,7 @@ interface DownloadDao { @Query("SELECT * FROM downloads WHERE url=:url AND (status='Error' OR status='Cancelled') LIMIT 1") fun checkIfErrorOrCancelled(url: String) : DownloadItem + + @Query("SELECT * FROM downloads WHERE url=:url AND format=:format AND (status='Error' OR status='Cancelled') LIMIT 1") + fun getUnfinishedByURLAndFormat(url: String, format: String) : DownloadItem } \ 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 bb505639..229b6c37 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 @@ -4,6 +4,7 @@ import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel +import java.util.* @Entity(tableName = "downloads") data class DownloadItem( @@ -16,8 +17,6 @@ data class DownloadItem( val duration: String, var type: DownloadViewModel.Type, var format: Format, - @ColumnInfo(defaultValue = "0") - var removeAudio: Boolean, var downloadPath: String, val website: String, val downloadSize: String, @@ -26,5 +25,7 @@ data class DownloadItem( var addChapters: Boolean, var SaveThumb: Boolean, @ColumnInfo(defaultValue = "Queued") - var status: String + var status: String, + @ColumnInfo(defaultValue = "0") + var downloadStartTime: Long ) \ 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 0813e8fc..5b6db42d 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 @@ -1,6 +1,7 @@ package com.deniscerri.ytdlnis.database.repository import androidx.lifecycle.LiveData +import com.deniscerri.ytdlnis.database.Converters import com.deniscerri.ytdlnis.database.dao.DownloadDao import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.models.ResultItem @@ -49,7 +50,18 @@ class DownloadRepository(private val downloadDao: DownloadDao) { downloadDao.queueAllProcessing() } - fun checkIfPresent(item: ResultItem): DownloadItem{ + fun checkIfPresentForProcessing(item: ResultItem): DownloadItem{ return downloadDao.checkIfErrorOrCancelled(item.url) } + + fun checkIfReDownloadingErroredOrCancelled(item: DownloadItem) : Long { + val converters = Converters() + val format = converters.formatToString(item.format) + return try { + val i = downloadDao.getUnfinishedByURLAndFormat(item.url, format) + i.id + }catch (e: Exception){ + 0L + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/DownloadViewModel.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/DownloadViewModel.kt index aa31388a..7d8b9912 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/DownloadViewModel.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/DownloadViewModel.kt @@ -3,6 +3,7 @@ package com.deniscerri.ytdlnis.database.viewmodel import android.app.Activity import android.app.Application import android.content.SharedPreferences +import android.util.Log import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData @@ -22,6 +23,8 @@ import com.deniscerri.ytdlnis.work.DownloadWorker import com.google.gson.Gson import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import java.util.* +import java.util.concurrent.TimeUnit class DownloadViewModel(application: Application) : AndroidViewModel(application) { private val repository : DownloadRepository @@ -31,8 +34,8 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application val activeDownloads : LiveData> val processingDownloads : LiveData> - private var bestVideoFormat: Format - private var bestAudioFormat: Format + private var bestVideoFormat : Format + private var bestAudioFormat : Format enum class Type { audio, video, command } @@ -90,8 +93,8 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application resultItem.thumb, resultItem.duration, type, - getFormat(resultItem, type), false, - "", resultItem.website, "", resultItem.playlistTitle, embedSubs, addChapters, saveThumb, DownloadRepository.Status.Processing.toString() + getFormat(resultItem, type), + "", resultItem.website, "", resultItem.playlistTitle, embedSubs, addChapters, saveThumb, DownloadRepository.Status.Processing.toString(), 0 ) } @@ -133,7 +136,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application items.forEachIndexed { i, it -> val tmpDownloadItem = downloadItems[i] try { - val item = repository.checkIfPresent(it!!) + val item = repository.checkIfPresentForProcessing(it!!) tmpDownloadItem.id = item.id tmpDownloadItem.status = DownloadRepository.Status.Processing.toString() repository.update(tmpDownloadItem) @@ -162,15 +165,24 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application val context = getApplication().applicationContext items.forEach { it.status = DownloadRepository.Status.Queued.toString() + it.id = repository.checkIfReDownloadingErroredOrCancelled(it) if (it.id == 0L){ val id = repository.insert(it) it.id = id }else repository.update(it) + val currentTime = System.currentTimeMillis() + var delay = if (it.downloadStartTime != 0L){ + it.downloadStartTime - currentTime + } else 0 + if (delay < 0L) delay = 0L + val workRequest = OneTimeWorkRequestBuilder() .setInputData(Data.Builder().putLong("id", it.id).build()) .addTag("download") + .setInitialDelay(delay, TimeUnit.MILLISECONDS) .build() + WorkManager.getInstance(context).beginUniqueWork( it.id.toString(), ExistingWorkPolicy.KEEP, diff --git a/app/src/main/java/com/deniscerri/ytdlnis/receiver/CancelDownloadNotificationReceiver.kt b/app/src/main/java/com/deniscerri/ytdlnis/receiver/CancelDownloadNotificationReceiver.kt index 3110b5c1..a82cd233 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/receiver/CancelDownloadNotificationReceiver.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/receiver/CancelDownloadNotificationReceiver.kt @@ -19,7 +19,7 @@ class CancelDownloadNotificationReceiver : BroadcastReceiver() { if (message != null) { val notificationUtil = NotificationUtil(c) - YoutubeDL.getInstance().destroyProcessById(id.toString()); + YoutubeDL.getInstance().destroyProcessById(id.toString()) WorkManager.getInstance(c).cancelUniqueWork(id.toString()) notificationUtil.cancelDownloadNotification(id) } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/DownloadFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/DownloadFragment.kt deleted file mode 100644 index 5d29ecf0..00000000 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/DownloadFragment.kt +++ /dev/null @@ -1,282 +0,0 @@ -package com.deniscerri.ytdlnis.ui - -import android.app.Activity -import android.content.* -import android.net.Uri -import android.os.Bundle -import android.os.Handler -import android.os.Looper -import android.view.* -import android.view.View.* -import android.widget.* -import androidx.core.content.FileProvider -import androidx.fragment.app.Fragment -import androidx.lifecycle.ViewModelProvider -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.ActiveDownloadAdapter -import com.deniscerri.ytdlnis.adapter.QueuedDownloadAdapter -import com.deniscerri.ytdlnis.database.models.DownloadItem -import com.deniscerri.ytdlnis.database.models.HistoryItem -import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel -import com.deniscerri.ytdlnis.databinding.FragmentHistoryBinding -import com.deniscerri.ytdlnis.util.FileUtil -import com.facebook.shimmer.ShimmerFrameLayout -import com.google.android.material.appbar.AppBarLayout -import com.google.android.material.appbar.MaterialToolbar -import com.google.android.material.bottomsheet.BottomSheetDialog -import com.google.android.material.chip.ChipGroup -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton -import java.io.File - -/** - * A fragment representing a list of Items. - */ -class DownloadFragment : Fragment(), ActiveDownloadAdapter.OnItemClickListener, QueuedDownloadAdapter.OnItemClickListener, - OnClickListener, OnLongClickListener { - private lateinit var downloadViewModel : DownloadViewModel - - private var downloading = false - private var fragmentView: View? = null - private var activity: Activity? = null - private var mainActivity: MainActivity? = null - private var fragmentContext: Context? = null - private var layoutinflater: LayoutInflater? = null - private var shimmerCards: ShimmerFrameLayout? = null - private var topAppBar: MaterialToolbar? = null - private var activeRecyclerView: RecyclerView? = null - private var othersRecyclerView: RecyclerView? = null - private var downloadsAdapter: ActiveDownloadAdapter? = null - private var queuedDownloadsAdapter: QueuedDownloadAdapter? = null - private var bottomSheet: BottomSheetDialog? = null - private var sortSheet: BottomSheetDialog? = null - private var uiHandler: Handler? = null - private var noResults: RelativeLayout? = null - private var websiteGroup: ChipGroup? = null - private var downloadsList: List? = null - private var allDownloadsList: List? = null - private var selectedObjects: ArrayList? = null - private var deleteFab: ExtendedFloatingActionButton? = null - private var fileUtil: FileUtil? = null - - private var _binding : FragmentHistoryBinding? = null - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - _binding = FragmentHistoryBinding.inflate(inflater, container, false) - fragmentView = inflater.inflate(R.layout.fragment_downloads, container, false) - activity = getActivity() - mainActivity = activity as MainActivity? - - return fragmentView - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - fragmentContext = context - layoutinflater = LayoutInflater.from(context) - topAppBar = view.findViewById(R.id.downloads_toolbar) - noResults = view.findViewById(R.id.no_results) - websiteGroup = view.findViewById(R.id.website_chip_group) - deleteFab = view.findViewById(R.id.delete_selected_fab) - fileUtil = FileUtil() - deleteFab?.tag = "deleteSelected" - deleteFab?.setOnClickListener(this) - uiHandler = Handler(Looper.getMainLooper()) - selectedObjects = ArrayList() - downloading = mainActivity!!.isDownloadServiceRunning() - - - downloadsList = mutableListOf() - allDownloadsList = mutableListOf() - - downloadsAdapter = - ActiveDownloadAdapter( - this, - requireActivity() - ) - activeRecyclerView = view.findViewById(R.id.recyclerviewactivedownloads) - activeRecyclerView?.layoutManager = LinearLayoutManager(context) - activeRecyclerView?.adapter = downloadsAdapter - - queuedDownloadsAdapter = - QueuedDownloadAdapter( - this, - requireActivity() - ) - othersRecyclerView = view.findViewById(R.id.recyclerviewotherdownloads) - othersRecyclerView?.layoutManager = LinearLayoutManager(context) - othersRecyclerView?.adapter = queuedDownloadsAdapter - - noResults?.visibility = GONE - shimmerCards?.visibility = GONE - - - downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] - downloadViewModel.activeDownloads.observe(viewLifecycleOwner){ - // update active - } - downloadViewModel.queuedDownloads.observe(viewLifecycleOwner){ - // update queued - } - } - - fun scrollToTop() { - activeRecyclerView!!.scrollToPosition(0) - Handler(Looper.getMainLooper()).post { - (topAppBar!!.parent as AppBarLayout).setExpanded( - true, - true - ) - } - } - - override fun onLongClick(v: View): Boolean { - val id = v.id - if (id == R.id.bottom_sheet_link) { - copyLinkToClipBoard(v.tag as Long) - return true - } - return false - } - - private fun removeSelectedItems() { -// if (bottomSheet != null) bottomSheet!!.hide() -// val deleteFile = booleanArrayOf(false) -// val deleteDialog = MaterialAlertDialogBuilder(fragmentContext!!) -// deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items)) -// deleteDialog.setMultiChoiceItems( -// arrayOf(getString(R.string.delete_files_too)), -// booleanArrayOf(false) -// ) { _: DialogInterface?, _: Int, b: Boolean -> deleteFile[0] = b } -// deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() } -// deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int -> -// for (item in selectedObjects!!){ -// historyViewModel.delete(item, deleteFile[0]) -// } -// selectedObjects = ArrayList() -// historyAdapter!!.clearCheckeditems() -// deleteFab!!.visibility = GONE -// } -// deleteDialog.show() - } - - private fun removeItem(id: Long) { -// if (bottomSheet != null) bottomSheet!!.hide() -// val deleteFile = booleanArrayOf(false) -// val v = findItem(id) -// val deleteDialog = MaterialAlertDialogBuilder(fragmentContext!!) -// deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + v!!.title + "\"!") -// deleteDialog.setMultiChoiceItems( -// arrayOf(getString(R.string.delete_file_too)), -// booleanArrayOf(false) -// ) { _: DialogInterface?, _: Int, b: Boolean -> deleteFile[0] = b } -// deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() } -// deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int -> -// historyViewModel.delete(v, deleteFile[0]) -// } -// deleteDialog.show() - } - - private fun copyLinkToClipBoard(id: Long) { - val url = findItem(id)?.url - val clipboard = context?.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager - val clip = ClipData.newPlainText(getString(R.string.url), url) - clipboard.setPrimaryClip(clip) - if (bottomSheet != null) bottomSheet!!.hide() - Toast.makeText(context, getString(R.string.link_copied_to_clipboard), Toast.LENGTH_SHORT) - .show() - } - - private fun openLinkIntent(id: Long) { - val url = findItem(id)?.url - val i = Intent(Intent.ACTION_VIEW) - i.data = Uri.parse(url) - if (bottomSheet != null) bottomSheet!!.hide() - startActivity(i) - } - - private fun openFileIntent(id: Long) { - val downloadPath = findItem(id)!!.downloadPath - val file = File(downloadPath) - val uri = FileProvider.getUriForFile( - fragmentContext!!, - fragmentContext!!.packageName + ".fileprovider", - file - ) - val mime = mainActivity!!.contentResolver.getType(uri) - val i = Intent(Intent.ACTION_VIEW) - i.setDataAndType(uri, mime) - i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - startActivity(i) - } - - override fun onCardClick(videoID: Long, isPresent: Boolean) { - bottomSheet = BottomSheetDialog(fragmentContext!!) - bottomSheet!!.requestWindowFeature(Window.FEATURE_NO_TITLE) - bottomSheet!!.setContentView(R.layout.history_item_details_bottom_sheet) - val video = findItem(videoID) - val title = bottomSheet!!.findViewById(R.id.bottom_sheet_title) - title!!.text = video!!.title - val author = bottomSheet!!.findViewById(R.id.bottom_sheet_author) - author!!.text = video.author - val link = bottomSheet!!.findViewById