1
0
mirror of https://github.com/ultrasonic/ultrasonic synced 2025-02-16 19:50:35 +01:00

Make SongView display a percentag while downloading

Closes #403
This commit is contained in:
tzugen 2021-04-10 20:51:57 +02:00
parent cf68038e20
commit aac74d1eef
No known key found for this signature in database
GPG Key ID: 61E9C34BC10EC930
3 changed files with 130 additions and 118 deletions

View File

@ -76,14 +76,14 @@ class DownloadFile(
partialFile = File(saveFile.parent, FileUtil.getPartialFile(saveFile.name)) partialFile = File(saveFile.parent, FileUtil.getPartialFile(saveFile.name))
completeFile = File(saveFile.parent, FileUtil.getCompleteFile(saveFile.name)) completeFile = File(saveFile.parent, FileUtil.getCompleteFile(saveFile.name))
mediaStoreService = MediaStoreService(context) mediaStoreService = MediaStoreService(context)
} }
/** /**
* Returns the effective bit rate. * Returns the effective bit rate.
*/ */
fun getBitRate(): Int { fun getBitRate(): Int {
return if (song.bitRate == null) desiredBitRate else song.bitRate!! return if (song.bitRate == null) desiredBitRate else song.bitRate!!
} }
@Synchronized @Synchronized
fun download() { fun download() {
@ -91,32 +91,31 @@ class DownloadFile(
isFailed = false isFailed = false
downloadTask = DownloadTask() downloadTask = DownloadTask()
downloadTask!!.start() downloadTask!!.start()
} }
@Synchronized @Synchronized
fun cancelDownload() { fun cancelDownload() {
if (downloadTask != null) { if (downloadTask != null) {
downloadTask!!.cancel() downloadTask!!.cancel()
} }
}
}
fun getCompleteFile(): File { fun getCompleteFile(): File {
if (saveFile.exists()) { if (saveFile.exists()) {
return saveFile return saveFile
} }
return if (completeFile.exists()) { return if (completeFile.exists()) {
completeFile completeFile
} else saveFile } else saveFile
} }
val completeOrPartialFile: File val completeOrPartialFile: File
get() = if (isCompleteFileAvailable) { get() = if (isCompleteFileAvailable) {
getCompleteFile() getCompleteFile()
} else { } else {
partialFile partialFile
} }
val isSaved: Boolean val isSaved: Boolean
get() = saveFile.exists() get() = saveFile.exists()
@ -140,7 +139,7 @@ class DownloadFile(
fun shouldSave(): Boolean { fun shouldSave(): Boolean {
return save return save
} }
fun delete() { fun delete() {
cancelDownload() cancelDownload()
@ -148,43 +147,43 @@ class DownloadFile(
Util.delete(completeFile) Util.delete(completeFile)
Util.delete(saveFile) Util.delete(saveFile)
mediaStoreService.deleteFromMediaStore(this) mediaStoreService.deleteFromMediaStore(this)
} }
fun unpin() { fun unpin() {
if (saveFile.exists()) { if (saveFile.exists()) {
if (!saveFile.renameTo(completeFile)){ if (!saveFile.renameTo(completeFile)) {
Timber.w( Timber.w(
"Renaming file failed. Original file: %s; Rename to: %s", "Renaming file failed. Original file: %s; Rename to: %s",
saveFile.name, completeFile.name saveFile.name, completeFile.name
) )
} }
} }
} }
fun cleanup(): Boolean { fun cleanup(): Boolean {
var ok = true var ok = true
if (completeFile.exists() || saveFile.exists()) { if (completeFile.exists() || saveFile.exists()) {
ok = Util.delete(partialFile) ok = Util.delete(partialFile)
} }
if (saveFile.exists()) { if (saveFile.exists()) {
ok = ok and Util.delete(completeFile) ok = ok and Util.delete(completeFile)
} }
return ok return ok
} }
// In support of LRU caching. // In support of LRU caching.
fun updateModificationDate() { fun updateModificationDate() {
updateModificationDate(saveFile) updateModificationDate(saveFile)
updateModificationDate(partialFile) updateModificationDate(partialFile)
updateModificationDate(completeFile) updateModificationDate(completeFile)
} }
fun setPlaying(isPlaying: Boolean) { fun setPlaying(isPlaying: Boolean) {
if (!isPlaying) doPendingRename() if (!isPlaying) doPendingRename()
this.isPlaying = isPlaying this.isPlaying = isPlaying
} }
// Do a pending rename after the song has stopped playing // Do a pending rename after the song has stopped playing
private fun doPendingRename() { private fun doPendingRename() {
@ -198,18 +197,17 @@ class DownloadFile(
mediaStoreService.saveInMediaStore(this@DownloadFile) mediaStoreService.saveInMediaStore(this@DownloadFile)
} else { } else {
Util.renameFile(partialFile, completeFile) Util.renameFile(partialFile, completeFile)
} }
completeWhenDone = false completeWhenDone = false
} }
} catch (ex: IOException) { } catch (ex: IOException) {
Timber.w("Failed to rename file %s to %s", completeFile, saveFile) Timber.w("Failed to rename file %s to %s", completeFile, saveFile)
} }
}
}
override fun toString(): String { override fun toString(): String {
return String.format("DownloadFile (%s)", song) return String.format("DownloadFile (%s)", song)
} }
private inner class DownloadTask : CancellableTask() { private inner class DownloadTask : CancellableTask() {
override fun execute() { override fun execute() {
@ -225,7 +223,7 @@ class DownloadFile(
if (saveFile.exists()) { if (saveFile.exists()) {
Timber.i("%s already exists. Skipping.", saveFile) Timber.i("%s already exists. Skipping.", saveFile)
return return
} }
if (completeFile.exists()) { if (completeFile.exists()) {
if (save) { if (save) {
@ -233,23 +231,23 @@ class DownloadFile(
saveWhenDone = true saveWhenDone = true
} else { } else {
Util.renameFile(completeFile, saveFile) Util.renameFile(completeFile, saveFile)
} }
} else { } else {
Timber.i("%s already exists. Skipping.", completeFile) Timber.i("%s already exists. Skipping.", completeFile)
} }
return return
} }
val musicService = getMusicService(context) val musicService = getMusicService(context)
// Some devices seem to throw error on partial file which doesn't exist // Some devices seem to throw error on partial file which doesn't exist
val needsDownloading: Boolean val needsDownloading: Boolean
val duration = song.duration val duration = song.duration
var fileLength: Long = 0 var fileLength: Long = 0
if (!partialFile.exists()) { if (!partialFile.exists()) {
fileLength = partialFile.length() fileLength = partialFile.length()
} }
needsDownloading = ( needsDownloading = (
desiredBitRate == 0 || duration == null || desiredBitRate == 0 || duration == null ||
@ -257,7 +255,7 @@ class DownloadFile(
) )
if (needsDownloading) { if (needsDownloading) {
// Attempt partial HTTP GET, appending to the file if it exists. // Attempt partial HTTP GET, appending to the file if it exists.
val (inStream, partial) = musicService val (inStream, partial) = musicService
.getDownloadInputStream(song, partialFile.length(), desiredBitRate) .getDownloadInputStream(song, partialFile.length(), desiredBitRate)
@ -268,14 +266,13 @@ class DownloadFile(
"Executed partial HTTP GET, skipping %d bytes", "Executed partial HTTP GET, skipping %d bytes",
partialFile.length() partialFile.length()
) )
} }
outputStream = FileOutputStream(partialFile, partial) outputStream = FileOutputStream(partialFile, partial)
val len = inputStream.copyTo(outputStream) { val len = inputStream.copyTo(outputStream) { totalBytesCopied ->
totalBytesCopied ->
setProgress(totalBytesCopied) setProgress(totalBytesCopied)
} }
Timber.i("Downloaded %d bytes to %s", len, partialFile) Timber.i("Downloaded %d bytes to %s", len, partialFile)
@ -287,7 +284,7 @@ class DownloadFile(
throw Exception(String.format("Download of '%s' was cancelled", song)) throw Exception(String.format("Download of '%s' was cancelled", song))
} }
downloadAndSaveCoverArt(musicService) downloadAndSaveCoverArt(musicService)
} }
if (isPlaying) { if (isPlaying) {
completeWhenDone = true completeWhenDone = true
@ -297,8 +294,8 @@ class DownloadFile(
mediaStoreService.saveInMediaStore(this@DownloadFile) mediaStoreService.saveInMediaStore(this@DownloadFile)
} else { } else {
Util.renameFile(partialFile, completeFile) Util.renameFile(partialFile, completeFile)
} }
} }
} catch (x: Exception) { } catch (x: Exception) {
Util.close(outputStream) Util.close(outputStream)
Util.delete(completeFile) Util.delete(completeFile)
@ -306,19 +303,19 @@ class DownloadFile(
if (!isCancelled) { if (!isCancelled) {
isFailed = true isFailed = true
Timber.w(x, "Failed to download '%s'.", song) Timber.w(x, "Failed to download '%s'.", song)
} }
} finally { } finally {
Util.close(inputStream) Util.close(inputStream)
Util.close(outputStream) Util.close(outputStream)
if (wakeLock != null) { if (wakeLock != null) {
wakeLock.release() wakeLock.release()
Timber.i("Released wake lock %s", wakeLock) Timber.i("Released wake lock %s", wakeLock)
} }
wifiLock?.release() wifiLock?.release()
CacheCleaner(context).cleanSpace() CacheCleaner(context).cleanSpace()
downloader.value.checkDownloads() downloader.value.checkDownloads()
} }
} }
private fun acquireWakeLock(wakeLock: WakeLock?): WakeLock? { private fun acquireWakeLock(wakeLock: WakeLock?): WakeLock? {
var wakeLock1 = wakeLock var wakeLock1 = wakeLock
@ -328,24 +325,24 @@ class DownloadFile(
wakeLock1 = pm.newWakeLock(flags, toString()) wakeLock1 = pm.newWakeLock(flags, toString())
wakeLock1.acquire(10 * 60 * 1000L /*10 minutes*/) wakeLock1.acquire(10 * 60 * 1000L /*10 minutes*/)
Timber.i("Acquired wake lock %s", wakeLock1) Timber.i("Acquired wake lock %s", wakeLock1)
} }
return wakeLock1 return wakeLock1
} }
override fun toString(): String { override fun toString(): String {
return String.format("DownloadTask (%s)", song) return String.format("DownloadTask (%s)", song)
} }
private fun downloadAndSaveCoverArt(musicService: MusicService) { private fun downloadAndSaveCoverArt(musicService: MusicService) {
try { try {
if (!TextUtils.isEmpty(song.coverArt)) { if (!TextUtils.isEmpty(song.coverArt)) {
val size = Util.getMinDisplayMetric(context) val size = Util.getMinDisplayMetric(context)
musicService.getCoverArt(context, song, size, true, true) musicService.getCoverArt(context, song, size, true, true)
} }
} catch (x: Exception) { } catch (x: Exception) {
Timber.e(x, "Failed to get cover art.") Timber.e(x, "Failed to get cover art.")
} }
}
}
@Throws(IOException::class) @Throws(IOException::class)
fun InputStream.copyTo(out: OutputStream, onCopy: (totalBytesCopied: Long) -> Any): Long { fun InputStream.copyTo(out: OutputStream, onCopy: (totalBytesCopied: Long) -> Any): Long {
@ -357,16 +354,16 @@ class DownloadFile(
bytesCopied += bytes bytesCopied += bytes
onCopy(bytesCopied) onCopy(bytesCopied)
bytes = read(buffer) bytes = read(buffer)
} }
return bytesCopied return bytesCopied
} }
} }
private fun setProgress(totalBytesCopied: Long) { private fun setProgress(totalBytesCopied: Long) {
if (song.size != null) { if (song.size != null) {
progress.postValue((totalBytesCopied * 100 / song.size!!).toInt()) progress.postValue((totalBytesCopied * 100 / song.size!!).toInt())
} }
} }
companion object { companion object {
private fun updateModificationDate(file: File) { private fun updateModificationDate(file: File) {
@ -387,9 +384,9 @@ class DownloadFile(
raf.close() raf.close()
} catch (e: Exception) { } catch (e: Exception) {
Timber.w("Failed to set last-modified date on %s", file) Timber.w("Failed to set last-modified date on %s", file)
} }
} }
} }
} }
} }
} }

