mirror of
https://github.com/ultrasonic/ultrasonic
synced 2025-02-17 04:00:39 +01:00
parent
cf68038e20
commit
aac74d1eef
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user