VLC for Androidのリポジトリについて

# はじめに

リポジトリとはなんぞやとや言うことで有名OSSメディアプレイヤーアプリである「VLC for Android」にあるリポジトリの実装を調べる。
当方はAndroidの素人なのでその辺はご了承を。

# 各種リンク

[アプリ](https://play.google.com/store/apps/details?id=org.videolan.vlc&hl=ja&gl=US)
[GitHub](https://github.com/videolan/vlc-android)
[公式サイト](https://www.videolan.org/index.ja.html)

# アプリ内で定義しているリポジトリ

|クラス  |概要  |
|---|---|
|BrowserFavRepository |お気に入りを保持？  |
| DirectoryRepository |ディレクトリを保持？ |
| ExternalSubRepository |外部ストレージ |
| MediaMetadataRepository |メタデータ  |
| MediaPersonRepository |?|
| MoviepediaApiRepository |MoviepediaのApi |
| OpenSubtitleRepository |?|
| PersonRepository |MoviePediaから取得した人をキャッシュ |
| SlaveRepository |?|

# データソース

どこからデータを持ってきているかを調べた結果。データベースとAPI、オンメモリーの３つのパターンがある

|クラス  |データソース  |
|---|---|
| BrowserFavRepository | データベース(BrowserFavDao) |
| DirectoryRepository | データベース(CustomDirectoryDao) |
| ExternalSubRepository |  データベース(ExternalSubDao)、 オンメモリー |
| MediaMetadataRepository | データベース(MediaMetadataDataFullDao、MediaMetadataDao、MediaImageDao)  |
| MediaPersonRepository | データベース(MediaPersonJoinDao) |
| MoviepediaApiRepository | REST API(IMoviepediaApiService)  |
| OpenSubtitleRepository | REST API?(IOpenSubtitleService) |
| PersonRepository |  データベース(PersonDao) |
| SlaveRepository |  データベース(SlaveDao) |

# ユニットテストを入れているか

|クラス  |　テストクラス  |
|---|---|
| BrowserFavRepository |○(BrowserFavRepositoryTest)　|
| DirectoryRepository |○(DirectoryRepositoryTest) |
| ExternalSubRepository |○(ExternalSubRepositoryTest) |
| MediaMetadataRepository |× |
| MediaPersonRepository |× |
| MoviepediaApiRepository |× |
| OpenSubtitleRepository |○？(SubtitlesModelTest) |
| PersonRepository |× |
| SlaveRepository |○(SlaveRepositoryTest) |

# データ取得元がデータベースの場合のクラス構成

↓はExternalSubRepository周りのクラス構成。ExternalSubクラスはデータベース、SubtitleItemはオンメモリーで管理している。
LiveData、Coroutineが絡んでいるがここでは割愛する。

![Class Diagram0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39706/55e6a50b-91fc-db1b-2f75-c910148bcc44.png)

ExternalSubRepositoryの責務はなんなのかというと、↓のコードをにある、Roomでは表現できない？制約にそってCRUD操作を実装している。後はテスタビリティの確保。

```
   fun addDownloadingItem(key: Long, item: SubtitleItem) {
        _downloadingSubtitles.add(key, item.copy(state = State.Downloading))
    }

    fun getDownloadedSubtitles(mediaUri: Uri): LiveData<List<org.videolan.vlc.mediadb.models.ExternalSub>> {
        val externalSubs = externalSubDao.get(mediaUri.path!!)
        return Transformations.map(externalSubs) { list ->
            val existExternalSubs: MutableList<org.videolan.vlc.mediadb.models.ExternalSub> = mutableListOf()
            list.forEach {
                if (File(Uri.decode(it.subtitlePath)).exists())
                    existExternalSubs.add(it)
                else
                    deleteSubtitle(it.mediaPath, it.idSubtitle)
            }
            existExternalSubs
        }
    }
```
　
# データ取得元がREST APIの場合のクラス構成


↓はOpenSubtitleRepository周りのクラス構成。

![Class Diagram1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39706/0bff52ff-000a-c851-8dac-cf394d43e5af.png)

OpenSubtitleRepositoryの責務はなんなのかというと、↓のコードにある通りRest APIのクエリーパラメータの組み立てと補正。
クエリーオブジェクト的な責務を行っている状態。後はテスタビリティの確保。

```
    suspend fun queryWithImdbid(imdbId: Int, tag: String?, episode: Int? , season: Int?, languageId: String? ): List<OpenSubtitle> {
        val actualEpisode = episode ?: 0
        val actualSeason = season ?: 0
        val actualLanguageId = languageId ?: ""
        val actualTag = tag ?: ""
        return openSubtitleService.query(
                imdbId = String.format("%07d", imdbId),
                tag = actualTag,
                episode = actualEpisode,
                season = actualSeason,
                languageId = actualLanguageId)
    }
```

# ExternalSubRepositoryのユニットテスト
案の定、ExternalSubDaoをモック化してテストしている。

# OpenSubtitleRepositoryのユニットテスト
SubtitlesModelTestで行っている。SubtitlesModelはViewModel派生のクラスで色々面白い事をしているがここでは触れない。
OpenSubtitleRepositoryそのもの、ExternalSubDaoをモック化してテストしている。 RetroFitのクラスはモック化していなかった。

# その他リポジトリの(テスタビリティ担保以外の）責務

|クラス  | (テスタビリティ担保以外の）責務  |
|---|---|
| BrowserFavRepository |サーバー保存のお気に入りとローカル保存のお気に入りの整合性をとっている？|
| DirectoryRepository |実際にフォルダを作成、削除して、DBに同期している？|
| ExternalSubRepository |説明済 |
| MediaMetadataRepository |クエリーの組み立て |
| MediaPersonRepository |なし |
| MoviepediaApiRepository |クエリーの組み立て |
| OpenSubtitleRepository |説明済 |
| PersonRepository |なし|
| SlaveRepository |様々な保存パターンに対応する。SQL例外の補正 |

# まとめ
- テスタビリティ担保以外の主な責務はクエリーの組み立て、サーバーとローカルキャッシュの整合性、SQL周りの隠蔽、Retrofit周りの隠蔽
- Coroutine、LiveDataと密結合している。AndroidのUIでしか使わないから？
- シングルトンについてSingletonHolderとCompanionオブジェクトを分けているのはなに？