From e55d894f45e49135f5f688d7bc2ef28ca669b7ce Mon Sep 17 00:00:00 2001 From: tzugen Date: Wed, 26 May 2021 21:32:52 +0200 Subject: [PATCH 01/15] Consistently throw Exeception in OfflineMusicService --- .../service/OfflineMusicService.java | 82 ++++++++----------- 1 file changed, 35 insertions(+), 47 deletions(-) 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 6bff4eb4..d5c1fac1 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.java @@ -787,54 +787,48 @@ public class OfflineMusicService implements MusicService } @Override - public MusicDirectory getAlbumList2(String type, int size, int offset, String musicFolderId) { - Timber.w("OfflineMusicService.getAlbumList2 was called but it isn't available"); - return null; + public MusicDirectory getAlbumList2(String type, int size, int offset, String musicFolderId) throws OfflineException { + throw new OfflineException("OfflineMusicService.getAlbumList2 was called but it isn't available"); } @Override - public String getVideoUrl(String id, boolean useFlash) { - Timber.w("OfflineMusicService.getVideoUrl was called but it isn't available"); - return null; + public String getVideoUrl(String id, boolean useFlash) throws OfflineException { + throw new OfflineException("OfflineMusicService.getVideoUrl was called but it isn't available"); } @Override - public List getChatMessages(Long since) { - Timber.w("OfflineMusicService.getChatMessages was called but it isn't available"); - return null; + public List getChatMessages(Long since) throws OfflineException { + throw new OfflineException("OfflineMusicService.getChatMessages was called but it isn't available"); + } + + @Override + public void addChatMessage(String message) throws OfflineException { + throw new OfflineException("OfflineMusicService.addChatMessage was called but it isn't available"); } @Override - public void addChatMessage(String message) { - Timber.w("OfflineMusicService.addChatMessage was called but it isn't available"); + public List getBookmarks() throws OfflineException { + throw new OfflineException("OfflineMusicService.getBookmarks was called but it isn't available"); } @Override - public List getBookmarks() { - Timber.w("OfflineMusicService.getBookmarks was called but it isn't available"); - return null; + public void deleteBookmark(String id) throws OfflineException { + throw new OfflineException("OfflineMusicService.deleteBookmark was called but it isn't available"); } @Override - public void deleteBookmark(String id) { - Timber.w("OfflineMusicService.deleteBookmark was called but it isn't available"); + public void createBookmark(String id, int position) throws OfflineException { + throw new OfflineException("OfflineMusicService.createBookmark was called but it isn't available"); } @Override - public void createBookmark(String id, int position) { - Timber.w("OfflineMusicService.createBookmark was called but it isn't available"); + public MusicDirectory getVideos(boolean refresh) throws OfflineException { + throw new OfflineException("OfflineMusicService.getVideos was called but it isn't available"); } @Override - public MusicDirectory getVideos(boolean refresh) { - Timber.w("OfflineMusicService.getVideos was called but it isn't available"); - return null; - } - - @Override - public SearchResult getStarred2() { - Timber.w("OfflineMusicService.getStarred2 was called but it isn't available"); - return null; + public SearchResult getStarred2() throws OfflineException { + throw new OfflineException("OfflineMusicService.getStarred2 was called but it isn't available"); } @Override @@ -847,43 +841,37 @@ public class OfflineMusicService implements MusicService } @Override - public Indexes getArtists(boolean refresh) { - Timber.w("OfflineMusicService.getArtists was called but it isn't available"); - return null; + public Indexes getArtists(boolean refresh) throws OfflineException { + throw new OfflineException("OfflineMusicService.getArtists was called but it isn't available"); } @Override - public MusicDirectory getArtist(String id, String name, boolean refresh) { - Timber.w("OfflineMusicService.getArtist was called but it isn't available"); - return null; + public MusicDirectory getArtist(String id, String name, boolean refresh) throws OfflineException { + throw new OfflineException("OfflineMusicService.getArtist was called but it isn't available"); } @Override - public MusicDirectory getAlbum(String id, String name, boolean refresh) { - Timber.w("OfflineMusicService.getAlbum was called but it isn't available"); - return null; + public MusicDirectory getAlbum(String id, String name, boolean refresh) throws OfflineException { + throw new OfflineException("OfflineMusicService.getAlbum was called but it isn't available"); } @Override - public MusicDirectory getPodcastEpisodes(String podcastChannelId) { - Timber.w("OfflineMusicService.getPodcastEpisodes was called but it isn't available"); - return null; + public MusicDirectory getPodcastEpisodes(String podcastChannelId) throws OfflineException { + throw new OfflineException("OfflineMusicService.getPodcastEpisodes was called but it isn't available"); } @Override - public Pair getDownloadInputStream(MusicDirectory.Entry song, long offset, int maxBitrate) { - Timber.w("OfflineMusicService.getDownloadInputStream was called but it isn't available"); - return null; + public Pair getDownloadInputStream(MusicDirectory.Entry song, long offset, int maxBitrate) throws OfflineException { + throw new OfflineException("OfflineMusicService.getDownloadInputStream was called but it isn't available"); } @Override - public void setRating(String id, int rating) { - Timber.w("OfflineMusicService.setRating was called but it isn't available"); + public void setRating(String id, int rating) throws OfflineException { + throw new OfflineException("OfflineMusicService.setRating was called but it isn't available"); } @Override - public List getPodcastsChannels(boolean refresh) { - Timber.w("OfflineMusicService.getPodcastsChannels was called but it isn't available"); - return null; + public List getPodcastsChannels(boolean refresh) throws OfflineException { + throw new OfflineException("OfflineMusicService.getPodcastsChannels was called but it isn't available"); } } From 3f2daaa7ec25e5533b4d27502b35dddbffeff896 Mon Sep 17 00:00:00 2001 From: tzugen Date: Wed, 26 May 2021 23:17:52 +0200 Subject: [PATCH 02/15] Rename .java to .kt --- .../service/{CachedMusicService.java => CachedMusicService.kt} | 0 .../ultrasonic/service/{MusicService.java => MusicService.kt} | 0 .../service/{OfflineException.java => OfflineException.kt} | 0 .../service/{OfflineMusicService.java => OfflineMusicService.kt} | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename ultrasonic/src/main/java/org/moire/ultrasonic/service/{CachedMusicService.java => CachedMusicService.kt} (100%) rename ultrasonic/src/main/java/org/moire/ultrasonic/service/{MusicService.java => MusicService.kt} (100%) rename ultrasonic/src/main/java/org/moire/ultrasonic/service/{OfflineException.java => OfflineException.kt} (100%) rename ultrasonic/src/main/java/org/moire/ultrasonic/service/{OfflineMusicService.java => OfflineMusicService.kt} (100%) diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/CachedMusicService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/CachedMusicService.kt similarity index 100% rename from ultrasonic/src/main/java/org/moire/ultrasonic/service/CachedMusicService.java rename to ultrasonic/src/main/java/org/moire/ultrasonic/service/CachedMusicService.kt diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicService.kt similarity index 100% rename from ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicService.java rename to ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicService.kt diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineException.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineException.kt similarity index 100% rename from ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineException.java rename to ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineException.kt diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.kt similarity index 100% rename from ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.java rename to ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.kt From 5ac36b749bd8ab8c263a9c7f1bac97e7fb75f38e Mon Sep 17 00:00:00 2001 From: tzugen Date: Wed, 26 May 2021 23:17:52 +0200 Subject: [PATCH 03/15] Convert all Services to Kotlin --- .../ultrasonic/service/CachedMusicService.kt | 978 +++++----- .../moire/ultrasonic/service/MusicService.kt | 237 ++- .../ultrasonic/service/OfflineException.kt | 39 +- .../ultrasonic/service/OfflineMusicService.kt | 1592 ++++++++--------- 4 files changed, 1328 insertions(+), 1518 deletions(-) diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/CachedMusicService.kt b/ultrasonic/src/main/java/org/moire/ultrasonic/service/CachedMusicService.kt index 5d81f7e0..417802ca 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/CachedMusicService.kt +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/CachedMusicService.kt @@ -16,524 +16,470 @@ Copyright 2009 (C) Sindre Mehus */ -package org.moire.ultrasonic.service; +package org.moire.ultrasonic.service -import android.graphics.Bitmap; - -import org.moire.ultrasonic.data.ActiveServerProvider; -import org.moire.ultrasonic.domain.Bookmark; -import org.moire.ultrasonic.domain.ChatMessage; -import org.moire.ultrasonic.domain.Genre; -import org.moire.ultrasonic.domain.Indexes; -import org.moire.ultrasonic.domain.JukeboxStatus; -import org.moire.ultrasonic.domain.Lyrics; -import org.moire.ultrasonic.domain.MusicDirectory; -import org.moire.ultrasonic.domain.MusicFolder; -import org.moire.ultrasonic.domain.Playlist; -import org.moire.ultrasonic.domain.PodcastsChannel; -import org.moire.ultrasonic.domain.SearchCriteria; -import org.moire.ultrasonic.domain.SearchResult; -import org.moire.ultrasonic.domain.Share; -import org.moire.ultrasonic.domain.UserInfo; -import org.moire.ultrasonic.util.Constants; -import org.moire.ultrasonic.util.LRUCache; -import org.moire.ultrasonic.util.TimeLimitedCache; -import org.moire.ultrasonic.util.Util; - -import java.io.InputStream; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.concurrent.TimeUnit; - -import kotlin.Lazy; -import kotlin.Pair; - -import static org.koin.java.KoinJavaComponent.inject; +import android.graphics.Bitmap +import org.koin.java.KoinJavaComponent.inject +import org.moire.ultrasonic.data.ActiveServerProvider +import org.moire.ultrasonic.domain.Bookmark +import org.moire.ultrasonic.domain.ChatMessage +import org.moire.ultrasonic.domain.Genre +import org.moire.ultrasonic.domain.Indexes +import org.moire.ultrasonic.domain.JukeboxStatus +import org.moire.ultrasonic.domain.Lyrics +import org.moire.ultrasonic.domain.MusicDirectory +import org.moire.ultrasonic.domain.MusicFolder +import org.moire.ultrasonic.domain.Playlist +import org.moire.ultrasonic.domain.PodcastsChannel +import org.moire.ultrasonic.domain.SearchCriteria +import org.moire.ultrasonic.domain.SearchResult +import org.moire.ultrasonic.domain.Share +import org.moire.ultrasonic.domain.UserInfo +import org.moire.ultrasonic.util.Constants +import org.moire.ultrasonic.util.LRUCache +import org.moire.ultrasonic.util.TimeLimitedCache +import org.moire.ultrasonic.util.Util +import java.io.InputStream +import java.util.concurrent.TimeUnit /** * @author Sindre Mehus */ -public class CachedMusicService implements MusicService -{ - private final Lazy activeServerProvider = inject(ActiveServerProvider.class); - - private static final int MUSIC_DIR_CACHE_SIZE = 100; - - private final MusicService musicService; - private final LRUCache> cachedMusicDirectories; - private final LRUCache> cachedArtist; - private final LRUCache> cachedAlbum; - private final LRUCache> cachedUserInfo; - private final TimeLimitedCache cachedLicenseValid = new TimeLimitedCache<>(120, TimeUnit.SECONDS); - private final TimeLimitedCache cachedIndexes = new TimeLimitedCache<>(60 * 60, TimeUnit.SECONDS); - private final TimeLimitedCache cachedArtists = new TimeLimitedCache<>(60 * 60, TimeUnit.SECONDS); - private final TimeLimitedCache> cachedPlaylists = new TimeLimitedCache<>(3600, TimeUnit.SECONDS); - private final TimeLimitedCache> cachedPodcastsChannels = new TimeLimitedCache<>(3600, TimeUnit.SECONDS); - private final TimeLimitedCache> cachedMusicFolders = new TimeLimitedCache<>(10 * 3600, TimeUnit.SECONDS); - private final TimeLimitedCache> cachedGenres = new TimeLimitedCache<>(10 * 3600, TimeUnit.SECONDS); - - private String restUrl; - private String cachedMusicFolderId; - - public CachedMusicService(MusicService musicService) - { - this.musicService = musicService; - cachedMusicDirectories = new LRUCache<>(MUSIC_DIR_CACHE_SIZE); - cachedArtist = new LRUCache<>(MUSIC_DIR_CACHE_SIZE); - cachedAlbum = new LRUCache<>(MUSIC_DIR_CACHE_SIZE); - cachedUserInfo = new LRUCache<>(MUSIC_DIR_CACHE_SIZE); - } - - @Override - public void ping() throws Exception - { - checkSettingsChanged(); - musicService.ping(); - } - - @Override - public boolean isLicenseValid() throws Exception - { - checkSettingsChanged(); - Boolean result = cachedLicenseValid.get(); - if (result == null) - { - result = musicService.isLicenseValid(); - cachedLicenseValid.set(result, result ? 30L * 60L : 2L * 60L, TimeUnit.SECONDS); - } - return result; - } - - @Override - public List getMusicFolders(boolean refresh) throws Exception - { - checkSettingsChanged(); - if (refresh) - { - cachedMusicFolders.clear(); - } - List result = cachedMusicFolders.get(); - if (result == null) - { - result = musicService.getMusicFolders(refresh); - cachedMusicFolders.set(result); - } - return result; - } - - @Override - public Indexes getIndexes(String musicFolderId, boolean refresh) throws Exception - { - checkSettingsChanged(); - if (refresh) - { - cachedIndexes.clear(); - cachedMusicFolders.clear(); - cachedMusicDirectories.clear(); - } - Indexes result = cachedIndexes.get(); - if (result == null) - { - result = musicService.getIndexes(musicFolderId, refresh); - cachedIndexes.set(result); - } - return result; - } - - @Override - public Indexes getArtists(boolean refresh) throws Exception - { - checkSettingsChanged(); - if (refresh) - { - cachedArtists.clear(); - } - Indexes result = cachedArtists.get(); - if (result == null) - { - result = musicService.getArtists(refresh); - cachedArtists.set(result); - } - return result; - } - - @Override - public MusicDirectory getMusicDirectory(String id, String name, boolean refresh) throws Exception - { - checkSettingsChanged(); - TimeLimitedCache cache = refresh ? null : cachedMusicDirectories.get(id); - - MusicDirectory dir = cache == null ? null : cache.get(); - - if (dir == null) - { - dir = musicService.getMusicDirectory(id, name, refresh); - cache = new TimeLimitedCache<>(Util.getDirectoryCacheTime(), TimeUnit.SECONDS); - cache.set(dir); - cachedMusicDirectories.put(id, cache); - } - return dir; - } - - @Override - public MusicDirectory getArtist(String id, String name, boolean refresh) throws Exception - { - checkSettingsChanged(); - TimeLimitedCache cache = refresh ? null : cachedArtist.get(id); - MusicDirectory dir = cache == null ? null : cache.get(); - if (dir == null) - { - dir = musicService.getArtist(id, name, refresh); - cache = new TimeLimitedCache<>(Util.getDirectoryCacheTime(), TimeUnit.SECONDS); - cache.set(dir); - cachedArtist.put(id, cache); - } - return dir; - } - - @Override - public MusicDirectory getAlbum(String id, String name, boolean refresh) throws Exception - { - checkSettingsChanged(); - TimeLimitedCache cache = refresh ? null : cachedAlbum.get(id); - MusicDirectory dir = cache == null ? null : cache.get(); - if (dir == null) - { - dir = musicService.getAlbum(id, name, refresh); - cache = new TimeLimitedCache<>(Util.getDirectoryCacheTime(), TimeUnit.SECONDS); - cache.set(dir); - cachedAlbum.put(id, cache); - } - return dir; - } - - @Override - public SearchResult search(SearchCriteria criteria) throws Exception - { - return musicService.search(criteria); - } - - @Override - public MusicDirectory getPlaylist(String id, String name) throws Exception - { - return musicService.getPlaylist(id, name); - } - - @Override - public List getPodcastsChannels(boolean refresh) throws Exception { - checkSettingsChanged(); - List result = refresh ? null : cachedPodcastsChannels.get(); - if (result == null) - { - result = musicService.getPodcastsChannels(refresh); - cachedPodcastsChannels.set(result); - } - return result; - } - - @Override - public MusicDirectory getPodcastEpisodes(String podcastChannelId) throws Exception { - return musicService.getPodcastEpisodes(podcastChannelId); - } - - - @Override - public List getPlaylists(boolean refresh) throws Exception - { - checkSettingsChanged(); - List result = refresh ? null : cachedPlaylists.get(); - if (result == null) - { - result = musicService.getPlaylists(refresh); - cachedPlaylists.set(result); - } - return result; - } - - @Override - public void createPlaylist(String id, String name, List entries) throws Exception - { - cachedPlaylists.clear(); - musicService.createPlaylist(id, name, entries); - } - - @Override - public void deletePlaylist(String id) throws Exception - { - musicService.deletePlaylist(id); - } - - @Override - public void updatePlaylist(String id, String name, String comment, boolean pub) throws Exception - { - musicService.updatePlaylist(id, name, comment, pub); - } - - @Override - public Lyrics getLyrics(String artist, String title) throws Exception - { - return musicService.getLyrics(artist, title); - } - - @Override - public void scrobble(String id, boolean submission) throws Exception - { - musicService.scrobble(id, submission); - } - - @Override - public MusicDirectory getAlbumList(String type, int size, int offset, String musicFolderId) throws Exception - { - return musicService.getAlbumList(type, size, offset, musicFolderId); - } - - @Override - public MusicDirectory getAlbumList2(String type, int size, int offset, String musicFolderId) throws Exception - { - return musicService.getAlbumList2(type, size, offset, musicFolderId); - } - - @Override - public MusicDirectory getRandomSongs(int size) throws Exception - { - return musicService.getRandomSongs(size); - } - - @Override - public SearchResult getStarred() throws Exception - { - return musicService.getStarred(); - } - - @Override - public SearchResult getStarred2() throws Exception - { - return musicService.getStarred2(); - } - - @Override - public Bitmap getCoverArt(MusicDirectory.Entry entry, int size, boolean saveToFile, boolean highQuality) throws Exception - { - return musicService.getCoverArt(entry, size, saveToFile, highQuality); - } - - @Override - public Pair getDownloadInputStream(MusicDirectory.Entry song, long offset, int maxBitrate) throws Exception - { - return musicService.getDownloadInputStream(song, offset, maxBitrate); - } - - @Override - public String getVideoUrl(String id, boolean useFlash) throws Exception - { - return musicService.getVideoUrl(id, useFlash); - } - - @Override - public JukeboxStatus updateJukeboxPlaylist(List ids) throws Exception - { - return musicService.updateJukeboxPlaylist(ids); - } - - @Override - public JukeboxStatus skipJukebox(int index, int offsetSeconds) throws Exception - { - return musicService.skipJukebox(index, offsetSeconds); - } - - @Override - public JukeboxStatus stopJukebox() throws Exception - { - return musicService.stopJukebox(); - } - - @Override - public JukeboxStatus startJukebox() throws Exception - { - return musicService.startJukebox(); - } - - @Override - public JukeboxStatus getJukeboxStatus() throws Exception - { - return musicService.getJukeboxStatus(); - } - - @Override - public JukeboxStatus setJukeboxGain(float gain) throws Exception - { - return musicService.setJukeboxGain(gain); - } - - private void checkSettingsChanged() - { - String newUrl = activeServerProvider.getValue().getRestUrl(null); - String newFolderId = activeServerProvider.getValue().getActiveServer().getMusicFolderId(); - if (!Util.equals(newUrl, restUrl) || !Util.equals(cachedMusicFolderId,newFolderId)) - { - cachedMusicFolders.clear(); - cachedMusicDirectories.clear(); - cachedLicenseValid.clear(); - cachedIndexes.clear(); - cachedPlaylists.clear(); - cachedGenres.clear(); - cachedAlbum.clear(); - cachedArtist.clear(); - cachedUserInfo.clear(); - restUrl = newUrl; - cachedMusicFolderId = newFolderId; - } - } - - @Override - public void star(String id, String albumId, String artistId) throws Exception - { - musicService.star(id, albumId, artistId); - } - - @Override - public void unstar(String id, String albumId, String artistId) throws Exception - { - musicService.unstar(id, albumId, artistId); - } - - @Override - public void setRating(String id, int rating) throws Exception - { - musicService.setRating(id, rating); - } - - @Override - public List getGenres(boolean refresh) throws Exception - { - checkSettingsChanged(); - if (refresh) - { - cachedGenres.clear(); - } - List result = cachedGenres.get(); - - if (result == null) - { - result = musicService.getGenres(refresh); - cachedGenres.set(result); - } - - Collections.sort(result, new Comparator() - { - @Override - public int compare(Genre genre, Genre genre2) - { - return genre.getName().compareToIgnoreCase(genre2.getName()); - } - }); - - return result; - } - - @Override - public MusicDirectory getSongsByGenre(String genre, int count, int offset) throws Exception - { - return musicService.getSongsByGenre(genre, count, offset); - } - - @Override - public List getShares(boolean refresh) throws Exception - { - return musicService.getShares(refresh); - } - - @Override - public List getChatMessages(Long since) throws Exception - { - return musicService.getChatMessages(since); - } - - @Override - public void addChatMessage(String message) throws Exception - { - musicService.addChatMessage(message); - } - - @Override - public List getBookmarks() throws Exception - { - return musicService.getBookmarks(); - } - - @Override - public void deleteBookmark(String id) throws Exception - { - musicService.deleteBookmark(id); - } - - @Override - public void createBookmark(String id, int position) throws Exception - { - musicService.createBookmark(id, position); - } - - @Override - public MusicDirectory getVideos(boolean refresh) throws Exception - { - checkSettingsChanged(); - TimeLimitedCache cache = refresh ? null : cachedMusicDirectories.get(Constants.INTENT_EXTRA_NAME_VIDEOS); - - MusicDirectory dir = cache == null ? null : cache.get(); - - if (dir == null) - { - dir = musicService.getVideos(refresh); - cache = new TimeLimitedCache<>(Util.getDirectoryCacheTime(), TimeUnit.SECONDS); - cache.set(dir); - cachedMusicDirectories.put(Constants.INTENT_EXTRA_NAME_VIDEOS, cache); - } - - return dir; - } - - @Override - public UserInfo getUser(String username) throws Exception - { - checkSettingsChanged(); - - TimeLimitedCache cache = cachedUserInfo.get(username); - - UserInfo userInfo = cache == null ? null : cache.get(); - - if (userInfo == null) - { - userInfo = musicService.getUser(username); - cache = new TimeLimitedCache<>(Util.getDirectoryCacheTime(), TimeUnit.SECONDS); - cache.set(userInfo); - cachedUserInfo.put(username, cache); - } - - return userInfo; - } - - @Override - public List createShare(List ids, String description, Long expires) throws Exception - { - return musicService.createShare(ids, description, expires); - } - - @Override - public void deleteShare(String id) throws Exception - { - musicService.deleteShare(id); - } - - @Override - public void updateShare(String id, String description, Long expires) throws Exception - { - musicService.updateShare(id, description, expires); - } - - @Override - public Bitmap getAvatar(String username, int size, boolean saveToFile, boolean highQuality) throws Exception - { - return musicService.getAvatar(username, size, saveToFile, highQuality); - } -} +class CachedMusicService(private val musicService: MusicService) : MusicService { + private val activeServerProvider = inject( + ActiveServerProvider::class.java + ) + private val cachedMusicDirectories: LRUCache> + private val cachedArtist: LRUCache> + private val cachedAlbum: LRUCache> + private val cachedUserInfo: LRUCache> + private val cachedLicenseValid = TimeLimitedCache(120, TimeUnit.SECONDS) + private val cachedIndexes = TimeLimitedCache(60 * 60, TimeUnit.SECONDS) + private val cachedArtists = TimeLimitedCache(60 * 60, TimeUnit.SECONDS) + private val cachedPlaylists = TimeLimitedCache?>(3600, TimeUnit.SECONDS) + private val cachedPodcastsChannels = + TimeLimitedCache>(3600, TimeUnit.SECONDS) + private val cachedMusicFolders = + TimeLimitedCache?>(10 * 3600, TimeUnit.SECONDS) + private val cachedGenres = TimeLimitedCache?>(10 * 3600, TimeUnit.SECONDS) + private var restUrl: String? = null + private var cachedMusicFolderId: String? = null + + @Throws(Exception::class) + override fun ping() { + checkSettingsChanged() + musicService.ping() + } + + @Throws(Exception::class) + override fun isLicenseValid(): Boolean { + checkSettingsChanged() + var result = cachedLicenseValid.get() + if (result == null) { + result = musicService.isLicenseValid() + cachedLicenseValid[result, if (result) 30L * 60L else 2L * 60L] = TimeUnit.SECONDS + } + return result + } + + @Throws(Exception::class) + override fun getMusicFolders(refresh: Boolean): List { + checkSettingsChanged() + if (refresh) { + cachedMusicFolders.clear() + } + + val cache = cachedMusicFolders.get() + if (cache != null) return cache + + val result = musicService.getMusicFolders(refresh) + cachedMusicFolders.set(result) + + return result + } + + @Throws(Exception::class) + override fun getIndexes(musicFolderId: String?, refresh: Boolean): Indexes { + checkSettingsChanged() + if (refresh) { + cachedIndexes.clear() + cachedMusicFolders.clear() + cachedMusicDirectories.clear() + } + var result = cachedIndexes.get() + if (result == null) { + result = musicService.getIndexes(musicFolderId, refresh) + cachedIndexes.set(result) + } + return result + } + + @Throws(Exception::class) + override fun getArtists(refresh: Boolean): Indexes { + checkSettingsChanged() + if (refresh) { + cachedArtists.clear() + } + var result = cachedArtists.get() + if (result == null) { + result = musicService.getArtists(refresh) + cachedArtists.set(result) + } + return result + } + + @Throws(Exception::class) + override fun getMusicDirectory(id: String, name: String?, refresh: Boolean): MusicDirectory { + checkSettingsChanged() + var cache = if (refresh) null else cachedMusicDirectories[id] + var dir = cache?.get() + if (dir == null) { + dir = musicService.getMusicDirectory(id, name, refresh) + cache = TimeLimitedCache( + Util.getDirectoryCacheTime().toLong(), TimeUnit.SECONDS + ) + cache.set(dir) + cachedMusicDirectories.put(id, cache) + } + return dir + } + + @Throws(Exception::class) + override fun getArtist(id: String, name: String?, refresh: Boolean): MusicDirectory { + checkSettingsChanged() + var cache = if (refresh) null else cachedArtist[id] + var dir = cache?.get() + if (dir == null) { + dir = musicService.getArtist(id, name, refresh) + cache = TimeLimitedCache( + Util.getDirectoryCacheTime().toLong(), TimeUnit.SECONDS + ) + cache.set(dir) + cachedArtist.put(id, cache) + } + return dir + } + + @Throws(Exception::class) + override fun getAlbum(id: String, name: String?, refresh: Boolean): MusicDirectory { + checkSettingsChanged() + var cache = if (refresh) null else cachedAlbum[id] + var dir = cache?.get() + if (dir == null) { + dir = musicService.getAlbum(id, name, refresh) + cache = TimeLimitedCache( + Util.getDirectoryCacheTime().toLong(), TimeUnit.SECONDS + ) + cache.set(dir) + cachedAlbum.put(id, cache) + } + return dir + } + + @Throws(Exception::class) + override fun search(criteria: SearchCriteria): SearchResult? { + return musicService.search(criteria) + } + + @Throws(Exception::class) + override fun getPlaylist(id: String, name: String): MusicDirectory { + return musicService.getPlaylist(id, name) + } + + @Throws(Exception::class) + override fun getPodcastsChannels(refresh: Boolean): List { + checkSettingsChanged() + var result = if (refresh) null else cachedPodcastsChannels.get() + if (result == null) { + result = musicService.getPodcastsChannels(refresh) + cachedPodcastsChannels.set(result) + } + return result + } + + @Throws(Exception::class) + override fun getPodcastEpisodes(podcastChannelId: String?): MusicDirectory? { + return musicService.getPodcastEpisodes(podcastChannelId) + } + + @Throws(Exception::class) + override fun getPlaylists(refresh: Boolean): List { + checkSettingsChanged() + var result = if (refresh) null else cachedPlaylists.get() + if (result == null) { + result = musicService.getPlaylists(refresh) + cachedPlaylists.set(result) + } + return result + } + + @Throws(Exception::class) + override fun createPlaylist(id: String, name: String, entries: List) { + cachedPlaylists.clear() + musicService.createPlaylist(id, name, entries) + } + + @Throws(Exception::class) + override fun deletePlaylist(id: String) { + musicService.deletePlaylist(id) + } + + @Throws(Exception::class) + override fun updatePlaylist(id: String, name: String?, comment: String?, pub: Boolean) { + musicService.updatePlaylist(id, name, comment, pub) + } + + @Throws(Exception::class) + override fun getLyrics(artist: String, title: String): Lyrics? { + return musicService.getLyrics(artist, title) + } + + @Throws(Exception::class) + override fun scrobble(id: String, submission: Boolean) { + musicService.scrobble(id, submission) + } + + @Throws(Exception::class) + override fun getAlbumList( + type: String, + size: Int, + offset: Int, + musicFolderId: String? + ): MusicDirectory { + return musicService.getAlbumList(type, size, offset, musicFolderId) + } + + @Throws(Exception::class) + override fun getAlbumList2( + type: String, + size: Int, + offset: Int, + musicFolderId: String? + ): MusicDirectory { + return musicService.getAlbumList2(type, size, offset, musicFolderId) + } + + @Throws(Exception::class) + override fun getRandomSongs(size: Int): MusicDirectory { + return musicService.getRandomSongs(size) + } + + @Throws(Exception::class) + override fun getStarred(): SearchResult = musicService.getStarred() + + @Throws(Exception::class) + override fun getStarred2(): SearchResult = musicService.getStarred2() + + @Throws(Exception::class) + override fun getCoverArt( + entry: MusicDirectory.Entry?, + size: Int, + saveToFile: Boolean, + highQuality: Boolean + ): Bitmap? { + return musicService.getCoverArt(entry, size, saveToFile, highQuality) + } + + @Throws(Exception::class) + override fun getDownloadInputStream( + song: MusicDirectory.Entry, + offset: Long, + maxBitrate: Int + ): Pair { + return musicService.getDownloadInputStream(song, offset, maxBitrate) + } + + @Throws(Exception::class) + override fun getVideoUrl(id: String, useFlash: Boolean): String? { + return musicService.getVideoUrl(id, useFlash) + } + + @Throws(Exception::class) + override fun updateJukeboxPlaylist(ids: List?): JukeboxStatus { + return musicService.updateJukeboxPlaylist(ids) + } + + @Throws(Exception::class) + override fun skipJukebox(index: Int, offsetSeconds: Int): JukeboxStatus { + return musicService.skipJukebox(index, offsetSeconds) + } + + @Throws(Exception::class) + override fun stopJukebox(): JukeboxStatus { + return musicService.stopJukebox() + } + + @Throws(Exception::class) + override fun startJukebox(): JukeboxStatus { + return musicService.startJukebox() + } + + @Throws(Exception::class) + override fun getJukeboxStatus(): JukeboxStatus = musicService.getJukeboxStatus() + + @Throws(Exception::class) + override fun setJukeboxGain(gain: Float): JukeboxStatus { + return musicService.setJukeboxGain(gain) + } + + private fun checkSettingsChanged() { + val newUrl = activeServerProvider.value.getRestUrl(null) + val newFolderId = activeServerProvider.value.getActiveServer().musicFolderId + if (!Util.equals(newUrl, restUrl) || !Util.equals(cachedMusicFolderId, newFolderId)) { + cachedMusicFolders.clear() + cachedMusicDirectories.clear() + cachedLicenseValid.clear() + cachedIndexes.clear() + cachedPlaylists.clear() + cachedGenres.clear() + cachedAlbum.clear() + cachedArtist.clear() + cachedUserInfo.clear() + restUrl = newUrl + cachedMusicFolderId = newFolderId + } + } + + @Throws(Exception::class) + override fun star(id: String?, albumId: String?, artistId: String?) { + musicService.star(id, albumId, artistId) + } + + @Throws(Exception::class) + override fun unstar(id: String?, albumId: String?, artistId: String?) { + musicService.unstar(id, albumId, artistId) + } + + @Throws(Exception::class) + override fun setRating(id: String, rating: Int) { + musicService.setRating(id, rating) + } + + @Throws(Exception::class) + override fun getGenres(refresh: Boolean): List? { + checkSettingsChanged() + if (refresh) { + cachedGenres.clear() + } + var result = cachedGenres.get() + if (result == null) { + result = musicService.getGenres(refresh) + cachedGenres.set(result) + } + + val sorted = result?.toMutableList() + sorted?.sortWith { genre, genre2 -> + genre.name.compareTo( + genre2.name, + ignoreCase = true + ) + } + return sorted + } + + @Throws(Exception::class) + override fun getSongsByGenre(genre: String, count: Int, offset: Int): MusicDirectory { + return musicService.getSongsByGenre(genre, count, offset) + } + + @Throws(Exception::class) + override fun getShares(refresh: Boolean): List { + return musicService.getShares(refresh) + } + + @Throws(Exception::class) + override fun getChatMessages(since: Long?): List? { + return musicService.getChatMessages(since) + } + + @Throws(Exception::class) + override fun addChatMessage(message: String) { + musicService.addChatMessage(message) + } + + @Throws(Exception::class) + override fun getBookmarks(): List? = musicService.getBookmarks() + + @Throws(Exception::class) + override fun deleteBookmark(id: String) { + musicService.deleteBookmark(id) + } + + @Throws(Exception::class) + override fun createBookmark(id: String, position: Int) { + musicService.createBookmark(id, position) + } + + @Throws(Exception::class) + override fun getVideos(refresh: Boolean): MusicDirectory? { + checkSettingsChanged() + var cache = + if (refresh) null else cachedMusicDirectories[Constants.INTENT_EXTRA_NAME_VIDEOS] + var dir = cache?.get() + if (dir == null) { + dir = musicService.getVideos(refresh) + cache = TimeLimitedCache( + Util.getDirectoryCacheTime().toLong(), TimeUnit.SECONDS + ) + cache.set(dir) + cachedMusicDirectories.put(Constants.INTENT_EXTRA_NAME_VIDEOS, cache) + } + return dir + } + + @Throws(Exception::class) + override fun getUser(username: String): UserInfo { + checkSettingsChanged() + var cache = cachedUserInfo[username] + var userInfo = cache?.get() + if (userInfo == null) { + userInfo = musicService.getUser(username) + cache = TimeLimitedCache( + Util.getDirectoryCacheTime().toLong(), TimeUnit.SECONDS + ) + cache.set(userInfo) + cachedUserInfo.put(username, cache) + } + return userInfo + } + + @Throws(Exception::class) + override fun createShare( + ids: List, + description: String?, + expires: Long? + ): List { + return musicService.createShare(ids, description, expires) + } + + @Throws(Exception::class) + override fun deleteShare(id: String) { + musicService.deleteShare(id) + } + + @Throws(Exception::class) + override fun updateShare(id: String, description: String?, expires: Long?) { + musicService.updateShare(id, description, expires) + } + + @Throws(Exception::class) + override fun getAvatar( + username: String?, + size: Int, + saveToFile: Boolean, + highQuality: Boolean + ): Bitmap? { + return musicService.getAvatar(username, size, saveToFile, highQuality) + } + + companion object { + private const val MUSIC_DIR_CACHE_SIZE = 100 + } + + init { + cachedMusicDirectories = LRUCache(MUSIC_DIR_CACHE_SIZE) + cachedArtist = LRUCache(MUSIC_DIR_CACHE_SIZE) + cachedAlbum = LRUCache(MUSIC_DIR_CACHE_SIZE) + cachedUserInfo = LRUCache(MUSIC_DIR_CACHE_SIZE) + } +} \ No newline at end of file diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicService.kt b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicService.kt index 09aee4a5..4705d78a 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicService.kt +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicService.kt @@ -1,151 +1,192 @@ /* - 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 + * MusicService.kt + * Copyright (C) 2009-2021 Ultrasonic developers + * + * Distributed under terms of the GNU GPLv3 license. */ -package org.moire.ultrasonic.service; +package org.moire.ultrasonic.service -import android.graphics.Bitmap; +import android.graphics.Bitmap +import org.moire.ultrasonic.domain.Bookmark +import org.moire.ultrasonic.domain.ChatMessage +import org.moire.ultrasonic.domain.Genre +import org.moire.ultrasonic.domain.Indexes +import org.moire.ultrasonic.domain.JukeboxStatus +import org.moire.ultrasonic.domain.Lyrics +import org.moire.ultrasonic.domain.MusicDirectory +import org.moire.ultrasonic.domain.MusicFolder +import org.moire.ultrasonic.domain.Playlist +import org.moire.ultrasonic.domain.PodcastsChannel +import org.moire.ultrasonic.domain.SearchCriteria +import org.moire.ultrasonic.domain.SearchResult +import org.moire.ultrasonic.domain.Share +import org.moire.ultrasonic.domain.UserInfo +import java.io.InputStream -import org.moire.ultrasonic.domain.Bookmark; -import org.moire.ultrasonic.domain.ChatMessage; -import org.moire.ultrasonic.domain.Genre; -import org.moire.ultrasonic.domain.Indexes; -import org.moire.ultrasonic.domain.JukeboxStatus; -import org.moire.ultrasonic.domain.Lyrics; -import org.moire.ultrasonic.domain.MusicDirectory; -import org.moire.ultrasonic.domain.MusicFolder; -import org.moire.ultrasonic.domain.Playlist; -import org.moire.ultrasonic.domain.PodcastsChannel; -import org.moire.ultrasonic.domain.SearchCriteria; -import org.moire.ultrasonic.domain.SearchResult; -import org.moire.ultrasonic.domain.Share; -import org.moire.ultrasonic.domain.UserInfo; +interface MusicService { + @Throws(Exception::class) + fun ping() -import java.io.InputStream; -import java.util.List; + @Throws(Exception::class) + fun isLicenseValid(): Boolean -import kotlin.Pair; + @Throws(Exception::class) + fun getGenres(refresh: Boolean): List? -/** - * @author Sindre Mehus - */ -public interface MusicService -{ + @Throws(Exception::class) + fun star(id: String?, albumId: String?, artistId: String?) - void ping() throws Exception; + @Throws(Exception::class) + fun unstar(id: String?, albumId: String?, artistId: String?) - boolean isLicenseValid() throws Exception; + @Throws(Exception::class) + fun setRating(id: String, rating: Int) - List getGenres(boolean refresh) throws Exception; + @Throws(Exception::class) + fun getMusicFolders(refresh: Boolean): List - void star(String id, String albumId, String artistId) throws Exception; + @Throws(Exception::class) + fun getIndexes(musicFolderId: String?, refresh: Boolean): Indexes - void unstar(String id, String albumId, String artistId) throws Exception; + @Throws(Exception::class) + fun getArtists(refresh: Boolean): Indexes - void setRating(String id, int rating) throws Exception; + @Throws(Exception::class) + fun getMusicDirectory(id: String, name: String?, refresh: Boolean): MusicDirectory - List getMusicFolders(boolean refresh) throws Exception; + @Throws(Exception::class) + fun getArtist(id: String, name: String?, refresh: Boolean): MusicDirectory - Indexes getIndexes(String musicFolderId, boolean refresh) throws Exception; + @Throws(Exception::class) + fun getAlbum(id: String, name: String?, refresh: Boolean): MusicDirectory - Indexes getArtists(boolean refresh) throws Exception; + @Throws(Exception::class) + fun search(criteria: SearchCriteria): SearchResult? - MusicDirectory getMusicDirectory(String id, String name, boolean refresh) throws Exception; + @Throws(Exception::class) + fun getPlaylist(id: String, name: String): MusicDirectory - MusicDirectory getArtist(String id, String name, boolean refresh) throws Exception; + @Throws(Exception::class) + fun getPodcastsChannels(refresh: Boolean): List - MusicDirectory getAlbum(String id, String name, boolean refresh) throws Exception; + @Throws(Exception::class) + fun getPlaylists(refresh: Boolean): List - SearchResult search(SearchCriteria criteria) throws Exception; + @Throws(Exception::class) + fun createPlaylist(id: String, name: String, entries: List) - MusicDirectory getPlaylist(String id, String name) throws Exception; + @Throws(Exception::class) + fun deletePlaylist(id: String) - List getPodcastsChannels(boolean refresh) throws Exception; + @Throws(Exception::class) + fun updatePlaylist(id: String, name: String?, comment: String?, pub: Boolean) - List getPlaylists(boolean refresh) throws Exception; + @Throws(Exception::class) + fun getLyrics(artist: String, title: String): Lyrics? - void createPlaylist(String id, String name, List entries) throws Exception; + @Throws(Exception::class) + fun scrobble(id: String, submission: Boolean) - void deletePlaylist(String id) throws Exception; + @Throws(Exception::class) + fun getAlbumList(type: String, size: Int, offset: Int, musicFolderId: String?): MusicDirectory - void updatePlaylist(String id, String name, String comment, boolean pub) throws Exception; + @Throws(Exception::class) + fun getAlbumList2( + type: String, + size: Int, + offset: Int, + musicFolderId: String? + ): MusicDirectory - Lyrics getLyrics(String artist, String title) throws Exception; + @Throws(Exception::class) + fun getRandomSongs(size: Int): MusicDirectory - void scrobble(String id, boolean submission) throws Exception; + @Throws(Exception::class) + fun getSongsByGenre(genre: String, count: Int, offset: Int): MusicDirectory - MusicDirectory getAlbumList(String type, int size, int offset, String musicFolderId) throws Exception; + @Throws(Exception::class) + fun getStarred(): SearchResult - MusicDirectory getAlbumList2(String type, int size, int offset, String musicFolderId) throws Exception; + @Throws(Exception::class) + fun getStarred2(): SearchResult - MusicDirectory getRandomSongs(int size) throws Exception; + @Throws(Exception::class) + fun getCoverArt( + entry: MusicDirectory.Entry?, + size: Int, + saveToFile: Boolean, + highQuality: Boolean + ): Bitmap? - MusicDirectory getSongsByGenre(String genre, int count, int offset) throws Exception; + @Throws(Exception::class) + fun getAvatar(username: String?, size: Int, saveToFile: Boolean, highQuality: Boolean): Bitmap? - SearchResult getStarred() throws Exception; + /** + * Return response [InputStream] and a [Boolean] that indicates if this response is + * partial. + */ + @Throws(Exception::class) + fun getDownloadInputStream( + song: MusicDirectory.Entry, + offset: Long, + maxBitrate: Int + ): Pair - SearchResult getStarred2() throws Exception; + // TODO: Refactor and remove this call (see RestMusicService implementation) + @Throws(Exception::class) + fun getVideoUrl(id: String, useFlash: Boolean): String? - Bitmap getCoverArt(MusicDirectory.Entry entry, int size, boolean saveToFile, boolean highQuality) throws Exception; + @Throws(Exception::class) + fun updateJukeboxPlaylist(ids: List?): JukeboxStatus - Bitmap getAvatar(String username, int size, boolean saveToFile, boolean highQuality) throws Exception; + @Throws(Exception::class) + fun skipJukebox(index: Int, offsetSeconds: Int): JukeboxStatus - /** - * Return response {@link InputStream} and a {@link Boolean} that indicates if this response is - * partial. - */ - Pair getDownloadInputStream(MusicDirectory.Entry song, long offset, int maxBitrate) throws Exception; + @Throws(Exception::class) + fun stopJukebox(): JukeboxStatus - // TODO: Refactor and remove this call (see RestMusicService implementation) - String getVideoUrl(String id, boolean useFlash) throws Exception; + @Throws(Exception::class) + fun startJukebox(): JukeboxStatus - JukeboxStatus updateJukeboxPlaylist(List ids) throws Exception; + @Throws(Exception::class) + fun getJukeboxStatus(): JukeboxStatus - JukeboxStatus skipJukebox(int index, int offsetSeconds) throws Exception; + @Throws(Exception::class) + fun setJukeboxGain(gain: Float): JukeboxStatus - JukeboxStatus stopJukebox() throws Exception; + @Throws(Exception::class) + fun getShares(refresh: Boolean): List - JukeboxStatus startJukebox() throws Exception; + @Throws(Exception::class) + fun getChatMessages(since: Long?): List? - JukeboxStatus getJukeboxStatus() throws Exception; + @Throws(Exception::class) + fun addChatMessage(message: String) - JukeboxStatus setJukeboxGain(float gain) throws Exception; + @Throws(Exception::class) + fun getBookmarks(): List? - List getShares(boolean refresh) throws Exception; + @Throws(Exception::class) + fun deleteBookmark(id: String) - List getChatMessages(Long since) throws Exception; + @Throws(Exception::class) + fun createBookmark(id: String, position: Int) - void addChatMessage(String message) throws Exception; + @Throws(Exception::class) + fun getVideos(refresh: Boolean): MusicDirectory? - List getBookmarks() throws Exception; + @Throws(Exception::class) + fun getUser(username: String): UserInfo - void deleteBookmark(String id) throws Exception; + @Throws(Exception::class) + fun createShare(ids: List, description: String?, expires: Long?): List - void createBookmark(String id, int position) throws Exception; + @Throws(Exception::class) + fun deleteShare(id: String) - MusicDirectory getVideos(boolean refresh) throws Exception; + @Throws(Exception::class) + fun updateShare(id: String, description: String?, expires: Long?) - UserInfo getUser(String username) throws Exception; - - List createShare(List ids, String description, Long expires) throws Exception; - - void deleteShare(String id) throws Exception; - - void updateShare(String id, String description, Long expires) throws Exception; - - MusicDirectory getPodcastEpisodes(String podcastChannelId) throws Exception; + @Throws(Exception::class) + fun getPodcastEpisodes(podcastChannelId: String?): MusicDirectory? } \ No newline at end of file diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineException.kt b/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineException.kt index 845ba693..5833e133 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineException.kt +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineException.kt @@ -1,35 +1,16 @@ /* - 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 + * OfflineException.kt + * Copyright (C) 2009-2021 Ultrasonic developers + * + * Distributed under terms of the GNU GPLv3 license. */ -package org.moire.ultrasonic.service; +package org.moire.ultrasonic.service /** * Thrown by service methods that are not available in offline mode. - * - * @author Sindre Mehus - * @version $Id$ */ -public class OfflineException extends Exception -{ - private static final long serialVersionUID = -4479642294747429444L; - - public OfflineException(String message) - { - super(message); - } -} +class OfflineException(message: String?) : Exception(message) { + companion object { + private const val serialVersionUID = -4479642294747429444L + } +} \ No newline at end of file diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.kt b/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.kt index d5c1fac1..6b696964 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.kt +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.kt @@ -1,877 +1,719 @@ /* - 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 + * OfflineMusicService.kt + * Copyright (C) 2009-2021 Ultrasonic developers + * + * Distributed under terms of the GNU GPLv3 license. */ -package org.moire.ultrasonic.service; - -import android.graphics.Bitmap; -import android.media.MediaMetadataRetriever; - -import org.moire.ultrasonic.data.ActiveServerProvider; -import org.moire.ultrasonic.domain.Artist; -import org.moire.ultrasonic.domain.Bookmark; -import org.moire.ultrasonic.domain.ChatMessage; -import org.moire.ultrasonic.domain.Genre; -import org.moire.ultrasonic.domain.Indexes; -import org.moire.ultrasonic.domain.JukeboxStatus; -import org.moire.ultrasonic.domain.Lyrics; -import org.moire.ultrasonic.domain.MusicDirectory; -import org.moire.ultrasonic.domain.MusicFolder; -import org.moire.ultrasonic.domain.Playlist; -import org.moire.ultrasonic.domain.PodcastsChannel; -import org.moire.ultrasonic.domain.SearchCriteria; -import org.moire.ultrasonic.domain.SearchResult; -import org.moire.ultrasonic.domain.Share; -import org.moire.ultrasonic.domain.UserInfo; -import org.moire.ultrasonic.util.Constants; -import org.moire.ultrasonic.util.FileUtil; -import org.moire.ultrasonic.util.Util; - -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.InputStream; -import java.io.Reader; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Random; -import java.util.SortedSet; -import java.util.concurrent.TimeUnit; -import java.util.regex.Pattern; - -import kotlin.Lazy; -import kotlin.Pair; -import timber.log.Timber; - -import static org.koin.java.KoinJavaComponent.inject; - -/** - * @author Sindre Mehus - */ -public class OfflineMusicService implements MusicService -{ - private static final Pattern COMPILE = Pattern.compile(" "); - private final Lazy activeServerProvider = inject(ActiveServerProvider.class); - - @Override - public Indexes getIndexes(String musicFolderId, boolean refresh) - { - List artists = new ArrayList<>(); - File root = FileUtil.getMusicDirectory(); - for (File file : FileUtil.listFiles(root)) - { - if (file.isDirectory()) - { - Artist artist = new Artist(); - artist.setId(file.getPath()); - artist.setIndex(file.getName().substring(0, 1)); - artist.setName(file.getName()); - artists.add(artist); - } - } - - String ignoredArticlesString = "The El La Los Las Le Les"; - final String[] ignoredArticles = COMPILE.split(ignoredArticlesString); - - Collections.sort(artists, (lhsArtist, rhsArtist) -> { - String lhs = lhsArtist.getName().toLowerCase(); - String rhs = rhsArtist.getName().toLowerCase(); - - char lhs1 = lhs.charAt(0); - char rhs1 = rhs.charAt(0); - - if (Character.isDigit(lhs1) && !Character.isDigit(rhs1)) - { - return 1; - } - - if (Character.isDigit(rhs1) && !Character.isDigit(lhs1)) - { - return -1; - } - - for (String article : ignoredArticles) - { - int index = lhs.indexOf(String.format("%s ", article.toLowerCase())); - - if (index == 0) - { - lhs = lhs.substring(article.length() + 1); - } - - index = rhs.indexOf(String.format("%s ", article.toLowerCase())); - - if (index == 0) - { - rhs = rhs.substring(article.length() + 1); - } - } - - return lhs.compareTo(rhs); - }); - - return new Indexes(0L, ignoredArticlesString, Collections.emptyList(), artists); - } - - @Override - public MusicDirectory getMusicDirectory(String id, String artistName, boolean refresh) - { - File dir = new File(id); - MusicDirectory result = new MusicDirectory(); - result.setName(dir.getName()); - - Collection names = new HashSet<>(); - - for (File file : FileUtil.listMediaFiles(dir)) - { - String name = getName(file); - if (name != null & !names.contains(name)) - { - names.add(name); - result.addChild(createEntry(file, name)); - } - } - - return result; - } - - private static String getName(File file) - { - String name = file.getName(); - - if (file.isDirectory()) - { - return name; - } - - if (name.endsWith(".partial") || name.contains(".partial.") || name.equals(Constants.ALBUM_ART_FILE)) - { - return null; - } - - name = name.replace(".complete", ""); - return FileUtil.getBaseName(name); - } - - private static MusicDirectory.Entry createEntry(File file, String name) - { - MusicDirectory.Entry entry = new MusicDirectory.Entry(); - entry.setDirectory(file.isDirectory()); - entry.setId(file.getPath()); - entry.setParent(file.getParent()); - entry.setSize(file.length()); - String root = FileUtil.getMusicDirectory().getPath(); - entry.setPath(file.getPath().replaceFirst(String.format("^%s/", root), "")); - entry.setTitle(name); - - if (file.isFile()) - { - String artist = null; - String album = null; - String title = null; - String track = null; - String disc = null; - String year = null; - String genre = null; - String duration = null; - String hasVideo = null; - - try - { - MediaMetadataRetriever mmr = new MediaMetadataRetriever(); - mmr.setDataSource(file.getPath()); - artist = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST); - album = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM); - title = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE); - track = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER); - disc = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER); - year = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_YEAR); - genre = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_GENRE); - duration = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); - hasVideo = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO); - mmr.release(); - } - catch (Exception ignored) - { - } - - entry.setArtist(artist != null ? artist : file.getParentFile().getParentFile().getName()); - entry.setAlbum(album != null ? album : file.getParentFile().getName()); - - if (title != null) - { - entry.setTitle(title); - } - - entry.setVideo(hasVideo != null); - - Timber.i("Offline Stuff: %s", track); - - if (track != null) - { - - int trackValue = 0; - - try - { - int slashIndex = track.indexOf('/'); - - if (slashIndex > 0) - { - track = track.substring(0, slashIndex); - } - - trackValue = Integer.parseInt(track); - } - catch (Exception ex) - { - Timber.e(ex,"Offline Stuff"); - } - - Timber.i("Offline Stuff: Setting Track: %d", trackValue); - - entry.setTrack(trackValue); - } - - if (disc != null) - { - int discValue = 0; - - try - { - int slashIndex = disc.indexOf('/'); - - if (slashIndex > 0) - { - disc = disc.substring(0, slashIndex); - } - - discValue = Integer.parseInt(disc); - } - catch (Exception ignored) - { - } - - entry.setDiscNumber(discValue); - } - - if (year != null) - { - int yearValue = 0; - - try - { - yearValue = Integer.parseInt(year); - } - catch (Exception ignored) - { - } - - entry.setYear(yearValue); - } - - if (genre != null) - { - entry.setGenre(genre); - } - - if (duration != null) - { - long durationValue = 0; - - try - { - durationValue = Long.parseLong(duration); - durationValue = TimeUnit.MILLISECONDS.toSeconds(durationValue); - } - catch (Exception ignored) - { - } - - entry.setDuration(durationValue); - } - } - - entry.setSuffix(FileUtil.getExtension(file.getName().replace(".complete", ""))); - - File albumArt = FileUtil.getAlbumArtFile(entry); - - if (albumArt.exists()) - { - entry.setCoverArt(albumArt.getPath()); - } - - return entry; - } - - @Override - public Bitmap getAvatar(String username, int size, boolean saveToFile, boolean highQuality) - { - try - { - Bitmap bitmap = FileUtil.getAvatarBitmap(username, size, highQuality); - return Util.scaleBitmap(bitmap, size); - } - catch (Exception e) - { - return null; - } - } - - @Override - public Bitmap getCoverArt(MusicDirectory.Entry entry, int size, boolean saveToFile, boolean highQuality) - { - try - { - Bitmap bitmap = FileUtil.getAlbumArtBitmap(entry, size, highQuality); - return Util.scaleBitmap(bitmap, size); - } - catch (Exception e) - { - return null; - } - } - - @Override - public SearchResult search(SearchCriteria criteria) - { - List artists = new ArrayList<>(); - List albums = new ArrayList<>(); - List songs = new ArrayList<>(); - File root = FileUtil.getMusicDirectory(); - int closeness; - - for (File artistFile : FileUtil.listFiles(root)) - { - String artistName = artistFile.getName(); - if (artistFile.isDirectory()) - { - if ((closeness = matchCriteria(criteria, artistName)) > 0) - { - Artist artist = new Artist(); - artist.setId(artistFile.getPath()); - artist.setIndex(artistFile.getName().substring(0, 1)); - artist.setName(artistName); - artist.setCloseness(closeness); - artists.add(artist); - } - - recursiveAlbumSearch(artistName, artistFile, criteria, albums, songs); - } - } - - Collections.sort(artists, (lhs, rhs) -> { - if (lhs.getCloseness() == rhs.getCloseness()) - { - return 0; - } - - else return lhs.getCloseness() > rhs.getCloseness() ? -1 : 1; - }); - - Collections.sort(albums, (lhs, rhs) -> { - if (lhs.getCloseness() == rhs.getCloseness()) - { - return 0; - } - - else return lhs.getCloseness() > rhs.getCloseness() ? -1 : 1; - }); - - Collections.sort(songs, (lhs, rhs) -> { - if (lhs.getCloseness() == rhs.getCloseness()) - { - return 0; - } - - else return lhs.getCloseness() > rhs.getCloseness() ? -1 : 1; - }); - - return new SearchResult(artists, albums, songs); - } - - private static void recursiveAlbumSearch(String artistName, File file, SearchCriteria criteria, List albums, List songs) - { - int closeness; - - for (File albumFile : FileUtil.listMediaFiles(file)) - { - if (albumFile.isDirectory()) - { - String albumName = getName(albumFile); - if ((closeness = matchCriteria(criteria, albumName)) > 0) - { - MusicDirectory.Entry album = createEntry(albumFile, albumName); - album.setArtist(artistName); - album.setCloseness(closeness); - albums.add(album); - } - - for (File songFile : FileUtil.listMediaFiles(albumFile)) - { - String songName = getName(songFile); - - if (songFile.isDirectory()) - { - recursiveAlbumSearch(artistName, songFile, criteria, albums, songs); - } - else if ((closeness = matchCriteria(criteria, songName)) > 0) - { - MusicDirectory.Entry song = createEntry(albumFile, songName); - song.setArtist(artistName); - song.setAlbum(albumName); - song.setCloseness(closeness); - songs.add(song); - } - } - } - else - { - String songName = getName(albumFile); - - if ((closeness = matchCriteria(criteria, songName)) > 0) - { - MusicDirectory.Entry song = createEntry(albumFile, songName); - song.setArtist(artistName); - song.setAlbum(songName); - song.setCloseness(closeness); - songs.add(song); - } - } - } - } - - private static int matchCriteria(SearchCriteria criteria, String name) - { - String query = criteria.getQuery().toLowerCase(); - String[] queryParts = COMPILE.split(query); - String[] nameParts = COMPILE.split(name.toLowerCase()); - - int closeness = 0; - - for (String queryPart : queryParts) - { - for (String namePart : nameParts) - { - if (namePart.equals(queryPart)) - { - closeness++; - } - } - } - - return closeness; - } - - @Override - public List getPlaylists(boolean refresh) - { - List playlists = new ArrayList<>(); - File root = FileUtil.getPlaylistDirectory(); - String lastServer = null; - boolean removeServer = true; - for (File folder : FileUtil.listFiles(root)) - { - if (folder.isDirectory()) - { - String server = folder.getName(); - SortedSet fileList = FileUtil.listFiles(folder); - for (File file : fileList) - { - if (FileUtil.isPlaylistFile(file)) - { - String id = file.getName(); - String filename = server + ": " + FileUtil.getBaseName(id); - Playlist playlist = new Playlist(server, filename); - playlists.add(playlist); - } - } - - if (!server.equals(lastServer) && !fileList.isEmpty()) - { - if (lastServer != null) - { - removeServer = false; - } - lastServer = server; - } - } - else - { - // Delete legacy playlist files - try - { - if (!folder.delete()) { - Timber.w("Failed to delete old playlist file: %s", folder.getName()); - } - } - catch (Exception e) - { - Timber.w(e, "Failed to delete old playlist file: %s", folder.getName()); - } - } - } - - if (removeServer) - { - for (Playlist playlist : playlists) - { - playlist.setName(playlist.getName().substring(playlist.getId().length() + 2)); - } - } - return playlists; - } - - @Override - public MusicDirectory getPlaylist(String id, String name) throws Exception - { - Reader reader = null; - BufferedReader buffer = null; - try - { - int firstIndex = name.indexOf(id); - - if (firstIndex != -1) - { - name = name.substring(id.length() + 2); - } - - File playlistFile = FileUtil.getPlaylistFile(id, name); - reader = new FileReader(playlistFile); - buffer = new BufferedReader(reader); - - MusicDirectory playlist = new MusicDirectory(); - String line = buffer.readLine(); - if (!"#EXTM3U".equals(line)) return playlist; - - while ((line = buffer.readLine()) != null) - { - File entryFile = new File(line); - String entryName = getName(entryFile); - - if (entryFile.exists() && entryName != null) - { - playlist.addChild(createEntry(entryFile, entryName)); - } - } - - return playlist; - } - finally - { - Util.close(buffer); - Util.close(reader); - } - } - - @Override - public void createPlaylist(String id, String name, List entries) throws Exception - { - File playlistFile = FileUtil.getPlaylistFile(activeServerProvider.getValue().getActiveServer().getName(), name); - FileWriter fw = new FileWriter(playlistFile); - BufferedWriter bw = new BufferedWriter(fw); - try - { - fw.write("#EXTM3U\n"); - for (MusicDirectory.Entry e : entries) - { - String filePath = FileUtil.getSongFile(e).getAbsolutePath(); - if (!new File(filePath).exists()) - { - String ext = FileUtil.getExtension(filePath); - String base = FileUtil.getBaseName(filePath); - filePath = base + ".complete." + ext; - } - fw.write(filePath + '\n'); - } - } - catch (Exception e) - { - Timber.w("Failed to save playlist: %s", name); - } - finally - { - bw.close(); - fw.close(); - } - } - - - @Override - public MusicDirectory getRandomSongs(int size) - { - File root = FileUtil.getMusicDirectory(); - List children = new LinkedList<>(); - listFilesRecursively(root, children); - MusicDirectory result = new MusicDirectory(); - - if (children.isEmpty()) - { - return result; - } - - Random random = new java.security.SecureRandom(); - for (int i = 0; i < size; i++) - { - File file = children.get(random.nextInt(children.size())); - result.addChild(createEntry(file, getName(file))); - } - - return result; - } - - private static void listFilesRecursively(File parent, List children) - { - for (File file : FileUtil.listMediaFiles(parent)) - { - if (file.isFile()) - { - children.add(file); - } - else - { - listFilesRecursively(file, children); - } - } - } - - @Override - public void deletePlaylist(String id) throws Exception - { - throw new OfflineException("Playlists not available in offline mode"); - } - - @Override - public void updatePlaylist(String id, String name, String comment, boolean pub) throws Exception - { - throw new OfflineException("Updating playlist not available in offline mode"); - } - - @Override - public Lyrics getLyrics(String artist, String title) throws Exception - { - throw new OfflineException("Lyrics not available in offline mode"); - } - - @Override - public void scrobble(String id, boolean submission) throws Exception - { - throw new OfflineException("Scrobbling not available in offline mode"); - } - - @Override - public MusicDirectory getAlbumList(String type, int size, int offset, String musicFolderId) throws Exception - { - throw new OfflineException("Album lists not available in offline mode"); - } - - @Override - public JukeboxStatus updateJukeboxPlaylist(List ids) throws Exception - { - throw new OfflineException("Jukebox not available in offline mode"); - } - - @Override - public JukeboxStatus skipJukebox(int index, int offsetSeconds) throws Exception - { - throw new OfflineException("Jukebox not available in offline mode"); - } - - @Override - public JukeboxStatus stopJukebox() throws Exception - { - throw new OfflineException("Jukebox not available in offline mode"); - } - - @Override - public JukeboxStatus startJukebox() throws Exception - { - throw new OfflineException("Jukebox not available in offline mode"); - } - - @Override - public JukeboxStatus getJukeboxStatus() throws Exception - { - throw new OfflineException("Jukebox not available in offline mode"); - } - - @Override - public JukeboxStatus setJukeboxGain(float gain) throws Exception - { - throw new OfflineException("Jukebox not available in offline mode"); - } - - @Override - public SearchResult getStarred() throws Exception - { - throw new OfflineException("Starred not available in offline mode"); - } - - @Override - public MusicDirectory getSongsByGenre(String genre, int count, int offset) throws Exception - { - throw new OfflineException("Getting Songs By Genre not available in offline mode"); - } - - @Override - public List getGenres(boolean refresh) throws Exception - { - throw new OfflineException("Getting Genres not available in offline mode"); - } - - @Override - public UserInfo getUser(String username) throws Exception - { - throw new OfflineException("Getting user info not available in offline mode"); - } - - @Override - public List createShare(List ids, String description, Long expires) throws Exception - { - throw new OfflineException("Creating shares not available in offline mode"); - } - - @Override - public List getShares(boolean refresh) throws Exception - { - throw new OfflineException("Getting shares not available in offline mode"); - } - - @Override - public void deleteShare(String id) throws Exception - { - throw new OfflineException("Deleting shares not available in offline mode"); - } - - @Override - public void updateShare(String id, String description, Long expires) throws Exception - { - throw new OfflineException("Updating shares not available in offline mode"); - } - - @Override - public void star(String id, String albumId, String artistId) throws Exception - { - throw new OfflineException("Star not available in offline mode"); - } - - @Override - public void unstar(String id, String albumId, String artistId) throws Exception - { - throw new OfflineException("UnStar not available in offline mode"); - } - @Override - public List getMusicFolders(boolean refresh) throws Exception - { - throw new OfflineException("Music folders not available in offline mode"); - } - - @Override - public MusicDirectory getAlbumList2(String type, int size, int offset, String musicFolderId) throws OfflineException { - throw new OfflineException("OfflineMusicService.getAlbumList2 was called but it isn't available"); - } - - @Override - public String getVideoUrl(String id, boolean useFlash) throws OfflineException { - throw new OfflineException("OfflineMusicService.getVideoUrl was called but it isn't available"); - } - - @Override - public List getChatMessages(Long since) throws OfflineException { - throw new OfflineException("OfflineMusicService.getChatMessages was called but it isn't available"); - } - - @Override - public void addChatMessage(String message) throws OfflineException { - throw new OfflineException("OfflineMusicService.addChatMessage was called but it isn't available"); - } - - @Override - public List getBookmarks() throws OfflineException { - throw new OfflineException("OfflineMusicService.getBookmarks was called but it isn't available"); - } - - @Override - public void deleteBookmark(String id) throws OfflineException { - throw new OfflineException("OfflineMusicService.deleteBookmark was called but it isn't available"); - } - - @Override - public void createBookmark(String id, int position) throws OfflineException { - throw new OfflineException("OfflineMusicService.createBookmark was called but it isn't available"); - } - - @Override - public MusicDirectory getVideos(boolean refresh) throws OfflineException { - throw new OfflineException("OfflineMusicService.getVideos was called but it isn't available"); - } - - @Override - public SearchResult getStarred2() throws OfflineException { - throw new OfflineException("OfflineMusicService.getStarred2 was called but it isn't available"); - } - - @Override - public void ping() { - } - - @Override - public boolean isLicenseValid() { - return true; - } - - @Override - public Indexes getArtists(boolean refresh) throws OfflineException { - throw new OfflineException("OfflineMusicService.getArtists was called but it isn't available"); - } - - @Override - public MusicDirectory getArtist(String id, String name, boolean refresh) throws OfflineException { - throw new OfflineException("OfflineMusicService.getArtist was called but it isn't available"); - } - - @Override - public MusicDirectory getAlbum(String id, String name, boolean refresh) throws OfflineException { - throw new OfflineException("OfflineMusicService.getAlbum was called but it isn't available"); - } - - @Override - public MusicDirectory getPodcastEpisodes(String podcastChannelId) throws OfflineException { - throw new OfflineException("OfflineMusicService.getPodcastEpisodes was called but it isn't available"); - } - - @Override - public Pair getDownloadInputStream(MusicDirectory.Entry song, long offset, int maxBitrate) throws OfflineException { - throw new OfflineException("OfflineMusicService.getDownloadInputStream was called but it isn't available"); - } - - @Override - public void setRating(String id, int rating) throws OfflineException { - throw new OfflineException("OfflineMusicService.setRating was called but it isn't available"); - } - - @Override - public List getPodcastsChannels(boolean refresh) throws OfflineException { - throw new OfflineException("OfflineMusicService.getPodcastsChannels was called but it isn't available"); - } -} +package org.moire.ultrasonic.service + +import android.graphics.Bitmap +import android.media.MediaMetadataRetriever +import org.koin.java.KoinJavaComponent.inject +import org.moire.ultrasonic.data.ActiveServerProvider +import org.moire.ultrasonic.domain.Artist +import org.moire.ultrasonic.domain.Bookmark +import org.moire.ultrasonic.domain.ChatMessage +import org.moire.ultrasonic.domain.Genre +import org.moire.ultrasonic.domain.Indexes +import org.moire.ultrasonic.domain.JukeboxStatus +import org.moire.ultrasonic.domain.Lyrics +import org.moire.ultrasonic.domain.MusicDirectory +import org.moire.ultrasonic.domain.MusicFolder +import org.moire.ultrasonic.domain.Playlist +import org.moire.ultrasonic.domain.PodcastsChannel +import org.moire.ultrasonic.domain.SearchCriteria +import org.moire.ultrasonic.domain.SearchResult +import org.moire.ultrasonic.domain.Share +import org.moire.ultrasonic.domain.UserInfo +import org.moire.ultrasonic.util.Constants +import org.moire.ultrasonic.util.FileUtil +import org.moire.ultrasonic.util.Util +import timber.log.Timber +import java.io.BufferedReader +import java.io.BufferedWriter +import java.io.File +import java.io.FileReader +import java.io.FileWriter +import java.io.InputStream +import java.io.Reader +import java.security.SecureRandom +import java.util.ArrayList +import java.util.HashSet +import java.util.LinkedList +import java.util.Locale +import java.util.Random +import java.util.concurrent.TimeUnit +import java.util.regex.Pattern + +class OfflineMusicService : MusicService { + private val activeServerProvider = inject( + ActiveServerProvider::class.java + ) + + override fun getIndexes(musicFolderId: String?, refresh: Boolean): Indexes { + val artists: MutableList = ArrayList() + val root = FileUtil.getMusicDirectory() + for (file in FileUtil.listFiles(root)) { + if (file.isDirectory) { + val artist = Artist() + artist.id = file.path + artist.index = file.name.substring(0, 1) + artist.name = file.name + artists.add(artist) + } + } + val ignoredArticlesString = "The El La Los Las Le Les" + val ignoredArticles = COMPILE.split(ignoredArticlesString) + artists.sortWith { lhsArtist, rhsArtist -> + var lhs = lhsArtist.name!!.toLowerCase(Locale.ROOT) + var rhs = rhsArtist.name!!.toLowerCase(Locale.ROOT) + val lhs1 = lhs[0] + val rhs1 = rhs[0] + if (Character.isDigit(lhs1) && !Character.isDigit(rhs1)) { + return@sortWith 1 + } + if (Character.isDigit(rhs1) && !Character.isDigit(lhs1)) { + return@sortWith -1 + } + for (article in ignoredArticles) { + var index = lhs.indexOf(String.format("%s ", article.toLowerCase(Locale.ROOT))) + if (index == 0) { + lhs = lhs.substring(article.length + 1) + } + index = rhs.indexOf(String.format("%s ", article.toLowerCase(Locale.ROOT))) + if (index == 0) { + rhs = rhs.substring(article.length + 1) + } + } + lhs.compareTo(rhs) + } + + return Indexes(0L, ignoredArticlesString, artists = artists) + } + + override fun getMusicDirectory( + id: String, + name: String?, + refresh: Boolean + ): MusicDirectory { + val dir = File(id) + val result = MusicDirectory() + result.name = dir.name + + val seen: MutableCollection = HashSet() + + for (file in FileUtil.listMediaFiles(dir)) { + val filename = getName(file) + if (filename != null && !seen.contains(filename)) { + seen.add(filename) + result.addChild(createEntry(file, filename)) + } + } + + return result + } + + override fun getAvatar( + username: String?, + size: Int, + saveToFile: Boolean, + highQuality: Boolean + ): Bitmap? { + return try { + val bitmap = FileUtil.getAvatarBitmap(username, size, highQuality) + Util.scaleBitmap(bitmap, size) + } catch (e: Exception) { + null + } + } + + override fun getCoverArt( + entry: MusicDirectory.Entry?, + size: Int, + saveToFile: Boolean, + highQuality: Boolean + ): Bitmap? { + return try { + val bitmap = FileUtil.getAlbumArtBitmap(entry, size, highQuality) + Util.scaleBitmap(bitmap, size) + } catch (e: Exception) { + null + } + } + + override fun search(criteria: SearchCriteria): SearchResult { + val artists: MutableList = ArrayList() + val albums: MutableList = ArrayList() + val songs: MutableList = ArrayList() + val root = FileUtil.getMusicDirectory() + var closeness: Int + for (artistFile in FileUtil.listFiles(root)) { + val artistName = artistFile.name + if (artistFile.isDirectory) { + if (matchCriteria(criteria, artistName).also { closeness = it } > 0) { + val artist = Artist() + artist.id = artistFile.path + artist.index = artistFile.name.substring(0, 1) + artist.name = artistName + artist.closeness = closeness + artists.add(artist) + } + recursiveAlbumSearch(artistName, artistFile, criteria, albums, songs) + } + } + + artists.sortWith { lhs, rhs -> + when { + lhs.closeness == rhs.closeness -> { + return@sortWith 0 + } + lhs.closeness > rhs.closeness -> { + return@sortWith -1 + } + else -> { + return@sortWith 1 + } + } + } + + albums.sortWith { lhs, rhs -> + when { + lhs.closeness == rhs.closeness -> { + return@sortWith 0 + } + lhs.closeness > rhs.closeness -> { + return@sortWith -1 + } + else -> { + return@sortWith 1 + } + } + } + + songs.sortWith { lhs, rhs -> + when { + lhs.closeness == rhs.closeness -> { + return@sortWith 0 + } + lhs.closeness > rhs.closeness -> { + return@sortWith -1 + } + else -> { + return@sortWith 1 + } + } + } + + return SearchResult(artists, albums, songs) + } + + override fun getPlaylists(refresh: Boolean): List { + val playlists: MutableList = ArrayList() + val root = FileUtil.getPlaylistDirectory() + var lastServer: String? = null + var removeServer = true + for (folder in FileUtil.listFiles(root)) { + if (folder.isDirectory) { + val server = folder.name + val fileList = FileUtil.listFiles(folder) + for (file in fileList) { + if (FileUtil.isPlaylistFile(file)) { + val id = file.name + val filename = server + ": " + FileUtil.getBaseName(id) + val playlist = Playlist(server, filename) + playlists.add(playlist) + } + } + if (server != lastServer && !fileList.isEmpty()) { + if (lastServer != null) { + removeServer = false + } + lastServer = server + } + } else { + // Delete legacy playlist files + try { + if (!folder.delete()) { + Timber.w("Failed to delete old playlist file: %s", folder.name) + } + } catch (e: Exception) { + Timber.w(e, "Failed to delete old playlist file: %s", folder.name) + } + } + } + if (removeServer) { + for (playlist in playlists) { + playlist.name = playlist.name.substring(playlist.id.length + 2) + } + } + return playlists + } + + @Throws(Exception::class) + override fun getPlaylist(id: String, name: String): MusicDirectory { + var playlistName = name + var reader: Reader? = null + var buffer: BufferedReader? = null + + return try { + val firstIndex = playlistName.indexOf(id) + if (firstIndex != -1) { + playlistName = playlistName.substring(id.length + 2) + } + val playlistFile = FileUtil.getPlaylistFile(id, playlistName) + reader = FileReader(playlistFile) + buffer = BufferedReader(reader) + val playlist = MusicDirectory() + var line = buffer.readLine() + if ("#EXTM3U" != line) return playlist + while (buffer.readLine().also { line = it } != null) { + val entryFile = File(line) + val entryName = getName(entryFile) + if (entryFile.exists() && entryName != null) { + playlist.addChild(createEntry(entryFile, entryName)) + } + } + playlist + } finally { + Util.close(buffer) + Util.close(reader) + } + } + + @Throws(Exception::class) + override fun createPlaylist(id: String, name: String, entries: List) { + val playlistFile = + FileUtil.getPlaylistFile(activeServerProvider.value.getActiveServer().name, name) + val fw = FileWriter(playlistFile) + val bw = BufferedWriter(fw) + try { + fw.write("#EXTM3U\n") + for (e in entries) { + var filePath = FileUtil.getSongFile(e).absolutePath + if (!File(filePath).exists()) { + val ext = FileUtil.getExtension(filePath) + val base = FileUtil.getBaseName(filePath) + filePath = "$base.complete.$ext" + } + fw.write( + """ + $filePath + + """.trimIndent() + ) + } + } catch (e: Exception) { + Timber.w("Failed to save playlist: %s", name) + } finally { + bw.close() + fw.close() + } + } + + override fun getRandomSongs(size: Int): MusicDirectory { + val root = FileUtil.getMusicDirectory() + val children: MutableList = LinkedList() + listFilesRecursively(root, children) + val result = MusicDirectory() + if (children.isEmpty()) { + return result + } + val random: Random = SecureRandom() + for (i in 0 until size) { + val file = children[random.nextInt(children.size)] + result.addChild(createEntry(file, getName(file))) + } + return result + } + + @Throws(Exception::class) + override fun deletePlaylist(id: String) { + throw OfflineException("Playlists not available in offline mode") + } + + @Throws(Exception::class) + override fun updatePlaylist(id: String, name: String?, comment: String?, pub: Boolean) { + throw OfflineException("Updating playlist not available in offline mode") + } + + @Throws(Exception::class) + override fun getLyrics(artist: String, title: String): Lyrics? { + throw OfflineException("Lyrics not available in offline mode") + } + + @Throws(Exception::class) + override fun scrobble(id: String, submission: Boolean) { + throw OfflineException("Scrobbling not available in offline mode") + } + + @Throws(Exception::class) + override fun getAlbumList( + type: String, + size: Int, + offset: Int, + musicFolderId: String? + ): MusicDirectory { + throw OfflineException("Album lists not available in offline mode") + } + + @Throws(Exception::class) + override fun updateJukeboxPlaylist(ids: List?): JukeboxStatus { + throw OfflineException("Jukebox not available in offline mode") + } + + @Throws(Exception::class) + override fun skipJukebox(index: Int, offsetSeconds: Int): JukeboxStatus { + throw OfflineException("Jukebox not available in offline mode") + } + + @Throws(Exception::class) + override fun stopJukebox(): JukeboxStatus { + throw OfflineException("Jukebox not available in offline mode") + } + + @Throws(Exception::class) + override fun startJukebox(): JukeboxStatus { + throw OfflineException("Jukebox not available in offline mode") + } + + @Throws(Exception::class) + override fun getJukeboxStatus(): JukeboxStatus { + throw OfflineException("Jukebox not available in offline mode") + } + + @Throws(Exception::class) + override fun setJukeboxGain(gain: Float): JukeboxStatus { + throw OfflineException("Jukebox not available in offline mode") + } + + @Throws(Exception::class) + override fun getStarred(): SearchResult { + throw OfflineException("Starred not available in offline mode") + } + + @Throws(Exception::class) + override fun getSongsByGenre(genre: String, count: Int, offset: Int): MusicDirectory { + throw OfflineException("Getting Songs By Genre not available in offline mode") + } + + @Throws(Exception::class) + override fun getGenres(refresh: Boolean): List? { + throw OfflineException("Getting Genres not available in offline mode") + } + + @Throws(Exception::class) + override fun getUser(username: String): UserInfo { + throw OfflineException("Getting user info not available in offline mode") + } + + @Throws(Exception::class) + override fun createShare( + ids: List, + description: String?, + expires: Long? + ): List { + throw OfflineException("Creating shares not available in offline mode") + } + + @Throws(Exception::class) + override fun getShares(refresh: Boolean): List { + throw OfflineException("Getting shares not available in offline mode") + } + + @Throws(Exception::class) + override fun deleteShare(id: String) { + throw OfflineException("Deleting shares not available in offline mode") + } + + @Throws(Exception::class) + override fun updateShare(id: String, description: String?, expires: Long?) { + throw OfflineException("Updating shares not available in offline mode") + } + + @Throws(Exception::class) + override fun star(id: String?, albumId: String?, artistId: String?) { + throw OfflineException("Star not available in offline mode") + } + + @Throws(Exception::class) + override fun unstar(id: String?, albumId: String?, artistId: String?) { + throw OfflineException("UnStar not available in offline mode") + } + + @Throws(Exception::class) + override fun getMusicFolders(refresh: Boolean): List { + throw OfflineException("Music folders not available in offline mode") + } + + @Throws(OfflineException::class) + override fun getAlbumList2( + type: String, + size: Int, + offset: Int, + musicFolderId: String? + ): MusicDirectory { + throw OfflineException("OfflineMusicService.getAlbumList2 was called but it isn't available") + } + + @Throws(OfflineException::class) + override fun getVideoUrl(id: String, useFlash: Boolean): String? { + throw OfflineException("OfflineMusicService.getVideoUrl was called but it isn't available") + } + + @Throws(OfflineException::class) + override fun getChatMessages(since: Long?): List? { + throw OfflineException("OfflineMusicService.getChatMessages was called but it isn't available") + } + + @Throws(OfflineException::class) + override fun addChatMessage(message: String) { + throw OfflineException("OfflineMusicService.addChatMessage was called but it isn't available") + } + + @Throws(OfflineException::class) + override fun getBookmarks(): List? { + throw OfflineException("OfflineMusicService.getBookmarks was called but it isn't available") + } + + @Throws(OfflineException::class) + override fun deleteBookmark(id: String) { + throw OfflineException("OfflineMusicService.deleteBookmark was called but it isn't available") + } + + @Throws(OfflineException::class) + override fun createBookmark(id: String, position: Int) { + throw OfflineException("OfflineMusicService.createBookmark was called but it isn't available") + } + + @Throws(OfflineException::class) + override fun getVideos(refresh: Boolean): MusicDirectory? { + throw OfflineException("OfflineMusicService.getVideos was called but it isn't available") + } + + @Throws(OfflineException::class) + override fun getStarred2(): SearchResult { + throw OfflineException("OfflineMusicService.getStarred2 was called but it isn't available") + } + + override fun ping() {} + + override fun isLicenseValid(): Boolean = true + + @Throws(OfflineException::class) + override fun getArtists(refresh: Boolean): Indexes { + throw OfflineException("OfflineMusicService.getArtists was called but it isn't available") + } + + @Throws(OfflineException::class) + override fun getArtist(id: String, name: String?, refresh: Boolean): MusicDirectory { + throw OfflineException("OfflineMusicService.getArtist was called but it isn't available") + } + + @Throws(OfflineException::class) + override fun getAlbum(id: String, name: String?, refresh: Boolean): MusicDirectory { + throw OfflineException("OfflineMusicService.getAlbum was called but it isn't available") + } + + @Throws(OfflineException::class) + override fun getPodcastEpisodes(podcastChannelId: String?): MusicDirectory? { + throw OfflineException("OfflineMusicService.getPodcastEpisodes was called but it isn't available") + } + + @Throws(OfflineException::class) + override fun getDownloadInputStream( + song: MusicDirectory.Entry, + offset: Long, + maxBitrate: Int + ): Pair { + throw OfflineException("OfflineMusicService.getDownloadInputStream was called but it isn't available") + } + + @Throws(OfflineException::class) + override fun setRating(id: String, rating: Int) { + throw OfflineException("OfflineMusicService.setRating was called but it isn't available") + } + + @Throws(OfflineException::class) + override fun getPodcastsChannels(refresh: Boolean): List { + throw OfflineException("OfflineMusicService.getPodcastsChannels was called but it isn't available") + } + + companion object { + private val COMPILE = Pattern.compile(" ") + private fun getName(file: File): String? { + var name = file.name + if (file.isDirectory) { + return name + } + if (name.endsWith(".partial") || name.contains(".partial.") || name == Constants.ALBUM_ART_FILE) { + return null + } + name = name.replace(".complete", "") + return FileUtil.getBaseName(name) + } + + private fun createEntry(file: File, name: String?): MusicDirectory.Entry { + val entry = MusicDirectory.Entry(file.path) + entry.isDirectory = file.isDirectory + entry.parent = file.parent + entry.size = file.length() + val root = FileUtil.getMusicDirectory().path + entry.path = file.path.replaceFirst(String.format("^%s/", root).toRegex(), "") + entry.title = name + if (file.isFile) { + var artist: String? = null + var album: String? = null + var title: String? = null + var track: String? = null + var disc: String? = null + var year: String? = null + var genre: String? = null + var duration: String? = null + var hasVideo: String? = null + try { + val mmr = MediaMetadataRetriever() + mmr.setDataSource(file.path) + artist = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST) + album = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM) + title = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE) + track = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER) + disc = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER) + year = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_YEAR) + genre = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_GENRE) + duration = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION) + hasVideo = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO) + mmr.release() + } catch (ignored: Exception) { + } + entry.artist = artist ?: file.parentFile!!.parentFile!!.name + entry.album = album ?: file.parentFile!!.name + if (title != null) { + entry.title = title + } + entry.isVideo = hasVideo != null + Timber.i("Offline Stuff: %s", track) + if (track != null) { + var trackValue = 0 + try { + val slashIndex = track.indexOf('/') + if (slashIndex > 0) { + track = track.substring(0, slashIndex) + } + trackValue = track.toInt() + } catch (ex: Exception) { + Timber.e(ex, "Offline Stuff") + } + Timber.i("Offline Stuff: Setting Track: %d", trackValue) + entry.track = trackValue + } + if (disc != null) { + var discValue = 0 + try { + val slashIndex = disc.indexOf('/') + if (slashIndex > 0) { + disc = disc.substring(0, slashIndex) + } + discValue = disc.toInt() + } catch (ignored: Exception) { + } + entry.discNumber = discValue + } + if (year != null) { + var yearValue = 0 + try { + yearValue = year.toInt() + } catch (ignored: Exception) { + } + entry.year = yearValue + } + if (genre != null) { + entry.genre = genre + } + if (duration != null) { + var durationValue: Long = 0 + try { + durationValue = duration.toLong() + durationValue = TimeUnit.MILLISECONDS.toSeconds(durationValue) + } catch (ignored: Exception) { + } + entry.setDuration(durationValue) + } + } + entry.suffix = FileUtil.getExtension(file.name.replace(".complete", "")) + val albumArt = FileUtil.getAlbumArtFile(entry) + if (albumArt.exists()) { + entry.coverArt = albumArt.path + } + return entry + } + + private fun recursiveAlbumSearch( + artistName: String, + file: File, + criteria: SearchCriteria, + albums: MutableList, + songs: MutableList + ) { + var closeness: Int + for (albumFile in FileUtil.listMediaFiles(file)) { + if (albumFile.isDirectory) { + val albumName = getName(albumFile) + if (matchCriteria(criteria, albumName).also { closeness = it } > 0) { + val album = createEntry(albumFile, albumName) + album.artist = artistName + album.closeness = closeness + albums.add(album) + } + for (songFile in FileUtil.listMediaFiles(albumFile)) { + val songName = getName(songFile) + if (songFile.isDirectory) { + recursiveAlbumSearch(artistName, songFile, criteria, albums, songs) + } else if (matchCriteria(criteria, songName).also { closeness = it } > 0) { + val song = createEntry(albumFile, songName) + song.artist = artistName + song.album = albumName + song.closeness = closeness + songs.add(song) + } + } + } else { + val songName = getName(albumFile) + if (matchCriteria(criteria, songName).also { closeness = it } > 0) { + val song = createEntry(albumFile, songName) + song.artist = artistName + song.album = songName + song.closeness = closeness + songs.add(song) + } + } + } + } + + private fun matchCriteria(criteria: SearchCriteria, name: String?): Int { + val query = criteria.query.toLowerCase(Locale.ROOT) + val queryParts = COMPILE.split(query) + val nameParts = COMPILE.split( + name!!.toLowerCase(Locale.ROOT) + ) + var closeness = 0 + for (queryPart in queryParts) { + for (namePart in nameParts) { + if (namePart == queryPart) { + closeness++ + } + } + } + return closeness + } + + private fun listFilesRecursively(parent: File, children: MutableList) { + for (file in FileUtil.listMediaFiles(parent)) { + if (file.isFile) { + children.add(file) + } else { + listFilesRecursively(file, children) + } + } + } + } +} \ No newline at end of file From 3257fb9153de9881a21fdb354c7aff24bd9f1f24 Mon Sep 17 00:00:00 2001 From: tzugen Date: Wed, 26 May 2021 23:18:51 +0200 Subject: [PATCH 04/15] Make Entry always have an id --- .../main/kotlin/org/moire/ultrasonic/domain/MusicDirectory.kt | 2 +- .../org/moire/ultrasonic/domain/APIMusicDirectoryConverter.kt | 3 +-- .../kotlin/org/moire/ultrasonic/fragment/AlbumRowAdapter.kt | 2 +- .../kotlin/org/moire/ultrasonic/fragment/ArtistRowAdapter.kt | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/MusicDirectory.kt b/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/MusicDirectory.kt index 7523dd12..0a5295dd 100644 --- a/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/MusicDirectory.kt +++ b/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/MusicDirectory.kt @@ -36,7 +36,7 @@ class MusicDirectory { } data class Entry( - override var id: String? = null, + override var id: String, var parent: String? = null, var isDirectory: Boolean = false, var title: String? = null, diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APIMusicDirectoryConverter.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APIMusicDirectoryConverter.kt index 72c6e00c..adff7ee5 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APIMusicDirectoryConverter.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/APIMusicDirectoryConverter.kt @@ -13,8 +13,7 @@ internal val dateFormat: DateFormat by lazy { SimpleDateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.getDefault()) } -fun MusicDirectoryChild.toDomainEntity(): MusicDirectory.Entry = MusicDirectory.Entry().apply { - id = this@toDomainEntity.id +fun MusicDirectoryChild.toDomainEntity(): MusicDirectory.Entry = MusicDirectory.Entry(id).apply { parent = this@toDomainEntity.parent isDirectory = this@toDomainEntity.isDir title = this@toDomainEntity.title diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/AlbumRowAdapter.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/AlbumRowAdapter.kt index 2a5f1c46..e3974614 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/AlbumRowAdapter.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/AlbumRowAdapter.kt @@ -57,7 +57,7 @@ class AlbumRowAdapter( imageLoader.loadImage( holder.coverArt, - MusicDirectory.Entry().apply { coverArt = holder.coverArtId }, + MusicDirectory.Entry("-1").apply { coverArt = holder.coverArtId }, false, 0, false, true, R.drawable.unknown_album ) } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ArtistRowAdapter.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ArtistRowAdapter.kt index dfd86ef9..30f76f4e 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ArtistRowAdapter.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ArtistRowAdapter.kt @@ -62,7 +62,7 @@ class ArtistRowAdapter( holder.coverArt.visibility = View.VISIBLE imageLoader.loadImage( holder.coverArt, - MusicDirectory.Entry().apply { coverArt = holder.coverArtId }, + MusicDirectory.Entry("-1").apply { coverArt = holder.coverArtId }, false, 0, false, true, R.drawable.ic_contact_picture ) } else { From b30584f99ce3f32e35ad7a1caff44f1d5f63d8aa Mon Sep 17 00:00:00 2001 From: tzugen Date: Wed, 26 May 2021 23:20:19 +0200 Subject: [PATCH 05/15] Make Entry always have an id --- .../org/moire/ultrasonic/fragment/TrackCollectionModel.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionModel.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionModel.kt index f0d5e4e2..2cfd9a7f 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionModel.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionModel.kt @@ -73,12 +73,11 @@ class TrackCollectionModel(application: Application) : GenericListModel(applicat musicDirectory.findChild(allSongsId) == null && hasOnlyFolders(musicDirectory) ) { - val allSongs = MusicDirectory.Entry() + val allSongs = MusicDirectory.Entry(allSongsId) allSongs.isDirectory = true allSongs.artist = name allSongs.parent = id - allSongs.id = allSongsId allSongs.title = String.format( context.resources.getString(R.string.select_album_all_songs), name ) @@ -135,12 +134,11 @@ class TrackCollectionModel(application: Application) : GenericListModel(applicat musicDirectory.findChild(allSongsId) == null && hasOnlyFolders(musicDirectory) ) { - val allSongs = MusicDirectory.Entry() + val allSongs = MusicDirectory.Entry(allSongsId) allSongs.isDirectory = true allSongs.artist = name allSongs.parent = id - allSongs.id = allSongsId allSongs.title = String.format( context.resources.getString(R.string.select_album_all_songs), name ) From e059d737bce1bc0cc04aa7c11e9165c7da544797 Mon Sep 17 00:00:00 2001 From: tzugen Date: Wed, 26 May 2021 23:21:56 +0200 Subject: [PATCH 06/15] Adopt changes from stricter nullabilities --- .../fragment/TrackCollectionFragment.kt | 8 ++-- .../fragment/TrackCollectionModel.kt | 14 +++--- .../service/MediaPlayerController.kt | 5 +-- .../ultrasonic/service/RESTMusicService.kt | 45 +++++++------------ .../ultrasonic/subsonic/DownloadHandler.kt | 2 +- .../moire/ultrasonic/subsonic/ShareHandler.kt | 4 +- 6 files changed, 30 insertions(+), 48 deletions(-) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt index b5ecc21f..49346615 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt @@ -258,7 +258,7 @@ class TrackCollectionFragment : Fragment() { model.getMusicFolders(refresh) if (playlistId != null) { - setTitle(playlistName) + setTitle(playlistName!!) model.getPlaylist(playlistId, playlistName) } else if (podcastChannelId != null) { setTitle(getString(R.string.podcasts_label)) @@ -282,12 +282,12 @@ class TrackCollectionFragment : Fragment() { setTitle(name) if (!isOffline() && Util.getShouldUseId3Tags()) { if (isAlbum) { - model.getAlbum(refresh, id, name, parentId) + model.getAlbum(refresh, id!!, name, parentId!!) } else { - model.getArtist(refresh, id, name) + model.getArtist(refresh, id!!, name) } } else { - model.getMusicDirectory(refresh, id, name, parentId) + model.getMusicDirectory(refresh, id!!, name, parentId!!) } } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionModel.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionModel.kt index 2cfd9a7f..957df5d9 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionModel.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionModel.kt @@ -43,9 +43,9 @@ class TrackCollectionModel(application: Application) : GenericListModel(applicat suspend fun getMusicDirectory( refresh: Boolean, - id: String?, + id: String, name: String?, - parentId: String? + parentId: String ) { withContext(Dispatchers.IO) { @@ -121,7 +121,7 @@ class TrackCollectionModel(application: Application) : GenericListModel(applicat * TODO: This method should be moved to AlbumListModel, * since it displays a list of albums by a specified artist. */ - suspend fun getArtist(refresh: Boolean, id: String?, name: String?) { + suspend fun getArtist(refresh: Boolean, id: String, name: String?) { withContext(Dispatchers.IO) { val service = MusicServiceFactory.getMusicService() @@ -152,7 +152,7 @@ class TrackCollectionModel(application: Application) : GenericListModel(applicat } } - suspend fun getAlbum(refresh: Boolean, id: String?, name: String?, parentId: String?) { + suspend fun getAlbum(refresh: Boolean, id: String, name: String?, parentId: String) { withContext(Dispatchers.IO) { @@ -210,9 +210,9 @@ class TrackCollectionModel(application: Application) : GenericListModel(applicat val musicDirectory: MusicDirectory if (Util.getShouldUseId3Tags()) { - musicDirectory = Util.getSongsFromSearchResult(service.starred2) + musicDirectory = Util.getSongsFromSearchResult(service.getStarred2()) } else { - musicDirectory = Util.getSongsFromSearchResult(service.starred) + musicDirectory = Util.getSongsFromSearchResult(service.getStarred()) } currentDirectory.postValue(musicDirectory) @@ -239,7 +239,7 @@ class TrackCollectionModel(application: Application) : GenericListModel(applicat } } - suspend fun getPlaylist(playlistId: String, playlistName: String?) { + suspend fun getPlaylist(playlistId: String, playlistName: String) { withContext(Dispatchers.IO) { val service = MusicServiceFactory.getMusicService() diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerController.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerController.kt index 1706cf7e..7f88a75d 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerController.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerController.kt @@ -429,10 +429,7 @@ class MediaPlayerController( get() { try { val username = activeServerProvider.getActiveServer().userName - val (_, _, _, _, _, _, _, _, _, _, _, _, jukeboxRole) = getMusicService().getUser( - username - ) - return jukeboxRole + return getMusicService().getUser(username).jukeboxRole } catch (e: Exception) { Timber.w(e, "Error getting user information") } 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 61898ceb..28ff9476 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt @@ -1,20 +1,8 @@ /* - 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 + * RestMusicService.kt + * Copyright (C) 2009-2021 Ultrasonic developers + * + * Distributed under terms of the GNU GPLv3 license. */ package org.moire.ultrasonic.service @@ -64,7 +52,6 @@ import timber.log.Timber /** * This Music Service implementation connects to a server using the Subsonic REST API - * @author Sindre Mehus */ open class RESTMusicService( private val subsonicAPIClient: SubsonicAPIClient, @@ -109,7 +96,7 @@ open class RESTMusicService( override fun getIndexes( musicFolderId: String?, refresh: Boolean - ): Indexes? { + ): Indexes { val indexName = INDEXES_STORAGE_NAME + (musicFolderId ?: "") val cachedIndexes = fileStorage.load(indexName, getIndexesSerializer()) @@ -171,7 +158,7 @@ open class RESTMusicService( id: String, name: String?, refresh: Boolean - ): MusicDirectory? { + ): MusicDirectory { val response = responseChecker.callWithResponseCheck { api -> api.getMusicDirectory(id).execute() } @@ -268,7 +255,7 @@ open class RESTMusicService( @Throws(Exception::class) override fun getPlaylist( id: String, - name: String? + name: String ): MusicDirectory { val response = responseChecker.callWithResponseCheck { api -> api.getPlaylist(id).execute() @@ -282,7 +269,7 @@ open class RESTMusicService( @Throws(IOException::class) private fun savePlaylist( - name: String?, + name: String, playlist: MusicDirectory ) { val playlistFile = FileUtil.getPlaylistFile( @@ -326,16 +313,14 @@ open class RESTMusicService( @Throws(Exception::class) override fun createPlaylist( - id: String?, - name: String?, + id: String, + name: String, entries: List ) { val pSongIds: MutableList = ArrayList(entries.size) for ((id1) in entries) { - if (id1 != null) { - pSongIds.add(id1) - } + pSongIds.add(id1) } responseChecker.callWithResponseCheck { api -> api.createPlaylist(id, name, pSongIds.toList()).execute() @@ -400,8 +385,8 @@ open class RESTMusicService( @Throws(Exception::class) override fun getLyrics( - artist: String?, - title: String? + artist: String, + title: String ): Lyrics { val response = responseChecker.callWithResponseCheck { api -> api.getLyrics(artist, title).execute() @@ -587,7 +572,7 @@ open class RESTMusicService( ): Pair { val songOffset = if (offset < 0) 0 else offset - val response = subsonicAPIClient.stream(song.id!!, maxBitrate, songOffset) + val response = subsonicAPIClient.stream(song.id, maxBitrate, songOffset) checkStreamResponseError(response) if (response.stream == null) { @@ -704,7 +689,7 @@ open class RESTMusicService( @Throws(Exception::class) override fun getGenres( refresh: Boolean - ): List { + ): List? { val response = responseChecker.callWithResponseCheck { api -> api.getGenres().execute() } return response.body()!!.genresList.toDomainEntityList() diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/DownloadHandler.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/DownloadHandler.kt index 487e552a..7782d8a0 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/DownloadHandler.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/DownloadHandler.kt @@ -226,7 +226,7 @@ class DownloadHandler( } } } else { - root = musicService.getPlaylist(id, name) + root = musicService.getPlaylist(id, name!!) } getSongsRecursively(root, songs) } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ShareHandler.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ShareHandler.kt index e52e98d9..74a612b7 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ShareHandler.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ShareHandler.kt @@ -68,9 +68,9 @@ class ShareHandler(val context: Context) { ) { @Throws(Throwable::class) override fun doInBackground(): Share { - val ids: MutableList = ArrayList() + val ids: MutableList = ArrayList() if (shareDetails.Entries.isEmpty()) { - ids.add(fragment.arguments?.getString(Constants.INTENT_EXTRA_NAME_ID)) + fragment.arguments?.getString(Constants.INTENT_EXTRA_NAME_ID)?.let { ids.add(it) } } else { for ((id) in shareDetails.Entries) { ids.add(id) From 8567fc01054d96bbdb6e17bc1143e329df14ee7f Mon Sep 17 00:00:00 2001 From: tzugen Date: Wed, 26 May 2021 23:22:13 +0200 Subject: [PATCH 07/15] Fix warning in FileLoggerTree.kt --- .../src/main/kotlin/org/moire/ultrasonic/log/FileLoggerTree.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/log/FileLoggerTree.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/log/FileLoggerTree.kt index c6b701e9..28be7d2a 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/log/FileLoggerTree.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/log/FileLoggerTree.kt @@ -161,7 +161,7 @@ class FileLoggerTree : Timber.DebugTree() { } } - private fun getLogFileList(): Array { + private fun getLogFileList(): Array? { val directory = FileUtil.getUltrasonicDirectory() return directory.listFiles { t -> t.name.matches(fileNameRegex) } } From 38c1480f772e7d1170e0779a3d462f591ea4b970 Mon Sep 17 00:00:00 2001 From: tzugen Date: Wed, 26 May 2021 23:25:56 +0200 Subject: [PATCH 08/15] Whitespace and detekt --- .../ultrasonic/service/CachedMusicService.kt | 10 ++- .../moire/ultrasonic/service/MusicService.kt | 4 +- .../ultrasonic/service/OfflineException.kt | 2 +- .../ultrasonic/service/OfflineMusicService.kt | 69 ++++++++++--------- 4 files changed, 42 insertions(+), 43 deletions(-) diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/CachedMusicService.kt b/ultrasonic/src/main/java/org/moire/ultrasonic/service/CachedMusicService.kt index 417802ca..bf939bc3 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/CachedMusicService.kt +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/CachedMusicService.kt @@ -19,6 +19,8 @@ package org.moire.ultrasonic.service import android.graphics.Bitmap +import java.io.InputStream +import java.util.concurrent.TimeUnit import org.koin.java.KoinJavaComponent.inject import org.moire.ultrasonic.data.ActiveServerProvider import org.moire.ultrasonic.domain.Bookmark @@ -39,12 +41,8 @@ import org.moire.ultrasonic.util.Constants import org.moire.ultrasonic.util.LRUCache import org.moire.ultrasonic.util.TimeLimitedCache import org.moire.ultrasonic.util.Util -import java.io.InputStream -import java.util.concurrent.TimeUnit -/** - * @author Sindre Mehus - */ +@Suppress("TooManyFunctions") class CachedMusicService(private val musicService: MusicService) : MusicService { private val activeServerProvider = inject( ActiveServerProvider::class.java @@ -482,4 +480,4 @@ class CachedMusicService(private val musicService: MusicService) : MusicService cachedAlbum = LRUCache(MUSIC_DIR_CACHE_SIZE) cachedUserInfo = LRUCache(MUSIC_DIR_CACHE_SIZE) } -} \ No newline at end of file +} diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicService.kt b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicService.kt index 4705d78a..ab2746f4 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicService.kt +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicService.kt @@ -7,6 +7,7 @@ package org.moire.ultrasonic.service import android.graphics.Bitmap +import java.io.InputStream import org.moire.ultrasonic.domain.Bookmark import org.moire.ultrasonic.domain.ChatMessage import org.moire.ultrasonic.domain.Genre @@ -21,7 +22,6 @@ import org.moire.ultrasonic.domain.SearchCriteria import org.moire.ultrasonic.domain.SearchResult import org.moire.ultrasonic.domain.Share import org.moire.ultrasonic.domain.UserInfo -import java.io.InputStream interface MusicService { @Throws(Exception::class) @@ -189,4 +189,4 @@ interface MusicService { @Throws(Exception::class) fun getPodcastEpisodes(podcastChannelId: String?): MusicDirectory? -} \ No newline at end of file +} diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineException.kt b/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineException.kt index 5833e133..5301a8d4 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineException.kt +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineException.kt @@ -13,4 +13,4 @@ class OfflineException(message: String?) : Exception(message) { companion object { private const val serialVersionUID = -4479642294747429444L } -} \ No newline at end of file +} diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.kt b/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.kt index 6b696964..ed5b9a53 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.kt +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.kt @@ -8,6 +8,21 @@ package org.moire.ultrasonic.service import android.graphics.Bitmap import android.media.MediaMetadataRetriever +import java.io.BufferedReader +import java.io.BufferedWriter +import java.io.File +import java.io.FileReader +import java.io.FileWriter +import java.io.InputStream +import java.io.Reader +import java.security.SecureRandom +import java.util.ArrayList +import java.util.HashSet +import java.util.LinkedList +import java.util.Locale +import java.util.Random +import java.util.concurrent.TimeUnit +import java.util.regex.Pattern import org.koin.java.KoinJavaComponent.inject import org.moire.ultrasonic.data.ActiveServerProvider import org.moire.ultrasonic.domain.Artist @@ -29,21 +44,6 @@ import org.moire.ultrasonic.util.Constants import org.moire.ultrasonic.util.FileUtil import org.moire.ultrasonic.util.Util import timber.log.Timber -import java.io.BufferedReader -import java.io.BufferedWriter -import java.io.File -import java.io.FileReader -import java.io.FileWriter -import java.io.InputStream -import java.io.Reader -import java.security.SecureRandom -import java.util.ArrayList -import java.util.HashSet -import java.util.LinkedList -import java.util.Locale -import java.util.Random -import java.util.concurrent.TimeUnit -import java.util.regex.Pattern class OfflineMusicService : MusicService { private val activeServerProvider = inject( @@ -299,7 +299,7 @@ class OfflineMusicService : MusicService { """ $filePath - """.trimIndent() + """.trimIndent() ) } } catch (e: Exception) { @@ -452,47 +452,47 @@ class OfflineMusicService : MusicService { offset: Int, musicFolderId: String? ): MusicDirectory { - throw OfflineException("OfflineMusicService.getAlbumList2 was called but it isn't available") + throw OfflineException("getAlbumList2 isn't available in offline mode") } @Throws(OfflineException::class) override fun getVideoUrl(id: String, useFlash: Boolean): String? { - throw OfflineException("OfflineMusicService.getVideoUrl was called but it isn't available") + throw OfflineException("getVideoUrl isn't available in offline mode") } @Throws(OfflineException::class) override fun getChatMessages(since: Long?): List? { - throw OfflineException("OfflineMusicService.getChatMessages was called but it isn't available") + throw OfflineException("getChatMessages isn't available in offline mode") } @Throws(OfflineException::class) override fun addChatMessage(message: String) { - throw OfflineException("OfflineMusicService.addChatMessage was called but it isn't available") + throw OfflineException("addChatMessage isn't available in offline mode") } @Throws(OfflineException::class) override fun getBookmarks(): List? { - throw OfflineException("OfflineMusicService.getBookmarks was called but it isn't available") + throw OfflineException("getBookmarks isn't available in offline mode") } @Throws(OfflineException::class) override fun deleteBookmark(id: String) { - throw OfflineException("OfflineMusicService.deleteBookmark was called but it isn't available") + throw OfflineException("deleteBookmark isn't available in offline mode") } @Throws(OfflineException::class) override fun createBookmark(id: String, position: Int) { - throw OfflineException("OfflineMusicService.createBookmark was called but it isn't available") + throw OfflineException("createBookmark isn't available in offline mode") } @Throws(OfflineException::class) override fun getVideos(refresh: Boolean): MusicDirectory? { - throw OfflineException("OfflineMusicService.getVideos was called but it isn't available") + throw OfflineException("getVideos isn't available in offline mode") } @Throws(OfflineException::class) override fun getStarred2(): SearchResult { - throw OfflineException("OfflineMusicService.getStarred2 was called but it isn't available") + throw OfflineException("getStarred2 isn't available in offline mode") } override fun ping() {} @@ -501,22 +501,22 @@ class OfflineMusicService : MusicService { @Throws(OfflineException::class) override fun getArtists(refresh: Boolean): Indexes { - throw OfflineException("OfflineMusicService.getArtists was called but it isn't available") + throw OfflineException("getArtists isn't available in offline mode") } @Throws(OfflineException::class) override fun getArtist(id: String, name: String?, refresh: Boolean): MusicDirectory { - throw OfflineException("OfflineMusicService.getArtist was called but it isn't available") + throw OfflineException("getArtist isn't available in offline mode") } @Throws(OfflineException::class) override fun getAlbum(id: String, name: String?, refresh: Boolean): MusicDirectory { - throw OfflineException("OfflineMusicService.getAlbum was called but it isn't available") + throw OfflineException("getAlbum isn't available in offline mode") } @Throws(OfflineException::class) override fun getPodcastEpisodes(podcastChannelId: String?): MusicDirectory? { - throw OfflineException("OfflineMusicService.getPodcastEpisodes was called but it isn't available") + throw OfflineException("getPodcastEpisodes isn't available in offline mode") } @Throws(OfflineException::class) @@ -525,17 +525,17 @@ class OfflineMusicService : MusicService { offset: Long, maxBitrate: Int ): Pair { - throw OfflineException("OfflineMusicService.getDownloadInputStream was called but it isn't available") + throw OfflineException("getDownloadInputStream isn't available in offline mode") } @Throws(OfflineException::class) override fun setRating(id: String, rating: Int) { - throw OfflineException("OfflineMusicService.setRating was called but it isn't available") + throw OfflineException("setRating isn't available in offline mode") } @Throws(OfflineException::class) override fun getPodcastsChannels(refresh: Boolean): List { - throw OfflineException("OfflineMusicService.getPodcastsChannels was called but it isn't available") + throw OfflineException("getPodcastsChannels isn't available in offline mode") } companion object { @@ -545,7 +545,8 @@ class OfflineMusicService : MusicService { if (file.isDirectory) { return name } - if (name.endsWith(".partial") || name.contains(".partial.") || name == Constants.ALBUM_ART_FILE) { + if (name.endsWith(".partial") || name.contains(".partial.") + || name == Constants.ALBUM_ART_FILE) { return null } name = name.replace(".complete", "") @@ -716,4 +717,4 @@ class OfflineMusicService : MusicService { } } } -} \ No newline at end of file +} From aa1c0d8baa7217b5849d0478dc8ee2a15fcc8d96 Mon Sep 17 00:00:00 2001 From: tzugen Date: Thu, 27 May 2021 11:13:23 +0200 Subject: [PATCH 09/15] Convert TimeLimitedCache to Kotlin --- .../ultrasonic/util/TimeLimitedCache.java | 61 ------------------- .../moire/ultrasonic/util/TimeLimitedCache.kt | 31 ++++++++++ 2 files changed, 31 insertions(+), 61 deletions(-) delete mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/util/TimeLimitedCache.java create mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/util/TimeLimitedCache.kt diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/TimeLimitedCache.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/TimeLimitedCache.java deleted file mode 100644 index 1a39a440..00000000 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/TimeLimitedCache.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - 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 - */ -package org.moire.ultrasonic.util; - -import java.lang.ref.SoftReference; -import java.util.concurrent.TimeUnit; - -/** - * @author Sindre Mehus - * @version $Id$ - */ -public class TimeLimitedCache -{ - - private SoftReference value; - private final long ttlMillis; - private long expires; - - public TimeLimitedCache(long ttl, TimeUnit timeUnit) - { - this.ttlMillis = TimeUnit.MILLISECONDS.convert(ttl, timeUnit); - } - - public T get() - { - return System.currentTimeMillis() < expires ? value.get() : null; - } - - public void set(T value) - { - set(value, ttlMillis, TimeUnit.MILLISECONDS); - } - - public void set(T value, long ttl, TimeUnit timeUnit) - { - this.value = new SoftReference(value); - expires = System.currentTimeMillis() + timeUnit.toMillis(ttl); - } - - public void clear() - { - expires = 0L; - value = null; - } -} diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/TimeLimitedCache.kt b/ultrasonic/src/main/java/org/moire/ultrasonic/util/TimeLimitedCache.kt new file mode 100644 index 00000000..09afdb41 --- /dev/null +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/TimeLimitedCache.kt @@ -0,0 +1,31 @@ +/* + * TimeLimitedCache.kt + * Copyright (C) 2009-2021 Ultrasonic developers + * + * Distributed under terms of the GNU GPLv3 license. + */ +package org.moire.ultrasonic.util + +import java.lang.ref.SoftReference +import java.util.concurrent.TimeUnit + +class TimeLimitedCache(expiresAfter: Long = 60L, timeUnit: TimeUnit = TimeUnit.MINUTES) { + private var value: SoftReference? = null + private val expiresMillis: Long = TimeUnit.MILLISECONDS.convert(expiresAfter, timeUnit) + private var expires: Long = 0 + + fun get(): T? { + return if (System.currentTimeMillis() < expires) value!!.get() else null + } + + @JvmOverloads + fun set(value: T, ttl: Long = expiresMillis, timeUnit: TimeUnit = TimeUnit.MILLISECONDS) { + this.value = SoftReference(value) + expires = System.currentTimeMillis() + timeUnit.toMillis(ttl) + } + + fun clear() { + expires = 0L + value = null + } +} From 154662bec5edd6fd2bd8c792cb701194017c0ffa Mon Sep 17 00:00:00 2001 From: tzugen Date: Thu, 27 May 2021 11:14:23 +0200 Subject: [PATCH 10/15] Make Artist and Entry comparable (thus sortable) --- .../kotlin/org/moire/ultrasonic/domain/Artist.kt | 16 +++++++++++++++- .../moire/ultrasonic/domain/MusicDirectory.kt | 16 +++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/Artist.kt b/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/Artist.kt index 466d4832..b7112e2f 100644 --- a/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/Artist.kt +++ b/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/Artist.kt @@ -9,8 +9,22 @@ data class Artist( var coverArt: String? = null, var albumCount: Long? = null, var closeness: Int = 0 -) : Serializable, GenericEntry() { +) : Serializable, GenericEntry(), Comparable { companion object { private const val serialVersionUID = -5790532593784846982L } + + override fun compareTo(other: Artist): Int { + when { + this.closeness == other.closeness -> { + return 0 + } + this.closeness > other.closeness -> { + return -1 + } + else -> { + return 1 + } + } + } } diff --git a/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/MusicDirectory.kt b/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/MusicDirectory.kt index 0a5295dd..cdd035a5 100644 --- a/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/MusicDirectory.kt +++ b/core/domain/src/main/kotlin/org/moire/ultrasonic/domain/MusicDirectory.kt @@ -66,7 +66,7 @@ class MusicDirectory { var bookmarkPosition: Int = 0, var userRating: Int? = null, var averageRating: Float? = null - ) : Serializable, GenericEntry() { + ) : Serializable, GenericEntry(), Comparable { fun setDuration(duration: Long) { this.duration = duration.toInt() } @@ -74,5 +74,19 @@ class MusicDirectory { companion object { private const val serialVersionUID = -3339106650010798108L } + + override fun compareTo(other: Entry): Int { + when { + this.closeness == other.closeness -> { + return 0 + } + this.closeness > other.closeness -> { + return -1 + } + else -> { + return 1 + } + } + } } } From ac77d9557cafa08de11ff9815ca90fc6f780cb90 Mon Sep 17 00:00:00 2001 From: tzugen Date: Thu, 27 May 2021 11:15:28 +0200 Subject: [PATCH 11/15] Clarify time handling for the time limited cache (Less magic numbers) --- .../ultrasonic/service/CachedMusicService.kt | 45 +++++++------------ 1 file changed, 16 insertions(+), 29 deletions(-) diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/CachedMusicService.kt b/ultrasonic/src/main/java/org/moire/ultrasonic/service/CachedMusicService.kt index bf939bc3..1086a797 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/CachedMusicService.kt +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/CachedMusicService.kt @@ -1,20 +1,8 @@ /* - 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 + * CachedMusicService.kt + * Copyright (C) 2009-2021 Ultrasonic developers + * + * Distributed under terms of the GNU GPLv3 license. */ package org.moire.ultrasonic.service @@ -51,15 +39,14 @@ class CachedMusicService(private val musicService: MusicService) : MusicService private val cachedArtist: LRUCache> private val cachedAlbum: LRUCache> private val cachedUserInfo: LRUCache> - private val cachedLicenseValid = TimeLimitedCache(120, TimeUnit.SECONDS) - private val cachedIndexes = TimeLimitedCache(60 * 60, TimeUnit.SECONDS) - private val cachedArtists = TimeLimitedCache(60 * 60, TimeUnit.SECONDS) - private val cachedPlaylists = TimeLimitedCache?>(3600, TimeUnit.SECONDS) - private val cachedPodcastsChannels = - TimeLimitedCache>(3600, TimeUnit.SECONDS) + private val cachedLicenseValid = TimeLimitedCache(expiresAfter = 10, TimeUnit.MINUTES) + private val cachedIndexes = TimeLimitedCache() + private val cachedArtists = TimeLimitedCache() + private val cachedPlaylists = TimeLimitedCache?>() + private val cachedPodcastsChannels = TimeLimitedCache>() private val cachedMusicFolders = - TimeLimitedCache?>(10 * 3600, TimeUnit.SECONDS) - private val cachedGenres = TimeLimitedCache?>(10 * 3600, TimeUnit.SECONDS) + TimeLimitedCache?>(10, TimeUnit.HOURS) + private val cachedGenres = TimeLimitedCache?>(10, TimeUnit.HOURS) private var restUrl: String? = null private var cachedMusicFolderId: String? = null @@ -72,12 +59,12 @@ class CachedMusicService(private val musicService: MusicService) : MusicService @Throws(Exception::class) override fun isLicenseValid(): Boolean { checkSettingsChanged() - var result = cachedLicenseValid.get() - if (result == null) { - result = musicService.isLicenseValid() - cachedLicenseValid[result, if (result) 30L * 60L else 2L * 60L] = TimeUnit.SECONDS + var isValid = cachedLicenseValid.get() + if (isValid == null) { + isValid = musicService.isLicenseValid() + cachedLicenseValid.set(isValid) } - return result + return isValid } @Throws(Exception::class) From cc7e273d50850dfecdd07743adcba1a5b4a6b657 Mon Sep 17 00:00:00 2001 From: tzugen Date: Thu, 27 May 2021 11:18:29 +0200 Subject: [PATCH 12/15] Fix remaining warnings --- detekt-config.yml | 2 + .../moire/ultrasonic/service/MusicService.kt | 1 + .../ultrasonic/service/OfflineMusicService.kt | 79 +++++++------------ .../moire/ultrasonic/subsonic/ShareHandler.kt | 4 +- 4 files changed, 35 insertions(+), 51 deletions(-) diff --git a/detekt-config.yml b/detekt-config.yml index 9301b0b7..d095b0b5 100644 --- a/detekt-config.yml +++ b/detekt-config.yml @@ -69,6 +69,8 @@ style: ignorePropertyDeclaration: true UnnecessaryAbstractClass: active: false + ReturnCount: + max: 3 comments: active: true diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicService.kt b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicService.kt index ab2746f4..f4f88592 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicService.kt +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicService.kt @@ -23,6 +23,7 @@ import org.moire.ultrasonic.domain.SearchResult import org.moire.ultrasonic.domain.Share import org.moire.ultrasonic.domain.UserInfo +@Suppress("TooManyFunctions") interface MusicService { @Throws(Exception::class) fun ping() diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.kt b/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.kt index ed5b9a53..8766665e 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.kt +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.kt @@ -45,6 +45,10 @@ import org.moire.ultrasonic.util.FileUtil import org.moire.ultrasonic.util.Util import timber.log.Timber + +// TODO: There are quite a number of deeply nested and complicated functions in this class.. +// Simplify them :) +@Suppress("TooManyFunctions") class OfflineMusicService : MusicService { private val activeServerProvider = inject( ActiveServerProvider::class.java @@ -76,11 +80,15 @@ class OfflineMusicService : MusicService { return@sortWith -1 } for (article in ignoredArticles) { - var index = lhs.indexOf(String.format("%s ", article.toLowerCase(Locale.ROOT))) + var index = lhs.indexOf( + String.format(Locale.ROOT, "%s ", article.toLowerCase(Locale.ROOT)) + ) if (index == 0) { lhs = lhs.substring(article.length + 1) } - index = rhs.indexOf(String.format("%s ", article.toLowerCase(Locale.ROOT))) + index = rhs.indexOf( + String.format(Locale.ROOT, "%s ", article.toLowerCase(Locale.ROOT)) + ) if (index == 0) { rhs = rhs.substring(article.length + 1) } @@ -122,7 +130,7 @@ class OfflineMusicService : MusicService { return try { val bitmap = FileUtil.getAvatarBitmap(username, size, highQuality) Util.scaleBitmap(bitmap, size) - } catch (e: Exception) { + } catch (ignored: Exception) { null } } @@ -136,7 +144,7 @@ class OfflineMusicService : MusicService { return try { val bitmap = FileUtil.getAlbumArtBitmap(entry, size, highQuality) Util.scaleBitmap(bitmap, size) - } catch (e: Exception) { + } catch (ignored: Exception) { null } } @@ -162,51 +170,14 @@ class OfflineMusicService : MusicService { } } - artists.sortWith { lhs, rhs -> - when { - lhs.closeness == rhs.closeness -> { - return@sortWith 0 - } - lhs.closeness > rhs.closeness -> { - return@sortWith -1 - } - else -> { - return@sortWith 1 - } - } - } - - albums.sortWith { lhs, rhs -> - when { - lhs.closeness == rhs.closeness -> { - return@sortWith 0 - } - lhs.closeness > rhs.closeness -> { - return@sortWith -1 - } - else -> { - return@sortWith 1 - } - } - } - - songs.sortWith { lhs, rhs -> - when { - lhs.closeness == rhs.closeness -> { - return@sortWith 0 - } - lhs.closeness > rhs.closeness -> { - return@sortWith -1 - } - else -> { - return@sortWith 1 - } - } - } + artists.sort() + albums.sort() + songs.sort() return SearchResult(artists, albums, songs) } + @Suppress("NestedBlockDepth", "TooGenericExceptionCaught") override fun getPlaylists(refresh: Boolean): List { val playlists: MutableList = ArrayList() val root = FileUtil.getPlaylistDirectory() @@ -280,6 +251,7 @@ class OfflineMusicService : MusicService { } } + @Suppress("TooGenericExceptionCaught") @Throws(Exception::class) override fun createPlaylist(id: String, name: String, entries: List) { val playlistFile = @@ -302,7 +274,7 @@ class OfflineMusicService : MusicService { """.trimIndent() ) } - } catch (e: Exception) { + } catch (ignored: Exception) { Timber.w("Failed to save playlist: %s", name) } finally { bw.close() @@ -495,7 +467,9 @@ class OfflineMusicService : MusicService { throw OfflineException("getStarred2 isn't available in offline mode") } - override fun ping() {} + override fun ping() { + // Void + } override fun isLicenseValid(): Boolean = true @@ -545,21 +519,25 @@ class OfflineMusicService : MusicService { if (file.isDirectory) { return name } - if (name.endsWith(".partial") || name.contains(".partial.") - || name == Constants.ALBUM_ART_FILE) { + if (name.endsWith(".partial") || name.contains(".partial.") || + name == Constants.ALBUM_ART_FILE + ) { return null } name = name.replace(".complete", "") return FileUtil.getBaseName(name) } + @Suppress("TooGenericExceptionCaught", "ComplexMethod", "LongMethod", "NestedBlockDepth") private fun createEntry(file: File, name: String?): MusicDirectory.Entry { val entry = MusicDirectory.Entry(file.path) entry.isDirectory = file.isDirectory entry.parent = file.parent entry.size = file.length() val root = FileUtil.getMusicDirectory().path - entry.path = file.path.replaceFirst(String.format("^%s/", root).toRegex(), "") + entry.path = file.path.replaceFirst( + String.format(Locale.ROOT, "^%s/", root).toRegex(), "" + ) entry.title = name if (file.isFile) { var artist: String? = null @@ -648,6 +626,7 @@ class OfflineMusicService : MusicService { return entry } + @Suppress("NestedBlockDepth") private fun recursiveAlbumSearch( artistName: String, file: File, diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ShareHandler.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ShareHandler.kt index 74a612b7..4ae89551 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ShareHandler.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ShareHandler.kt @@ -70,7 +70,9 @@ class ShareHandler(val context: Context) { override fun doInBackground(): Share { val ids: MutableList = ArrayList() if (shareDetails.Entries.isEmpty()) { - fragment.arguments?.getString(Constants.INTENT_EXTRA_NAME_ID)?.let { ids.add(it) } + fragment.arguments?.getString(Constants.INTENT_EXTRA_NAME_ID)?.let { + ids.add(it) + } } else { for ((id) in shareDetails.Entries) { ids.add(id) From 6dc0eb7eccccc0e17aac0304fcbe68eb046bd9c9 Mon Sep 17 00:00:00 2001 From: tzugen Date: Thu, 27 May 2021 11:33:10 +0200 Subject: [PATCH 13/15] Move files to Koltin dir --- .../org/moire/ultrasonic/service/CachedMusicService.kt | 0 .../{java => kotlin}/org/moire/ultrasonic/service/MusicService.kt | 0 .../org/moire/ultrasonic/service/OfflineException.kt | 0 .../org/moire/ultrasonic/service/OfflineMusicService.kt | 0 .../org/moire/ultrasonic/util/TimeLimitedCache.kt | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename ultrasonic/src/main/{java => kotlin}/org/moire/ultrasonic/service/CachedMusicService.kt (100%) rename ultrasonic/src/main/{java => kotlin}/org/moire/ultrasonic/service/MusicService.kt (100%) rename ultrasonic/src/main/{java => kotlin}/org/moire/ultrasonic/service/OfflineException.kt (100%) rename ultrasonic/src/main/{java => kotlin}/org/moire/ultrasonic/service/OfflineMusicService.kt (100%) rename ultrasonic/src/main/{java => kotlin}/org/moire/ultrasonic/util/TimeLimitedCache.kt (100%) diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/CachedMusicService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CachedMusicService.kt similarity index 100% rename from ultrasonic/src/main/java/org/moire/ultrasonic/service/CachedMusicService.kt rename to ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CachedMusicService.kt diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MusicService.kt similarity index 100% rename from ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicService.kt rename to ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MusicService.kt diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineException.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineException.kt similarity index 100% rename from ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineException.kt rename to ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineException.kt diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt similarity index 100% rename from ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.kt rename to ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/TimeLimitedCache.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/TimeLimitedCache.kt similarity index 100% rename from ultrasonic/src/main/java/org/moire/ultrasonic/util/TimeLimitedCache.kt rename to ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/TimeLimitedCache.kt From e21ae1299bb296451ca921759541469fcead260e Mon Sep 17 00:00:00 2001 From: tzugen Date: Thu, 27 May 2021 11:41:00 +0200 Subject: [PATCH 14/15] Fix more detekt and lint issues and remove them from the baseline. --- .../loader/image/AvatarRequestHandler.kt | 4 +- .../loader/image/CoverArtRequestHandler.kt | 4 +- detekt-baseline.xml | 10 --- ultrasonic/lint-baseline.xml | 77 ------------------- .../ultrasonic/activity/NavigationActivity.kt | 2 +- .../fragment/TrackCollectionFragment.kt | 3 +- .../ultrasonic/service/LocalMediaPlayer.kt | 4 +- .../ultrasonic/service/MediaPlayerService.kt | 5 +- .../ultrasonic/service/OfflineMusicService.kt | 4 +- .../ultrasonic/service/RESTMusicService.kt | 2 +- 10 files changed, 11 insertions(+), 104 deletions(-) diff --git a/core/subsonic-api-image-loader/src/main/kotlin/org/moire/ultrasonic/subsonic/loader/image/AvatarRequestHandler.kt b/core/subsonic-api-image-loader/src/main/kotlin/org/moire/ultrasonic/subsonic/loader/image/AvatarRequestHandler.kt index 9ea19237..ab8ac70e 100644 --- a/core/subsonic-api-image-loader/src/main/kotlin/org/moire/ultrasonic/subsonic/loader/image/AvatarRequestHandler.kt +++ b/core/subsonic-api-image-loader/src/main/kotlin/org/moire/ultrasonic/subsonic/loader/image/AvatarRequestHandler.kt @@ -26,10 +26,10 @@ class AvatarRequestHandler( ?: throw IllegalArgumentException("Nullable username") val response = apiClient.getAvatar(username) - if (response.hasError()) { + if (response.hasError() || response.stream == null) { throw IOException("${response.apiError}") } else { - return Result(Okio.source(response.stream), Picasso.LoadedFrom.NETWORK) + return Result(Okio.source(response.stream!!), Picasso.LoadedFrom.NETWORK) } } } diff --git a/core/subsonic-api-image-loader/src/main/kotlin/org/moire/ultrasonic/subsonic/loader/image/CoverArtRequestHandler.kt b/core/subsonic-api-image-loader/src/main/kotlin/org/moire/ultrasonic/subsonic/loader/image/CoverArtRequestHandler.kt index bc1f197a..7e242479 100644 --- a/core/subsonic-api-image-loader/src/main/kotlin/org/moire/ultrasonic/subsonic/loader/image/CoverArtRequestHandler.kt +++ b/core/subsonic-api-image-loader/src/main/kotlin/org/moire/ultrasonic/subsonic/loader/image/CoverArtRequestHandler.kt @@ -24,10 +24,10 @@ class CoverArtRequestHandler(private val apiClient: SubsonicAPIClient) : Request ?: throw IllegalArgumentException("Nullable id") val response = apiClient.getCoverArt(id) - if (response.hasError()) { + if (response.hasError() || response.stream == null) { throw IOException("${response.apiError}") } else { - return Result(Okio.source(response.stream), NETWORK) + return Result(Okio.source(response.stream!!), NETWORK) } } } diff --git a/detekt-baseline.xml b/detekt-baseline.xml index 6423c4f4..e1cb4ed7 100644 --- a/detekt-baseline.xml +++ b/detekt-baseline.xml @@ -67,18 +67,10 @@ NestedBlockDepth:DownloadFile.kt$DownloadFile.DownloadTask$override fun execute() NestedBlockDepth:DownloadHandler.kt$DownloadHandler$private fun downloadRecursively( fragment: Fragment, id: String, name: String?, isShare: Boolean, isDirectory: Boolean, save: Boolean, append: Boolean, autoPlay: Boolean, shuffle: Boolean, background: Boolean, playNext: Boolean, unpin: Boolean, isArtist: Boolean ) NestedBlockDepth:MediaPlayerService.kt$MediaPlayerService$private fun setupOnSongCompletedHandler() - ReturnCount:ActiveServerProvider.kt$ActiveServerProvider$ fun getActiveServer(): ServerSetting ReturnCount:CommunicationErrorHandler.kt$CommunicationErrorHandler.Companion$fun getErrorMessage(error: Throwable, context: Context): String - ReturnCount:FileLoggerTree.kt$FileLoggerTree$ private fun getNextLogFile() - ReturnCount:MediaPlayerService.kt$MediaPlayerService$private fun generateAction(context: Context, requestCode: Int): NotificationCompat.Action? - ReturnCount:RESTMusicService.kt$RESTMusicService$@Throws(Exception::class) override fun getAvatar( username: String?, size: Int, saveToFile: Boolean, highQuality: Boolean ): Bitmap? ReturnCount:RESTMusicService.kt$RESTMusicService$@Throws(Exception::class) override fun getCoverArt( entry: MusicDirectory.Entry?, size: Int, saveToFile: Boolean, highQuality: Boolean ): Bitmap? ReturnCount:ServerRowAdapter.kt$ServerRowAdapter$ private fun popupMenuItemClick(menuItem: MenuItem, position: Int): Boolean ReturnCount:TrackCollectionFragment.kt$TrackCollectionFragment$override fun onContextItemSelected(menuItem: MenuItem): Boolean - ReturnCount:TrackCollectionFragment.kt$TrackCollectionFragment$override fun onOptionsItemSelected(item: MenuItem): Boolean - SwallowedException:LocalMediaPlayer.kt$LocalMediaPlayer$catch (e: Throwable) { // Froyo or lower } - SwallowedException:LocalMediaPlayer.kt$LocalMediaPlayer$catch (e: Throwable) { } - SwallowedException:MediaPlayerService.kt$MediaPlayerService$catch (x: IndexOutOfBoundsException) { // Ignored } SwallowedException:NavigationActivity.kt$NavigationActivity$catch (e: Resources.NotFoundException) { destination.id.toString() } ThrowsCount:ApiCallResponseChecker.kt$ApiCallResponseChecker.Companion$@Throws(SubsonicRESTException::class, IOException::class) fun checkResponseSuccessful(response: Response<out SubsonicResponse>) TooGenericExceptionCaught:DownloadFile.kt$DownloadFile$e: Exception @@ -89,7 +81,6 @@ TooGenericExceptionCaught:LocalMediaPlayer.kt$LocalMediaPlayer$x: Exception TooGenericExceptionCaught:LocalMediaPlayer.kt$LocalMediaPlayer.PositionCache$e: Exception TooGenericExceptionCaught:MediaPlayerService.kt$MediaPlayerService$e: Exception - TooGenericExceptionCaught:MediaPlayerService.kt$MediaPlayerService$x: IndexOutOfBoundsException TooGenericExceptionCaught:SongView.kt$SongView$e: Exception TooGenericExceptionCaught:SubsonicUncaughtExceptionHandler.kt$SubsonicUncaughtExceptionHandler$x: Throwable TooGenericExceptionCaught:VideoPlayer.kt$VideoPlayer$e: Exception @@ -98,7 +89,6 @@ TooManyFunctions:MediaPlayerService.kt$MediaPlayerService : Service TooManyFunctions:RESTMusicService.kt$RESTMusicService : MusicService TooManyFunctions:TrackCollectionFragment.kt$TrackCollectionFragment : Fragment - UnusedPrivateMember:RESTMusicService.kt$RESTMusicService.Companion$private const val INDEXES_FOLDER_STORAGE_NAME = "indexes_folder" UtilityClassWithPublicConstructor:CommunicationErrorHandler.kt$CommunicationErrorHandler UtilityClassWithPublicConstructor:FragmentTitle.kt$FragmentTitle diff --git a/ultrasonic/lint-baseline.xml b/ultrasonic/lint-baseline.xml index c34a884d..21a3f032 100644 --- a/ultrasonic/lint-baseline.xml +++ b/ultrasonic/lint-baseline.xml @@ -23,72 +23,6 @@ column="55"/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - val dest: String = try { resources.getResourceName(destination.id) - } catch (e: Resources.NotFoundException) { + } catch (ignored: Resources.NotFoundException) { destination.id.toString() } Timber.d("Navigated to $dest") diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt index 49346615..876bf849 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt @@ -28,7 +28,6 @@ import androidx.lifecycle.Observer import androidx.lifecycle.viewModelScope import androidx.navigation.Navigation import androidx.swiperefreshlayout.widget.SwipeRefreshLayout -import java.security.SecureRandom import java.util.Collections import java.util.Random import kotlinx.coroutines.CoroutineExceptionHandler @@ -92,7 +91,7 @@ class TrackCollectionFragment : Fragment() { private var cancellationToken: CancellationToken? = null private val model: TrackCollectionModel by viewModels() - private val random: Random = SecureRandom() + private val random: Random = Random() override fun onCreate(savedInstanceState: Bundle?) { Util.applyTheme(this.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 26b96e36..0e4c2503 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/LocalMediaPlayer.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/LocalMediaPlayer.kt @@ -106,7 +106,7 @@ class LocalMediaPlayer( i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, mediaPlayer.audioSessionId) i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.packageName) context.sendBroadcast(i) - } catch (e: Throwable) { + } catch (ignored: Throwable) { // Froyo or lower } mediaPlayerLooper = Looper.myLooper() @@ -466,7 +466,7 @@ class LocalMediaPlayer( // the equalizer or visualizer with the player try { nextMediaPlayer!!.audioSessionId = mediaPlayer.audioSessionId - } catch (e: Throwable) { + } catch (ignored: Throwable) { } nextMediaPlayer!!.setDataSource(file.path) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerService.kt index de935eef..fdc3b4cb 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerService.kt @@ -24,7 +24,6 @@ import android.view.KeyEvent import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import org.koin.android.ext.android.inject -import org.koin.core.component.KoinApiExtension import org.moire.ultrasonic.R import org.moire.ultrasonic.activity.NavigationActivity import org.moire.ultrasonic.app.UApp @@ -49,7 +48,6 @@ import timber.log.Timber * Android Foreground Service for playing music * while the rest of the Ultrasonic App is in the background. */ -@KoinApiExtension @Suppress("LargeClass") class MediaPlayerService : Service() { private val binder: IBinder = SimpleServiceBinder(this) @@ -173,8 +171,7 @@ class MediaPlayerService : Service() { fun setCurrentPlaying(currentPlayingIndex: Int) { try { localMediaPlayer.setCurrentPlaying(downloader.downloadList[currentPlayingIndex]) - } catch (x: IndexOutOfBoundsException) { - // Ignored + } catch (ignored: IndexOutOfBoundsException) { } } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt index 8766665e..8e751382 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt @@ -15,7 +15,6 @@ import java.io.FileReader import java.io.FileWriter import java.io.InputStream import java.io.Reader -import java.security.SecureRandom import java.util.ArrayList import java.util.HashSet import java.util.LinkedList @@ -45,7 +44,6 @@ import org.moire.ultrasonic.util.FileUtil import org.moire.ultrasonic.util.Util import timber.log.Timber - // TODO: There are quite a number of deeply nested and complicated functions in this class.. // Simplify them :) @Suppress("TooManyFunctions") @@ -290,7 +288,7 @@ class OfflineMusicService : MusicService { if (children.isEmpty()) { return result } - val random: Random = SecureRandom() + val random = Random() for (i in 0 until size) { val file = children[random.nextInt(children.size)] result.addChild(createEntry(file, getName(file))) 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 28ff9476..fd9f2de6 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt @@ -53,6 +53,7 @@ import timber.log.Timber /** * This Music Service implementation connects to a server using the Subsonic REST API */ +@Suppress("LargeClass") open class RESTMusicService( private val subsonicAPIClient: SubsonicAPIClient, private val fileStorage: PermanentFileStorage, @@ -868,7 +869,6 @@ open class RESTMusicService( companion object { private const val MUSIC_FOLDER_STORAGE_NAME = "music_folder" private const val INDEXES_STORAGE_NAME = "indexes" - private const val INDEXES_FOLDER_STORAGE_NAME = "indexes_folder" private const val ARTISTS_STORAGE_NAME = "artists" } } From ee9c478bfe73a4dc0768f2afb7d519d1c44a7b6a Mon Sep 17 00:00:00 2001 From: tzugen Date: Fri, 28 May 2021 12:35:29 +0200 Subject: [PATCH 15/15] Make parentId nullable in TrackCollectionModel --- .../moire/ultrasonic/fragment/TrackCollectionFragment.kt | 4 ++-- .../org/moire/ultrasonic/fragment/TrackCollectionModel.kt | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt index 876bf849..08dd7fd7 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt @@ -281,12 +281,12 @@ class TrackCollectionFragment : Fragment() { setTitle(name) if (!isOffline() && Util.getShouldUseId3Tags()) { if (isAlbum) { - model.getAlbum(refresh, id!!, name, parentId!!) + model.getAlbum(refresh, id!!, name, parentId) } else { model.getArtist(refresh, id!!, name) } } else { - model.getMusicDirectory(refresh, id!!, name, parentId!!) + model.getMusicDirectory(refresh, id!!, name, parentId) } } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionModel.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionModel.kt index 957df5d9..45b2d81a 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionModel.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionModel.kt @@ -45,7 +45,7 @@ class TrackCollectionModel(application: Application) : GenericListModel(applicat refresh: Boolean, id: String, name: String?, - parentId: String + parentId: String? ) { withContext(Dispatchers.IO) { @@ -53,7 +53,7 @@ class TrackCollectionModel(application: Application) : GenericListModel(applicat var root = MusicDirectory() - if (allSongsId == id) { + if (allSongsId == id && parentId != null) { val musicDirectory = service.getMusicDirectory( parentId, name, refresh ) @@ -152,7 +152,7 @@ class TrackCollectionModel(application: Application) : GenericListModel(applicat } } - suspend fun getAlbum(refresh: Boolean, id: String, name: String?, parentId: String) { + suspend fun getAlbum(refresh: Boolean, id: String, name: String?, parentId: String?) { withContext(Dispatchers.IO) { @@ -160,7 +160,7 @@ class TrackCollectionModel(application: Application) : GenericListModel(applicat val musicDirectory: MusicDirectory - if (allSongsId == id) { + if (allSongsId == id && parentId != null) { val root = MusicDirectory() val songs: MutableCollection = LinkedList()