From d9f53920e57e503228b7e12525ff82f665ef636d Mon Sep 17 00:00:00 2001 From: Vologhat Date: Sun, 24 Sep 2023 18:39:12 +0300 Subject: [PATCH] optimize music cache logic --- .../libvkx/client/LibVKXClient.java | 11 +- .../java/com/vk/api/audio/AudioGetLyrics.java | 2 +- .../com/vk/music/view/x/LyricsHolder.java | 2 +- .../java/ru/vtosters/hooks/CatalogsHook.java | 2 +- .../vtosters/hooks/MusicCacheFilesHook.java | 16 - .../music}/MusicArtistSelectorHook.java | 2 +- .../music}/MusicBottomSheetHook.java | 52 +-- .../hooks/music/MusicCacheFilesHook.java | 14 + .../music}/PlaylistFormatterHook.java | 2 +- .../music/injectors}/CatalogJsonInjector.java | 8 +- .../music}/injectors/PlaylistInjector.java | 14 +- .../music}/injectors/ShuffleInjector.java | 2 +- .../music}/injectors/TracklistInjector.java | 10 +- .../lite/downloaders/AudioDownloader.java | 24 +- .../vtosters/lite/music/LastFMScrobbler.java | 4 +- .../ru/vtosters/lite/music/M3UDownloader.java | 158 -------- .../vtosters/lite/music/TransportStream.java | 36 -- .../ru/vtosters/lite/music/VKM3UParser.java | 126 ------- .../music/cache/CacheDatabaseDelegate.java | 343 ------------------ .../music/cache/FileCacheImplementation.java | 50 --- .../music/cache/MusicCacheDbDelegate.java | 87 +++++ .../lite/music/cache/MusicCacheImpl.java | 83 +++++ .../lite/music/cache/db/MusicCacheDb.java | 250 +++++++++++++ .../music/cache/helpers/PlaylistHelper.java | 11 +- .../music/cache/helpers/TracklistHelper.java | 14 +- .../music/callback/MusicCallbackBuilder.java | 2 +- .../converter/playlist/PlaylistConverter.java | 3 +- .../lite/music/converter/ts/FFMpeg.java | 119 +++--- .../lite/music/converter/ts/TSMerger.java | 72 +--- .../lite/music/downloader/M3UDownloader.java | 186 ++++++++++ .../music/{ => downloader}/MP3Downloader.java | 2 +- .../music/downloader/PlaylistDownloader.java | 76 ++-- .../music/downloader/ThumbnailDownloader.java | 65 ++-- .../music/downloader/TrackDownloader.java | 11 +- .../lite/music/{ => interfaces}/Callback.java | 2 +- .../music/interfaces/ITrackDownloader.java | 1 - .../lite/ui/fragments/MediaFragment.java | 10 +- .../lite/ui/fragments/VTSettings.java | 5 +- .../java/ru/vtosters/lite/utils/IOUtils.java | 27 -- .../utils/music/MusicCacheStorageUtils.java | 40 ++ .../vtosters/lite/utils/music/VKXUtils.java | 48 +++ .../core/api/music/CatalogGetAudio.smali | 2 +- .../MusicArtistSelector$Companion.smali | 4 +- .../playlist/PlaylistBottomSheet.smali | 2 +- .../PlaylistBottomSheetClickListener.smali | 4 +- .../MusicTrackBottomSheetClickListener.smali | 2 +- ...kMusicTrackBottomSheetActionsFactory.smali | 4 +- .../music/e/MusicCatalogConfiguration.smali | 4 +- .../vk/music/model/IpcPlayerModelImpl.smali | 4 +- .../com/vk/music/model/PlayerModelImpl.smali | 4 +- .../music/playlist/ModernPlaylistModel.smali | 2 +- .../modern/MusicPlaylistContract4.smali | 2 +- .../common/formatting/PlaylistFormatter.smali | 6 +- .../lite/audio/player/CacheUtils.smali | 4 +- 54 files changed, 953 insertions(+), 1083 deletions(-) delete mode 100644 app/src/main/java/ru/vtosters/hooks/MusicCacheFilesHook.java rename app/src/main/java/ru/vtosters/{lite/music/hook => hooks/music}/MusicArtistSelectorHook.java (89%) rename app/src/main/java/ru/vtosters/{lite/music/hook => hooks/music}/MusicBottomSheetHook.java (87%) create mode 100644 app/src/main/java/ru/vtosters/hooks/music/MusicCacheFilesHook.java rename app/src/main/java/ru/vtosters/{lite/music/hook => hooks/music}/PlaylistFormatterHook.java (97%) rename app/src/main/java/ru/vtosters/{lite/music/catalog/inject => hooks/music/injectors}/CatalogJsonInjector.java (96%) rename app/src/main/java/ru/vtosters/{lite/music/cache => hooks/music}/injectors/PlaylistInjector.java (92%) rename app/src/main/java/ru/vtosters/{lite/music/cache => hooks/music}/injectors/ShuffleInjector.java (93%) rename app/src/main/java/ru/vtosters/{lite/music/cache => hooks/music}/injectors/TracklistInjector.java (96%) delete mode 100644 app/src/main/java/ru/vtosters/lite/music/M3UDownloader.java delete mode 100644 app/src/main/java/ru/vtosters/lite/music/TransportStream.java delete mode 100644 app/src/main/java/ru/vtosters/lite/music/VKM3UParser.java delete mode 100644 app/src/main/java/ru/vtosters/lite/music/cache/CacheDatabaseDelegate.java delete mode 100644 app/src/main/java/ru/vtosters/lite/music/cache/FileCacheImplementation.java create mode 100644 app/src/main/java/ru/vtosters/lite/music/cache/MusicCacheDbDelegate.java create mode 100644 app/src/main/java/ru/vtosters/lite/music/cache/MusicCacheImpl.java create mode 100644 app/src/main/java/ru/vtosters/lite/music/cache/db/MusicCacheDb.java create mode 100644 app/src/main/java/ru/vtosters/lite/music/downloader/M3UDownloader.java rename app/src/main/java/ru/vtosters/lite/music/{ => downloader}/MP3Downloader.java (98%) rename app/src/main/java/ru/vtosters/lite/music/{ => interfaces}/Callback.java (79%) create mode 100644 app/src/main/java/ru/vtosters/lite/utils/music/MusicCacheStorageUtils.java create mode 100644 app/src/main/java/ru/vtosters/lite/utils/music/VKXUtils.java diff --git a/app/src/main/java/bruhcollective/itaysonlab/libvkx/client/LibVKXClient.java b/app/src/main/java/bruhcollective/itaysonlab/libvkx/client/LibVKXClient.java index 2232550d1..bf6d4d773 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/libvkx/client/LibVKXClient.java +++ b/app/src/main/java/bruhcollective/itaysonlab/libvkx/client/LibVKXClient.java @@ -10,9 +10,9 @@ import com.vk.music.common.MusicPlaybackLaunchContext; import java.util.ArrayList; import java.util.List; -import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext; import static ru.vtosters.hooks.other.Preferences.getBoolValue; import static ru.vtosters.hooks.other.Preferences.isValidSignature; +import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext; public class LibVKXClient { @SuppressLint("StaticFieldLeak") @@ -80,11 +80,6 @@ public class LibVKXClient { lambdaplay(list, musicTrack, iLibVkxService, null); } - public static String asId(MusicTrack musicTrack) { - String id = musicTrack.e + "_" + musicTrack.d; - if (musicTrack.O != null && !musicTrack.O.equals("")) { - id += "_" + musicTrack.O; - } - return id; - } + public static String asId(MusicTrack musicTrack) + { return musicTrack.y1(); } } \ No newline at end of file diff --git a/app/src/main/java/com/vk/api/audio/AudioGetLyrics.java b/app/src/main/java/com/vk/api/audio/AudioGetLyrics.java index b6a1d8d6f..9df599cb2 100644 --- a/app/src/main/java/com/vk/api/audio/AudioGetLyrics.java +++ b/app/src/main/java/com/vk/api/audio/AudioGetLyrics.java @@ -6,9 +6,9 @@ import com.vk.api.base.ApiRequest; import com.vk.dto.music.MusicTrack; import com.vk.log.L; import org.json.JSONObject; +import ru.vtosters.hooks.other.Preferences; import ru.vtosters.lite.music.Genius; import ru.vtosters.lite.utils.AndroidUtils; -import ru.vtosters.hooks.other.Preferences; import java.util.ArrayList; diff --git a/app/src/main/java/com/vk/music/view/x/LyricsHolder.java b/app/src/main/java/com/vk/music/view/x/LyricsHolder.java index 3d72f3a4d..7ae66f0c5 100644 --- a/app/src/main/java/com/vk/music/view/x/LyricsHolder.java +++ b/app/src/main/java/com/vk/music/view/x/LyricsHolder.java @@ -23,8 +23,8 @@ import com.vtosters.lite.R; import com.vtosters.lite.ViewUtils; import com.vtosters.lite.ui.MusicErrorViewHelper; import com.vtosters.lite.ui.holder.RecyclerHolder; -import ru.vtosters.lite.music.Genius; import ru.vtosters.hooks.other.Preferences; +import ru.vtosters.lite.music.Genius; import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; diff --git a/app/src/main/java/ru/vtosters/hooks/CatalogsHook.java b/app/src/main/java/ru/vtosters/hooks/CatalogsHook.java index 8368e27e4..fe9dad5b4 100644 --- a/app/src/main/java/ru/vtosters/hooks/CatalogsHook.java +++ b/app/src/main/java/ru/vtosters/hooks/CatalogsHook.java @@ -2,7 +2,7 @@ package ru.vtosters.hooks; import org.json.JSONException; import org.json.JSONObject; -import ru.vtosters.lite.music.catalog.inject.CatalogJsonInjector; +import ru.vtosters.hooks.music.injectors.CatalogJsonInjector; public class CatalogsHook { public static JSONObject injectCatalogGetAudio(JSONObject json) { diff --git a/app/src/main/java/ru/vtosters/hooks/MusicCacheFilesHook.java b/app/src/main/java/ru/vtosters/hooks/MusicCacheFilesHook.java deleted file mode 100644 index f2e97b8d4..000000000 --- a/app/src/main/java/ru/vtosters/hooks/MusicCacheFilesHook.java +++ /dev/null @@ -1,16 +0,0 @@ -package ru.vtosters.hooks; - -import ru.vtosters.lite.music.cache.CacheDatabaseDelegate; -import ru.vtosters.lite.music.cache.FileCacheImplementation; - -import java.io.File; - -public class MusicCacheFilesHook { - public static File getTrackFile(String trackId) { - return new File(FileCacheImplementation.getTrackFolder(trackId), "track.mp3"); - } - - public static boolean isTrackExist(String trackId) { - return CacheDatabaseDelegate.isCached(trackId); - } -} diff --git a/app/src/main/java/ru/vtosters/lite/music/hook/MusicArtistSelectorHook.java b/app/src/main/java/ru/vtosters/hooks/music/MusicArtistSelectorHook.java similarity index 89% rename from app/src/main/java/ru/vtosters/lite/music/hook/MusicArtistSelectorHook.java rename to app/src/main/java/ru/vtosters/hooks/music/MusicArtistSelectorHook.java index 6532036ec..33863ceab 100644 --- a/app/src/main/java/ru/vtosters/lite/music/hook/MusicArtistSelectorHook.java +++ b/app/src/main/java/ru/vtosters/hooks/music/MusicArtistSelectorHook.java @@ -1,4 +1,4 @@ -package ru.vtosters.lite.music.hook; +package ru.vtosters.hooks.music; import android.content.Context; import com.vk.music.fragment.MusicFragment; diff --git a/app/src/main/java/ru/vtosters/lite/music/hook/MusicBottomSheetHook.java b/app/src/main/java/ru/vtosters/hooks/music/MusicBottomSheetHook.java similarity index 87% rename from app/src/main/java/ru/vtosters/lite/music/hook/MusicBottomSheetHook.java rename to app/src/main/java/ru/vtosters/hooks/music/MusicBottomSheetHook.java index 2efc34865..4dfa46707 100644 --- a/app/src/main/java/ru/vtosters/lite/music/hook/MusicBottomSheetHook.java +++ b/app/src/main/java/ru/vtosters/hooks/music/MusicBottomSheetHook.java @@ -1,4 +1,4 @@ -package ru.vtosters.lite.music.hook; +package ru.vtosters.hooks.music; import android.os.RemoteException; import bruhcollective.itaysonlab.libvkx.client.LibVKXClientImpl; @@ -8,45 +8,33 @@ import com.vk.music.bottomsheets.a.MusicAction; import com.vk.music.common.MusicPlaybackLaunchContext; import com.vtosters.lite.R; import ru.vtosters.lite.downloaders.AudioDownloader; -import ru.vtosters.lite.music.cache.CacheDatabaseDelegate; +import ru.vtosters.lite.music.cache.MusicCacheImpl; +import ru.vtosters.lite.utils.music.VKXUtils; import ru.vtosters.lite.utils.AndroidUtils; import java.util.ArrayList; import static bruhcollective.itaysonlab.libvkx.client.LibVKXClient.*; import static ru.vtosters.hooks.other.Preferences.milkshake; -import static ru.vtosters.lite.music.cache.CacheDatabaseDelegate.isCached; -import static ru.vtosters.lite.music.cache.CacheDatabaseDelegate.isVkxCached; import static ru.vtosters.lite.utils.NetworkUtils.isNetworkConnected; public class MusicBottomSheetHook { - public static ArrayList hook(ArrayList actions, MusicTrack musicTrack) { + public static ArrayList hook(final ArrayList actions,final MusicTrack musicTrack) + { if (musicTrack.F1()) return actions; - - var trackid = asId(musicTrack); - - if (isVkxInstalled()) { - actions.add(getPlayInVKXAction()); + final var trackId=asId(musicTrack); + if (isVkxInstalled())actions.add(getPlayInVKXAction()); + if (isIntegrationEnabled()) + { + if(VKXUtils.isVkxCached(trackId))actions.add(getRemoveCacheTrackVkxAction()); + else if(isNetworkConnected())actions.add(addToCacheTrackVkxAction()); } - - if (isIntegrationEnabled()) { - if (isVkxCached(trackid)) { - actions.add(getRemoveCacheTrackVkxAction()); - } else if (isNetworkConnected()) { - actions.add(addToCacheTrackVkxAction()); - } - } else { - if (isCached(trackid)) { - actions.add(getRemoveCacheTrackAction()); - } else if (isNetworkConnected()) { - actions.add(addToCacheTrackAction()); - } + else + { + if(MusicCacheImpl.isCachedTrack(trackId))actions.add(getRemoveCacheTrackAction()); + else if(isNetworkConnected())actions.add(addToCacheTrackAction()); } - - if (isNetworkConnected()) { - actions.add(downloadAsMp3Action()); - } - + if (isNetworkConnected())actions.add(downloadAsMp3Action()); return actions; } @@ -54,9 +42,7 @@ public class MusicBottomSheetHook { public static ArrayList hookDownloadBTN(ArrayList actions, MusicTrack musicTrack) { if (musicTrack.F1()) return actions; - var trackid = asId(musicTrack); - - if (isCached(trackid)) { + if (MusicCacheImpl.isCachedTrack(musicTrack.y1())) { actions.add(new MusicAction( R.id.music_action_toggle_download, milkshake() ? R.drawable.ic_delete_outline_28 : R.drawable.ic_delete_24, @@ -85,7 +71,7 @@ public class MusicBottomSheetHook { if (isVkxInstalled()) actions.add(getPlayInVKXAction()); if (isIntegrationEnabled()) { - if (isVkxCached(playlist.a, playlist.b)) { + if (VKXUtils.isVkxCached(playlist.a, playlist.b)) { actions.add(getRemoveCacheTrackVkxAction()); } else if (isNetworkConnected()) { actions.add(addToCacheTrackVkxAction()); @@ -124,7 +110,7 @@ public class MusicBottomSheetHook { } if (actionId == R.id.remove_from_cache) { - CacheDatabaseDelegate.removeTrackFromCache(asId(track)); + MusicCacheImpl.removeTrack(asId(track)); AndroidUtils.sendToast(AndroidUtils.getString("audio_deleted_from_cache")); return true; } else if (actionId == R.id.add_to_cache) { diff --git a/app/src/main/java/ru/vtosters/hooks/music/MusicCacheFilesHook.java b/app/src/main/java/ru/vtosters/hooks/music/MusicCacheFilesHook.java new file mode 100644 index 000000000..d0dc4671c --- /dev/null +++ b/app/src/main/java/ru/vtosters/hooks/music/MusicCacheFilesHook.java @@ -0,0 +1,14 @@ +package ru.vtosters.hooks.music; + +import ru.vtosters.lite.music.cache.MusicCacheImpl; +import ru.vtosters.lite.utils.music.MusicCacheStorageUtils; + +import java.io.File; + +public class MusicCacheFilesHook { + public static File getTrackFile(String trackId) + { return MusicCacheStorageUtils.getTrackFile(trackId); } + + public static boolean isTrackExist(String trackId) + { return MusicCacheImpl.isCachedTrack(trackId); } +} diff --git a/app/src/main/java/ru/vtosters/lite/music/hook/PlaylistFormatterHook.java b/app/src/main/java/ru/vtosters/hooks/music/PlaylistFormatterHook.java similarity index 97% rename from app/src/main/java/ru/vtosters/lite/music/hook/PlaylistFormatterHook.java rename to app/src/main/java/ru/vtosters/hooks/music/PlaylistFormatterHook.java index 033af0d9e..774f878ef 100644 --- a/app/src/main/java/ru/vtosters/lite/music/hook/PlaylistFormatterHook.java +++ b/app/src/main/java/ru/vtosters/hooks/music/PlaylistFormatterHook.java @@ -1,4 +1,4 @@ -package ru.vtosters.lite.music.hook; +package ru.vtosters.hooks.music; import android.content.Context; import androidx.annotation.AttrRes; diff --git a/app/src/main/java/ru/vtosters/lite/music/catalog/inject/CatalogJsonInjector.java b/app/src/main/java/ru/vtosters/hooks/music/injectors/CatalogJsonInjector.java similarity index 96% rename from app/src/main/java/ru/vtosters/lite/music/catalog/inject/CatalogJsonInjector.java rename to app/src/main/java/ru/vtosters/hooks/music/injectors/CatalogJsonInjector.java index 328542dce..1a47d3e68 100644 --- a/app/src/main/java/ru/vtosters/lite/music/catalog/inject/CatalogJsonInjector.java +++ b/app/src/main/java/ru/vtosters/hooks/music/injectors/CatalogJsonInjector.java @@ -1,4 +1,4 @@ -package ru.vtosters.lite.music.catalog.inject; +package ru.vtosters.hooks.music.injectors; import android.util.Log; import bruhcollective.itaysonlab.libvkx.client.LibVKXClient; @@ -12,7 +12,7 @@ import org.json.JSONException; import org.json.JSONObject; import ru.vtosters.hooks.other.Preferences; import ru.vtosters.lite.di.singleton.VtOkHttpClient; -import ru.vtosters.lite.music.cache.CacheDatabaseDelegate; +import ru.vtosters.lite.music.cache.MusicCacheImpl; import ru.vtosters.lite.utils.AccountManagerUtils; import ru.vtosters.lite.utils.AndroidUtils; @@ -48,7 +48,7 @@ public class CatalogJsonInjector { setDefaultAudioPage(oldItems, catalog); } - if (CacheDatabaseDelegate.hasTracks() && !LibVKXClient.isIntegrationEnabled()) { // inj in playlist list + if (!MusicCacheImpl.isEmpty() && !LibVKXClient.isIntegrationEnabled()) { // inj in playlist list var noPlaylists = !json.has("playlists"); if (noPlaylists) { @@ -91,7 +91,7 @@ public class CatalogJsonInjector { fixDailyMix(blocks); - if (!isUsersCatalog || !CacheDatabaseDelegate.hasTracks() || LibVKXClient.isIntegrationEnabled()) { + if (!isUsersCatalog || MusicCacheImpl.isEmpty() || LibVKXClient.isIntegrationEnabled()) { return; // early return if not users catalog or no tracks or integration enabled } diff --git a/app/src/main/java/ru/vtosters/lite/music/cache/injectors/PlaylistInjector.java b/app/src/main/java/ru/vtosters/hooks/music/injectors/PlaylistInjector.java similarity index 92% rename from app/src/main/java/ru/vtosters/lite/music/cache/injectors/PlaylistInjector.java rename to app/src/main/java/ru/vtosters/hooks/music/injectors/PlaylistInjector.java index a41c83e0c..31654ef2e 100644 --- a/app/src/main/java/ru/vtosters/lite/music/cache/injectors/PlaylistInjector.java +++ b/app/src/main/java/ru/vtosters/hooks/music/injectors/PlaylistInjector.java @@ -1,4 +1,4 @@ -package ru.vtosters.lite.music.cache.injectors; +package ru.vtosters.hooks.music.injectors; import android.os.RemoteException; import android.text.TextUtils; @@ -12,7 +12,7 @@ import io.reactivex.ObservableOnSubscribe; import org.json.JSONException; import org.json.JSONObject; import ru.vtosters.lite.downloaders.AudioDownloader; -import ru.vtosters.lite.music.cache.CacheDatabaseDelegate; +import ru.vtosters.lite.music.cache.MusicCacheImpl; import ru.vtosters.lite.music.cache.helpers.PlaylistHelper; import ru.vtosters.lite.music.cache.helpers.TracklistHelper; @@ -25,13 +25,11 @@ public class PlaylistInjector { public final static String CHANNEL_NAME = "VTCH"; private static final ExecutorService executor = Executors.newCachedThreadPool(); - public static void injectDownloadPlaylist(Playlist playlist) { - executor.submit(() -> AudioDownloader.cachePlaylist(playlist)); - } + public static void injectDownloadPlaylist(Playlist playlist) + { executor.submit(() -> AudioDownloader.cachePlaylist(playlist)); } - public static boolean eligibleForOfflineCaching() { - return CacheDatabaseDelegate.hasTracks(); - } + public static boolean eligibleForOfflineCaching() + { return !MusicCacheImpl.isEmpty(); } public static Observable injectGetPlaylist(AudioGetPlaylist audioGetPlaylist) { try { diff --git a/app/src/main/java/ru/vtosters/lite/music/cache/injectors/ShuffleInjector.java b/app/src/main/java/ru/vtosters/hooks/music/injectors/ShuffleInjector.java similarity index 93% rename from app/src/main/java/ru/vtosters/lite/music/cache/injectors/ShuffleInjector.java rename to app/src/main/java/ru/vtosters/hooks/music/injectors/ShuffleInjector.java index 1460876c5..32d506aca 100644 --- a/app/src/main/java/ru/vtosters/lite/music/cache/injectors/ShuffleInjector.java +++ b/app/src/main/java/ru/vtosters/hooks/music/injectors/ShuffleInjector.java @@ -1,4 +1,4 @@ -package ru.vtosters.lite.music.cache.injectors; +package ru.vtosters.hooks.music.injectors; import com.vk.dto.music.MusicTrack; import com.vk.music.model.IpcPlayerModelImpl; diff --git a/app/src/main/java/ru/vtosters/lite/music/cache/injectors/TracklistInjector.java b/app/src/main/java/ru/vtosters/hooks/music/injectors/TracklistInjector.java similarity index 96% rename from app/src/main/java/ru/vtosters/lite/music/cache/injectors/TracklistInjector.java rename to app/src/main/java/ru/vtosters/hooks/music/injectors/TracklistInjector.java index 037986b71..dbf7f7ba8 100644 --- a/app/src/main/java/ru/vtosters/lite/music/cache/injectors/TracklistInjector.java +++ b/app/src/main/java/ru/vtosters/hooks/music/injectors/TracklistInjector.java @@ -1,4 +1,4 @@ -package ru.vtosters.lite.music.cache.injectors; +package ru.vtosters.hooks.music.injectors; import android.util.Log; import bruhcollective.itaysonlab.libvkx.client.LibVKXClient; @@ -11,7 +11,7 @@ import io.reactivex.ObservableOnSubscribe; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import ru.vtosters.lite.music.cache.CacheDatabaseDelegate; +import ru.vtosters.lite.music.cache.MusicCacheImpl; import ru.vtosters.lite.music.cache.helpers.PlaylistHelper; import ru.vtosters.lite.music.cache.helpers.TracklistHelper; import ru.vtosters.lite.utils.AccountManagerUtils; @@ -56,7 +56,7 @@ public class TracklistInjector { } }); } else { - if (!CacheDatabaseDelegate.hasTracks()) { + if (MusicCacheImpl.isEmpty()) { observableEmitter.b(parser.c(getEmptyCatalog())); } else { observableEmitter.b(parser.c(createVirtualCatalog(TracklistHelper.getTracks()))); @@ -135,7 +135,7 @@ public class TracklistInjector { button.put("action", action); button.put("block_id", "shuffle"); - button.put("ref_items_count", CacheDatabaseDelegate.getTrackCount()); + button.put("ref_items_count", MusicCacheImpl.getTracksCount()); button.put("ref_layout_name", "list"); button.put("ref_data_type", "music_audios"); @@ -158,7 +158,7 @@ public class TracklistInjector { JSONArray blocks = firstSection.getJSONArray("blocks"); - if (CacheDatabaseDelegate.hasTracks() && !LibVKXClient.isIntegrationEnabled()) { // inj in playlist list + if (!MusicCacheImpl.isEmpty() && !LibVKXClient.isIntegrationEnabled()) { // inj in playlist list boolean noPlaylists = hasNoPlaylists(blocks); if (noPlaylists) { diff --git a/app/src/main/java/ru/vtosters/lite/downloaders/AudioDownloader.java b/app/src/main/java/ru/vtosters/lite/downloaders/AudioDownloader.java index 3892d7fc7..8c1b707e8 100644 --- a/app/src/main/java/ru/vtosters/lite/downloaders/AudioDownloader.java +++ b/app/src/main/java/ru/vtosters/lite/downloaders/AudioDownloader.java @@ -1,24 +1,24 @@ package ru.vtosters.lite.downloaders; import android.os.Environment; +import android.util.Log; import bruhcollective.itaysonlab.libvkx.client.LibVKXClient; import com.vk.core.util.ToastUtils; import com.vk.dto.music.MusicTrack; import com.vk.dto.music.Playlist; import com.vtosters.lite.R; -import ru.vtosters.hooks.MusicCacheFilesHook; -import ru.vtosters.lite.music.cache.CacheDatabaseDelegate; +import ru.vtosters.hooks.music.MusicCacheFilesHook; +import ru.vtosters.lite.music.cache.MusicCacheImpl; import ru.vtosters.lite.music.callback.MusicCallbackBuilder; import ru.vtosters.lite.music.converter.playlist.PlaylistConverter; -import ru.vtosters.lite.music.downloader.AudioGet; -import ru.vtosters.lite.music.downloader.PlaylistDownloader; -import ru.vtosters.lite.music.downloader.TrackDownloader; +import ru.vtosters.lite.music.downloader.*; import ru.vtosters.lite.music.notification.MusicNotificationBuilder; import ru.vtosters.lite.utils.AccountManagerUtils; import ru.vtosters.lite.utils.AndroidUtils; import ru.vtosters.lite.utils.IOUtils; import java.io.File; +import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -33,7 +33,8 @@ import static ru.vtosters.lite.utils.AndroidUtils.getString; public class AudioDownloader { public static final ExecutorService executor = Executors.newCachedThreadPool(); - public static void downloadPlaylist(Playlist playlist) { + public static void downloadPlaylist(Playlist playlist) + { var tracks = PlaylistConverter.getPlaylist(playlist); var playlistName = IOUtils.getValidFileName(playlist.g); @@ -53,15 +54,14 @@ public class AudioDownloader { } - public static void downloadAudio(MusicTrack track) { - downloadM3U8(track, false); - } + public static void downloadAudio(MusicTrack track) + { downloadM3U8(track,false); } public static void cacheTrack(MusicTrack track) { - var trackId = track.y1(); + var trackId = LibVKXClient.asId(track); - if (CacheDatabaseDelegate.isCached(trackId)) { - CacheDatabaseDelegate.removeTrackFromCache(LibVKXClient.asId(track)); + if (MusicCacheImpl.isCachedTrack(trackId)) { + MusicCacheImpl.removeTrack(trackId); AndroidUtils.sendToast(AndroidUtils.getString("audio_deleted_from_cache")); return; } diff --git a/app/src/main/java/ru/vtosters/lite/music/LastFMScrobbler.java b/app/src/main/java/ru/vtosters/lite/music/LastFMScrobbler.java index 5f33f274c..3c36fcd3f 100644 --- a/app/src/main/java/ru/vtosters/lite/music/LastFMScrobbler.java +++ b/app/src/main/java/ru/vtosters/lite/music/LastFMScrobbler.java @@ -14,7 +14,7 @@ import org.json.JSONObject; import ru.vtosters.hooks.other.Preferences; import ru.vtosters.lite.di.singleton.VtOkHttpClient; import ru.vtosters.lite.downloaders.AudioDownloader; -import ru.vtosters.lite.music.cache.CacheDatabaseDelegate; +import ru.vtosters.lite.music.cache.MusicCacheImpl; import ru.vtosters.lite.utils.AndroidUtils; import java.io.IOException; @@ -47,7 +47,7 @@ public class LastFMScrobbler { return; } - if (Preferences.autocache() && !CacheDatabaseDelegate.isCached(uid)) { + if (Preferences.autocache() && !MusicCacheImpl.isCachedTrack(uid)) { if (LibVKXClient.isIntegrationEnabled()) { LibVKXClient.getInstance().runOnService(service -> { try { diff --git a/app/src/main/java/ru/vtosters/lite/music/M3UDownloader.java b/app/src/main/java/ru/vtosters/lite/music/M3UDownloader.java deleted file mode 100644 index 79b4bca4b..000000000 --- a/app/src/main/java/ru/vtosters/lite/music/M3UDownloader.java +++ /dev/null @@ -1,158 +0,0 @@ -package ru.vtosters.lite.music; - -import android.util.Log; -import com.vk.dto.music.MusicTrack; -import java8.util.concurrent.CompletableFuture; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import ru.vtosters.lite.di.singleton.VtOkHttpClient; -import ru.vtosters.lite.music.cache.CacheDatabaseDelegate; -import ru.vtosters.lite.music.converter.ts.FFMpeg; -import ru.vtosters.lite.music.converter.ts.TSMerger; -import ru.vtosters.lite.music.downloader.ThumbnailDownloader; -import ru.vtosters.lite.music.interfaces.ITrackDownloader; -import ru.vtosters.lite.utils.AndroidUtils; -import ru.vtosters.lite.utils.IOUtils; - -import javax.crypto.NoSuchPaddingException; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; - -public class M3UDownloader implements ITrackDownloader { - private static final OkHttpClient client = VtOkHttpClient.getInstance(); - - public static M3UDownloader getInstance() { - return Holder.INSTANCE; - } - - public static String getTitle(MusicTrack track) { - String title = null; - - if (track.f != null) { - title = track.f; - if (!track.g.isEmpty()) { - title += " (" + track.g + ")"; - } - } - - return title; - } - - public void downloadTrack(MusicTrack track, File outDir, Callback callback, boolean cache) { - if (track.D.isEmpty()) { - Log.d("TrackDownloader", "link error: " + track.y1() + ", title: " + M3UDownloader.getTitle(track)); - callback.onFailure(); - return; - } - - try { - Request request = new Request.a().b(track.D).a(); - String response = client.a(request).execute().a().g(); - parse(response, outDir, callback, track, cache); - } catch (IOException e) { - e.printStackTrace(); - callback.onFailure(); - } - } - - private void parse(String payload, File outDir, Callback callback, MusicTrack track, boolean cache) throws UnsupportedEncodingException { - if (cache) downloadThumbnails(track); - - VKM3UParser parser = new VKM3UParser(track.D.substring(0, track.D.lastIndexOf("/") + 1), payload); - AtomicInteger progress = new AtomicInteger(0); - List> futures = createFutures(parser, progress, callback, track); - - var resultTs = getResultTsFile(track); - var resultMp3 = getResultMp3File(outDir, cache, track); - - callback.onProgress(5); - var future = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); - future.join(); - future.exceptionally(throwable -> { - callback.onFailure(); - return null; - }).thenApply(v -> TSMerger.merge(getTsesDir(track), resultTs)) - .thenApply(mergeResult -> mergeResult && FFMpeg.convert(resultTs, resultMp3.getAbsolutePath(), track)) - .thenApply(convertResult -> { - if (convertResult) - callback.onProgress(10 + Math.round(80.0f * progress.addAndGet(1) / parser.mTransportStreams.size())); - else callback.onFailure(); - return convertResult; - }).thenRun(() -> IOUtils.deleteRecursive(getTsesDir(track))).thenRun(() -> { - callback.onSuccess(); - if (cache) CacheDatabaseDelegate.insertTrack(track); - }); - } - - private void downloadThumbnails(MusicTrack track) { - try { - ThumbnailDownloader.downloadThumbnails(track); - } catch (IOException e) { - e.printStackTrace(); - } - } - - private List> createFutures(VKM3UParser parser, AtomicInteger progress, Callback callback, MusicTrack track) { - List> futures = new ArrayList<>(parser.mTransportStreams.size()); - - var tses = parser.mTransportStreams; - for (var i = 0; i < tses.size(); ++i) { - final var ts = tses.get(i); - final var filename = i + ".ts"; - futures.add(CompletableFuture.runAsync(() -> processTransportStream(parser, ts, filename, progress, tses.size(), callback, track))); - } - return futures; - } - - private void processTransportStream(VKM3UParser parser, TransportStream ts, String filename, AtomicInteger progress, int size, Callback callback, MusicTrack track) { - try { - InputStream mediaIs = IOUtils.openStream(ts.getMediaSegmentUri()); - byte[] content; - if (ts.needDecoding()) { - InputStream keyPubIs = IOUtils.openStream(ts.getKeyURL()); - content = IOUtils.decodeStream(mediaIs, IOUtils.readFully(keyPubIs)); - keyPubIs.close(); - } else { - content = IOUtils.readFully(mediaIs); - } - File tsDump = new File(getTsesDir(track), filename); - IOUtils.writeToFile(tsDump, content); - callback.onProgress(10 + Math.round(80.0f * progress.addAndGet(1) / size)); - callback.onSizeReceived((long) content.length * size, parser.mHeapSize); - mediaIs.close(); - } catch (IOException | NoSuchPaddingException | NoSuchAlgorithmException | - InvalidAlgorithmParameterException | InvalidKeyException e) { - callback.onFailure(); - throw new RuntimeException(e); - } - } - - private File getTsesDir(MusicTrack track) { - var filesDir = AndroidUtils.getGlobalContext().getFilesDir(); - var tsesDir = new File(filesDir, String.valueOf(track.d)); - tsesDir.mkdirs(); - return tsesDir; - } - - private File getResultTsFile(MusicTrack track) { - return new File(getTsesDir(track), "result.ts"); - } - - private File getResultMp3File(File outDir, boolean cache, MusicTrack track) { - var fileName = IOUtils.getValidFileName((cache ? "track" : track.C + " - " + getTitle(track)) + ".mp3"); - return new File(outDir, fileName); - } - - // Initialization-on-demand - private static class Holder { - private static final M3UDownloader INSTANCE = new M3UDownloader(); - } -} diff --git a/app/src/main/java/ru/vtosters/lite/music/TransportStream.java b/app/src/main/java/ru/vtosters/lite/music/TransportStream.java deleted file mode 100644 index 2bc3609f8..000000000 --- a/app/src/main/java/ru/vtosters/lite/music/TransportStream.java +++ /dev/null @@ -1,36 +0,0 @@ -package ru.vtosters.lite.music; - -import android.text.TextUtils; - -public class TransportStream { - - private final String mMediaSegmentUri; - private String mKeyUri; - private String mMediaSegmentName; - - public TransportStream(String baseURI, String mediaSegmentName) { - this.mMediaSegmentUri = baseURI + mediaSegmentName; - } - - public TransportStream(String keyURI, String baseURI, String mediaSegmentName) { - this.mKeyUri = keyURI; - this.mMediaSegmentUri = baseURI + mediaSegmentName; - this.mMediaSegmentName = mediaSegmentName; - } - - public boolean needDecoding() { - return !TextUtils.isEmpty(mKeyUri); - } - - public String getKeyURL() { - return mKeyUri; - } - - public String getMediaSegmentUri() { - return this.mMediaSegmentUri; - } - - public String getMediaSegmentName() { - return this.mMediaSegmentName; - } -} diff --git a/app/src/main/java/ru/vtosters/lite/music/VKM3UParser.java b/app/src/main/java/ru/vtosters/lite/music/VKM3UParser.java deleted file mode 100644 index a95b583a1..000000000 --- a/app/src/main/java/ru/vtosters/lite/music/VKM3UParser.java +++ /dev/null @@ -1,126 +0,0 @@ -package ru.vtosters.lite.music; - -import android.text.TextUtils; - -import java.util.ArrayList; -import java.util.List; -import java.util.Scanner; - -/** - * The parser for extended M3U playlist format used by VK. - * - * @see M3U - **/ -public class VKM3UParser { - //Playlist types - static public int PLAYLIST_TYPE_VOD = 0; - static public int PLAYLIST_TYPE_EVENT = 1; - private final String mData; - //region Extended M3U global extensions - public List mTransportStreams = new ArrayList<>(); - /** - * @see EXT-X-VERSION - **/ - public int mVersion = -1; - /** - * The EXT-X-ALLOW-CACHE tag is used in protocol version 6 or lower. - * If the protocol version is upgraded by VK, we will not support that. - **/ - public boolean mAllowCache = false; - /** - * @see EXT-X-TARGETDURATION - **/ - public int mTargetDuration = -1; - /** - * @see EXT-X-MEDIA-SEQUENCE - **/ - public int mMediaSequence = -1; - /** - * @see EXT-X-DISCONTINUITY-SEQUENCE - **/ - public int mDiscontinuitySequence = -1; - //endregion - /** - * @see EXT-X-PLAYLIST-TYPE - **/ - public int mPlaylistType = -1; - /** - * @see EXT-X-I-FRAMES-ONLY - **/ - public boolean mIFramesOnly = false; - public long mHeapSize = 0; - - public VKM3UParser(String baseUri, String data) { - mData = data; - init(baseUri); - } - - public static boolean isTS(String str) { - return str.matches(".+\\.ts.*"); - } - - private void init(String baseUri) { - if (TextUtils.isEmpty(mData)) throw new NullPointerException("mData==null"); - Scanner scanner = new Scanner(mData); - String line = scanner.nextLine(); - if (!"#EXTM3U".equals(line)) - throw new IllegalStateException(String.format("Unknown initial M3U tag: %s", line)); - boolean aes128 = false; - String keyUri = ""; - while (scanner.hasNextLine()) { - line = scanner.nextLine(); - if ('#' == line.charAt(0)) { - parseTag(line); - if (line.startsWith("#EXT-X-KEY:") && line.contains("METHOD=AES-128")) { - aes128 = true; - keyUri = getKeyUri(line, baseUri); - } - } else if (isTS(line)) { - addTransportStream(aes128, keyUri, baseUri, line); - aes128 = false; - } else { - throw new RuntimeException(String.format("Failed to parse: %s", line)); - } - mHeapSize += line.getBytes().length; - } - } - - private void parseTag(String line) { - if (line.startsWith("#EXT-X-VERSION:")) - mVersion = Integer.parseInt(line.substring(15)); - else if (line.startsWith("#EXT-X-ALLOW-CACHE:")) - mAllowCache = "YES".equalsIgnoreCase(line.substring(19)); - else if (line.startsWith("#EXT-X-TARGETDURATION:")) - mTargetDuration = Integer.parseInt(line.substring(22)); - else if (line.startsWith("#EXT-X-MEDIA-SEQUENCE:")) - mMediaSequence = Integer.parseInt(line.substring(22)); - else if (line.startsWith("#EXT-X-DISCONTINUITY-SEQUENCE:")) - mDiscontinuitySequence = Integer.parseInt(line.substring(30)); - else if (line.startsWith("#EXT-X-PLAYLIST-TYPE:")) - parsePlaylistType(line.substring(21)); - else if (line.startsWith("#EXT-X-I-FRAMES-ONLY")) - mIFramesOnly = true; - } - - private void parsePlaylistType(String type) { - switch (type) { - case "VOD" -> mPlaylistType = PLAYLIST_TYPE_VOD; - case "EVENT" -> mPlaylistType = PLAYLIST_TYPE_EVENT; - default -> throw new IllegalStateException(String.format("Unknown playlist type: %s", type)); - } - } - - private String getKeyUri(String line, String baseUri) { - String substr = line.substring(11); - substr = substr.substring(substr.indexOf('"') + 1, substr.lastIndexOf('"')); - //TODO: supposed endpoint - return substr.startsWith("http") ? substr : baseUri + substr; - } - - private void addTransportStream(boolean aes128, String keyUri, String baseUri, String line) { - TransportStream ts = aes128 - ? new TransportStream(keyUri, baseUri, line) - : new TransportStream(baseUri, line); - mTransportStreams.add(ts); - } -} diff --git a/app/src/main/java/ru/vtosters/lite/music/cache/CacheDatabaseDelegate.java b/app/src/main/java/ru/vtosters/lite/music/cache/CacheDatabaseDelegate.java deleted file mode 100644 index 344a12fc9..000000000 --- a/app/src/main/java/ru/vtosters/lite/music/cache/CacheDatabaseDelegate.java +++ /dev/null @@ -1,343 +0,0 @@ -package ru.vtosters.lite.music.cache; - -import android.content.Context; -import android.database.Cursor; -import android.database.DatabaseUtils; -import android.database.SQLException; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; -import android.os.RemoteException; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import bruhcollective.itaysonlab.libvkx.ILibVkxService; -import bruhcollective.itaysonlab.libvkx.client.LibVKXClient; -import bruhcollective.itaysonlab.libvkx.client.LibVKXClientImpl; -import com.vk.core.util.ToastUtils; -import com.vk.dto.music.MusicTrack; -import com.vtosters.lite.R; -import org.json.JSONObject; -import ru.vtosters.lite.utils.AndroidUtils; - -import java.net.URLDecoder; -import java.net.URLEncoder; -import java.util.ArrayList; -import java.util.List; - -import static ru.vtosters.lite.music.cache.FileCacheImplementation.getCacheDir; -import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext; -import static ru.vtosters.lite.utils.AndroidUtils.sendToast; -import static ru.vtosters.lite.utils.IOUtils.deleteRecursive; - -public class CacheDatabaseDelegate { - private static final int DB_VERSION = 0x1; - private static final String DB_NAME = "vt_lite_cache.db"; - private static final String TABLE_NAME = "tracks"; - private static final String COLUMN_ID = "id"; - private static final String COLUMN_TRACK_ID = "trackId"; - private static final String COLUMN_ALBUM_ID = "albumId"; - private static final String COLUMN_TITLE = "title"; - private static final String COLUMN_SUBTITLE = "subtitle"; - private static final String COLUMN_ARTIST = "artist"; - private static final String COLUMN_ALBUM_TITLE = "albumTitle"; - private static final String COLUMN_EXPLICIT = "explicit"; - private static final String COLUMN_HAS_ARTWORK = "hasArtwork"; - private static final String COLUMN_DURATION = "duration"; - private static DbHelper helperInstance; - - protected static DbHelper getHelper() { - if (helperInstance == null) helperInstance = new DbHelper(getGlobalContext()); - return helperInstance; - } - - // You MUST call this method ONLY AFTER TRACK IS DOWNLOADED! - public static void insertTrack(MusicTrack track) { - try { - var json = track.J(); - var albumNode = json.optJSONObject("album"); - - var hasArtwork = false; - var albumId = -1; - var albumName = ""; - - if (albumNode != null) { - hasArtwork = albumNode.has("thumb"); - albumId = albumNode.getInt("id"); - albumName = albumNode.getString("title"); - } - - var formula = String.format( - "INSERT INTO %s (%s, %s, %s, %s, %s, %s, %s, %s, %s) VALUES ('%s', %s, '%s', '%s', '%s', '%s', %s, %s, %s)", - TABLE_NAME, - COLUMN_TRACK_ID, COLUMN_ALBUM_ID, COLUMN_TITLE, COLUMN_SUBTITLE, COLUMN_ARTIST, COLUMN_ALBUM_TITLE, COLUMN_EXPLICIT, COLUMN_DURATION, COLUMN_HAS_ARTWORK, - track.y1(), - albumId, - URLEncoder.encode(json.getString("title"), "UTF-8"), - URLEncoder.encode(json.optString("subtitle"), "UTF-8"), - URLEncoder.encode(json.getString("artist"), "UTF-8"), - URLEncoder.encode(albumName, "UTF-8"), - json.optInt("is_explicit"), - json.getInt("duration"), - hasArtwork ? 1 : 0 - ); - getHelper().getReadableDatabase().execSQL(formula); - } catch (Exception e) { - e.printStackTrace(); - ToastUtils.a(AndroidUtils.getString(R.string.track_bd_insert_err) + " [" + e.getMessage() + "]", true); - } - } - - public static void insertTracks(List tracks) { - for (var track : tracks) insertTrack(track); - } - - public static void clear() { - getHelper().getReadableDatabase().execSQL(String.format("DELETE FROM %s", TABLE_NAME)); - deleteRecursive(getCacheDir()); - sendToast(AndroidUtils.getString(R.string.cached_tracks_deleted)); - } - - @NonNull - private static JSONObject reconstructFromDb(Cursor cursor) { - try { - var trackId = cursor.getString(getColumnIndex(cursor, COLUMN_TRACK_ID)); - var splits = trackId.split("_"); - var id = Integer.parseInt(splits[1]); - var ownerId = Integer.parseInt(splits[0]); - - return new JSONObject() - .put("url", FileCacheImplementation.getServerPath()) - .put("id", id) - .put("owner_id", ownerId) - .put("title", URLDecoder.decode(cursor.getString(getColumnIndex(cursor, COLUMN_TITLE)), "UTF-8")) - .put("subtitle", URLDecoder.decode(cursor.getString(getColumnIndex(cursor, COLUMN_SUBTITLE)), "UTF-8")) - .put("artist", URLDecoder.decode(cursor.getString(getColumnIndex(cursor, COLUMN_ARTIST)), "UTF-8")) - .put("duration", cursor.getInt(getColumnIndex(cursor, COLUMN_DURATION))) - .put("is_explicit", cursor.getInt(getColumnIndex(cursor, COLUMN_EXPLICIT))) - .put("access_key", 2281488) - .putOpt("album", reconstructAlbumFromDb(cursor, trackId)); - } catch (Exception e) { - e.printStackTrace(); - return new JSONObject(); - } - } - - @Nullable - private static JSONObject reconstructAlbumFromDb(Cursor cursor, String trackId) { - try { - if (!FileCacheImplementation.getTrackThumbnail(trackId, 300).exists() - && !FileCacheImplementation.getTrackThumbnail(trackId, 600).exists() - && !FileCacheImplementation.getTrackThumbnail(trackId, 1200).exists() - ) { - var ownerId = Integer.parseInt(trackId.split("_")[1]); - return new JSONObject() - .put("id", cursor.getInt(getColumnIndex(cursor, COLUMN_ALBUM_ID))) - .put("owner_id", ownerId) - .put("title", URLDecoder.decode(cursor.getString(getColumnIndex(cursor, COLUMN_ALBUM_TITLE)), "UTF-8")) - .putOpt("thumb", reconstructAlbumThumbFromDb(cursor, trackId)); - } - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } - - @Nullable - private static JSONObject reconstructAlbumThumbFromDb(Cursor cursor, String trackId) { - try { - if (cursor.getInt(getColumnIndex(cursor, COLUMN_HAS_ARTWORK)) != 0) - new JSONObject() - .put("height", 600) - .put("width", 600) - .putOpt("photo_300", FileCacheImplementation.getFileUri(FileCacheImplementation.getTrackThumbnail(trackId, 300))) - .putOpt("photo_600", FileCacheImplementation.getFileUri(FileCacheImplementation.getTrackThumbnail(trackId, 600))) - .putOpt("photo_1200", FileCacheImplementation.getFileUri(FileCacheImplementation.getTrackThumbnail(trackId, 1200))); - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } - - private static int getColumnIndex(Cursor cursor, String name) { - return cursor.getColumnIndexOrThrow(name); - } - - public static boolean hasTracks() { - if (LibVKXClient.isIntegrationEnabled()) { - return true; - } - - var formula = String.format( - "SELECT COUNT(%s) FROM %s", - COLUMN_ID, - TABLE_NAME - ); - var cursor = getHelper().getReadableDatabase().rawQuery(formula, null); - if (cursor != null) { - cursor.moveToFirst(); - int count = cursor.getInt(0); - cursor.close(); - return count > 0; - } - return false; - } - - public static List getTracks() { - return getTracks(String.format( - "SELECT * FROM %s", - TABLE_NAME - )); - } - - public static List getTracksAsPlaylist() { - return getTracks(String.format( - "SELECT * FROM %s WHERE %s > -1 GROUP BY %s HAVING COUNT(*) > 1 ORDER by id DESC", - TABLE_NAME, - COLUMN_ALBUM_ID, - COLUMN_ALBUM_ID - )); - } - - public static List getTracksByAlbum(String id) { - return getTracks(String.format( - "SELECT COUNT(%s) FROM %s WHERE %s = %s", - COLUMN_ID, - TABLE_NAME, - COLUMN_ALBUM_ID, - id - )); - } - - public static List getTracksByAlbumOne(String id) { - return getTracks(String.format( - "SELECT * FROM %s WHERE %s = %s LIMIT 1", - TABLE_NAME, - COLUMN_ALBUM_ID, - id - )); - } - - public static List getTracks(String query) { - List list = new ArrayList<>(); - try (Cursor cursor = getHelper().getReadableDatabase().rawQuery(query, new String[0])) { - while (cursor.moveToNext()) { - list.add(new MusicTrack(reconstructFromDb(cursor))); - } - } catch (Exception e) { - e.printStackTrace(); - } - return list; - } - - public static boolean isCached(String trackId) { - var formula = String.format( - "SELECT COUNT(%s) FROM %s WHERE %s = '%s'", - COLUMN_ID, - TABLE_NAME, - COLUMN_TRACK_ID, - trackId - ); - try { - Cursor size = getHelper().getReadableDatabase().rawQuery(formula, null); - if (size != null) { - size.moveToFirst(); - int count = size.getInt(0); - size.close(); - return count != 0; - } - } catch (SQLException e) { - e.printStackTrace(); - } - return false; - } - - public static boolean isVkxCached(int playlist_id, int owner_id) { - return LibVKXClient.getInstance().runOnServiceSync(new LibVKXClientImpl.LibVKXActionGeneric<>() { - @Override - public Boolean run(ILibVkxService service) { - try { - return service.isPlaylistCached(playlist_id, owner_id); - } catch (RemoteException e) { - return false; - } - } - - @Override - public Boolean defaultValue() { - return false; - } - }); - } - - public static boolean isVkxCached(String trackId) { - return LibVKXClient.getInstance().runOnServiceSync(new LibVKXClientImpl.LibVKXActionGeneric<>() { - @Override - public Boolean run(ILibVkxService service) { - try { - var splits = trackId.split("_"); - var id = Integer.parseInt(splits[1]); - var owner = Integer.parseInt(splits[0]); - return service.isTrackCached(id, owner); - } catch (RemoteException e) { - return false; - } - } - - @Override - public Boolean defaultValue() { - return false; - } - }); - } - - public static void removeTrackFromCache(String trackId) { - deleteRecursive(FileCacheImplementation.getTrackFolder(trackId)); - - var formula = String.format( - "DELETE FROM %s WHERE %s = '%s'", - TABLE_NAME, - COLUMN_TRACK_ID, - trackId - ); - getHelper().getWritableDatabase().execSQL(formula); - } - - public static long getTrackCount() { - if (!LibVKXClient.isIntegrationEnabled()) - return DatabaseUtils.queryNumEntries(getHelper().getReadableDatabase(), TABLE_NAME); - return LibVKXClient.getInstance().runOnServiceSync(new LibVKXClientImpl.LibVKXActionGeneric() { - @Override - public Long run(ILibVkxService service) { - try { - return (long) service.getCache().size(); - } catch (RemoteException e) { - e.printStackTrace(); - return defaultValue(); - } - } - - @Override - public Long defaultValue() { - return 0L; - } - }); - } - - private static class DbHelper extends SQLiteOpenHelper { - public DbHelper(Context context) { - super(context, DB_NAME, null, DB_VERSION); - } - - public void onCreate(SQLiteDatabase db) { - var formula = String.format( - "CREATE TABLE %s (%s INTEGER PRIMARY KEY AUTOINCREMENT, %s TEXT, %s INTEGER, %s TEXT, %s TEXT, %s TEXT, %s TEXT, %s INTEGER, %s INTEGER, %s INTEGER)", - TABLE_NAME, - COLUMN_ID, COLUMN_TRACK_ID, COLUMN_ALBUM_ID, COLUMN_TITLE, COLUMN_SUBTITLE, COLUMN_ARTIST, COLUMN_ALBUM_TITLE, COLUMN_EXPLICIT, COLUMN_DURATION, COLUMN_HAS_ARTWORK - ); - db.execSQL(formula); - } - - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - - } - } -} \ No newline at end of file diff --git a/app/src/main/java/ru/vtosters/lite/music/cache/FileCacheImplementation.java b/app/src/main/java/ru/vtosters/lite/music/cache/FileCacheImplementation.java deleted file mode 100644 index 266d14289..000000000 --- a/app/src/main/java/ru/vtosters/lite/music/cache/FileCacheImplementation.java +++ /dev/null @@ -1,50 +0,0 @@ -package ru.vtosters.lite.music.cache; - -import android.net.Uri; -import com.vk.dto.music.MusicTrack; -import ru.vtosters.hooks.MusicCacheFilesHook; -import ru.vtosters.lite.utils.AndroidUtils; -import ru.vtosters.lite.utils.IOUtils; - -import java.io.File; - -public class FileCacheImplementation { - - public static void clear() { - CacheDatabaseDelegate.clear(); - IOUtils.deleteRecursive(getCacheDir(), false); - } - - public static File getCacheDir() { - return AndroidUtils.getGlobalContext().getExternalFilesDir("vt_tracks"); - } - - public static File getTrackFolder(String trackId) { - var dir = new File(getCacheDir(), trackId); - dir.mkdirs(); - return dir; - } - - public static File getThumbnailsFolder(String trackId) { - var dir = new File(getTrackFolder(trackId), "thumbs"); - dir.mkdirs(); - return dir; - } - - public static File getTrackFile(MusicTrack track) { - return MusicCacheFilesHook.getTrackFile(track.y1()); - } - - public static File getTrackThumbnail(String trackId, int factor) { - return new File(getThumbnailsFolder(trackId), "thumb_" + factor + ".png"); - } - - public static String getFileUri(File file) { - if (!file.exists()) return null; - return Uri.fromFile(file).toString(); - } - - public static String getServerPath() { - return "https://vtosters.app"; - } -} diff --git a/app/src/main/java/ru/vtosters/lite/music/cache/MusicCacheDbDelegate.java b/app/src/main/java/ru/vtosters/lite/music/cache/MusicCacheDbDelegate.java new file mode 100644 index 000000000..b819b14f7 --- /dev/null +++ b/app/src/main/java/ru/vtosters/lite/music/cache/MusicCacheDbDelegate.java @@ -0,0 +1,87 @@ +package ru.vtosters.lite.music.cache; + +import android.content.Context; +import com.vk.dto.music.MusicTrack; +import ru.vtosters.lite.music.cache.db.MusicCacheDb; + +import java.util.List; + +public class MusicCacheDbDelegate +{ + static synchronized private MusicCacheDb connectToDb(final Context context) + { + synchronized(MusicCacheDb.class) + { return new MusicCacheDb(context); } + } + + static public void addTrack(final Context context, + final String trackId, + final String albumId, + final String title, + final String subtitle, + final String artist, + final String albumTitle, + final boolean explicit, + final int duration, + final boolean hasArtwork) + { + try(final var db=connectToDb(context)) + { + db.addTrack( + trackId, + albumId, + title, + subtitle, + artist, + albumTitle, + explicit, + duration, + hasArtwork); + } + } + + static public void removeTrack(final Context context,final String trackId) + { + try(final var db=connectToDb(context)) + { db.deleteTrack(trackId); } + } + + static public List< MusicTrack > getAllTracks(final Context context) + { + try(final var db=connectToDb(context)) + { return db.getAllTracks(); } + } + + static public List< MusicTrack > getAlbumById(final Context context,final String albumId) + { + try(final var db=connectToDb(context)) + { return db.getAlbum(albumId); } + } + + static public List< MusicTrack > getFirstAlbumTrack(final Context context,final String albumId) + { + try(final var db=connectToDb(context)) + { return db.getFirstAlbumTrack(albumId); } + } + + static public List< MusicTrack > getPlaylist(final Context context) + { + try(final var db=connectToDb(context)) + { return db.getPlaylist(); } + } + + static public long getTracksCount(final Context context) + { + try(final var db=connectToDb(context)) + { return db.getTracksCount(); } + } + + static public boolean isCachedTrack(final Context context,final String trackId) + { + try(final var db=connectToDb(context)) + { return db.isCachedTrack(trackId); } + } + + static public void drop(final Context context) + { context.deleteDatabase(MusicCacheDb.Constants.DB_NAME); } +} diff --git a/app/src/main/java/ru/vtosters/lite/music/cache/MusicCacheImpl.java b/app/src/main/java/ru/vtosters/lite/music/cache/MusicCacheImpl.java new file mode 100644 index 000000000..84c03b9e0 --- /dev/null +++ b/app/src/main/java/ru/vtosters/lite/music/cache/MusicCacheImpl.java @@ -0,0 +1,83 @@ +package ru.vtosters.lite.music.cache; + +import android.os.RemoteException; +import bruhcollective.itaysonlab.libvkx.ILibVkxService; +import bruhcollective.itaysonlab.libvkx.client.LibVKXClient; +import bruhcollective.itaysonlab.libvkx.client.LibVKXClientImpl; +import com.vk.dto.music.MusicTrack; +import ru.vtosters.lite.utils.music.MusicCacheStorageUtils; +import ru.vtosters.lite.utils.AndroidUtils; + +import java.util.List; + +public class MusicCacheImpl +{ + static public void addTrack(final MusicTrack track) + { + MusicCacheDbDelegate.addTrack( + AndroidUtils.getGlobalContext(), + track.y1(), + track.I!=null?track.I.getId()+"":"-1", + track.f, + track.g, + track.C, + track.I!=null?track.I.getTitle():"", + track.K, + track.h, + track.I!=null&&track.I.u1()!=null); + } + + static public void removeTrack(final String trackId) + { + MusicCacheDbDelegate.removeTrack(AndroidUtils.getGlobalContext(),trackId); + MusicCacheStorageUtils.removeTrackDirById(trackId); + } + + static public List< MusicTrack > getAllTracks() + { return MusicCacheDbDelegate.getAllTracks(AndroidUtils.getGlobalContext()); } + + static public List< MusicTrack > getAlbumById(final String albumId) + { return MusicCacheDbDelegate.getAlbumById(AndroidUtils.getGlobalContext(),albumId); } + + static public List< MusicTrack > getFirstAlbumTrack(final String albumId) + { return MusicCacheDbDelegate.getFirstAlbumTrack(AndroidUtils.getGlobalContext(),albumId); } + + static public List< MusicTrack > getPlaylist() + { return MusicCacheDbDelegate.getPlaylist(AndroidUtils.getGlobalContext()); } + + static public long getTracksCount() + { + return !LibVKXClient.isIntegrationEnabled() + ?MusicCacheDbDelegate.getTracksCount(AndroidUtils.getGlobalContext()) + :LibVKXClient.getInstance().runOnServiceSync( + new LibVKXClientImpl.LibVKXActionGeneric< Long >() { + @Override + public Long run(final ILibVkxService service) + { + try + { return (long)service.getCache().size(); } + catch(RemoteException e) + { + e.printStackTrace(); + return defaultValue(); + } + } + + @Override + public Long defaultValue() + { return 0L; } + }); + } + + static public boolean isCachedTrack(final String trackId) + { return MusicCacheDbDelegate.isCachedTrack(AndroidUtils.getGlobalContext(),trackId); } + + static public boolean isEmpty() + { return !LibVKXClient.isIntegrationEnabled()&&getTracksCount()==0L; } + + static public void clear() + { + MusicCacheDbDelegate.drop(AndroidUtils.getGlobalContext()); + MusicCacheStorageUtils.clear(); + } +} diff --git a/app/src/main/java/ru/vtosters/lite/music/cache/db/MusicCacheDb.java b/app/src/main/java/ru/vtosters/lite/music/cache/db/MusicCacheDb.java new file mode 100644 index 000000000..518d6bdba --- /dev/null +++ b/app/src/main/java/ru/vtosters/lite/music/cache/db/MusicCacheDb.java @@ -0,0 +1,250 @@ +package ru.vtosters.lite.music.cache.db; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.net.Uri; +import android.util.Log; +import androidx.annotation.Nullable; +import com.vk.dto.music.AlbumLink; +import com.vk.dto.music.MusicTrack; +import com.vk.dto.music.Thumb; +import org.json.JSONObject; +import ru.vtosters.lite.utils.music.MusicCacheStorageUtils; +import ru.vtosters.lite.utils.AndroidUtils; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.net.URLDecoder; +import java.util.ArrayList; +import java.util.List; + +/**

Note: the {@link AutoCloseable} interface was + * first added in the {@link android.os.Build.VERSION_CODES#Q} release for the {@link SQLiteOpenHelper}

*/ +public class MusicCacheDb +extends SQLiteOpenHelper +implements AutoCloseable//for pre-Q +{ + public MusicCacheDb(@Nullable Context context) + { super(context,Constants.DB_NAME,null,Constants.DV_VERSION); } + + @Override + public void onCreate(final SQLiteDatabase db) + { db.execSQL(Constants.CREATE_QUERY); } + + @Override + public void onUpgrade(final SQLiteDatabase db,final int oldVersion,final int newVersion) + { + if(oldVersion getAllTracks() + { return getTracksWithCursor(getReadableDatabase().query(Constants.TABLE_NAME,null,null,null,null,null,null)); } + + public List< MusicTrack > getAlbum(final String albumId) + { return getAlbumTracks(albumId,null); } + + public List< MusicTrack > getFirstAlbumTrack(final String albumId) + { return getAlbumTracks(albumId,"0,1"); } + + private List< MusicTrack > getAlbumTracks(final String albumId,@Nullable final String limit) + { + return getTracksWithCursor(getReadableDatabase().query( + Constants.TABLE_NAME, + null, + Constants.COLUMN_ALBUM_ID+" = ?", + new String[]{ albumId }, + null, + null, + null, + limit)); + } + + + public List< MusicTrack > getPlaylist() + { + return getTracksWithCursor(getWritableDatabase().query( + Constants.TABLE_NAME, + null, + Constants.COLUMN_ALBUM_ID+" > -1", + null, + Constants.COLUMN_ALBUM_ID, + "count(*) > 1", + Constants.COLUMN_ID+" desc")); + } + + private List< MusicTrack > getTracksWithCursor(final Cursor cur) + { + final var res=new ArrayList< MusicTrack >(); + if(cur!=null&&cur.moveToFirst()) + try(cur) + { + do + { + final var track=fromCursor(cur); + if(track!=null)res.add(track); + }while(cur.moveToNext()); + } + return res; + } + + static private MusicTrack fromCursor(final Cursor cur) + { + try + { + do + { + final var res=new MusicTrack(); + res.J=Integer.MAX_VALUE+"";//access_key + res.D="https://vtosters.app";//url + var i=cur.getColumnIndex(Constants.COLUMN_TRACK_ID); + if(i==-1)break; + final var trackId=cur.getString(i); + final var splits=trackId.split("_"); + if(splits.length!=2)break; + res.d=Integer.parseInt(splits[1]);//id + res.e=Integer.parseInt(splits[0]);//owner_id + i=cur.getColumnIndex(Constants.COLUMN_TITLE); + if(i==-1)break; + res.f=URLDecoder.decode(cur.getString(i),"UTF-8");//title + i=cur.getColumnIndex(Constants.COLUMN_SUBTITLE); + if(i==-1)break; + res.g=URLDecoder.decode(cur.getString(i),"UTF-8");//subtitle + i=cur.getColumnIndex(Constants.COLUMN_ARTIST); + if(i==-1)break; + res.C=URLDecoder.decode(cur.getString(i),"UTF-8");//artist + i=cur.getColumnIndex(Constants.COLUMN_DURATION); + if(i==-1)break; + res.h=cur.getInt(i);//duration + i=cur.getColumnIndex(Constants.COLUMN_EXPLICIT); + if(i==-1)break; + res.K=cur.getInt(i)>0;//is_explicit + i=cur.getColumnIndex(Constants.COLUMN_ALBUM_ID); + if(i==-1)break; + final var albumId=Integer.parseInt(cur.getString(i)); + i=cur.getColumnIndex(Constants.COLUMN_ALBUM_TITLE); + final String albumTitle=i!=-1?cur.getString(i):null; + final var node=new JSONObject(); + var f=MusicCacheStorageUtils.getTrackThumb(trackId,300); + if(f.exists())node.putOpt("photo_600",Uri.fromFile(f).toString()); + f=MusicCacheStorageUtils.getTrackThumb(trackId,600); + if(f.exists())node.putOpt("photo_600",Uri.fromFile(f).toString()); + f=MusicCacheStorageUtils.getTrackThumb(trackId,1200); + if(f.exists())node.putOpt("photo_1200",Uri.fromFile(f).toString()); + res.I=new AlbumLink( + albumId, + res.e, + res.J, + albumTitle, + node.length()>0 + ?new Thumb(node.put("width",600) + .put("height",600)) + :null + );//album + return res; + }while(false); + } + catch(Throwable ignored) + { } + return null; + } + + @Retention(RetentionPolicy.SOURCE) + public @interface Constants + { + int DV_VERSION=0x2; + String DB_NAME="vt_lite_cache.db"; + String TABLE_NAME="tracks"; + //columns + String COLUMN_ID="id"; + String COLUMN_TRACK_ID="trackId"; + String COLUMN_ALBUM_ID="albumId"; + String COLUMN_TITLE="title"; + String COLUMN_SUBTITLE="subtitle"; + String COLUMN_ARTIST="artist"; + String COLUMN_ALBUM_TITLE="albumTitle"; + String COLUMN_EXPLICIT="explicit"; + String COLUMN_DURATION="duration"; + String COLUMN_HAS_ARTWORK="hasArtwork"; + //queries + String CREATE_QUERY="create table "+TABLE_NAME+"(" + +COLUMN_ID+" INTEGER PRIMARY KEY autoincrement," + +COLUMN_TRACK_ID+" text not null," + +COLUMN_ALBUM_ID+" text not null," + +COLUMN_TITLE+" text not null," + +COLUMN_SUBTITLE+" text not null," + +COLUMN_ARTIST+" text not null," + +COLUMN_ALBUM_TITLE+" text not null," + +COLUMN_EXPLICIT+" int not null," + +COLUMN_DURATION+" int not null," + +COLUMN_HAS_ARTWORK+" int not null" + +")"; + String DROP_QUERY="drop table if exists "+TABLE_NAME; + } +} diff --git a/app/src/main/java/ru/vtosters/lite/music/cache/helpers/PlaylistHelper.java b/app/src/main/java/ru/vtosters/lite/music/cache/helpers/PlaylistHelper.java index 9c0558965..00fc3f8e8 100644 --- a/app/src/main/java/ru/vtosters/lite/music/cache/helpers/PlaylistHelper.java +++ b/app/src/main/java/ru/vtosters/lite/music/cache/helpers/PlaylistHelper.java @@ -1,12 +1,13 @@ package ru.vtosters.lite.music.cache.helpers; +import android.net.Uri; import com.vk.dto.music.Playlist; import com.vtosters.lite.R; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import ru.vtosters.lite.music.cache.CacheDatabaseDelegate; -import ru.vtosters.lite.music.cache.FileCacheImplementation; +import ru.vtosters.lite.music.cache.MusicCacheImpl; +import ru.vtosters.lite.utils.music.MusicCacheStorageUtils; import ru.vtosters.lite.utils.AndroidUtils; import java.util.ArrayList; @@ -44,8 +45,8 @@ public class PlaylistHelper { if (!photo.isEmpty()) jPhoto.put("height", 600) .put("width", 600) - .putOpt("photo_300", FileCacheImplementation.getFileUri(FileCacheImplementation.getTrackThumbnail(trackId, 300))) - .putOpt("photo_600", FileCacheImplementation.getFileUri(FileCacheImplementation.getTrackThumbnail(trackId, 600))); + .putOpt("photo_300",Uri.fromFile(MusicCacheStorageUtils.getTrackThumb(trackId,300)).toString()) + .putOpt("photo_600",Uri.fromFile(MusicCacheStorageUtils.getTrackThumb(trackId,600)).toString()); var obj = new JSONObject() .put("id", -2) .put("owner_id", albumId) @@ -68,7 +69,7 @@ public class PlaylistHelper { public static List getAlbumPlaylists() { List list = new ArrayList<>(); - for (var track : CacheDatabaseDelegate.getTracksAsPlaylist()) { + for (var track : MusicCacheImpl.getPlaylist()) { var albumLink = track.I; var thumb = albumLink.u1(); list.add(createAlbum( diff --git a/app/src/main/java/ru/vtosters/lite/music/cache/helpers/TracklistHelper.java b/app/src/main/java/ru/vtosters/lite/music/cache/helpers/TracklistHelper.java index 1cf71fe4c..0eecf6c7c 100644 --- a/app/src/main/java/ru/vtosters/lite/music/cache/helpers/TracklistHelper.java +++ b/app/src/main/java/ru/vtosters/lite/music/cache/helpers/TracklistHelper.java @@ -7,8 +7,8 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import ru.vtosters.hooks.other.Preferences; -import ru.vtosters.lite.music.cache.CacheDatabaseDelegate; -import ru.vtosters.lite.music.cache.FileCacheImplementation; +import ru.vtosters.lite.music.cache.MusicCacheImpl; +import ru.vtosters.lite.utils.music.MusicCacheStorageUtils; import java.io.File; import java.net.MalformedURLException; @@ -22,7 +22,7 @@ public class TracklistHelper { } public static List getTracks() { - List tracks = CacheDatabaseDelegate.getTracks(); + List tracks = MusicCacheImpl.getAllTracks(); boolean doNotinvertOrder = Preferences.getBoolValue("invertCachedTracks", false); @@ -35,11 +35,11 @@ public class TracklistHelper { public static List getTracks(String id) { - return CacheDatabaseDelegate.getTracksByAlbum(id); + return MusicCacheImpl.getAlbumById(id); } public static List getTracksOne(String id) { - return CacheDatabaseDelegate.getTracksByAlbumOne(id); + return MusicCacheImpl.getFirstAlbumTrack(id); } public static JSONArray tracksToIds(List tracks) { @@ -56,7 +56,7 @@ public class TracklistHelper { var arr = new JSONArray(); for (MusicTrack track : tracks) { var json = track.J(); - var folder = FileCacheImplementation.getThumbnailsFolder(LibVKXClient.asId(track)); + var folder = MusicCacheStorageUtils.getThumbDirById(LibVKXClient.asId(track)); try { addCachedThumbnails(json, folder); } catch (JSONException | MalformedURLException e) { @@ -71,7 +71,7 @@ public class TracklistHelper { List tracks = new ArrayList<>(); for (MusicTrack track : list) { var json = track.J(); - var folder = FileCacheImplementation.getThumbnailsFolder(LibVKXClient.asId(track)); + var folder = MusicCacheStorageUtils.getThumbDirById(LibVKXClient.asId(track)); try { addCachedThumbnails(json, folder); } catch (JSONException | MalformedURLException e) { diff --git a/app/src/main/java/ru/vtosters/lite/music/callback/MusicCallbackBuilder.java b/app/src/main/java/ru/vtosters/lite/music/callback/MusicCallbackBuilder.java index 4e0223f25..7b9f2249d 100644 --- a/app/src/main/java/ru/vtosters/lite/music/callback/MusicCallbackBuilder.java +++ b/app/src/main/java/ru/vtosters/lite/music/callback/MusicCallbackBuilder.java @@ -5,7 +5,7 @@ import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; import com.vtosters.lite.R; import ru.vtosters.lite.downloaders.notifications.NotificationChannels; -import ru.vtosters.lite.music.Callback; +import ru.vtosters.lite.music.interfaces.Callback; import ru.vtosters.lite.utils.AndroidUtils; public class MusicCallbackBuilder { diff --git a/app/src/main/java/ru/vtosters/lite/music/converter/playlist/PlaylistConverter.java b/app/src/main/java/ru/vtosters/lite/music/converter/playlist/PlaylistConverter.java index 6b24fa4b4..e969dc7b6 100644 --- a/app/src/main/java/ru/vtosters/lite/music/converter/playlist/PlaylistConverter.java +++ b/app/src/main/java/ru/vtosters/lite/music/converter/playlist/PlaylistConverter.java @@ -26,7 +26,8 @@ import static ru.vtosters.lite.proxy.ProxyUtils.getApi; * stuff for converting playlists DTO */ public class PlaylistConverter { - public static List getPlaylist(Playlist oldPlaylist) { + public static List getPlaylist(Playlist oldPlaylist) + { var tracks = requestPlaylist(0, oldPlaylist.b, oldPlaylist.Q, oldPlaylist.a); return tracks.stream().filter(track -> !track.D.isEmpty()).collect(Collectors.toList()); } diff --git a/app/src/main/java/ru/vtosters/lite/music/converter/ts/FFMpeg.java b/app/src/main/java/ru/vtosters/lite/music/converter/ts/FFMpeg.java index 14202e146..77696c6a8 100644 --- a/app/src/main/java/ru/vtosters/lite/music/converter/ts/FFMpeg.java +++ b/app/src/main/java/ru/vtosters/lite/music/converter/ts/FFMpeg.java @@ -1,23 +1,21 @@ package ru.vtosters.lite.music.converter.ts; -import android.util.Log; import com.arthenica.ffmpegkit.FFmpegKit; import com.arthenica.ffmpegkit.ReturnCode; import com.vk.core.util.ToastUtils; import com.vk.dto.music.Artist; import com.vk.dto.music.MusicTrack; import com.vtosters.lite.R; +import ru.vtosters.lite.music.downloader.M3UDownloader; import ru.vtosters.lite.utils.AndroidUtils; import ru.vtosters.lite.utils.IOUtils; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; import java.util.stream.Collectors; -public class FFMpeg { - private static final int BUFFER_SIZE = 2048; +public class FFMpeg +{ /** * @param in Merged .ts file via {@link ru.vtosters.lite.music.converter.ts.TSMerger} @@ -25,79 +23,48 @@ public class FFMpeg { * @param track Track to be converted * @return True if success, else false */ - public static boolean convert(File in, String out, MusicTrack track) { - + public static void convert(File in, String out, MusicTrack track) + throws Throwable + { // convert .ts file to .mp3 file via ffmpeg - var sb = new StringBuilder(); - sb.append("-y -i \"").append(in.getAbsolutePath()).append("\""); - sb.append(" -map 0 -dn "); - //sb.append(" -loglevel error"); - //sb.append(" -hide_banner"); - sb.append(" -write_id3v2 1"); - - String title = null; - - if (track.f != null) { - title = track.f; - if (!track.g.isEmpty()) { - title += " (" + track.g + ")"; - } - } - - if (title != null) { - sb.append(" -metadata title=\"").append(IOUtils.getValidFileName(title)).append("\""); - } - - if (track.L != null) { - sb.append(" -metadata artist=\"").append(track.L.stream().map(Artist::w1).collect(Collectors.joining(", "))).append("\""); - } else if (track.C != null) { - sb.append(" -metadata artist=\"").append(track.C).append("\""); - } - if (track.I != null) { - sb.append(" -metadata album=\"").append(track.I.getTitle()).append("\""); - } - - sb.append(" -c copy \"").append(out).append("\""); - - var command = sb.toString(); - - var session = FFmpegKit.execute(command); - var rc = session.getReturnCode(); - - if (ReturnCode.isSuccess(rc)) { - return true; - } - - Log.e("FFMpeg", "FFmpegKit failed with return code " + rc); - return false; + final var cmd=" -y -i \""+in.getAbsolutePath()+'"' + +" -map 0 -dn" + +" -loglevel error" + +" -hide_banner" + +" -write_id3v2 1" +// +" -metadata title=\""+IOUtils.getValidFileName(M3UDownloader.getTitle(track))+'"' +// +" -metadata artist=\""+normalizeMetadata(track.L.stream().map(Artist::w1).collect(Collectors.joining(", ")))+'"' +// +" -metadata album=\""+normalizeMetadata(track.I.getTitle())+'"' + +" -c copy \""+out+'"'; + final var ses=FFmpegKit.execute(cmd); + if(ses.getReturnCode().isValueCancel())throw new RuntimeException(ses.getLogsAsString()); } - private static void copyAssets() { - try { - String[] files = AndroidUtils.getGlobalContext().getAssets().list("ffmpeg"); - for (String file : files) { - if (file.endsWith(".so")) { - InputStream in = AndroidUtils.getGlobalContext().getAssets().open("ffmpeg" + File.separator + file); - FileOutputStream out = new FileOutputStream(AndroidUtils.getGlobalContext().getFilesDir() + File.separator + file); - byte[] buffer = new byte[BUFFER_SIZE]; - int read; - while ((read = in.read(buffer)) != -1) { - out.write(buffer, 0, read); - } - in.close(); - out.close(); - } - } - } catch (IOException e) { - e.printStackTrace(); - } - } + static String normalizeMetadata(String in) + { return in.replace('"','‟'); } - private static void checkFFMpegLibs() { - var lib = new File(AndroidUtils.getGlobalContext().getFilesDir(), "libffmpegkit_abidetect.so"); - if (!lib.exists()) { - ToastUtils.a(R.string.ffmpegkit_copy); - copyAssets(); - } - } +// private static void copyAssets() +// throws IOException +// { +// try +// { +// for (var filename:AndroidUtils.getGlobalContext().getAssets().list("ffmpeg")) +// if(filename.endsWith(".so")) +// IOUtils.writeToFile( +// new File(AndroidUtils.getGlobalContext().getFilesDir(),filename), +// IOUtils.readFully(AndroidUtils.getGlobalContext().getAssets().open("ffmpeg"+File.separator+filename))); +// } +// catch(IOException e) +// { throw e; } +// } +// +// private static void checkFFMpegLibs() +// { +// var lib=new File(AndroidUtils.getGlobalContext().getFilesDir(),"libffmpegkit_abidetect.so"); +// if(!lib.exists()) +// { +// ToastUtils.a(R.string.ffmpegkit_copy); +// copyAssets(); +// } +// } } diff --git a/app/src/main/java/ru/vtosters/lite/music/converter/ts/TSMerger.java b/app/src/main/java/ru/vtosters/lite/music/converter/ts/TSMerger.java index f4c1ec2c3..f4c4546ef 100644 --- a/app/src/main/java/ru/vtosters/lite/music/converter/ts/TSMerger.java +++ b/app/src/main/java/ru/vtosters/lite/music/converter/ts/TSMerger.java @@ -1,14 +1,13 @@ package ru.vtosters.lite.music.converter.ts; -import android.os.Build; import ru.vtosters.lite.utils.IOUtils; import java.io.ByteArrayOutputStream; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; -import java.nio.file.Files; import java.util.Arrays; +import java.util.Comparator; +import java.util.regex.Pattern; public class TSMerger { /** @@ -17,58 +16,23 @@ public class TSMerger { * @param in Directory with .ts files * @return True if success, else false */ - public static boolean merge(File in, File out) { - var tsesList = in.listFiles(); - - if (tsesList == null) { - return false; - } - - var mergedByteArrayStream = new ByteArrayOutputStream(); - - sortTsesList(tsesList); - - for (var file : tsesList) { - if (file.getName().endsWith(".ts")) { - try { - mergedByteArrayStream.write(IOUtils.readFully(file)); - } catch (IOException e) { - e.printStackTrace(); - return false; - } - } - } - - return writeOutputFile(out, mergedByteArrayStream); + public static void merge(final File in,final File out) + throws Throwable + { + var tsFiles=in.listFiles((dir,name) -> name.matches(".*\\d+\\.ts")); + if(tsFiles==null||tsFiles.length==0)throw new IOException("Dir "+in.getAbsolutePath()+" doesn't contain indexed \"*.ts\" files"); + Arrays.sort(tsFiles,Comparator.comparingInt(TSMerger::getFileIndex)); + final var bis=new ByteArrayOutputStream(); + for(var f:tsFiles)bis.write(IOUtils.readFully(f)); + bis.close(); + IOUtils.writeToFile(out,bis.toByteArray()); } - private static void sortTsesList(File[] tsesList) { - Arrays.sort(tsesList, (ts1, ts2) -> { - String ts1Name = ts1.getName(); - String ts2Name = ts2.getName(); - int i1 = Integer.parseInt(ts1Name.substring(0, ts1Name.indexOf('.', 1))); - int i2 = Integer.parseInt(ts2Name.substring(0, ts2Name.indexOf('.', 1))); - return Integer.compare(i1, i2); - }); - } - - private static boolean writeOutputFile(File out, ByteArrayOutputStream mergedByteArrayStream) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - try { - Files.write(out.toPath(), mergedByteArrayStream.toByteArray()); - } catch (IOException e) { - e.printStackTrace(); - return false; - } - } else { - try (FileOutputStream fos = new FileOutputStream(out)) { - fos.write(mergedByteArrayStream.toByteArray()); - } catch (IOException e) { - e.printStackTrace(); - return false; - } - } - - return true; + static int getFileIndex(final File f) + { + final var p=Pattern.compile(".*(\\d+)\\.ts"); + final var m=p.matcher(f.getName()); + if(m.find())return Integer.parseInt(m.group(1)); + throw new NullPointerException("Filename has no index"+f.getName()); } } diff --git a/app/src/main/java/ru/vtosters/lite/music/downloader/M3UDownloader.java b/app/src/main/java/ru/vtosters/lite/music/downloader/M3UDownloader.java new file mode 100644 index 000000000..5d6e27714 --- /dev/null +++ b/app/src/main/java/ru/vtosters/lite/music/downloader/M3UDownloader.java @@ -0,0 +1,186 @@ +package ru.vtosters.lite.music.downloader; + +import android.net.Uri; +import android.text.TextUtils; +import android.util.Log; +import com.google.android.exoplayer2.source.hls.playlist.e; +import com.google.android.exoplayer2.source.hls.playlist.f; +import com.google.android.exoplayer2.source.hls.playlist.f.a; +import com.vk.dto.music.MusicTrack; +import java8.util.concurrent.CompletableFuture; +import java8.util.concurrent.CompletionException; +import ru.vtosters.lite.music.cache.MusicCacheImpl; +import ru.vtosters.lite.music.converter.ts.FFMpeg; +import ru.vtosters.lite.music.converter.ts.TSMerger; +import ru.vtosters.lite.music.interfaces.Callback; +import ru.vtosters.lite.music.interfaces.ITrackDownloader; +import ru.vtosters.lite.utils.AndroidUtils; +import ru.vtosters.lite.utils.IOUtils; +import ru.vtosters.lite.utils.music.MusicCacheStorageUtils; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +public class M3UDownloader +implements ITrackDownloader +{ + public static M3UDownloader getInstance() + { return Holder.INSTANCE; } + + @Override + public void downloadTrack( + final MusicTrack track, + final File outDir, + final Callback callback, + final boolean cache) + { + if (track.D.isEmpty()) + { + Log.d("TrackDownloader", "link error: " + track.y1() + ", title: " + M3UDownloader.getTitle(track)); + callback.onFailure(); + return; + } + try + { parse(outDir,callback,track,cache); } + catch(IOException e) + { + callback.onFailure(); + Log.e("M3UDownloader",e+""); + throw new RuntimeException(e); + } + } + + static void parse( + final File outDir, + final Callback callback, + final MusicTrack track, + final boolean cache) + throws IOException + { + final var parser=new com.google.android.exoplayer2.source.hls.playlist.h(e.a(track.D)); + final var baseUri=track.D.substring(0,track.D.lastIndexOf("/")+1); + final var segments=((f)parser.a(Uri.parse(baseUri),IOUtils.openStream(track.D))).o; + final var progress=new AtomicInteger(); + callback.onProgress(5); + final var resultTs=getResultTsFile(track); + final var resultMp3=getResultMp3File(outDir, cache, track); + try + { + CompletableFuture.allOf(createFutures(baseUri,segments,progress,callback,track)) + .thenRun(() -> + { + if(cache) + try + { ThumbnailDownloader.downloadThumbnails(track); } + catch(IOException e) + { throw new RuntimeException("Failed to download thumbs",e); } + }) + .thenRun(() -> + { + try + { TSMerger.merge(getTsesDir(track),resultTs); } + catch(Throwable e) + { throw new RuntimeException("Failed to merge ts files",e); } + }) + .thenRun(() -> + { + try + { FFMpeg.convert(resultTs,resultMp3.getAbsolutePath(),track); } + catch(Throwable e) + { throw new RuntimeException("FFmpeg error",e); } + }) + .thenRun(() -> callback.onProgress(10+Math.round(80.0f*progress.addAndGet(1)/segments.size()))) + .thenRun(() -> MusicCacheImpl.addTrack(track)) + .whenComplete((unused,tr) -> { + if(tr!=null) + { + IOUtils.deleteRecursive(MusicCacheStorageUtils.getTrackDirById(track.y1())); + callback.onFailure(); + } + else callback.onSuccess(); + IOUtils.deleteRecursive(getTsesDir(track)); + }) + .join(); + } + catch(CompletionException e) + { Log.e("M3UDownloader","Failed to download track",e.getCause()); } + } + + @SuppressWarnings("rawtypes") + static CompletableFuture[] createFutures( + final String baseUri, + final List< a > segments, + final AtomicInteger progress, + final Callback callback, + final MusicTrack track) + { + final var futures=new CompletableFuture[segments.size()]; + for(var i=0;i processSegment(baseUri,segment,filename,progress,segments.size(),callback,track)); + } + return futures; + } + + static void processSegment( + final String baseUri, + final a segment, + final String filename, + final AtomicInteger progress, + final int size, + final Callback callback, + final MusicTrack track) + { + try + { + var buff=IOUtils.readFully(IOUtils.openStream(baseUri+segment.a/*url*/)); + if(!TextUtils.isEmpty(segment.g)) + { + final var cipher=Cipher.getInstance("AES/CBC/PKCS7Padding"); + final var cipherKey=new SecretKeySpec(IOUtils.readFully(IOUtils.openStream(segment.g)),"AES"); + var cipherBytes=segment.h.getBytes(); + //if IV doesn't pass, it must be created manually and filled with zeros + if(cipherBytes.length!=16)Arrays.fill((cipherBytes=new byte[16]),(byte)0x0); + var cipherIv=new IvParameterSpec(cipherBytes); + cipher.init(Cipher.DECRYPT_MODE,cipherKey,cipherIv); + buff=cipher.doFinal(buff); + } + IOUtils.writeToFile(new File(getTsesDir(track),filename),buff); + callback.onProgress(10+Math.round(80.0f*progress.addAndGet(1)/size)); + } + catch(Exception e) + { throw new RuntimeException(e); } + } + + static public String getTitle(final MusicTrack track) + { return track.f+(!TextUtils.isEmpty(track.g)?+'('+track.g+')':null); } + + static File getTsesDir(final MusicTrack track) + { + var filesDir=AndroidUtils.getGlobalContext().getFilesDir(); + var tsesDir=new File(filesDir,String.valueOf(track.d)); + tsesDir.mkdirs(); + return tsesDir; + } + + static File getResultTsFile(final MusicTrack track) + { return new File(getTsesDir(track),"result.ts"); } + + static File getResultMp3File(final File outDir,final boolean cache,final MusicTrack track) + { return new File(outDir,IOUtils.getValidFileName((cache?"track":track.C+" - "+getTitle(track))+".mp3")); } + + // Initialization-on-demand + private static class Holder + { + private static final M3UDownloader INSTANCE = new M3UDownloader(); + } +} diff --git a/app/src/main/java/ru/vtosters/lite/music/MP3Downloader.java b/app/src/main/java/ru/vtosters/lite/music/downloader/MP3Downloader.java similarity index 98% rename from app/src/main/java/ru/vtosters/lite/music/MP3Downloader.java rename to app/src/main/java/ru/vtosters/lite/music/downloader/MP3Downloader.java index 1d28adf35..72e6c7157 100644 --- a/app/src/main/java/ru/vtosters/lite/music/MP3Downloader.java +++ b/app/src/main/java/ru/vtosters/lite/music/downloader/MP3Downloader.java @@ -70,7 +70,7 @@ // } // // @Override -// public void downloadTrack(String url, File outDir, ru.vtosters.lite.music.Callback callback) { +// public void downloadTrack(String url, File outDir, ru.vtosters.lite.music.interfaces.Callback callback) { // // } //} diff --git a/app/src/main/java/ru/vtosters/lite/music/downloader/PlaylistDownloader.java b/app/src/main/java/ru/vtosters/lite/music/downloader/PlaylistDownloader.java index 2b624f182..60e213043 100644 --- a/app/src/main/java/ru/vtosters/lite/music/downloader/PlaylistDownloader.java +++ b/app/src/main/java/ru/vtosters/lite/music/downloader/PlaylistDownloader.java @@ -2,34 +2,36 @@ package ru.vtosters.lite.music.downloader; import android.util.Log; import com.vk.dto.music.MusicTrack; -import ru.vtosters.lite.music.Callback; +import ru.vtosters.lite.music.interfaces.Callback; +import ru.vtosters.lite.utils.IOUtils; +import ru.vtosters.lite.utils.music.MusicCacheStorageUtils; import java.io.File; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; -public class PlaylistDownloader { - public static void downloadPlaylist(List playlist, String playlistName, String path, Callback callback) { - File outDir = new File(path); +public class PlaylistDownloader +{ + static public void downloadPlaylist(final List< MusicTrack > playlist, + final String playlistName, + final String path, + final Callback callback) + { + final var outDir=new File(path); if (!outDir.exists()) - if (outDir.mkdirs()) - Log.v("PlaylistDownloader", "Directory created"); - else - Log.e("PlaylistDownloader", "Directory creation failed"); - - var downloadedTracks = new AtomicInteger(0); - - for (MusicTrack musicTrack : playlist) { - TrackDownloader.downloadTrack(musicTrack, path, new Callback() { + if(outDir.mkdirs())Log.v("PlaylistDownloader","Directory created"); + else Log.e("PlaylistDownloader","Directory creation failed"); + final var downloadedTracks=new AtomicInteger(); + for (var musicTrack:playlist) + TrackDownloader.downloadTrack(musicTrack, path, new Callback() + { @Override - public void onProgress(int progress) { - } + public void onProgress(int progress) + { } @Override - public void onSuccess() { - var currentProgress = downloadedTracks.incrementAndGet(); - callback.onProgress(currentProgress); - } + public void onSuccess() + { callback.onProgress(downloadedTracks.incrementAndGet()); } @Override public void onFailure() { @@ -37,36 +39,32 @@ public class PlaylistDownloader { } @Override - public void onSizeReceived(long size, long header) { - } + public void onSizeReceived(long size, long header) + { } }); - } } - public static void cachePlaylist(List playlist, Callback callback) { - var downloadedTracks = new AtomicInteger(0); - - for (MusicTrack musicTrack : playlist) { - TrackDownloader.cacheTrack(musicTrack, new Callback() { + static public void cachePlaylist(final List< MusicTrack > playlist,final Callback callback) + { + final var downloadedTracks=new AtomicInteger(); + for (var musicTrack:playlist) + TrackDownloader.cacheTrack(musicTrack,new Callback() + { @Override - public void onProgress(int progress) { - } + public void onProgress(int progress) + { } @Override - public void onSuccess() { - var currentProgress = downloadedTracks.incrementAndGet(); - callback.onProgress(currentProgress); - } + public void onSuccess() + { callback.onProgress(downloadedTracks.incrementAndGet()); } @Override - public void onFailure() { - callback.onFailure(); - } + public void onFailure() + { callback.onFailure(); } @Override - public void onSizeReceived(long size, long header) { - } + public void onSizeReceived(long size, long header) + { } }); - } } } \ No newline at end of file diff --git a/app/src/main/java/ru/vtosters/lite/music/downloader/ThumbnailDownloader.java b/app/src/main/java/ru/vtosters/lite/music/downloader/ThumbnailDownloader.java index 34d2c189a..425c8b249 100644 --- a/app/src/main/java/ru/vtosters/lite/music/downloader/ThumbnailDownloader.java +++ b/app/src/main/java/ru/vtosters/lite/music/downloader/ThumbnailDownloader.java @@ -2,7 +2,7 @@ package ru.vtosters.lite.music.downloader; import bruhcollective.itaysonlab.libvkx.client.LibVKXClient; import com.vk.dto.music.MusicTrack; -import ru.vtosters.lite.music.cache.FileCacheImplementation; +import ru.vtosters.lite.utils.music.MusicCacheStorageUtils; import ru.vtosters.lite.utils.IOUtils; import java.io.IOException; @@ -11,36 +11,43 @@ import java.net.URL; public class ThumbnailDownloader { public static void downloadThumbnails(MusicTrack track) - throws IOException { - var obj = track.J(); - var trackId = LibVKXClient.asId(track); - - if (!obj.has("album")) return; - var album = obj.optJSONObject("album"); - - if (!album.has("thumb")) return; - var thumb = album.optJSONObject("thumb"); - - if (thumb.has("sizes")) { - var sizes = thumb.optJSONArray("sizes"); - for (int i = 0; i < sizes.length(); i++) { - var size = sizes.optJSONObject(i); - var width = size.optInt("width"); - var src = size.optString("src"); - downloadThumbnail(src, width, trackId); + throws IOException + { + do + { + final var json=track.J(); + final var album=json.optJSONObject("album"); + if(album==null)break; + final var thumb=album.optJSONObject("thumb"); + if(thumb==null)break; + final var sizes=thumb.optJSONArray("sizes"); + if(sizes==null) + { + final var names=thumb.names(); + if(names==null)break; + final var trackId=LibVKXClient.asId(track); + for(var i=0;i { - if (CacheDatabaseDelegate.getTrackCount() == 0) { + if (MusicCacheImpl.isEmpty()) { AndroidUtils.sendToast(requireContext().getString(R.string.no_cache_error)); } else { delcache(requireContext()); @@ -123,7 +123,7 @@ public class MediaFragment extends TrackedMaterialPreferenceToolbarFragment { return true; }); - findPreference("invertCachedTracks").setVisible(CacheDatabaseDelegate.hasTracks()); + findPreference("invertCachedTracks").setVisible(!MusicCacheImpl.isEmpty()); if (!LibVKXClient.isVkxInstalled()) { findPreference("vkx_sett").setVisible(false); @@ -217,7 +217,7 @@ public class MediaFragment extends TrackedMaterialPreferenceToolbarFragment { .setTitle(R.string.warning) .setMessage(R.string.cached_tracks_remove_confirm) .setPositiveButton(R.string.yes, - (dialog, which) -> executor.submit(CacheDatabaseDelegate::clear)) + (dialog, which) -> executor.submit(MusicCacheImpl::clear)) .setNeutralButton(R.string.no, (dialog, which) -> dialog.cancel()) .show(); diff --git a/app/src/main/java/ru/vtosters/lite/ui/fragments/VTSettings.java b/app/src/main/java/ru/vtosters/lite/ui/fragments/VTSettings.java index 6cadb0029..fe0edbfc8 100644 --- a/app/src/main/java/ru/vtosters/lite/ui/fragments/VTSettings.java +++ b/app/src/main/java/ru/vtosters/lite/ui/fragments/VTSettings.java @@ -1,7 +1,10 @@ package ru.vtosters.lite.ui.fragments; import android.annotation.SuppressLint; -import android.content.*; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; diff --git a/app/src/main/java/ru/vtosters/lite/utils/IOUtils.java b/app/src/main/java/ru/vtosters/lite/utils/IOUtils.java index c8dcd5075..e1d0e5df4 100644 --- a/app/src/main/java/ru/vtosters/lite/utils/IOUtils.java +++ b/app/src/main/java/ru/vtosters/lite/utils/IOUtils.java @@ -1,40 +1,13 @@ package ru.vtosters.lite.utils; -import javax.crypto.Cipher; -import javax.crypto.CipherInputStream; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; import java.io.*; import java.net.URL; import java.net.URLConnection; import java.nio.charset.StandardCharsets; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.Key; -import java.security.NoSuchAlgorithmException; -import java.util.Arrays; public class IOUtils { public static int BUFFER_SIZE = 8192; - public static byte[] decodeStream(InputStream encIs, byte[] aesKey) - throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, IOException { - CipherInputStream cip = new CipherInputStream(encIs, getCipher(aesKey)); - return readFully(cip); - } - - public static Cipher getCipher(byte[] aesKey) - throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException { - Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); - Key keySpec = new SecretKeySpec(aesKey, "AES"); - byte[] iv = new byte[16]; - Arrays.fill(iv, (byte) 0x0); - IvParameterSpec ivps = new IvParameterSpec(iv); - cipher.init(Cipher.DECRYPT_MODE, keySpec, ivps); - return cipher; - } - public static InputStream openStream(String url) throws IOException { URLConnection connection = new URL(url).openConnection(); diff --git a/app/src/main/java/ru/vtosters/lite/utils/music/MusicCacheStorageUtils.java b/app/src/main/java/ru/vtosters/lite/utils/music/MusicCacheStorageUtils.java new file mode 100644 index 000000000..157912f15 --- /dev/null +++ b/app/src/main/java/ru/vtosters/lite/utils/music/MusicCacheStorageUtils.java @@ -0,0 +1,40 @@ +package ru.vtosters.lite.utils.music; + +import ru.vtosters.lite.utils.AndroidUtils; +import ru.vtosters.lite.utils.IOUtils; + +import java.io.File; + +public class MusicCacheStorageUtils +{ + static public File getCacheStorageDir() + { return AndroidUtils.getGlobalContext().getExternalFilesDir("vt_tracks"); } + + @SuppressWarnings("ResultOfMethodCallIgnored") + static public File getTrackDirById(final String trackId) + { + final var res=new File(getCacheStorageDir(),trackId); + res.mkdirs(); + return res; + } + + static public File getTrackFile(final String trackId) + { return new File(getTrackDirById(trackId),"track.mp3"); } + + @SuppressWarnings("ResultOfMethodCallIgnored") + static public File getThumbDirById(final String trackId) + { + final var res=new File(getTrackDirById(trackId),"thumbs"); + res.mkdirs(); + return res; + } + + static public File getTrackThumb(final String trackId,final int factor) + { return new File(getThumbDirById(trackId),"thumb_"+factor+".png"); } + + static public void removeTrackDirById(final String trackId) + { IOUtils.deleteRecursive(getTrackDirById(trackId)); } + + static public void clear() + { IOUtils.deleteRecursive(getCacheStorageDir()); } +} diff --git a/app/src/main/java/ru/vtosters/lite/utils/music/VKXUtils.java b/app/src/main/java/ru/vtosters/lite/utils/music/VKXUtils.java new file mode 100644 index 000000000..fa71d6206 --- /dev/null +++ b/app/src/main/java/ru/vtosters/lite/utils/music/VKXUtils.java @@ -0,0 +1,48 @@ +package ru.vtosters.lite.utils.music; + +import android.os.RemoteException; +import bruhcollective.itaysonlab.libvkx.ILibVkxService; +import bruhcollective.itaysonlab.libvkx.client.LibVKXClient; +import bruhcollective.itaysonlab.libvkx.client.LibVKXClientImpl; + +public class VKXUtils +{ + public static boolean isVkxCached(int playlist_id, int owner_id) { + return LibVKXClient.getInstance().runOnServiceSync(new LibVKXClientImpl.LibVKXActionGeneric<>() { + @Override + public Boolean run(ILibVkxService service) { + try { + return service.isPlaylistCached(playlist_id, owner_id); + } catch (RemoteException e) { + return false; + } + } + + @Override + public Boolean defaultValue() { + return false; + } + }); + } + + public static boolean isVkxCached(String trackId) { + return LibVKXClient.getInstance().runOnServiceSync(new LibVKXClientImpl.LibVKXActionGeneric<>() { + @Override + public Boolean run(ILibVkxService service) { + try { + var splits = trackId.split("_"); + var id = Integer.parseInt(splits[1]); + var owner = Integer.parseInt(splits[0]); + return service.isTrackCached(id, owner); + } catch (RemoteException e) { + return false; + } + } + + @Override + public Boolean defaultValue() { + return false; + } + }); + } +} diff --git a/smali/smali_classes2/com/vk/catalog2/core/api/music/CatalogGetAudio.smali b/smali/smali_classes2/com/vk/catalog2/core/api/music/CatalogGetAudio.smali index ee55f91a2..aa24b306c 100644 --- a/smali/smali_classes2/com/vk/catalog2/core/api/music/CatalogGetAudio.smali +++ b/smali/smali_classes2/com/vk/catalog2/core/api/music/CatalogGetAudio.smali @@ -89,7 +89,7 @@ invoke-static {p1, v1}, Lkotlin/jvm/internal/Intrinsics;->a(Ljava/lang/Object;Ljava/lang/String;)V - invoke-static/range {p1 .. p1}, Lru/vtosters/lite/music/cache/injectors/TracklistInjector;->injectIntoExistingCatalog(Lorg/json/JSONObject;)V + invoke-static/range {p1 .. p1}, Lru/vtosters/hooks/music/injectors/TracklistInjector;->injectIntoExistingCatalog(Lorg/json/JSONObject;)V invoke-virtual {v0, p1}, Lcom/vk/catalog2/core/CatalogParser;->c(Lorg/json/JSONObject;)Lcom/vk/catalog2/core/api/dto/CatalogResponse; diff --git a/smali/smali_classes3/com/vk/music/artists/chooser/MusicArtistSelector$Companion.smali b/smali/smali_classes3/com/vk/music/artists/chooser/MusicArtistSelector$Companion.smali index 1b66bd4a9..037dc7748 100644 --- a/smali/smali_classes3/com/vk/music/artists/chooser/MusicArtistSelector$Companion.smali +++ b/smali/smali_classes3/com/vk/music/artists/chooser/MusicArtistSelector$Companion.smali @@ -83,8 +83,8 @@ .locals 1 .line 21 - # ru.vtosters.lite.music.hook.MusicArtistSelectorHook.navigateToSearch(Context context, String artist) - invoke-static {p1, p2}, Lru/vtosters/lite/music/hook/MusicArtistSelectorHook;->navigateToSearch(Landroid/content/Context;Ljava/lang/String;)V + # ru.vtosters.hooks.music.MusicArtistSelectorHook.navigateToSearch(Context context, String artist) + invoke-static {p1, p2}, Lru/vtosters/hooks/music/MusicArtistSelectorHook;->navigateToSearch(Landroid/content/Context;Ljava/lang/String;)V # new-instance v0, Lcom/vk/music/search/MusicSearchFragment$a; # diff --git a/smali/smali_classes3/com/vk/music/bottomsheets/playlist/PlaylistBottomSheet.smali b/smali/smali_classes3/com/vk/music/bottomsheets/playlist/PlaylistBottomSheet.smali index 26684c314..19b383dc5 100644 --- a/smali/smali_classes3/com/vk/music/bottomsheets/playlist/PlaylistBottomSheet.smali +++ b/smali/smali_classes3/com/vk/music/bottomsheets/playlist/PlaylistBottomSheet.smali @@ -466,7 +466,7 @@ :cond_8 iget-object v2, v0, Lcom/vk/music/bottomsheets/playlist/PlaylistBottomSheet;->b:Lcom/vk/dto/music/Playlist; - invoke-static {v1, v2}, Lru/vtosters/lite/music/hook/MusicBottomSheetHook;->hook(Ljava/util/ArrayList;Lcom/vk/dto/music/Playlist;)Ljava/util/ArrayList; + invoke-static {v1, v2}, Lru/vtosters/hooks/music/MusicBottomSheetHook;->hook(Ljava/util/ArrayList;Lcom/vk/dto/music/Playlist;)Ljava/util/ArrayList; move-result-object v1 diff --git a/smali/smali_classes3/com/vk/music/bottomsheets/playlist/PlaylistBottomSheetClickListener.smali b/smali/smali_classes3/com/vk/music/bottomsheets/playlist/PlaylistBottomSheetClickListener.smali index f75b56f56..5da56227d 100644 --- a/smali/smali_classes3/com/vk/music/bottomsheets/playlist/PlaylistBottomSheetClickListener.smali +++ b/smali/smali_classes3/com/vk/music/bottomsheets/playlist/PlaylistBottomSheetClickListener.smali @@ -139,7 +139,7 @@ move-result p1 - invoke-static {p1, p2}, Lru/vtosters/lite/music/hook/MusicBottomSheetHook;->injectOnClick(ILcom/vk/dto/music/Playlist;)Z + invoke-static {p1, p2}, Lru/vtosters/hooks/music/MusicBottomSheetHook;->injectOnClick(ILcom/vk/dto/music/Playlist;)Z move-result v3 @@ -162,7 +162,7 @@ iget-object v0, p0, Lcom/vk/music/bottomsheets/playlist/PlaylistBottomSheetClickListener;->e:Lcom/vk/dto/music/Playlist; - invoke-static/range {v0 .. v0}, Lru/vtosters/lite/music/cache/injectors/PlaylistInjector;->injectDownloadPlaylist(Lcom/vk/dto/music/Playlist;)V + invoke-static/range {v0 .. v0}, Lru/vtosters/hooks/music/injectors/PlaylistInjector;->injectDownloadPlaylist(Lcom/vk/dto/music/Playlist;)V goto/16 :goto_0 diff --git a/smali/smali_classes3/com/vk/music/bottomsheets/track/MusicTrackBottomSheetClickListener.smali b/smali/smali_classes3/com/vk/music/bottomsheets/track/MusicTrackBottomSheetClickListener.smali index 512d59286..cac44e95c 100644 --- a/smali/smali_classes3/com/vk/music/bottomsheets/track/MusicTrackBottomSheetClickListener.smali +++ b/smali/smali_classes3/com/vk/music/bottomsheets/track/MusicTrackBottomSheetClickListener.smali @@ -716,7 +716,7 @@ move-result-object v3 - invoke-static {p1, v0, v2, v3}, Lru/vtosters/lite/music/hook/MusicBottomSheetHook;->injectOnClick(ILcom/vk/dto/music/MusicTrack;Lcom/vk/music/common/MusicPlaybackLaunchContext;Lcom/vk/dto/music/Playlist;)Z + invoke-static {p1, v0, v2, v3}, Lru/vtosters/hooks/music/MusicBottomSheetHook;->injectOnClick(ILcom/vk/dto/music/MusicTrack;Lcom/vk/music/common/MusicPlaybackLaunchContext;Lcom/vk/dto/music/Playlist;)Z move-result v3 diff --git a/smali/smali_classes3/com/vk/music/bottomsheets/track/d/VkMusicTrackBottomSheetActionsFactory.smali b/smali/smali_classes3/com/vk/music/bottomsheets/track/d/VkMusicTrackBottomSheetActionsFactory.smali index dde784927..f7a823ba2 100644 --- a/smali/smali_classes3/com/vk/music/bottomsheets/track/d/VkMusicTrackBottomSheetActionsFactory.smali +++ b/smali/smali_classes3/com/vk/music/bottomsheets/track/d/VkMusicTrackBottomSheetActionsFactory.smali @@ -111,7 +111,7 @@ move-object/from16 v10, p1 - invoke-static {v1, v10}, Lru/vtosters/lite/music/hook/MusicBottomSheetHook;->hookDownloadBTN(Ljava/util/ArrayList;Lcom/vk/dto/music/MusicTrack;)Ljava/util/ArrayList; + invoke-static {v1, v10}, Lru/vtosters/hooks/music/MusicBottomSheetHook;->hookDownloadBTN(Ljava/util/ArrayList;Lcom/vk/dto/music/MusicTrack;)Ljava/util/ArrayList; move-result-object v1 @@ -705,7 +705,7 @@ .line 31 :cond_9 - invoke-static {v3, v1}, Lru/vtosters/lite/music/hook/MusicBottomSheetHook;->hook(Ljava/util/ArrayList;Lcom/vk/dto/music/MusicTrack;)Ljava/util/ArrayList; + invoke-static {v3, v1}, Lru/vtosters/hooks/music/MusicBottomSheetHook;->hook(Ljava/util/ArrayList;Lcom/vk/dto/music/MusicTrack;)Ljava/util/ArrayList; move-result-object v3 diff --git a/smali/smali_classes3/com/vk/music/e/MusicCatalogConfiguration.smali b/smali/smali_classes3/com/vk/music/e/MusicCatalogConfiguration.smali index e8c1f541d..66e4786d6 100644 --- a/smali/smali_classes3/com/vk/music/e/MusicCatalogConfiguration.smali +++ b/smali/smali_classes3/com/vk/music/e/MusicCatalogConfiguration.smali @@ -65,7 +65,7 @@ } .end annotation - invoke-static {}, Lru/vtosters/lite/music/cache/injectors/TracklistInjector;->eligibleForOfflineCaching()Z + invoke-static {}, Lru/vtosters/hooks/music/injectors/TracklistInjector;->eligibleForOfflineCaching()Z move-result v0 @@ -75,7 +75,7 @@ move-result-object v0 - invoke-static {v0}, Lru/vtosters/lite/music/cache/injectors/TracklistInjector;->createOfflineRx(Lcom/vk/catalog2/core/CatalogParser;)Lio/reactivex/Observable; + invoke-static {v0}, Lru/vtosters/hooks/music/injectors/TracklistInjector;->createOfflineRx(Lcom/vk/catalog2/core/CatalogParser;)Lio/reactivex/Observable; move-result-object v0 diff --git a/smali/smali_classes3/com/vk/music/model/IpcPlayerModelImpl.smali b/smali/smali_classes3/com/vk/music/model/IpcPlayerModelImpl.smali index 3e1e5f2d0..f8bb63069 100644 --- a/smali/smali_classes3/com/vk/music/model/IpcPlayerModelImpl.smali +++ b/smali/smali_classes3/com/vk/music/model/IpcPlayerModelImpl.smali @@ -1780,13 +1780,13 @@ .line 90 :cond_3 - invoke-static {}, Lru/vtosters/lite/music/cache/injectors/TracklistInjector;->eligibleForOfflineCaching()Z + invoke-static {}, Lru/vtosters/hooks/music/injectors/TracklistInjector;->eligibleForOfflineCaching()Z move-result v0 if-eqz v0, :cond_4 - invoke-static {p0}, Lru/vtosters/lite/music/cache/injectors/ShuffleInjector;->shuffleTracks(Lcom/vk/music/model/IpcPlayerModelImpl;)V + invoke-static {p0}, Lru/vtosters/hooks/music/injectors/ShuffleInjector;->shuffleTracks(Lcom/vk/music/model/IpcPlayerModelImpl;)V return-void diff --git a/smali/smali_classes3/com/vk/music/model/PlayerModelImpl.smali b/smali/smali_classes3/com/vk/music/model/PlayerModelImpl.smali index c3defb98a..cce388043 100644 --- a/smali/smali_classes3/com/vk/music/model/PlayerModelImpl.smali +++ b/smali/smali_classes3/com/vk/music/model/PlayerModelImpl.smali @@ -1362,13 +1362,13 @@ .line 44 :cond_0 - invoke-static {}, Lru/vtosters/lite/music/cache/injectors/TracklistInjector;->eligibleForOfflineCaching()Z + invoke-static {}, Lru/vtosters/hooks/music/injectors/TracklistInjector;->eligibleForOfflineCaching()Z move-result v0 if-eqz v0, :cond_1 - invoke-static {p0}, Lru/vtosters/lite/music/cache/injectors/ShuffleInjector;->shuffleTracks(Lcom/vk/music/model/PlayerModelImpl;)V + invoke-static {p0}, Lru/vtosters/hooks/music/injectors/ShuffleInjector;->shuffleTracks(Lcom/vk/music/model/PlayerModelImpl;)V return-void diff --git a/smali/smali_classes3/com/vk/music/playlist/ModernPlaylistModel.smali b/smali/smali_classes3/com/vk/music/playlist/ModernPlaylistModel.smali index a67da6df5..df3073377 100644 --- a/smali/smali_classes3/com/vk/music/playlist/ModernPlaylistModel.smali +++ b/smali/smali_classes3/com/vk/music/playlist/ModernPlaylistModel.smali @@ -239,7 +239,7 @@ const/4 v1, 0x1 - invoke-static {p1}, Lru/vtosters/lite/music/cache/injectors/PlaylistInjector;->injectGetPlaylist(Lcom/vk/api/audio/AudioGetPlaylist;)Lio/reactivex/Observable; + invoke-static {p1}, Lru/vtosters/hooks/music/injectors/PlaylistInjector;->injectGetPlaylist(Lcom/vk/api/audio/AudioGetPlaylist;)Lio/reactivex/Observable; move-result-object v2 diff --git a/smali/smali_classes3/com/vk/music/playlist/modern/MusicPlaylistContract4.smali b/smali/smali_classes3/com/vk/music/playlist/modern/MusicPlaylistContract4.smali index c317eaef5..4656625f5 100644 --- a/smali/smali_classes3/com/vk/music/playlist/modern/MusicPlaylistContract4.smali +++ b/smali/smali_classes3/com/vk/music/playlist/modern/MusicPlaylistContract4.smali @@ -593,7 +593,7 @@ move-result-object v0 - invoke-static/range {v0 .. v0}, Lru/vtosters/lite/music/cache/injectors/PlaylistInjector;->injectDownloadPlaylist(Lcom/vk/dto/music/Playlist;)V + invoke-static/range {v0 .. v0}, Lru/vtosters/hooks/music/injectors/PlaylistInjector;->injectDownloadPlaylist(Lcom/vk/dto/music/Playlist;)V return-void diff --git a/smali/smali_classes3/com/vk/music/ui/common/formatting/PlaylistFormatter.smali b/smali/smali_classes3/com/vk/music/ui/common/formatting/PlaylistFormatter.smali index 4182a600f..d335c6b78 100644 --- a/smali/smali_classes3/com/vk/music/ui/common/formatting/PlaylistFormatter.smali +++ b/smali/smali_classes3/com/vk/music/ui/common/formatting/PlaylistFormatter.smali @@ -423,9 +423,9 @@ .end annotation .end param - # ru.vtosters.lite.music.hook.PlaylistFormatterHook -> public static CharSequence titleAndSubtitleMergeFix(Context context, Playlist playlist, @AttrRes int i) + # ru.vtosters.hooks.music.PlaylistFormatterHook -> public static CharSequence titleAndSubtitleMergeFix(Context context, Playlist playlist, @AttrRes int i) .line 15 - invoke-static {p1, p2, p3}, Lru/vtosters/lite/music/hook/PlaylistFormatterHook;->titleAndSubtitleMergeFix(Landroid/content/Context;Lcom/vk/dto/music/Playlist;I)Ljava/lang/CharSequence; + invoke-static {p1, p2, p3}, Lru/vtosters/hooks/music/PlaylistFormatterHook;->titleAndSubtitleMergeFix(Landroid/content/Context;Lcom/vk/dto/music/Playlist;I)Ljava/lang/CharSequence; move-result-object p1 @@ -888,7 +888,7 @@ if-eqz v0, :cond_1 - invoke-static {p1, p2}, Lru/vtosters/lite/music/hook/PlaylistFormatterHook;->getOwnerText(Landroid/content/Context;Lcom/vk/dto/music/Playlist;)Ljava/lang/String; + invoke-static {p1, p2}, Lru/vtosters/hooks/music/PlaylistFormatterHook;->getOwnerText(Landroid/content/Context;Lcom/vk/dto/music/Playlist;)Ljava/lang/String; move-result-object p1 diff --git a/smali/smali_classes4/com/vtosters/lite/audio/player/CacheUtils.smali b/smali/smali_classes4/com/vtosters/lite/audio/player/CacheUtils.smali index 49e233fa5..506287c61 100644 --- a/smali/smali_classes4/com/vtosters/lite/audio/player/CacheUtils.smali +++ b/smali/smali_classes4/com/vtosters/lite/audio/player/CacheUtils.smali @@ -112,14 +112,14 @@ .method static a(Ljava/lang/String;)Ljava/io/File; .locals 6 - invoke-static {p0}, Lru/vtosters/hooks/MusicCacheFilesHook;->isTrackExist(Ljava/lang/String;)Z + invoke-static {p0}, Lru/vtosters/hooks/music/MusicCacheFilesHook;->isTrackExist(Ljava/lang/String;)Z move-result v5 if-eqz v5, :cond_228 .line 5 - invoke-static {p0}, Lru/vtosters/hooks/MusicCacheFilesHook;->getTrackFile(Ljava/lang/String;)Ljava/io/File; + invoke-static {p0}, Lru/vtosters/hooks/music/MusicCacheFilesHook;->getTrackFile(Ljava/lang/String;)Ljava/io/File; move-result-object v0