Store loaded indexes in persistent storage.

Signed-off-by: Yahor Berdnikau <egorr.berd@gmail.com>
This commit is contained in:
Yahor Berdnikau 2018-03-11 21:58:59 +01:00
parent 63715aab18
commit 423461c3ba
7 changed files with 215 additions and 24 deletions

View File

@ -0,0 +1,60 @@
@file:JvmMultifileClass
@file:JvmName("DomainSerializers")
package org.moire.ultrasonic.cache.serializers
import com.twitter.serial.serializer.CollectionSerializers
import com.twitter.serial.serializer.ObjectSerializer
import com.twitter.serial.serializer.SerializationContext
import com.twitter.serial.stream.SerializerDefs
import com.twitter.serial.stream.SerializerInput
import com.twitter.serial.stream.SerializerOutput
import org.moire.ultrasonic.domain.Artist
private const val SERIALIZER_VERSION = 1
/**
* Serializer/deserializer for [Artist] domain entity.
*/
val artistSerializer get() = object : ObjectSerializer<Artist>(SERIALIZER_VERSION) {
override fun serializeObject(
context: SerializationContext,
output: SerializerOutput<out SerializerOutput<*>>,
item: Artist
) {
output.writeString(item.id)
.writeString(item.name)
.writeString(item.index)
.writeString(item.coverArt)
.apply {
val albumCount = item.albumCount
if (albumCount != null) writeLong(albumCount) else writeNull()
}
.writeInt(item.closeness)
}
override fun deserializeObject(
context: SerializationContext,
input: SerializerInput,
versionNumber: Int
): Artist? {
if (versionNumber != SERIALIZER_VERSION) return null
val id = input.readString()
val name = input.readString()
val index = input.readString()
val coverArt = input.readString()
val albumCount = if (input.peekType() == SerializerDefs.TYPE_NULL) {
input.readNull()
null
} else {
input.readLong()
}
val closeness = input.readInt()
return Artist(id, name, index, coverArt, albumCount, closeness)
}
}
/**
* Serializer/deserializer for list of [Artist] domain entities.
*/
val artistListSerializer = CollectionSerializers.getListSerializer(artistSerializer)

View File

@ -0,0 +1,44 @@
@file:JvmMultifileClass
@file:JvmName("DomainSerializers")
package org.moire.ultrasonic.cache.serializers
import com.twitter.serial.serializer.CollectionSerializers
import com.twitter.serial.serializer.ObjectSerializer
import com.twitter.serial.serializer.SerializationContext
import com.twitter.serial.stream.SerializerInput
import com.twitter.serial.stream.SerializerOutput
import org.moire.ultrasonic.domain.Artist
import org.moire.ultrasonic.domain.Indexes
private const val SERIALIZATION_VERSION = 1
val indexesSerializer get() = object : ObjectSerializer<Indexes>(SERIALIZATION_VERSION) {
override fun serializeObject(
context: SerializationContext,
output: SerializerOutput<out SerializerOutput<*>>,
item: Indexes
) {
output.writeLong(item.lastModified)
.writeString(item.ignoredArticles)
.writeObject<MutableList<Artist>>(context, item.shortcuts,
CollectionSerializers.getListSerializer(artistSerializer))
.writeObject<MutableList<Artist>>(context, item.artists,
CollectionSerializers.getListSerializer(artistSerializer))
}
override fun deserializeObject(
context: SerializationContext,
input: SerializerInput,
versionNumber: Int
): Indexes? {
if (versionNumber != SERIALIZATION_VERSION) return null
val lastModified = input.readLong()
val ignoredArticles = input.readString() ?: return null
val shortcutsList = input.readObject(context,
CollectionSerializers.getListSerializer(artistSerializer)) ?: return null
val artistsList = input.readObject(context,
CollectionSerializers.getListSerializer(artistSerializer)) ?: return null
return Indexes(lastModified, ignoredArticles, shortcutsList, artistsList)
}
}

View File

@ -1,6 +1,7 @@
package org.moire.ultrasonic.cache
import com.nhaarman.mockito_kotlin.mock
import com.twitter.serial.util.SerializationUtils
import org.amshove.kluent.`it returns`
import org.junit.Before
import org.junit.Rule
@ -31,4 +32,9 @@ abstract class BaseStorageTest {
}
protected val storageDir get() = File(mockDirectories.getInternalDataDir(), STORAGE_DIR_NAME)
protected fun validateSerializedData(index: Int = 0) {
val serializedFileBytes = storageDir.listFiles()[index].readBytes()
SerializationUtils.validateSerializedData(serializedFileBytes)
}
}

View File

@ -0,0 +1,57 @@
package org.moire.ultrasonic.cache.serializers
import org.amshove.kluent.`should equal`
import org.junit.Test
import org.moire.ultrasonic.cache.BaseStorageTest
import org.moire.ultrasonic.domain.Artist
/**
* [Artist] serializers test.
*/
class ArtistSerializerTest : BaseStorageTest() {
@Test
fun `Should correctly serialize Artist object`() {
val item = Artist("id", "name", "index", "coverArt", 1, 0)
storage.store("some-name", item, artistSerializer)
validateSerializedData()
}
@Test
fun `Should correctly deserialize Artist object`() {
val itemName = "some-name"
val item = Artist("id", "name", "index", "coverArt", null, 0)
storage.store(itemName, item, artistSerializer)
val loadedItem = storage.load(itemName, artistSerializer)
loadedItem `should equal` item
}
@Test
fun `Should correctly serialize list of Artists`() {
val itemsList = listOf(
Artist(id = "1"),
Artist(id = "2", name = "some")
)
storage.store("some-name", itemsList, artistListSerializer)
validateSerializedData()
}
@Test
fun `Should correctly deserialize list of Artists`() {
val name = "some-name"
val itemsList = listOf(
Artist(id = "1"),
Artist(id = "2", name = "some")
)
storage.store(name, itemsList, artistListSerializer)
val loadedItems = storage.load(name, artistListSerializer)
loadedItems `should equal` itemsList
}
}

