Commit c74bbdeb authored by Vladislav Bogdashkin's avatar Vladislav Bogdashkin 🎣

integrate download service

parent 696b8464
......@@ -60,6 +60,10 @@ android {
targetCompatibility 1.8
sourceCompatibility 1.8
}
lintOptions {
disable 'BinaryOperationInTimber'
}
}
kapt {
......
......@@ -19,7 +19,7 @@ interface IBigantoApi {
fun downloadFile(uri: String, headers: Map<String, String>?): Flowable<ResponseBody>
// fun getToursFiles(tour_ids: List<Int>, resolution: String): Flowable<Map<String, List<TourFileRaw>>>?
fun getTourMetaAsString(tour_id: String): Observable<String>?
fun getTourMetaAsString(tour_id: String): Observable<String>
fun getTourFiles(tour_id: String, resolution: String): Observable<List<TourFilesDataRaw>>
fun getAppVersion(): Observable<AppVersionRaw>
fun getToursPreviewById(tourIds: List<String>): Observable<List<TourPreviewRaw>>
......
......@@ -68,6 +68,12 @@ class FileModule @Inject constructor(val context: Application) {
// fun deleteFile(uri:String)= getFile(uri).delete()
fun deleteAssetFile(uri:String) =
getAssetFile(uri).delete()
fun getAssetFile(uri:String) =
File(assetsDirectory(context).plus(uri))
fun deleteAllCacheObservable() =
Observable.create<Pair<Int, Int>> {emitter ->
val foldersToDelete = listOf(
......
package com.biganto.visual.roompark.data.service.download
import com.biganto.visual.roompark.data.repository.db.requrey.RevisionString
import com.biganto.visual.roompark.data.repository.db.requrey.model.FileEntity
import com.biganto.visual.roompark.data.repository.db.requrey.model.TourFileJunctionEntity
import com.biganto.visual.roomparkvr.data.repository.db.requery.model.DownloadState
/**
* Created by Vladislav Bogdashkin on 14.04.2020.
*/
data class TourFileData(
val fileUrl: RevisionString,
val tourId: String,
var tempDownloadedSize: Long = 0L,
var tempOverallFileSize: Long = 0L,
var fileDownloadedSize: Long = 0L,
var tempTourTotalDiff: Long = 0L,
var isDownloaded: Boolean = false,
val fatalState: DownloadState? = null
) {
constructor(entity: FileEntity, junction: TourFileJunctionEntity) : this(
fileUrl = junction.file
, tourId = junction.tour
, tempDownloadedSize = 0L
, tempOverallFileSize = entity.totalSize
, fileDownloadedSize = entity.downloadedSize
, tempTourTotalDiff = 0L
, isDownloaded = entity.isDownloaded
)
}
package com.biganto.visual.roompark.data.service.download
import android.app.Service
import android.content.Context
import android.content.Intent
import android.media.MediaScannerConnection
import android.os.IBinder
import androidx.core.math.MathUtils.clamp
import androidx.lifecycle.LifecycleObserver
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.data.repository.api.biganto.IBigantoApi
import com.biganto.visual.roompark.data.repository.db.IDb
import com.biganto.visual.roompark.data.repository.db.requrey.RevisionString
import com.biganto.visual.roompark.data.repository.db.requrey.model.FileEntity
import com.biganto.visual.roompark.data.repository.db.requrey.model.TourFileJunctionEntity
import com.biganto.visual.roompark.data.repository.db.requrey.model.fromRaw
import com.biganto.visual.roompark.data.repository.file.FileModule
import com.biganto.visual.roompark.data.service.lifecycle.AppLifecycleListener
import com.biganto.visual.roompark.data.service.notification.NotificationCenter
import com.biganto.visual.roomparkvr.data.repository.db.requery.model.DownloadState
import com.biganto.visual.roomparkvr.data.repository.db.requery.model.TourPreviewEntity
import com.jakewharton.rxrelay2.PublishRelay
import io.reactivex.BackpressureStrategy
import io.reactivex.Flowable
import io.reactivex.Observable
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import okhttp3.ResponseBody
import okio.Okio
import timber.log.Timber
import java.io.File
import java.util.*
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
import javax.inject.Singleton
/**
* Created by Vladislav Bogdashkin on 13.04.2020.
*/
private const val DB_ACCESS_CHUNK_SIZE = 256
private const val READ_SYNC_MILLS = 64L
private const val DEQUE_REQUEST_TIMEOUT_MILLS=100L
private const val META_PREDICTION="/tourMeta_"
private const val META_FILE_TYPE=".json"
@Singleton
class DownloadManagerService @Inject constructor(
): Service() {
//region define dependencies
@Inject
lateinit var db: IDb
@Inject
lateinit var api: IBigantoApi
@Inject
lateinit var fileModule: FileModule
@Inject
lateinit var context: Context
@Inject
lateinit var notificationsCenter: NotificationCenter
@Inject
lateinit var appLifeCycle: AppLifecycleListener
//endregion
//region downloading flow
private val disposable = CompositeDisposable()
private val downloadQueue = ArrayDeque<String>()
private val deletingQueue = ArrayDeque<String>()
private var activeDownloading: AtomicBoolean = AtomicBoolean(false)
private fun downloadTourFiles(data: TourPreviewEntity) {
if (!downloadQueue.contains(data.id))
downloadQueue.add(data.id)
}
private fun deleteTourFiles(data: TourPreviewEntity) {
if (!deletingQueue.contains(data.id))
deletingQueue.add(data.id)
}
//endregion
init {
db = RoomParkApplication.component.providedb()
api = RoomParkApplication.component.provideBigantoApi()
context = RoomParkApplication.component.provideAppContext()
fileModule = RoomParkApplication.component.provideFileSystem()
appLifeCycle = RoomParkApplication.component.provideLifeCycle()
notificationsCenter = RoomParkApplication.component.provideNotifivations()
}
override fun onCreate() {
super.onCreate()
Timber.d("START SERVICE AND SEND NOTIFICATION")
startForeground(notificationsCenter.foregroundDownloadServiceChannelId
, notificationsCenter.foregroundDownloadServiceNotification)
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// Timber.d(" GOT INTENT $intent with action ${intent?.action}")
// if (intent?.action == NOTIFICATION_INTENT_STOP_SERVICE_ACTION)
// {
// stopForeground(false)
// stopSelf()
// }
Timber.d(" ON START COMMAND,${disposable.size()}")
if (disposable.size() == 0)
attachDownloader()
return Service.START_NOT_STICKY
}
override fun onDestroy() {
disposable.clear()
downloadQueue.forEach { id -> setTourStatus(id, DownloadState.NotDownloaded) }
downloadQueue.clear()
deletingQueue.forEach { id ->
val tour = db.getTourPreview(id).observable().blockingSubscribe { tour ->
setTourStatus(id,
if (tour.downloadedSize == tour.overallSize)
DownloadState.Downloaded
else DownloadState.Suspended
)
}
}
deletingQueue.clear()
super.onDestroy()
}
private fun setTourStatus(id:String,state:DownloadState) =
db.getTourPreview(id)
.observable()
.map { it.apply { isDownloaded = state } }
.flatMap { db.upsert(it).toObservable() }
.blockingSubscribe()
private fun writeFile(response: ResponseBody, model: TourFileData): Observable<TourFileData> {
return Observable.create<TourFileData> { sub ->
try {
if (model.fatalState == DownloadState.Crushed) {
sub.onNext(model)
sub.onComplete()
}
val fileStorage = fileModule.getFile(model.fileUrl.uri())
val sink = Okio.buffer(Okio.appendingSink(fileStorage))
val buffer = sink.buffer()
var read = 0L
val step = 4096
val source = response.source()
val timer = System.currentTimeMillis()
var stop: Boolean = false
sink.use {
while (!stop && { read = source.read(buffer, step.toLong());read }() != -1L) {
model.tempDownloadedSize += read
if (System.currentTimeMillis() - timer > READ_SYNC_MILLS || source.exhausted()) {
model.fileDownloadedSize += read
if (model.tempOverallFileSize == 0L)
model.tempTourTotalDiff += model.tempDownloadedSize
model.isDownloaded = (source.exhausted()
&& (model.fileDownloadedSize == model.tempOverallFileSize
|| model.tempOverallFileSize == 0L))
sub.onNext(model.copy())
model.tempTourTotalDiff = 0
model.tempDownloadedSize = 0
}
}
}
model.isDownloaded = (source.exhausted()
&& (model.fileDownloadedSize == model.tempOverallFileSize
|| model.tempOverallFileSize == 0L))
sub.onNext(model.copy())
model.tempTourTotalDiff = 0
model.tempDownloadedSize = 0
sub.onComplete()
sink.close()
// refreshGallery(file) //-> обновляем представление файлов в файловой системе android (чтобы их можно было просматривать через explorer и видеть размер, корректно открывать и т.п.)
} catch (e: Exception) {
Timber.e(e)
if (!sub.isDisposed)
sub.onError(e)
setTourStatus(model.tourId,DownloadState.Crushed)
}
}
}
private fun mergeFiles(files: List<FileEntity>) {
Timber.d("Merge files")
files.forEach { file ->
val entity: FileEntity? = db.getFileEntity(file.uri).firstOrNull()
entity?.let {
file.setDownloaded(it.isDownloaded)
file.setDownloadedSize(it.downloadedSize)
file.setTotalSize(it.totalSize)
}
}
Timber.d("save files: ${files.size}")
files.chunked(DB_ACCESS_CHUNK_SIZE).forEach {chunk ->
db.upsertFileEntity(chunk).blockingSubscribe { Timber.d("file saved ${chunk.size}") }
}
}
private fun setDownloadInfo(
id: String,
downloadedSize: Long? = null
, downloadedDiffSize: Long? = null
, totalSize: Long? = null
, resolution: Int? = null
, filesCount: Int? = null
, tempLoadedFiles: Int? = null
, totalSizedDiffSize: Long? = null
) =
db.getTourPreview(id).observable().map {entity ->
entity.also {
downloadedSize?.let { entity.downloadedSize = it }
downloadedDiffSize?.let { entity.downloadedSize += it }
totalSize?.let { entity.overallSize = it }
resolution?.let { entity.targetResolution = it }
filesCount?.let { entity.overallFiles = it }
tempLoadedFiles?.let { entity.downloadedFiles += it }
totalSizedDiffSize?.let { entity.overallSize += it }
if (entity.downloadedFiles == entity.overallFiles)
entity.isDownloaded = DownloadState.Downloaded
}
}.flatMap { db.upsert(it).toObservable() }
private fun flowableFilesDownloading(tour: TourPreviewEntity) =
api.getTourFiles(tour.id, tour.targetResolution.toString())
.map { it.first() }
.flatMap { raw ->
var downloadedSize = 0L
var totalSize = 0L
val fileEntities = raw.files.map(::fromRaw)
mergeFiles(fileEntities)
val jlist = db.getTourFilesJunction(tour.id).toList()
val junctionList = fileEntities
.map {file ->
val entity = jlist.firstOrNull{it.tour == tour.id && it.file == file.uri}
?: TourFileJunctionEntity()
entity.setTour(tour.id)
entity.setFile(file.uri)
downloadedSize += file.downloadedSize
totalSize += file.totalSize
entity
}
setDownloadInfo(
raw.id.toString()
, downloadedSize = downloadedSize
, totalSize = totalSize
, resolution = raw.resolution
, filesCount = raw.files.count()
).map { junctionList }
}
.flatMap{ db.upsertTourFileJunction(it) }
.flatMapIterable { it }
.flatMap { junction ->
db.getFileEntity(junction.file)
.observable()
.map { entity -> TourFileData(entity,junction) }
}
.toFlowable(BackpressureStrategy.BUFFER)
// .map(::validateFile)
.parallel(clamp(Runtime.getRuntime().availableProcessors() - 2, 2, 4))
.runOn(Schedulers.io())
.flatMap { model ->
if (model.isDownloaded) return@flatMap Flowable.just(model)
var header: HashMap<String, String>? = null
if (model.fileDownloadedSize > 0)
header = hashMapOf(Pair("Range", "bytes=${model.fileDownloadedSize}-"))
api.downloadFile(model.fileUrl.revisionUri(), header)
.doOnError {
Timber.e(it)
setTourStatus(model.tourId, DownloadState.Crushed)
}
.flatMap<TourFileData> {
writeFile(it, model)
.toFlowable(BackpressureStrategy.BUFFER)
.doOnCancel { Timber.d("CANCELLED") }
}
}
.flatMap { downloadInfo ->
db.upsertFileEntity(
FileEntity().apply {
setUri(downloadInfo.fileUrl)
setDownloadedSize(downloadInfo.fileDownloadedSize)
setTotalSize(downloadInfo.tempOverallFileSize)
setDownloaded(downloadInfo.isDownloaded)
}
)
.toFlowable(BackpressureStrategy.BUFFER)
.map { downloadInfo }
}
.sequential()
.observeOn(Schedulers.computation())
.map { model ->
setDownloadInfo(
model.tourId
, totalSizedDiffSize = model.tempTourTotalDiff
, downloadedDiffSize = model.tempDownloadedSize
, tempLoadedFiles = if (model.isDownloaded) 1 else null
)
model.tempDownloadedSize = 0
model.tourId
}
.delay(12L, TimeUnit.MILLISECONDS)
.toObservable()
private val checkService =
Observable.interval(0L, DEQUE_REQUEST_TIMEOUT_MILLS, TimeUnit.MILLISECONDS)
.filter { hasTasks }
.filter { !activeDownloading.get() }
.switchMap<String> {
when {
deletingQueue.isNotEmpty() -> {
notifyDeleting()
deleteTourFromQueue()
}
downloadQueue.isNotEmpty() -> {
notifyDownloading()
downloadTourFromQueue()
}
else -> {
Timber.e("Empty queues!");return@switchMap Observable.empty()
}
}
}
private fun downloadTourFromQueue(): Observable<String> =
Observable.fromCallable { activeDownloading.set(true);downloadQueue.poll() }
.flatMap { db.getTourPreview(it).observable() }
.filter {
val forward = it.isDownloaded == DownloadState.Downloading
activeDownloading.set(forward)
forward
}
.flatMap { tour ->
flowableFilesDownloading(tour)
.doFinally {
activeDownloading.set(false)
notifyDownloadProgress()
}
}
.map { it }
.subscribeOn(Schedulers.single())
private fun deleteTourFromQueue() =
Observable.fromCallable { activeDownloading.set(true);deletingQueue.poll() }
.flatMap { db.getTourPreview(it).observable() }
.filter {
val forward = it.isDownloaded == DownloadState.Deleting
activeDownloading.set(forward)
forward
}
.map { it.id }
.doOnNext {
deleteTourSync(it)
}
.onErrorResumeNext(Observable.empty())
.doFinally {
activeDownloading.set(false)
notifyDeleteProgress()
}
.subscribeOn(Schedulers.single())
private fun deleteTourSync(tourId:String) {
try {
db.getTourFilesJunctionUniqueFiles(tourId)
.filterNotNull()
.let { list ->
list.asSequence()
.map{ FileEntity().apply { setUri(it.file) } }
.chunked(DB_ACCESS_CHUNK_SIZE)
.forEach { db.deleteFiles( it) }
list.forEach { j -> if (!fileModule.deleteAssetFile(j.file.uri()))
Timber.w("Not success to delete file! \n" +
"short uri: ${j.file.uri()} \n" +
"full uri: ${fileModule.getAssetFile(j.file.uri())}")
}
}
val resultRowsDelete = db.deleteTourFilesJunction(tourId).value()
Timber.d("Deleted form TourFile Junction rows $resultRowsDelete")
db.deleteTourPreview(tourId)
} catch (err: java.lang.Exception) {
Timber.e(err)
error("can't delete tour")
}
}
private val hasTasks: Boolean
get() = downloadQueue.isNotEmpty() || deletingQueue.isNotEmpty()
//region Notifications
private fun notifyDownloadProgress() {
if (downloadQueue.isEmpty()) {
notificationsCenter
.completeProgressMessage(
resources.getString(R.string.on_all_tours_downloaded_notification_message)
)
if (!hasTasks) {
stopForeground(false)
stopSelf()
}
} else notifyDownloading()
}
private fun notifyDeleteProgress() {
if (deletingQueue.isEmpty()) {
notificationsCenter
.completeProgressMessage(
resources.getString(R.string.on_all_tours_deleted_notification_message)
)
if (!hasTasks) {
stopForeground(false)
stopSelf()
}
} else notifyDeleting()
}
private fun notifyDeleting() = notificationsCenter
.indeterminateProgressMessage(
progress = 1
, progressMax = (deletingQueue.size + 1)
, message =
String.format(
context.resources.getString(R.string.noty_tours_delete_left)
, deletingQueue.size)
)
private fun notifyDownloading() = notificationsCenter
.indeterminateProgressMessage(
progress = 1
, progressMax = (downloadQueue.size + 1)
, message = String.format(
context.resources.getString(R.string.noty_tours_download_left)
, downloadQueue.size)
)
//endregion Notifications
private val toursToDownloadObserver = PublishRelay.create<String>()
public fun addTourToQueue(id:String) = toursToDownloadObserver.accept(id)
private fun attachDownloader() {
disposable.add(
checkService
.subscribe {
if (!appLifeCycle.IsAppForeground
&& !hasTasks
&& !activeDownloading.get()) {
stopForeground(true)
stopSelf()
}
}
)
// disposable.add(touresCache.observableToursForDeleting().subscribe(::deleteTourFiles))
disposable.add(
toursToDownloadObserver
.flatMap { db.getTourPreview(it).observable() }
.doOnNext {
it.isDownloaded = DownloadState.Downloading
it.downloadedFiles = 0
it.downloadedSize = 0L
it.tempSize = it.overallSize // <- overall changes due downloading!!
it.overallSize = 0L
}
.observeOn(Schedulers.computation())
.flatMap(::getMeta)
.subscribeOn(Schedulers.io())
.doOnError(Timber::e)
.subscribe { downloadQueue.add(it) }
)
}
private fun getMeta(tour: TourPreviewEntity): Observable<String> =
api.getTourMetaAsString(tour.id)
.doOnNext { meta ->
tour.let {
val metaUri = RevisionString("$META_PREDICTION${tour.id}$META_FILE_TYPE")
it.setMetaFileEntityId(metaUri)
fileModule.saveFileToDisk(fileModule.getFile(metaUri.uri()), meta)
}
}
.map { tour.id }
.onErrorReturn {
setTourStatus(tour.id,DownloadState.Crushed)
tour.id
}
//#endregion oldMethod
private fun refreshGallery(file: File) {
MediaScannerConnection.scanFile(context, arrayOf(file.path), null
)
{ path, uri ->
{}//Timber.d("Scanned $path")
}
}
}
package com.biganto.visual.roompark.data.service.lifecycle
/**
* Created by Vladislav Bogdashkin on 14.04.2020.
*/
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import timber.log.Timber
import javax.inject.Singleton
@Singleton
class AppLifecycleListener : LifecycleObserver {
private var isForeground=false
val IsAppForeground : Boolean
get() = isForeground
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onMoveToForeground() {
Timber.d("Returning to foreground…")
isForeground=true
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onMoveToBackground() {
Timber.d("Moving to background…")
isForeground=false
}
}
......@@ -39,9 +39,9 @@ const val PENDING_REQUEST_CODE=0
class NotificationCenter @Inject constructor(val context: Context) {
private val updateProgressNotificationDelay_Milliseconds= 333
var lastTimeProgressNotificationUpdated = 0L
private var lastTimeProgressNotificationUpdated = 0L
val actualNotifyManager:NotificationManager
private val actualNotifyManager:NotificationManager
get()= context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
init {
......
......@@ -10,6 +10,8 @@ import com.biganto.visual.roompark.data.repository.api.room_park.IRoomParkApi
import com.biganto.visual.roompark.data.repository.db.IDb
import com.biganto.visual.roompark.data.repository.db.requrey.DbModule
import com.biganto.visual.roompark.data.repository.file.FileModule
import com.biganto.visual.roompark.data.service.lifecycle.AppLifecycleListener
import com.biganto.visual.roompark.data.service.notification.NotificationCenter
import com.biganto.visual.roompark.domain.contract.*
import dagger.BindsInstance
import dagger.Component
......@@ -64,6 +66,11 @@ interface AppComponent : AndroidInjector<RoomParkApplication>{
fun provideTour():TourContract
fun provideLifeCycle(): AppLifecycleListener
fun provideNotifivations():NotificationCenter
fun provideAppContext():Application
fun provideFileSystem(): FileModule
......
......@@ -10,6 +10,8 @@ import com.biganto.visual.roompark.data.repository.api.room_park.RetrofitReposit
import com.biganto.visual.roompark.data.repository.db.IDb
import com.biganto.visual.roompark.data.repository.db.requrey.DbModule
import com.biganto.visual.roompark.data.repository.db.requrey.RequeryRepository
import com.biganto.visual.roompark.data.service.lifecycle.AppLifecycleListener
import com.biganto.visual.roompark.data.service.notification.NotificationCenter
import com.biganto.visual.roompark.domain.contract.*
import dagger.Binds
import dagger.Component
......@@ -71,6 +73,14 @@ abstract class DataModule {
@Binds
abstract fun provideRoomParkApi(roomParkApi:RetrofitRepository): IRoomParkApi
@Singleton
@Binds
abstract fun provideLifecycleObserver(obs:AppLifecycleListener): AppLifecycleListener
@Singleton
@Binds
abstract fun provideNotyCenter(center:NotificationCenter): NotificationCenter
@Singleton
@Binds
abstract fun provideDb(db: RequeryRepository) : IDb
......
......@@ -10,6 +10,7 @@ import com.biganto.visual.roompark.data.repository.db.requrey.model.FileEntity
import com.biganto.visual.roompark.data.repository.db.requrey.model.TourFileJunctionEntity
import com.biganto.visual.roompark.data.repository.db.requrey.model.fromRaw
import com.biganto.visual.roompark.data.repository.file.FileModule
import com.biganto.visual.roompark.data.service.download.TourFileData
import com.biganto.visual.roomparkvr.data.repository.db.requery.model.DownloadState
import com.biganto.visual.roomparkvr.data.repository.db.requery.model.TourPreviewEntity
import io.reactivex.BackpressureStrategy
......@@ -142,7 +143,8 @@ class DownloadUseCase @Inject constructor(
}
private fun observableTourDownloading(tour: TourPreviewEntity, token: CancellationToken) =
private fun observableTourDownloading(tour: TourPreviewEntity, token: CancellationToken)
: Observable<TourPreviewEntity> =
api.getTourFiles(tour.id, tour.targetResolution.toString())
.map { tourDbModel = tour;it.first() }
.map { raw ->
......@@ -172,30 +174,18 @@ class DownloadUseCase @Inject constructor(
junctionList
}
.doOnNext { junctionList ->
db.upsertTourFileJunction(junctionList)?.subscribe { Timber.d("junction upserted") }
}
.doOnNext { _ ->
.flatMap{ list ->
tourDbModel?.let {
db.upsertTourPreview(it)?.subscribe { Timber.d("tour upserted") }
Observable.merge(
db.upsertTourPreview(it).map { list }, db.upsertTourFileJunction(list)
)
}
}
.flatMapIterable { it }
.flatMap { junction ->
db.getFileEntity(junction.file)
.observable()
.map { entity ->
TourFileData(
fileUrl = junction.file
, tourId = junction.tour
, tempDownloadedSize = 0L
, tempOverallFileSize = entity.totalSize
, fileDownloadedSize = entity.downloadedSize
, tempTourTotalDiff = 0L
, isDownloaded = entity.isDownloaded
)
}
.map { entity -> TourFileData(entity,junction) }
}
.toFlowable(BackpressureStrategy.BUFFER)
.parallel(4)
......@@ -205,41 +195,32 @@ class DownloadUseCase @Inject constructor(
if (model.isDownloaded)
return@flatMap Flowable.just(model)
var header: HashMap<String, String>? = null
if (model.fileDownloadedSize > 0){
header = hashMapOf(Pair("Range", "bytes=${model.fileDownloadedSize}-"))
Timber.w("trying to continue download file " +
"url by: ${model.fileUrl}" +
"size is: ${model.fileDownloadedSize}/${model.tempOverallFileSize}" +
"and header is: $header")
}
val header: HashMap<String, String>? =
if (model.fileDownloadedSize > 0)
hashMapOf(Pair("Range", "bytes=${model.fileDownloadedSize}-"))
else null
api.downloadFile(model.fileUrl.revisionUri(), header)
.doOnError {
Timber.e(it)
}
.flatMap<TourFileData> {
writeFile(it, model, token)
.toFlowable(BackpressureStrategy.BUFFER)
.doOnCancel { Timber.d("CANCELLED") }
.doOnCancel { Timber.w("TOUR DOWNLOADING CANCELLED") }
}
.flatMap { downloadInfo ->
db.upsertFileEntity(
FileEntity().also{
it.setUri(downloadInfo.fileUrl)
it.setDownloadedSize(downloadInfo.fileDownloadedSize)
it.setTotalSize(downloadInfo.tempOverallFileSize)
it.setDownloaded(downloadInfo.isDownloaded)})
FileEntity().apply {
setUri(downloadInfo.fileUrl)
setDownloadedSize(downloadInfo.fileDownloadedSize)
setTotalSize(downloadInfo.tempOverallFileSize)
setDownloaded(downloadInfo.isDownloaded)
}
)
.toFlowable(BackpressureStrategy.BUFFER)
.map { downloadInfo }
}
}
.sequential()
.toObservable()
// .buffer(15L,TimeUnit.MILLISECONDS)
// .flatMapIterable { it }
.map { model ->
setDownloadInfo(
model.tourId
......@@ -250,8 +231,7 @@ class DownloadUseCase @Inject constructor(
model.tempDownloadedSize = 0
model.tourId
}
.delay(14L, TimeUnit.MILLISECONDS)
// .sample(37L, TimeUnit.MILLISECONDS)
.delay(12L, TimeUnit.MILLISECONDS)
.flatMap { db.upsertTourPreview(tourDbModel!!) }
......@@ -273,27 +253,25 @@ class DownloadUseCase @Inject constructor(
private fun getMeta(tour: TourPreviewEntity) =
api.getTourMetaAsString(tour.id)
?.doOnNext { meta ->
tour.let {
val metaUri = RevisionString("$META_PREDICTION${tour.id}$META_FILE_TYPE")
it.setMetaFileEntityId(metaUri)
.map { meta ->
tour.apply {
val metaUri =
RevisionString("$META_PREDICTION${tour.id}$META_FILE_TYPE")
setMetaFileEntityId(metaUri)
fileModule.saveFileToDisk(
File(
FileModule.assetsDirectory(context).plus(metaUri.uri())
),meta
File(FileModule.assetsDirectory(context).plus(metaUri.uri()))
, meta
)
}
}
?.map { tour }
?.onErrorReturn {
.onErrorReturn {
tour.isDownloaded = DownloadState.Crushed
db.upsertTourPreview(tour)?.blockingSubscribe()
db.upsertTourPreview(tour).blockingSubscribe()
tour
}
//#endregion oldMethod
private fun refreshGallery(file: File) {
MediaScannerConnection.scanFile(
context, arrayOf(file.path), null
......@@ -303,15 +281,5 @@ class DownloadUseCase @Inject constructor(
}
}
data class TourFileData(
val fileUrl: RevisionString,
val tourId: String,
var tempDownloadedSize: Long = 0L,
var tempOverallFileSize: Long = 0L,
var fileDownloadedSize: Long = 0L,
var tempTourTotalDiff: Long = 0L,
var isDownloaded: Boolean = false
)
data class CancellationToken(var isCancelled: Boolean)
}
\ No newline at end of file
......@@ -112,6 +112,14 @@
<!--endregion-->
<string name="on_all_tours_downloaded_notification_message">Загрузка туров завершена</string>
<string name="on_all_tours_deleted_notification_message">Удаление туров завершено</string>
<string name="noty_tours_delete_left">Осталось удалить: %d%n</string>
<string name="noty_tours_download_left">Осталось загрузить: %d%n</string>
<string name="game_view_content_description" />
</resources>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment