mirror of
https://github.com/NiciDieNase/chaosflix
synced 2025-01-30 13:03:30 +00:00
remove RxJava and move Downloader from touch-project
This commit is contained in:
parent
14609cc907
commit
c2a83fae2d
14 changed files with 376 additions and 41 deletions
|
@ -45,12 +45,9 @@ dependencies {
|
|||
api "android.arch.persistence.room:rxjava2:${rootProject.ext.archCompVersion}"
|
||||
kapt "android.arch.persistence.room:compiler:${rootProject.ext.archCompVersion}"
|
||||
|
||||
api 'io.reactivex.rxjava2:rxjava:2.1.5'
|
||||
|
||||
api 'com.squareup.retrofit2:retrofit:2.3.0'
|
||||
|
||||
api 'com.squareup.retrofit2:converter-gson:2.3.0'
|
||||
api 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
|
||||
|
||||
api 'com.fasterxml.jackson.module:jackson-module-kotlin:2.9.0'
|
||||
|
||||
|
|
|
@ -27,12 +27,14 @@ data class Recording(
|
|||
) {
|
||||
|
||||
var recordingID: Long
|
||||
var eventID: Long
|
||||
|
||||
init {
|
||||
val strings = url.split("/".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
recordingID = (strings[strings.size - 1]).toLong()
|
||||
}
|
||||
|
||||
fun getEventString():String {
|
||||
val split = eventUrl.split("/".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
eventID = (split[split.size - 1]).toLong()
|
||||
return (split[split.size - 1])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ interface EventDao: PersistentItemDao<PersistentEvent> {
|
|||
@Query("SELECT * FROM event WHERE conference = :id ORDER BY title ASC")
|
||||
fun findEventsByConference(id: Long):LiveData<List<PersistentEvent>>
|
||||
|
||||
@Query("SELECT * FROM event WHERE conference = :id ORDER BY title ASC")
|
||||
@Query("SELECT * FROM event WHERE conferenceId = :id ORDER BY title ASC")
|
||||
fun findEventsByConferenceSync(id: Long):List<PersistentEvent>
|
||||
|
||||
@Query("SELECT * FROM event INNER JOIN watchlist_item WHERE event.id = watchlist_item.event_id")
|
||||
|
|
|
@ -13,7 +13,7 @@ import de.nicidienase.chaosflix.common.mediadata.entities.recording.Recording
|
|||
childColumns = arrayOf("eventId"))),
|
||||
indices = arrayOf(Index("eventId")))
|
||||
data class PersistentRecording(
|
||||
var eventId: Long,
|
||||
var eventId: Long = 0,
|
||||
var size: Int = 0,
|
||||
var length: Int = 0,
|
||||
var mimeType: String = "",
|
||||
|
@ -51,23 +51,23 @@ data class PersistentRecording(
|
|||
}
|
||||
|
||||
@Ignore
|
||||
constructor(rec: Recording) : this(
|
||||
rec.eventID,
|
||||
rec.size,
|
||||
rec.length,
|
||||
rec.mimeType,
|
||||
rec.language,
|
||||
rec.filename,
|
||||
rec.state,
|
||||
rec.folder,
|
||||
rec.isHighQuality,
|
||||
rec.width,
|
||||
rec.height,
|
||||
rec.updatedAt,
|
||||
rec.recordingUrl,
|
||||
rec.url,
|
||||
rec.eventUrl,
|
||||
rec.conferenceUrl)
|
||||
constructor(rec: Recording, eventId: Long = 0) : this(
|
||||
eventId = eventId,
|
||||
size = rec.size,
|
||||
length = rec.length,
|
||||
mimeType = rec.mimeType,
|
||||
language = rec.language,
|
||||
filename = rec.filename,
|
||||
state = rec.state,
|
||||
folder = rec.folder,
|
||||
isHighQuality = rec.isHighQuality,
|
||||
width = rec.width,
|
||||
height = rec.height,
|
||||
updatedAt = rec.updatedAt,
|
||||
recordingUrl = rec.recordingUrl,
|
||||
url = rec.url,
|
||||
eventUrl = rec.eventUrl,
|
||||
conferenceUrl = rec.conferenceUrl)
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
parcel.writeLong(eventId)
|
||||
|
|
|
@ -7,7 +7,7 @@ import android.arch.persistence.room.OnConflictStrategy
|
|||
import android.arch.persistence.room.Query
|
||||
|
||||
@Dao
|
||||
interface RecordingDao{
|
||||
interface RecordingDao: PersistentItemDao<RecordingDao>{
|
||||
|
||||
@Query("SELECT * FROM recording")
|
||||
fun getAllRecordings(): LiveData<List<PersistentRecording>>
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
package de.nicidienase.chaosflix.common.mediadata.network
|
||||
|
||||
import android.content.res.Resources
|
||||
import com.google.gson.Gson
|
||||
import de.nicidienase.chaosflix.R
|
||||
import okhttp3.OkHttpClient
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class ApiFactory(val res: Resources) {
|
||||
|
||||
val gsonConverterFactory by lazy { GsonConverterFactory.create(Gson()) }
|
||||
|
||||
val client: OkHttpClient by lazy {
|
||||
OkHttpClient.Builder()
|
||||
.connectTimeout(60, TimeUnit.SECONDS)
|
||||
.readTimeout(60, TimeUnit.SECONDS)
|
||||
.build()
|
||||
}
|
||||
|
||||
val recordingApi: RecordingService by lazy {
|
||||
Retrofit.Builder()
|
||||
.baseUrl(res.getString(R.string.api_media_ccc_url))
|
||||
.client(client)
|
||||
.addConverterFactory(gsonConverterFactory)
|
||||
.build()
|
||||
.create(RecordingService::class.java)
|
||||
}
|
||||
|
||||
val streamingApi: StreamingService by lazy { Retrofit.Builder()
|
||||
.baseUrl(res.getString(R.string.streaming_media_ccc_url))
|
||||
.client(client)
|
||||
.addConverterFactory(gsonConverterFactory)
|
||||
.build()
|
||||
.create(StreamingService::class.java) }
|
||||
}
|
|
@ -4,31 +4,28 @@ import de.nicidienase.chaosflix.common.mediadata.entities.recording.Conference
|
|||
import de.nicidienase.chaosflix.common.mediadata.entities.recording.ConferencesWrapper
|
||||
import de.nicidienase.chaosflix.common.mediadata.entities.recording.Event
|
||||
import de.nicidienase.chaosflix.common.mediadata.entities.recording.Recording
|
||||
import io.reactivex.Single
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Path
|
||||
|
||||
public interface RecordingService {
|
||||
interface RecordingService {
|
||||
|
||||
@GET("public/conferences")
|
||||
fun getConferencesWrapper(): Single<ConferencesWrapper>
|
||||
|
||||
@GET("public/events")
|
||||
fun getAllEvents(): Single<List<Event>>
|
||||
fun getConferencesWrapper(): Call<ConferencesWrapper>
|
||||
|
||||
@GET("public/conferences/{id}")
|
||||
fun getConference(@Path("id") id: Long): Single<Conference>
|
||||
|
||||
@GET("public/conferences/{id}")
|
||||
fun getConferenceString(@Path("id") id: Long): Single<String>
|
||||
fun getConference(@Path("id") id: Long): Call<Conference>
|
||||
|
||||
@GET("public/conferences/{name}")
|
||||
fun getConferenceByname(@Path("name") name: String): Single<Conference>
|
||||
fun getConferenceByName(@Path("name") name: String): Call<Conference>
|
||||
|
||||
@GET("public/events/{id}")
|
||||
fun getEvent(@Path("id") id: Long): Single<Event>
|
||||
fun getEvent(@Path("id") id: Long): Call<Event>
|
||||
|
||||
@GET("public/events/{guid}")
|
||||
fun getEventByGUID(@Path("guid") guid: String): Call<Event>
|
||||
|
||||
@GET("public/recordings/{id}")
|
||||
fun getRecording(@Path("id") id: Long): Single<Recording>
|
||||
fun getRecording(@Path("id") id: Long): Call<Recording>
|
||||
|
||||
}
|
||||
}
|
|
@ -1,11 +1,10 @@
|
|||
package de.nicidienase.chaosflix.common.mediadata.network
|
||||
|
||||
import de.nicidienase.chaosflix.common.mediadata.entities.streaming.LiveConference
|
||||
import io.reactivex.Flowable
|
||||
import io.reactivex.Single
|
||||
import retrofit2.http.GET
|
||||
|
||||
public interface StreamingService {
|
||||
interface StreamingService {
|
||||
|
||||
@GET("streams/v2.json")
|
||||
fun getStreamingConferences(): Single<List<LiveConference>>
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
package de.nicidienase.chaosflix.touch.sync
|
||||
|
||||
import android.content.Intent
|
||||
import android.support.v4.app.JobIntentService
|
||||
import de.nicidienase.chaosflix.common.mediadata.sync.Downloader
|
||||
import de.nicidienase.chaosflix.touch.ViewModelFactory
|
||||
|
||||
class DownloadJobService : JobIntentService() {
|
||||
|
||||
override fun onHandleWork(intent: Intent) {
|
||||
val downloader = Downloader(ViewModelFactory.recordingApi, ViewModelFactory.database)
|
||||
val entity: String? = intent.getStringExtra(ENTITY_KEY)
|
||||
val id: Long = intent.getLongExtra(ID_KEY, -1)
|
||||
if (entity != null) {
|
||||
when (entity) {
|
||||
// ENTITY_KEY_EVERYTHING -> downloader.updateEverything()
|
||||
ENTITY_KEY_CONFERENCES -> downloader.updateConferencesAndGroups()
|
||||
ENTITY_KEY_EVENTS -> downloader.updateEventsForConference(id)
|
||||
ENTITY_KEY_RECORDINGS -> downloader.updateRecordingsForEvent(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ENTITY_KEY: String = "entity_key"
|
||||
// val ENTITY_KEY_EVERYTHING = "everything"
|
||||
val ENTITY_KEY_CONFERENCES: String = "conferences"
|
||||
val ENTITY_KEY_EVENTS: String = "events"
|
||||
val ENTITY_KEY_RECORDINGS: String = "recodings"
|
||||
val ID_KEY: String = "id_key"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
package de.nicidienase.chaosflix.common.mediadata.sync
|
||||
|
||||
import android.arch.lifecycle.LiveData
|
||||
import android.database.sqlite.SQLiteConstraintException
|
||||
import android.util.Log
|
||||
import de.nicidienase.chaosflix.common.Util
|
||||
import de.nicidienase.chaosflix.common.mediadata.entities.MediaDatabase
|
||||
import de.nicidienase.chaosflix.common.mediadata.entities.recording.ConferencesWrapper
|
||||
import de.nicidienase.chaosflix.common.mediadata.entities.recording.Event
|
||||
import de.nicidienase.chaosflix.common.mediadata.entities.recording.Recording
|
||||
import de.nicidienase.chaosflix.common.mediadata.entities.recording.persistence.ConferenceGroup
|
||||
import de.nicidienase.chaosflix.common.mediadata.entities.recording.persistence.PersistentConference
|
||||
import de.nicidienase.chaosflix.common.mediadata.entities.recording.persistence.PersistentEvent
|
||||
import de.nicidienase.chaosflix.common.mediadata.entities.recording.persistence.PersistentRecording
|
||||
import de.nicidienase.chaosflix.common.mediadata.network.RecordingService
|
||||
import de.nicidienase.chaosflix.common.util.LiveEvent
|
||||
import de.nicidienase.chaosflix.common.util.SingleLiveEvent
|
||||
import de.nicidienase.chaosflix.common.util.ThreadHandler
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
|
||||
class Downloader(val recordingApi: RecordingService,
|
||||
val database: MediaDatabase) {
|
||||
|
||||
private val threadHandler = ThreadHandler()
|
||||
|
||||
enum class DownloaderState{
|
||||
RUNNING, DONE
|
||||
}
|
||||
|
||||
fun updateConferencesAndGroups(): LiveData<LiveEvent<DownloaderState, ConferencesWrapper, String>> {
|
||||
val updateState = SingleLiveEvent<LiveEvent<DownloaderState, ConferencesWrapper, String>>()
|
||||
threadHandler.runOnBackgroundThread {
|
||||
updateState.postValue(LiveEvent(DownloaderState.RUNNING,null, null))
|
||||
val response = recordingApi.getConferencesWrapper().execute()
|
||||
|
||||
if(!response.isSuccessful){
|
||||
updateState.postValue(LiveEvent(state = DownloaderState.DONE, error = response.message()))
|
||||
return@runOnBackgroundThread
|
||||
}
|
||||
try {
|
||||
response.body()?.let { conferencesWrapper ->
|
||||
saveConferences(conferencesWrapper)
|
||||
updateState.postValue(LiveEvent(DownloaderState.DONE,conferencesWrapper))
|
||||
}
|
||||
} catch (e: Exception){
|
||||
updateState.postValue(LiveEvent(DownloaderState.DONE, error = "Error updating conferences."))
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
return updateState
|
||||
}
|
||||
|
||||
private val TAG: String? = Downloader::class.simpleName
|
||||
|
||||
fun updateEventsForConference(conference: PersistentConference) : LiveData<LiveEvent<DownloaderState, List<PersistentEvent>, String>> {
|
||||
val updateState = SingleLiveEvent<LiveEvent<DownloaderState, List<PersistentEvent>, String>>()
|
||||
updateState.postValue(LiveEvent(DownloaderState.RUNNING))
|
||||
threadHandler.runOnBackgroundThread {
|
||||
val response = recordingApi.getConferenceByName(conference.acronym).execute()
|
||||
if(!response.isSuccessful){
|
||||
updateState.postValue(LiveEvent(DownloaderState.DONE,error = response.message()))
|
||||
return@runOnBackgroundThread
|
||||
}
|
||||
try {
|
||||
val persistentEvents = response.body()?.events?.let { events ->
|
||||
return@let saveEvents(conference, events)
|
||||
}
|
||||
updateState.postValue(LiveEvent(DownloaderState.DONE, data = persistentEvents))
|
||||
} catch (e: Exception){
|
||||
updateState.postValue(LiveEvent(DownloaderState.DONE, error = "Error updating Events for ${conference.acronym}"))
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
return updateState
|
||||
}
|
||||
|
||||
fun updateRecordingsForEvent(event: PersistentEvent) :
|
||||
LiveData<LiveEvent<DownloaderState, List<PersistentRecording>, String>> {
|
||||
val updateState = SingleLiveEvent<LiveEvent<DownloaderState, List<PersistentRecording>, String>>()
|
||||
updateState.postValue(LiveEvent(DownloaderState.RUNNING))
|
||||
threadHandler.runOnBackgroundThread {
|
||||
val response = recordingApi.getEventByGUID(event.guid).execute()
|
||||
if(!response.isSuccessful){
|
||||
updateState.postValue(LiveEvent(DownloaderState.DONE, error = response.message()))
|
||||
return@runOnBackgroundThread
|
||||
}
|
||||
try {
|
||||
val recordings = response.body()?.recordings?.let { recordings ->
|
||||
return@let saveRecordings(event, recordings)
|
||||
}
|
||||
updateState.postValue(LiveEvent(DownloaderState.DONE, data = recordings))
|
||||
} catch (e: Exception){
|
||||
updateState.postValue(LiveEvent(DownloaderState.DONE, error = "Error updating Recordings for ${event.title}"))
|
||||
e.printStackTrace()}
|
||||
}
|
||||
return updateState
|
||||
}
|
||||
|
||||
fun saveConferences(conferencesWrapper: ConferencesWrapper): List<PersistentConference> {
|
||||
return conferencesWrapper.conferencesMap.map { entry ->
|
||||
val conferenceGroup: ConferenceGroup = getOrCreateConferenceGroup(entry.key)
|
||||
val conferenceList = entry.value
|
||||
.map { PersistentConference(it) }
|
||||
.map { it.conferenceGroupId = conferenceGroup.id; it }.toTypedArray()
|
||||
val conferenceIds = database.conferenceDao().insert(*conferenceList).toList()
|
||||
return@map conferenceList.zip(conferenceIds) { conf: PersistentConference, id: Long ->
|
||||
conf.id = id
|
||||
return@zip conf
|
||||
}
|
||||
}.flatten()
|
||||
}
|
||||
|
||||
private fun getOrCreateConferenceGroup(name: String): ConferenceGroup{
|
||||
val conferenceGroup: ConferenceGroup?
|
||||
= database.conferenceGroupDao().getConferenceGroupByName(name)
|
||||
if (conferenceGroup != null) {
|
||||
return conferenceGroup
|
||||
}
|
||||
val group = ConferenceGroup(name)
|
||||
val index = Util.orderedConferencesList.indexOf(group.name)
|
||||
if (index != -1)
|
||||
group.index = index
|
||||
else if (group.name == "other conferences")
|
||||
group.index = 1_000_001
|
||||
group.id = database.conferenceGroupDao().insert(group)
|
||||
return group
|
||||
}
|
||||
|
||||
private fun saveEvents(persistentConference: PersistentConference, events: List<Event>): List<PersistentEvent> {
|
||||
val persistantEvents = events.map { PersistentEvent(it,persistentConference.id) }
|
||||
val insertEventIds = database.eventDao().insert(*(persistantEvents.toTypedArray())).asList()
|
||||
val oldEvents = database.eventDao()
|
||||
.findEventsByConferenceSync(persistentConference.id)
|
||||
.filter { !insertEventIds.contains(it.id) }
|
||||
.toTypedArray()
|
||||
try {
|
||||
database.eventDao().delete(*oldEvents)
|
||||
} catch (ex: SQLiteConstraintException){
|
||||
Log.d(TAG,"SQLiteException",ex)
|
||||
}
|
||||
persistantEvents.zip(insertEventIds) {event: PersistentEvent, id: Long ->
|
||||
event.id = id
|
||||
}
|
||||
return persistantEvents
|
||||
}
|
||||
|
||||
private fun saveRecordings(event: PersistentEvent,recordings: List<Recording>): List<PersistentRecording> {
|
||||
val persistentRecordings = recordings.map { PersistentRecording(it, event.id) }
|
||||
database.recordingDao().insert(*persistentRecordings.toTypedArray())
|
||||
return persistentRecordings
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package de.nicidienase.chaosflix.common.util
|
||||
|
||||
class LiveEvent<T,U,V>(
|
||||
val state: T,
|
||||
val data: U? = null,
|
||||
val error: V? = null)
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright 2017 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package de.nicidienase.chaosflix.common.util
|
||||
|
||||
import android.arch.lifecycle.LifecycleOwner
|
||||
import android.arch.lifecycle.MutableLiveData
|
||||
import android.arch.lifecycle.Observer
|
||||
import android.support.annotation.MainThread
|
||||
import android.util.Log
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
/**
|
||||
* A lifecycle-aware observable that sends only new updates after subscription, used for events like
|
||||
* navigation and Snackbar messages.
|
||||
*
|
||||
*
|
||||
* This avoids a common problem with events: on configuration change (like rotation) an update
|
||||
* can be emitted if the observer is active. This LiveData only calls the observable if there's an
|
||||
* explicit call to setValue() or call().
|
||||
*
|
||||
*
|
||||
* Note that only one observer is going to be notified of changes.
|
||||
*/
|
||||
class SingleLiveEvent<T> : MutableLiveData<T>() {
|
||||
|
||||
private val mPending = AtomicBoolean(false)
|
||||
|
||||
@MainThread
|
||||
override fun observe(owner: LifecycleOwner, observer: Observer<T>) {
|
||||
|
||||
if (hasActiveObservers()) {
|
||||
Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")
|
||||
}
|
||||
|
||||
// Observe the internal MutableLiveData
|
||||
super.observe(owner, Observer { t ->
|
||||
if (mPending.compareAndSet(true, false)) {
|
||||
observer.onChanged(t)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@MainThread
|
||||
override fun setValue(t: T?) {
|
||||
mPending.set(true)
|
||||
super.setValue(t)
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for cases where T is Void, to make calls cleaner.
|
||||
*/
|
||||
@MainThread
|
||||
fun call() {
|
||||
value = null
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val TAG = "SingleLiveEvent"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package de.nicidienase.chaosflix.common.util
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.HandlerThread
|
||||
import android.os.Looper
|
||||
import android.os.Process
|
||||
|
||||
class ThreadHandler {
|
||||
|
||||
private val uiThreadHandler: Handler
|
||||
private val backgroundThreadHandler: Handler
|
||||
|
||||
init {
|
||||
val handlerTread = HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND)
|
||||
handlerTread.start()
|
||||
backgroundThreadHandler = android.os.Handler(handlerTread.looper)
|
||||
uiThreadHandler = Handler(Looper.getMainLooper())
|
||||
}
|
||||
|
||||
fun runOnBackgroundThread(runnable: ()->Unit){
|
||||
backgroundThreadHandler.post(runnable)
|
||||
}
|
||||
|
||||
fun runOnMainThread(runnable: ()->Unit){
|
||||
uiThreadHandler.post(runnable)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = ThreadHandler::class.java.simpleName
|
||||
}
|
||||
}
|
5
common/src/main/res/values/urlstrings.xml
Normal file
5
common/src/main/res/values/urlstrings.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="recording_url">https://api.media.ccc.de</string>
|
||||
<string name="streaming_url">https://streaming.media.ccc.de</string>
|
||||
</resources>
|
Loading…
Reference in a new issue