From 51ff22abcdacd221b803154404ceeb4b740469c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20=C3=87erri?= <64997243+deniscerri@users.noreply.github.com> Date: Sun, 8 Jan 2023 00:32:46 +0100 Subject: [PATCH] made file transfer a separate worker made the app create default folders since downloading on a fresh install wouldnt work without checking the settings first, now you can made each download have their own separate temporary folder using an url and download type. this way they can be resumed fixed sponsorblock filters not working on a fresh install fixed custom command notification not being in the proper channel and other small stuff --- app/build.gradle | 7 +- app/src/main/AndroidManifest.xml | 1 - .../main/java/com/deniscerri/ytdlnis/App.kt | 6 + .../deniscerri/ytdlnis/DownloaderService.kt | 141 +++++++++++++----- .../com/deniscerri/ytdlnis/MainActivity.kt | 14 ++ .../ytdlnis/page/CustomCommandActivity.java | 9 +- .../ytdlnis/page/DownloadsFragment.java | 1 + .../ytdlnis/page/settings/SettingsFragment.kt | 7 +- .../com/deniscerri/ytdlnis/util/FileUtil.kt | 30 +++- .../ytdlnis/util/NotificationUtil.kt | 68 ++++++++- .../com/deniscerri/ytdlnis/util/UpdateUtil.kt | 12 +- .../ytdlnis/work/FileTransferWorker.kt | 55 +++++++ app/src/main/res/layout/result_card.xml | 2 +- app/src/main/res/values/strings.xml | 8 +- app/src/main/res/values/styles.xml | 1 - build.gradle | 1 + 16 files changed, 295 insertions(+), 68 deletions(-) create mode 100644 app/src/main/java/com/deniscerri/ytdlnis/work/FileTransferWorker.kt diff --git a/app/build.gradle b/app/build.gradle index 2333c2b0..a32a83dc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -119,7 +119,7 @@ dependencies { implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0' implementation 'com.facebook.shimmer:shimmer:0.5.0' - implementation "androidx.work:work-runtime:$workVer" + implementation "androidx.work:work-runtime-ktx:$workVer" implementation "androidx.room:room-runtime:2.4.3" implementation "androidx.room:room-ktx:2.4.3" @@ -127,7 +127,8 @@ dependencies { androidTestImplementation "androidx.room:room-testing:2.4.3" implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' implementation "androidx.compose.runtime:runtime:$composeVer" - - androidTestImplementation "com.google.truth:truth:1.1.3" + + api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutineVer" + api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVer" } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2c5f8958..f5d5794c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -22,7 +22,6 @@ android:theme="@style/AppTheme" > diff --git a/app/src/main/java/com/deniscerri/ytdlnis/App.kt b/app/src/main/java/com/deniscerri/ytdlnis/App.kt index 308bf2a9..c8cddd94 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/App.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/App.kt @@ -2,10 +2,15 @@ package com.deniscerri.ytdlnis import android.annotation.SuppressLint import android.app.Application +import android.content.Context +import android.content.pm.PackageInfo +import android.content.pm.PackageManager +import android.os.Build import android.util.Log import android.widget.Toast import androidx.preference.PreferenceManager import com.deniscerri.ytdlnis.util.NotificationUtil +import com.deniscerri.ytdlnis.util.UpdateUtil import com.google.android.material.color.DynamicColors import com.yausername.aria2c.Aria2c import com.yausername.ffmpeg.FFmpeg @@ -17,6 +22,7 @@ import io.reactivex.exceptions.UndeliverableException import io.reactivex.observers.DisposableCompletableObserver import io.reactivex.plugins.RxJavaPlugins import io.reactivex.schedulers.Schedulers +import java.io.File class App : Application() { override fun onCreate() { diff --git a/app/src/main/java/com/deniscerri/ytdlnis/DownloaderService.kt b/app/src/main/java/com/deniscerri/ytdlnis/DownloaderService.kt index 9418452b..42c03e5d 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/DownloaderService.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/DownloaderService.kt @@ -7,10 +7,17 @@ import android.content.Context import android.content.Intent import android.net.Uri import android.os.Binder +import android.os.Build import android.os.IBinder +import android.os.SystemClock import android.provider.DocumentsContract +import android.provider.Settings.Global import android.util.Log import android.widget.Toast +import androidx.work.ExistingWorkPolicy +import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.WorkManager +import androidx.work.workDataOf import com.deniscerri.ytdlnis.database.Video import com.deniscerri.ytdlnis.page.CustomCommandActivity import com.deniscerri.ytdlnis.service.DownloadInfo @@ -18,6 +25,10 @@ import com.deniscerri.ytdlnis.service.IDownloaderListener import com.deniscerri.ytdlnis.service.IDownloaderService import com.deniscerri.ytdlnis.util.FileUtil import com.deniscerri.ytdlnis.util.NotificationUtil +import com.deniscerri.ytdlnis.work.FileTransferWorker +import com.deniscerri.ytdlnis.work.FileTransferWorker.Companion.originDir +import com.deniscerri.ytdlnis.work.FileTransferWorker.Companion.title +import com.deniscerri.ytdlnis.work.FileTransferWorker.Companion.downLocation import com.yausername.youtubedl_android.DownloadProgressCallback import com.yausername.youtubedl_android.YoutubeDL import com.yausername.youtubedl_android.YoutubeDLRequest @@ -26,6 +37,7 @@ import io.reactivex.Observable import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.CompositeDisposable import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.* import java.io.File import java.io.FileOutputStream import java.util.* @@ -46,6 +58,8 @@ class DownloaderService : Service() { private var context: Context? = null var downloadProcessID = "processID" private var downloadNotificationID = 0 + private lateinit var notificationChannelID : String + private lateinit var workManager : WorkManager private val callback: (Float, Long?, String?) -> Unit = { progress: Float, _: Long?, line: String? -> @@ -58,7 +72,8 @@ class DownloaderService : Service() { } notificationUtil.updateDownloadNotification( downloadNotificationID, - line!!, progress.toInt(), downloadQueue.size, title + line!!, progress.toInt(), downloadQueue.size, title, + notificationChannelID ) try { for (activity in activities.keys) { @@ -74,17 +89,11 @@ class DownloaderService : Service() { } catch (ignored: Exception) { } } -// private val callback = object : DownloadProgressCallback { -// override fun onProgressUpdate(progress: Float, etaInSeconds: Long, line: String?) { -// override fun onProgressUpdate(progress: Float, etaInSeconds: Long, line: String?) { -// -// } -// } -// } override fun onCreate() { super.onCreate() context = this + this.workManager = WorkManager.getInstance(context!!.applicationContext) } override fun onBind(intent: Intent): IBinder? { @@ -93,9 +102,9 @@ class DownloaderService : Service() { if (intent.getBooleanExtra("rebind", false)) { return binder } - val id = intent.getIntExtra("id", 1) - when (id) { + when (val id = intent.getIntExtra("id", 1)) { NotificationUtil.DOWNLOAD_NOTIFICATION_ID -> { + notificationChannelID = NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID theIntent = Intent(this, MainActivity::class.java) pendingIntent = PendingIntent.getActivity(this, 0, theIntent, PendingIntent.FLAG_IMMUTABLE) @@ -106,21 +115,23 @@ class DownloaderService : Service() { downloadInfo.downloadQueue = downloadQueue val title = downloadInfo.video.title val notification = - App.notificationUtil.createDownloadServiceNotification(pendingIntent, title) + App.notificationUtil.createDownloadServiceNotification(pendingIntent, title, NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID) startForeground(downloadNotificationID, notification) startDownload(downloadQueue) } NotificationUtil.COMMAND_DOWNLOAD_NOTIFICATION_ID -> { + notificationChannelID = NotificationUtil.COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID theIntent = Intent(this, CustomCommandActivity::class.java) pendingIntent = PendingIntent.getActivity(this, 0, theIntent, PendingIntent.FLAG_IMMUTABLE) downloadNotificationID = id val command = intent.getStringExtra("command") - val command_notification = App.notificationUtil.createDownloadServiceNotification( + val commandNotification = App.notificationUtil.createDownloadServiceNotification( pendingIntent, - getString(R.string.running_ytdlp_command) + getString(R.string.running_ytdlp_command), + NotificationUtil.COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID ) - startForeground(downloadNotificationID, command_notification) + startForeground(downloadNotificationID, commandNotification) startCommandDownload(command) } } @@ -129,7 +140,11 @@ class DownloaderService : Service() { override fun onDestroy() { super.onDestroy() - stopForeground(true) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){ + stopForeground(STOP_FOREGROUND_REMOVE) + }else{ + stopForeground(true) + } stopSelf() } @@ -294,6 +309,11 @@ class DownloaderService : Service() { val url = video.getURL() val request = YoutubeDLRequest(url) val type = video.downloadedType + val downloadLocation = getDownloadLocation(type) + + var tempFileDir = File(cacheDir.absolutePath + """/${video.title}##${video.downloadedType}""") + tempFileDir.delete() + tempFileDir.mkdir() val sharedPreferences = context!!.getSharedPreferences("root_preferences", MODE_PRIVATE) val aria2 = sharedPreferences.getBoolean("aria2", false) @@ -312,11 +332,16 @@ class DownloaderService : Service() { request.addOption("--convert-thumbnails", "png") } request.addOption("--no-mtime") - val sponsorBlockFilters = sharedPreferences.getString("sponsorblock_filter", "") - if (!sponsorBlockFilters!!.isEmpty()) { - request.addOption("--sponsorblock-remove", sponsorBlockFilters) + val sponsorBlockFilters = sharedPreferences.getStringSet("sponsorblock_filters", emptySet()) + if (sponsorBlockFilters!!.isNotEmpty()) { + val filters = java.lang.String.join(",", sponsorBlockFilters) + request.addOption("--sponsorblock-remove", filters) } if (type == "audio") { + if (downloadLocation.equals(getString(R.string.music_path))){ + tempFileDir = File(getString(R.string.music_path)) + } + request.addOption("-x") var format = video.audioFormat if (format == null) format = sharedPreferences.getString("audio_format", "") @@ -348,8 +373,12 @@ class DownloaderService : Service() { video.author ) ) - request.addOption("-o", filesDir.absolutePath + "/%(uploader)s - %(title)s.%(ext)s") + request.addOption("-o", tempFileDir.absolutePath + "/%(uploader)s - %(title)s.%(ext)s") } else if (type == "video") { + if (downloadLocation.equals(getString(R.string.video_path))){ + tempFileDir = File(getString(R.string.video_path)) + } + val addChapters = sharedPreferences.getBoolean("add_chapters", false) if (addChapters) { request.addOption("--sponsorblock-mark", "all") @@ -380,7 +409,7 @@ class DownloaderService : Service() { request.addOption("--embed-thumbnail") } } - request.addOption("-o", filesDir.absolutePath + "/%(uploader)s - %(title)s.%(ext)s") + request.addOption("-o", tempFileDir.absolutePath + "/%(uploader)s - %(title)s.%(ext)s") } val disposable = Observable.fromCallable { @@ -389,10 +418,22 @@ class DownloaderService : Service() { .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ - val destDir = Uri.parse(getDownloadLocation(type)).run { - DocumentsContract.buildChildDocumentsUriUsingTree(this, DocumentsContract.getTreeDocumentId(this)) - } - fileUtil.moveFile(filesDir, context!!, destDir) + val workTag = video.getURL() + val workData = workDataOf( + originDir to tempFileDir.absolutePath, + downLocation to downloadLocation, + title to video.title + ) + val fileTransferWorkRequest = OneTimeWorkRequestBuilder() + .addTag(workTag) + .setInputData(workData) + .build() + workManager.enqueueUniqueWork( + workTag, + ExistingWorkPolicy.KEEP, + fileTransferWorkRequest + ) + downloadInfo.downloadPath = fileUtil.formatPath(getDownloadLocation(type)!!) Log.e(TAG, downloadInfo.downloadPath) downloadInfo.downloadType = type @@ -416,10 +457,15 @@ class DownloaderService : Service() { downloadInfo.downloadQueue = videos startDownload(videos) }) { e: Throwable? -> - if (BuildConfig.DEBUG) Log.e(TAG, getString(R.string.failed_download), e) + tempFileDir.delete() + if (BuildConfig.DEBUG) { + Toast.makeText(context, e!!.message, Toast.LENGTH_LONG).show() + Log.e(TAG, getString(R.string.failed_download), e) + } notificationUtil.updateDownloadNotification( NotificationUtil.DOWNLOAD_NOTIFICATION_ID, - getString(R.string.failed_download), 0, 0, downloadQueue.peek()?.title + getString(R.string.failed_download), 0, 0, downloadQueue.peek()?.title, + NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID ) downloadInfo.downloadType = type try { @@ -445,9 +491,22 @@ class DownloaderService : Service() { private fun startCommandDownload(text: String?) { var text = text - if (text!!.startsWith("yt-dlp")) { - text = text.substring(5).trim { it <= ' ' } + if (text!!.startsWith("yt-dlp ")) { + text = text.substring(6).trim { it <= ' ' } } + + val sharedPreferences = context!!.getSharedPreferences("root_preferences", MODE_PRIVATE) + val downloadLocation = sharedPreferences.getString("command_path", getString(R.string.command_path)) + + var tempFileDir = File(cacheDir.absolutePath + "/command") + tempFileDir.delete() + tempFileDir.mkdir() + + if (downloadLocation.equals(getString(R.string.command_path))){ + tempFileDir = File(getString(R.string.command_path)) + } + + val request = YoutubeDLRequest(emptyList()) val commandRegex = "\"([^\"]*)\"|(\\S+)" val m = Pattern.compile(commandRegex).matcher(text) @@ -458,8 +517,7 @@ class DownloaderService : Service() { request.addOption(m.group(2)) } } - val sharedPreferences = context!!.getSharedPreferences("root_preferences", MODE_PRIVATE) - request.addOption("-o", filesDir.absolutePath + "/%(title)s.%(ext)s") + request.addOption("-o", tempFileDir.absolutePath + "/%(title)s.%(ext)s") val disposable = Observable.fromCallable { YoutubeDL.getInstance().execute(request, downloadProcessID, callback) } @@ -467,12 +525,22 @@ class DownloaderService : Service() { .observeOn(AndroidSchedulers.mainThread()) .subscribe({ youtubeDLResponse: YoutubeDLResponse -> downloadInfo.outputLine = youtubeDLResponse.out - val downloadsDir = sharedPreferences.getString("command_path", getString(R.string.command_path)) - val destDir = Uri.parse(downloadsDir).run { - DocumentsContract.buildChildDocumentsUriUsingTree(this, DocumentsContract.getTreeDocumentId(this)) - } - fileUtil.moveFile(filesDir, context!!, destDir) - downloadInfo.outputLine += "\n\nMoved File to ${fileUtil.formatPath(downloadsDir!!)}" + + val workTag = SystemClock.uptimeMillis().toString() + val workData = workDataOf( + originDir to tempFileDir.absolutePath, + downLocation to downloadLocation, + title to "" + ) + val fileTransferWorkRequest = OneTimeWorkRequestBuilder() + .addTag(workTag) + .setInputData(workData) + .build() + workManager.enqueueUniqueWork( + workTag, + ExistingWorkPolicy.KEEP, + fileTransferWorkRequest + ) try { for (activity in activities.keys) { @@ -489,6 +557,7 @@ class DownloaderService : Service() { } }) { e: Throwable -> downloadInfo.outputLine = e.message + tempFileDir.delete() try { for (activity in activities.keys) { activity.runOnUiThread { diff --git a/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt b/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt index 31fecae6..e1ca99ec 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt @@ -30,6 +30,7 @@ import com.deniscerri.ytdlnis.service.IDownloaderService import com.deniscerri.ytdlnis.util.UpdateUtil import com.google.android.material.dialog.MaterialAlertDialogBuilder import java.io.BufferedReader +import java.io.File import java.io.InputStreamReader import java.io.Reader import java.nio.charset.Charset @@ -315,6 +316,17 @@ class MainActivity : AppCompatActivity() { } } + + private fun createDefaultFolders(){ + val audio = File(getString(R.string.music_path)) + val video = File(getString(R.string.video_path)) + val command = File(getString(R.string.command_path)) + + audio.mkdirs() + video.mkdirs() + command.mkdirs() + } + override fun onRequestPermissionsResult( requestCode: Int, permissions: Array, @@ -325,6 +337,8 @@ class MainActivity : AppCompatActivity() { for (i in permissions.indices) { if (grantResults[i] == PackageManager.PERMISSION_DENIED) { createPermissionRequestDialog() + }else{ + createDefaultFolders() } } } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/page/CustomCommandActivity.java b/app/src/main/java/com/deniscerri/ytdlnis/page/CustomCommandActivity.java index 2da2e870..3533daa3 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/page/CustomCommandActivity.java +++ b/app/src/main/java/com/deniscerri/ytdlnis/page/CustomCommandActivity.java @@ -76,7 +76,14 @@ public class CustomCommandActivity extends AppCompatActivity { } public void onDownloadProgress(DownloadInfo info) { - output.append("\n" + info.getOutputLine() + "\n"); + String newInfo = info.getOutputLine(); + if (newInfo.contains("[download]")){ + String temp = output.getText().toString(); + output.setText( + temp.substring(0, temp.lastIndexOf(System.getProperty("line.separator")) - 2) + ); + } + output.append(info.getOutputLine() + " \n"); output.scrollTo(0, output.getHeight()); scrollView.fullScroll(View.FOCUS_DOWN); } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/page/DownloadsFragment.java b/app/src/main/java/com/deniscerri/ytdlnis/page/DownloadsFragment.java index f11cbce1..46d38c76 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/page/DownloadsFragment.java +++ b/app/src/main/java/com/deniscerri/ytdlnis/page/DownloadsFragment.java @@ -162,6 +162,7 @@ public class DownloadsFragment extends Fragment implements DownloadsRecyclerView } databaseManager = new DatabaseManager(context); + //TODO observe file transfer worker here and then update, but first you need to turn this to kotlin and implement proper mvvm try { item.setDownloadedTime(downloadedTime); item.setDownloadPath(downloadPath); diff --git a/app/src/main/java/com/deniscerri/ytdlnis/page/settings/SettingsFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/page/settings/SettingsFragment.kt index c3800cc5..6ed77cfb 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/page/settings/SettingsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/page/settings/SettingsFragment.kt @@ -85,10 +85,7 @@ class SettingsFragment : PreferenceFragmentCompat() { editor.putInt("concurrent_fragments", concurrentFragments!!.value) editor.putString("limit_rate", limitRate!!.text) editor.putBoolean("aria2", aria2!!.isChecked) - editor.putString( - "sponsorblock_filter", - java.lang.String.join(",", sponsorblockFilters!!.values) - ) + editor.putStringSet("sponsorblock_filters", sponsorblockFilters!!.values) editor.putBoolean("embed_subtitles", embedSubtitles!!.isChecked) editor.putBoolean("embed_thumbnail", embedThumbnail!!.isChecked) editor.putBoolean("add_chapters", addChapters!!.isChecked) @@ -178,7 +175,7 @@ class SettingsFragment : PreferenceFragmentCompat() { sponsorblockFilters!!.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any? -> sponsorblockFilters!!.values = newValue as Set? - editor.putString("sponsorblock_filter", java.lang.String.join(",", newValue)) + editor.putStringSet("sponsorblock_filters", newValue) editor.apply() true } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/util/FileUtil.kt b/app/src/main/java/com/deniscerri/ytdlnis/util/FileUtil.kt index f40d2ef5..7034c9b3 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/util/FileUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/util/FileUtil.kt @@ -3,13 +3,17 @@ package com.deniscerri.ytdlnis.util import android.content.Context import android.media.MediaScannerConnection import android.net.Uri +import android.os.FileUtils import android.provider.DocumentsContract import android.util.Log import android.webkit.MimeTypeMap +import android.widget.Toast import com.deniscerri.ytdlnis.DownloaderService import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.database.Video import java.io.File +import java.io.InputStream +import java.io.OutputStream import java.net.URLDecoder import java.nio.charset.StandardCharsets @@ -77,9 +81,8 @@ class FileUtil() { return context.getString(R.string.unfound_file); } - - fun moveFile(originDir : File, context:Context, destDir : Uri){ - originDir.listFiles().forEach { + fun moveFile(originDir : File, context:Context, destDir : Uri, progress: (p: Int) -> Unit){ + originDir.listFiles()?.forEach { val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(it.extension) ?: "*/*" @@ -96,13 +99,28 @@ class FileUtil() { ) ?: return@forEach val inputStream = it.inputStream() - val outputStream = - context.contentResolver.openOutputStream(destUri) ?: return@forEach - inputStream.copyTo(outputStream) + val outputStream = context.contentResolver.openOutputStream(destUri) ?: return@forEach + val fileLength = it.length() + var bytesCopied: Long = 0 + val buffer = ByteArray(DEFAULT_BUFFER_SIZE) + var bytes = inputStream.read(buffer) + + try { + while (bytes >= 0) { + outputStream.write(buffer, 0, bytes) + bytesCopied += bytes + progress((bytesCopied * 100 / fileLength).toInt()) + bytes = inputStream.read(buffer) + } + }catch (e : Exception){ + Toast.makeText(context, e.message, Toast.LENGTH_LONG).show() + } + inputStream.close() outputStream.close() it.delete() } + originDir.delete() } } \ 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 4fe57faf..6a005eea 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/util/NotificationUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/util/NotificationUtil.kt @@ -13,8 +13,9 @@ import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.receiver.NotificationReceiver class NotificationUtil(var context: Context) { - private var notificationBuilder: NotificationCompat.Builder = - NotificationCompat.Builder(context, DOWNLOAD_SERVICE_CHANNEL_ID) + private val downloadNotificationBuilder: NotificationCompat.Builder = NotificationCompat.Builder(context, DOWNLOAD_SERVICE_CHANNEL_ID) + private val commandDownloadNotificationBuilder: NotificationCompat.Builder = NotificationCompat.Builder(context, COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID) + private val fileTransferNotificationBuilder: NotificationCompat.Builder = NotificationCompat.Builder(context, FILE_TRANSFER_CHANNEL_ID) private val notificationManager: NotificationManager = context.getSystemService(NotificationManager::class.java) fun createNotificationChannel() { @@ -38,13 +39,63 @@ class NotificationUtil(var context: Context) { channel = NotificationChannel(COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID, name, importance) channel.description = description notificationManager.createNotificationChannel(channel) + + //file transfers + name = context.getString(R.string.file_transfer_notification_channel_name) + description = context.getString(R.string.file_transfer_notification_channel_description) + channel = NotificationChannel(FILE_TRANSFER_CHANNEL_ID, name, importance) + channel.description = description + notificationManager.createNotificationChannel(channel) } } + fun createFileTransferNotification( + pendingIntent: PendingIntent?, + title: String?, + ) : Notification { + val intent = Intent(context, NotificationReceiver::class.java) + + return fileTransferNotificationBuilder + .setContentTitle(title) + .setCategory(Notification.CATEGORY_PROGRESS) + .setSmallIcon(R.drawable.ic_app_icon) + .setContentText("") + .setPriority(NotificationCompat.PRIORITY_DEFAULT) + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + .setProgress(PROGRESS_MAX, PROGRESS_CURR, false) + .setContentIntent(pendingIntent) + .setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE) + .build() + } + + fun updateFileTransferNotification( + id: Int, + progress: Int + ) { + try { + fileTransferNotificationBuilder.setProgress(100, progress, false) + notificationManager.notify(id, fileTransferNotificationBuilder.build()) + } catch (ignored: Exception) { + } + } + + private fun getBuilder(channel: String) : NotificationCompat.Builder { + when(channel) { + DOWNLOAD_SERVICE_CHANNEL_ID -> { return downloadNotificationBuilder} + COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID -> { return commandDownloadNotificationBuilder } + FILE_TRANSFER_CHANNEL_ID -> { return fileTransferNotificationBuilder } + } + return downloadNotificationBuilder + } + + fun createDownloadServiceNotification( pendingIntent: PendingIntent?, - title: String? + title: String?, + channel: String ): Notification { + val notificationBuilder = getBuilder(channel) + val intent = Intent(context, NotificationReceiver::class.java) intent.putExtra("cancel", "") val cancelNotificationPendingIntent = PendingIntent.getBroadcast( @@ -80,17 +131,21 @@ class NotificationUtil(var context: Context) { desc: String, progress: Int, queue: Int, - title: String? + title: String?, + channel : String ) { + + val notificationBuilder = getBuilder(channel) var contentText = "" - if (queue > 1) contentText += """${queue - 1} ${context.getString(R.string.items_left)}""" + if (queue > 1) contentText += """${queue - 1} ${context.getString(R.string.items_left)}""" + "\n" contentText += desc.replace("\\[.*?\\] ".toRegex(), "") try { notificationBuilder.setProgress(100, progress, false) .setContentTitle(title) .setStyle(NotificationCompat.BigTextStyle().bigText(contentText)) notificationManager.notify(id, notificationBuilder.build()) - } catch (ignored: Exception) { + } catch (e: Exception) { + e.printStackTrace() } } @@ -103,6 +158,7 @@ class NotificationUtil(var context: Context) { const val DOWNLOAD_NOTIFICATION_ID = 1 const val COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID = "2" const val COMMAND_DOWNLOAD_NOTIFICATION_ID = 2 + const val FILE_TRANSFER_CHANNEL_ID = "3" private const val PROGRESS_MAX = 100 private const val PROGRESS_CURR = 0 } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/util/UpdateUtil.kt b/app/src/main/java/com/deniscerri/ytdlnis/util/UpdateUtil.kt index bc195d27..6b900488 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/util/UpdateUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/util/UpdateUtil.kt @@ -24,6 +24,7 @@ import java.io.InputStreamReader import java.net.HttpURLConnection import java.net.URL import java.util.concurrent.atomic.AtomicReference +import java.io.File class UpdateUtil(var context: Context) { private val tag = "UpdateUtil" @@ -163,11 +164,12 @@ class UpdateUtil(var context: Context) { .observeOn(AndroidSchedulers.mainThread()) .subscribe({ status: UpdateStatus -> when (status) { - UpdateStatus.DONE -> Toast.makeText( - context, - context.getString(R.string.ytld_update_success), - Toast.LENGTH_LONG - ).show() + UpdateStatus.DONE -> + Toast.makeText( + context, + context.getString(R.string.ytld_update_success), + Toast.LENGTH_LONG + ).show() UpdateStatus.ALREADY_UP_TO_DATE -> Toast.makeText( context, context.getString(R.string.you_are_in_latest_version), diff --git a/app/src/main/java/com/deniscerri/ytdlnis/work/FileTransferWorker.kt b/app/src/main/java/com/deniscerri/ytdlnis/work/FileTransferWorker.kt new file mode 100644 index 00000000..47f1a480 --- /dev/null +++ b/app/src/main/java/com/deniscerri/ytdlnis/work/FileTransferWorker.kt @@ -0,0 +1,55 @@ +package com.deniscerri.ytdlnis.work + +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.SystemClock +import android.provider.DocumentsContract +import androidx.work.CoroutineWorker +import androidx.work.ForegroundInfo +import androidx.work.WorkerParameters +import com.deniscerri.ytdlnis.MainActivity +import com.deniscerri.ytdlnis.page.CustomCommandActivity +import com.deniscerri.ytdlnis.util.FileUtil +import com.deniscerri.ytdlnis.util.NotificationUtil +import java.io.File + +class FileTransferWorker( + private val context: Context, + workerParams: WorkerParameters +) : CoroutineWorker(context, workerParams) { + + override suspend fun doWork(): Result { + val originDir = File(inputData.getString(originDir)!!) + val downLocation = inputData.getString(downLocation) + val fileTitle = inputData.getString(title) ?: "" + val destDir = Uri.parse(downLocation).run { + DocumentsContract.buildChildDocumentsUriUsingTree(this, DocumentsContract.getTreeDocumentId(this)) + } + + val fileUtil = FileUtil() + + val notificationUtil = NotificationUtil(context) + val intent = Intent(context, MainActivity::class.java) + val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) + val title = "Moving $fileTitle to ${fileUtil.formatPath(downLocation!!)}" + val id : Int = SystemClock.uptimeMillis().toInt() + val notification = notificationUtil.createFileTransferNotification(pendingIntent, title) + val foregroundInfo = ForegroundInfo(id, notification) + setForeground(foregroundInfo) + + fileUtil.moveFile(originDir, context, destDir){ progress -> + notificationUtil.updateFileTransferNotification(id, progress) + } + + return Result.success() + } + + + companion object { + const val downLocation = "downLocation" + const val originDir = "originDir" + const val title = "title" + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/result_card.xml b/app/src/main/res/layout/result_card.xml index e1b9af5b..d1bf85b7 100644 --- a/app/src/main/res/layout/result_card.xml +++ b/app/src/main/res/layout/result_card.xml @@ -49,7 +49,7 @@ Pick a range to download from the playlist (to not download everything). Last index cannot be smaller than the first. First index cannot be larger than the last. - /storage/emulated/0/Download - /storage/emulated/0/Download - /storage/emulated/0/Download + /storage/emulated/0/Download/YTDLnis/Audio + /storage/emulated/0/Download/YTDLnis/Video + /storage/emulated/0/Download/YTDLnis/Command Number of fragments of a DASH/HLS native video to download concurrently Concurrent Fragments Downloading @@ -149,4 +149,6 @@ Subscription Reminders Select SponsorBlock Filtering /404.404 + File Transfers + Notification showing the progress of moving files from the temporary folder to set destination \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 37623ec8..993ab2c6 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -5,7 +5,6 @@ ?attr/isLightTheme @android:color/transparent @android:color/transparent - true