View File

@ -0,0 +1,40 @@
package org.moire.ultrasonic.cache.serializers
import org.amshove.kluent.`should equal`
import org.junit.Test
import org.moire.ultrasonic.cache.BaseStorageTest
import org.moire.ultrasonic.domain.Artist
import org.moire.ultrasonic.domain.Indexes
/**
* Test [Indexes] domain entity serializer.
*/
class IndexesSerializerTest : BaseStorageTest() {
@Test
fun `Should correctly serialize Indexes object`() {
val item = Indexes(220L, "", mutableListOf(
Artist("12")
), mutableListOf(
Artist("233", "some")
))
storage.store("some-name", item, indexesSerializer)
validateSerializedData()
}
@Test
fun `Should correctly deserialize Indexes object`() {
val name = "some-name"
val item = Indexes(220L, "", mutableListOf(
Artist("12")
), mutableListOf(
Artist("233", "some")
))
storage.store(name, item, indexesSerializer)
val loadedItem = storage.load(name, indexesSerializer)
loadedItem `should equal` item
}
}

View File

@ -1,6 +1,5 @@
package org.moire.ultrasonic.cache.serializers
import com.twitter.serial.util.SerializationUtils
import org.amshove.kluent.`should equal`
import org.junit.Test
import org.moire.ultrasonic.cache.BaseStorageTest
@ -16,8 +15,7 @@ class MusicFolderSerializerTest : BaseStorageTest() {
storage.store("some-name", item, musicFolderSerializer)
val serializedFileBytes = storageDir.listFiles()[0].readBytes()
SerializationUtils.validateSerializedData(serializedFileBytes)
validateSerializedData()
}
@Test
@ -40,8 +38,7 @@ class MusicFolderSerializerTest : BaseStorageTest() {
storage.store("some-name", itemsList, musicFolderListSerializer)
val serializedFileBytes = storageDir.listFiles()[0].readBytes()
SerializationUtils.validateSerializedData(serializedFileBytes)
validateSerializedData()
}
@Test

View File

@ -120,6 +120,7 @@ public class RESTMusicService implements MusicService {
private static final String TAG = RESTMusicService.class.getSimpleName();
private static final String MUSIC_FOLDER_STORAGE_NAME = "music_folder";
private static final String INDEXES_STORAGE_NAME = "indexes";
private final SubsonicAPIClient subsonicAPIClient;
private final PermanentFileStorage fileStorage;
@ -155,8 +156,8 @@ public class RESTMusicService implements MusicService {
public List<MusicFolder> getMusicFolders(boolean refresh,
Context context,
ProgressListener progressListener) throws Exception {
List<MusicFolder> cachedMusicFolders = fileStorage
.load(MUSIC_FOLDER_STORAGE_NAME, DomainSerializers.getMusicFolderListSerializer());
List<MusicFolder> cachedMusicFolders = fileStorage.load(MUSIC_FOLDER_STORAGE_NAME,
DomainSerializers.getMusicFolderListSerializer());
if (cachedMusicFolders != null && !refresh) {
return cachedMusicFolders;
}
@ -177,7 +178,8 @@ public class RESTMusicService implements MusicService {
boolean refresh,
Context context,
ProgressListener progressListener) throws Exception {
Indexes cachedIndexes = readCachedIndexes(context, musicFolderId);
Indexes cachedIndexes = fileStorage.load(INDEXES_STORAGE_NAME,
DomainSerializers.getIndexesSerializer());
if (cachedIndexes != null && !refresh) {
return cachedIndexes;
}
@ -188,25 +190,10 @@ public class RESTMusicService implements MusicService {
checkResponseSuccessful(response);
Indexes indexes = APIIndexesConverter.toDomainEntity(response.body().getIndexes());
writeCachedIndexes(context, indexes, musicFolderId);
fileStorage.store(INDEXES_STORAGE_NAME, indexes, DomainSerializers.getIndexesSerializer());
return indexes;
}
private static Indexes readCachedIndexes(Context context, String musicFolderId) {
String filename = getCachedIndexesFilename(context, musicFolderId);
return FileUtil.deserialize(context, filename);
}
private static void writeCachedIndexes(Context context, Indexes indexes, String musicFolderId) {
String filename = getCachedIndexesFilename(context, musicFolderId);
FileUtil.serialize(context, indexes, filename);
}
private static String getCachedIndexesFilename(Context context, String musicFolderId) {
String s = Util.getRestUrl(context, null) + musicFolderId;
return String.format(Locale.US, "indexes-%d.ser", Math.abs(s.hashCode()));
}
@Override
public Indexes getArtists(boolean refresh,
Context context,