From 21389b1e9e1dcabed6bca75f572dacf764599c85 Mon Sep 17 00:00:00 2001 From: Martijn Brekhof Date: Thu, 9 Jun 2016 21:57:29 +0200 Subject: [PATCH] Fixed issue showing songs without an album or artist --- .../org/xbmc/kore/provider/MediaContract.java | 31 ++++++++ .../org/xbmc/kore/provider/MediaDatabase.java | 49 ++++++++---- .../org/xbmc/kore/provider/MediaProvider.java | 63 ++++++++++++---- .../xbmc/kore/service/library/SyncMusic.java | 19 ++++- .../xbmc/kore/service/library/SyncUtils.java | 47 ++++++------ .../xbmc/kore/ui/ArtistOverviewFragment.java | 14 ++-- .../org/xbmc/kore/ui/SongsListFragment.java | 75 +++++++------------ .../org/xbmc/kore/utils/SelectionBuilder.java | 18 ++++- 8 files changed, 202 insertions(+), 114 deletions(-) diff --git a/app/src/main/java/org/xbmc/kore/provider/MediaContract.java b/app/src/main/java/org/xbmc/kore/provider/MediaContract.java index ed77950..f12cf86 100644 --- a/app/src/main/java/org/xbmc/kore/provider/MediaContract.java +++ b/app/src/main/java/org/xbmc/kore/provider/MediaContract.java @@ -41,6 +41,7 @@ public class MediaContract { public static final String PATH_ALBUMS = "albums"; public static final String PATH_AUDIO_GENRES = "audio_genres"; public static final String PATH_SONGS = "songs"; + public static final String PATH_SONG_ARTISTS = "song_artists"; public static final String PATH_ALBUM_ARTISTS = "album_artists"; public static final String PATH_ALBUM_GENRES = "album_genres"; public static final String PATH_MUSIC_VIDEOS = "music_videos"; @@ -701,6 +702,36 @@ public class MediaContract { }; } + /** + * Columns for SongArtists table + * All Other IDs refer to XBMC Ids, not Internal ones + */ + public interface SongArtistsColumns { + String HOST_ID = "host_id"; + String SONGID = "songid"; + String ARTISTID = "artistid"; + } + + public static class SongArtists implements BaseColumns, SongArtistsColumns { + public static final Uri CONTENT_URI = + BASE_CONTENT_URI.buildUpon().appendPath(PATH_SONG_ARTISTS).build(); + public static final String CONTENT_TYPE = + "vnd.android.cursor.dir/vnd.org.xbmc." + PATH_SONG_ARTISTS; + + /** Build {@link Uri} for requested {@link #_ID}. */ + public static Uri buildSongsForArtistListUri(long hostId, long artistId) { + return Hosts.buildHostUri(hostId).buildUpon() + .appendPath(PATH_ARTISTS) + .appendPath(String.valueOf(artistId)) + .appendPath(PATH_SONGS) + .build(); + } + + public final static String[] ALL_COLUMNS = { + _ID, HOST_ID, SONGID, ARTISTID, + }; + } + /** * Columns for AlbumGenres table * All Other IDs refer to XBMC Ids, not Internal ones diff --git a/app/src/main/java/org/xbmc/kore/provider/MediaDatabase.java b/app/src/main/java/org/xbmc/kore/provider/MediaDatabase.java index eb4cf12..fa127c1 100644 --- a/app/src/main/java/org/xbmc/kore/provider/MediaDatabase.java +++ b/app/src/main/java/org/xbmc/kore/provider/MediaDatabase.java @@ -32,7 +32,7 @@ public class MediaDatabase extends SQLiteOpenHelper { private static final String DB_NAME = "xbmc.sqlite"; private static final int DB_VERSION_PRE_EVENT_SERVER = 4, - DB_VERSION = 5; + DB_VERSION_PRE_SONG_ARTISTS = 5, DB_VERSION = 6; /** * Tables exposed @@ -48,6 +48,7 @@ public class MediaDatabase extends SQLiteOpenHelper { String ARTISTS = "artists"; String ALBUMS = "albums"; String SONGS = "songs"; + String SONG_ARTISTS = "song_artists"; String AUDIO_GENRES = "audio_genres"; String ALBUM_ARTISTS = "album_artists"; String ALBUM_GENRES = "album_genres"; @@ -90,25 +91,25 @@ public class MediaDatabase extends SQLiteOpenHelper { ALBUM_GENRES + "." + MediaContract.AlbumGenres.GENREID + "=" + AUDIO_GENRES + "." + MediaContract.AudioGenres.GENREID; /** - * Join to get Songs for an Artist + * Join to get Songs for an Artist or Album with artist info and album info only if available */ - String SONGS_FOR_ARTIST_JOIN = - SONGS + " JOIN " + ALBUM_ARTISTS + " ON " + - SONGS + "." + MediaContract.Songs.HOST_ID + "=" + ALBUM_ARTISTS + "." + MediaContract.AlbumArtists.HOST_ID + + String SONGS_FOR_ARTIST_AND_OR_ALBUM_JOIN = + SONG_ARTISTS + " JOIN " + SONGS + " ON " + + SONG_ARTISTS + "." + MediaContract.SongArtists.HOST_ID + "=" + SONGS + "." + MediaContract.Songs.HOST_ID + " AND " + - SONGS + "." + MediaContract.Songs.ALBUMID + "=" + ALBUM_ARTISTS + "." + MediaContract.AlbumArtists.ALBUMID + - " JOIN " + ALBUMS + " ON " + - SONGS + "." + MediaContract.Songs.HOST_ID + "=" + ALBUMS + "." + MediaContract.Albums.HOST_ID + + SONG_ARTISTS + "." + MediaContract.SongArtists.SONGID + "=" + SONGS + "." + MediaContract.Songs.SONGID + + " LEFT JOIN " + ARTISTS + " ON " + + SONG_ARTISTS + "." + MediaContract.SongArtists.HOST_ID + "=" + ARTISTS + "." + MediaContract.Artists.HOST_ID + " AND " + - SONGS + "." + MediaContract.Songs.ALBUMID + "=" + ALBUMS + "." + MediaContract.Albums.ALBUMID; - - String SONGS_AND_ALBUM_JOIN = - SONGS + " JOIN " + ALBUMS + " ON " + - SONGS + "." + MediaContract.Songs.HOST_ID + "=" + ALBUMS + "." + MediaContract.Albums.HOST_ID + + SONG_ARTISTS + "." + MediaContract.SongArtists.ARTISTID + "=" + ARTISTS + "." + MediaContract.Artists.ARTISTID + + " LEFT JOIN " + ALBUMS + " ON " + + SONG_ARTISTS + "." + MediaContract.SongArtists.HOST_ID + "=" + ALBUMS + "." + MediaContract.Albums.HOST_ID + " AND " + SONGS + "." + MediaContract.Songs.ALBUMID + "=" + ALBUMS + "." + MediaContract.Albums.ALBUMID; } + + private interface References { String HOST_ID = "REFERENCES " + Tables.HOSTS + "(" + BaseColumns._ID + ")"; @@ -118,6 +119,8 @@ public class MediaDatabase extends SQLiteOpenHelper { "REFERENCES " + Tables.ARTISTS + "(" + MediaContract.ArtistsColumns.ARTISTID + ")"; String GENREID = "REFERENCES " + Tables.AUDIO_GENRES + "(" + MediaContract.AudioGenresColumns.GENREID + ")"; + String SONGID = + "REFERENCES " + Tables.SONGS + "(" + MediaContract.Songs.SONGID + ")"; } public MediaDatabase(Context context) { @@ -359,6 +362,8 @@ public class MediaDatabase extends SQLiteOpenHelper { ") ON CONFLICT REPLACE)" ); + createSongArtistsTable(db); + // AudioGenres db.execSQL("CREATE TABLE " + Tables.AUDIO_GENRES + "(" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + @@ -378,7 +383,7 @@ public class MediaDatabase extends SQLiteOpenHelper { BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + MediaContract.AlbumArtistsColumns.HOST_ID + " INTEGER NOT NULL " + References.HOST_ID + ", " + MediaContract.AlbumArtistsColumns.ALBUMID + " INTEGER NOT NULL " + References.ALBUMID + ", " + - MediaContract.AlbumArtistsColumns.ARTISTID + " INTEGER NOT NULL " + References .ARTISTID + ", " + + MediaContract.AlbumArtistsColumns.ARTISTID + " INTEGER NOT NULL " + References.ARTISTID + ", " + "UNIQUE (" + MediaContract.AlbumArtistsColumns.HOST_ID + ", " + MediaContract.AlbumArtistsColumns.ALBUMID + ", " + @@ -471,6 +476,8 @@ public class MediaDatabase extends SQLiteOpenHelper { db.execSQL("ALTER TABLE " + Tables.HOSTS + " ADD COLUMN " + MediaContract.HostsColumns.EVENT_SERVER_PORT + " INTEGER DEFAULT " + HostInfo.DEFAULT_EVENT_SERVER_PORT + ";"); + case DB_VERSION_PRE_SONG_ARTISTS: + createSongArtistsTable(db); } } @@ -515,5 +522,19 @@ public class MediaDatabase extends SQLiteOpenHelper { return order.toString(); } + + private void createSongArtistsTable(SQLiteDatabase db) { + db.execSQL("CREATE TABLE " + Tables.SONG_ARTISTS + "(" + + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + MediaContract.SongArtistsColumns.HOST_ID + " INTEGER NOT NULL " + References.HOST_ID + ", " + + MediaContract.SongArtistsColumns.SONGID + " INTEGER NOT NULL " + References.SONGID + ", " + + MediaContract.SongArtistsColumns.ARTISTID + " INTEGER NOT NULL " + References .ARTISTID + ", " + + "UNIQUE (" + + MediaContract.SongArtistsColumns.HOST_ID + ", " + + MediaContract.SongArtistsColumns.SONGID + ", " + + MediaContract.SongArtistsColumns.ARTISTID + + ") ON CONFLICT REPLACE)" + ); + } } diff --git a/app/src/main/java/org/xbmc/kore/provider/MediaProvider.java b/app/src/main/java/org/xbmc/kore/provider/MediaProvider.java index 75f8e3e..062ebe0 100644 --- a/app/src/main/java/org/xbmc/kore/provider/MediaProvider.java +++ b/app/src/main/java/org/xbmc/kore/provider/MediaProvider.java @@ -73,6 +73,7 @@ public class MediaProvider extends ContentProvider { private static final int ARTISTS_LIST = 601; private static final int ARTISTS_ID = 602; private static final int ARTIST_ALBUMS_LIST = 610; + private static final int ARTIST_SONGS_LIST = 611; private static final int ALBUMS_ALL = 700; private static final int ALBUMS_LIST = 701; @@ -81,7 +82,6 @@ public class MediaProvider extends ContentProvider { private static final int ALBUM_GENRES_LIST = 711; private static final int SONGS_ALL = 800; - private static final int SONGS_ARTIST = 801; private static final int SONGS_ALBUM = 802; private static final int SONGS_ID = 803; private static final int SONGS_LIST = 804; @@ -93,6 +93,7 @@ public class MediaProvider extends ContentProvider { private static final int ALBUM_ARTISTS_ALL = 1000; private static final int ALBUM_GENRES_ALL = 1001; + private static final int SONG_ARTISTS_ALL = 1002; private static final int MUSIC_VIDEOS_ALL = 1100; private static final int MUSIC_VIDEOS_LIST = 1101; @@ -169,6 +170,9 @@ public class MediaProvider extends ContentProvider { matcher.addURI(authority, MediaContract.PATH_HOSTS + "/*/" + MediaContract.PATH_ARTISTS + "/*/" + MediaContract.PATH_ALBUMS, ARTIST_ALBUMS_LIST); + matcher.addURI(authority, MediaContract.PATH_HOSTS + "/*/" + + MediaContract.PATH_ARTISTS + "/*/" + + MediaContract.PATH_SONGS, ARTIST_SONGS_LIST); // Albums matcher.addURI(authority, MediaContract.PATH_ALBUMS, ALBUMS_ALL); @@ -193,9 +197,6 @@ public class MediaProvider extends ContentProvider { matcher.addURI(authority, MediaContract.PATH_HOSTS + "/*/" + MediaContract.PATH_ALBUMS + "/*/" + MediaContract.PATH_SONGS + "/*", SONGS_ID); - matcher.addURI(authority, MediaContract.PATH_HOSTS + "/*/" + - MediaContract.PATH_ARTISTS + "/*/" + - MediaContract.PATH_SONGS, SONGS_ARTIST); // Genres matcher.addURI(authority, MediaContract.PATH_AUDIO_GENRES, AUDIO_GENRES_ALL); @@ -211,6 +212,8 @@ public class MediaProvider extends ContentProvider { matcher.addURI(authority, MediaContract.PATH_ALBUM_ARTISTS, ALBUM_ARTISTS_ALL); // AlbumGenres matcher.addURI(authority, MediaContract.PATH_ALBUM_GENRES, ALBUM_GENRES_ALL); + // SongArtists + matcher.addURI(authority, MediaContract.PATH_SONG_ARTISTS, SONG_ARTISTS_ALL); // Music Videos matcher.addURI(authority, MediaContract.PATH_MUSIC_VIDEOS, MUSIC_VIDEOS_ALL); @@ -288,7 +291,7 @@ public class MediaProvider extends ContentProvider { return MediaContract.Albums.CONTENT_ITEM_TYPE; case SONGS_ALL: case SONGS_LIST: - case SONGS_ARTIST: + case ARTIST_SONGS_LIST: case SONGS_ALBUM: return MediaContract.Songs.CONTENT_TYPE; case SONGS_ID: @@ -303,6 +306,8 @@ public class MediaProvider extends ContentProvider { return MediaContract.AlbumArtists.CONTENT_TYPE; case ALBUM_GENRES_ALL: return MediaContract.AlbumGenres.CONTENT_TYPE; + case SONG_ARTISTS_ALL: + return MediaContract.SongArtists.CONTENT_TYPE; case MUSIC_VIDEOS_ALL: case MUSIC_VIDEOS_LIST: return MediaContract.MusicVideos.CONTENT_TYPE; @@ -326,7 +331,6 @@ public class MediaProvider extends ContentProvider { default: { // Most cases are handled with simple SelectionBuilder final SelectionBuilder builder = buildQuerySelection(uri, match); - cursor = builder.where(selection, selectionArgs) .query(db, projection, sortOrder); } @@ -413,6 +417,10 @@ public class MediaProvider extends ContentProvider { table = MediaDatabase.Tables.ALBUM_ARTISTS; break; } + case SONG_ARTISTS_ALL: { + table = MediaDatabase.Tables.SONG_ARTISTS; + break; + } case MUSIC_VIDEOS_ALL: { table = MediaDatabase.Tables.MUSIC_VIDEOS; break; @@ -430,6 +438,7 @@ public class MediaProvider extends ContentProvider { switch (match) { case ALBUM_GENRES_ALL: case ALBUM_ARTISTS_ALL: + case SONG_ARTISTS_ALL: // Nothing to add to these tables break; default: @@ -658,8 +667,15 @@ public class MediaProvider extends ContentProvider { } case SONGS_LIST: { final String hostId = MediaContract.Hosts.getHostId(uri); - return builder.table(MediaDatabase.Tables.SONGS_AND_ALBUM_JOIN) - .where(Qualified.SONGS_HOST_ID + "=?", hostId); + return builder.table(MediaDatabase.Tables.SONGS_FOR_ARTIST_AND_OR_ALBUM_JOIN) + .mapToTable(MediaContract.Songs.SONGID, MediaDatabase.Tables.SONGS) + .mapToTable(MediaContract.Songs.TITLE, MediaDatabase.Tables.SONGS) + .mapToTable(MediaContract.Songs.ALBUMID, MediaDatabase.Tables.SONGS) + .mapToTable(MediaContract.Songs.UPDATED, MediaDatabase.Tables.SONGS) + .mapToTable(MediaContract.Songs.THUMBNAIL, MediaDatabase.Tables.SONGS) + .mapToTable(MediaContract.SongArtists.ARTISTID, MediaDatabase.Tables.SONG_ARTISTS) + .where(Qualified.SONGS_HOST_ID + "=?", hostId) + .groupBy(Qualified.SONGS_SONGID); } case SONGS_ALBUM: { final String hostId = MediaContract.Hosts.getHostId(uri); @@ -668,14 +684,6 @@ public class MediaProvider extends ContentProvider { .where(MediaContract.Songs.HOST_ID + "=?", hostId) .where(MediaContract.Songs.ALBUMID + "=?", albumId); } - case SONGS_ARTIST: { - final String hostId = MediaContract.Hosts.getHostId(uri); - final String artistId = MediaContract.Artists.getArtistId(uri); - LogUtils.LOGD(TAG, "buildQuerySelection: SONGS_ARTIST: "+MediaDatabase.Tables.SONGS_FOR_ARTIST_JOIN); - return builder.table(MediaDatabase.Tables.SONGS_FOR_ARTIST_JOIN) - .where(Qualified.SONGS_HOST_ID + "=?", hostId) - .where(Qualified.ALBUM_ARTISTS_ARTISTID + "=?", artistId); - } case SONGS_ID: { final String hostId = MediaContract.Hosts.getHostId(uri); final String albumId = MediaContract.Albums.getAlbumId(uri); @@ -703,6 +711,9 @@ public class MediaProvider extends ContentProvider { case ALBUM_ARTISTS_ALL: { return builder.table(MediaDatabase.Tables.ALBUM_ARTISTS); } + case SONG_ARTISTS_ALL: { + return builder.table(MediaDatabase.Tables.SONG_ARTISTS); + } case ALBUM_GENRES_ALL: { return builder.table(MediaDatabase.Tables.ALBUM_GENRES); } @@ -718,6 +729,20 @@ public class MediaProvider extends ContentProvider { .where(Qualified.ALBUM_ARTISTS_HOST_ID + "=?", hostId) .where(Qualified.ALBUM_ARTISTS_ARTISTID + "=?", artistId); } + case ARTIST_SONGS_LIST: { + // Songs for Artists + final String hostId = MediaContract.Hosts.getHostId(uri); + final String artistId = MediaContract.Artists.getArtistId(uri); + return builder.table(MediaDatabase.Tables.SONGS_FOR_ARTIST_AND_OR_ALBUM_JOIN) + .mapToTable(MediaContract.Songs.SONGID, MediaDatabase.Tables.SONGS) + .mapToTable(MediaContract.Songs.TITLE, MediaDatabase.Tables.SONGS) + .mapToTable(MediaContract.Songs.ALBUMID, MediaDatabase.Tables.SONGS) + .mapToTable(MediaContract.Songs.UPDATED, MediaDatabase.Tables.SONGS) + .mapToTable(MediaContract.Songs.THUMBNAIL, MediaDatabase.Tables.SONGS) + .mapToTable(MediaContract.SongArtists.ARTISTID, MediaDatabase.Tables.SONG_ARTISTS) + .where(Qualified.SONG_ARTISTS_HOST_ID + "=?", hostId) + .where(Qualified.SONG_ARTISTS_ARTISTID + "=?", artistId); + } case ALBUM_ARTISTS_LIST: { // Artists for Album final String hostId = MediaContract.Hosts.getHostId(uri); @@ -795,5 +820,11 @@ public class MediaProvider extends ContentProvider { MediaDatabase.Tables.ALBUM_GENRES + "." + MediaContract.AlbumGenres.ALBUMID; String SONGS_HOST_ID = MediaDatabase.Tables.SONGS + "." + MediaContract.Songs.HOST_ID; + String SONGS_SONGID = + MediaDatabase.Tables.SONGS + "." + MediaContract.Songs.SONGID; + String SONG_ARTISTS_HOST_ID = + MediaDatabase.Tables.SONG_ARTISTS + "." + MediaContract.SongArtists.HOST_ID; + String SONG_ARTISTS_ARTISTID = + MediaDatabase.Tables.SONG_ARTISTS + "." + MediaContract.SongArtists.ARTISTID; } } diff --git a/app/src/main/java/org/xbmc/kore/service/library/SyncMusic.java b/app/src/main/java/org/xbmc/kore/service/library/SyncMusic.java index 9bcf37f..764e8e0 100644 --- a/app/src/main/java/org/xbmc/kore/service/library/SyncMusic.java +++ b/app/src/main/java/org/xbmc/kore/service/library/SyncMusic.java @@ -317,7 +317,8 @@ public class SyncMusic extends SyncItem { AudioType.FieldsSong.THUMBNAIL, AudioType.FieldsSong.FILE, AudioType.FieldsSong.ALBUMID, //AudioType.FieldsSong.LASTPLAYED, AudioType.FieldsSong.DISC, - //AudioType.FieldsSong.GENREID, AudioType.FieldsSong.ARTISTID, + //AudioType.FieldsSong.GENREID, + AudioType.FieldsSong.ARTISTID, //AudioType.FieldsSong.DISPLAYARTIST, AudioType.FieldsSong.ALBUMARTISTID }; @@ -345,14 +346,30 @@ public class SyncMusic extends SyncItem { limitsReturned = result.limits; } + int totalArtistsCount = 0; // Save partial results to DB ContentValues songValuesBatch[] = new ContentValues[items.size()]; for (int i = 0; i < items.size(); i++) { AudioType.DetailsSong song = items.get(i); songValuesBatch[i] = SyncUtils.contentValuesFromSong(hostId, song); + totalArtistsCount += song.artistid.size(); } contentResolver.bulkInsert(MediaContract.Songs.CONTENT_URI, songValuesBatch); + // Iterate on each song, collect the artists and insert them + ContentValues songArtistsValuesBatch[] = new ContentValues[totalArtistsCount]; + int artistCount = 0; + for (AudioType.DetailsSong song : items) { + for (int artistId : song.artistid) { + songArtistsValuesBatch[artistCount] = new ContentValues(); + songArtistsValuesBatch[artistCount].put(MediaContract.SongArtists.HOST_ID, hostId); + songArtistsValuesBatch[artistCount].put(MediaContract.SongArtists.SONGID, song.songid); + songArtistsValuesBatch[artistCount].put(MediaContract.SongArtists.ARTISTID, artistId); + artistCount++; + } + } + contentResolver.bulkInsert(MediaContract.SongArtists.CONTENT_URI, songArtistsValuesBatch); + if (moreItemsAvailable(limitsReturned)) { LogUtils.LOGD(TAG, "chainCallSyncSongs: More results on media center, recursing."); result = null; // Help the GC? diff --git a/app/src/main/java/org/xbmc/kore/service/library/SyncUtils.java b/app/src/main/java/org/xbmc/kore/service/library/SyncUtils.java index c5fad35..2eb4233 100644 --- a/app/src/main/java/org/xbmc/kore/service/library/SyncUtils.java +++ b/app/src/main/java/org/xbmc/kore/service/library/SyncUtils.java @@ -27,7 +27,6 @@ import org.xbmc.kore.jsonrpc.type.VideoType; import org.xbmc.kore.jsonrpc.type.AudioType; import org.xbmc.kore.jsonrpc.type.LibraryType; import org.xbmc.kore.provider.MediaContract; -import org.xbmc.kore.service.library.LibrarySyncService; import org.xbmc.kore.utils.Utils; import java.util.ArrayList; @@ -42,7 +41,7 @@ public class SyncUtils { void onServiceConnected(LibrarySyncService librarySyncService); } - public static final String LIST_DELIMETER = ", "; + public static final String LIST_DELIMITER = ", "; /** * Returns {@link android.content.ContentValues} from a {@link org.xbmc.kore.jsonrpc.type.VideoType.DetailsMovie} movie @@ -61,7 +60,7 @@ public class SyncUtils { movieValues.put(MediaContract.MoviesColumns.TITLE, movie.title); movieValues.put(MediaContract.MoviesColumns.FILE, movie.file); movieValues.put(MediaContract.MoviesColumns.PLOT, movie.plot); - movieValues.put(MediaContract.MoviesColumns.DIRECTOR, Utils.listStringConcat(movie.director, LIST_DELIMETER)); + movieValues.put(MediaContract.MoviesColumns.DIRECTOR, Utils.listStringConcat(movie.director, LIST_DELIMITER)); movieValues.put(MediaContract.MoviesColumns.RUNTIME, movie.runtime); if (movie.streamdetails != null) { if (movie.streamdetails.audio.size() > 0) { @@ -78,7 +77,7 @@ public class SyncUtils { movieValues.put(MediaContract.MoviesColumns.AUDIO_CHANNELS, selectedStream.channels); movieValues.put(MediaContract.MoviesColumns.AUDIO_CODEC, selectedStream.codec); movieValues.put(MediaContract.MoviesColumns.AUDIO_LANGUAGE, - Utils.listStringConcat(languages, LIST_DELIMETER)); + Utils.listStringConcat(languages, LIST_DELIMITER)); } if (movie.streamdetails.subtitle.size() > 0) { // Concat all subtitle languages @@ -87,7 +86,7 @@ public class SyncUtils { subtitles.add(movie.streamdetails.subtitle.get(j).language); } movieValues.put(MediaContract.MoviesColumns.SUBTITLES_LANGUAGES, - Utils.listStringConcat(subtitles, LIST_DELIMETER)); + Utils.listStringConcat(subtitles, LIST_DELIMITER)); } if (movie.streamdetails.video.size() > 0) { // We're only getting the first video channel... @@ -102,22 +101,22 @@ public class SyncUtils { } } movieValues.put(MediaContract.MoviesColumns.COUNTRIES, - Utils.listStringConcat(movie.country, LIST_DELIMETER)); + Utils.listStringConcat(movie.country, LIST_DELIMITER)); movieValues.put(MediaContract.MoviesColumns.GENRES, - Utils.listStringConcat(movie.genre, LIST_DELIMETER)); + Utils.listStringConcat(movie.genre, LIST_DELIMITER)); movieValues.put(MediaContract.MoviesColumns.IMDBNUMBER, movie.imdbnumber); movieValues.put(MediaContract.MoviesColumns.MPAA, movie.mpaa); movieValues.put(MediaContract.MoviesColumns.RATING, movie.rating); movieValues.put(MediaContract.MoviesColumns.SET, movie.set); movieValues.put(MediaContract.MoviesColumns.SETID, movie.setid); movieValues.put(MediaContract.MoviesColumns.STUDIOS, - Utils.listStringConcat(movie.studio, LIST_DELIMETER)); + Utils.listStringConcat(movie.studio, LIST_DELIMITER)); movieValues.put(MediaContract.MoviesColumns.TAGLINE, movie.tagline); movieValues.put(MediaContract.MoviesColumns.TOP250, movie.top250); movieValues.put(MediaContract.MoviesColumns.TRAILER, movie.trailer); movieValues.put(MediaContract.MoviesColumns.VOTES, movie.votes); movieValues.put(MediaContract.MoviesColumns.WRITERS, - Utils.listStringConcat(movie.writer, LIST_DELIMETER)); + Utils.listStringConcat(movie.writer, LIST_DELIMITER)); movieValues.put(MediaContract.MoviesColumns.YEAR, movie.year); return movieValues; @@ -164,10 +163,10 @@ public class SyncUtils { tvshowValues.put(MediaContract.TVShowsColumns.PREMIERED, tvshow.premiered); tvshowValues.put(MediaContract.TVShowsColumns.RATING, tvshow.rating); tvshowValues.put(MediaContract.TVShowsColumns.STUDIO, - Utils.listStringConcat(tvshow.studio, LIST_DELIMETER)); + Utils.listStringConcat(tvshow.studio, LIST_DELIMITER)); tvshowValues.put(MediaContract.TVShowsColumns.WATCHEDEPISODES, tvshow.watchedepisodes); tvshowValues.put(MediaContract.TVShowsColumns.GENRES, - Utils.listStringConcat(tvshow.genre, LIST_DELIMETER)); + Utils.listStringConcat(tvshow.genre, LIST_DELIMITER)); return tvshowValues; } @@ -215,12 +214,12 @@ public class SyncUtils { episodeValues.put(MediaContract.EpisodesColumns.TITLE, episode.title); episodeValues.put(MediaContract.EpisodesColumns.FILE, episode.file); episodeValues.put(MediaContract.EpisodesColumns.PLOT, episode.plot); - episodeValues.put(MediaContract.EpisodesColumns.DIRECTOR, Utils.listStringConcat(episode.director, LIST_DELIMETER)); + episodeValues.put(MediaContract.EpisodesColumns.DIRECTOR, Utils.listStringConcat(episode.director, LIST_DELIMITER)); episodeValues.put(MediaContract.EpisodesColumns.RUNTIME, episode.runtime); episodeValues.put(MediaContract.EpisodesColumns.FIRSTAIRED, episode.firstaired); episodeValues.put(MediaContract.EpisodesColumns.RATING, episode.rating); episodeValues.put(MediaContract.EpisodesColumns.SHOWTITLE, episode.showtitle); - episodeValues.put(MediaContract.EpisodesColumns.WRITER, Utils.listStringConcat(episode.writer, LIST_DELIMETER)); + episodeValues.put(MediaContract.EpisodesColumns.WRITER, Utils.listStringConcat(episode.writer, LIST_DELIMITER)); if (episode.streamdetails.audio.size() > 0) { // Get the stream with the most channels and concat all the languages @@ -235,7 +234,7 @@ public class SyncUtils { } episodeValues.put(MediaContract.EpisodesColumns.AUDIO_CHANNELS, selectedStream.channels); episodeValues.put(MediaContract.EpisodesColumns.AUDIO_CODEC, selectedStream.codec); - episodeValues.put(MediaContract.EpisodesColumns.AUDIO_LANGUAGE, Utils.listStringConcat(languages, LIST_DELIMETER)); + episodeValues.put(MediaContract.EpisodesColumns.AUDIO_LANGUAGE, Utils.listStringConcat(languages, LIST_DELIMITER)); } if (episode.streamdetails.subtitle.size() > 0) { // Concat all subtitle languages @@ -243,7 +242,7 @@ public class SyncUtils { for (int j = 0; j < episode.streamdetails.subtitle.size(); j++) { subtitles.add(episode.streamdetails.subtitle.get(j).language); } - episodeValues.put(MediaContract.EpisodesColumns.SUBTITLES_LANGUAGES, Utils.listStringConcat(subtitles, LIST_DELIMETER)); + episodeValues.put(MediaContract.EpisodesColumns.SUBTITLES_LANGUAGES, Utils.listStringConcat(subtitles, LIST_DELIMITER)); } if (episode.streamdetails.video.size() > 0) { // We're only getting the first video channel... @@ -273,7 +272,7 @@ public class SyncUtils { castValues.put(MediaContract.ArtistsColumns.ARTIST, artist.artist); castValues.put(MediaContract.ArtistsColumns.DESCRIPTION, artist.description); castValues.put(MediaContract.ArtistsColumns.GENRE, - Utils.listStringConcat(artist.genre, LIST_DELIMETER)); + Utils.listStringConcat(artist.genre, LIST_DELIMITER)); castValues.put(MediaContract.ArtistsColumns.FANART, artist.fanart); castValues.put(MediaContract.ArtistsColumns.THUMBNAIL, artist.thumbnail); @@ -315,7 +314,7 @@ public class SyncUtils { castValues.put(MediaContract.Albums.ALBUMLABEL, album.albumlabel); castValues.put(MediaContract.Albums.DESCRIPTION, album.description); castValues.put(MediaContract.Albums.PLAYCOUNT, album.playcount); - castValues.put(MediaContract.Albums.GENRE, Utils.listStringConcat(album.genre, LIST_DELIMETER)); + castValues.put(MediaContract.Albums.GENRE, Utils.listStringConcat(album.genre, LIST_DELIMITER)); return castValues; } @@ -357,7 +356,7 @@ public class SyncUtils { musicVideoValues.put(MediaContract.MusicVideosColumns.TITLE, musicVideo.title); musicVideoValues.put(MediaContract.MusicVideosColumns.FILE, musicVideo.file); musicVideoValues.put(MediaContract.MusicVideosColumns.PLOT, musicVideo.plot); - musicVideoValues.put(MediaContract.MusicVideosColumns.DIRECTOR, Utils.listStringConcat(musicVideo.director, LIST_DELIMETER)); + musicVideoValues.put(MediaContract.MusicVideosColumns.DIRECTOR, Utils.listStringConcat(musicVideo.director, LIST_DELIMITER)); musicVideoValues.put(MediaContract.MusicVideosColumns.RUNTIME, musicVideo.runtime); if (musicVideo.streamdetails != null) { if (musicVideo.streamdetails.audio.size() > 0) { @@ -374,7 +373,7 @@ public class SyncUtils { musicVideoValues.put(MediaContract.MusicVideosColumns.AUDIO_CHANNELS, selectedStream.channels); musicVideoValues.put(MediaContract.MusicVideosColumns.AUDIO_CODEC, selectedStream.codec); musicVideoValues.put(MediaContract.MusicVideosColumns.AUDIO_LANGUAGE, - Utils.listStringConcat(languages, LIST_DELIMETER)); + Utils.listStringConcat(languages, LIST_DELIMITER)); } if (musicVideo.streamdetails.subtitle.size() > 0) { // Concat all subtitle languages @@ -383,7 +382,7 @@ public class SyncUtils { subtitles.add(musicVideo.streamdetails.subtitle.get(j).language); } musicVideoValues.put(MediaContract.MusicVideosColumns.SUBTITLES_LANGUAGES, - Utils.listStringConcat(subtitles, LIST_DELIMETER)); + Utils.listStringConcat(subtitles, LIST_DELIMITER)); } if (musicVideo.streamdetails.video.size() > 0) { // We're only getting the first video channel... @@ -399,13 +398,13 @@ public class SyncUtils { } musicVideoValues.put(MediaContract.MusicVideosColumns.ALBUM, musicVideo.album); musicVideoValues.put(MediaContract.MusicVideosColumns.ARTIST, - Utils.listStringConcat(musicVideo.artist, LIST_DELIMETER)); + Utils.listStringConcat(musicVideo.artist, LIST_DELIMITER)); musicVideoValues.put(MediaContract.MusicVideosColumns.GENRES, - Utils.listStringConcat(musicVideo.genre, LIST_DELIMETER)); + Utils.listStringConcat(musicVideo.genre, LIST_DELIMITER)); musicVideoValues.put(MediaContract.MusicVideosColumns.STUDIOS, - Utils.listStringConcat(musicVideo.studio, LIST_DELIMETER)); + Utils.listStringConcat(musicVideo.studio, LIST_DELIMITER)); musicVideoValues.put(MediaContract.MusicVideosColumns.TAG, - Utils.listStringConcat(musicVideo.tag, LIST_DELIMETER)); + Utils.listStringConcat(musicVideo.tag, LIST_DELIMITER)); musicVideoValues.put(MediaContract.MusicVideosColumns.TRACK, musicVideo.track); musicVideoValues.put(MediaContract.MusicVideosColumns.YEAR, musicVideo.year); diff --git a/app/src/main/java/org/xbmc/kore/ui/ArtistOverviewFragment.java b/app/src/main/java/org/xbmc/kore/ui/ArtistOverviewFragment.java index ba33e6f..11c1ee6 100644 --- a/app/src/main/java/org/xbmc/kore/ui/ArtistOverviewFragment.java +++ b/app/src/main/java/org/xbmc/kore/ui/ArtistOverviewFragment.java @@ -229,7 +229,7 @@ public class ArtistOverviewFragment extends AbstractDetailsFragment case LOADER_SONGS: uri = MediaContract.Songs.buildArtistSongsListUri(hostInfo.getId(), artistId); return new CursorLoader(getActivity(), uri, - AlbumSongsListQuery.PROJECTION, null, null, AlbumSongsListQuery.SORT); + SongsListQuery.PROJECTION, null, null, SongsListQuery.SORT); default: return null; } @@ -299,16 +299,16 @@ public class ArtistOverviewFragment extends AbstractDetailsFragment private FileDownloadHelper.SongInfo createSongInfo(Cursor cursor) { FileDownloadHelper.SongInfo songInfo = null; - String albumTitle = albumTitles.get(cursor.getInt(AlbumSongsListQuery.ALBUMID)); + String albumTitle = albumTitles.get(cursor.getInt(SongsListQuery.ALBUMID)); if (albumTitle != null) { // Add this song to the list songInfo = new FileDownloadHelper.SongInfo( artistTitle, albumTitle, - cursor.getInt(AlbumSongsListQuery.SONGID), - cursor.getInt(AlbumSongsListQuery.TRACK), - cursor.getString(AlbumSongsListQuery.TITLE), - cursor.getString(AlbumSongsListQuery.FILE)); + cursor.getInt(SongsListQuery.SONGID), + cursor.getInt(SongsListQuery.TRACK), + cursor.getString(SongsListQuery.TITLE), + cursor.getString(SongsListQuery.FILE)); } return songInfo; } @@ -457,7 +457,7 @@ public class ArtistOverviewFragment extends AbstractDetailsFragment /** * Song list query parameters. */ - private interface AlbumSongsListQuery { + private interface SongsListQuery { String[] PROJECTION = { MediaDatabase.Tables.SONGS + "." + BaseColumns._ID, MediaDatabase.Tables.SONGS + "." + MediaContract.Songs.TITLE, diff --git a/app/src/main/java/org/xbmc/kore/ui/SongsListFragment.java b/app/src/main/java/org/xbmc/kore/ui/SongsListFragment.java index 119d22b..965ad81 100644 --- a/app/src/main/java/org/xbmc/kore/ui/SongsListFragment.java +++ b/app/src/main/java/org/xbmc/kore/ui/SongsListFragment.java @@ -49,6 +49,8 @@ import org.xbmc.kore.utils.LogUtils; import org.xbmc.kore.utils.MediaPlayerUtils; import org.xbmc.kore.utils.UIUtils; +import java.util.ArrayList; + /** * Fragment that presents the songs list */ @@ -94,7 +96,7 @@ public class SongsListFragment extends AbstractCursorListFragment { } return new CursorLoader(getActivity(), uri, - SongsAlbumsListQuery.PROJECTION, selection, selectionArgs, SongsAlbumsListQuery.SORT); + SongsListQuery.PROJECTION, selection, selectionArgs, SongsListQuery.SORT); } @Override @@ -129,35 +131,10 @@ public class SongsListFragment extends AbstractCursorListFragment { super.onCreateOptionsMenu(menu, inflater); } - /** - * Song list query parameters. - */ - private interface SongsListQuery { - String[] PROJECTION = { - MediaDatabase.Tables.SONGS + "." + BaseColumns._ID, - MediaDatabase.Tables.SONGS + "." + MediaContract.Songs.TITLE, - MediaDatabase.Tables.SONGS + "." + MediaContract.Songs.TRACK, - MediaDatabase.Tables.SONGS + "." + MediaContract.Songs.DURATION, - MediaDatabase.Tables.SONGS + "." + MediaContract.Songs.FILE, - MediaDatabase.Tables.SONGS + "." + MediaContract.Songs.SONGID, - MediaDatabase.Tables.SONGS + "." + MediaContract.Songs.ALBUMID, - }; - - String SORT = MediaContract.Songs.TRACK + " ASC"; - - int ID = 0; - int TITLE = 1; - int TRACK = 2; - int DURATION = 3; - int FILE = 4; - int SONGID = 5; - int ALBUMID = 6; - } - /** * Album songs list query parameters. */ - private interface SongsAlbumsListQuery { + private interface SongsListQuery { String[] PROJECTION = { MediaDatabase.Tables.SONGS + "." + BaseColumns._ID, MediaDatabase.Tables.SONGS + "." + MediaContract.Songs.TITLE, @@ -170,6 +147,7 @@ public class SongsListFragment extends AbstractCursorListFragment { MediaDatabase.Tables.ALBUMS + "." + MediaContract.Albums.GENRE, MediaDatabase.Tables.ALBUMS + "." + MediaContract.Albums.YEAR, MediaDatabase.Tables.ALBUMS + "." + MediaContract.Albums.THUMBNAIL, + MediaDatabase.Tables.ARTISTS + "." + MediaContract.Artists.ARTIST }; String SORT = MediaDatabase.sortCommonTokens(MediaDatabase.Tables.SONGS @@ -187,6 +165,7 @@ public class SongsListFragment extends AbstractCursorListFragment { int GENRE = 8; int YEAR = 9; int THUMBNAIL = 10; + int ARTIST = 11; } private class SongsAdapter extends CursorAdapter { @@ -229,25 +208,30 @@ public class SongsListFragment extends AbstractCursorListFragment { public void bindView(View view, Context context, Cursor cursor) { final ViewHolder viewHolder = (ViewHolder)view.getTag(); - String title = cursor.getString(SongsAlbumsListQuery.TITLE); - viewHolder.songId = cursor.getInt(SongsAlbumsListQuery.SONGID); + String title = cursor.getString(SongsListQuery.TITLE); + viewHolder.songId = cursor.getInt(SongsListQuery.SONGID); viewHolder.title.setText(title); - viewHolder.artist.setText(String.valueOf(cursor.getString(SongsAlbumsListQuery.ALBUMARTIST))); - int year = cursor.getInt(SongsAlbumsListQuery.YEAR); + String artist = cursor.getString(SongsListQuery.ALBUMARTIST); + if (TextUtils.isEmpty(artist)) + artist = cursor.getString(SongsListQuery.ARTIST); + + viewHolder.artist.setText(artist); + + int year = cursor.getInt(SongsListQuery.YEAR); if (year > 0) { setDetails(viewHolder.details, - cursor.getString(SongsAlbumsListQuery.ALBUMTITLE), + cursor.getString(SongsListQuery.ALBUMTITLE), String.valueOf(year), - cursor.getString(SongsAlbumsListQuery.GENRE)); + cursor.getString(SongsListQuery.GENRE)); } else { setDetails(viewHolder.details, - cursor.getString(SongsAlbumsListQuery.ALBUMTITLE), - cursor.getString(SongsAlbumsListQuery.GENRE)); + cursor.getString(SongsListQuery.ALBUMTITLE), + cursor.getString(SongsListQuery.GENRE)); } - String thumbnail = cursor.getString(SongsAlbumsListQuery.THUMBNAIL); + String thumbnail = cursor.getString(SongsListQuery.THUMBNAIL); UIUtils.loadImageWithCharacterAvatar(context, hostManager, thumbnail, title, viewHolder.art, artWidth, artHeight); @@ -306,21 +290,12 @@ public class SongsListFragment extends AbstractCursorListFragment { return; } - StringBuilder stringBuilder = new StringBuilder(); - - int i = 0; - int size = elements.length - 1; - for (; i < size; i++) { - if (!TextUtils.isEmpty(elements[i])) { - stringBuilder.append(elements[i]); - stringBuilder.append(" | "); - } + ArrayList details = new ArrayList<>(); + for (int i = 0; i < elements.length; i++) { + if (!TextUtils.isEmpty(elements[i])) + details.add(elements[i]); } - if (elements.length > 0) { - stringBuilder.append(elements[i]); - } - - textView.setText(stringBuilder.toString()); + textView.setText(TextUtils.join(" | ", details.toArray())); } } diff --git a/app/src/main/java/org/xbmc/kore/utils/SelectionBuilder.java b/app/src/main/java/org/xbmc/kore/utils/SelectionBuilder.java index f407ae4..5081ea1 100644 --- a/app/src/main/java/org/xbmc/kore/utils/SelectionBuilder.java +++ b/app/src/main/java/org/xbmc/kore/utils/SelectionBuilder.java @@ -45,7 +45,7 @@ public class SelectionBuilder { private Map mProjectionMap = new HashMap(); private StringBuilder mSelection = new StringBuilder(); private ArrayList mSelectionArgs = new ArrayList(); - + private StringBuilder mGroupBy = new StringBuilder(); /** * Reset any internal state, allowing this builder to be recycled. */ @@ -56,6 +56,20 @@ public class SelectionBuilder { return this; } + public SelectionBuilder groupBy(String... groupByArgs) { + if (groupByArgs != null) { + if (mGroupBy.length() > 0) + mGroupBy.append(", "); + + int size = groupByArgs.length - 1; + for (int i = 0; i < size; i++) { + mGroupBy.append(groupByArgs[i] + ", "); + } + mGroupBy.append(groupByArgs[size]); + } + return this; + } + /** * Append the given selection clause to the internal state. Each clause is * surrounded with parenthesis and combined using {@code AND}. @@ -141,7 +155,7 @@ public class SelectionBuilder { * Execute query using the current internal state as {@code WHERE} clause. */ public Cursor query(SQLiteDatabase db, String[] columns, String orderBy) { - return query(db, columns, null, null, orderBy, null); + return query(db, columns, mGroupBy.toString(), null, orderBy, null); } /**