View File

@ -343,6 +343,23 @@ public class Util
toast.show(); toast.show();
} }
/**
* Formats an Int to a percentage string
* For instance:
* <ul>
* <li><code>format(99)</code> returns <em>"99 %"</em>.</li>
* </ul>
*
* @param percent The percent as a range from 0 - 100
* @return The formatted string.
*/
public static synchronized String formatPercentage(int percent)
{
return Math.min(Math.max(percent,0),100) + " %";
}
/** /**
* Converts a byte-count to a formatted string suitable for display to the user. * Converts a byte-count to a formatted string suitable for display to the user.
* For instance: * For instance:

View File

@ -225,59 +225,7 @@ class SongView(context: Context) : UpdateView(context), Checkable {
downloadFile = mediaPlayerControllerLazy.value.getDownloadFileForSong(entry) downloadFile = mediaPlayerControllerLazy.value.getDownloadFileForSong(entry)
val partialFile = downloadFile!!.partialFile updateDownloadStatus(downloadFile!!)
if (downloadFile!!.isWorkDone) {
val newLeftImageType =
if (downloadFile!!.isSaved) ImageType.Pin else ImageType.Downloaded
if (leftImageType != newLeftImageType) {
leftImage = if (downloadFile!!.isSaved) pinImage else downloadedImage
leftImageType = newLeftImageType
}
} else {
leftImageType = ImageType.None
leftImage = null
}
val rightImageType: ImageType
val rightImage: Drawable?
if (
downloadFile!!.isDownloading &&
!downloadFile!!.isDownloadCancelled &&
partialFile.exists()
) {
viewHolder?.status?.text = Util.formatLocalizedBytes(
partialFile.length(), this.context
)
rightImageType = ImageType.Downloading
rightImage = downloadingImage
} else {
rightImageType = ImageType.None
rightImage = null
val statusText = viewHolder?.status?.text
if (!statusText.isNullOrEmpty()) viewHolder?.status?.text = null
}
if (previousLeftImageType != leftImageType || previousRightImageType != rightImageType) {
previousLeftImageType = leftImageType
previousRightImageType = rightImageType
if (viewHolder?.status != null) {
viewHolder?.status?.setCompoundDrawablesWithIntrinsicBounds(
leftImage, null, rightImage, null
)
if (rightImage === downloadingImage) {
val frameAnimation = rightImage as AnimationDrawable?
frameAnimation!!.setVisible(true, true)
frameAnimation.start()
}
}
}
if (entry?.starred != true) { if (entry?.starred != true) {
if (viewHolder?.star?.drawable !== starHollowDrawable) { if (viewHolder?.star?.drawable !== starHollowDrawable) {
@ -325,6 +273,56 @@ class SongView(context: Context) : UpdateView(context), Checkable {
} }
} }
private fun updateDownloadStatus(downloadFile: DownloadFile) {
if (downloadFile.isWorkDone) {
val newLeftImageType =
if (downloadFile.isSaved) ImageType.Pin else ImageType.Downloaded
if (leftImageType != newLeftImageType) {
leftImage = if (downloadFile.isSaved) pinImage else downloadedImage
leftImageType = newLeftImageType
}
} else {
leftImageType = ImageType.None
leftImage = null
}
val rightImageType: ImageType
val rightImage: Drawable?
if (downloadFile.isDownloading && !downloadFile.isDownloadCancelled) {
viewHolder?.status?.text = Util.formatPercentage(downloadFile.progress.value!!)
rightImageType = ImageType.Downloading
rightImage = downloadingImage
} else {
rightImageType = ImageType.None
rightImage = null
val statusText = viewHolder?.status?.text
if (!statusText.isNullOrEmpty()) viewHolder?.status?.text = null
}
if (previousLeftImageType != leftImageType || previousRightImageType != rightImageType) {
previousLeftImageType = leftImageType
previousRightImageType = rightImageType
if (viewHolder?.status != null) {
viewHolder?.status?.setCompoundDrawablesWithIntrinsicBounds(
leftImage, null, rightImage, null
)
if (rightImage === downloadingImage) {
val frameAnimation = rightImage as AnimationDrawable?
frameAnimation!!.setVisible(true, true)
frameAnimation.start()
}
}
}
}
override fun setChecked(b: Boolean) { override fun setChecked(b: Boolean) {
viewHolder?.check?.isChecked = b viewHolder?.check?.isChecked = b
} }