diff --git a/app/src/main/java/ru/vtosters/hooks/music/MusicBottomSheetHook.java b/app/src/main/java/ru/vtosters/hooks/music/MusicBottomSheetHook.java index ad050c7b1..4aaa8299e 100644 --- a/app/src/main/java/ru/vtosters/hooks/music/MusicBottomSheetHook.java +++ b/app/src/main/java/ru/vtosters/hooks/music/MusicBottomSheetHook.java @@ -9,6 +9,7 @@ import com.vk.music.common.MusicPlaybackLaunchContext; import com.vtosters.lite.R; import ru.vtosters.lite.downloaders.AudioDownloader; import ru.vtosters.lite.music.cache.MusicCacheImpl; +import ru.vtosters.lite.music.cache.delegate.PlaylistCacheDbDelegate; import ru.vtosters.lite.utils.AndroidUtils; import ru.vtosters.lite.utils.music.VKXUtils; @@ -19,18 +20,29 @@ import static ru.vtosters.hooks.other.Preferences.milkshake; import static ru.vtosters.lite.utils.NetworkUtils.isNetworkConnected; public class MusicBottomSheetHook { - public static ArrayList hook(final ArrayList actions, final MusicTrack musicTrack) { + public static ArrayList hook(ArrayList actions, MusicTrack musicTrack) { if (musicTrack.F1()) return actions; - final var trackId = asId(musicTrack); + + String trackId = asId(musicTrack); + if (isVkxInstalled()) actions.add(getPlayInVKXAction()); + if (isIntegrationEnabled()) { - if (VKXUtils.isVkxCached(trackId)) actions.add(getRemoveCacheTrackVkxAction()); - else if (isNetworkConnected()) actions.add(addToCacheTrackVkxAction()); + if (VKXUtils.isVkxCached(trackId)) { + actions.add(getRemoveCacheTrackVkxAction()); + } else if (isNetworkConnected()) { + actions.add(addToCacheTrackVkxAction()); + } } else { - if (MusicCacheImpl.isCachedTrack(trackId)) actions.add(getRemoveCacheTrackAction()); - else if (isNetworkConnected()) actions.add(addToCacheTrackAction()); + if (MusicCacheImpl.isCachedTrack(trackId)) { + actions.add(getRemoveCacheTrackAction()); + } else if (isNetworkConnected()) { + actions.add(addToCacheTrackAction()); + } } + if (isNetworkConnected()) actions.add(downloadAsMp3Action()); + return actions; } @@ -72,8 +84,12 @@ public class MusicBottomSheetHook { } else if (isNetworkConnected()) { actions.add(addToCacheTrackVkxAction()); } - } else if (isNetworkConnected()) { - actions.add(addToCacheTrackAction()); + } else { + if (PlaylistCacheDbDelegate.isCachedPlaylist(AndroidUtils.getGlobalContext(), playlist.v1())) { + actions.add(getRemoveCacheTrackAction()); + } else if (isNetworkConnected()) { + actions.add(addToCacheTrackAction()); + } } if (isNetworkConnected()) actions.add(downloadAsMp3Action()); @@ -134,20 +150,24 @@ public class MusicBottomSheetHook { } public static boolean injectOnClick(int actionId, Playlist playlist) { // playlist inj - if (actionId == R.id.play_in_vkx) + if (actionId == R.id.play_in_vkx) { return tryPlayInVKX(null, null, playlist); - - if (actionId == R.id.add_to_cache) { + } + + if (actionId == R.id.remove_from_cache) { + PlaylistCacheDbDelegate.deletePlaylist(AndroidUtils.getGlobalContext(), playlist.v1()); + return true; + } else if (actionId == R.id.add_to_cache) { AudioDownloader.cachePlaylist(playlist); return true; } - + if (actionId == R.id.remove_from_cache_vkx) { getInstance().runOnService(service -> { try { service.deletePlaylistFromCache(playlist.a, playlist.b); } catch (RemoteException e) { - e.printStackTrace(); + e.fillInStackTrace(); } }); return true; @@ -156,7 +176,7 @@ public class MusicBottomSheetHook { try { service.addPlaylistToCache(playlist.a, playlist.b, (playlist.Q != null) ? playlist.Q : ""); } catch (RemoteException e) { - e.printStackTrace(); + e.fillInStackTrace(); } }); return true; @@ -168,7 +188,7 @@ public class MusicBottomSheetHook { try { service.downloadPlaylist(playlist.a, playlist.b, (playlist.Q != null) ? playlist.Q : ""); } catch (RemoteException e) { - e.printStackTrace(); + e.fillInStackTrace(); } }); } else { @@ -258,4 +278,4 @@ public class MusicBottomSheetHook { false ); } -} +} \ No newline at end of file diff --git a/app/src/main/java/ru/vtosters/hooks/music/injectors/CatalogJsonInjector.java b/app/src/main/java/ru/vtosters/hooks/music/injectors/CatalogJsonInjector.java index c16e648bf..b45260887 100644 --- a/app/src/main/java/ru/vtosters/hooks/music/injectors/CatalogJsonInjector.java +++ b/app/src/main/java/ru/vtosters/hooks/music/injectors/CatalogJsonInjector.java @@ -11,6 +11,7 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import ru.vtosters.hooks.other.Preferences; +import ru.vtosters.lite.music.cache.helpers.PlaylistHelper; import ru.vtosters.lite.music.cache.MusicCacheImpl; import ru.vtosters.lite.utils.AccountManagerUtils; import ru.vtosters.lite.utils.AndroidUtils; @@ -51,19 +52,13 @@ public class CatalogJsonInjector { if (!MusicCacheImpl.isEmpty() && !LibVKXClient.isIntegrationEnabled()) { // inj in playlist list var noPlaylists = !json.has("playlists"); - if (noPlaylists) { - json.put("playlists", new JSONArray().put(getPlaylist())); - Log.d("catalogInjector", "added new pl"); - } else { - json.optJSONArray("playlists").put(getPlaylist()); - Log.d("catalogInjector", "added to exist pl"); - } + PlaylistHelper.addCachedPlaylists(json.optJSONArray("playlists"), noPlaylists); if (!useOldAppVer || noPlaylists) { var newBlocks = new JSONArray(); newBlocks - .put(getCatalogHeader()) + .put(getCatalogHeader("Кешированные плейлисты")) .put(getCatalogPlaylist()) .put(getCatalogSeparator()); @@ -98,23 +93,13 @@ public class CatalogJsonInjector { var noPlaylists = !json.has("playlists"); - if (noPlaylists) { - json.put("playlists", new JSONArray().put(getPlaylist())); - Log.d("catalogInjector", "added new pl"); - } else { - try { - json.optJSONArray("playlists").put(getPlaylist()); - Log.d("catalogInjector", "added to exist pl"); - } catch (Exception e) { - Log.d("catalogInjector", e.toString()); - } - } + PlaylistHelper.addCachedPlaylists(json.optJSONArray("playlists"), noPlaylists); if (!useOldAppVer && !noPlaylists) { var newBlocks = new JSONArray(); newBlocks - .put(getCatalogHeader()) + .put(getCatalogHeader("Кешированные плейлисты")) .put(getCatalogPlaylist()) .put(getCatalogSeparator()); @@ -132,12 +117,15 @@ public class CatalogJsonInjector { if (type.equals("music_playlists") && j.has("playlists_ids")) { var newarr = new JSONArray(); - var playlists_ids = j.optJSONArray("playlists_ids"); + var playlists_ids = j.getJSONArray("playlists_ids"); + var savedPlaylists = PlaylistHelper.getCachedPlaylistsIds(); - newarr.put(getUserId() + "_-1"); + for (int n = 0; n < savedPlaylists.length(); n++) { + newarr.put(savedPlaylists.getString(n)); + } for (int n = 0; n < playlists_ids.length(); n++) { - newarr.put(playlists_ids.optString(n)); + newarr.put(playlists_ids.getString(n)); } Log.d("catalogInjector", "added to pl ids"); @@ -220,27 +208,33 @@ public class CatalogJsonInjector { } private static void removeUnsupportedLayouts(JSONArray blocks) throws JSONException { - for (int i = 0; i < blocks.length(); i++) { - var block = blocks.optJSONObject(i); - var layout = block.optJSONObject("layout"); - var data_type = block.optString("data_type"); + for (int i = blocks.length() - 1; i >= 0; i--) { + JSONObject block = blocks.optJSONObject(i); + JSONObject layout = block.optJSONObject("layout"); + String data_type = block.optString("data_type"); - if (Objects.equals(data_type, "music_recommended_playlists")) { + if (Objects.equals(data_type, "music_recommended_playlists") || Objects.equals(data_type, "radiostations")) { + blocks.remove(i); // block + if (i - 1 >= 0) blocks.remove(i - 1); // text + if (i - 2 >= 0) blocks.remove(i - 2); // divider + continue; // Skip to the next iteration after removing + } + + if (layout == null) continue; // Skip to the next iteration if layout is null + + String name = layout.optString("name"); + if (!name.equals("header_extended")) { + continue; // Skip to the next iteration if not header_extended + } + + if (layout.has("top_title")) { blocks.remove(i); - blocks.remove(i - 1); - blocks.remove(i - 2); - } - - if (layout != null) { - var name = layout.optString("name"); - if (name.equals("header_extended")) { - if (layout.has("top_title")) blocks.remove(i); - layout.put("name", "header"); - } } + layout.put("name", "header"); } } + private static void setDefaultAudioPage(JSONArray jsonArray, JSONObject catalog) throws JSONException { for (int i = 0; i < jsonArray.length(); i++) { var item = jsonArray.optJSONObject(i); @@ -273,7 +267,9 @@ public class CatalogJsonInjector { + "&access_token=" + AccountManagerUtils.getUserToken()) .a("Accept-Encoding", "gzip") - .a(Headers.a("User-Agent", Network.l.c().a(), "Content-Type", "application/x-www-form-urlencoded; charset=utf-8")).a(); + .a("User-Agent", Network.l.c().a()) + .a("Content-Type", "application/x-www-form-urlencoded; charset=utf-8") + .a(); try (Response resp = Network.b(CLIENT_API).a(request).execute()) { var json = new JSONObject(GzipDecompressor.decompressResponse(resp)).getJSONObject("response"); var catalogarr = json.optJSONObject("catalog").optJSONArray("sections").optJSONObject(0); diff --git a/app/src/main/java/ru/vtosters/hooks/music/injectors/PlaylistInjector.java b/app/src/main/java/ru/vtosters/hooks/music/injectors/PlaylistInjector.java index d01149529..dd9d1984a 100644 --- a/app/src/main/java/ru/vtosters/hooks/music/injectors/PlaylistInjector.java +++ b/app/src/main/java/ru/vtosters/hooks/music/injectors/PlaylistInjector.java @@ -18,8 +18,12 @@ import ru.vtosters.lite.music.cache.helpers.TracklistHelper; import java.util.ArrayList; import java.util.List; +import java.util.Objects; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; public class PlaylistInjector { + private static final ExecutorService executor = Executors.newCachedThreadPool(); public final static String CHANNEL_NAME = "VTCH"; public static void injectDownloadPlaylist(Playlist playlist) { @@ -32,7 +36,7 @@ public class PlaylistInjector { public static Observable injectGetPlaylist(AudioGetPlaylist audioGetPlaylist) { try { - if (!eligibleForOfflineCaching()) + if (MusicCacheImpl.isEmpty()) return null; var requestArgs = audioGetPlaylist.b(); @@ -42,8 +46,6 @@ public class PlaylistInjector { boolean isVirtualPlaylist = accessKey != null && (accessKey.equals("cache")); boolean isAlbumVirtualPlaylist = accessKey != null && (accessKey.equals("cacheAlbum")); - Log.d("PlaylistInjector", "id = " + id + " / owner = " + ownerId + " / access = " + accessKey + " / " + isVirtualPlaylist + " + " + isAlbumVirtualPlaylist); - if (TextUtils.isEmpty(id) || (!isVirtualPlaylist && !isAlbumVirtualPlaylist)) return null; @@ -70,7 +72,7 @@ public class PlaylistInjector { response.c = tracks; } catch (RemoteException | JSONException e) { - e.printStackTrace(); + e.fillInStackTrace(); response.c = new ArrayList<>(); } observableEmitter.b(response); @@ -79,18 +81,20 @@ public class PlaylistInjector { return; } - if (isVirtualPlaylist) { - response.c = (ArrayList) TracklistHelper.getMyCachedMusicTracks(); + response.c = (ArrayList) MusicCacheImpl.getPlaylistSongs(ownerId, id); + + Log.d("Playlist", "Open playlist " + ownerId + "_" + id); + + if (Objects.equals(id, "-1")) { response.b = PlaylistHelper.createCachedPlaylistMetadata(); } else { - response.c = (ArrayList) TracklistHelper.getTracks(requestArgs.get("owner_id")); - response.b = PlaylistHelper.createCachedPlaylistMetadata(requestArgs.get("owner_id")); + response.b = MusicCacheImpl.getPlaylist(id, ownerId); } observableEmitter.b(response); }); } catch (Exception e) { - e.printStackTrace(); + e.fillInStackTrace(); } return null; } diff --git a/app/src/main/java/ru/vtosters/hooks/music/injectors/TracklistInjector.java b/app/src/main/java/ru/vtosters/hooks/music/injectors/TracklistInjector.java index dbf7f7ba8..8ed829e35 100644 --- a/app/src/main/java/ru/vtosters/hooks/music/injectors/TracklistInjector.java +++ b/app/src/main/java/ru/vtosters/hooks/music/injectors/TracklistInjector.java @@ -12,6 +12,7 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import ru.vtosters.lite.music.cache.MusicCacheImpl; +import ru.vtosters.lite.music.cache.delegate.PlaylistCacheDbDelegate; import ru.vtosters.lite.music.cache.helpers.PlaylistHelper; import ru.vtosters.lite.music.cache.helpers.TracklistHelper; import ru.vtosters.lite.utils.AccountManagerUtils; @@ -22,6 +23,8 @@ import java.util.ArrayList; import java.util.List; import java.util.UUID; +import static ru.vtosters.lite.music.cache.helpers.PlaylistHelper.*; + public class TracklistInjector { public static boolean eligibleForOfflineCaching() { @@ -59,7 +62,9 @@ public class TracklistInjector { if (MusicCacheImpl.isEmpty()) { observableEmitter.b(parser.c(getEmptyCatalog())); } else { - observableEmitter.b(parser.c(createVirtualCatalog(TracklistHelper.getTracks()))); + observableEmitter.b(parser.c(createVirtualCatalog( + PlaylistCacheDbDelegate.getTracksInPlaylist(AndroidUtils.getGlobalContext(), + AccountManagerUtils.getUserId() + "_-1")))); } } }); @@ -162,16 +167,12 @@ public class TracklistInjector { boolean noPlaylists = hasNoPlaylists(blocks); if (noPlaylists) { - catalogNode.put("playlists", new JSONArray().put(PlaylistHelper.getPlaylist())); + PlaylistHelper.addCachedPlaylists(catalogNode.optJSONArray("playlists"), true); - JSONArray newBlocks = createNewBlocks(blocks); - - Log.d("catalogInjector", "added cache catalog playlist"); - - firstSection.put("blocks", newBlocks); + firstSection.put("blocks", createNewBlocks(blocks)); } else { try { - catalogNode.optJSONArray("playlists").put(PlaylistHelper.getPlaylist()); + PlaylistHelper.addCachedPlaylists(catalogNode.optJSONArray("playlists"), false); updatePlaylistsIds(blocks); } catch (Exception e) { @@ -188,7 +189,7 @@ public class TracklistInjector { return firstSection.getString("url").equals("https://vk.com/audios" + AccountManagerUtils.getUserId() + "?section=all"); } - private static boolean hasNoPlaylists(JSONArray blocks) throws JSONException { + private static boolean hasNoPlaylists(JSONArray blocks) { return blocks.optJSONObject(0).optJSONArray("buttons").optJSONObject(0).optJSONObject("action").optString("type").equals("create_playlist"); } @@ -196,13 +197,14 @@ public class TracklistInjector { JSONArray newBlocks = new JSONArray(); newBlocks - .put(PlaylistHelper.getCatalogHeader()) + .put(PlaylistHelper.getCatalogHeader(AndroidUtils.getString(R.string.cached_tracks_title))) .put(PlaylistHelper.getCatalogPlaylist()) .put(PlaylistHelper.getCatalogSeparator()); for (int i = 0; i < blocks.length(); i++) { newBlocks.put(blocks.optJSONObject(i)); } + return newBlocks; } @@ -212,16 +214,15 @@ public class TracklistInjector { String type = j.optString("data_type"); if (type.equals("music_playlists") && j.has("playlists_ids")) { - JSONArray newarr = new JSONArray(); + JSONArray newarr = PlaylistHelper.getCachedPlaylistsIds(); JSONArray playlists_ids = j.optJSONArray("playlists_ids"); - newarr.put(AccountManagerUtils.getUserId() + "_-1"); - for (int n = 0; n < playlists_ids.length(); n++) { newarr.put(playlists_ids.optString(n)); } j.put("playlists_ids", newarr); + break; } } @@ -240,7 +241,19 @@ public class TracklistInjector { audiosBlock.put("layout", layout); - var blocks = new JSONArray().put(getShuffleButton()).put(audiosBlock); + var blocks = new JSONArray(); + + if (MusicCacheImpl.hasPlaylist()) { + blocks.put(getCatalogHeader("Кешированные плейлисты")) + .put(getCatalogPlaylist()); + } + + if (!MusicCacheImpl.getAllOwnTracks().isEmpty()) { + if (MusicCacheImpl.hasPlaylist()) blocks.put(getCatalogSeparator()); + blocks.put(getCatalogHeader(AndroidUtils.getString(R.string.cached_tracks_title))) + .put(getShuffleButton()) + .put(audiosBlock); + } var randomId = getRandomId(); @@ -259,6 +272,7 @@ public class TracklistInjector { return new JSONObject() .put("catalog", catalog) + .put("playlists", addCachedPlaylists(new JSONArray(), false)) .put("audios", TracklistHelper.tracksToJsons(tracks)); } 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 668db7be0..2f5c183e3 100644 --- a/app/src/main/java/ru/vtosters/lite/downloaders/AudioDownloader.java +++ b/app/src/main/java/ru/vtosters/lite/downloaders/AudioDownloader.java @@ -1,27 +1,35 @@ package ru.vtosters.lite.downloaders; import android.os.Environment; +import android.util.Log; +import androidx.core.app.NotificationCompat; 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 java8.util.concurrent.CompletableFuture; +import org.json.JSONObject; import ru.vtosters.hooks.music.MusicCacheFilesHook; import ru.vtosters.hooks.other.Preferences; import ru.vtosters.lite.music.cache.MusicCacheImpl; +import ru.vtosters.lite.music.cache.delegate.PlaylistCacheDbDelegate; +import ru.vtosters.lite.music.cache.helpers.PlaylistHelper; 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.ThumbnailPlaylistDownloader; import ru.vtosters.lite.music.downloader.TrackDownloader; +import ru.vtosters.lite.music.interfaces.Callback; 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 ru.vtosters.lite.utils.music.PlaylistUtils; import java.io.File; +import java.util.List; import java.util.Objects; import static ru.vtosters.lite.utils.AndroidUtils.getString; @@ -35,18 +43,18 @@ import static ru.vtosters.lite.utils.AndroidUtils.getString; public class AudioDownloader { public static final String dlpath = Preferences.getBoolValue("dldir", false) ? Environment.DIRECTORY_DOWNLOADS : Environment.DIRECTORY_MUSIC; - public static CompletableFuture downloadPlaylist(Playlist playlist) { - var tracks = PlaylistConverter.getPlaylist(playlist); + public static void downloadPlaylist(Playlist playlist) { + List tracks = PlaylistConverter.getPlaylist(playlist); - var playlistName = IOUtils.getValidFileName(playlist.g); + String playlistName = IOUtils.getValidFileName(playlist.g); - var musicPath = Environment.getExternalStoragePublicDirectory(dlpath).getAbsolutePath(); - var downloadPath = musicPath + File.separator + playlistName; + String musicPath = Environment.getExternalStoragePublicDirectory(dlpath).getAbsolutePath(); + String downloadPath = musicPath + File.separator + playlistName; - var notificationId = playlistName.hashCode(); - var notification = MusicNotificationBuilder.buildPlaylistDownloadNotification(playlistName, notificationId); + int notificationId = playlistName.hashCode(); + NotificationCompat.Builder notification = MusicNotificationBuilder.buildPlaylistDownloadNotification(playlistName, notificationId); - return PlaylistDownloader.downloadPlaylist( + PlaylistDownloader.downloadPlaylist( tracks, IOUtils.getValidFileName(playlist.g), downloadPath, @@ -60,53 +68,96 @@ public class AudioDownloader { } public static void cacheTrack(MusicTrack track) { - var trackId = LibVKXClient.asId(track); + String trackId = LibVKXClient.asId(track); if (MusicCacheImpl.isCachedTrack(trackId)) { MusicCacheImpl.removeTrack(trackId); AndroidUtils.sendToast(AndroidUtils.getString("audio_deleted_from_cache")); } else { - var trackFile = MusicCacheFilesHook.getTrackFile(trackId); - if (!trackFile.exists()) + File trackFile = MusicCacheFilesHook.getTrackFile(trackId); + if (!trackFile.exists()) { trackFile.getParentFile().mkdirs(); + } downloadM3U8(track, true); } } - public static CompletableFuture cachePlaylist(Playlist playlist) { - var tracks = PlaylistConverter.getPlaylist(playlist); + public static void cachePlaylist(Playlist playlist) { + List tracks = PlaylistConverter.getPlaylist(playlist); - var notificationId = playlist.g.hashCode(); - var notification = MusicNotificationBuilder.buildPlaylistDownloadNotification(playlist.g, notificationId); + int notificationId = playlist.g.hashCode(); + NotificationCompat.Builder notification = MusicNotificationBuilder.buildPlaylistDownloadNotification(playlist.g, notificationId); - return PlaylistDownloader.cachePlaylist( + JSONObject thumbs = PlaylistUtils.getThumb(playlist); + + if (thumbs != null) { + new ThumbnailPlaylistDownloader().download(null, new Callback() { + @Override + public void onProgress(int progress) { + + } + + @Override + public void onSuccess() { + PlaylistCacheDbDelegate.addPlaylist(AndroidUtils.getGlobalContext(), playlist); + + Log.d("Playlist", "adding to cache with thumbs " + playlist.v1()); + } + + @Override + public void onFailure(Throwable e) { + throw new RuntimeException(e); + } + + @Override + public void onSizeReceived(long size, long header) { + + } + }, playlist); + } else { + PlaylistCacheDbDelegate.addPlaylist(AndroidUtils.getGlobalContext(), playlist); + + Log.d("Playlist", "adding to cache without thumbs " + playlist.a); + } + + PlaylistDownloader.cachePlaylist( tracks, - MusicCallbackBuilder.buildPlaylistCallback(tracks.size(), notification, notificationId) + MusicCallbackBuilder.buildPlaylistCallback(tracks.size(), notification, notificationId), + playlist ); } public static void cacheAllAudios() { - var tracks = AudioGet.getAudios(); + List tracks = AudioGet.getAudios(); - var notificationId = AccountManagerUtils.getUserId(); - var notification = MusicNotificationBuilder.buildAllAudiosDownloadNotification(notificationId); + int notificationId = AccountManagerUtils.getUserId(); + NotificationCompat.Builder notification = MusicNotificationBuilder.buildAllAudiosDownloadNotification(notificationId); + + if (tracks == null || tracks.isEmpty()) { + return; + } PlaylistDownloader.cachePlaylist( tracks, - MusicCallbackBuilder.buildPlaylistCallback(tracks.size(), notification, notificationId) + MusicCallbackBuilder.buildPlaylistCallback(tracks.size(), notification, notificationId), + PlaylistHelper.createCachedPlaylistMetadata() ); } public static void downloadAllAudios() { - var tracks = AudioGet.getAudios(); + List tracks = AudioGet.getAudios(); - var notificationId = AccountManagerUtils.getUserId(); - var notification = MusicNotificationBuilder.buildAllAudiosDownloadNotification(notificationId); + int notificationId = AccountManagerUtils.getUserId(); + NotificationCompat.Builder notification = MusicNotificationBuilder.buildAllAudiosDownloadNotification(notificationId); - var playlistName = "Audios of " + AccountManagerUtils.getUserId(); + String playlistName = "Audios of " + AccountManagerUtils.getUserId(); - var musicPath = Environment.getExternalStoragePublicDirectory(dlpath).getAbsolutePath(); - var downloadPath = musicPath + File.separator + playlistName; + String musicPath = Environment.getExternalStoragePublicDirectory(dlpath).getAbsolutePath(); + String downloadPath = musicPath + File.separator + playlistName; + + if (tracks == null || tracks.isEmpty()) { + return; + } PlaylistDownloader.downloadPlaylist( tracks, @@ -116,21 +167,21 @@ public class AudioDownloader { ); } - private static void - downloadM3U8(MusicTrack track, boolean cache) { + private static void downloadM3U8(MusicTrack track, boolean cache) { Objects.requireNonNull(track.D, () -> { ToastUtils.a(getString(R.string.link_audio_error)); return "link must not be null"; }); - var musicPath = Environment.getExternalStoragePublicDirectory(dlpath).getAbsolutePath(); - var tempId = track.d; - var downloadPath = musicPath + File.separator; - var notification = MusicNotificationBuilder.buildDownloadNotification(track, tempId); + String musicPath = Environment.getExternalStoragePublicDirectory(dlpath).getAbsolutePath(); + int tempId = track.d; + String downloadPath = musicPath + File.separator; + NotificationCompat.Builder notification = MusicNotificationBuilder.buildDownloadNotification(track, tempId); - var cb = MusicCallbackBuilder.buildOneTrackCallback(tempId, notification); - if (cache) - TrackDownloader.cacheTrack(track, cb); - else + Callback cb = MusicCallbackBuilder.buildOneTrackCallback(tempId, notification); + if (cache) { + TrackDownloader.cacheTrack(track, cb, PlaylistHelper.createCachedPlaylistMetadata()); + } else { TrackDownloader.downloadTrack(track, downloadPath, cb); + } } -} +} \ No newline at end of file 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 index 453dca77e..edf93c27c 100644 --- a/app/src/main/java/ru/vtosters/lite/music/cache/MusicCacheImpl.java +++ b/app/src/main/java/ru/vtosters/lite/music/cache/MusicCacheImpl.java @@ -10,6 +10,10 @@ import java.util.List; import bruhcollective.itaysonlab.libvkx.ILibVkxService; import bruhcollective.itaysonlab.libvkx.client.LibVKXClient; import bruhcollective.itaysonlab.libvkx.client.LibVKXClientImpl; +import com.vk.dto.music.Playlist; +import ru.vtosters.lite.music.cache.delegate.MusicCacheDbDelegate; +import ru.vtosters.lite.music.cache.delegate.PlaylistCacheDbDelegate; +import ru.vtosters.lite.utils.AccountManagerUtils; import ru.vtosters.lite.utils.AndroidUtils; import ru.vtosters.lite.utils.music.MusicCacheStorageUtils; import ru.vtosters.lite.utils.music.MusicTrackUtils; @@ -34,20 +38,28 @@ public class MusicCacheImpl { MusicCacheStorageUtils.removeTrackDirById(trackId); } - public static List getAllTracks() { - return MusicCacheDbDelegate.getAllTracks(AndroidUtils.getGlobalContext()); + public static List getAllOwnTracks() { + return PlaylistCacheDbDelegate.getTracksInPlaylist(AndroidUtils.getGlobalContext(), AccountManagerUtils.getUserId() + "_-1"); } - public static List getAlbumById(String albumId) { - return MusicCacheDbDelegate.getAlbumById(AndroidUtils.getGlobalContext(), albumId); + public static List getPlaylistSongs(String owner_id, String playlist_id) { + return PlaylistCacheDbDelegate.getTracksInPlaylist(AndroidUtils.getGlobalContext(), owner_id + "_" + playlist_id); // get songs from playlist } - public static List getFirstAlbumTrack(String albumId) { - return MusicCacheDbDelegate.getFirstAlbumTrack(AndroidUtils.getGlobalContext(), albumId); + public static Playlist getPlaylist(String playlist_id, String owner_id) { + return getPlaylistById(owner_id + "_" + playlist_id); // get playlist from db } - public static List getPlaylist() { - return MusicCacheDbDelegate.getPlaylist(AndroidUtils.getGlobalContext()); + public static List getPlaylists() { + return PlaylistCacheDbDelegate.getAllPlaylists(AndroidUtils.getGlobalContext()); // get all cached playlists + } + + public static Playlist getPlaylistById(String query) { + return PlaylistCacheDbDelegate.getPlaylistById(AndroidUtils.getGlobalContext(), query); // get playlist from db + } + + public static boolean hasPlaylist() { + return PlaylistCacheDbDelegate.getPlaylistsCount(AndroidUtils.getGlobalContext()) != 0; // has playlists or not } public static long getTracksCount() { @@ -60,7 +72,7 @@ public class MusicCacheImpl { try { return (long) service.getCache().size(); } catch (RemoteException e) { - e.printStackTrace(); + e.fillInStackTrace(); return defaultValue(); } } @@ -82,6 +94,7 @@ public class MusicCacheImpl { public static void clear() { MusicCacheDbDelegate.drop(AndroidUtils.getGlobalContext()); + PlaylistCacheDbDelegate.drop(AndroidUtils.getGlobalContext()); MusicCacheStorageUtils.clear(); } -} +} \ No newline at end of file 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 index c998c0175..4fa02fc36 100644 --- 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 @@ -24,6 +24,8 @@ import java.net.URLDecoder; import java.util.ArrayList; import java.util.List; +import ru.vtosters.lite.music.cache.delegate.PlaylistCacheDbDelegate; +import ru.vtosters.lite.utils.AccountManagerUtils; import ru.vtosters.lite.utils.AndroidUtils; import ru.vtosters.lite.utils.music.MusicCacheStorageUtils; import ru.vtosters.lite.utils.music.MusicTrackUtils; @@ -127,6 +129,8 @@ public class MusicCacheDb extends SQLiteOpenHelper implements AutoCloseable { // vals.put(Constants.COLUMN_DURATION, track.h); vals.put(Constants.COLUMN_HAS_ARTWORK, track.I != null && track.I.u1() != null); db.insert(Constants.TABLE_NAME, null, vals); + + PlaylistCacheDbDelegate.addTrackToPlaylist(AndroidUtils.getGlobalContext(), AccountManagerUtils.getUserId() + "_-1", track.y1()); } } } @@ -170,18 +174,21 @@ public class MusicCacheDb extends SQLiteOpenHelper implements AutoCloseable { // return DatabaseUtils.queryNumEntries(getReadableDatabase(), Constants.TABLE_NAME); } + public MusicTrack getTrackById(String trackId) { + SQLiteDatabase db = getReadableDatabase(); + Cursor cursor = db.query(Constants.TABLE_NAME, null, Constants.COLUMN_TRACK_ID + " = ?", new String[]{trackId}, null, null, null); + MusicTrack track = null; + if (cursor.moveToFirst()) { + track = fromCursor(cursor); + } + cursor.close(); + return track; + } + public List getAllTracks() { return getTracksWithCursor(getReadableDatabase().query(Constants.TABLE_NAME, null, null, null, null, null, null)); } - public List getAlbum(String albumId) { - return getAlbumTracks(albumId, null); - } - - public List getFirstAlbumTrack(String albumId) { - return getAlbumTracks(albumId, "0,1"); - } - private List getAlbumTracks(String albumId, @Nullable String limit) { return getTracksWithCursor(getReadableDatabase().query( Constants.TABLE_NAME, @@ -220,7 +227,7 @@ public class MusicCacheDb extends SQLiteOpenHelper implements AutoCloseable { // @Retention(RetentionPolicy.SOURCE) public @interface Constants { - int DV_VERSION = 0x2; + int DV_VERSION = 0x3; String DB_NAME = "vt_lite_cache.db"; String TABLE_NAME = "tracks"; //columns diff --git a/app/src/main/java/ru/vtosters/lite/music/cache/db/PlaylistCacheDb.java b/app/src/main/java/ru/vtosters/lite/music/cache/db/PlaylistCacheDb.java new file mode 100644 index 000000000..2e12653e8 --- /dev/null +++ b/app/src/main/java/ru/vtosters/lite/music/cache/db/PlaylistCacheDb.java @@ -0,0 +1,254 @@ +package ru.vtosters.lite.music.cache.db; + +import android.annotation.SuppressLint; +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.util.Log; +import com.vk.dto.music.MusicTrack; +import com.vk.dto.music.Playlist; +import org.json.JSONException; +import org.json.JSONObject; +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.music.MusicCacheStorageUtils; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; + +import static ru.vtosters.lite.utils.music.MusicCacheStorageUtils.*; + +public class PlaylistCacheDb extends SQLiteOpenHelper implements AutoCloseable { + public PlaylistCacheDb(Context context) { + super(context, Constants.DB_NAME, null, Constants.DV_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL(Constants.CREATE_QUERY); + db.execSQL(Constants.CREATE_PLAYLIST_TRACKS_QUERY); + } + + private static Playlist fromCursor(Cursor cur) throws JSONException { + @SuppressLint("Range") + String photoString = cur.getString(cur.getColumnIndex(Constants.COLUMN_PHOTO)); + JSONObject photo = null; + + try { + photo = new JSONObject(photoString); + } catch (JSONException e) { + // ok and + } + + @SuppressLint("Range") + JSONObject playlist = PlaylistHelper.generatePlaylist( + cur.getInt(cur.getColumnIndex(Constants.COLUMN_ID)), + cur.getInt(cur.getColumnIndex(Constants.COLUMN_OWNER_ID)), + Boolean.parseBoolean(cur.getString(cur.getColumnIndex(Constants.COLUMN_IS_EXPLICIT))), + cur.getString(cur.getColumnIndex(Constants.COLUMN_TITLE)), + cur.getString(cur.getColumnIndex(Constants.COLUMN_DESCRIPTION)), + photo, + 0); + + Log.d("Playlist", "generated " + Playlist.U.a(playlist).v1()); + + return Playlist.U.a(playlist); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + db.execSQL(Constants.DROP_QUERY); + onCreate(db); + } + + public void addPlaylist(int id, int ownerId, boolean isExplicit, String title, String description, JSONObject photo) { + ContentValues values = new ContentValues(); + values.put(Constants.COLUMN_ID, id); + values.put(Constants.COLUMN_OWNER_ID, ownerId); + values.put(Constants.COLUMN_IS_EXPLICIT, isExplicit); + values.put(Constants.COLUMN_TITLE, title); + values.put(Constants.COLUMN_DESCRIPTION, description); + values.put(Constants.COLUMN_PHOTO, String.valueOf(photo)); + + getWritableDatabase().insert(Constants.TABLE_NAME, null, values); + } + + private void deletePlaylist(int id, int ownerId) { + getWritableDatabase().delete(Constants.TABLE_NAME, Constants.COLUMN_ID + " = ? AND " + Constants.COLUMN_OWNER_ID + " = ?", new String[]{String.valueOf(id), String.valueOf(ownerId)}); + } + + public boolean isCachedPlaylist(int id, int ownerId) { + Cursor cursor = getReadableDatabase().query(Constants.TABLE_NAME, new String[]{Constants.COLUMN_ID}, Constants.COLUMN_ID + " = ? AND " + Constants.COLUMN_OWNER_ID + " = ?", new String[]{String.valueOf(id), String.valueOf(ownerId)}, null, null, null); + boolean exists = cursor.getCount() > 0; + cursor.close(); + return exists; + } + + public long getPlaylistsCount() { + return DatabaseUtils.queryNumEntries(getReadableDatabase(), Constants.TABLE_NAME); + } + + public void deletePlaylistWithTracks(String playlistId) { + SQLiteDatabase db = getWritableDatabase(); + String[] parts = playlistId.split("_"); + int id = Integer.parseInt(parts[1]); + int ownerId = Integer.parseInt(parts[0]); + + // Get all tracks in the playlist + List tracks = getTracksInPlaylist(playlistId); + + // Delete the playlist + deletePlaylist(id, ownerId); + + // Delete each track from the playlist_tracks table + for (MusicTrack track : tracks) { + String trackId = track.y1(); + removeTrackFromPlaylist(playlistId, trackId); + removePlaylistThumb(playlistId); + + if (!isTrackInAnyOtherPlaylist(trackId, playlistId)) { + MusicCacheImpl.removeTrack(trackId); + Log.d("Playlist", "removed " + trackId); + } + } + + // Delete all entries from the playlist_tracks table for this playlist + db.delete(Constants.TABLE_PLAYLIST_TRACKS, Constants.COLUMN_PLAYLIST_ID + " = ?", new String[]{playlistId}); + } + + private boolean isTrackInAnyOtherPlaylist(String trackId, String playlistId) { + SQLiteDatabase db = getReadableDatabase(); + Cursor cursor = db.query( + Constants.TABLE_PLAYLIST_TRACKS, + null, + Constants.COLUMN_TRACK_ID + " = ? AND " + Constants.COLUMN_PLAYLIST_ID + " != ?", + new String[]{trackId, playlistId}, + null, null, null + ); + boolean exists = cursor.getCount() > 0; + cursor.close(); + return exists; + } + + public List getAllPlaylists() { + Cursor cursor = getReadableDatabase().query(Constants.TABLE_NAME, null, null, null, null, null, null); + List playlists = getPlaylistsWithCursor(cursor); + cursor.close(); + return playlists; + } + + public Playlist getPlaylistById(int id, int ownerId) { + SQLiteDatabase db = getReadableDatabase(); + Cursor cursor = db.query(Constants.TABLE_NAME, null, Constants.COLUMN_ID + " = ? AND " + Constants.COLUMN_OWNER_ID + " = ?", new String[]{String.valueOf(id), String.valueOf(ownerId)}, null, null, null); + Playlist playlist = null; + if (cursor.moveToFirst()) { + try { + playlist = fromCursor(cursor); + } catch (JSONException e) { + throw new RuntimeException(e); + } + } + cursor.close(); + return playlist; + } + + private List getPlaylistsWithCursor(Cursor cur) { + List playlists = new ArrayList<>(); + while (cur.moveToNext()) { + try { + playlists.add(fromCursor(cur)); + } catch (JSONException e) { + throw new RuntimeException(e); + } + } + return playlists; + } + + public void addTrackToPlaylist(String playlistId, String trackId) { + ContentValues values = new ContentValues(); + values.put(Constants.COLUMN_PLAYLIST_ID, playlistId); + values.put(Constants.COLUMN_TRACK_ID, trackId); + getWritableDatabase().insert(Constants.TABLE_PLAYLIST_TRACKS, null, values); + } + + public void removeTrackFromPlaylist(String playlistId, String trackId) { + getWritableDatabase().delete(Constants.TABLE_PLAYLIST_TRACKS, Constants.COLUMN_PLAYLIST_ID + " = ? AND " + Constants.COLUMN_TRACK_ID + " = ?", new String[]{playlistId, trackId}); + } + + public List getTracksInPlaylist(String playlistId) { + SQLiteDatabase db = getReadableDatabase(); + Cursor cursor = db.query( + Constants.TABLE_PLAYLIST_TRACKS, + new String[]{Constants.COLUMN_TRACK_ID}, + Constants.COLUMN_PLAYLIST_ID + " = ?", + new String[]{playlistId}, + null, null, null + ); + + List tracks = new ArrayList<>(); + while (cursor.moveToNext()) { + @SuppressLint("Range") + String trackId = cursor.getString(cursor.getColumnIndex(Constants.COLUMN_TRACK_ID)); + MusicTrack track = TracklistHelper.getTrack(trackId); + + Log.d("Playlist", "add " + trackId + " to playlist " + playlistId); + + if (track != null) { + tracks.add(track); + } + } + + cursor.close(); + return tracks; + } + + @Override + public void onConfigure(SQLiteDatabase db) { + super.onConfigure(db); + db.setForeignKeyConstraintsEnabled(true); + } + + @Retention(RetentionPolicy.SOURCE) + public @interface Constants { + int DV_VERSION = 0x2; + String DB_NAME = "vt_lite_cache_playlists.db"; + String TABLE_NAME = "playlists"; + + //columns + String COLUMN_ID = "id"; + String COLUMN_OWNER_ID = "owner_id"; + String COLUMN_IS_EXPLICIT = "is_explicit"; + String COLUMN_TITLE = "title"; + String COLUMN_DESCRIPTION = "description"; + String COLUMN_PHOTO = "photo"; + + // tracks + String COLUMN_TRACK_ID = "track_id"; + String TABLE_PLAYLIST_TRACKS = "playlist_tracks"; + String COLUMN_PLAYLIST_ID = "playlist_id"; + + //queries + String CREATE_QUERY = "create table " + TABLE_NAME + + "(" + + COLUMN_ID + " INTEGER PRIMARY KEY autoincrement," + + COLUMN_OWNER_ID + " INTEGER not null," + + COLUMN_IS_EXPLICIT + " TEXT not null," + + COLUMN_TITLE + " TEXT not null," + + COLUMN_DESCRIPTION + " TEXT not null," + + COLUMN_PHOTO + " TEXT not null" + + ")"; + + String CREATE_PLAYLIST_TRACKS_QUERY = "create table " + TABLE_PLAYLIST_TRACKS + "(" + + COLUMN_PLAYLIST_ID + " TEXT not null," + + COLUMN_TRACK_ID + " TEXT not null" + + ")"; + + String DROP_QUERY = "drop table if exists " + TABLE_NAME; + } +} diff --git a/app/src/main/java/ru/vtosters/lite/music/cache/MusicCacheDbDelegate.java b/app/src/main/java/ru/vtosters/lite/music/cache/delegate/MusicCacheDbDelegate.java similarity index 72% rename from app/src/main/java/ru/vtosters/lite/music/cache/MusicCacheDbDelegate.java rename to app/src/main/java/ru/vtosters/lite/music/cache/delegate/MusicCacheDbDelegate.java index 1da2bc462..74d6ac22c 100644 --- a/app/src/main/java/ru/vtosters/lite/music/cache/MusicCacheDbDelegate.java +++ b/app/src/main/java/ru/vtosters/lite/music/cache/delegate/MusicCacheDbDelegate.java @@ -1,4 +1,4 @@ -package ru.vtosters.lite.music.cache; +package ru.vtosters.lite.music.cache.delegate; import android.content.Context; @@ -43,27 +43,9 @@ public class MusicCacheDbDelegate { } } - public static List getAllTracks(Context context) { + public static MusicTrack getTrackById(Context context, String trackId) { try (var db = connectToDb(context)) { - return db.getAllTracks(); - } - } - - public static List getAlbumById(Context context, String albumId) { - try (var db = connectToDb(context)) { - return db.getAlbum(albumId); - } - } - - public static List getFirstAlbumTrack(Context context, String albumId) { - try (var db = connectToDb(context)) { - return db.getFirstAlbumTrack(albumId); - } - } - - public static List getPlaylist(Context context) { - try (var db = connectToDb(context)) { - return db.getPlaylist(); + return db.getTrackById(trackId); } } diff --git a/app/src/main/java/ru/vtosters/lite/music/cache/delegate/PlaylistCacheDbDelegate.java b/app/src/main/java/ru/vtosters/lite/music/cache/delegate/PlaylistCacheDbDelegate.java new file mode 100644 index 000000000..15efe803f --- /dev/null +++ b/app/src/main/java/ru/vtosters/lite/music/cache/delegate/PlaylistCacheDbDelegate.java @@ -0,0 +1,118 @@ +package ru.vtosters.lite.music.cache.delegate; + +import android.content.Context; +import android.net.Uri; +import android.util.Log; +import com.vk.dto.music.MusicTrack; +import com.vk.dto.music.Playlist; +import org.json.JSONException; +import org.json.JSONObject; +import ru.vtosters.lite.music.cache.db.PlaylistCacheDb; +import ru.vtosters.lite.utils.music.MusicCacheStorageUtils; +import ru.vtosters.lite.utils.music.PlaylistUtils; + +import java.util.List; + +public class PlaylistCacheDbDelegate { + static synchronized private PlaylistCacheDb connectToDb(Context context) { + synchronized (PlaylistCacheDb.class) { + return new PlaylistCacheDb(context); + } + } + + public static void addPlaylist(Context context, Playlist playlist) { + try (var db = connectToDb(context)) { + db.addPlaylist(playlist.a, playlist.b, playlist.C, playlist.g, playlist.B, generatePhotoJSON(playlist)); + } + } + + public static JSONObject generatePhotoJSON(Playlist playlist) { + if (PlaylistUtils.getThumb(playlist) != null) { + JSONObject photo = new JSONObject(); + String playlistId = playlist.v1(); + try { + photo.put("height", 300); + photo.put("width", 300); + + addPhotoSizeToJSON(photo, playlistId, 600, "photo_600"); + addPhotoSizeToJSON(photo, playlistId, 34, "photo_34"); + addPhotoSizeToJSON(photo, playlistId, 1200, "photo_1200"); + addPhotoSizeToJSON(photo, playlistId, 68, "photo_68"); + addPhotoSizeToJSON(photo, playlistId, 135, "photo_135"); + addPhotoSizeToJSON(photo, playlistId, 300, "photo_300"); + addPhotoSizeToJSON(photo, playlistId, 270, "photo_270"); + + return photo; + } catch (JSONException e) { + throw new RuntimeException(e); + } + } else { + return null; + } + } + + private static void addPhotoSizeToJSON(JSONObject photo, String playlistId, int size, String key) throws JSONException { + photo.put(key, Uri.fromFile(MusicCacheStorageUtils.getPlaylistThumb(playlistId, size)).toString()); + Log.d("Playlist", "thumb link " + Uri.fromFile(MusicCacheStorageUtils.getPlaylistThumb(playlistId, size)).toString()); + } + + public static void deletePlaylist(Context context, String playlistId) { + try (var db = connectToDb(context)) { + db.deletePlaylistWithTracks(playlistId); + } + } + + public static boolean isCachedPlaylist(Context context, String id) { + try (var db = connectToDb(context)) { + String[] parts = id.split("_"); + int playlistId = Integer.parseInt(parts[1]); + int ownerId = Integer.parseInt(parts[0]); + + return db.isCachedPlaylist(playlistId, ownerId); + } + } + + public static long getPlaylistsCount(Context context) { + try (var db = connectToDb(context)) { + return db.getPlaylistsCount(); + } + } + + public static List getAllPlaylists(Context context) { + try (var db = connectToDb(context)) { + return db.getAllPlaylists(); + } + } + + public static Playlist getPlaylistById(Context context, String playlistId) { + try (var db = connectToDb(context)) { + String[] parts = playlistId.split("_"); + int id = Integer.parseInt(parts[1]); + int ownerId = Integer.parseInt(parts[0]); + + return db.getPlaylistById(id, ownerId); + } + } + + public static void addTrackToPlaylist(Context context, String playlistId, String trackId) { + try (var db = connectToDb(context)) { + db.addTrackToPlaylist(playlistId, trackId); + } + } + + public static void removeTrackFromPlaylist(Context context, String playlistId, String trackId) { + try (var db = connectToDb(context)) { + db.removeTrackFromPlaylist(playlistId, trackId); + } + } + + public static List getTracksInPlaylist(Context context, String playlistId) { + try (var db = connectToDb(context)) { + return db.getTracksInPlaylist(playlistId); + } + } + + public static void drop(Context context) { + context.deleteDatabase(PlaylistCacheDb.Constants.DB_NAME); + } +} \ No newline at end of file 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 1062f911a..51420f3b8 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,96 +1,82 @@ package ru.vtosters.lite.music.cache.helpers; -import static ru.vtosters.lite.utils.AccountManagerUtils.getUserId; - -import android.net.Uri; - +import android.util.Log; import com.vk.dto.music.Playlist; import com.vtosters.lite.R; - import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; - -import java.util.ArrayList; -import java.util.List; - import ru.vtosters.lite.music.cache.MusicCacheImpl; +import ru.vtosters.lite.utils.AccountManagerUtils; import ru.vtosters.lite.utils.AndroidUtils; -import ru.vtosters.lite.utils.music.MusicCacheStorageUtils; -import ru.vtosters.lite.utils.music.MusicTrackUtils; +import ru.vtosters.lite.utils.NetworkUtils; +import ru.vtosters.lite.utils.music.PlaylistUtils; public class PlaylistHelper { public static Playlist createCachedPlaylistMetadata() { try { - return new Playlist(getPlaylist()); + return new Playlist(getCachedSongsPlaylist()); } catch (JSONException e) { - e.printStackTrace(); return null; } } - public static Playlist createCachedPlaylistMetadata(String id) { - var track = TracklistHelper.getTracksOne(id).get(0); - var albumLink = track.I; - var thumb = albumLink.u1(); - return createAlbum( - thumb != null ? thumb.h(600) : "", - albumLink.getTitle(), - MusicTrackUtils.getArtists(track), - track.y1(), - albumLink.getId() - ); - } - - public static Playlist createAlbum(String photo, String title, String artist, String trackId, int albumId) { + public static JSONArray addCachedPlaylists(JSONArray jsonArray, Boolean noPlaylists) { try { - var jPhoto = new JSONObject(); - if (!photo.isEmpty()) - jPhoto.put("height", 600) - .put("width", 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) - .put("type", 1) - .put("album_type", "main_only") - .put("title", title) - .put("description", albumId + "/ " + AndroidUtils.getString(R.string.cached_album_warning)) - .put("main_artists", new JSONArray() - .put(new JSONObject().put("name", artist) - .put("id", "-1") - .put("domain", "-1"))) - .putOpt("photo", jPhoto) - .put("count", 0); - return new Playlist(obj); + if (noPlaylists) { + jsonArray.put(new JSONArray().put(getCachedSongsPlaylist())); + } else { + jsonArray.put(getCachedSongsPlaylist()); + } + + for (Playlist playlist : MusicCacheImpl.getPlaylists()) { + if (noPlaylists) { + jsonArray.put(new JSONArray().put(generatePlaylist(playlist.a, playlist.b, playlist.C, playlist.g, playlist.B, PlaylistUtils.getThumb(playlist), playlist.O))); + } else { + jsonArray.put(generatePlaylist(playlist.a, playlist.b, playlist.C, playlist.g, playlist.B, PlaylistUtils.getThumb(playlist), playlist.O)); + } + + Log.d("PlaylistHelper", "Playlist cache added: " + playlist.a + " " + playlist.b + " " + playlist.C + " " + playlist.g + " " + playlist.B); + } } catch (JSONException e) { - e.printStackTrace(); - return null; + e.fillInStackTrace(); } + return jsonArray; } - public static List getAlbumPlaylists() { - List list = new ArrayList<>(); - for (var track : MusicCacheImpl.getPlaylist()) { - var albumLink = track.I; - var thumb = albumLink.u1(); - list.add(createAlbum( - thumb != null ? thumb.h(600) : "", - albumLink.getTitle(), - MusicTrackUtils.getArtists(track), - track.y1(), - albumLink.getId() - )); + public static JSONObject generatePlaylist(int id, int owner_id, boolean is_explicit, String title, String description, JSONObject photo, int count) throws JSONException { + JSONObject playlist = new JSONObject() + .put("id", id) + .put("owner_id", owner_id) + .put("type", 0) + .put("album_type", "playlist") + .put("is_explicit", is_explicit) + .put("title", title) + .put("description", description) + .put("access_key", "cache") + .put("genres", new JSONArray()) + .put("is_following", false) + .put("followers", 0) + .put("plays", 0) + .put("create_time", 0) + .put("update_time", 0) + .put("subtitle", "") + .put("meta", new JSONObject() + .put("view", "compact")) + .put("count", count); + + if (photo != null) { + playlist.put("photo", photo); } - return list; + + return playlist; } - public static JSONObject getPlaylist() throws JSONException { + public static JSONObject getCachedSongsPlaylist() throws JSONException { return new JSONObject() .put("id", -1) - .put("owner_id", getUserId()) + .put("owner_id", AccountManagerUtils.getUserId()) .put("type", 0) .put("album_type", "playlist") .put("title", AndroidUtils.getString(R.string.cached_tracks_title)) @@ -115,7 +101,21 @@ public class PlaylistHelper { .put("height", 600)) .put("meta", new JSONObject() .put("view", "compact")) - .put("count", 0); + .put("count", MusicCacheImpl.getAllOwnTracks().size()); + } + + public static JSONArray getCachedPlaylistsIds() { + JSONArray arr = new JSONArray(); + + if (NetworkUtils.isNetworkConnected() && !MusicCacheImpl.getAllOwnTracks().isEmpty()) { + arr.put(AccountManagerUtils.getUserId() + "_-1"); + } + + for (var playlist : MusicCacheImpl.getPlaylists()) { + arr.put(playlist.v1()); + } + + return arr; } public static JSONObject getCatalogPlaylist() throws JSONException { @@ -125,18 +125,17 @@ public class PlaylistHelper { .put("layout", new JSONObject() .put("name", "large_slider") .put("is_editable", 0) - .put("owner_id", getUserId())) - .put("playlists_ids", new JSONArray() - .put(getUserId() + "_-1")); + .put("owner_id", AccountManagerUtils.getUserId())) + .put("playlists_ids", getCachedPlaylistsIds()); } - public static JSONObject getCatalogHeader() throws JSONException { + public static JSONObject getCatalogHeader(String text) throws JSONException { return new JSONObject() .put("id", "cache") .put("data_type", "none") .put("layout", new JSONObject() .put("name", "header") - .put("title", AndroidUtils.getString(R.string.cached_tracks_title))); + .put("title", text)); } public static JSONObject getCatalogSeparator() throws JSONException { @@ -146,4 +145,4 @@ public class PlaylistHelper { .put("layout", new JSONObject() .put("name", "separator")); } -} +} \ No newline at end of file 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 0eecf6c7c..400970115 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 @@ -8,6 +8,8 @@ import org.json.JSONException; import org.json.JSONObject; import ru.vtosters.hooks.other.Preferences; import ru.vtosters.lite.music.cache.MusicCacheImpl; +import ru.vtosters.lite.music.cache.delegate.MusicCacheDbDelegate; +import ru.vtosters.lite.utils.AndroidUtils; import ru.vtosters.lite.utils.music.MusicCacheStorageUtils; import java.io.File; @@ -22,7 +24,7 @@ public class TracklistHelper { } public static List getTracks() { - List tracks = MusicCacheImpl.getAllTracks(); + List tracks = MusicCacheImpl.getAllOwnTracks(); boolean doNotinvertOrder = Preferences.getBoolValue("invertCachedTracks", false); @@ -33,13 +35,8 @@ public class TracklistHelper { return tracks; } - - public static List getTracks(String id) { - return MusicCacheImpl.getAlbumById(id); - } - - public static List getTracksOne(String id) { - return MusicCacheImpl.getFirstAlbumTrack(id); + public static MusicTrack getTrack(String id) { + return MusicCacheDbDelegate.getTrackById(AndroidUtils.getGlobalContext(), id); } public static JSONArray tracksToIds(List tracks) { 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 139599d6d..5ced6b561 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 @@ -53,7 +53,9 @@ public class PlaylistConverter { var request = new Request.a() .b(requestUrl) .a("Accept-Encoding", "gzip") - .a(Headers.a("User-Agent", Network.l.c().a(), "Content-Type", "application/x-www-form-urlencoded; charset=utf-8")).a(); + .a("User-Agent", Network.l.c().a()) + .a("Content-Type", "application/x-www-form-urlencoded; charset=utf-8") + .a(); try { var response = CompletableFuture.supplyAsync(() -> { diff --git a/app/src/main/java/ru/vtosters/lite/music/downloader/AudioGet.java b/app/src/main/java/ru/vtosters/lite/music/downloader/AudioGet.java index a8e161a17..2b907baf1 100644 --- a/app/src/main/java/ru/vtosters/lite/music/downloader/AudioGet.java +++ b/app/src/main/java/ru/vtosters/lite/music/downloader/AudioGet.java @@ -47,8 +47,9 @@ public class AudioGet { var request = new Request.a() .b(requestUrl) .a("Accept-Encoding", "gzip") - .a(Headers.a("User-Agent", Network.l.c().a(), "Content-Type", "application/x-www-form-urlencoded; charset=utf-8")).a(); - + .a("User-Agent", Network.l.c().a()) + .a("Content-Type", "application/x-www-form-urlencoded; charset=utf-8") + .a(); try { var response = CompletableFuture.supplyAsync(() -> { try (Response resp = Network.b(CLIENT_API).a(request).execute()) { diff --git a/app/src/main/java/ru/vtosters/lite/music/downloader/CachedDownloader.java b/app/src/main/java/ru/vtosters/lite/music/downloader/CachedDownloader.java index d3549ea2b..df88d6a07 100644 --- a/app/src/main/java/ru/vtosters/lite/music/downloader/CachedDownloader.java +++ b/app/src/main/java/ru/vtosters/lite/music/downloader/CachedDownloader.java @@ -2,9 +2,12 @@ package ru.vtosters.lite.music.downloader; import com.vk.dto.music.MusicTrack; +import com.vk.dto.music.Playlist; import ru.vtosters.lite.music.cache.MusicCacheImpl; +import ru.vtosters.lite.music.cache.delegate.PlaylistCacheDbDelegate; import ru.vtosters.lite.music.interfaces.Callback; import ru.vtosters.lite.music.interfaces.ITrackDownloader; +import ru.vtosters.lite.utils.AndroidUtils; public final class CachedDownloader implements ITrackDownloader { @@ -14,9 +17,8 @@ public final class CachedDownloader implements ITrackDownloader { this.origin = origin; } - @Override - public void download(MusicTrack track, Callback callback) { + public void download(MusicTrack track, Callback callback, Playlist playlist) { origin.download(track, new Callback() { @Override public void onProgress(int progress) { @@ -25,18 +27,26 @@ public final class CachedDownloader implements ITrackDownloader { @Override public void onSuccess() { - new ThumbnailDownloader().download(track, new Callback() { - @Override public void onProgress(int progress) {} + new ThumbnailTrackDownloader().download(track, new Callback() { + @Override public void onProgress(int progress) { - @Override public void onSuccess() {} + } + + @Override public void onSuccess() { + + } @Override public void onFailure(Throwable e) { callback.onFailure(e); } - @Override public void onSizeReceived(long size, long header) {} - }); + @Override public void onSizeReceived(long size, long header) { + + } + }, null); + + PlaylistCacheDbDelegate.addTrackToPlaylist(AndroidUtils.getGlobalContext(), playlist.v1(), track.y1()); MusicCacheImpl.addTrack(track); callback.onSuccess(); } @@ -50,6 +60,6 @@ public final class CachedDownloader implements ITrackDownloader { public void onSizeReceived(long size, long header) { callback.onSizeReceived(size, header); } - }); + }, playlist); } -} +} \ No newline at end of file diff --git a/app/src/main/java/ru/vtosters/lite/music/downloader/Mp3Downloader.java b/app/src/main/java/ru/vtosters/lite/music/downloader/Mp3Downloader.java index 5bc542691..94f382304 100644 --- a/app/src/main/java/ru/vtosters/lite/music/downloader/Mp3Downloader.java +++ b/app/src/main/java/ru/vtosters/lite/music/downloader/Mp3Downloader.java @@ -20,6 +20,7 @@ import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; +import com.vk.dto.music.Playlist; import ru.vtosters.hooks.other.Preferences; import ru.vtosters.lite.music.converter.ts.MpegDemuxer; import ru.vtosters.lite.music.interfaces.Callback; @@ -34,7 +35,7 @@ public final class Mp3Downloader implements ITrackDownloader { } @Override - public void download(MusicTrack track, Callback callback) { + public void download(MusicTrack track, Callback callback, Playlist playlist) { String uri = track.D; if (Objects.requireNonNull(uri).isEmpty()) { String msg = "link error: " + track.y1() + ", title: " + Mp3Downloader.getTitle(track); 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 4d4e59f78..cb9c30741 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 @@ -3,23 +3,23 @@ package ru.vtosters.lite.music.downloader; import android.util.Log; import com.vk.dto.music.MusicTrack; +import com.vk.dto.music.Playlist; +import ru.vtosters.lite.music.interfaces.Callback; import java.io.File; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java8.util.concurrent.CompletableFuture; -import ru.vtosters.lite.music.interfaces.Callback; public class PlaylistDownloader { - public static CompletableFuture - downloadPlaylist(List playlist, String playlistName, String path, Callback callback) { + public static void downloadPlaylist(List playlist, String playlistName, String path, Callback callback) { var outDir = new File(path); if (!outDir.exists()) if (outDir.mkdirs()) Log.v("PlaylistDownloader", "Directory created"); else Log.e("PlaylistDownloader", "Directory creation failed"); Callback delegate = new ProgressCallback(callback); - return CompletableFuture.allOf(playlist + CompletableFuture.allOf(playlist .stream() .map(x -> { Callback.CompletableFutureCallback d = new @@ -30,14 +30,13 @@ public class PlaylistDownloader { .toArray(CompletableFuture[]::new)); } - public static CompletableFuture cachePlaylist(List playlist, Callback callback) { + public static void cachePlaylist(List playlist, Callback callback, Playlist playlistId) { Callback delegate = new ProgressCallback(callback); - return CompletableFuture.allOf(playlist + CompletableFuture.allOf(playlist .stream() .map(x -> { - Callback.CompletableFutureCallback d = new - Callback.CompletableFutureCallback(delegate); - TrackDownloader.cacheTrack(x, d); + Callback.CompletableFutureCallback d = new Callback.CompletableFutureCallback(delegate); + TrackDownloader.cacheTrack(x, d, playlistId); return d; }) .toArray(CompletableFuture[]::new)); diff --git a/app/src/main/java/ru/vtosters/lite/music/downloader/ThumbnailPlaylistDownloader.java b/app/src/main/java/ru/vtosters/lite/music/downloader/ThumbnailPlaylistDownloader.java new file mode 100644 index 000000000..84cf49771 --- /dev/null +++ b/app/src/main/java/ru/vtosters/lite/music/downloader/ThumbnailPlaylistDownloader.java @@ -0,0 +1,57 @@ +package ru.vtosters.lite.music.downloader; + +import android.util.Log; +import com.vk.dto.music.MusicTrack; +import com.vk.dto.music.Playlist; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import ru.vtosters.lite.music.interfaces.Callback; +import ru.vtosters.lite.music.interfaces.ITrackDownloader; +import ru.vtosters.lite.utils.IOUtils; +import ru.vtosters.lite.utils.music.MusicCacheStorageUtils; +import ru.vtosters.lite.utils.music.PlaylistUtils; + +import java.io.IOException; +import java.net.URL; +import java.util.List; + +public class ThumbnailPlaylistDownloader implements ITrackDownloader { + + @Override + public void download(MusicTrack track, Callback callback, Playlist playlist) { + JSONObject json = PlaylistUtils.getThumb(playlist); // get thumbnail photo + + if (json == null) { + callback.onFailure(new IOException("No thumbnail found")); + return; + } + + try { + List sizes = List.of("600", "1200", "68", "135", "300", "270"); // define the order of sizes to download + JSONArray sizesArray = json.getJSONArray("sizes"); + for (String size : sizes) { + for (int i = 0; i < sizesArray.length(); i++) { + JSONObject sizeObj = sizesArray.getJSONObject(i); + if (sizeObj.getInt("width") == Integer.parseInt(size)) { + String url = sizeObj.getString("src"); + + if (url.isEmpty()) continue; + + Log.d("Playlist", "downloading thumb " + url); + + downloadThumbnailPlaylist(url, Integer.parseInt(size), playlist.v1()); + } + } + } + callback.onSuccess(); + } catch (IOException | JSONException e) { + callback.onFailure(e); + throw new RuntimeException(e); + } + } + + private static void downloadThumbnailPlaylist(String url, int res, String playlistId) throws IOException { + IOUtils.writeToFile(MusicCacheStorageUtils.getPlaylistThumb(playlistId, res), IOUtils.readFully(new URL(url).openStream())); + } +} diff --git a/app/src/main/java/ru/vtosters/lite/music/downloader/ThumbnailDownloader.java b/app/src/main/java/ru/vtosters/lite/music/downloader/ThumbnailTrackDownloader.java similarity index 90% rename from app/src/main/java/ru/vtosters/lite/music/downloader/ThumbnailDownloader.java rename to app/src/main/java/ru/vtosters/lite/music/downloader/ThumbnailTrackDownloader.java index 99f9141a2..dcb19e801 100644 --- a/app/src/main/java/ru/vtosters/lite/music/downloader/ThumbnailDownloader.java +++ b/app/src/main/java/ru/vtosters/lite/music/downloader/ThumbnailTrackDownloader.java @@ -2,6 +2,7 @@ package ru.vtosters.lite.music.downloader; import com.vk.dto.music.MusicTrack; +import com.vk.dto.music.Playlist; import org.json.JSONArray; import org.json.JSONObject; @@ -14,10 +15,10 @@ import ru.vtosters.lite.music.interfaces.ITrackDownloader; import ru.vtosters.lite.utils.IOUtils; import ru.vtosters.lite.utils.music.MusicCacheStorageUtils; -public final class ThumbnailDownloader implements ITrackDownloader { +public final class ThumbnailTrackDownloader implements ITrackDownloader { @Override - public void download(MusicTrack track, Callback callback) { + public void download(MusicTrack track, Callback callback, Playlist playlist) { JSONObject json = track.J(); JSONObject album = json.optJSONObject("album"); if (album == null) return; @@ -50,5 +51,4 @@ public final class ThumbnailDownloader implements ITrackDownloader { private static void downloadThumbnail(String url, int res, String trackId) throws IOException { IOUtils.writeToFile(MusicCacheStorageUtils.getTrackThumb(trackId, res), IOUtils.readFully(new URL(url).openStream())); } - -} +} \ No newline at end of file diff --git a/app/src/main/java/ru/vtosters/lite/music/downloader/TrackDownloader.java b/app/src/main/java/ru/vtosters/lite/music/downloader/TrackDownloader.java index 23ed0b130..2f13c387a 100644 --- a/app/src/main/java/ru/vtosters/lite/music/downloader/TrackDownloader.java +++ b/app/src/main/java/ru/vtosters/lite/music/downloader/TrackDownloader.java @@ -7,6 +7,7 @@ import com.vk.dto.music.MusicTrack; import java.io.File; import bruhcollective.itaysonlab.libvkx.client.LibVKXClient; +import com.vk.dto.music.Playlist; import ru.vtosters.lite.concurrent.VTExecutors; import ru.vtosters.lite.music.cache.MusicCacheImpl; import ru.vtosters.lite.music.interfaces.Callback; @@ -21,25 +22,24 @@ public final class TrackDownloader { public static void downloadTrack(MusicTrack track, String path, Callback callback) { File outDir = new File(path); if (!outDir.exists()) { - if (outDir.mkdir()) + if (outDir.mkdir()) { Log.v("TrackDownloader", "Directory created"); - else + } else { Log.e("TrackDownloader", "Directory creation failed"); + } } - var outputFile = new File(outDir, IOUtils.getValidFileName( - MusicTrackUtils.getArtists(track) + " - " + Mp3Downloader.getTitle(track)) + ".mp3"); + File outputFile = new File(outDir, IOUtils.getValidFileName(MusicTrackUtils.getArtists(track) + " - " + Mp3Downloader.getTitle(track)) + ".mp3"); - VTExecutors.getMusicDownloadExecutor().submit(() -> new Mp3Downloader(outputFile).download(track, callback)); + VTExecutors.getMusicDownloadExecutor().submit(() -> new Mp3Downloader(outputFile).download(track, callback, null)); } - public static void cacheTrack(MusicTrack track, Callback callback) { - if (MusicCacheImpl.isCachedTrack(LibVKXClient.asId(track))) + public static void cacheTrack(MusicTrack track, Callback callback, Playlist playlist) { + if (MusicCacheImpl.isCachedTrack(LibVKXClient.asId(track))) { return; - var outputFile = MusicCacheStorageUtils - .getTrackFile(LibVKXClient.asId(track)); + } - VTExecutors.getMusicDownloadExecutor().submit(() -> new CachedDownloader( - new Mp3Downloader(outputFile) - ).download(track, callback)); + File outputFile = MusicCacheStorageUtils.getTrackFile(LibVKXClient.asId(track)); + + VTExecutors.getMusicDownloadExecutor().submit(() -> new CachedDownloader(new Mp3Downloader(outputFile)).download(track, callback, playlist)); } } diff --git a/app/src/main/java/ru/vtosters/lite/music/interfaces/ITrackDownloader.java b/app/src/main/java/ru/vtosters/lite/music/interfaces/ITrackDownloader.java index a1d33533e..02894e764 100644 --- a/app/src/main/java/ru/vtosters/lite/music/interfaces/ITrackDownloader.java +++ b/app/src/main/java/ru/vtosters/lite/music/interfaces/ITrackDownloader.java @@ -1,6 +1,7 @@ package ru.vtosters.lite.music.interfaces; import com.vk.dto.music.MusicTrack; +import com.vk.dto.music.Playlist; /** * Interface for track downloaders @@ -11,6 +12,7 @@ public interface ITrackDownloader { * * @param track Music track * @param callback Callback to call when download status changes + * @param playlist */ - void download(MusicTrack track, Callback callback); + void download(MusicTrack track, Callback callback, Playlist playlist); } \ No newline at end of file diff --git a/app/src/main/java/ru/vtosters/lite/ui/fragments/MusicFragment.java b/app/src/main/java/ru/vtosters/lite/ui/fragments/MusicFragment.java index 9d30cc10e..47ee2782e 100644 --- a/app/src/main/java/ru/vtosters/lite/ui/fragments/MusicFragment.java +++ b/app/src/main/java/ru/vtosters/lite/ui/fragments/MusicFragment.java @@ -1,29 +1,33 @@ package ru.vtosters.lite.ui.fragments; +import android.annotation.SuppressLint; import android.content.Context; import android.os.Bundle; import android.view.ViewGroup; import android.widget.EditText; import android.widget.LinearLayout; -import androidx.preference.Preference; import bruhcollective.itaysonlab.libvkx.client.LibVKXClient; import com.vk.core.dialogs.alert.VkAlertDialog; +import com.vk.dto.music.Playlist; import com.vtosters.lite.ui.SummaryListPreference; import ru.vtosters.hooks.other.Preferences; import ru.vtosters.hooks.other.ThemesUtils; import ru.vtosters.lite.downloaders.AudioDownloader; import ru.vtosters.lite.music.LastFMScrobbler; import ru.vtosters.lite.music.cache.MusicCacheImpl; +import ru.vtosters.lite.music.cache.delegate.PlaylistCacheDbDelegate; import ru.vtosters.lite.ui.PreferenceFragmentUtils; import ru.vtosters.lite.utils.AndroidUtils; import ru.vtosters.lite.utils.LifecycleUtils; +import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class MusicFragment extends TrackedMaterialPreferenceToolbarFragment { private static final ExecutorService executor = Executors.newCachedThreadPool(); + @SuppressLint("DefaultLocale") @Override public void onCreate(Bundle bundle) { super.onCreate(bundle); @@ -50,6 +54,20 @@ public class MusicFragment extends TrackedMaterialPreferenceToolbarFragment { } ); + if (MusicCacheImpl.hasPlaylist()) { + PreferenceFragmentUtils.addPreference( + getPreferenceScreen(), + "cached_playlists", + "Кешированные плейлисты", + String.format("Скачано плейлистов: %d", MusicCacheImpl.getPlaylists().size()), + null, + preference -> { + cachedPlaylistsDialog(requireContext()); + return true; + } + ); + } + PreferenceFragmentUtils.addPreference( getPreferenceScreen(), "audio_download", @@ -322,6 +340,26 @@ public class MusicFragment extends TrackedMaterialPreferenceToolbarFragment { .show(); } + private static void cachedPlaylistsDialog(Context ctx) { + List playlists = PlaylistCacheDbDelegate.getAllPlaylists(ctx); + String[] playlistNames = new String[playlists.size()]; + + for (int i = 0; i < playlists.size(); i++) { + playlistNames[i] = playlists.get(i).g; // Assuming Playlist class has a getTitle() method + } + + VkAlertDialog.Builder builder = new VkAlertDialog.Builder(ctx); + builder.setTitle("Скачанные плейлисты"); + builder.setItems(playlistNames, (dialog, which) -> { + Playlist selectedPlaylist = playlists.get(which); + String playlistId = selectedPlaylist.v1(); + PlaylistCacheDbDelegate.deletePlaylist(ctx, playlistId); + AndroidUtils.sendToast("Плейлист удален"); + }); + + builder.show(); + } + private void delcache(Context ctx) { new VkAlertDialog.Builder(ctx) .setTitle(com.vtosters.lite.R.string.warning) 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 index a0ea4d54d..504bebdd5 100644 --- a/app/src/main/java/ru/vtosters/lite/utils/music/MusicCacheStorageUtils.java +++ b/app/src/main/java/ru/vtosters/lite/utils/music/MusicCacheStorageUtils.java @@ -11,32 +11,40 @@ public class MusicCacheStorageUtils { } @SuppressWarnings("ResultOfMethodCallIgnored") - public static File getTrackDirById(final String trackId) { - final var res = new File(getCacheStorageDir(), trackId); + public static File getTrackDirById(String trackId) { + File res = new File(getCacheStorageDir(), trackId); res.mkdirs(); return res; } - public static File getTrackFile(final String trackId) { + public static File getTrackFile(String trackId) { return new File(getTrackDirById(trackId), "track.mp3"); } @SuppressWarnings("ResultOfMethodCallIgnored") - public static File getThumbDirById(final String trackId) { - final var res = new File(getTrackDirById(trackId), "thumbs"); + public static File getThumbDirById(String trackId) { + File res = new File(getTrackDirById(trackId), "thumbs"); res.mkdirs(); return res; } - public static File getTrackThumb(final String trackId, final int factor) { + public static File getTrackThumb(String trackId, int factor) { return new File(getThumbDirById(trackId), "thumb_" + factor + ".png"); } - public static void removeTrackDirById(final String trackId) { + public static File getPlaylistThumb(String playlistId, int factor) { + return new File(getThumbDirById(playlistId), "thumb_playlist_" + factor + ".png"); + } + + public static void removePlaylistThumb(String playlistId) { + IOUtils.deleteRecursive(getThumbDirById(playlistId)); + } + + public static void removeTrackDirById(String trackId) { IOUtils.deleteRecursive(getTrackDirById(trackId)); } public static void clear() { IOUtils.deleteRecursive(getCacheStorageDir()); } -} +} \ No newline at end of file diff --git a/app/src/main/java/ru/vtosters/lite/utils/music/PlaylistUtils.java b/app/src/main/java/ru/vtosters/lite/utils/music/PlaylistUtils.java new file mode 100644 index 000000000..60ee024a3 --- /dev/null +++ b/app/src/main/java/ru/vtosters/lite/utils/music/PlaylistUtils.java @@ -0,0 +1,17 @@ +package ru.vtosters.lite.utils.music; + +import android.util.Log; +import com.vk.dto.music.Playlist; +import org.json.JSONObject; + +public class PlaylistUtils { + public static JSONObject getThumb(Playlist playlist) { + try { + Log.d("Playlist", "thumb is not null"); + return playlist.F.J(); + } catch (Exception e) { + Log.d("Playlist", "thumb is null"); + return null; + } + } +} \ No newline at end of file