Skip to content

Commit

Permalink
Use java.nio.file APIs in download functionality.
Browse files Browse the repository at this point in the history
  • Loading branch information
Isira-Seneviratne committed Mar 16, 2023
1 parent 9c23786 commit 13f8b3d
Show file tree
Hide file tree
Showing 12 changed files with 82 additions and 71 deletions.
8 changes: 5 additions & 3 deletions app/src/main/java/com/github/libretube/api/obj/Streams.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.github.libretube.api.obj

import com.github.libretube.db.obj.DownloadItem
import com.github.libretube.enums.FileType
import java.nio.file.Paths
import kotlinx.datetime.LocalDate
import kotlinx.serialization.Serializable

Expand Down Expand Up @@ -33,6 +34,7 @@ data class Streams(
val uploaderSubscriberCount: Long = 0,
val previewFrames: List<PreviewFrames> = emptyList()
) {
@Suppress("NewApi") // The Paths class is desugared.
fun toDownloadItems(
videoId: String,
fileName: String,
Expand All @@ -53,7 +55,7 @@ data class Streams(
type = FileType.VIDEO,
videoId = videoId,
fileName = stream?.getQualityString(fileName).orEmpty(),
path = "",
path = Paths.get(""),
url = stream?.url,
format = videoFormat,
quality = videoQuality
Expand All @@ -70,7 +72,7 @@ data class Streams(
type = FileType.AUDIO,
videoId = videoId,
fileName = stream?.getQualityString(fileName).orEmpty(),
path = "",
path = Paths.get(""),
url = stream?.url,
format = audioFormat,
quality = audioQuality
Expand All @@ -84,7 +86,7 @@ data class Streams(
type = FileType.SUBTITLE,
videoId = videoId,
fileName = "${fileName}_$subtitleCode.srt",
path = "",
path = Paths.get(""),
url = subtitles.find { it.code == subtitleCode }?.url
)
)
Expand Down
8 changes: 8 additions & 0 deletions app/src/main/java/com/github/libretube/db/Converters.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.github.libretube.db

import androidx.room.TypeConverter
import java.nio.file.Path
import java.nio.file.Paths
import kotlinx.datetime.LocalDate
import kotlinx.datetime.toLocalDate

Expand All @@ -10,4 +12,10 @@ object Converters {

@TypeConverter
fun stringToLocalDate(string: String?) = string?.toLocalDate()

@TypeConverter
fun pathToString(path: Path?) = path?.toString()

@TypeConverter
fun stringToPath(string: String?) = string?.let { Paths.get(it) }
}
3 changes: 2 additions & 1 deletion app/src/main/java/com/github/libretube/db/obj/Download.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.github.libretube.db.obj

import androidx.room.Entity
import androidx.room.PrimaryKey
import java.nio.file.Path
import kotlinx.datetime.LocalDate

@Entity(tableName = "download")
Expand All @@ -12,5 +13,5 @@ data class Download(
val description: String = "",
val uploader: String = "",
val uploadDate: LocalDate? = null,
val thumbnailPath: String? = null
val thumbnailPath: Path? = null
)
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import androidx.room.ForeignKey
import androidx.room.Index
import androidx.room.PrimaryKey
import com.github.libretube.enums.FileType
import java.nio.file.Path

@Entity(
tableName = "downloadItem",
Expand All @@ -24,7 +25,7 @@ data class DownloadItem(
val type: FileType,
val videoId: String,
val fileName: String,
var path: String,
var path: Path,
var url: String? = null,
var format: String? = null,
var quality: String? = null,
Expand Down
11 changes: 11 additions & 0 deletions app/src/main/java/com/github/libretube/extensions/Path.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.github.libretube.extensions

import android.net.Uri
import androidx.core.net.toUri
import java.nio.file.Path
import kotlin.io.path.exists

fun Path.toAndroidUri(): Uri? {
@Suppress("NewApi") // The Path class is desugared.
return if (exists()) toFile().toUri() else null
}
27 changes: 13 additions & 14 deletions app/src/main/java/com/github/libretube/helpers/DownloadHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import com.github.libretube.constants.IntentData
import com.github.libretube.constants.PreferenceKeys
import com.github.libretube.db.obj.DownloadItem
import com.github.libretube.services.DownloadService
import java.io.File
import java.nio.file.Path
import kotlin.io.path.createDirectories

object DownloadHelper {
const val VIDEO_DIR = "video"
Expand All @@ -20,23 +21,21 @@ object DownloadHelper {
const val DEFAULT_TIMEOUT = 15 * 1000
const val DEFAULT_RETRY = 3

fun getOfflineStorageDir(context: Context): File {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return context.filesDir

return try {
context.getExternalFilesDir(null)!!
} catch (e: Exception) {
private fun getOfflineStorageDir(context: Context): Path {
val file = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
context.filesDir
} else {
try {
context.getExternalFilesDir(null)!!
} catch (e: Exception) {
context.filesDir
}
}
return file.toPath()
}

fun getDownloadDir(context: Context, path: String): File {
return File(
getOfflineStorageDir(context),
path
).apply {
if (!this.exists()) this.mkdirs()
}
fun getDownloadDir(context: Context, path: String): Path {
return getOfflineStorageDir(context).resolve(path).createDirectories()
}

fun getMaxConcurrentDownloads(): Int {
Expand Down
15 changes: 8 additions & 7 deletions app/src/main/java/com/github/libretube/helpers/ImageHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@ import android.graphics.BitmapFactory
import android.net.Uri
import android.widget.ImageView
import androidx.core.graphics.drawable.toBitmap
import androidx.core.net.toUri
import coil.ImageLoader
import coil.disk.DiskCache
import coil.load
import coil.request.CachePolicy
import coil.request.ImageRequest
import com.github.libretube.api.CronetHelper
import com.github.libretube.constants.PreferenceKeys
import com.github.libretube.extensions.toAndroidUri
import com.github.libretube.util.DataSaverMode
import java.io.File
import java.nio.file.Path

object ImageHelper {
lateinit var imageLoader: ImageLoader
Expand Down Expand Up @@ -54,9 +56,10 @@ object ImageHelper {
if (!DataSaverMode.isEnabled(target.context)) target.load(url, imageLoader)
}

fun downloadImage(context: Context, url: String, path: String) {
fun downloadImage(context: Context, url: String, path: Path) {
getAsync(context, url) { bitmap ->
saveImage(context, bitmap, Uri.fromFile(File(path)))
@Suppress("NewApi") // The Path class is desugared.
saveImage(context, bitmap, path.toFile().toUri())
}
}

Expand All @@ -69,10 +72,8 @@ object ImageHelper {
imageLoader.enqueue(request)
}

fun getDownloadedImage(context: Context, path: String): Bitmap? {
val file = File(path)
if (!file.exists()) return null
return getImage(context, Uri.fromFile(file))
fun getDownloadedImage(context: Context, path: Path): Bitmap? {
return path.toAndroidUri()?.let { getImage(context, it) }
}

private fun saveImage(context: Context, bitmapImage: Bitmap, imagePath: Uri) {
Expand Down
42 changes: 20 additions & 22 deletions app/src/main/java/com/github/libretube/services/DownloadService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,12 @@ import java.io.File
import java.net.HttpURLConnection
import java.net.SocketTimeoutException
import java.net.URL
import java.nio.file.Path
import java.nio.file.StandardOpenOption
import java.util.concurrent.Executors
import kotlin.io.path.absolute
import kotlin.io.path.createFile
import kotlin.io.path.fileSize
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
Expand All @@ -48,7 +53,6 @@ import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okio.BufferedSink
import okio.buffer
import okio.sink
import okio.source
Expand Down Expand Up @@ -96,21 +100,22 @@ class DownloadService : LifecycleService() {
RetrofitInstance.api.getStreams(videoId)
}

val thumbnailTargetFile = getDownloadFile(DownloadHelper.THUMBNAIL_DIR, fileName)
val thumbnailTargetPath = getDownloadPath(DownloadHelper.THUMBNAIL_DIR, fileName)
.absolute()

val download = Download(
videoId,
streams.title,
streams.description,
streams.uploader,
streams.uploadDate,
thumbnailTargetFile.absolutePath
thumbnailTargetPath
)
Database.downloadDao().insertDownload(download)
ImageHelper.downloadImage(
this@DownloadService,
streams.thumbnailUrl,
thumbnailTargetFile.absolutePath
thumbnailTargetPath
)

val downloadItems = streams.toDownloadItems(
Expand All @@ -136,13 +141,11 @@ class DownloadService : LifecycleService() {
* for the requested file.
*/
private fun start(item: DownloadItem) {
val file = when (item.type) {
FileType.AUDIO -> getDownloadFile(DownloadHelper.AUDIO_DIR, item.fileName)
FileType.VIDEO -> getDownloadFile(DownloadHelper.VIDEO_DIR, item.fileName)
FileType.SUBTITLE -> getDownloadFile(DownloadHelper.SUBTITLE_DIR, item.fileName)
}
file.createNewFile()
item.path = file.absolutePath
item.path = when (item.type) {
FileType.AUDIO -> getDownloadPath(DownloadHelper.AUDIO_DIR, item.fileName)
FileType.VIDEO -> getDownloadPath(DownloadHelper.VIDEO_DIR, item.fileName)
FileType.SUBTITLE -> getDownloadPath(DownloadHelper.SUBTITLE_DIR, item.fileName)
}.createFile().absolute()

lifecycleScope.launch(coroutineContext) {
item.id = Database.downloadDao().insertDownloadItem(item).toInt()
Expand All @@ -158,8 +161,8 @@ class DownloadService : LifecycleService() {
downloadQueue[item.id] = true
val notificationBuilder = getNotificationBuilder(item)
setResumeNotification(notificationBuilder, item)
val file = File(item.path)
var totalRead = file.length()
val path = item.path
var totalRead = path.fileSize()
val url = URL(item.url ?: return)

url.getContentLength().let { size ->
Expand Down Expand Up @@ -206,7 +209,8 @@ class DownloadService : LifecycleService() {
return
}

val sink: BufferedSink = file.sink(true).buffer()
@Suppress("NewApi") // The StandardOpenOption enum is desugared.
val sink = path.sink(StandardOpenOption.APPEND).buffer()
val sourceByte = con.inputStream.source()

var lastTime = System.currentTimeMillis() / 1000
Expand Down Expand Up @@ -437,14 +441,8 @@ class DownloadService : LifecycleService() {
/**
* Get a [File] from the corresponding download directory and the file name
*/
private fun getDownloadFile(directory: String, fileName: String): File {
return File(
DownloadHelper.getDownloadDir(
this@DownloadService,
directory
),
fileName
)
private fun getDownloadPath(directory: String, fileName: String): Path {
return DownloadHelper.getDownloadDir(this, directory).resolve(fileName)
}

override fun onDestroy() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import com.github.libretube.databinding.ActivityOfflinePlayerBinding
import com.github.libretube.databinding.ExoStyledPlayerControlViewBinding
import com.github.libretube.db.DatabaseHolder.Database
import com.github.libretube.enums.FileType
import com.github.libretube.extensions.toAndroidUri
import com.github.libretube.extensions.updateParameters
import com.github.libretube.helpers.PlayerHelper
import com.github.libretube.helpers.PlayerHelper.loadPlaybackParams
Expand All @@ -33,7 +34,6 @@ import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
import com.google.android.exoplayer2.ui.StyledPlayerView
import com.google.android.exoplayer2.upstream.FileDataSource
import com.google.android.exoplayer2.util.MimeTypes
import java.io.File
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
Expand Down Expand Up @@ -107,10 +107,6 @@ class OfflinePlayerActivity : BaseActivity() {
)
}

private fun File.toUri(): Uri? {
return if (this.exists()) Uri.fromFile(this) else null
}

private fun playVideo() {
lifecycleScope.launch {
val downloadFiles = withContext(Dispatchers.IO) {
Expand All @@ -121,9 +117,9 @@ class OfflinePlayerActivity : BaseActivity() {
val audio = downloadFiles.firstOrNull { it.type == FileType.AUDIO }
val subtitle = downloadFiles.firstOrNull { it.type == FileType.SUBTITLE }

val videoUri = video?.path?.let { File(it).toUri() }
val audioUri = audio?.path?.let { File(it).toUri() }
val subtitleUri = subtitle?.path?.let { File(it).toUri() }
val videoUri = video?.path?.toAndroidUri()
val audioUri = audio?.path?.toAndroidUri()
val subtitleUri = subtitle?.path?.toAndroidUri()

setMediaSource(videoUri, audioUri, subtitleUri)

Expand All @@ -143,7 +139,6 @@ class OfflinePlayerActivity : BaseActivity() {
.setMimeType(MimeTypes.APPLICATION_SUBRIP)
.build()
}
subtitle?.id

when {
videoUri != null && audioUri != null -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ import com.github.libretube.ui.activities.OfflinePlayerActivity
import com.github.libretube.ui.viewholders.DownloadsViewHolder
import com.github.libretube.util.TextUtils
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import java.io.File
import kotlin.io.path.deleteIfExists
import kotlin.io.path.fileSize
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking

Expand Down Expand Up @@ -46,7 +47,7 @@ class DownloadsAdapter(
videoInfo.text = download.uploadDate?.let { TextUtils.localizeDate(it) }

val downloadSize = items.sumOf { it.downloadSize }
val currentSize = items.sumOf { File(it.path).length() }
val currentSize = items.sumOf { it.path.fileSize() }

if (downloadSize == -1L) {
progressBar.isIndeterminate = true
Expand Down Expand Up @@ -96,16 +97,10 @@ class DownloadsAdapter(
.setTitle(R.string.delete)
.setMessage(R.string.irreversible)
.setPositiveButton(R.string.okay) { _, _ ->
items.map { File(it.path) }.forEach { file ->
runCatching {
if (file.exists()) file.delete()
}
}
runCatching {
download.thumbnailPath?.let {
File(it).delete()
}
items.forEach {
it.path.deleteIfExists()
}
download.thumbnailPath?.deleteIfExists()

runBlocking(Dispatchers.IO) {
DatabaseHolder.Database.downloadDao().deleteDownload(download)
Expand Down
Loading

0 comments on commit 13f8b3d

Please sign in to comment.