From 080b5809f3179a9a2f210bc62f11ba6278038453 Mon Sep 17 00:00:00 2001 From: Martijn Brekhof Date: Fri, 23 Sep 2016 20:08:22 +0200 Subject: [PATCH] Unittest/mediaprovider (#251) Implemented integration tests for music items --- .../org/xbmc/kore/testhelpers/Database.java | 83 +---- .../org/xbmc/kore/testhelpers/TestUtils.java | 77 ++++ .../java/org/xbmc/kore/testhelpers/Utils.java | 22 ++ .../mediaprovider/MediaProviderMusicTest.java | 335 ++++++++++++++++++ .../kore/tests/mediaprovider/TestValues.java | 203 +++++++++++ .../ui/RestoreSearchQueryViewPagerTest.java | 4 +- .../assets/AudioLibrary.GetAlbums.json | 39 +- .../assets/AudioLibrary.GetArtists.json | 64 +++- .../assets/AudioLibrary.GetSongs.json | 50 ++- .../org/xbmc/kore/provider/MediaProvider.java | 2 +- .../xbmc/kore/service/library/SyncMusic.java | 172 +++++---- .../xbmc/kore/ui/AlbumDetailsFragment.java | 4 +- .../org/xbmc/kore/ui/AlbumListFragment.java | 2 +- .../xbmc/kore/ui/AudioGenresListFragment.java | 2 +- .../org/xbmc/kore/ui/SongsListFragment.java | 2 +- .../main/java/org/xbmc/kore/utils/Utils.java | 16 + tools/json/README.md | 27 ++ tools/json/gentestnumbers.pl | 283 +++++++++++++++ 18 files changed, 1229 insertions(+), 158 deletions(-) create mode 100644 app/src/androidTest/java/org/xbmc/kore/testhelpers/TestUtils.java create mode 100644 app/src/androidTest/java/org/xbmc/kore/tests/mediaprovider/MediaProviderMusicTest.java create mode 100644 app/src/androidTest/java/org/xbmc/kore/tests/mediaprovider/TestValues.java create mode 100644 tools/json/README.md create mode 100755 tools/json/gentestnumbers.pl diff --git a/app/src/androidTest/java/org/xbmc/kore/testhelpers/Database.java b/app/src/androidTest/java/org/xbmc/kore/testhelpers/Database.java index 7c10a0b..d4f938d 100644 --- a/app/src/androidTest/java/org/xbmc/kore/testhelpers/Database.java +++ b/app/src/androidTest/java/org/xbmc/kore/testhelpers/Database.java @@ -30,6 +30,7 @@ import org.xbmc.kore.jsonrpc.type.LibraryType; import org.xbmc.kore.jsonrpc.type.VideoType; import org.xbmc.kore.provider.MediaContract; import org.xbmc.kore.provider.MediaProvider; +import org.xbmc.kore.service.library.SyncMusic; import org.xbmc.kore.service.library.SyncUtils; import org.xbmc.kore.utils.LogUtils; @@ -45,12 +46,12 @@ public class Database { mediaProvider.onCreate(); HostInfo hostInfo = addHost(context); - + SyncMusic syncMusic = new SyncMusic(hostInfo.getId(), null); insertMovies(context, hostInfo.getId()); - insertArtists(context, hostInfo.getId()); - insertGenres(context, hostInfo.getId()); - insertAlbums(context, hostInfo.getId()); - insertSongs(context, hostInfo.getId()); + insertArtists(context, syncMusic); + insertGenres(context, syncMusic); + insertAlbums(context, syncMusic); + insertSongs(context, syncMusic); return hostInfo; } @@ -96,86 +97,34 @@ public class Database { context.getContentResolver().bulkInsert(MediaContract.MovieCast.CONTENT_URI, movieCastValuesBatch); } - private static void insertArtists(Context context, int hostId) throws ApiException, IOException { + private static void insertArtists(Context context, SyncMusic syncMusic) throws ApiException, IOException { AudioLibrary.GetArtists getArtists = new AudioLibrary.GetArtists(false); String result = Utils.readFile(context, "AudioLibrary.GetArtists.json"); ArrayList artistList = (ArrayList) getArtists.resultFromJson(result).items; - ContentValues artistValuesBatch[] = new ContentValues[artistList.size()]; - for (int i = 0; i < artistList.size(); i++) { - AudioType.DetailsArtist artist = artistList.get(i); - artistValuesBatch[i] = SyncUtils.contentValuesFromArtist(hostId, artist); - } - - context.getContentResolver().bulkInsert(MediaContract.Artists.CONTENT_URI, artistValuesBatch); + syncMusic.insertArtists(artistList, context.getContentResolver()); } - private static void insertGenres(Context context, int hostId) throws ApiException, IOException { + private static void insertGenres(Context context, SyncMusic syncMusic) throws ApiException, IOException { AudioLibrary.GetGenres getGenres = new AudioLibrary.GetGenres(); ArrayList genreList = (ArrayList) getGenres.resultFromJson(Utils.readFile(context, "AudioLibrary.GetGenres.json")); - ContentValues genresValuesBatch[] = new ContentValues[genreList.size()]; - for (int i = 0; i < genreList.size(); i++) { - LibraryType.DetailsGenre genre = genreList.get(i); - genresValuesBatch[i] = SyncUtils.contentValuesFromAudioGenre(hostId, genre); - } - - context.getContentResolver().bulkInsert(MediaContract.AudioGenres.CONTENT_URI, genresValuesBatch); + syncMusic.insertGenresItems(genreList, context.getContentResolver()); } - private static void insertAlbums(Context context, int hostId) throws ApiException, IOException { + private static void insertAlbums(Context context, SyncMusic syncMusic) throws ApiException, IOException { AudioLibrary.GetAlbums getAlbums = new AudioLibrary.GetAlbums(); String result = Utils.readFile(context, "AudioLibrary.GetAlbums.json"); ArrayList albumList = (ArrayList) getAlbums.resultFromJson(result).items; - ContentResolver contentResolver = context.getContentResolver(); - - ContentValues albumValuesBatch[] = new ContentValues[albumList.size()]; - int artistsCount = 0, genresCount = 0; - for (int i = 0; i < albumList.size(); i++) { - AudioType.DetailsAlbum album = albumList.get(i); - albumValuesBatch[i] = SyncUtils.contentValuesFromAlbum(hostId, album); - - artistsCount += album.artistid.size(); - genresCount += album.genreid.size(); - } - contentResolver.bulkInsert(MediaContract.Albums.CONTENT_URI, albumValuesBatch); - - // Iterate on each album, collect the artists and the genres and insert them - ContentValues albumArtistsValuesBatch[] = new ContentValues[artistsCount]; - ContentValues albumGenresValuesBatch[] = new ContentValues[genresCount]; - int artistCount = 0, genreCount = 0; - for (AudioType.DetailsAlbum album : albumList) { - for (int artistId : album.artistid) { - albumArtistsValuesBatch[artistCount] = new ContentValues(); - albumArtistsValuesBatch[artistCount].put(MediaContract.AlbumArtists.HOST_ID, hostId); - albumArtistsValuesBatch[artistCount].put(MediaContract.AlbumArtists.ALBUMID, album.albumid); - albumArtistsValuesBatch[artistCount].put(MediaContract.AlbumArtists.ARTISTID, artistId); - artistCount++; - } - - for (int genreId : album.genreid) { - albumGenresValuesBatch[genreCount] = new ContentValues(); - albumGenresValuesBatch[genreCount].put(MediaContract.AlbumGenres.HOST_ID, hostId); - albumGenresValuesBatch[genreCount].put(MediaContract.AlbumGenres.ALBUMID, album.albumid); - albumGenresValuesBatch[genreCount].put(MediaContract.AlbumGenres.GENREID, genreId); - genreCount++; - } - } - - contentResolver.bulkInsert(MediaContract.AlbumArtists.CONTENT_URI, albumArtistsValuesBatch); - contentResolver.bulkInsert(MediaContract.AlbumGenres.CONTENT_URI, albumGenresValuesBatch); + syncMusic.insertAlbumsItems(albumList, context.getContentResolver()); } - private static void insertSongs(Context context, int hostId) throws ApiException, IOException { + private static void insertSongs(Context context, SyncMusic syncMusic) throws ApiException, IOException { AudioLibrary.GetSongs getSongs = new AudioLibrary.GetSongs(); - ArrayList songList = (ArrayList) getSongs.resultFromJson(Utils.readFile(context, "AudioLibrary.GetSongs.json")).items; + ArrayList songList = (ArrayList) + getSongs.resultFromJson(Utils.readFile(context, "AudioLibrary.GetSongs.json")).items; - ContentValues songValuesBatch[] = new ContentValues[songList.size()]; - for (int i = 0; i < songList.size(); i++) { - AudioType.DetailsSong song = songList.get(i); - songValuesBatch[i] = SyncUtils.contentValuesFromSong(hostId, song); - } - context.getContentResolver().bulkInsert(MediaContract.Songs.CONTENT_URI, songValuesBatch); + syncMusic.insertSongsItems(songList, context.getContentResolver()); } } diff --git a/app/src/androidTest/java/org/xbmc/kore/testhelpers/TestUtils.java b/app/src/androidTest/java/org/xbmc/kore/testhelpers/TestUtils.java new file mode 100644 index 0000000..1cf9d31 --- /dev/null +++ b/app/src/androidTest/java/org/xbmc/kore/testhelpers/TestUtils.java @@ -0,0 +1,77 @@ +/* + * Copyright 2016 Martijn Brekhof. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.xbmc.kore.testhelpers; + +import android.database.Cursor; + +import org.xbmc.kore.ui.SongsListFragment; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class TestUtils { + /** + * Tests if cursor contains all numbers from ids given column index. + * @param cursor + * @param columnIndex + * @param numbers + */ + public static void testCursorContainsNumbers(Cursor cursor, int columnIndex, int... numbers) { + HashMap idsFound = new HashMap<>(); + for(int number : numbers) { + idsFound.put(number, false); + } + + assertTrue(cursor.moveToFirst()); + do { + idsFound.put(cursor.getInt(columnIndex), true); + } while(cursor.moveToNext()); + + for(Map.Entry entry : idsFound.entrySet() ) { + int key = entry.getKey(); + assertTrue("Id " + key + " not found", entry.getValue()); + } + } + + /** + * Tests if cursor contains all numbers from start until end for given column index. + * @param columnIndex + * @param cursor + * @param start + * @param end + */ + public static void testCursorContainsRange(Cursor cursor, int columnIndex, int start, int end) { + HashMap idsFound = new HashMap<>(); + for(int i = start; i <= end; i++) { + idsFound.put(i, false); + } + + assertTrue(cursor.moveToFirst()); + do { + idsFound.put(cursor.getInt(columnIndex), true); + } while(cursor.moveToNext()); + + for(Map.Entry entry : idsFound.entrySet() ) { + int key = entry.getKey(); + assertTrue("Id " + key + " not found", entry.getValue()); + } + } +} diff --git a/app/src/androidTest/java/org/xbmc/kore/testhelpers/Utils.java b/app/src/androidTest/java/org/xbmc/kore/testhelpers/Utils.java index 0121b73..aa8b259 100644 --- a/app/src/androidTest/java/org/xbmc/kore/testhelpers/Utils.java +++ b/app/src/androidTest/java/org/xbmc/kore/testhelpers/Utils.java @@ -18,6 +18,7 @@ package org.xbmc.kore.testhelpers; import android.content.Context; import android.content.pm.PackageManager; +import android.database.Cursor; import android.os.IBinder; import android.support.test.rule.ActivityTestRule; import android.support.v4.widget.DrawerLayout; @@ -92,6 +93,15 @@ public class Utils { isInitialized = false; } + public static String cursorToString(Cursor cursor) { + StringBuffer stringBuffer = new StringBuffer(); + for (String name : cursor.getColumnNames()) { + int index = cursor.getColumnIndex(name); + stringBuffer.append(name + "=" + cursor.getString(index) + "\n"); + } + return stringBuffer.toString(); + } + private static void disableAnimations() { int permStatus = context.checkCallingOrSelfPermission(ANIMATION_PERMISSION); if (permStatus == PackageManager.PERMISSION_GRANTED) { @@ -127,4 +137,16 @@ public class Utils { Log.e("SystemAnimations", "Could not change animation scale to " + animationScale + " :'("); } } + + public static boolean moveCursorTo(Cursor cursor, int index, int item) { + if (( cursor == null ) || ( ! cursor.moveToFirst() )) + return false; + + do { + if ( cursor.getInt(index) == item ) + return true; + } while (cursor.moveToNext()); + + return false; + } } diff --git a/app/src/androidTest/java/org/xbmc/kore/tests/mediaprovider/MediaProviderMusicTest.java b/app/src/androidTest/java/org/xbmc/kore/tests/mediaprovider/MediaProviderMusicTest.java new file mode 100644 index 0000000..868213f --- /dev/null +++ b/app/src/androidTest/java/org/xbmc/kore/tests/mediaprovider/MediaProviderMusicTest.java @@ -0,0 +1,335 @@ +/* + * Copyright 2016 Martijn Brekhof. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.xbmc.kore.tests.mediaprovider; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.support.test.rule.ActivityTestRule; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.xbmc.kore.host.HostInfo; +import org.xbmc.kore.provider.MediaContract; +import org.xbmc.kore.testhelpers.Database; +import org.xbmc.kore.testhelpers.TestUtils; +import org.xbmc.kore.testhelpers.Utils; +import org.xbmc.kore.ui.MoviesActivity; +import org.xbmc.kore.utils.LogUtils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +@RunWith(AndroidJUnit4.class) +public class MediaProviderMusicTest { + private static HostInfo hostInfo; + private static Context context; + private ContentResolver contentResolver; + + /** + * Note that the activity MoviesActivity is only needed for context and is not tested + */ + @Rule + public ActivityTestRule mActivityRule = new ActivityTestRule<>( + MoviesActivity.class); + + @Before + public void setUp() throws Exception { + context = mActivityRule.getActivity(); + + if (hostInfo == null) // We only need to fill the database the first time + hostInfo = Database.fill(context); + + contentResolver = mActivityRule.getActivity().getContentResolver(); + } + + @After + public void tearDown() throws Exception { + + } + + @AfterClass + public static void cleanup() { + Database.flush(context, hostInfo); + } + + @Test + public void queryAllArtistsTest() { + Uri uri = MediaContract.Artists.buildArtistsListUri(hostInfo.getId()); + + Cursor cursor = contentResolver.query(uri, TestValues.Artist.PROJECTION, null, null, null); + + assertNotNull(cursor); + assertEquals("cursor size ", 227, cursor.getCount()); + TestUtils.testCursorContainsRange(cursor, cursor.getColumnIndex(MediaContract.ArtistsColumns.ARTISTID), + 1, 94); + //Artist id 95 should be missing + TestUtils.testCursorContainsRange(cursor, cursor.getColumnIndex(MediaContract.ArtistsColumns.ARTISTID), + 96, 228); + } + + @Test + public void queryArtistTest() { + Uri uri = MediaContract.Artists.buildArtistUri(hostInfo.getId(), TestValues.Artist.artistId); + + Cursor cursor = contentResolver.query(uri, TestValues.Artist.PROJECTION, null, null, null); + + assertNotNull(cursor); + assertEquals("cursor size ", 1, cursor.getCount()); + assertTrue(cursor.moveToFirst()); + TestValues.Artist.test(cursor); + } + + @Test + public void queryAllAlbumsTest() { + Uri uri = MediaContract.Albums.buildAlbumsListUri(hostInfo.getId()); + + Cursor cursor = contentResolver.query(uri, TestValues.Album.PROJECTION, null, null, null); + + assertNotNull(cursor); + assertEquals("cursor size ", 232, cursor.getCount()); + int columnIndex = cursor.getColumnIndex(MediaContract.AlbumsColumns.ALBUMID); + TestUtils.testCursorContainsRange(cursor, columnIndex, 1, 75); + TestUtils.testCursorContainsRange(cursor, columnIndex, 77, 82); + TestUtils.testCursorContainsRange(cursor, columnIndex, 84, 234); + } + + @Test + public void queryAlbumTest() { + Uri uri = MediaContract.Albums.buildAlbumUri(hostInfo.getId(), TestValues.Album.albumId); + + Cursor cursor = contentResolver.query(uri, TestValues.Album.PROJECTION, null, null, null); + + assertNotNull(cursor); + assertEquals("cursor size ", 1, cursor.getCount()); + assertTrue(cursor.moveToFirst()); + TestValues.Album.test(cursor); + } + + @Test + public void queryAlbumsForArtistTest() { + Uri uri = MediaContract.AlbumArtists.buildAlbumsForArtistListUri(hostInfo.getId(), + TestValues.Artist.artistId); + + Cursor cursor = contentResolver.query(uri, TestValues.Album.PROJECTION, null, null, null); + + assertNotNull(cursor); + assertEquals("cursor size ", 1, cursor.getCount()); + assertTrue(cursor.moveToFirst()); + TestValues.Album.test(cursor); + } + + @Test + public void queryAlbumsForGenreTest() { + int genreId = 13; + Uri uri = MediaContract.AlbumGenres.buildAlbumsForGenreListUri(hostInfo.getId(), genreId); + + Cursor cursor = contentResolver.query(uri, TestValues.Album.PROJECTION, null, null, null); + + assertNotNull(cursor); + assertEquals("cursor size ", 31, cursor.getCount()); + TestUtils.testCursorContainsNumbers(cursor, cursor.getColumnIndex(MediaContract.Albums.ALBUMID), + 28, 43, 47, 66, 100); + TestUtils.testCursorContainsRange(cursor, cursor.getColumnIndex(MediaContract.Albums.ALBUMID), + 50, 55); + TestUtils.testCursorContainsRange(cursor, cursor.getColumnIndex(MediaContract.Albums.ALBUMID), + 201, 220); + } + + @Test + public void queryAlbumSongsTest() { + Uri uri = MediaContract.Songs.buildAlbumSongsListUri(hostInfo.getId(), TestValues.Album.albumId); + + Cursor cursor = contentResolver.query(uri, new String[] {MediaContract.Songs.SONGID}, null, null, null); + + assertNotNull(cursor); + assertEquals("cursor size ", 17, cursor.getCount()); + TestUtils.testCursorContainsRange(cursor, cursor.getColumnIndex(MediaContract.SongsColumns.SONGID), + 96, 112); + } + + @Test + public void queryAlbumWithoutArtist() { + Uri uri = MediaContract.Albums.buildAlbumUri(hostInfo.getId(), + TestValues.AlbumWithoutArtist.albumId); + + Cursor cursor = contentResolver.query(uri, TestValues.AlbumWithoutArtist.PROJECTION, null, null, null); + + assertNotNull(cursor); + assertEquals("cursor size ", 1, cursor.getCount()); + assertTrue(cursor.moveToFirst()); + TestValues.AlbumWithoutArtist.test(cursor); + } + + @Test + public void queryAlbumWithMultipleArtists() { + Uri uri = MediaContract.Albums.buildAlbumUri(hostInfo.getId(), + TestValues.AlbumWithMultipleArtists.albumId); + + Cursor cursor = contentResolver.query(uri, TestValues.AlbumWithMultipleArtists.PROJECTION, + null, null, null); + + assertNotNull(cursor); + assertEquals("cursor size ", 1, cursor.getCount()); + assertTrue(cursor.moveToFirst()); + TestValues.AlbumWithMultipleArtists.test(cursor); + } + + @Test + public void queryArtistSongsTest() { + Uri uri = MediaContract.Songs.buildArtistSongsListUri(hostInfo.getId(), TestValues.ArtistSong.artistId); + + Cursor cursor = contentResolver.query(uri, TestValues.ArtistSong.PROJECTION, null, null, null); + + assertNotNull(cursor); + assertEquals("cursor size ", 17, cursor.getCount()); + TestUtils.testCursorContainsRange(cursor, cursor.getColumnIndex(MediaContract.SongsColumns.SONGID), + 96, 112); + assertTrue(Utils.moveCursorTo(cursor, cursor.getColumnIndex(MediaContract.Songs.SONGID), + TestValues.ArtistSong.songId)); + } + + @Test + public void querySongWithArtistWithoutAlbumTest() { + Uri uri = MediaContract.Songs.buildArtistSongsListUri(hostInfo.getId(), + TestValues.SongWithArtistWithoutAlbum.artistId); + + Cursor cursor = contentResolver.query(uri, TestValues.SongWithArtistWithoutAlbum.PROJECTION, + null, null, null); + + assertNotNull(cursor); + assertEquals("cursor size ", 1, cursor.getCount()); + assertTrue(cursor.moveToFirst()); + Utils.cursorToString(cursor); + TestValues.SongWithArtistWithoutAlbum.test(cursor); + } + + @Test + public void queryFirstArtistSongWithMultipleArtistsTest() { + Uri uri = MediaContract.Songs.buildArtistSongsListUri(hostInfo.getId(), + TestValues.SongWithMultipleArtists.firstArtistId); + + Cursor cursor = contentResolver.query(uri, TestValues.SongWithMultipleArtists.PROJECTION, + null, null, null); + + assertNotNull(cursor); + assertEquals("cursor size ", 1, cursor.getCount()); + assertTrue(cursor.moveToFirst()); + TestValues.SongWithMultipleArtists.test(cursor); + } + + @Test + public void querySecondArtistSongWithMultipleArtistsTest() { + Uri uri = MediaContract.Songs.buildArtistSongsListUri(hostInfo.getId(), + TestValues.SongWithMultipleArtists.secondArtistId); + + Cursor cursor = contentResolver.query(uri, TestValues.SongWithMultipleArtists.PROJECTION, + null, null, null); + + assertNotNull(cursor); + assertEquals("cursor size ", 1, cursor.getCount()); + assertTrue(cursor.moveToFirst()); + TestValues.SongWithMultipleArtists.test(cursor); + } + + @Test + public void queryThirdArtistSongWithMultipleArtistsTest() { + Uri uri = MediaContract.Songs.buildArtistSongsListUri(hostInfo.getId(), + TestValues.SongWithMultipleArtists.thirdArtistId); + + Cursor cursor = contentResolver.query(uri, + TestValues.SongWithMultipleArtists.PROJECTION, + null, null, null); + + assertNotNull(cursor); + assertEquals("cursor size ", 1, cursor.getCount()); + assertTrue(cursor.moveToFirst()); + TestValues.SongWithMultipleArtists.test(cursor); + } + + @Test + public void queryAllSongsTest() { + Uri uri = MediaContract.Songs.buildSongsListUri(hostInfo.getId()); + + Cursor cursor = contentResolver.query(uri, + TestValues.ArtistSong.PROJECTION, + null, null, null); + + assertNotNull(cursor); + assertEquals("cursor size ", 1804, cursor.getCount()); + TestUtils.testCursorContainsRange(cursor, cursor.getColumnIndex(MediaContract.Songs.SONGID), + 1, 1804); + + //Test if list also contains a song WITH an album AND an artist + assertTrue(Utils.moveCursorTo(cursor, cursor.getColumnIndex(MediaContract.Songs.SONGID), + TestValues.SongWithAlbumAndArtist.songId)); + TestValues.SongWithAlbumAndArtist.test(cursor); + + //Test if list also contains a song WITHOUT an album but WITH an artist + assertTrue(Utils.moveCursorTo(cursor, cursor.getColumnIndex(MediaContract.Songs.SONGID), + TestValues.SongWithArtistWithoutAlbum.songId)); + TestValues.SongWithArtistWithoutAlbum.test(cursor); + + //Test if list also contains a song WITH an album but WITHOUT an artist + assertTrue(Utils.moveCursorTo(cursor, cursor.getColumnIndex(MediaContract.Songs.SONGID), + TestValues.SongWithAlbumWithoutArtist.songId)); + TestValues.SongWithAlbumWithoutArtist.test(cursor); + + //Test if list contains a song WITH MULTIPLE artists + assertTrue(Utils.moveCursorTo(cursor, cursor.getColumnIndex(MediaContract.Songs.SONGID), + TestValues.SongWithMultipleArtists.songId)); + TestValues.SongWithMultipleArtists.test(cursor); + } + + @Test + public void queryAlbumWithMultipleArtistsTest() { + Uri uri = MediaContract.Albums.buildAlbumUri(hostInfo.getId(), + TestValues.AlbumWithMultipleArtists.albumId); + + Cursor cursor = contentResolver.query(uri, + TestValues.AlbumWithMultipleArtists.PROJECTION, + null, null, null); + + assertNotNull(cursor); + assertEquals("cursor size ", 1, cursor.getCount()); + assertTrue(cursor.moveToFirst()); + LogUtils.LOGD("MediaProviderMusicTest", Utils.cursorToString(cursor)); + TestValues.AlbumWithMultipleArtists.test(cursor); + } + + @Test + public void queryAllGenresTest() { + Uri uri = MediaContract.AudioGenres.buildAudioGenresListUri(hostInfo.getId()); + + Cursor cursor = contentResolver.query(uri, + new String[] {MediaContract.AudioGenresColumns.GENREID}, + null, null, null); + + assertNotNull(cursor); + assertEquals("cursor size ", 39, cursor.getCount()); + TestUtils.testCursorContainsRange(cursor, + cursor.getColumnIndex(MediaContract.AudioGenresColumns.GENREID), + 1, 39); + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/org/xbmc/kore/tests/mediaprovider/TestValues.java b/app/src/androidTest/java/org/xbmc/kore/tests/mediaprovider/TestValues.java new file mode 100644 index 0000000..393b0f9 --- /dev/null +++ b/app/src/androidTest/java/org/xbmc/kore/tests/mediaprovider/TestValues.java @@ -0,0 +1,203 @@ +/* + * Copyright 2016 Martijn Brekhof. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.xbmc.kore.tests.mediaprovider; + +import android.database.Cursor; + +import org.xbmc.kore.provider.MediaContract; +import org.xbmc.kore.provider.MediaDatabase; +import org.xbmc.kore.provider.MediaProvider; +import org.xbmc.kore.ui.AlbumListFragment; +import org.xbmc.kore.ui.ArtistListFragment; +import org.xbmc.kore.ui.ArtistOverviewFragment; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; + +import static org.junit.Assert.assertEquals; + +public class TestValues { + public static class Artist { + public static int artistId = 13; + public static String artist = "Bernstein, Charles"; + + public static String[] PROJECTION = MediaContract.Artists.ALL_COLUMNS; + + public static void test(Cursor cursor) { + assertEquals(TestValues.Artist.artistId, cursor.getInt(cursor.getColumnIndex(MediaContract.ArtistsColumns.ARTISTID))); + assertEquals(TestValues.Artist.artist, cursor.getString(cursor.getColumnIndex(MediaContract.ArtistsColumns.ARTIST))); + } + } + + public static class Album { + public static int albumId = 13; + public static String title = "The Entity"; + public static String displayArtist = "Bernstein, Charles"; + public static int year = 1982; + public static String genre = "Soundtrack"; + + public static String[] PROJECTION = MediaContract.Albums.ALL_COLUMNS; + + public static void test(Cursor cursor) { + int resultAlbumId = cursor.getInt(cursor.getColumnIndex(MediaContract.AlbumsColumns.ALBUMID)); + assertEquals(albumId, resultAlbumId); + String resultTitle = cursor.getString(cursor.getColumnIndex(MediaContract.AlbumsColumns.TITLE)); + assertEquals(title, resultTitle); + String resultArtist = cursor.getString(cursor.getColumnIndex(MediaContract.AlbumsColumns.DISPLAYARTIST)); + assertEquals(displayArtist, resultArtist); + String resultGenre = cursor.getString(cursor.getColumnIndex(MediaContract.AlbumsColumns.GENRE)); + assertEquals(genre, resultGenre); + int resultYear = cursor.getInt(cursor.getColumnIndex(MediaContract.AlbumsColumns.YEAR)); + assertEquals(year, resultYear); + } + } + + public static class AlbumWithoutArtist { + public static int albumId = 82; + public static String title = "The Album"; + public static String displayArtist = ""; + public static int year = 0; + public static String genre = ""; + + public static String[] PROJECTION = MediaContract.Albums.ALL_COLUMNS; + + public static void test(Cursor cursor) { + int resultAlbumId = cursor.getInt(cursor.getColumnIndex(MediaContract.AlbumsColumns.ALBUMID)); + assertEquals(albumId, resultAlbumId); + String resultTitle = cursor.getString(cursor.getColumnIndex(MediaContract.AlbumsColumns.TITLE)); + assertEquals(title, resultTitle); + String resultArtist = cursor.getString(cursor.getColumnIndex(MediaContract.AlbumsColumns.DISPLAYARTIST)); + assertEquals(displayArtist, resultArtist); + String resultGenre = cursor.getString(cursor.getColumnIndex(MediaContract.AlbumsColumns.GENRE)); + assertEquals(genre, resultGenre); + int resultYear = cursor.getInt(cursor.getColumnIndex(MediaContract.AlbumsColumns.YEAR)); + assertEquals(year, resultYear); + } + } + + public static class AlbumWithMultipleArtists { + public static int albumId = 234; + public static String title = "ThreeArtistsAlbum"; + public static String displayArtist = "First artist / Second artist / Third artist"; + public static int year = 0; + public static String genre = ""; + + public static String[] PROJECTION = MediaContract.Albums.ALL_COLUMNS; + + public static void test(Cursor cursor) { + int resultAlbumId = cursor.getInt(cursor.getColumnIndex(MediaContract.AlbumsColumns.ALBUMID)); + assertEquals(albumId, resultAlbumId); + String resultTitle = cursor.getString(cursor.getColumnIndex(MediaContract.AlbumsColumns.TITLE)); + assertEquals(title, resultTitle); + String resultArtist = cursor.getString(cursor.getColumnIndex(MediaContract.AlbumsColumns.DISPLAYARTIST)); + assertEquals(displayArtist, resultArtist); + String resultGenre = cursor.getString(cursor.getColumnIndex(MediaContract.AlbumsColumns.GENRE)); + assertEquals(genre, resultGenre); + int resultYear = cursor.getInt(cursor.getColumnIndex(MediaContract.AlbumsColumns.YEAR)); + assertEquals(year, resultYear); + } + } + + public static class ArtistSong { + public static int songId = 96; + public static int artistId = Artist.artistId; + public static int albumId = Album.albumId; + public static String title = "Intro & Main Title"; + public static String[] PROJECTION = new String[] { MediaContract.Songs.SONGID, + MediaContract.Songs.TITLE, + MediaContract.Songs.ALBUMID, + MediaContract.SongArtists.ARTISTID, + MediaContract.Artists.ARTIST }; + + public static void test(Cursor cursor) { + assertEquals(songId, cursor.getInt(cursor.getColumnIndex(MediaContract.Songs.SONGID))); + assertEquals(title, cursor.getString(cursor.getColumnIndex(MediaContract.Songs.TITLE))); + assertEquals(albumId, cursor.getInt(cursor.getColumnIndex(MediaContract.Songs.ALBUMID))); + assertEquals(artistId, cursor.getInt(cursor.getColumnIndex(MediaContract.SongArtists.ARTISTID))); + } + } + + public static class SongWithAlbumAndArtist { + public static int songId = 1487; + public static int artistId = 195; + public static int albumId = 201; + public static String title = "The Lone Ranger (William Tell Overture)"; + + public static String[] PROJECTION = ArtistSong.PROJECTION; + + public static void test(Cursor cursor) { + assertEquals(songId, cursor.getInt(cursor.getColumnIndex(MediaContract.Songs.SONGID))); + assertEquals(title, cursor.getString(cursor.getColumnIndex(MediaContract.Songs.TITLE))); + assertEquals(albumId, cursor.getInt(cursor.getColumnIndex(MediaContract.Songs.ALBUMID))); + assertEquals(artistId, cursor.getInt(cursor.getColumnIndex(MediaContract.SongArtists.ARTISTID))); + } + } + + public static class SongWithAlbumWithoutArtist { + public static int songId = 1219; + public static int artistId = 0; + public static String title = "Unknown"; + public static int albumId = 82; + + public static String[] PROJECTION = ArtistSong.PROJECTION; + + public static void test(Cursor cursor) { + assertEquals(songId, cursor.getInt(cursor.getColumnIndex(MediaContract.Songs.SONGID))); + assertEquals(title, cursor.getString(cursor.getColumnIndex(MediaContract.Songs.TITLE))); + assertEquals(albumId, cursor.getInt(cursor.getColumnIndex(MediaContract.Songs.ALBUMID))); + assertEquals(artistId, cursor.getInt(cursor.getColumnIndex(MediaContract.SongArtists.ARTISTID))); + + } + } + + public static class SongWithArtistWithoutAlbum { + public static int songId = 1128; + public static int artistId = 73; + public static int albumId = 76; + public static String title = "Unknown"; + + public static String[] PROJECTION = ArtistSong.PROJECTION; + + public static void test(Cursor cursor) { + assertEquals(songId, cursor.getInt(cursor.getColumnIndex(MediaContract.Songs.SONGID))); + assertEquals(title, cursor.getString(cursor.getColumnIndex(MediaContract.Songs.TITLE))); + assertEquals(albumId, cursor.getInt(cursor.getColumnIndex(MediaContract.Songs.ALBUMID))); + assertEquals(artistId, cursor.getInt(cursor.getColumnIndex(MediaContract.SongArtists.ARTISTID))); + } + } + + public static class SongWithMultipleArtists { + public static int songId = 1804; + public static int firstArtistId = 226; + public static int secondArtistId = 227; + public static int thirdArtistId = 228; + public static int albumId = 234; + public static String title = "threeartists"; + + public static String[] PROJECTION = ArtistSong.PROJECTION; + + public static void test(Cursor cursor) { + assertEquals(songId, cursor.getInt(cursor.getColumnIndex(MediaContract.Songs.SONGID))); + assertEquals(title, cursor.getString(cursor.getColumnIndex(MediaContract.Songs.TITLE))); + assertEquals(albumId, cursor.getInt(cursor.getColumnIndex(MediaContract.Songs.ALBUMID))); + } + } +} diff --git a/app/src/androidTest/java/org/xbmc/kore/tests/ui/RestoreSearchQueryViewPagerTest.java b/app/src/androidTest/java/org/xbmc/kore/tests/ui/RestoreSearchQueryViewPagerTest.java index 4e6e485..b152c16 100644 --- a/app/src/androidTest/java/org/xbmc/kore/tests/ui/RestoreSearchQueryViewPagerTest.java +++ b/app/src/androidTest/java/org/xbmc/kore/tests/ui/RestoreSearchQueryViewPagerTest.java @@ -49,8 +49,8 @@ public class RestoreSearchQueryViewPagerTest { private final int ARTIST_SEARCH_QUERY_LIST_SIZE = 2; private final String ALBUMS_SEARCH_QUERY = "tes"; private final int ALBUM_SEARCH_QUERY_LIST_SIZE = 3; - private final int ARTIST_COMPLETE_LIST_SIZE = 224; - private final int ALBUM_COMPLETE_LIST_SIZE = 231; + private final int ARTIST_COMPLETE_LIST_SIZE = 227; + private final int ALBUM_COMPLETE_LIST_SIZE = 232; private LoaderIdlingResource loaderIdlingResource; diff --git a/app/src/instrumentationTest/assets/AudioLibrary.GetAlbums.json b/app/src/instrumentationTest/assets/AudioLibrary.GetAlbums.json index ed015da..12b0f1d 100644 --- a/app/src/instrumentationTest/assets/AudioLibrary.GetAlbums.json +++ b/app/src/instrumentationTest/assets/AudioLibrary.GetAlbums.json @@ -3,9 +3,9 @@ "jsonrpc" : "2.0", "result" : { "limits" : { - "total" : 231, + "total" : 232, "start" : 0, - "end" : 231 + "end" : 234 }, "albums" : [ { @@ -7538,6 +7538,41 @@ "theme" : [], "musicbrainzalbumid" : "", "playcount" : 0 + }, + { + "musicbrainzalbumid" : "", + "description" : "", + "artist" : [ + "First artist", + "Second artist", + "Third artist" + ], + "type" : "", + "title" : "ThreeArtistsAlbum", + "label" : "ThreeArtistsAlbum", + "rating" : 0, + "albumlabel" : "", + "playcount" : 0, + "albumid" : 234, + "thumbnail" : "", + "genreid" : [], + "artistid" : [ + 226, + 227, + 228 + ], + "mood" : [], + "theme" : [], + "genre" : [], + "displayartist" : "First artist / Second artist / Third artist", + "style" : [], + "fanart" : "", + "year" : 0, + "musicbrainzalbumartistid" : [ + "", + "", + "" + ] } ] } diff --git a/app/src/instrumentationTest/assets/AudioLibrary.GetArtists.json b/app/src/instrumentationTest/assets/AudioLibrary.GetArtists.json index b9ec582..85b43f6 100644 --- a/app/src/instrumentationTest/assets/AudioLibrary.GetArtists.json +++ b/app/src/instrumentationTest/assets/AudioLibrary.GetArtists.json @@ -4482,11 +4482,71 @@ "fanart" : "", "mood" : [], "style" : [] + }, + { + "description" : "", + "disbanded" : "", + "artist" : "First artist", + "instrument" : [], + "label" : "First artist", + "born" : "", + "yearsactive" : [], + "genre" : [], + "thumbnail" : "", + "mood" : [], + "artistid" : 226, + "musicbrainzartistid" : [ + "" + ], + "died" : "", + "formed" : "", + "style" : [], + "fanart" : "" + }, + { + "description" : "", + "disbanded" : "", + "artist" : "Second artist", + "instrument" : [], + "label" : "Second artist", + "born" : "", + "yearsactive" : [], + "genre" : [], + "thumbnail" : "", + "mood" : [], + "artistid" : 227, + "musicbrainzartistid" : [ + "" + ], + "died" : "", + "formed" : "", + "style" : [], + "fanart" : "" + }, + { + "description" : "", + "disbanded" : "", + "artist" : "Third artist", + "instrument" : [], + "label" : "Third artist", + "born" : "", + "yearsactive" : [], + "genre" : [], + "thumbnail" : "", + "mood" : [], + "artistid" : 228, + "musicbrainzartistid" : [ + "" + ], + "died" : "", + "formed" : "", + "style" : [], + "fanart" : "" } ], "limits" : { - "total" : 224, - "end" : 224, + "total" : 227, + "end" : 228, "start" : 0 } } diff --git a/app/src/instrumentationTest/assets/AudioLibrary.GetSongs.json b/app/src/instrumentationTest/assets/AudioLibrary.GetSongs.json index 91f0443..c00163b 100644 --- a/app/src/instrumentationTest/assets/AudioLibrary.GetSongs.json +++ b/app/src/instrumentationTest/assets/AudioLibrary.GetSongs.json @@ -75109,12 +75109,58 @@ "songid" : 1618, "title" : "Reel - Upstairs in a Tent", "album" : "Ireland's Best Fiddle Tunes Disk 2" + }, + { + "musicbrainzalbumartistid" : [], + "year" : 0, + "albumartistid" : [ + 226, + 227, + 228 + ], + "disc" : 0, + "musicbrainztrackid" : "", + "fanart" : "", + "file" : "/Users/martijn/Projects/dummymediafiles/media/music/ThreeArtists/ThreeArtistsAlbum/01-threeartists.mp3", + "genre" : [], + "displayartist" : "First artist / Second artist / Third artist", + "comment" : "", + "musicbrainzartistid" : [], + "artistid" : [ + 226, + 227, + 228 + ], + "lastplayed" : "", + "songid" : 1804, + "playcount" : 0, + "thumbnail" : "", + "albumartist" : [ + "First artist", + "Second artist", + "Third artist" + ], + "albumid" : 234, + "genreid" : [], + "track" : 1, + "label" : "threeartists", + "title" : "threeartists", + "rating" : 0, + "artist" : [ + "First artist", + "Second artist", + "Third artist" + ], + "lyrics" : "", + "duration" : 5, + "album" : "ThreeArtistsAlbum", + "musicbrainzalbumid" : "" } ], "limits" : { - "end" : 1803, + "end" : 1804, "start" : 0, - "total" : 1803 + "total" : 1804 } }, "jsonrpc" : "2.0" 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 062ebe0..385ba21 100644 --- a/app/src/main/java/org/xbmc/kore/provider/MediaProvider.java +++ b/app/src/main/java/org/xbmc/kore/provider/MediaProvider.java @@ -805,7 +805,7 @@ public class MediaProvider extends ContentProvider { * {@link MediaContract} fields that are fully qualified with a specific * parent {@link MediaDatabase.Tables}. Used when needed to work around SQL ambiguity. */ - private interface Qualified { + public interface Qualified { String ALBUM_ARTISTS_HOST_ID = MediaDatabase.Tables.ALBUM_ARTISTS + "." + MediaContract.AlbumArtists.HOST_ID; String ALBUM_ARTISTS_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 764e8e0..258920b 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 @@ -86,6 +86,7 @@ public class SyncMusic extends SyncItem { AudioType.FieldsArtists.FANART, AudioType.FieldsArtists.THUMBNAIL }; + /** * Gets all artists recursively and forwards the call to Genres * Genres->Albums->Songs @@ -115,13 +116,7 @@ public class SyncMusic extends SyncItem { // First delete all music info if (startIdx == 0) deleteMusicInfo(contentResolver, hostId); - // Insert artists - ContentValues artistValuesBatch[] = new ContentValues[items.size()]; - for (int i = 0; i < items.size(); i++) { - AudioType.DetailsArtist artist = items.get(i); - artistValuesBatch[i] = SyncUtils.contentValuesFromArtist(hostId, artist); - } - contentResolver.bulkInsert(MediaContract.Artists.CONTENT_URI, artistValuesBatch); + insertArtists(items, contentResolver); if (moreItemsAvailable(limitsReturned)) { LogUtils.LOGD(TAG, "chainCallSyncArtists: More results on media center, recursing."); @@ -151,6 +146,8 @@ public class SyncMusic extends SyncItem { where, new String[]{String.valueOf(hostId)}); contentResolver.delete(MediaContract.AlbumGenres.CONTENT_URI, where, new String[]{String.valueOf(hostId)}); + contentResolver.delete(MediaContract.SongArtists.CONTENT_URI, + where, new String[]{String.valueOf(hostId)}); contentResolver.delete(MediaContract.Songs.CONTENT_URI, where, new String[]{String.valueOf(hostId)}); contentResolver.delete(MediaContract.AudioGenres.CONTENT_URI, @@ -178,16 +175,9 @@ public class SyncMusic extends SyncItem { action.execute(hostConnection, new ApiCallback>() { @Override public void onSuccess(List result) { - if (result == null) result = new ArrayList<>(0); // Safeguard - ContentValues genresValuesBatch[] = new ContentValues[result.size()]; + if (result != null) + insertGenresItems(result, contentResolver); - for (int i = 0; i < result.size(); i++) { - LibraryType.DetailsGenre genre = result.get(i); - genresValuesBatch[i] = SyncUtils.contentValuesFromAudioGenre(hostId, genre); - } - - // Insert the genres and proceed to albums - contentResolver.bulkInsert(MediaContract.AudioGenres.CONTENT_URI, genresValuesBatch); chainCallSyncAlbums(orchestrator, hostConnection, callbackHandler, contentResolver, 0); } @@ -240,44 +230,7 @@ public class SyncMusic extends SyncItem { } // Insert the partial results - ContentValues albumValuesBatch[] = new ContentValues[items.size()]; - int artistsCount = 0, genresCount = 0; - for (int i = 0; i < items.size(); i++) { - AudioType.DetailsAlbum album = items.get(i); - albumValuesBatch[i] = SyncUtils.contentValuesFromAlbum(hostId, album); - - artistsCount += album.artistid.size(); - genresCount += album.genreid.size(); - } - contentResolver.bulkInsert(MediaContract.Albums.CONTENT_URI, albumValuesBatch); - - LogUtils.LOGD(TAG, "Finished inserting albums in: " + - (System.currentTimeMillis() - albumSyncStartTime)); - - // Iterate on each album, collect the artists and the genres and insert them - ContentValues albumArtistsValuesBatch[] = new ContentValues[artistsCount]; - ContentValues albumGenresValuesBatch[] = new ContentValues[genresCount]; - int artistCount = 0, genreCount = 0; - for (AudioType.DetailsAlbum album : items) { - for (int artistId : album.artistid) { - albumArtistsValuesBatch[artistCount] = new ContentValues(); - albumArtistsValuesBatch[artistCount].put(MediaContract.AlbumArtists.HOST_ID, hostId); - albumArtistsValuesBatch[artistCount].put(MediaContract.AlbumArtists.ALBUMID, album.albumid); - albumArtistsValuesBatch[artistCount].put(MediaContract.AlbumArtists.ARTISTID, artistId); - artistCount++; - } - - for (int genreId : album.genreid) { - albumGenresValuesBatch[genreCount] = new ContentValues(); - albumGenresValuesBatch[genreCount].put(MediaContract.AlbumGenres.HOST_ID, hostId); - albumGenresValuesBatch[genreCount].put(MediaContract.AlbumGenres.ALBUMID, album.albumid); - albumGenresValuesBatch[genreCount].put(MediaContract.AlbumGenres.GENREID, genreId); - genreCount++; - } - } - - contentResolver.bulkInsert(MediaContract.AlbumArtists.CONTENT_URI, albumArtistsValuesBatch); - contentResolver.bulkInsert(MediaContract.AlbumGenres.CONTENT_URI, albumGenresValuesBatch); + insertAlbumsItems(items, contentResolver); LogUtils.LOGD(TAG, "Finished inserting artists and genres in: " + (System.currentTimeMillis() - albumSyncStartTime)); @@ -346,29 +299,8 @@ 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); + insertSongsItems(items, contentResolver); if (moreItemsAvailable(limitsReturned)) { LogUtils.LOGD(TAG, "chainCallSyncSongs: More results on media center, recursing."); @@ -384,12 +316,98 @@ public class SyncMusic extends SyncItem { @Override public void onError(int errorCode, String description) { - // Ok, something bad happend, just quit + // Ok, something bad happened, just quit orchestrator.syncItemFailed(errorCode, description); } }, callbackHandler); } + public void insertArtists(List items, ContentResolver contentResolver) { + ContentValues artistValuesBatch[] = new ContentValues[items.size()]; + for (int i = 0; i < items.size(); i++) { + AudioType.DetailsArtist artist = items.get(i); + artistValuesBatch[i] = SyncUtils.contentValuesFromArtist(hostId, artist); + } + contentResolver.bulkInsert(MediaContract.Artists.CONTENT_URI, artistValuesBatch); + } + + public void insertGenresItems(List items, ContentResolver contentResolver) { + ContentValues genresValuesBatch[] = new ContentValues[items.size()]; + + for (int i = 0; i < items.size(); i++) { + LibraryType.DetailsGenre genre = items.get(i); + genresValuesBatch[i] = SyncUtils.contentValuesFromAudioGenre(hostId, genre); + } + + // Insert the genres and proceed to albums + contentResolver.bulkInsert(MediaContract.AudioGenres.CONTENT_URI, genresValuesBatch); + } + + public void insertAlbumsItems(List items, ContentResolver contentResolver) { + ContentValues albumValuesBatch[] = new ContentValues[items.size()]; + int artistsCount = 0, genresCount = 0; + for (int i = 0; i < items.size(); i++) { + AudioType.DetailsAlbum album = items.get(i); + albumValuesBatch[i] = SyncUtils.contentValuesFromAlbum(hostId, album); + + artistsCount += album.artistid.size(); + genresCount += album.genreid.size(); + } + contentResolver.bulkInsert(MediaContract.Albums.CONTENT_URI, albumValuesBatch); + + // Iterate on each album, collect the artists and the genres and insert them + ContentValues albumArtistsValuesBatch[] = new ContentValues[artistsCount]; + ContentValues albumGenresValuesBatch[] = new ContentValues[genresCount]; + int artistCount = 0, genreCount = 0; + for (AudioType.DetailsAlbum album : items) { + for (int artistId : album.artistid) { + albumArtistsValuesBatch[artistCount] = new ContentValues(); + albumArtistsValuesBatch[artistCount].put(MediaContract.AlbumArtists.HOST_ID, hostId); + albumArtistsValuesBatch[artistCount].put(MediaContract.AlbumArtists.ALBUMID, album.albumid); + albumArtistsValuesBatch[artistCount].put(MediaContract.AlbumArtists.ARTISTID, artistId); + artistCount++; + } + + for (int genreId : album.genreid) { + albumGenresValuesBatch[genreCount] = new ContentValues(); + albumGenresValuesBatch[genreCount].put(MediaContract.AlbumGenres.HOST_ID, hostId); + albumGenresValuesBatch[genreCount].put(MediaContract.AlbumGenres.ALBUMID, album.albumid); + albumGenresValuesBatch[genreCount].put(MediaContract.AlbumGenres.GENREID, genreId); + genreCount++; + } + } + + contentResolver.bulkInsert(MediaContract.AlbumArtists.CONTENT_URI, albumArtistsValuesBatch); + contentResolver.bulkInsert(MediaContract.AlbumGenres.CONTENT_URI, albumGenresValuesBatch); + } + + public void insertSongsItems(List items, ContentResolver contentResolver) { + ContentValues songValuesBatch[] = new ContentValues[items.size()]; + int totalArtistsCount = 0; + 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); + } + private boolean moreItemsAvailable(ListType.LimitsReturned limitsReturned) { boolean moreItemsAvailable = false; if (limitsReturned != null) { diff --git a/app/src/main/java/org/xbmc/kore/ui/AlbumDetailsFragment.java b/app/src/main/java/org/xbmc/kore/ui/AlbumDetailsFragment.java index 167bfca..6304df8 100644 --- a/app/src/main/java/org/xbmc/kore/ui/AlbumDetailsFragment.java +++ b/app/src/main/java/org/xbmc/kore/ui/AlbumDetailsFragment.java @@ -710,7 +710,7 @@ public class AlbumDetailsFragment extends AbstractDetailsFragment /** * Album details query parameters. */ - private interface AlbumDetailsQuery { + public interface AlbumDetailsQuery { String[] PROJECTION = { BaseColumns._ID, MediaContract.Albums.TITLE, @@ -739,7 +739,7 @@ public class AlbumDetailsFragment extends AbstractDetailsFragment /** * Movie cast list query parameters. */ - private interface AlbumSongsListQuery { + public interface AlbumSongsListQuery { String[] PROJECTION = { BaseColumns._ID, MediaContract.Songs.TITLE, diff --git a/app/src/main/java/org/xbmc/kore/ui/AlbumListFragment.java b/app/src/main/java/org/xbmc/kore/ui/AlbumListFragment.java index 63dfb52..0d56d90 100644 --- a/app/src/main/java/org/xbmc/kore/ui/AlbumListFragment.java +++ b/app/src/main/java/org/xbmc/kore/ui/AlbumListFragment.java @@ -163,7 +163,7 @@ public class AlbumListFragment extends AbstractCursorListFragment { /** * Album list query parameters. */ - private interface AlbumListQuery { + public interface AlbumListQuery { String[] PROJECTION = { BaseColumns._ID, MediaContract.Albums.ALBUMID, diff --git a/app/src/main/java/org/xbmc/kore/ui/AudioGenresListFragment.java b/app/src/main/java/org/xbmc/kore/ui/AudioGenresListFragment.java index 10a096a..608c20a 100644 --- a/app/src/main/java/org/xbmc/kore/ui/AudioGenresListFragment.java +++ b/app/src/main/java/org/xbmc/kore/ui/AudioGenresListFragment.java @@ -108,7 +108,7 @@ public class AudioGenresListFragment extends AbstractCursorListFragment { /** * Audio genres list query parameters. */ - private interface AudioGenreListQuery { + public interface AudioGenreListQuery { String[] PROJECTION = { BaseColumns._ID, MediaContract.AudioGenres.GENREID, 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 965ad81..4e0cf09 100644 --- a/app/src/main/java/org/xbmc/kore/ui/SongsListFragment.java +++ b/app/src/main/java/org/xbmc/kore/ui/SongsListFragment.java @@ -134,7 +134,7 @@ public class SongsListFragment extends AbstractCursorListFragment { /** * Album songs list query parameters. */ - private interface SongsListQuery { + public 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/utils/Utils.java b/app/src/main/java/org/xbmc/kore/utils/Utils.java index 20cd148..5883ab5 100644 --- a/app/src/main/java/org/xbmc/kore/utils/Utils.java +++ b/app/src/main/java/org/xbmc/kore/utils/Utils.java @@ -76,6 +76,22 @@ public class Utils { return builder.toString(); } + /** + * Concats a list of integers... + * @param list + * @param delimiter + * @return + */ + public static String listIntegerConcat(List list, String delimiter) { + StringBuilder builder = new StringBuilder(); + boolean first = true; + for (Integer item : list) { + if (!first) builder.append(delimiter); + builder.append(item); + first = false; + } + return builder.toString(); + } /** * Calls {@link Context#startActivity(Intent)} with the given implicit {@link Intent} diff --git a/tools/json/README.md b/tools/json/README.md new file mode 100644 index 0000000..1ee1f64 --- /dev/null +++ b/tools/json/README.md @@ -0,0 +1,27 @@ +# JSON Tools + +Here you will find perl scripts that can be used to retrieve JSON responses from a Kodi instance. +The scripts are primarily used to create the JSON source files needed by the instrumentation tests. + + +## Getting json responses + +Currently there are two scripts available to get media details from a Kodi instance. + + * `getmovies.pl`: retrieves movie details + * `getmusic.pl`: retrieves music details + +By default the scripts connect to Kodi running on the same machine (`127.0.0.1`) on port `8080`. +If you need to contact a Kodi instance on a different port and/or IP-address change the +following line in each script: + +``` +my $url = "http://127.0.0.1:8080/jsonrpc"; +``` + +## Generating test values + +The instrumentation tests require some values such as "total number of songs", "list of artistids", "genre ids", etc. etc. +To generate these values from the JSON files, you can run +`gentestnumbers.pl`. This script expects the JSON files to be located in the same directory you execute the script. +It also assumes the names of the JSON files are the same as generated by `getmovies.pl` and `getmusic.pl`. \ No newline at end of file diff --git a/tools/json/gentestnumbers.pl b/tools/json/gentestnumbers.pl new file mode 100755 index 0000000..4b24113 --- /dev/null +++ b/tools/json/gentestnumbers.pl @@ -0,0 +1,283 @@ +#!/usr/bin/perl +# +# Copyright 2016 Martijn Brekhof. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +use strict; +use warnings; +use Cpanel::JSON::XS qw(encode_json decode_json); + +sub printRanges($\@) { + my $key = shift; + my $arg = shift; + my $count = 0; + + my @list = @{$arg}; + my $current; + for (my $i = 1; $i < @list; $i++) { + $current = $list[$i]->{$key}; + my $prev = $list[$i-1]->{$key}; + if ( $current - $prev == 1 ) { + $count++; + } else { + if ( $count == 0 ) { + print $prev; + } else { + print $prev - $count . "-" . $prev; + } + print " "; + $count = 0; + } + } + + if ( $count == 0 ) { + print $current; + } else { + print $current - $count . "-" . $current; + } +} + +sub decodeJson($) { + my $filename = shift; + local $/ = undef; + open (FH, $filename) or die "Error opening file $filename\n"; + my $json_hash = decode_json(); + close FH; + return $json_hash; +} + +sub printSong(\%) { + my $song = shift; + print "title: " . $song->{"title"} . "\n"; + + print "artistid: "; + for my $artistid ( @{$song->{"artistid"}} ) { + print $artistid . " "; + } + print "\n"; + + print "albumid: " . $song->{"albumid"} . "\n"; + print "songid: " . $song->{"songid"} . "\n"; +} + + +sub printAlbum(\%) { + my $album = shift; + + print "title: " . $album->{"title"} . "\n"; + print "albumid: " . $album->{"albumid"} . "\n"; + print "displayartist: " . $album->{"displayartist"} . "\n"; + print "year: " . $album->{"year"} . "\n"; + print "genre: " . @{$album->{"genre"}} . "\n"; +} + + +sub getArtists($) { + my $json_hash = shift; + return $json_hash->{"result"}->{"artists"}; +} + +sub getArtist($$) { + my $json_hash = shift; + my $artistid = shift; + + my $artists = getArtists($json_hash); + for my $artist (@{$artists}) { + if ( $artistid == $artist->{"artistid"} ) { + return $artist; + } + } + return undef; +} + +sub getAlbums($) { + my $json_hash = shift; + return $json_hash->{"result"}->{"albums"}; +} + +sub getAlbum($$) { + my $json_hash = shift; + my $albumid = shift; + + my $albums = getAlbums($json_hash); + for my $album (@{$albums}) { + if ( $albumid == $album->{"albumid"}) { + return $album; + } + } +} + +sub getAlbumsForGenre($$) { + my $json_hash = shift; + my $genreid = shift; + + my @result; + + my $albums = getAlbums($json_hash); + for my $album (@{$albums}) { + for my $albumGenreId (@{$album->{"genreid"}}) { + if ( $albumGenreId == $genreid ) { + push @result, $album; + } + } + } + + return @result; +} + +sub getSongs(%) { + my $json_hash = shift; + return $json_hash->{"result"}->{"songs"}; +} + +sub getSong(\%$) { + my $json_hash = shift; + my $songid = shift; + + my $songs = getSongs($json_hash); + for my $song (@{$songs}) { + if ( $songid == $song->{"songid"}) { + return $song; + } + } +} + +sub printArtistTestNumbers($) { + my $artistid = shift; + + my $json_hash = decodeJson( "AudioLibrary.GetArtists.json" ); + my $result = getArtists($json_hash); + print "Amount of artists: ", scalar @{$result}, "\n\n"; + + print "Artist ids: "; + my @artists = sort {$a->{"artistid"} <=> $b->{"artistid"}} @{$result}; + printRanges("artistid", @artists); + print "\n\n"; + + print "Artist with artistId $artistid\n"; + my $artist = getArtist($json_hash, $artistid); + print "artist: " . $artist->{"artist"} . "\n"; + print "artistid: " . $artist->{"artistid"} . "\n"; + print "\n\n"; +} + +sub printAlbumTestNumbers($$) { + my $albumid = shift; + my $genreid = shift; + my $json_hash = decodeJson( "AudioLibrary.GetAlbums.json" ); + my $result = getAlbums($json_hash); + print "Amount of albums: ", scalar @{$result}, "\n\n"; + + print "Album ids: "; + my @albums = sort {$a->{"albumid"} <=> $b->{"albumid"}} @{$result}; + printRanges("albumid", @albums); + print "\n\n"; + + print "Albums for genre id $genreid: "; + my @result = getAlbumsForGenre( $json_hash, $genreid ); + @albums = sort {$a->{"albumid"} <=> $b->{"albumid"}} @result; + printRanges("albumid", @albums); + print "\n\n"; + + print "Album with albumId $albumid\n"; + my $album = getAlbum($json_hash, $albumid); + printAlbum(%$album); + print "\n\n"; +} + +sub printSongTestNumbers($$) { + my $artistid = shift; + my $albumid = shift; + + my $json_hash = decodeJson( "AudioLibrary.GetSongs.json" ); + my $result = getSongs($json_hash); + print "Amount of songs: ", scalar @{$result}, "\n\n"; + + my @songsforartist; + my @songsforalbum; + + print "Song ids: "; + + my @songids = sort {$a->{"songid"} <=> $b->{"songid"}} @{$result}; + printRanges("songid", @songids); + + for my $song (@songids) { + for my $id (@{$song->{"artistid"}}) { + if ( $id == $artistid ) { + push @songsforartist, $song; + } + } + if ( $song->{"albumid"} == $albumid ) { + push @songsforalbum, $song; + } + } + print "\n\n"; + + print "Songs for artistid " . $artistid . ": total=" . scalar @songsforartist . ": ids="; + printRanges("songid", @songsforartist); + print "\n\n"; + + print "Songs for albumid " . $albumid . ": total=" . scalar @songsforalbum . ": ids="; + printRanges("songid", @songsforalbum); + print "\n\n"; +} + +sub printSongCornerCases() { + + my $json_hash = decodeJson( "AudioLibrary.GetSongs.json" ); + + print "Song with album and artist\n"; + my $song = getSong(%$json_hash, 1487); + printSong(%$song); + print "\n\n"; + + print "Songs with album but without artist\n"; + $song = getSong(%$json_hash, 1219); + printSong(%$song); + print "\n\n"; + + print "Song without album but with artist\n"; + $song = getSong(%$json_hash, 1128); + printSong(%$song); + + print "\n\n"; + + print "Song with multiple artists\n"; + $song = getSong(%$json_hash, 1804); + printSong(%$song); +} + +sub printAlbumCornerCases() { + my $json_hash = decodeJson( "AudioLibrary.GetAlbums.json" ); + + print "Album without an artist\n"; + my $album = getAlbum($json_hash, 82); + printAlbum(%$album); + print "\n\n"; + + print "Album with multiple artists\n"; + $album = getAlbum($json_hash, 234); + printAlbum(%$album); + print "\n\n"; +} + +printArtistTestNumbers(13); + +printAlbumTestNumbers(13, 13); +printAlbumCornerCases(); + +printSongTestNumbers(13, 13); + +printSongCornerCases(); \ No newline at end of file