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
This commit is contained in:
parent
6a1ed4fc7e
commit
51ff22abcd
16 changed files with 295 additions and 68 deletions
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -22,7 +22,6 @@
|
|||
android:theme="@style/AppTheme" >
|
||||
<activity android:name=".MainActivity"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:label="@string/download"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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<FileTransferWorker>()
|
||||
.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<FileTransferWorker>()
|
||||
.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 {
|
||||
|
|
|
|||
|
|
@ -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<String>,
|
||||
|
|
@ -325,6 +337,8 @@ class MainActivity : AppCompatActivity() {
|
|||
for (i in permissions.indices) {
|
||||
if (grantResults[i] == PackageManager.PERMISSION_DENIED) {
|
||||
createPermissionRequestDialog()
|
||||
}else{
|
||||
createDefaultFolders()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<String?>?
|
||||
editor.putString("sponsorblock_filter", java.lang.String.join(",", newValue))
|
||||
editor.putStringSet("sponsorblock_filters", newValue)
|
||||
editor.apply()
|
||||
true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -49,7 +49,7 @@
|
|||
<TextView
|
||||
android:id="@+id/result_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="100dp"
|
||||
android:layout_height="120dp"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingEnd="20dp"
|
||||
android:paddingBottom="20dp"
|
||||
|
|
|
|||
|
|
@ -65,9 +65,9 @@
|
|||
<string name="choose_range_desc">Pick a range to download from the playlist (to not download everything).</string>
|
||||
<string name="last_cant_be_smaller_than_first">Last index cannot be smaller than the first.</string>
|
||||
<string name="first_cant_be_larger_than_last">First index cannot be larger than the last.</string>
|
||||
<string name="music_path" translatable="false">/storage/emulated/0/Download</string>
|
||||
<string name="video_path" translatable="false">/storage/emulated/0/Download</string>
|
||||
<string name="command_path" translatable="false">/storage/emulated/0/Download</string>
|
||||
<string name="music_path" translatable="false">/storage/emulated/0/Download/YTDLnis/Audio</string>
|
||||
<string name="video_path" translatable="false">/storage/emulated/0/Download/YTDLnis/Video</string>
|
||||
<string name="command_path" translatable="false">/storage/emulated/0/Download/YTDLnis/Command</string>
|
||||
<string name="concurrent_fragments_summary">Number of fragments of a DASH/HLS native video to download concurrently</string>
|
||||
<string name="concurrent_fragments">Concurrent Fragments</string>
|
||||
<string name="downloading">Downloading</string>
|
||||
|
|
@ -149,4 +149,6 @@
|
|||
<string name="sponsorblock_reminders">Subscription Reminders</string>
|
||||
<string name="select_sponsorblock_filtering">Select SponsorBlock Filtering</string>
|
||||
<string name="unfound_file" translatable="false">/404.404</string>
|
||||
<string name="file_transfer_notification_channel_name">File Transfers</string>
|
||||
<string name="file_transfer_notification_channel_description">Notification showing the progress of moving files from the temporary folder to set destination</string>
|
||||
</resources>
|
||||
|
|
@ -5,7 +5,6 @@
|
|||
<item name="android:windowLightStatusBar">?attr/isLightTheme</item>
|
||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||
<item name="android:color">@android:color/transparent</item>
|
||||
<item name="android:windowIsTranslucent">true</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme" parent="AppDefaultTheme" />
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ buildscript {
|
|||
workVer = "2.7.1"
|
||||
composeVer = "1.3.2"
|
||||
kotlinVer = "1.7.20"
|
||||
coroutineVer = "1.6.4"
|
||||
}
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
|
|
|||
Loading…
Reference in a new issue