From 4952e1d2400dfa695e4850769db8e0ae4b280473 Mon Sep 17 00:00:00 2001 From: Yahor Berdnikau Date: Mon, 26 Feb 2018 22:03:33 +0100 Subject: [PATCH 01/13] Add new cache module. This module will be responsible for temporary and permanent caching of any data. Signed-off-by: Yahor Berdnikau --- cache/build.gradle | 40 ++++++++++++++++++++++++++++++++++++++++ settings.gradle | 2 +- ultrasonic/build.gradle | 1 + 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 cache/build.gradle diff --git a/cache/build.gradle b/cache/build.gradle new file mode 100644 index 00000000..336add8d --- /dev/null +++ b/cache/build.gradle @@ -0,0 +1,40 @@ +apply plugin: 'java-library' +apply plugin: 'kotlin' +apply plugin: 'jacoco' +apply from: '../gradle_scripts/code_quality.gradle' + +sourceSets { + main.java.srcDirs += "${projectDir}/src/main/kotlin" +} +dependencies { + api other.kotlinStdlib +} + +jacoco { + toolVersion(versions.jacoco) +} + +ext { + jacocoExclude = [] +} + +jacocoTestReport { + reports { + html.enabled true + csv.enabled false + xml.enabled true + } + + afterEvaluate { + classDirectories = files(classDirectories.files.collect { + fileTree(dir: it, excludes: jacocoExclude) + }) + } +} + +test.finalizedBy jacocoTestReport +test { + jacoco { + excludes += jacocoExclude + } +} diff --git a/settings.gradle b/settings.gradle index 7601205f..8fa424c2 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,4 +1,4 @@ -include ':library', ':subsonic-api' +include ':library', ':subsonic-api', ':cache' include ':menudrawer' include ':pulltorefresh' include ':ultrasonic' diff --git a/ultrasonic/build.gradle b/ultrasonic/build.gradle index e4050434..fbe94eca 100644 --- a/ultrasonic/build.gradle +++ b/ultrasonic/build.gradle @@ -48,6 +48,7 @@ dependencies { implementation project(':pulltorefresh') implementation project(':library') implementation project(':subsonic-api') + implementation project(':cache') implementation androidSupport.support implementation androidSupport.design From ad52e3ad95c3a9a2eb5179c10e74ab8a8656fb35 Mon Sep 17 00:00:00 2001 From: Yahor Berdnikau Date: Sat, 10 Mar 2018 18:24:37 +0100 Subject: [PATCH 02/13] Move domain entities to own module. This allow other modules to depend on them. Signed-off-by: Yahor Berdnikau --- domain/build.gradle | 38 +++++++++++++++++++ .../org/moire/ultrasonic/domain/Artist.kt | 0 .../org/moire/ultrasonic/domain/Bookmark.kt | 0 .../moire/ultrasonic/domain/ChatMessage.kt | 0 .../org/moire/ultrasonic/domain/Genre.kt | 0 .../org/moire/ultrasonic/domain/Indexes.kt | 0 .../moire/ultrasonic/domain/JukeboxStatus.kt | 0 .../org/moire/ultrasonic/domain/Lyrics.kt | 0 .../moire/ultrasonic/domain/MusicDirectory.kt | 0 .../moire/ultrasonic/domain/MusicFolder.kt | 0 .../moire/ultrasonic/domain/PlayerState.kt | 0 .../org/moire/ultrasonic/domain/Playlist.kt | 0 .../ultrasonic/domain/PodcastsChannel.kt | 0 .../org/moire/ultrasonic/domain/RepeatMode.kt | 0 .../moire/ultrasonic/domain/SearchCriteria.kt | 0 .../moire/ultrasonic/domain/SearchResult.kt | 6 ++- .../org/moire/ultrasonic/domain/Share.kt | 0 .../org/moire/ultrasonic/domain/UserInfo.kt | 0 .../org/moire/ultrasonic/domain/Version.kt | 0 settings.gradle | 5 ++- ultrasonic/build.gradle | 3 +- 21 files changed, 47 insertions(+), 5 deletions(-) create mode 100644 domain/build.gradle rename {ultrasonic => domain}/src/main/kotlin/org/moire/ultrasonic/domain/Artist.kt (100%) rename {ultrasonic => domain}/src/main/kotlin/org/moire/ultrasonic/domain/Bookmark.kt (100%) rename {ultrasonic => domain}/src/main/kotlin/org/moire/ultrasonic/domain/ChatMessage.kt (100%) rename {ultrasonic => domain}/src/main/kotlin/org/moire/ultrasonic/domain/Genre.kt (100%) rename {ultrasonic => domain}/src/main/kotlin/org/moire/ultrasonic/domain/Indexes.kt (100%) rename {ultrasonic => domain}/src/main/kotlin/org/moire/ultrasonic/domain/JukeboxStatus.kt (100%) rename {ultrasonic => domain}/src/main/kotlin/org/moire/ultrasonic/domain/Lyrics.kt (100%) rename {ultrasonic => domain}/src/main/kotlin/org/moire/ultrasonic/domain/MusicDirectory.kt (100%) rename {ultrasonic => domain}/src/main/kotlin/org/moire/ultrasonic/domain/MusicFolder.kt (100%) rename {ultrasonic => domain}/src/main/kotlin/org/moire/ultrasonic/domain/PlayerState.kt (100%) rename {ultrasonic => domain}/src/main/kotlin/org/moire/ultrasonic/domain/Playlist.kt (100%) rename {ultrasonic => domain}/src/main/kotlin/org/moire/ultrasonic/domain/PodcastsChannel.kt (100%) rename {ultrasonic => domain}/src/main/kotlin/org/moire/ultrasonic/domain/RepeatMode.kt (100%) rename {ultrasonic => domain}/src/main/kotlin/org/moire/ultrasonic/domain/SearchCriteria.kt (100%) rename {ultrasonic => domain}/src/main/kotlin/org/moire/ultrasonic/domain/SearchResult.kt (59%) rename {ultrasonic => domain}/src/main/kotlin/org/moire/ultrasonic/domain/Share.kt (100%) rename {ultrasonic => domain}/src/main/kotlin/org/moire/ultrasonic/domain/UserInfo.kt (100%) rename {ultrasonic => domain}/src/main/kotlin/org/moire/ultrasonic/domain/Version.kt (100%) diff --git a/domain/build.gradle b/domain/build.gradle new file mode 100644 index 00000000..cab611de --- /dev/null +++ b/domain/build.gradle @@ -0,0 +1,38 @@ +apply plugin: 'java-library' +apply plugin: 'kotlin' +apply plugin: 'jacoco' +apply from: '../gradle_scripts/code_quality.gradle' + +dependencies { + api other.kotlinStdlib + api other.semver +} + +jacoco { + toolVersion(versions.jacoco) +} + +ext { + jacocoExclude = [] +} + +jacocoTestReport { + reports { + html.enabled true + csv.enabled false + xml.enabled true + } + + afterEvaluate { + classDirectories = files(classDirectories.files.collect { + fileTree(dir: it, excludes: jacocoExclude) + }) + } +} + +test.finalizedBy jacocoTestReport +test { + jacoco { + excludes += jacocoExclude + } +} diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/Artist.kt b/domain/src/main/kotlin/org/moire/ultrasonic/domain/Artist.kt similarity index 100% rename from ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/Artist.kt rename to domain/src/main/kotlin/org/moire/ultrasonic/domain/Artist.kt diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/Bookmark.kt b/domain/src/main/kotlin/org/moire/ultrasonic/domain/Bookmark.kt similarity index 100% rename from ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/Bookmark.kt rename to domain/src/main/kotlin/org/moire/ultrasonic/domain/Bookmark.kt diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/ChatMessage.kt b/domain/src/main/kotlin/org/moire/ultrasonic/domain/ChatMessage.kt similarity index 100% rename from ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/ChatMessage.kt rename to domain/src/main/kotlin/org/moire/ultrasonic/domain/ChatMessage.kt diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/Genre.kt b/domain/src/main/kotlin/org/moire/ultrasonic/domain/Genre.kt similarity index 100% rename from ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/Genre.kt rename to domain/src/main/kotlin/org/moire/ultrasonic/domain/Genre.kt diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/Indexes.kt b/domain/src/main/kotlin/org/moire/ultrasonic/domain/Indexes.kt similarity index 100% rename from ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/Indexes.kt rename to domain/src/main/kotlin/org/moire/ultrasonic/domain/Indexes.kt diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/JukeboxStatus.kt b/domain/src/main/kotlin/org/moire/ultrasonic/domain/JukeboxStatus.kt similarity index 100% rename from ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/JukeboxStatus.kt rename to domain/src/main/kotlin/org/moire/ultrasonic/domain/JukeboxStatus.kt diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/Lyrics.kt b/domain/src/main/kotlin/org/moire/ultrasonic/domain/Lyrics.kt similarity index 100% rename from ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/Lyrics.kt rename to domain/src/main/kotlin/org/moire/ultrasonic/domain/Lyrics.kt diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/MusicDirectory.kt b/domain/src/main/kotlin/org/moire/ultrasonic/domain/MusicDirectory.kt similarity index 100% rename from ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/MusicDirectory.kt rename to domain/src/main/kotlin/org/moire/ultrasonic/domain/MusicDirectory.kt diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/MusicFolder.kt b/domain/src/main/kotlin/org/moire/ultrasonic/domain/MusicFolder.kt similarity index 100% rename from ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/MusicFolder.kt rename to domain/src/main/kotlin/org/moire/ultrasonic/domain/MusicFolder.kt diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/PlayerState.kt b/domain/src/main/kotlin/org/moire/ultrasonic/domain/PlayerState.kt similarity index 100% rename from ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/PlayerState.kt rename to domain/src/main/kotlin/org/moire/ultrasonic/domain/PlayerState.kt diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/Playlist.kt b/domain/src/main/kotlin/org/moire/ultrasonic/domain/Playlist.kt similarity index 100% rename from ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/Playlist.kt rename to domain/src/main/kotlin/org/moire/ultrasonic/domain/Playlist.kt diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/PodcastsChannel.kt b/domain/src/main/kotlin/org/moire/ultrasonic/domain/PodcastsChannel.kt similarity index 100% rename from ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/PodcastsChannel.kt rename to domain/src/main/kotlin/org/moire/ultrasonic/domain/PodcastsChannel.kt diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/RepeatMode.kt b/domain/src/main/kotlin/org/moire/ultrasonic/domain/RepeatMode.kt similarity index 100% rename from ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/RepeatMode.kt rename to domain/src/main/kotlin/org/moire/ultrasonic/domain/RepeatMode.kt diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/SearchCriteria.kt b/domain/src/main/kotlin/org/moire/ultrasonic/domain/SearchCriteria.kt similarity index 100% rename from ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/SearchCriteria.kt rename to domain/src/main/kotlin/org/moire/ultrasonic/domain/SearchCriteria.kt diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/SearchResult.kt b/domain/src/main/kotlin/org/moire/ultrasonic/domain/SearchResult.kt similarity index 59% rename from ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/SearchResult.kt rename to domain/src/main/kotlin/org/moire/ultrasonic/domain/SearchResult.kt index 60f9b1cf..738ccfc6 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/SearchResult.kt +++ b/domain/src/main/kotlin/org/moire/ultrasonic/domain/SearchResult.kt @@ -1,10 +1,12 @@ package org.moire.ultrasonic.domain +import org.moire.ultrasonic.domain.MusicDirectory.Entry + /** * The result of a search. Contains matching artists, albums and songs. */ data class SearchResult( val artists: List, - val albums: List, - val songs: List + val albums: List, + val songs: List ) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/Share.kt b/domain/src/main/kotlin/org/moire/ultrasonic/domain/Share.kt similarity index 100% rename from ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/Share.kt rename to domain/src/main/kotlin/org/moire/ultrasonic/domain/Share.kt diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/UserInfo.kt b/domain/src/main/kotlin/org/moire/ultrasonic/domain/UserInfo.kt similarity index 100% rename from ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/UserInfo.kt rename to domain/src/main/kotlin/org/moire/ultrasonic/domain/UserInfo.kt diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/Version.kt b/domain/src/main/kotlin/org/moire/ultrasonic/domain/Version.kt similarity index 100% rename from ultrasonic/src/main/kotlin/org/moire/ultrasonic/domain/Version.kt rename to domain/src/main/kotlin/org/moire/ultrasonic/domain/Version.kt diff --git a/settings.gradle b/settings.gradle index 8fa424c2..256032c5 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,4 +1,7 @@ -include ':library', ':subsonic-api', ':cache' +include ':library' +include ':domain' +include ':subsonic-api' +include ':cache' include ':menudrawer' include ':pulltorefresh' include ':ultrasonic' diff --git a/ultrasonic/build.gradle b/ultrasonic/build.gradle index fbe94eca..1a0b385c 100644 --- a/ultrasonic/build.gradle +++ b/ultrasonic/build.gradle @@ -47,6 +47,7 @@ dependencies { implementation project(':menudrawer') implementation project(':pulltorefresh') implementation project(':library') + implementation project(':domain') implementation project(':subsonic-api') implementation project(':cache') @@ -55,8 +56,6 @@ dependencies { implementation other.kotlinStdlib - implementation other.semver - testImplementation other.kotlinReflect testImplementation testing.junit testImplementation testing.kotlinJunit From c49e44724018e395c235c3f54269fd86e7bb1302 Mon Sep 17 00:00:00 2001 From: Yahor Berdnikau Date: Sat, 10 Mar 2018 22:20:34 +0100 Subject: [PATCH 03/13] Add permanent file storage. It serialize domain objects to byte array and store it to file. For now it only uses for MusicFolder entity store. Signed-off-by: Yahor Berdnikau --- cache/build.gradle | 12 +++- .../org/moire/ultrasonic/cache/Directories.kt | 14 ++++ .../ultrasonic/cache/PermanentFileStorage.kt | 68 +++++++++++++++++++ .../serializers/MusicFolderSerializer.kt | 43 ++++++++++++ .../moire/ultrasonic/cache/BaseStorageTest.kt | 34 ++++++++++ .../cache/PermanentFileStorageTest.kt | 67 ++++++++++++++++++ .../serializers/MusicFolderSerializerTest.kt | 60 ++++++++++++++++ dependencies.gradle | 2 + .../moire/ultrasonic/domain/MusicFolder.kt | 4 +- .../service/MusicServiceFactory.java | 37 +++++++++- .../service/OfflineMusicService.java | 5 +- .../ultrasonic/service/RESTMusicService.java | 34 ++++------ 12 files changed, 353 insertions(+), 27 deletions(-) create mode 100644 cache/src/main/kotlin/org/moire/ultrasonic/cache/Directories.kt create mode 100644 cache/src/main/kotlin/org/moire/ultrasonic/cache/PermanentFileStorage.kt create mode 100644 cache/src/main/kotlin/org/moire/ultrasonic/cache/serializers/MusicFolderSerializer.kt create mode 100644 cache/src/test/kotlin/org/moire/ultrasonic/cache/BaseStorageTest.kt create mode 100644 cache/src/test/kotlin/org/moire/ultrasonic/cache/PermanentFileStorageTest.kt create mode 100644 cache/src/test/kotlin/org/moire/ultrasonic/cache/serializers/MusicFolderSerializerTest.kt diff --git a/cache/build.gradle b/cache/build.gradle index 336add8d..57cecf59 100644 --- a/cache/build.gradle +++ b/cache/build.gradle @@ -3,11 +3,17 @@ apply plugin: 'kotlin' apply plugin: 'jacoco' apply from: '../gradle_scripts/code_quality.gradle' -sourceSets { - main.java.srcDirs += "${projectDir}/src/main/kotlin" -} dependencies { + api project(':domain') api other.kotlinStdlib + api other.twitterSerial + + testImplementation testing.junit + testImplementation testing.kotlinJunit + testImplementation testing.mockito + testImplementation testing.mockitoInline + testImplementation testing.mockitoKotlin + testImplementation testing.kluent } jacoco { diff --git a/cache/src/main/kotlin/org/moire/ultrasonic/cache/Directories.kt b/cache/src/main/kotlin/org/moire/ultrasonic/cache/Directories.kt new file mode 100644 index 00000000..ad0aa6de --- /dev/null +++ b/cache/src/main/kotlin/org/moire/ultrasonic/cache/Directories.kt @@ -0,0 +1,14 @@ +package org.moire.ultrasonic.cache + +import java.io.File + +/** + * Provides access to generic directories: + * - for temporary caches + * - for permanent data storage + */ +interface Directories { + fun getInternalCacheDir(): File + fun getInternalDataDir(): File + fun getExternalCacheDir(): File? +} diff --git a/cache/src/main/kotlin/org/moire/ultrasonic/cache/PermanentFileStorage.kt b/cache/src/main/kotlin/org/moire/ultrasonic/cache/PermanentFileStorage.kt new file mode 100644 index 00000000..243077f1 --- /dev/null +++ b/cache/src/main/kotlin/org/moire/ultrasonic/cache/PermanentFileStorage.kt @@ -0,0 +1,68 @@ +package org.moire.ultrasonic.cache + +import com.twitter.serial.serializer.SerializationContext +import com.twitter.serial.serializer.Serializer +import com.twitter.serial.stream.Serial +import com.twitter.serial.stream.bytebuffer.ByteBufferSerial +import java.io.File + +internal const val STORAGE_DIR_NAME = "persistent_storage" + +/** + * Provides access to permanent file based storage. + * + * Look at [org.moire.ultrasonic.cache.serializers] package for available [Serializer]s. + */ +class PermanentFileStorage( + private val directories: Directories, + private val debug: Boolean = false +) { + private val serializationContext = object : SerializationContext { + override fun isDebug(): Boolean = debug + override fun isRelease(): Boolean = !debug + } + + private val serializer: Serial = ByteBufferSerial(serializationContext) + + /** + * Stores given [objectToStore] using [name] as a key and [objectSerializer] as serializer. + */ + fun store( + name: String, + objectToStore: T, + objectSerializer: Serializer + ) { + val storeFile = getFile(name) + if (!storeFile.exists()) storeFile.createNewFile() + storeFile.writeBytes(serializer.toByteArray(objectToStore, objectSerializer)) + } + + /** + * Loads object with [name] key using [objectDeserializer] deserializer. + */ + fun load( + name: String, + objectDeserializer: Serializer + ): T? { + val storeFile = getFile(name) + if (!storeFile.exists()) return null + + return serializer.fromByteArray(storeFile.readBytes(), objectDeserializer) + } + + /** + * Clear all files in storage. + */ + fun clearAll() { + val storageDir = getStorageDir() + if (storageDir.exists()) { + storageDir.listFiles().forEach { if (it.isFile) it.delete() } + } + } + + private fun getFile(name: String) = File(getStorageDir(), "$name.ser") + + private fun getStorageDir() = File(directories.getInternalDataDir(), STORAGE_DIR_NAME).apply { + if (!exists()) mkdirs() + } +} diff --git a/cache/src/main/kotlin/org/moire/ultrasonic/cache/serializers/MusicFolderSerializer.kt b/cache/src/main/kotlin/org/moire/ultrasonic/cache/serializers/MusicFolderSerializer.kt new file mode 100644 index 00000000..655c3fd4 --- /dev/null +++ b/cache/src/main/kotlin/org/moire/ultrasonic/cache/serializers/MusicFolderSerializer.kt @@ -0,0 +1,43 @@ +@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.MusicFolder + +private const val SERIALIZATION_VERSION = 1 + +/** + * Serializer/deserializer for [MusicFolder] domain entity. + */ +val musicFolderSerializer = object : ObjectSerializer(SERIALIZATION_VERSION) { + + override fun serializeObject( + context: SerializationContext, + output: SerializerOutput>, + item: MusicFolder + ) { + output.writeString(item.id).writeString(item.name) + } + + override fun deserializeObject( + context: SerializationContext, + input: SerializerInput, + versionNumber: Int + ): MusicFolder? { + if (versionNumber != SERIALIZATION_VERSION) return null + + val id = input.readString() ?: return null + val name = input.readString() ?: return null + return MusicFolder(id, name) + } +} + +/** + * Serializer/deserializer for [List] of [MusicFolder] items. + */ +val musicFolderListSerializer = CollectionSerializers.getListSerializer(musicFolderSerializer) diff --git a/cache/src/test/kotlin/org/moire/ultrasonic/cache/BaseStorageTest.kt b/cache/src/test/kotlin/org/moire/ultrasonic/cache/BaseStorageTest.kt new file mode 100644 index 00000000..5a6d127b --- /dev/null +++ b/cache/src/test/kotlin/org/moire/ultrasonic/cache/BaseStorageTest.kt @@ -0,0 +1,34 @@ +package org.moire.ultrasonic.cache + +import com.nhaarman.mockito_kotlin.mock +import org.amshove.kluent.`it returns` +import org.junit.Before +import org.junit.Rule +import org.junit.rules.TemporaryFolder +import java.io.File + +internal const val INTERNAL_DATA_FOLDER = "data" +internal const val INTERNAL_CACHE_FOLDER = "cache" +internal const val EXTERNAL_CACHE_FOLDER = "external_cache" + +/** + * Base test class that inits the storage + */ +abstract class BaseStorageTest { + @get:Rule val tempFileRule = TemporaryFolder() + + protected lateinit var mockDirectories: Directories + protected lateinit var storage: PermanentFileStorage + + @Before + fun setUp() { + mockDirectories = mock { + on { getInternalDataDir() } `it returns` tempFileRule.newFolder(INTERNAL_DATA_FOLDER) + on { getInternalCacheDir() } `it returns` tempFileRule.newFolder(INTERNAL_CACHE_FOLDER) + on { getExternalCacheDir() } `it returns` tempFileRule.newFolder(EXTERNAL_CACHE_FOLDER) + } + storage = PermanentFileStorage(mockDirectories, true) + } + + protected val storageDir get() = File(mockDirectories.getInternalDataDir(), STORAGE_DIR_NAME) +} diff --git a/cache/src/test/kotlin/org/moire/ultrasonic/cache/PermanentFileStorageTest.kt b/cache/src/test/kotlin/org/moire/ultrasonic/cache/PermanentFileStorageTest.kt new file mode 100644 index 00000000..856e6590 --- /dev/null +++ b/cache/src/test/kotlin/org/moire/ultrasonic/cache/PermanentFileStorageTest.kt @@ -0,0 +1,67 @@ +package org.moire.ultrasonic.cache + +import org.amshove.kluent.`should contain` +import org.amshove.kluent.`should equal to` +import org.amshove.kluent.`should equal` +import org.junit.Test +import org.moire.ultrasonic.cache.serializers.musicFolderSerializer +import org.moire.ultrasonic.domain.MusicFolder + +/** + * Integration test for [PermanentFileStorage]. + */ +class PermanentFileStorageTest : BaseStorageTest() { + @Test + fun `Should create storage dir if it is not exist`() { + val item = MusicFolder("1", "2") + storage.store("test", item, musicFolderSerializer) + + storageDir.exists() `should equal to` true + } + + @Test + fun `Should serialize to file`() { + val item = MusicFolder("1", "23") + val name = "some-name" + + storage.store(name, item, musicFolderSerializer) + + val storageFiles = storageDir.listFiles() + storageFiles.size `should equal to` 1 + storageFiles[0].name `should contain` name + } + + @Test + fun `Should deserialize stored object`() { + val item = MusicFolder("some", "nice") + val name = "some-name" + storage.store(name, item, musicFolderSerializer) + + val loadedItem = storage.load(name, musicFolderSerializer) + + loadedItem `should equal` item + } + + @Test + fun `Should overwrite existing stored object`() { + val name = "some-nice-name" + val item1 = MusicFolder("1", "1") + val item2 = MusicFolder("2", "2") + storage.store(name, item1, musicFolderSerializer) + storage.store(name, item2, musicFolderSerializer) + + val loadedItem = storage.load(name, musicFolderSerializer) + + loadedItem `should equal` item2 + } + + @Test + fun `Should clear all files when clearAll is called`() { + storage.store("name1", MusicFolder("1", "1"), musicFolderSerializer) + storage.store("name2", MusicFolder("2", "2"), musicFolderSerializer) + + storage.clearAll() + + storageDir.listFiles().size `should equal to` 0 + } +} diff --git a/cache/src/test/kotlin/org/moire/ultrasonic/cache/serializers/MusicFolderSerializerTest.kt b/cache/src/test/kotlin/org/moire/ultrasonic/cache/serializers/MusicFolderSerializerTest.kt new file mode 100644 index 00000000..0fedccf1 --- /dev/null +++ b/cache/src/test/kotlin/org/moire/ultrasonic/cache/serializers/MusicFolderSerializerTest.kt @@ -0,0 +1,60 @@ +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 +import org.moire.ultrasonic.domain.MusicFolder + +/** + * [MusicFolder] serializers test. + */ +class MusicFolderSerializerTest : BaseStorageTest() { + @Test + fun `Should correctly serialize MusicFolder object`() { + val item = MusicFolder("Music", "Folder") + + storage.store("some-name", item, musicFolderSerializer) + + val serializedFileBytes = storageDir.listFiles()[0].readBytes() + SerializationUtils.validateSerializedData(serializedFileBytes) + } + + @Test + fun `Should correctly deserialize MusicFolder object`() { + val name = "name" + val item = MusicFolder("some", "none") + storage.store(name, item, musicFolderSerializer) + + val loadedItem = storage.load(name, musicFolderSerializer) + + loadedItem `should equal` item + } + + @Test + fun `Should correctly serialize list of MusicFolders objects`() { + val itemsList = listOf( + MusicFolder("1", "1"), + MusicFolder("2", "2") + ) + + storage.store("some-name", itemsList, musicFolderListSerializer) + + val serializedFileBytes = storageDir.listFiles()[0].readBytes() + SerializationUtils.validateSerializedData(serializedFileBytes) + } + + @Test + fun `Should correctly deserialize list of MusicFolder objects`() { + val name = "some-name" + val itemsList = listOf( + MusicFolder("1", "1"), + MusicFolder("2", "2") + ) + storage.store(name, itemsList, musicFolderListSerializer) + + val loadedItem = storage.load(name, musicFolderListSerializer) + + loadedItem `should equal` itemsList + } +} diff --git a/dependencies.gradle b/dependencies.gradle index 69ade4df..e32b7801 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -22,6 +22,7 @@ ext.versions = [ jackson : "2.9.0", okhttp : "3.9.0", semver : "1.0.0", + twitterSerial : "0.1.5", junit : "4.12", mockito : "2.12.0", @@ -52,6 +53,7 @@ ext.other = [ jacksonKotlin : "com.fasterxml.jackson.module:jackson-module-kotlin:$versions.jackson", okhttpLogging : "com.squareup.okhttp3:logging-interceptor:$versions.okhttp", semver : "net.swiftzer.semver:semver:$versions.semver", + twitterSerial : "com.twitter.serial:serial:$versions.twitterSerial", ] ext.testing = [ diff --git a/domain/src/main/kotlin/org/moire/ultrasonic/domain/MusicFolder.kt b/domain/src/main/kotlin/org/moire/ultrasonic/domain/MusicFolder.kt index a7e91d27..85661482 100644 --- a/domain/src/main/kotlin/org/moire/ultrasonic/domain/MusicFolder.kt +++ b/domain/src/main/kotlin/org/moire/ultrasonic/domain/MusicFolder.kt @@ -6,4 +6,6 @@ package org.moire.ultrasonic.domain data class MusicFolder( val id: String, val name: String -) +) { + companion object +} diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicServiceFactory.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicServiceFactory.java index 4d2b3d4b..c9bbea1d 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicServiceFactory.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicServiceFactory.java @@ -22,12 +22,17 @@ import android.content.Context; import android.content.SharedPreferences; import android.util.Log; +import org.jetbrains.annotations.NotNull; import org.moire.ultrasonic.BuildConfig; import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient; import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions; +import org.moire.ultrasonic.cache.Directories; +import org.moire.ultrasonic.cache.PermanentFileStorage; import org.moire.ultrasonic.util.Constants; import org.moire.ultrasonic.util.Util; +import java.io.File; + /** * @author Sindre Mehus * @version $Id$ @@ -44,7 +49,9 @@ public class MusicServiceFactory { synchronized (MusicServiceFactory.class) { if (OFFLINE_MUSIC_SERVICE == null) { Log.d(LOG_TAG, "Creating new offline music service"); - OFFLINE_MUSIC_SERVICE = new OfflineMusicService(createSubsonicApiClient(context)); + OFFLINE_MUSIC_SERVICE = new OfflineMusicService( + createSubsonicApiClient(context), + getPermanentFileStorage(context)); } } } @@ -57,7 +64,8 @@ public class MusicServiceFactory { if (REST_MUSIC_SERVICE == null) { Log.d(LOG_TAG, "Creating new rest music service"); REST_MUSIC_SERVICE = new CachedMusicService(new RESTMusicService( - createSubsonicApiClient(context))); + createSubsonicApiClient(context), + getPermanentFileStorage(context))); } } } @@ -104,4 +112,29 @@ public class MusicServiceFactory { Constants.REST_CLIENT_ID, allowSelfSignedCertificate, enableLdapUserSupport, BuildConfig.DEBUG); } + + private static PermanentFileStorage getPermanentFileStorage(final Context context) { + return new PermanentFileStorage(getDirectories(context), BuildConfig.DEBUG); + } + + private static Directories getDirectories(final Context context) { + return new Directories() { + @NotNull + @Override + public File getInternalCacheDir() { + return context.getCacheDir(); + } + + @NotNull + @Override + public File getInternalDataDir() { + return context.getFilesDir(); + } + + @Override + public File getExternalCacheDir() { + return context.getExternalCacheDir(); + } + }; + } } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.java index b3dac806..8a93dec0 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.java @@ -24,6 +24,7 @@ import android.media.MediaMetadataRetriever; import android.util.Log; import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient; +import org.moire.ultrasonic.cache.PermanentFileStorage; import org.moire.ultrasonic.domain.Artist; import org.moire.ultrasonic.domain.Genre; import org.moire.ultrasonic.domain.Indexes; @@ -67,8 +68,8 @@ public class OfflineMusicService extends RESTMusicService private static final String TAG = OfflineMusicService.class.getSimpleName(); private static final Pattern COMPILE = Pattern.compile(" "); - public OfflineMusicService(SubsonicAPIClient subsonicAPIClient) { - super(subsonicAPIClient); + public OfflineMusicService(SubsonicAPIClient subsonicAPIClient, PermanentFileStorage storage) { + super(subsonicAPIClient, storage); } @Override diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/RESTMusicService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/RESTMusicService.java index a4129f52..61d50c89 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/RESTMusicService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/RESTMusicService.java @@ -61,6 +61,8 @@ import org.moire.ultrasonic.api.subsonic.response.SharesResponse; import org.moire.ultrasonic.api.subsonic.response.StreamResponse; import org.moire.ultrasonic.api.subsonic.response.SubsonicResponse; import org.moire.ultrasonic.api.subsonic.response.VideosResponse; +import org.moire.ultrasonic.cache.PermanentFileStorage; +import org.moire.ultrasonic.cache.serializers.DomainSerializers; import org.moire.ultrasonic.domain.APIAlbumConverter; import org.moire.ultrasonic.domain.APIArtistConverter; import org.moire.ultrasonic.domain.APIBookmarkConverter; @@ -117,10 +119,17 @@ import retrofit2.Response; public class RESTMusicService implements MusicService { private static final String TAG = RESTMusicService.class.getSimpleName(); - private final SubsonicAPIClient subsonicAPIClient; + private static final String MUSIC_FOLDER_STORAGE_NAME = "music_folder"; - public RESTMusicService(SubsonicAPIClient subsonicAPIClient) { + private final SubsonicAPIClient subsonicAPIClient; + private final PermanentFileStorage fileStorage; + + public RESTMusicService( + final SubsonicAPIClient subsonicAPIClient, + final PermanentFileStorage fileStorage + ) { this.subsonicAPIClient = subsonicAPIClient; + this.fileStorage = fileStorage; } @Override @@ -146,7 +155,8 @@ public class RESTMusicService implements MusicService { public List getMusicFolders(boolean refresh, Context context, ProgressListener progressListener) throws Exception { - List cachedMusicFolders = readCachedMusicFolders(context); + List cachedMusicFolders = fileStorage + .load(MUSIC_FOLDER_STORAGE_NAME, DomainSerializers.getMusicFolderListSerializer()); if (cachedMusicFolders != null && !refresh) { return cachedMusicFolders; } @@ -157,25 +167,11 @@ public class RESTMusicService implements MusicService { List musicFolders = APIMusicFolderConverter .toDomainEntityList(response.body().getMusicFolders()); - writeCachedMusicFolders(context, musicFolders); + fileStorage.store(MUSIC_FOLDER_STORAGE_NAME, musicFolders, + DomainSerializers.getMusicFolderListSerializer()); return musicFolders; } - private static List readCachedMusicFolders(Context context) { - String filename = getCachedMusicFoldersFilename(context); - return FileUtil.deserialize(context, filename); - } - - private static void writeCachedMusicFolders(Context context, List musicFolders) { - String filename = getCachedMusicFoldersFilename(context); - FileUtil.serialize(context, new ArrayList<>(musicFolders), filename); - } - - private static String getCachedMusicFoldersFilename(Context context) { - String s = Util.getRestUrl(context, null); - return String.format(Locale.US, "musicFolders-%d.ser", Math.abs(s.hashCode())); - } - @Override public Indexes getIndexes(String musicFolderId, boolean refresh, From 1e806e3658c7ce065803a8b0e01aa39037fa96ff Mon Sep 17 00:00:00 2001 From: Yahor Berdnikau Date: Sun, 11 Mar 2018 11:52:24 +0100 Subject: [PATCH 04/13] Added cache module to code coverage reports. Signed-off-by: Yahor Berdnikau --- .circleci/config.yml | 2 +- gradle_scripts/jacoco.gradle | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 8fea41d8..367a35bf 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -22,7 +22,7 @@ jobs: - run: name: unit-tests command: | - ./gradlew :subsonic-api:test :ultrasonic:testDebugUnitTest + ./gradlew :subsonic-api:test :cache:test :ultrasonic:testDebugUnitTest ./gradlew jacocoFullReport bash <(curl -s https://codecov.io/bash) - run: diff --git a/gradle_scripts/jacoco.gradle b/gradle_scripts/jacoco.gradle index 67a75609..5be89860 100644 --- a/gradle_scripts/jacoco.gradle +++ b/gradle_scripts/jacoco.gradle @@ -6,9 +6,11 @@ task jacocoMergeReports(type: JacocoMerge) { def subsonicApi = project.findProject("subsonic-api") def ultrasonicApp = project.findProject("ultrasonic") + def cache = project.findProject("cache") executionData( "${subsonicApi.buildDir}/jacoco/test.exec", "${ultrasonicApp.buildDir}/jacoco/testDebugUnitTest.exec", + "${cache.buildDir}/jacoco/test.exec" ) destinationFile(file("${project.buildDir}/jacoco/jacoco.exec")) } @@ -20,6 +22,7 @@ def createJacocoFullReportTask() { def subsonicApi = project.findProject("subsonic-api") def ultrasonicApp = project.findProject("ultrasonic") + def cache = project.findProject("cache") classDirectories = files( fileTree( @@ -29,6 +32,10 @@ def createJacocoFullReportTask() { fileTree( dir: "${ultrasonicApp.buildDir}/intermediates/classes/debug/org", excludes: ultrasonicApp.jacocoExclude + ), + fileTree( + dir: "${cache.buildDir}/classes/kotlin/main", + excludes: cache.jacocoExclude ) ) sourceDirectories = files(subsonicApi.sourceSets.main.getAllSource(), From 63715aab1828d509bc95afdf7098c4189887fc1b Mon Sep 17 00:00:00 2001 From: Yahor Berdnikau Date: Sun, 11 Mar 2018 19:13:01 +0100 Subject: [PATCH 05/13] Add more test for PermanentFileStorage. Signed-off-by: Yahor Berdnikau --- .../org/moire/ultrasonic/cache/PermanentFileStorage.kt | 4 +--- .../org/moire/ultrasonic/cache/PermanentFileStorageTest.kt | 7 +++++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/cache/src/main/kotlin/org/moire/ultrasonic/cache/PermanentFileStorage.kt b/cache/src/main/kotlin/org/moire/ultrasonic/cache/PermanentFileStorage.kt index 243077f1..92404bcb 100644 --- a/cache/src/main/kotlin/org/moire/ultrasonic/cache/PermanentFileStorage.kt +++ b/cache/src/main/kotlin/org/moire/ultrasonic/cache/PermanentFileStorage.kt @@ -55,9 +55,7 @@ class PermanentFileStorage( */ fun clearAll() { val storageDir = getStorageDir() - if (storageDir.exists()) { - storageDir.listFiles().forEach { if (it.isFile) it.delete() } - } + storageDir.listFiles().forEach { it.deleteRecursively() } } private fun getFile(name: String) = File(getStorageDir(), "$name.ser") diff --git a/cache/src/test/kotlin/org/moire/ultrasonic/cache/PermanentFileStorageTest.kt b/cache/src/test/kotlin/org/moire/ultrasonic/cache/PermanentFileStorageTest.kt index 856e6590..d000c562 100644 --- a/cache/src/test/kotlin/org/moire/ultrasonic/cache/PermanentFileStorageTest.kt +++ b/cache/src/test/kotlin/org/moire/ultrasonic/cache/PermanentFileStorageTest.kt @@ -64,4 +64,11 @@ class PermanentFileStorageTest : BaseStorageTest() { storageDir.listFiles().size `should equal to` 0 } + + @Test + fun `Should return null if serialized file not available`() { + val loadedItem = storage.load("some-name", musicFolderSerializer) + + loadedItem `should equal` null + } } From 423461c3ba10c12b717d92ae30522994fee44b86 Mon Sep 17 00:00:00 2001 From: Yahor Berdnikau Date: Sun, 11 Mar 2018 21:58:59 +0100 Subject: [PATCH 06/13] Store loaded indexes in persistent storage. Signed-off-by: Yahor Berdnikau --- .../cache/serializers/ArtistSerializer.kt | 60 +++++++++++++++++++ .../cache/serializers/IndexesSerializer.kt | 44 ++++++++++++++ .../moire/ultrasonic/cache/BaseStorageTest.kt | 6 ++ .../cache/serializers/ArtistSerializerTest.kt | 57 ++++++++++++++++++ .../serializers/IndexesSerializerTest.kt | 40 +++++++++++++ .../serializers/MusicFolderSerializerTest.kt | 7 +-- .../ultrasonic/service/RESTMusicService.java | 25 ++------ 7 files changed, 215 insertions(+), 24 deletions(-) create mode 100644 cache/src/main/kotlin/org/moire/ultrasonic/cache/serializers/ArtistSerializer.kt create mode 100644 cache/src/main/kotlin/org/moire/ultrasonic/cache/serializers/IndexesSerializer.kt create mode 100644 cache/src/test/kotlin/org/moire/ultrasonic/cache/serializers/ArtistSerializerTest.kt create mode 100644 cache/src/test/kotlin/org/moire/ultrasonic/cache/serializers/IndexesSerializerTest.kt diff --git a/cache/src/main/kotlin/org/moire/ultrasonic/cache/serializers/ArtistSerializer.kt b/cache/src/main/kotlin/org/moire/ultrasonic/cache/serializers/ArtistSerializer.kt new file mode 100644 index 00000000..96e1a9f0 --- /dev/null +++ b/cache/src/main/kotlin/org/moire/ultrasonic/cache/serializers/ArtistSerializer.kt @@ -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(SERIALIZER_VERSION) { + override fun serializeObject( + context: SerializationContext, + output: 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) diff --git a/cache/src/main/kotlin/org/moire/ultrasonic/cache/serializers/IndexesSerializer.kt b/cache/src/main/kotlin/org/moire/ultrasonic/cache/serializers/IndexesSerializer.kt new file mode 100644 index 00000000..e59e837c --- /dev/null +++ b/cache/src/main/kotlin/org/moire/ultrasonic/cache/serializers/IndexesSerializer.kt @@ -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(SERIALIZATION_VERSION) { + override fun serializeObject( + context: SerializationContext, + output: SerializerOutput>, + item: Indexes + ) { + output.writeLong(item.lastModified) + .writeString(item.ignoredArticles) + .writeObject>(context, item.shortcuts, + CollectionSerializers.getListSerializer(artistSerializer)) + .writeObject>(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) + } +} diff --git a/cache/src/test/kotlin/org/moire/ultrasonic/cache/BaseStorageTest.kt b/cache/src/test/kotlin/org/moire/ultrasonic/cache/BaseStorageTest.kt index 5a6d127b..dd4a3ae7 100644 --- a/cache/src/test/kotlin/org/moire/ultrasonic/cache/BaseStorageTest.kt +++ b/cache/src/test/kotlin/org/moire/ultrasonic/cache/BaseStorageTest.kt @@ -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) + } } diff --git a/cache/src/test/kotlin/org/moire/ultrasonic/cache/serializers/ArtistSerializerTest.kt b/cache/src/test/kotlin/org/moire/ultrasonic/cache/serializers/ArtistSerializerTest.kt new file mode 100644 index 00000000..74bc4221 --- /dev/null +++ b/cache/src/test/kotlin/org/moire/ultrasonic/cache/serializers/ArtistSerializerTest.kt @@ -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 + } +} diff --git a/cache/src/test/kotlin/org/moire/ultrasonic/cache/serializers/IndexesSerializerTest.kt b/cache/src/test/kotlin/org/moire/ultrasonic/cache/serializers/IndexesSerializerTest.kt new file mode 100644 index 00000000..704d59ac --- /dev/null +++ b/cache/src/test/kotlin/org/moire/ultrasonic/cache/serializers/IndexesSerializerTest.kt @@ -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 + } +} diff --git a/cache/src/test/kotlin/org/moire/ultrasonic/cache/serializers/MusicFolderSerializerTest.kt b/cache/src/test/kotlin/org/moire/ultrasonic/cache/serializers/MusicFolderSerializerTest.kt index 0fedccf1..602ffa47 100644 --- a/cache/src/test/kotlin/org/moire/ultrasonic/cache/serializers/MusicFolderSerializerTest.kt +++ b/cache/src/test/kotlin/org/moire/ultrasonic/cache/serializers/MusicFolderSerializerTest.kt @@ -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 diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/RESTMusicService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/RESTMusicService.java index 61d50c89..03978c28 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/RESTMusicService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/RESTMusicService.java @@ -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 getMusicFolders(boolean refresh, Context context, ProgressListener progressListener) throws Exception { - List cachedMusicFolders = fileStorage - .load(MUSIC_FOLDER_STORAGE_NAME, DomainSerializers.getMusicFolderListSerializer()); + List 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, From fa7b8b1c886d7ff9591ea790b2348e555cf019aa Mon Sep 17 00:00:00 2001 From: Yahor Berdnikau Date: Mon, 12 Mar 2018 21:19:46 +0100 Subject: [PATCH 07/13] Store loaded artists in persistent file store. Signed-off-by: Yahor Berdnikau --- .../ultrasonic/service/RESTMusicService.java | 28 +++++-------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/RESTMusicService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/RESTMusicService.java index 03978c28..74e146d7 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/RESTMusicService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/RESTMusicService.java @@ -106,7 +106,6 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; -import java.util.Locale; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -121,6 +120,7 @@ public class RESTMusicService implements MusicService { private static final String MUSIC_FOLDER_STORAGE_NAME = "music_folder"; private static final String INDEXES_STORAGE_NAME = "indexes"; + private static final String ARTISTS_STORAGE_NAME = "artists"; private final SubsonicAPIClient subsonicAPIClient; private final PermanentFileStorage fileStorage; @@ -198,36 +198,22 @@ public class RESTMusicService implements MusicService { public Indexes getArtists(boolean refresh, Context context, ProgressListener progressListener) throws Exception { - Indexes cachedArtists = readCachedArtists(context); - if (cachedArtists != null && - !refresh) { + Indexes cachedArtists = fileStorage + .load(ARTISTS_STORAGE_NAME, DomainSerializers.getIndexesSerializer()); + if (cachedArtists != null && !refresh) { return cachedArtists; } updateProgressListener(progressListener, R.string.parser_reading); - Response response = subsonicAPIClient.getApi().getArtists(null).execute(); + Response response = subsonicAPIClient.getApi() + .getArtists(null).execute(); checkResponseSuccessful(response); Indexes indexes = APIIndexesConverter.toDomainEntity(response.body().getIndexes()); - writeCachedArtists(context, indexes); + fileStorage.store(ARTISTS_STORAGE_NAME, indexes, DomainSerializers.getIndexesSerializer()); return indexes; } - private static Indexes readCachedArtists(Context context) { - String filename = getCachedArtistsFilename(context); - return FileUtil.deserialize(context, filename); - } - - private static void writeCachedArtists(Context context, Indexes artists) { - String filename = getCachedArtistsFilename(context); - FileUtil.serialize(context, artists, filename); - } - - private static String getCachedArtistsFilename(Context context) { - String s = Util.getRestUrl(context, null); - return String.format(Locale.US, "indexes-%d.ser", Math.abs(s.hashCode())); - } - @Override public void star(String id, String albumId, From 334ffbf5e9644cae73305425fce3ec652d21c513 Mon Sep 17 00:00:00 2001 From: Yahor Berdnikau Date: Mon, 12 Mar 2018 21:40:55 +0100 Subject: [PATCH 08/13] Make persistent storage per server base. Signed-off-by: Yahor Berdnikau --- .../moire/ultrasonic/cache/PermanentFileStorage.kt | 11 +++++++++-- .../org/moire/ultrasonic/cache/BaseStorageTest.kt | 4 +++- .../ultrasonic/cache/PermanentFileStorageTest.kt | 11 +++++++++-- .../moire/ultrasonic/service/MusicServiceFactory.java | 8 +++++++- 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/cache/src/main/kotlin/org/moire/ultrasonic/cache/PermanentFileStorage.kt b/cache/src/main/kotlin/org/moire/ultrasonic/cache/PermanentFileStorage.kt index 92404bcb..043e1fcc 100644 --- a/cache/src/main/kotlin/org/moire/ultrasonic/cache/PermanentFileStorage.kt +++ b/cache/src/main/kotlin/org/moire/ultrasonic/cache/PermanentFileStorage.kt @@ -11,10 +11,14 @@ internal const val STORAGE_DIR_NAME = "persistent_storage" /** * Provides access to permanent file based storage. * + * [serverId] is currently active server. Should be unique per server so stored data will not + * interfere with other server data. + * * Look at [org.moire.ultrasonic.cache.serializers] package for available [Serializer]s. */ class PermanentFileStorage( private val directories: Directories, + private val serverId: String, private val debug: Boolean = false ) { private val serializationContext = object : SerializationContext { @@ -60,7 +64,10 @@ class PermanentFileStorage( private fun getFile(name: String) = File(getStorageDir(), "$name.ser") - private fun getStorageDir() = File(directories.getInternalDataDir(), STORAGE_DIR_NAME).apply { - if (!exists()) mkdirs() + private fun getStorageDir(): File { + val mainDir = File(directories.getInternalDataDir(), STORAGE_DIR_NAME) + val serverDir = File(mainDir, serverId) + if (!serverDir.exists()) serverDir.mkdirs() + return serverDir } } diff --git a/cache/src/test/kotlin/org/moire/ultrasonic/cache/BaseStorageTest.kt b/cache/src/test/kotlin/org/moire/ultrasonic/cache/BaseStorageTest.kt index dd4a3ae7..bc06f483 100644 --- a/cache/src/test/kotlin/org/moire/ultrasonic/cache/BaseStorageTest.kt +++ b/cache/src/test/kotlin/org/moire/ultrasonic/cache/BaseStorageTest.kt @@ -21,6 +21,8 @@ abstract class BaseStorageTest { protected lateinit var mockDirectories: Directories protected lateinit var storage: PermanentFileStorage + open val serverId: String = "" + @Before fun setUp() { mockDirectories = mock { @@ -28,7 +30,7 @@ abstract class BaseStorageTest { on { getInternalCacheDir() } `it returns` tempFileRule.newFolder(INTERNAL_CACHE_FOLDER) on { getExternalCacheDir() } `it returns` tempFileRule.newFolder(EXTERNAL_CACHE_FOLDER) } - storage = PermanentFileStorage(mockDirectories, true) + storage = PermanentFileStorage(mockDirectories, serverId, true) } protected val storageDir get() = File(mockDirectories.getInternalDataDir(), STORAGE_DIR_NAME) diff --git a/cache/src/test/kotlin/org/moire/ultrasonic/cache/PermanentFileStorageTest.kt b/cache/src/test/kotlin/org/moire/ultrasonic/cache/PermanentFileStorageTest.kt index d000c562..0c22e946 100644 --- a/cache/src/test/kotlin/org/moire/ultrasonic/cache/PermanentFileStorageTest.kt +++ b/cache/src/test/kotlin/org/moire/ultrasonic/cache/PermanentFileStorageTest.kt @@ -6,17 +6,22 @@ import org.amshove.kluent.`should equal` import org.junit.Test import org.moire.ultrasonic.cache.serializers.musicFolderSerializer import org.moire.ultrasonic.domain.MusicFolder +import java.io.File /** * Integration test for [PermanentFileStorage]. */ class PermanentFileStorageTest : BaseStorageTest() { + override val serverId: String + get() = "some-server-id" + @Test fun `Should create storage dir if it is not exist`() { val item = MusicFolder("1", "2") storage.store("test", item, musicFolderSerializer) storageDir.exists() `should equal to` true + getServerStorageDir().exists() `should equal to` true } @Test @@ -26,7 +31,7 @@ class PermanentFileStorageTest : BaseStorageTest() { storage.store(name, item, musicFolderSerializer) - val storageFiles = storageDir.listFiles() + val storageFiles = getServerStorageDir().listFiles() storageFiles.size `should equal to` 1 storageFiles[0].name `should contain` name } @@ -62,7 +67,7 @@ class PermanentFileStorageTest : BaseStorageTest() { storage.clearAll() - storageDir.listFiles().size `should equal to` 0 + getServerStorageDir().listFiles().size `should equal to` 0 } @Test @@ -71,4 +76,6 @@ class PermanentFileStorageTest : BaseStorageTest() { loadedItem `should equal` null } + + private fun getServerStorageDir() = File(storageDir, serverId) } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicServiceFactory.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicServiceFactory.java index c9bbea1d..3c29a6b5 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicServiceFactory.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicServiceFactory.java @@ -114,7 +114,13 @@ public class MusicServiceFactory { } private static PermanentFileStorage getPermanentFileStorage(final Context context) { - return new PermanentFileStorage(getDirectories(context), BuildConfig.DEBUG); + final SharedPreferences preferences = Util.getPreferences(context); + int instance = preferences.getInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, 1); + String serverUrl = preferences.getString( + Constants.PREFERENCES_KEY_SERVER_URL + instance, null); + String serverId = String.valueOf(Math.abs((serverUrl + instance).hashCode())); + + return new PermanentFileStorage(getDirectories(context), serverId, BuildConfig.DEBUG); } private static Directories getDirectories(final Context context) { From 80769567867db1cfa762a252657ac5c115209fa9 Mon Sep 17 00:00:00 2001 From: Yahor Berdnikau Date: Mon, 12 Mar 2018 22:21:07 +0100 Subject: [PATCH 09/13] Clear persistent storage on server deletion. Signed-off-by: Yahor Berdnikau --- .../ultrasonic/fragment/ServerSettingsFragment.java | 11 +++++++++++ .../ultrasonic/service/MusicServiceFactory.java | 12 ++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/ServerSettingsFragment.java b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/ServerSettingsFragment.java index 85456be2..536e6fc4 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/ServerSettingsFragment.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/ServerSettingsFragment.java @@ -12,7 +12,9 @@ import android.support.annotation.Nullable; import android.util.Log; import android.view.View; +import org.moire.ultrasonic.BuildConfig; import org.moire.ultrasonic.R; +import org.moire.ultrasonic.cache.PermanentFileStorage; import org.moire.ultrasonic.service.MusicService; import org.moire.ultrasonic.service.MusicServiceFactory; import org.moire.ultrasonic.util.Constants; @@ -279,6 +281,15 @@ public class ServerSettingsFragment extends PreferenceFragment int activeServers = sharedPreferences .getInt(Constants.PREFERENCES_KEY_ACTIVE_SERVERS, 0); + // Clear permanent storage + final String storageServerId = MusicServiceFactory.getServerId(sharedPreferences, serverId); + final PermanentFileStorage fileStorage = new PermanentFileStorage( + MusicServiceFactory.getDirectories(getActivity()), + storageServerId, + BuildConfig.DEBUG + ); + fileStorage.clearAll(); + // Reset values to null so when we ask for them again they are new sharedPreferences.edit() .remove(Constants.PREFERENCES_KEY_SERVER_NAME + serverId) diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicServiceFactory.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicServiceFactory.java index 3c29a6b5..eb859aa5 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicServiceFactory.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicServiceFactory.java @@ -116,14 +116,18 @@ public class MusicServiceFactory { private static PermanentFileStorage getPermanentFileStorage(final Context context) { final SharedPreferences preferences = Util.getPreferences(context); int instance = preferences.getInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, 1); - String serverUrl = preferences.getString( - Constants.PREFERENCES_KEY_SERVER_URL + instance, null); - String serverId = String.valueOf(Math.abs((serverUrl + instance).hashCode())); + final String serverId = getServerId(preferences, instance); return new PermanentFileStorage(getDirectories(context), serverId, BuildConfig.DEBUG); } - private static Directories getDirectories(final Context context) { + public static String getServerId(final SharedPreferences sp, final int instance) { + String serverUrl = sp.getString( + Constants.PREFERENCES_KEY_SERVER_URL + instance, null); + return String.valueOf(Math.abs((serverUrl + instance).hashCode())); + } + + public static Directories getDirectories(final Context context) { return new Directories() { @NotNull @Override From 72743346dce9f0847dc9988ab4c43801cd8d0d0a Mon Sep 17 00:00:00 2001 From: Yahor Berdnikau Date: Sun, 18 Mar 2018 17:30:55 +0100 Subject: [PATCH 10/13] Hide implementation under typealias. Though it is not working for java interop. Signed-off-by: Yahor Berdnikau --- .../ultrasonic/cache/PermanentFileStorage.kt | 8 +++--- .../cache/serializers/ArtistSerializer.kt | 15 +++++++---- .../cache/serializers/IndexesSerializer.kt | 26 +++++++++++-------- .../serializers/MusicFolderSerializer.kt | 17 ++++++++---- .../cache/PermanentFileStorageTest.kt | 22 ++++++++-------- .../cache/serializers/ArtistSerializerTest.kt | 12 ++++----- .../serializers/IndexesSerializerTest.kt | 6 ++--- .../serializers/MusicFolderSerializerTest.kt | 12 ++++----- 8 files changed, 68 insertions(+), 50 deletions(-) diff --git a/cache/src/main/kotlin/org/moire/ultrasonic/cache/PermanentFileStorage.kt b/cache/src/main/kotlin/org/moire/ultrasonic/cache/PermanentFileStorage.kt index 043e1fcc..dbfa9658 100644 --- a/cache/src/main/kotlin/org/moire/ultrasonic/cache/PermanentFileStorage.kt +++ b/cache/src/main/kotlin/org/moire/ultrasonic/cache/PermanentFileStorage.kt @@ -6,6 +6,8 @@ import com.twitter.serial.stream.Serial import com.twitter.serial.stream.bytebuffer.ByteBufferSerial import java.io.File +typealias DomainEntitySerializer = Serializer + internal const val STORAGE_DIR_NAME = "persistent_storage" /** @@ -14,7 +16,7 @@ internal const val STORAGE_DIR_NAME = "persistent_storage" * [serverId] is currently active server. Should be unique per server so stored data will not * interfere with other server data. * - * Look at [org.moire.ultrasonic.cache.serializers] package for available [Serializer]s. + * Look at [org.moire.ultrasonic.cache.serializers] package for available [DomainEntitySerializer]s. */ class PermanentFileStorage( private val directories: Directories, @@ -34,7 +36,7 @@ class PermanentFileStorage( fun store( name: String, objectToStore: T, - objectSerializer: Serializer + objectSerializer: DomainEntitySerializer ) { val storeFile = getFile(name) if (!storeFile.exists()) storeFile.createNewFile() @@ -46,7 +48,7 @@ class PermanentFileStorage( */ fun load( name: String, - objectDeserializer: Serializer + objectDeserializer: DomainEntitySerializer ): T? { val storeFile = getFile(name) if (!storeFile.exists()) return null diff --git a/cache/src/main/kotlin/org/moire/ultrasonic/cache/serializers/ArtistSerializer.kt b/cache/src/main/kotlin/org/moire/ultrasonic/cache/serializers/ArtistSerializer.kt index 96e1a9f0..751a97d5 100644 --- a/cache/src/main/kotlin/org/moire/ultrasonic/cache/serializers/ArtistSerializer.kt +++ b/cache/src/main/kotlin/org/moire/ultrasonic/cache/serializers/ArtistSerializer.kt @@ -8,14 +8,12 @@ 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.cache.DomainEntitySerializer import org.moire.ultrasonic.domain.Artist private const val SERIALIZER_VERSION = 1 -/** - * Serializer/deserializer for [Artist] domain entity. - */ -val artistSerializer get() = object : ObjectSerializer(SERIALIZER_VERSION) { +private val artistSerializer get() = object : ObjectSerializer(SERIALIZER_VERSION) { override fun serializeObject( context: SerializationContext, output: SerializerOutput>, @@ -54,7 +52,14 @@ val artistSerializer get() = object : ObjectSerializer(SERIALIZER_VERSIO } } +/** + * Serializer/deserializer for [Artist] domain entity. + */ +fun getArtistsSerializer(): DomainEntitySerializer = artistSerializer + +private val artistListSerializer = CollectionSerializers.getListSerializer(artistSerializer) + /** * Serializer/deserializer for list of [Artist] domain entities. */ -val artistListSerializer = CollectionSerializers.getListSerializer(artistSerializer) +fun getArtistListSerializer(): DomainEntitySerializer> = artistListSerializer diff --git a/cache/src/main/kotlin/org/moire/ultrasonic/cache/serializers/IndexesSerializer.kt b/cache/src/main/kotlin/org/moire/ultrasonic/cache/serializers/IndexesSerializer.kt index e59e837c..c237be76 100644 --- a/cache/src/main/kotlin/org/moire/ultrasonic/cache/serializers/IndexesSerializer.kt +++ b/cache/src/main/kotlin/org/moire/ultrasonic/cache/serializers/IndexesSerializer.kt @@ -2,28 +2,27 @@ @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.cache.DomainEntitySerializer import org.moire.ultrasonic.domain.Artist import org.moire.ultrasonic.domain.Indexes private const val SERIALIZATION_VERSION = 1 -val indexesSerializer get() = object : ObjectSerializer(SERIALIZATION_VERSION) { +private val indexesSerializer get() = object : ObjectSerializer(SERIALIZATION_VERSION) { override fun serializeObject( context: SerializationContext, output: SerializerOutput>, item: Indexes ) { + val artistListSerializer = getArtistListSerializer() output.writeLong(item.lastModified) .writeString(item.ignoredArticles) - .writeObject>(context, item.shortcuts, - CollectionSerializers.getListSerializer(artistSerializer)) - .writeObject>(context, item.artists, - CollectionSerializers.getListSerializer(artistSerializer)) + .writeObject>(context, item.shortcuts, artistListSerializer) + .writeObject>(context, item.artists, artistListSerializer) } override fun deserializeObject( @@ -33,12 +32,17 @@ val indexesSerializer get() = object : ObjectSerializer(SERIALIZATION_V ): Indexes? { if (versionNumber != SERIALIZATION_VERSION) return null + val artistListDeserializer = getArtistListSerializer() 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) + val shortcutsList = input.readObject(context, artistListDeserializer) ?: return null + val artistsList = input.readObject(context, artistListDeserializer) ?: return null + return Indexes(lastModified, ignoredArticles, shortcutsList.toMutableList(), + artistsList.toMutableList()) } } + +/** + * Get serializer/deserializer for [Indexes] entity. + */ +fun getIndexesSerializer(): DomainEntitySerializer = indexesSerializer diff --git a/cache/src/main/kotlin/org/moire/ultrasonic/cache/serializers/MusicFolderSerializer.kt b/cache/src/main/kotlin/org/moire/ultrasonic/cache/serializers/MusicFolderSerializer.kt index 655c3fd4..c65fabad 100644 --- a/cache/src/main/kotlin/org/moire/ultrasonic/cache/serializers/MusicFolderSerializer.kt +++ b/cache/src/main/kotlin/org/moire/ultrasonic/cache/serializers/MusicFolderSerializer.kt @@ -7,14 +7,12 @@ 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.cache.DomainEntitySerializer import org.moire.ultrasonic.domain.MusicFolder private const val SERIALIZATION_VERSION = 1 -/** - * Serializer/deserializer for [MusicFolder] domain entity. - */ -val musicFolderSerializer = object : ObjectSerializer(SERIALIZATION_VERSION) { +private val musicFolderSerializer = object : ObjectSerializer(SERIALIZATION_VERSION) { override fun serializeObject( context: SerializationContext, @@ -37,7 +35,16 @@ val musicFolderSerializer = object : ObjectSerializer(SERIALIZATION } } +/** + * Serializer/deserializer for [MusicFolder] domain entity. + */ +fun getMusicFolderSerializer(): DomainEntitySerializer = musicFolderSerializer + +private val musicFolderListSerializer = + CollectionSerializers.getListSerializer(musicFolderSerializer) + /** * Serializer/deserializer for [List] of [MusicFolder] items. */ -val musicFolderListSerializer = CollectionSerializers.getListSerializer(musicFolderSerializer) +fun getMusicFolderListSerializer(): DomainEntitySerializer> = + musicFolderListSerializer diff --git a/cache/src/test/kotlin/org/moire/ultrasonic/cache/PermanentFileStorageTest.kt b/cache/src/test/kotlin/org/moire/ultrasonic/cache/PermanentFileStorageTest.kt index 0c22e946..bd84b528 100644 --- a/cache/src/test/kotlin/org/moire/ultrasonic/cache/PermanentFileStorageTest.kt +++ b/cache/src/test/kotlin/org/moire/ultrasonic/cache/PermanentFileStorageTest.kt @@ -4,7 +4,7 @@ import org.amshove.kluent.`should contain` import org.amshove.kluent.`should equal to` import org.amshove.kluent.`should equal` import org.junit.Test -import org.moire.ultrasonic.cache.serializers.musicFolderSerializer +import org.moire.ultrasonic.cache.serializers.getMusicFolderSerializer import org.moire.ultrasonic.domain.MusicFolder import java.io.File @@ -18,7 +18,7 @@ class PermanentFileStorageTest : BaseStorageTest() { @Test fun `Should create storage dir if it is not exist`() { val item = MusicFolder("1", "2") - storage.store("test", item, musicFolderSerializer) + storage.store("test", item, getMusicFolderSerializer()) storageDir.exists() `should equal to` true getServerStorageDir().exists() `should equal to` true @@ -29,7 +29,7 @@ class PermanentFileStorageTest : BaseStorageTest() { val item = MusicFolder("1", "23") val name = "some-name" - storage.store(name, item, musicFolderSerializer) + storage.store(name, item, getMusicFolderSerializer()) val storageFiles = getServerStorageDir().listFiles() storageFiles.size `should equal to` 1 @@ -40,9 +40,9 @@ class PermanentFileStorageTest : BaseStorageTest() { fun `Should deserialize stored object`() { val item = MusicFolder("some", "nice") val name = "some-name" - storage.store(name, item, musicFolderSerializer) + storage.store(name, item, getMusicFolderSerializer()) - val loadedItem = storage.load(name, musicFolderSerializer) + val loadedItem = storage.load(name, getMusicFolderSerializer()) loadedItem `should equal` item } @@ -52,18 +52,18 @@ class PermanentFileStorageTest : BaseStorageTest() { val name = "some-nice-name" val item1 = MusicFolder("1", "1") val item2 = MusicFolder("2", "2") - storage.store(name, item1, musicFolderSerializer) - storage.store(name, item2, musicFolderSerializer) + storage.store(name, item1, getMusicFolderSerializer()) + storage.store(name, item2, getMusicFolderSerializer()) - val loadedItem = storage.load(name, musicFolderSerializer) + val loadedItem = storage.load(name, getMusicFolderSerializer()) loadedItem `should equal` item2 } @Test fun `Should clear all files when clearAll is called`() { - storage.store("name1", MusicFolder("1", "1"), musicFolderSerializer) - storage.store("name2", MusicFolder("2", "2"), musicFolderSerializer) + storage.store("name1", MusicFolder("1", "1"), getMusicFolderSerializer()) + storage.store("name2", MusicFolder("2", "2"), getMusicFolderSerializer()) storage.clearAll() @@ -72,7 +72,7 @@ class PermanentFileStorageTest : BaseStorageTest() { @Test fun `Should return null if serialized file not available`() { - val loadedItem = storage.load("some-name", musicFolderSerializer) + val loadedItem = storage.load("some-name", getMusicFolderSerializer()) loadedItem `should equal` null } diff --git a/cache/src/test/kotlin/org/moire/ultrasonic/cache/serializers/ArtistSerializerTest.kt b/cache/src/test/kotlin/org/moire/ultrasonic/cache/serializers/ArtistSerializerTest.kt index 74bc4221..7829467b 100644 --- a/cache/src/test/kotlin/org/moire/ultrasonic/cache/serializers/ArtistSerializerTest.kt +++ b/cache/src/test/kotlin/org/moire/ultrasonic/cache/serializers/ArtistSerializerTest.kt @@ -13,7 +13,7 @@ class ArtistSerializerTest : BaseStorageTest() { fun `Should correctly serialize Artist object`() { val item = Artist("id", "name", "index", "coverArt", 1, 0) - storage.store("some-name", item, artistSerializer) + storage.store("some-name", item, getArtistsSerializer()) validateSerializedData() } @@ -22,9 +22,9 @@ class ArtistSerializerTest : BaseStorageTest() { fun `Should correctly deserialize Artist object`() { val itemName = "some-name" val item = Artist("id", "name", "index", "coverArt", null, 0) - storage.store(itemName, item, artistSerializer) + storage.store(itemName, item, getArtistsSerializer()) - val loadedItem = storage.load(itemName, artistSerializer) + val loadedItem = storage.load(itemName, getArtistsSerializer()) loadedItem `should equal` item } @@ -36,7 +36,7 @@ class ArtistSerializerTest : BaseStorageTest() { Artist(id = "2", name = "some") ) - storage.store("some-name", itemsList, artistListSerializer) + storage.store("some-name", itemsList, getArtistListSerializer()) validateSerializedData() } @@ -48,9 +48,9 @@ class ArtistSerializerTest : BaseStorageTest() { Artist(id = "1"), Artist(id = "2", name = "some") ) - storage.store(name, itemsList, artistListSerializer) + storage.store(name, itemsList, getArtistListSerializer()) - val loadedItems = storage.load(name, artistListSerializer) + val loadedItems = storage.load(name, getArtistListSerializer()) loadedItems `should equal` itemsList } diff --git a/cache/src/test/kotlin/org/moire/ultrasonic/cache/serializers/IndexesSerializerTest.kt b/cache/src/test/kotlin/org/moire/ultrasonic/cache/serializers/IndexesSerializerTest.kt index 704d59ac..9cffbd8b 100644 --- a/cache/src/test/kotlin/org/moire/ultrasonic/cache/serializers/IndexesSerializerTest.kt +++ b/cache/src/test/kotlin/org/moire/ultrasonic/cache/serializers/IndexesSerializerTest.kt @@ -18,7 +18,7 @@ class IndexesSerializerTest : BaseStorageTest() { Artist("233", "some") )) - storage.store("some-name", item, indexesSerializer) + storage.store("some-name", item, getIndexesSerializer()) validateSerializedData() } @@ -31,9 +31,9 @@ class IndexesSerializerTest : BaseStorageTest() { ), mutableListOf( Artist("233", "some") )) - storage.store(name, item, indexesSerializer) + storage.store(name, item, getIndexesSerializer()) - val loadedItem = storage.load(name, indexesSerializer) + val loadedItem = storage.load(name, getIndexesSerializer()) loadedItem `should equal` item } diff --git a/cache/src/test/kotlin/org/moire/ultrasonic/cache/serializers/MusicFolderSerializerTest.kt b/cache/src/test/kotlin/org/moire/ultrasonic/cache/serializers/MusicFolderSerializerTest.kt index 602ffa47..0efe2811 100644 --- a/cache/src/test/kotlin/org/moire/ultrasonic/cache/serializers/MusicFolderSerializerTest.kt +++ b/cache/src/test/kotlin/org/moire/ultrasonic/cache/serializers/MusicFolderSerializerTest.kt @@ -13,7 +13,7 @@ class MusicFolderSerializerTest : BaseStorageTest() { fun `Should correctly serialize MusicFolder object`() { val item = MusicFolder("Music", "Folder") - storage.store("some-name", item, musicFolderSerializer) + storage.store("some-name", item, getMusicFolderSerializer()) validateSerializedData() } @@ -22,9 +22,9 @@ class MusicFolderSerializerTest : BaseStorageTest() { fun `Should correctly deserialize MusicFolder object`() { val name = "name" val item = MusicFolder("some", "none") - storage.store(name, item, musicFolderSerializer) + storage.store(name, item, getMusicFolderSerializer()) - val loadedItem = storage.load(name, musicFolderSerializer) + val loadedItem = storage.load(name, getMusicFolderSerializer()) loadedItem `should equal` item } @@ -36,7 +36,7 @@ class MusicFolderSerializerTest : BaseStorageTest() { MusicFolder("2", "2") ) - storage.store("some-name", itemsList, musicFolderListSerializer) + storage.store("some-name", itemsList, getMusicFolderListSerializer()) validateSerializedData() } @@ -48,9 +48,9 @@ class MusicFolderSerializerTest : BaseStorageTest() { MusicFolder("1", "1"), MusicFolder("2", "2") ) - storage.store(name, itemsList, musicFolderListSerializer) + storage.store(name, itemsList, getMusicFolderListSerializer()) - val loadedItem = storage.load(name, musicFolderListSerializer) + val loadedItem = storage.load(name, getMusicFolderListSerializer()) loadedItem `should equal` itemsList } From b6140d841f2befc060d4786fb85716cbef5a3d2e Mon Sep 17 00:00:00 2001 From: Yahor Berdnikau Date: Sun, 18 Mar 2018 17:36:22 +0100 Subject: [PATCH 11/13] Update twitter serial to 0.1.6 version. This fixes unknown type deserialization error. Signed-off-by: Yahor Berdnikau --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index e32b7801..d0173c23 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -22,7 +22,7 @@ ext.versions = [ jackson : "2.9.0", okhttp : "3.9.0", semver : "1.0.0", - twitterSerial : "0.1.5", + twitterSerial : "0.1.6", junit : "4.12", mockito : "2.12.0", From cd02aff0f56e95f426f054b19572b68a2d6f5a84 Mon Sep 17 00:00:00 2001 From: Yahor Berdnikau Date: Sun, 18 Mar 2018 17:37:11 +0100 Subject: [PATCH 12/13] Remove ununsed companion object from MusicFolder entity. Signed-off-by: Yahor Berdnikau --- .../main/kotlin/org/moire/ultrasonic/domain/MusicFolder.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/domain/src/main/kotlin/org/moire/ultrasonic/domain/MusicFolder.kt b/domain/src/main/kotlin/org/moire/ultrasonic/domain/MusicFolder.kt index 85661482..a7e91d27 100644 --- a/domain/src/main/kotlin/org/moire/ultrasonic/domain/MusicFolder.kt +++ b/domain/src/main/kotlin/org/moire/ultrasonic/domain/MusicFolder.kt @@ -6,6 +6,4 @@ package org.moire.ultrasonic.domain data class MusicFolder( val id: String, val name: String -) { - companion object -} +) From 1737fd69e715912c2a2ddbf98e4cd11517f6e54d Mon Sep 17 00:00:00 2001 From: Yahor Berdnikau Date: Sun, 18 Mar 2018 17:38:47 +0100 Subject: [PATCH 13/13] Remove unused method from FileUtil. Signed-off-by: Yahor Berdnikau --- .../src/main/java/org/moire/ultrasonic/util/FileUtil.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/FileUtil.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/FileUtil.java index c306cbb7..fdcf9abd 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/FileUtil.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/FileUtil.java @@ -26,7 +26,6 @@ import android.text.TextUtils; import android.util.Log; import org.moire.ultrasonic.activity.SubsonicTabActivity; -import org.moire.ultrasonic.domain.Artist; import org.moire.ultrasonic.domain.MusicDirectory; import java.io.File; @@ -300,11 +299,6 @@ public class FileUtil return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, opt); } - public static File getArtistDirectory(Context context, Artist artist) - { - return new File(String.format("%s/%s", getMusicDirectory(context).getPath(), fileSystemSafe(artist.getName()))); - } - public static File getAlbumArtDirectory() { File albumArtDir = new File(getUltraSonicDirectory(), "artwork");