Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Room Park Android
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Vladislav Bogdashkin
Room Park Android
Commits
c74bbdeb
Commit
c74bbdeb
authored
Apr 14, 2020
by
Vladislav Bogdashkin
🎣
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
integrate download service
parent
696b8464
Changes
11
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
680 additions
and
65 deletions
+680
-65
build.gradle
app/build.gradle
+4
-0
IBigantoApi.kt
...isual/roompark/data/repository/api/biganto/IBigantoApi.kt
+1
-1
FileModule.kt
...iganto/visual/roompark/data/repository/file/FileModule.kt
+6
-0
TourDownloadData.kt
...visual/roompark/data/service/download/TourDownloadData.kt
+32
-0
TourDownloadService.kt
...ual/roompark/data/service/download/TourDownloadService.kt
+547
-0
RoomParkAppLifesycleListener.kt
...rk/data/service/lifecycle/RoomParkAppLifesycleListener.kt
+33
-0
NotificationCenter.kt
.../roompark/data/service/notification/NotificationCenter.kt
+2
-2
AppComponent.kt
...ava/com/biganto/visual/roompark/di/dagger/AppComponent.kt
+7
-0
DataModule.kt
.../java/com/biganto/visual/roompark/di/dagger/DataModule.kt
+10
-0
DownloadUseCase.kt
...iganto/visual/roompark/domain/use_case/DownloadUseCase.kt
+30
-62
strings.xml
app/src/main/res/values/strings.xml
+8
-0
No files found.
app/build.gradle
View file @
c74bbdeb
...
...
@@ -60,6 +60,10 @@ android {
targetCompatibility
1.8
sourceCompatibility
1.8
}
lintOptions
{
disable
'BinaryOperationInTimber'
}
}
kapt
{
...
...
app/src/main/java/com/biganto/visual/roompark/data/repository/api/biganto/IBigantoApi.kt
View file @
c74bbdeb
...
...
@@ -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
>>
...
...
app/src/main/java/com/biganto/visual/roompark/data/repository/file/FileModule.kt
View file @
c74bbdeb
...
...
@@ -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
(
...
...
app/src/main/java/com/biganto/visual/roompark/data/service/download/TourDownloadData.kt
0 → 100644
View file @
c74bbdeb
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
)
}
app/src/main/java/com/biganto/visual/roompark/data/service/download/TourDownloadService.kt
0 → 100644
View file @
c74bbdeb
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")
}
}
}
app/src/main/java/com/biganto/visual/roompark/data/service/lifecycle/RoomParkAppLifesycleListener.kt
0 → 100644
View file @
c74bbdeb
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
}
}
app/src/main/java/com/biganto/visual/roompark/data/service/notification/NotificationCenter.kt
View file @
c74bbdeb
...
...
@@ -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
{
...
...
app/src/main/java/com/biganto/visual/roompark/di/dagger/AppComponent.kt
View file @
c74bbdeb
...
...
@@ -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
...
...
app/src/main/java/com/biganto/visual/roompark/di/dagger/DataModule.kt
View file @
c74bbdeb
...
...
@@ -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
...
...
app/src/main/java/com/biganto/visual/roompark/domain/use_case/DownloadUseCase.kt
View file @
c74bbdeb
...
...
@@ -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
app/src/main/res/values/strings.xml
View file @
c74bbdeb
...
...
@@ -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>
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment