From 0bf0d2db87e01fbc05063f1bc33ffff7c329d599 Mon Sep 17 00:00:00 2001 From: tzugen Date: Thu, 1 Apr 2021 13:15:41 +0200 Subject: [PATCH 01/12] Fix a number of lint issues --- ultrasonic/lint-baseline.xml | 77 ------------------- .../res/{layout-port => layout}/download.xml | 0 .../src/main/res/layout/media_buttons.xml | 21 +++-- ultrasonic/src/main/res/values/strings.xml | 7 ++ 4 files changed, 21 insertions(+), 84 deletions(-) rename ultrasonic/src/main/res/{layout-port => layout}/download.xml (100%) diff --git a/ultrasonic/lint-baseline.xml b/ultrasonic/lint-baseline.xml index 53d2ef0e..8347a5e7 100644 --- a/ultrasonic/lint-baseline.xml +++ b/ultrasonic/lint-baseline.xml @@ -320,28 +320,6 @@ column="25"/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - + a:src="?attr/media_shuffle" + a:contentDescription="@string/buttons.shuffle" /> + a:src="?attr/media_previous" + a:contentDescription="@string/buttons.previous" /> + tools:visibility="gone" + a:contentDescription="@string/buttons.play" /> + a:src="?attr/media_pause" + a:contentDescription="@string/buttons.pause" /> + tools:visibility="gone" + a:contentDescription="@string/buttons.stop" /> + a:src="?attr/media_next" + a:contentDescription="@string/buttons.next"/> + a:src="?attr/media_repeat_off" + a:contentDescription="@string/buttons.repeat" /> \ No newline at end of file diff --git a/ultrasonic/src/main/res/values/strings.xml b/ultrasonic/src/main/res/values/strings.xml index 4da395e3..aac3afd0 100644 --- a/ultrasonic/src/main/res/values/strings.xml +++ b/ultrasonic/src/main/res/values/strings.xml @@ -15,6 +15,13 @@ Chat Ultrasonic Main Now Playing + Play + Pause + Repeat + Shuffle + Stop + Next + Previous Podcast No podcasts channels registered Podcast From 6b750dac4d13cf59500d8596029b28460317b689 Mon Sep 17 00:00:00 2001 From: tzugen Date: Thu, 1 Apr 2021 13:21:15 +0200 Subject: [PATCH 02/12] Rename download layouts semantically. --- .../ultrasonic/fragment/PlayerFragment.java | 42 ++++++------ .../{download.xml => current_playing.xml} | 10 +-- .../{download.xml => current_playing.xml} | 10 +-- ...load_playlist.xml => current_playlist.xml} | 64 +++++++++---------- .../src/main/res/layout/media_buttons.xml | 14 ++-- .../src/main/res/layout/player_media_info.xml | 10 +-- .../src/main/res/layout/player_slider.xml | 6 +- 7 files changed, 78 insertions(+), 78 deletions(-) rename ultrasonic/src/main/res/layout-land/{download.xml => current_playing.xml} (93%) rename ultrasonic/src/main/res/layout/{download.xml => current_playing.xml} (93%) rename ultrasonic/src/main/res/layout/{download_playlist.xml => current_playlist.xml} (89%) diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/PlayerFragment.java b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/PlayerFragment.java index 4f009edc..f058a3f4 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/PlayerFragment.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/PlayerFragment.java @@ -145,7 +145,7 @@ public class PlayerFragment extends Fragment implements GestureDetector.OnGestur @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - return inflater.inflate(R.layout.download, container, false); + return inflater.inflate(R.layout.current_playing, container, false); } @Override @@ -168,27 +168,27 @@ public class PlayerFragment extends Fragment implements GestureDetector.OnGestur swipeVelocity = swipeDistance; gestureScanner = new GestureDetector(getContext(), this); - playlistFlipper = view.findViewById(R.id.download_playlist_flipper); - emptyTextView = view.findViewById(R.id.download_empty); - songTitleTextView = view.findViewById(R.id.download_song_title); - albumTextView = view.findViewById(R.id.download_album); - artistTextView = view.findViewById(R.id.download_artist); - albumArtImageView = view.findViewById(R.id.download_album_art_image); - positionTextView = view.findViewById(R.id.download_position); - downloadTrackTextView = view.findViewById(R.id.download_track); - downloadTotalDurationTextView = view.findViewById(R.id.download_total_duration); - durationTextView = view.findViewById(R.id.download_duration); - progressBar = view.findViewById(R.id.download_progress_bar); - playlistView = view.findViewById(R.id.download_list); - final AutoRepeatButton previousButton = view.findViewById(R.id.download_previous); - final AutoRepeatButton nextButton = view.findViewById(R.id.download_next); - pauseButton = view.findViewById(R.id.download_pause); - stopButton = view.findViewById(R.id.download_stop); - startButton = view.findViewById(R.id.download_start); - final View shuffleButton = view.findViewById(R.id.download_shuffle); - repeatButton = view.findViewById(R.id.download_repeat); + playlistFlipper = view.findViewById(R.id.current_playing_playlist_flipper); + emptyTextView = view.findViewById(R.id.playlist_empty); + songTitleTextView = view.findViewById(R.id.current_playing_song); + albumTextView = view.findViewById(R.id.current_playing_album); + artistTextView = view.findViewById(R.id.current_playing_artist); + albumArtImageView = view.findViewById(R.id.current_playing_album_art_image); + positionTextView = view.findViewById(R.id.current_playing_position); + downloadTrackTextView = view.findViewById(R.id.current_playing_track); + downloadTotalDurationTextView = view.findViewById(R.id.current_total_duration); + durationTextView = view.findViewById(R.id.current_playing_duration); + progressBar = view.findViewById(R.id.current_playing_progress_bar); + playlistView = view.findViewById(R.id.playlist_view); + final AutoRepeatButton previousButton = view.findViewById(R.id.button_previous); + final AutoRepeatButton nextButton = view.findViewById(R.id.button_next); + pauseButton = view.findViewById(R.id.button_pause); + stopButton = view.findViewById(R.id.button_stop); + startButton = view.findViewById(R.id.button_start); + final View shuffleButton = view.findViewById(R.id.button_shuffle); + repeatButton = view.findViewById(R.id.button_repeat); - visualizerViewLayout = view.findViewById(R.id.download_visualizer_view_layout); + visualizerViewLayout = view.findViewById(R.id.current_playing_visualizer_layout); LinearLayout ratingLinearLayout = view.findViewById(R.id.song_rating); fiveStar1ImageView = view.findViewById(R.id.song_five_star_1); diff --git a/ultrasonic/src/main/res/layout-land/download.xml b/ultrasonic/src/main/res/layout-land/current_playing.xml similarity index 93% rename from ultrasonic/src/main/res/layout-land/download.xml rename to ultrasonic/src/main/res/layout-land/current_playing.xml index e1219d57..2235bb65 100644 --- a/ultrasonic/src/main/res/layout-land/download.xml +++ b/ultrasonic/src/main/res/layout-land/current_playing.xml @@ -5,13 +5,13 @@ a:orientation="horizontal"> - + \ No newline at end of file diff --git a/ultrasonic/src/main/res/layout/download.xml b/ultrasonic/src/main/res/layout/current_playing.xml similarity index 93% rename from ultrasonic/src/main/res/layout/download.xml rename to ultrasonic/src/main/res/layout/current_playing.xml index 807b4ebb..43c5d843 100644 --- a/ultrasonic/src/main/res/layout/download.xml +++ b/ultrasonic/src/main/res/layout/current_playing.xml @@ -5,13 +5,13 @@ a:orientation="vertical" > - + diff --git a/ultrasonic/src/main/res/layout/download_playlist.xml b/ultrasonic/src/main/res/layout/current_playlist.xml similarity index 89% rename from ultrasonic/src/main/res/layout/download_playlist.xml rename to ultrasonic/src/main/res/layout/current_playlist.xml index 39e709b5..1b933376 100644 --- a/ultrasonic/src/main/res/layout/download_playlist.xml +++ b/ultrasonic/src/main/res/layout/current_playlist.xml @@ -1,33 +1,33 @@ - - - - - - - - + + + + + + + + \ No newline at end of file diff --git a/ultrasonic/src/main/res/layout/media_buttons.xml b/ultrasonic/src/main/res/layout/media_buttons.xml index 20c5b020..92f02e91 100644 --- a/ultrasonic/src/main/res/layout/media_buttons.xml +++ b/ultrasonic/src/main/res/layout/media_buttons.xml @@ -8,7 +8,7 @@ a:layout_marginRight="12dp" > @@ -18,7 +18,7 @@ a:layout_height="match_parent"> Date: Sat, 10 Apr 2021 13:15:54 +0200 Subject: [PATCH 03/12] Remove unused parameters from getDownloadInputStream() --- .../org/moire/ultrasonic/service/CachedMusicService.java | 4 ++-- .../main/java/org/moire/ultrasonic/service/DownloadFile.java | 3 +-- .../main/java/org/moire/ultrasonic/service/MusicService.java | 2 +- .../org/moire/ultrasonic/service/OfflineMusicService.java | 2 +- .../kotlin/org/moire/ultrasonic/service/RESTMusicService.kt | 5 +---- 5 files changed, 6 insertions(+), 10 deletions(-) diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/CachedMusicService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/CachedMusicService.java index d8033ceb..458132a8 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/CachedMusicService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/CachedMusicService.java @@ -321,9 +321,9 @@ public class CachedMusicService implements MusicService } @Override - public Pair getDownloadInputStream(Context context, MusicDirectory.Entry song, long offset, int maxBitrate, CancellableTask task) throws Exception + public Pair getDownloadInputStream(MusicDirectory.Entry song, long offset, int maxBitrate) throws Exception { - return musicService.getDownloadInputStream(context, song, offset, maxBitrate, task); + return musicService.getDownloadInputStream(song, offset, maxBitrate); } @Override diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadFile.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadFile.java index eb71800f..a3d84d6f 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadFile.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadFile.java @@ -377,8 +377,7 @@ public class DownloadFile { // Attempt partial HTTP GET, appending to the file if it exists. Pair response = musicService - .getDownloadInputStream(context, song, partialFile.length(), bitRate, - DownloadTask.this); + .getDownloadInputStream(song, partialFile.length(), bitRate); if (response.getSecond()) { diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicService.java index 8ef64e81..86782318 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicService.java @@ -108,7 +108,7 @@ public interface MusicService * Return response {@link InputStream} and a {@link Boolean} that indicates if this response is * partial. */ - Pair getDownloadInputStream(Context context, MusicDirectory.Entry song, long offset, int maxBitrate, CancellableTask task) throws Exception; + Pair getDownloadInputStream(MusicDirectory.Entry song, long offset, int maxBitrate) throws Exception; // TODO: Refactor and remove this call (see RestMusicService implementation) String getVideoUrl(Context context, String id, boolean useFlash) throws Exception; diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.java index 80688551..a78d09b2 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.java @@ -893,7 +893,7 @@ public class OfflineMusicService implements MusicService } @Override - public Pair getDownloadInputStream(Context context, MusicDirectory.Entry song, long offset, int maxBitrate, CancellableTask task) { + public Pair getDownloadInputStream(MusicDirectory.Entry song, long offset, int maxBitrate) { Timber.w("OfflineMusicService.getDownloadInputStream was called but it isn't available"); return null; } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt index 8f380d9e..e724065b 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt @@ -59,7 +59,6 @@ import org.moire.ultrasonic.domain.toDomainEntitiesList import org.moire.ultrasonic.domain.toDomainEntity import org.moire.ultrasonic.domain.toDomainEntityList import org.moire.ultrasonic.domain.toMusicDirectoryDomainEntity -import org.moire.ultrasonic.util.CancellableTask import org.moire.ultrasonic.util.FileUtil import org.moire.ultrasonic.util.Util import timber.log.Timber @@ -611,11 +610,9 @@ open class RESTMusicService( @Throws(Exception::class) override fun getDownloadInputStream( - context: Context, song: MusicDirectory.Entry, offset: Long, - maxBitrate: Int, - task: CancellableTask + maxBitrate: Int ): Pair { val songOffset = if (offset < 0) 0 else offset From 3139c94d11bd873e994511950b9658edcd059bac Mon Sep 17 00:00:00 2001 From: tzugen Date: Sat, 10 Apr 2021 15:59:33 +0200 Subject: [PATCH 04/12] Rename .java to .kt --- .../ultrasonic/service/{DownloadFile.java => DownloadFile.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename ultrasonic/src/main/java/org/moire/ultrasonic/service/{DownloadFile.java => DownloadFile.kt} (100%) diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadFile.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadFile.kt similarity index 100% rename from ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadFile.java rename to ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadFile.kt From cf68038e20055dff4c786209b29f5bdd90763569 Mon Sep 17 00:00:00 2001 From: tzugen Date: Sat, 10 Apr 2021 15:59:33 +0200 Subject: [PATCH 05/12] Migrate DownloadFile to Kotlin --- .../moire/ultrasonic/service/DownloadFile.kt | 673 +++++++----------- .../org/moire/ultrasonic/util/FileUtil.java | 22 + .../ultrasonic/service/LocalMediaPlayer.kt | 2 +- 3 files changed, 297 insertions(+), 400 deletions(-) diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadFile.kt b/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadFile.kt index a3d84d6f..23a46194 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadFile.kt +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadFile.kt @@ -16,505 +16,380 @@ Copyright 2009 (C) Sindre Mehus */ -package org.moire.ultrasonic.service; +package org.moire.ultrasonic.service -import android.content.Context; -import android.net.wifi.WifiManager; -import android.os.PowerManager; -import android.text.TextUtils; -import timber.log.Timber; - -import org.jetbrains.annotations.NotNull; -import org.moire.ultrasonic.domain.MusicDirectory; -import org.moire.ultrasonic.util.CacheCleaner; -import org.moire.ultrasonic.util.CancellableTask; -import org.moire.ultrasonic.util.FileUtil; -import org.moire.ultrasonic.util.Util; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.RandomAccessFile; - -import kotlin.Lazy; -import kotlin.Pair; - -import static android.content.Context.POWER_SERVICE; -import static android.os.PowerManager.ON_AFTER_RELEASE; -import static android.os.PowerManager.SCREEN_DIM_WAKE_LOCK; -import static org.koin.java.KoinJavaComponent.inject; +import android.content.Context +import android.net.wifi.WifiManager.WifiLock +import android.os.PowerManager +import android.os.PowerManager.WakeLock +import android.text.TextUtils +import androidx.lifecycle.MutableLiveData +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream +import java.io.OutputStream +import java.io.RandomAccessFile +import org.koin.java.KoinJavaComponent.inject +import org.moire.ultrasonic.domain.MusicDirectory +import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService +import org.moire.ultrasonic.util.CacheCleaner +import org.moire.ultrasonic.util.CancellableTask +import org.moire.ultrasonic.util.FileUtil +import org.moire.ultrasonic.util.Util +import timber.log.Timber /** + * This class represents a singe Song or Video that can be downloaded. + * * @author Sindre Mehus * @version $Id$ */ -public class DownloadFile -{ - private final Context context; - private final MusicDirectory.Entry song; - private final File partialFile; - private final File completeFile; - private final File saveFile; +class DownloadFile( + private val context: Context, + val song: MusicDirectory.Entry, + private val save: Boolean +) { + val partialFile: File + val completeFile: File + private val saveFile: File = FileUtil.getSongFile(context, song) + private val mediaStoreService: MediaStoreService + private var downloadTask: CancellableTask? = null + var isFailed = false - private final MediaStoreService mediaStoreService; - private CancellableTask downloadTask; - private final boolean save; - private boolean failed; - private int bitRate; - private volatile boolean isPlaying; - private volatile boolean saveWhenDone; - private volatile boolean completeWhenDone; + private val desiredBitRate: Int = Util.getMaxBitRate(context) - private final Lazy downloader = inject(Downloader.class); + @Volatile + private var isPlaying = false - public DownloadFile(Context context, MusicDirectory.Entry song, boolean save) - { - super(); - this.context = context; - this.song = song; - this.save = save; + @Volatile + private var saveWhenDone = false - saveFile = FileUtil.getSongFile(context, song); - bitRate = Util.getMaxBitRate(context); - partialFile = new File(saveFile.getParent(), String.format("%s.partial.%s", FileUtil.getBaseName(saveFile.getName()), FileUtil.getExtension(saveFile.getName()))); - completeFile = new File(saveFile.getParent(), String.format("%s.complete.%s", FileUtil.getBaseName(saveFile.getName()), FileUtil.getExtension(saveFile.getName()))); - mediaStoreService = new MediaStoreService(context); - } + @Volatile + private var completeWhenDone = false - public MusicDirectory.Entry getSong() - { - return song; + private val downloader = inject(Downloader::class.java) + + val progress: MutableLiveData = MutableLiveData(0) + + init { + partialFile = File(saveFile.parent, FileUtil.getPartialFile(saveFile.name)) + completeFile = File(saveFile.parent, FileUtil.getCompleteFile(saveFile.name)) + mediaStoreService = MediaStoreService(context) } /** * Returns the effective bit rate. */ - public int getBitRate() - { - if (!partialFile.exists()) - { - bitRate = Util.getMaxBitRate(context); + fun getBitRate(): Int { + return if (song.bitRate == null) desiredBitRate else song.bitRate!! } - if (bitRate > 0) - { - return bitRate; + @Synchronized + fun download() { + FileUtil.createDirectoryForParent(saveFile) + isFailed = false + downloadTask = DownloadTask() + downloadTask!!.start() } - return song.getBitRate() == null ? 160 : song.getBitRate(); + @Synchronized + fun cancelDownload() { + if (downloadTask != null) { + downloadTask!!.cancel() } - public synchronized void download() - { - FileUtil.createDirectoryForParent(saveFile); - failed = false; - - if (!partialFile.exists()) - { - bitRate = Util.getMaxBitRate(context); } - downloadTask = new DownloadTask(); - downloadTask.start(); + fun getCompleteFile(): File { + if (saveFile.exists()) { + return saveFile } - public synchronized void cancelDownload() - { - if (downloadTask != null) - { - downloadTask.cancel(); - } + return if (completeFile.exists()) { + completeFile + } else saveFile } - public File getCompleteFile() - { - if (saveFile.exists()) - { - return saveFile; - } - - if (completeFile.exists()) - { - return completeFile; - } - - return saveFile; - } - - public File getCompleteOrPartialFile() { - if (isCompleteFileAvailable()) { - return getCompleteFile(); + val completeOrPartialFile: File + get() = if (isCompleteFileAvailable) { + getCompleteFile() } else { - return getPartialFile(); - } + partialFile } - public File getPartialFile() - { - return partialFile; + val isSaved: Boolean + get() = saveFile.exists() + + @get:Synchronized + val isCompleteFileAvailable: Boolean + get() = saveFile.exists() || completeFile.exists() + + @get:Synchronized + val isWorkDone: Boolean + get() = saveFile.exists() || completeFile.exists() && !save || + saveWhenDone || completeWhenDone + + @get:Synchronized + val isDownloading: Boolean + get() = downloadTask != null && downloadTask!!.isRunning + + @get:Synchronized + val isDownloadCancelled: Boolean + get() = downloadTask != null && downloadTask!!.isCancelled + + fun shouldSave(): Boolean { + return save } - public boolean isSaved() - { - return saveFile.exists(); + fun delete() { + cancelDownload() + Util.delete(partialFile) + Util.delete(completeFile) + Util.delete(saveFile) + mediaStoreService.deleteFromMediaStore(this) } - public synchronized boolean isCompleteFileAvailable() - { - return saveFile.exists() || completeFile.exists(); - } - - public synchronized boolean isWorkDone() - { - return saveFile.exists() || (completeFile.exists() && !save) || saveWhenDone || completeWhenDone; - } - - public synchronized boolean isDownloading() - { - return downloadTask != null && downloadTask.isRunning(); - } - - public synchronized boolean isDownloadCancelled() - { - return downloadTask != null && downloadTask.isCancelled(); - } - - public boolean shouldSave() - { - return save; - } - - public boolean isFailed() - { - return failed; - } - - public void delete() - { - cancelDownload(); - Util.delete(partialFile); - Util.delete(completeFile); - Util.delete(saveFile); - mediaStoreService.deleteFromMediaStore(this); - } - - public void unpin() - { - if (saveFile.exists()) - { + fun unpin() { + if (saveFile.exists()) { if (!saveFile.renameTo(completeFile)){ - Timber.w("Renaming file failed. Original file: %s; Rename to: %s", saveFile.getName(), completeFile.getName()); + Timber.w( + "Renaming file failed. Original file: %s; Rename to: %s", + saveFile.name, completeFile.name + ) } } } - public boolean cleanup() - { - boolean ok = true; - - if (completeFile.exists() || saveFile.exists()) - { - ok = Util.delete(partialFile); + fun cleanup(): Boolean { + var ok = true + if (completeFile.exists() || saveFile.exists()) { + ok = Util.delete(partialFile) } - if (saveFile.exists()) - { - ok &= Util.delete(completeFile); + if (saveFile.exists()) { + ok = ok and Util.delete(completeFile) } - return ok; + return ok } // In support of LRU caching. - public void updateModificationDate() - { - updateModificationDate(saveFile); - updateModificationDate(partialFile); - updateModificationDate(completeFile); + fun updateModificationDate() { + updateModificationDate(saveFile) + updateModificationDate(partialFile) + updateModificationDate(completeFile) } - private static void updateModificationDate(File file) - { - if (file.exists()) - { - boolean ok = file.setLastModified(System.currentTimeMillis()); - - if (!ok) - { - Timber.i("Failed to set last-modified date on %s, trying alternate method", file); - - try - { - // Try alternate method to update last modified date to current time - // Found at https://code.google.com/p/android/issues/detail?id=18624 - RandomAccessFile raf = new RandomAccessFile(file, "rw"); - long length = raf.length(); - raf.setLength(length + 1); - raf.setLength(length); - raf.close(); - } - catch (Exception e) - { - Timber.w("Failed to set last-modified date on %s", file); - } - } - } + fun setPlaying(isPlaying: Boolean) { + if (!isPlaying) doPendingRename() + this.isPlaying = isPlaying } - public void setPlaying(boolean isPlaying) - { - try - { - if (saveWhenDone && !isPlaying) - { - Util.renameFile(completeFile, saveFile); - saveWhenDone = false; + // Do a pending rename after the song has stopped playing + private fun doPendingRename() { + try { + if (saveWhenDone) { + Util.renameFile(completeFile, saveFile) + saveWhenDone = false + } else if (completeWhenDone) { + if (save) { + Util.renameFile(partialFile, saveFile) + mediaStoreService.saveInMediaStore(this@DownloadFile) + } else { + Util.renameFile(partialFile, completeFile) } - else if (completeWhenDone && !isPlaying) - { - if (save) - { - Util.renameFile(partialFile, saveFile); - mediaStoreService.saveInMediaStore(DownloadFile.this); + completeWhenDone = false } - else - { - Util.renameFile(partialFile, completeFile); + } catch (ex: IOException) { + Timber.w("Failed to rename file %s to %s", completeFile, saveFile) } - completeWhenDone = false; - } - } - catch (IOException ex) - { - Timber.w("Failed to rename file %s to %s", completeFile, saveFile); } - this.isPlaying = isPlaying; + override fun toString(): String { + return String.format("DownloadFile (%s)", song) } - @NotNull - @Override - public String toString() - { - return String.format("DownloadFile (%s)", song); - } + private inner class DownloadTask : CancellableTask() { + override fun execute() { + var inputStream: InputStream? = null + var outputStream: FileOutputStream? = null + var wakeLock: WakeLock? = null + var wifiLock: WifiLock? = null + try { + wakeLock = acquireWakeLock(wakeLock) + wifiLock = Util.createWifiLock(context, toString()) + wifiLock.acquire() - private class DownloadTask extends CancellableTask - { - @Override - public void execute() - { - InputStream in = null; - FileOutputStream out = null; - PowerManager.WakeLock wakeLock = null; - WifiManager.WifiLock wifiLock = null; - - try - { - if (Util.isScreenLitOnDownload(context)) - { - PowerManager pm = (PowerManager) context.getSystemService(POWER_SERVICE); - wakeLock = pm.newWakeLock(SCREEN_DIM_WAKE_LOCK | ON_AFTER_RELEASE, toString()); - wakeLock.acquire(10*60*1000L /*10 minutes*/); - Timber.i("Acquired wake lock %s", wakeLock); + if (saveFile.exists()) { + Timber.i("%s already exists. Skipping.", saveFile) + return } - wifiLock = Util.createWifiLock(context, toString()); - wifiLock.acquire(); - - if (saveFile.exists()) - { - Timber.i("%s already exists. Skipping.", saveFile); - return; + if (completeFile.exists()) { + if (save) { + if (isPlaying) { + saveWhenDone = true + } else { + Util.renameFile(completeFile, saveFile) } - if (completeFile.exists()) - { - if (save) - { - if (isPlaying) - { - saveWhenDone = true; + } else { + Timber.i("%s already exists. Skipping.", completeFile) } - else - { - Util.renameFile(completeFile, saveFile); - } - } - else - { - Timber.i("%s already exists. Skipping.", completeFile); - } - return; + return } - MusicService musicService = MusicServiceFactory.getMusicService(context); + val musicService = getMusicService(context) // Some devices seem to throw error on partial file which doesn't exist - boolean compare; + val needsDownloading: Boolean + val duration = song.duration + var fileLength: Long = 0 - Integer duration = song.getDuration(); - long fileLength = 0; - - if (!partialFile.exists()) - { - fileLength = partialFile.length(); + if (!partialFile.exists()) { + fileLength = partialFile.length() } - try - { - compare = (bitRate == 0) || (duration == null || duration == 0) || (fileLength == 0); - //(bitRate * song.getDuration() * 1000 / 8) > partialFile.length(); - } - catch (Exception e) - { - compare = true; - } + needsDownloading = ( + desiredBitRate == 0 || duration == null || + duration == 0 || fileLength == 0L + ) - if (compare) - { + if (needsDownloading) { // Attempt partial HTTP GET, appending to the file if it exists. - Pair response = musicService - .getDownloadInputStream(song, partialFile.length(), bitRate); + val (inStream, partial) = musicService + .getDownloadInputStream(song, partialFile.length(), desiredBitRate) - if (response.getSecond()) - { - Timber.i("Executed partial HTTP GET, skipping %d bytes", partialFile.length()); + inputStream = inStream + + if (partial) { + Timber.i( + "Executed partial HTTP GET, skipping %d bytes", + partialFile.length() + ) } - out = new FileOutputStream(partialFile, response.getSecond()); - long n = copy(response.getFirst(), out); - Timber.i("Downloaded %d bytes to %s", n, partialFile); - out.flush(); - out.close(); + outputStream = FileOutputStream(partialFile, partial) - if (isCancelled()) - { - throw new Exception(String.format("Download of '%s' was cancelled", song)); + val len = inputStream.copyTo(outputStream) { + totalBytesCopied -> + setProgress(totalBytesCopied) } - downloadAndSaveCoverArt(musicService); + Timber.i("Downloaded %d bytes to %s", len, partialFile) + + inputStream.close() + outputStream.flush() + outputStream.close() + + if (isCancelled) { + throw Exception(String.format("Download of '%s' was cancelled", song)) + } + downloadAndSaveCoverArt(musicService) } - if (isPlaying) - { - completeWhenDone = true; + if (isPlaying) { + completeWhenDone = true + } else { + if (save) { + Util.renameFile(partialFile, saveFile) + mediaStoreService.saveInMediaStore(this@DownloadFile) + } else { + Util.renameFile(partialFile, completeFile) } - else - { - if (save) - { - Util.renameFile(partialFile, saveFile); - mediaStoreService.saveInMediaStore(DownloadFile.this); } - else - { - Util.renameFile(partialFile, completeFile); + } catch (x: Exception) { + Util.close(outputStream) + Util.delete(completeFile) + Util.delete(saveFile) + if (!isCancelled) { + isFailed = true + Timber.w(x, "Failed to download '%s'.", song) } + } finally { + Util.close(inputStream) + Util.close(outputStream) + if (wakeLock != null) { + wakeLock.release() + Timber.i("Released wake lock %s", wakeLock) } + wifiLock?.release() + CacheCleaner(context).cleanSpace() + downloader.value.checkDownloads() } - catch (Exception x) - { - Util.close(out); - Util.delete(completeFile); - Util.delete(saveFile); - - if (!isCancelled()) - { - failed = true; - Timber.w(x, "Failed to download '%s'.", song); } + private fun acquireWakeLock(wakeLock: WakeLock?): WakeLock? { + var wakeLock1 = wakeLock + if (Util.isScreenLitOnDownload(context)) { + val pm = context.getSystemService(Context.POWER_SERVICE) as PowerManager + val flags = PowerManager.SCREEN_DIM_WAKE_LOCK or PowerManager.ON_AFTER_RELEASE + wakeLock1 = pm.newWakeLock(flags, toString()) + wakeLock1.acquire(10 * 60 * 1000L /*10 minutes*/) + Timber.i("Acquired wake lock %s", wakeLock1) } - finally - { - Util.close(in); - Util.close(out); - if (wakeLock != null) - { - wakeLock.release(); - Timber.i("Released wake lock %s", wakeLock); + return wakeLock1 } - if (wifiLock != null) - { - wifiLock.release(); + override fun toString(): String { + return String.format("DownloadTask (%s)", song) } - new CacheCleaner(context).cleanSpace(); + private fun downloadAndSaveCoverArt(musicService: MusicService) { + try { + if (!TextUtils.isEmpty(song.coverArt)) { + val size = Util.getMinDisplayMetric(context) + musicService.getCoverArt(context, song, size, true, true) + } + } catch (x: Exception) { + Timber.e(x, "Failed to get cover art.") + } - downloader.getValue().checkDownloads(); + } + + @Throws(IOException::class) + fun InputStream.copyTo(out: OutputStream, onCopy: (totalBytesCopied: Long) -> Any): Long { + var bytesCopied: Long = 0 + val buffer = ByteArray(DEFAULT_BUFFER_SIZE) + var bytes = read(buffer) + while (!isCancelled && bytes >= 0) { + out.write(buffer, 0, bytes) + bytesCopied += bytes + onCopy(bytesCopied) + bytes = read(buffer) + } + return bytesCopied } } - @NotNull - @Override - public String toString() - { - return String.format("DownloadTask (%s)", song); - } - - private void downloadAndSaveCoverArt(MusicService musicService) - { - try - { - if (!TextUtils.isEmpty(song.getCoverArt())) { - int size = Util.getMinDisplayMetric(context); - musicService.getCoverArt(context, song, size, true, true); - } - } - catch (Exception x) - { - Timber.e(x, "Failed to get cover art."); - } - } - - private long copy(final InputStream in, OutputStream out) throws IOException - { - // Start a thread that will close the input stream if the task is - // cancelled, thus causing the copy() method to return. - new Thread() - { - @Override - public void run() - { - while (true) - { - Util.sleepQuietly(3000L); - - if (isCancelled()) - { - Util.close(in); - return; + private fun setProgress(totalBytesCopied: Long) { + if (song.size != null) { + progress.postValue((totalBytesCopied * 100 / song.size!!).toInt()) + } } - if (!isRunning()) - { - return; + companion object { + private fun updateModificationDate(file: File) { + if (file.exists()) { + val ok = file.setLastModified(System.currentTimeMillis()) + if (!ok) { + Timber.i( + "Failed to set last-modified date on %s, trying alternate method", + file + ) + try { + // Try alternate method to update last modified date to current time + // Found at https://code.google.com/p/android/issues/detail?id=18624 + val raf = RandomAccessFile(file, "rw") + val length = raf.length() + raf.setLength(length + 1) + raf.setLength(length) + raf.close() + } catch (e: Exception) { + Timber.w("Failed to set last-modified date on %s", file) } } } - }.start(); - - byte[] buffer = new byte[1024 * 16]; - long count = 0; - int n; - long lastLog = System.currentTimeMillis(); - - while (!isCancelled() && (n = in.read(buffer)) != -1) - { - out.write(buffer, 0, n); - count += n; - - long now = System.currentTimeMillis(); - if (now - lastLog > 3000L) - { // Only every so often. - Timber.i("Downloaded %s of %s", Util.formatBytes(count), song); - lastLog = now; - } - } - return count; } } -} \ No newline at end of file +} diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/FileUtil.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/FileUtil.java index 9a52eff5..61c0b05b 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/FileUtil.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/FileUtil.java @@ -549,6 +549,28 @@ public class FileUtil return index == -1 ? name : name.substring(0, index); } + /** + * Returns the file name of a .partial file of the given file. + * + * @param name The filename in question. + * @return The .partial file name + */ + public static String getPartialFile(String name) + { + return String.format("%s.partial.%s", FileUtil.getBaseName(name), FileUtil.getExtension(name)); + } + + /** + * Returns the file name of a .complete file of the given file. + * + * @param name The filename in question. + * @return The .complete file name + */ + public static String getCompleteFile(String name) + { + return String.format("%s.complete.%s", FileUtil.getBaseName(name), FileUtil.getExtension(name)); + } + public static boolean serialize(Context context, T obj, String fileName) { File file = new File(context.getCacheDir(), fileName); diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/LocalMediaPlayer.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/LocalMediaPlayer.kt index 87df94f7..477c0b2b 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/LocalMediaPlayer.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/LocalMediaPlayer.kt @@ -755,7 +755,7 @@ class LocalMediaPlayer( } // Calculate roughly how many bytes BUFFER_LENGTH_SECONDS corresponds to. - val bitRate = downloadFile.bitRate + val bitRate = downloadFile.getBitRate() val byteCount = max(100000, bitRate * 1024L / 8L * bufferLength) // Find out how large the file should grow before resuming playback. From aac74d1eefb73747cdc16032ab3eb9bd8f7f4cf7 Mon Sep 17 00:00:00 2001 From: tzugen Date: Sat, 10 Apr 2021 20:51:57 +0200 Subject: [PATCH 06/12] Make SongView display a percentag while downloading Closes #403 --- .../moire/ultrasonic/service/DownloadFile.kt | 127 +++++++++--------- .../java/org/moire/ultrasonic/util/Util.java | 17 +++ .../org/moire/ultrasonic/view/SongView.kt | 104 +++++++------- 3 files changed, 130 insertions(+), 118 deletions(-) diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadFile.kt b/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadFile.kt index 23a46194..d1039fd3 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadFile.kt +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadFile.kt @@ -76,14 +76,14 @@ class DownloadFile( partialFile = File(saveFile.parent, FileUtil.getPartialFile(saveFile.name)) completeFile = File(saveFile.parent, FileUtil.getCompleteFile(saveFile.name)) mediaStoreService = MediaStoreService(context) - } + } - /** - * Returns the effective bit rate. - */ + /** + * Returns the effective bit rate. + */ fun getBitRate(): Int { return if (song.bitRate == null) desiredBitRate else song.bitRate!! - } + } @Synchronized fun download() { @@ -91,32 +91,31 @@ class DownloadFile( isFailed = false downloadTask = DownloadTask() downloadTask!!.start() - } + } @Synchronized fun cancelDownload() { if (downloadTask != null) { downloadTask!!.cancel() - } - - } + } + } fun getCompleteFile(): File { if (saveFile.exists()) { return saveFile - } + } return if (completeFile.exists()) { completeFile } else saveFile - } + } val completeOrPartialFile: File get() = if (isCompleteFileAvailable) { getCompleteFile() - } else { + } else { partialFile - } + } val isSaved: Boolean get() = saveFile.exists() @@ -140,7 +139,7 @@ class DownloadFile( fun shouldSave(): Boolean { return save - } + } fun delete() { cancelDownload() @@ -148,43 +147,43 @@ class DownloadFile( Util.delete(completeFile) Util.delete(saveFile) mediaStoreService.deleteFromMediaStore(this) - } + } fun unpin() { if (saveFile.exists()) { - if (!saveFile.renameTo(completeFile)){ + if (!saveFile.renameTo(completeFile)) { Timber.w( "Renaming file failed. Original file: %s; Rename to: %s", saveFile.name, completeFile.name ) - } - } - } + } + } + } fun cleanup(): Boolean { var ok = true if (completeFile.exists() || saveFile.exists()) { ok = Util.delete(partialFile) - } + } if (saveFile.exists()) { ok = ok and Util.delete(completeFile) - } + } return ok - } + } - // In support of LRU caching. + // In support of LRU caching. fun updateModificationDate() { updateModificationDate(saveFile) updateModificationDate(partialFile) updateModificationDate(completeFile) - } + } fun setPlaying(isPlaying: Boolean) { if (!isPlaying) doPendingRename() this.isPlaying = isPlaying - } + } // Do a pending rename after the song has stopped playing private fun doPendingRename() { @@ -198,18 +197,17 @@ class DownloadFile( mediaStoreService.saveInMediaStore(this@DownloadFile) } else { Util.renameFile(partialFile, completeFile) - } + } completeWhenDone = false - } + } } catch (ex: IOException) { Timber.w("Failed to rename file %s to %s", completeFile, saveFile) - } - - } + } + } override fun toString(): String { return String.format("DownloadFile (%s)", song) - } + } private inner class DownloadTask : CancellableTask() { override fun execute() { @@ -225,7 +223,7 @@ class DownloadFile( if (saveFile.exists()) { Timber.i("%s already exists. Skipping.", saveFile) return - } + } if (completeFile.exists()) { if (save) { @@ -233,23 +231,23 @@ class DownloadFile( saveWhenDone = true } else { Util.renameFile(completeFile, saveFile) - } + } } else { Timber.i("%s already exists. Skipping.", completeFile) - } + } return - } + } 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 duration = song.duration var fileLength: Long = 0 if (!partialFile.exists()) { fileLength = partialFile.length() - } + } needsDownloading = ( desiredBitRate == 0 || duration == null || @@ -257,7 +255,7 @@ class DownloadFile( ) 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 .getDownloadInputStream(song, partialFile.length(), desiredBitRate) @@ -268,14 +266,13 @@ class DownloadFile( "Executed partial HTTP GET, skipping %d bytes", partialFile.length() ) - } + } outputStream = FileOutputStream(partialFile, partial) - val len = inputStream.copyTo(outputStream) { - totalBytesCopied -> + val len = inputStream.copyTo(outputStream) { totalBytesCopied -> setProgress(totalBytesCopied) - } + } 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)) } downloadAndSaveCoverArt(musicService) - } + } if (isPlaying) { completeWhenDone = true @@ -297,8 +294,8 @@ class DownloadFile( mediaStoreService.saveInMediaStore(this@DownloadFile) } else { Util.renameFile(partialFile, completeFile) - } - } + } + } } catch (x: Exception) { Util.close(outputStream) Util.delete(completeFile) @@ -306,19 +303,19 @@ class DownloadFile( if (!isCancelled) { isFailed = true Timber.w(x, "Failed to download '%s'.", song) - } + } } finally { Util.close(inputStream) Util.close(outputStream) if (wakeLock != null) { wakeLock.release() Timber.i("Released wake lock %s", wakeLock) - } + } wifiLock?.release() CacheCleaner(context).cleanSpace() downloader.value.checkDownloads() - } - } + } + } private fun acquireWakeLock(wakeLock: WakeLock?): WakeLock? { var wakeLock1 = wakeLock @@ -328,24 +325,24 @@ class DownloadFile( wakeLock1 = pm.newWakeLock(flags, toString()) wakeLock1.acquire(10 * 60 * 1000L /*10 minutes*/) Timber.i("Acquired wake lock %s", wakeLock1) - } + } return wakeLock1 - } + } + override fun toString(): String { return String.format("DownloadTask (%s)", song) - } + } private fun downloadAndSaveCoverArt(musicService: MusicService) { try { if (!TextUtils.isEmpty(song.coverArt)) { val size = Util.getMinDisplayMetric(context) musicService.getCoverArt(context, song, size, true, true) - } + } } catch (x: Exception) { Timber.e(x, "Failed to get cover art.") - } - - } + } + } @Throws(IOException::class) fun InputStream.copyTo(out: OutputStream, onCopy: (totalBytesCopied: Long) -> Any): Long { @@ -357,16 +354,16 @@ class DownloadFile( bytesCopied += bytes onCopy(bytesCopied) bytes = read(buffer) - } + } return bytesCopied - } - } + } + } private fun setProgress(totalBytesCopied: Long) { if (song.size != null) { progress.postValue((totalBytesCopied * 100 / song.size!!).toInt()) } - } + } companion object { private fun updateModificationDate(file: File) { @@ -387,9 +384,9 @@ class DownloadFile( raf.close() } catch (e: Exception) { Timber.w("Failed to set last-modified date on %s", file) - } - } - } - } - } + } + } + } + } + } } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java index ca1c5e0e..bd4bb8bc 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java @@ -343,6 +343,23 @@ public class Util toast.show(); } + + /** + * Formats an Int to a percentage string + * For instance: + *
    + *
  • format(99) returns "99 %".
  • + *
+ * + * @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. * For instance: diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/view/SongView.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/view/SongView.kt index 48441f58..4ef7a6c8 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/view/SongView.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/view/SongView.kt @@ -225,59 +225,7 @@ class SongView(context: Context) : UpdateView(context), Checkable { downloadFile = mediaPlayerControllerLazy.value.getDownloadFileForSong(entry) - val partialFile = downloadFile!!.partialFile - - 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() - } - } - } + updateDownloadStatus(downloadFile!!) if (entry?.starred != true) { 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) { viewHolder?.check?.isChecked = b } From a97cb30d1c0e0f984793417512f65521f302b287 Mon Sep 17 00:00:00 2001 From: tzugen Date: Sat, 10 Apr 2021 20:55:43 +0200 Subject: [PATCH 07/12] Move DownloadFile.kt to Kotlin directory --- .../{java => kotlin}/org/moire/ultrasonic/service/DownloadFile.kt | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename ultrasonic/src/main/{java => kotlin}/org/moire/ultrasonic/service/DownloadFile.kt (100%) diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadFile.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadFile.kt similarity index 100% rename from ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadFile.kt rename to ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadFile.kt From d9d82b77d453ca3f68409e8b88dd7dfadd210bc0 Mon Sep 17 00:00:00 2001 From: tzugen Date: Sat, 17 Apr 2021 12:25:21 +0200 Subject: [PATCH 08/12] Rename a function semantically --- .../org/moire/ultrasonic/util/CacheCleaner.java | 2 +- .../org/moire/ultrasonic/util/StreamProxy.java | 2 +- .../org/moire/ultrasonic/service/DownloadFile.kt | 15 ++++++--------- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/CacheCleaner.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/CacheCleaner.java index f9d71d20..890788f8 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/CacheCleaner.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/CacheCleaner.java @@ -229,7 +229,7 @@ public class CacheCleaner for (DownloadFile downloadFile : downloader.getValue().getDownloads()) { filesToNotDelete.add(downloadFile.getPartialFile()); - filesToNotDelete.add(downloadFile.getCompleteFile()); + filesToNotDelete.add(downloadFile.getCompleteOrSaveFile()); } filesToNotDelete.add(FileUtil.getMusicDirectory(context)); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/StreamProxy.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/StreamProxy.java index 0e9870c6..8dfb1cd5 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/StreamProxy.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/StreamProxy.java @@ -194,7 +194,7 @@ public class StreamProxy implements Runnable while (isRunning && !client.isClosed()) { // See if there's more to send - File file = downloadFile.isCompleteFileAvailable() ? downloadFile.getCompleteFile() : downloadFile.getPartialFile(); + File file = downloadFile.isCompleteFileAvailable() ? downloadFile.getCompleteOrSaveFile() : downloadFile.getPartialFile(); int cbSentThisBatch = 0; if (file.exists()) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadFile.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadFile.kt index d1039fd3..3754d645 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadFile.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadFile.kt @@ -100,19 +100,16 @@ class DownloadFile( } } - fun getCompleteFile(): File { - if (saveFile.exists()) { - return saveFile - } - - return if (completeFile.exists()) { + val completeOrSaveFile: File + get() = if (saveFile.exists()) { + saveFile + } else { completeFile - } else saveFile - } + } val completeOrPartialFile: File get() = if (isCompleteFileAvailable) { - getCompleteFile() + completeOrSaveFile } else { partialFile } From 0efb3547c30de38714955a9a54c20229c527e3bf Mon Sep 17 00:00:00 2001 From: tzugen Date: Fri, 16 Apr 2021 11:45:37 +0200 Subject: [PATCH 09/12] Update license of two files. --- .../moire/ultrasonic/service/DownloadFile.kt | 21 +++++-------------- .../ultrasonic/service/LocalMediaPlayer.kt | 7 +++++++ 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadFile.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadFile.kt index 3754d645..c9334f41 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadFile.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadFile.kt @@ -1,21 +1,10 @@ /* - This file is part of Subsonic. - - Subsonic is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Subsonic is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Subsonic. If not, see . - - Copyright 2009 (C) Sindre Mehus + * DownloadFile.kt + * Copyright (C) 2009-2021 Ultrasonic developers + * + * Distributed under terms of the GNU GPLv3 license. */ + package org.moire.ultrasonic.service import android.content.Context diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/LocalMediaPlayer.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/LocalMediaPlayer.kt index 477c0b2b..5dc20678 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/LocalMediaPlayer.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/LocalMediaPlayer.kt @@ -1,3 +1,10 @@ +/* + * LocalMediaPlayer.kt + * Copyright (C) 2009-2021 Ultrasonic developers + * + * Distributed under terms of the GNU GPLv3 license. + */ + package org.moire.ultrasonic.service import android.app.PendingIntent From 8d4d1ba6604bfd18e67db6599a9a3ffc0110418d Mon Sep 17 00:00:00 2001 From: tzugen Date: Fri, 16 Apr 2021 14:41:00 +0200 Subject: [PATCH 10/12] Move function out of companion object --- .../moire/ultrasonic/service/DownloadFile.kt | 39 +++++++++---------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadFile.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadFile.kt index c9334f41..08b081c6 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadFile.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadFile.kt @@ -351,26 +351,25 @@ class DownloadFile( } } - companion object { - private fun updateModificationDate(file: File) { - if (file.exists()) { - val ok = file.setLastModified(System.currentTimeMillis()) - if (!ok) { - Timber.i( - "Failed to set last-modified date on %s, trying alternate method", - file - ) - try { - // Try alternate method to update last modified date to current time - // Found at https://code.google.com/p/android/issues/detail?id=18624 - val raf = RandomAccessFile(file, "rw") - val length = raf.length() - raf.setLength(length + 1) - raf.setLength(length) - raf.close() - } catch (e: Exception) { - Timber.w("Failed to set last-modified date on %s", file) - } + private fun updateModificationDate(file: File) { + if (file.exists()) { + val ok = file.setLastModified(System.currentTimeMillis()) + if (!ok) { + Timber.i( + "Failed to set last-modified date on %s, trying alternate method", + file + ) + try { + // Try alternate method to update last modified date to current time + // Found at https://code.google.com/p/android/issues/detail?id=18624 + // According to the bug, this was fixed in Android 8.0 (API 26) + val raf = RandomAccessFile(file, "rw") + val length = raf.length() + raf.setLength(length + 1) + raf.setLength(length) + raf.close() + } catch (e: Exception) { + Timber.w("Failed to set last-modified date on %s", file) } } } From 4787924fcc280a2d18bd92c382d9e47f3cba7779 Mon Sep 17 00:00:00 2001 From: tzugen Date: Sat, 17 Apr 2021 11:56:11 +0200 Subject: [PATCH 11/12] Remove noisy log calls --- .../java/org/moire/ultrasonic/fragment/PlayerFragment.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/PlayerFragment.java b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/PlayerFragment.java index f058a3f4..d1ba25ea 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/PlayerFragment.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/PlayerFragment.java @@ -1377,11 +1377,9 @@ public class PlayerFragment extends Fragment implements GestureDetector.OnGestur case DOWNLOADING: final long bytes = currentPlaying != null ? currentPlaying.getPartialFile().length() : 0; String downloadStatus = getResources().getString(R.string.download_playerstate_downloading, Util.formatLocalizedBytes(bytes, getContext())); - Timber.d("Player set title"); FragmentTitle.Companion.setTitle(PlayerFragment.this, downloadStatus); break; case PREPARING: - Timber.d("Player set title"); FragmentTitle.Companion.setTitle(PlayerFragment.this, R.string.download_playerstate_buffering); break; case STARTED: @@ -1389,17 +1387,14 @@ public class PlayerFragment extends Fragment implements GestureDetector.OnGestur if (mediaPlayerController != null && mediaPlayerController.isShufflePlayEnabled()) { - Timber.d("Player set title"); FragmentTitle.Companion.setTitle(PlayerFragment.this, R.string.download_playerstate_playing_shuffle); } else { - Timber.d("Player set title"); FragmentTitle.Companion.setTitle(PlayerFragment.this, R.string.common_appname); } break; default: - Timber.d("Player set title"); FragmentTitle.Companion.setTitle(PlayerFragment.this, R.string.common_appname); break; case IDLE: From 467df602ec1b101e9f07c0d47dbb08a1948cc6d5 Mon Sep 17 00:00:00 2001 From: tzugen Date: Sat, 17 Apr 2021 11:58:42 +0200 Subject: [PATCH 12/12] Show download percentage also in the title --- .../java/org/moire/ultrasonic/fragment/PlayerFragment.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/PlayerFragment.java b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/PlayerFragment.java index d1ba25ea..052b8fe5 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/PlayerFragment.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/PlayerFragment.java @@ -1375,8 +1375,7 @@ public class PlayerFragment extends Fragment implements GestureDetector.OnGestur switch (playerState) { case DOWNLOADING: - final long bytes = currentPlaying != null ? currentPlaying.getPartialFile().length() : 0; - String downloadStatus = getResources().getString(R.string.download_playerstate_downloading, Util.formatLocalizedBytes(bytes, getContext())); + String downloadStatus = getResources().getString(R.string.download_playerstate_downloading, Util.formatPercentage(currentPlaying.getProgress().getValue())); FragmentTitle.Companion.setTitle(PlayerFragment.this, downloadStatus); break; case PREPARING: