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