Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
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 {
...
@@ -60,6 +60,10 @@ android {
targetCompatibility
1.8
targetCompatibility
1.8
sourceCompatibility
1.8
sourceCompatibility
1.8
}
}
lintOptions
{
disable
'BinaryOperationInTimber'
}
}
}
kapt
{
kapt
{
...
...
app/src/main/java/com/biganto/visual/roompark/data/repository/api/biganto/IBigantoApi.kt
View file @
c74bbdeb
...
@@ -19,7 +19,7 @@ interface IBigantoApi {
...
@@ -19,7 +19,7 @@ interface IBigantoApi {
fun
downloadFile
(
uri
:
String
,
headers
:
Map
<
String
,
String
>?):
Flowable
<
ResponseBody
>
fun
downloadFile
(
uri
:
String
,
headers
:
Map
<
String
,
String
>?):
Flowable
<
ResponseBody
>
// fun getToursFiles(tour_ids: List<Int>, resolution: String): Flowable<Map<String, List<TourFileRaw>>>?
// 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
getTourFiles
(
tour_id
:
String
,
resolution
:
String
):
Observable
<
List
<
TourFilesDataRaw
>>
fun
getAppVersion
():
Observable
<
AppVersionRaw
>
fun
getAppVersion
():
Observable
<
AppVersionRaw
>
fun
getToursPreviewById
(
tourIds
:
List
<
String
>):
Observable
<
List
<
TourPreviewRaw
>>
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) {
...
@@ -68,6 +68,12 @@ class FileModule @Inject constructor(val context: Application) {
// fun deleteFile(uri:String)= getFile(uri).delete()
// 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
()
=
fun
deleteAllCacheObservable
()
=
Observable
.
create
<
Pair
<
Int
,
Int
>>
{
emitter
->
Observable
.
create
<
Pair
<
Int
,
Int
>>
{
emitter
->
val
foldersToDelete
=
listOf
(
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
...
@@ -39,9 +39,9 @@ const val PENDING_REQUEST_CODE=0
class
NotificationCenter
@Inject
constructor
(
val
context
:
Context
)
{
class
NotificationCenter
@Inject
constructor
(
val
context
:
Context
)
{
private
val
updateProgressNotificationDelay_Milliseconds
=
333
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
get
()=
context
.
getSystemService
(
Context
.
NOTIFICATION_SERVICE
)
as
NotificationManager
init
{
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
...
@@ -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.IDb
import
com.biganto.visual.roompark.data.repository.db.requrey.DbModule
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.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
com.biganto.visual.roompark.domain.contract.*
import
dagger.BindsInstance
import
dagger.BindsInstance
import
dagger.Component
import
dagger.Component
...
@@ -64,6 +66,11 @@ interface AppComponent : AndroidInjector<RoomParkApplication>{
...
@@ -64,6 +66,11 @@ interface AppComponent : AndroidInjector<RoomParkApplication>{
fun
provideTour
():
TourContract
fun
provideTour
():
TourContract
fun
provideLifeCycle
():
AppLifecycleListener
fun
provideNotifivations
():
NotificationCenter
fun
provideAppContext
():
Application
fun
provideAppContext
():
Application
fun
provideFileSystem
():
FileModule
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
...
@@ -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.IDb
import
com.biganto.visual.roompark.data.repository.db.requrey.DbModule
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.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
com.biganto.visual.roompark.domain.contract.*
import
dagger.Binds
import
dagger.Binds
import
dagger.Component
import
dagger.Component
...
@@ -71,6 +73,14 @@ abstract class DataModule {
...
@@ -71,6 +73,14 @@ abstract class DataModule {
@Binds
@Binds
abstract
fun
provideRoomParkApi
(
roomParkApi
:
RetrofitRepository
):
IRoomParkApi
abstract
fun
provideRoomParkApi
(
roomParkApi
:
RetrofitRepository
):
IRoomParkApi
@Singleton
@Binds
abstract
fun
provideLifecycleObserver
(
obs
:
AppLifecycleListener
):
AppLifecycleListener
@Singleton
@Binds
abstract
fun
provideNotyCenter
(
center
:
NotificationCenter
):
NotificationCenter
@Singleton
@Singleton
@Binds
@Binds
abstract
fun
provideDb
(
db
:
RequeryRepository
)
:
IDb
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
...
@@ -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.TourFileJunctionEntity
import
com.biganto.visual.roompark.data.repository.db.requrey.model.fromRaw
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.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.DownloadState
import
com.biganto.visual.roomparkvr.data.repository.db.requery.model.TourPreviewEntity
import
com.biganto.visual.roomparkvr.data.repository.db.requery.model.TourPreviewEntity
import
io.reactivex.BackpressureStrategy
import
io.reactivex.BackpressureStrategy
...
@@ -142,7 +143,8 @@ class DownloadUseCase @Inject constructor(
...
@@ -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
())
api
.
getTourFiles
(
tour
.
id
,
tour
.
targetResolution
.
toString
())
.
map
{
tourDbModel
=
tour
;
it
.
first
()
}
.
map
{
tourDbModel
=
tour
;
it
.
first
()
}
.
map
{
raw
->
.
map
{
raw
->
...
@@ -172,30 +174,18 @@ class DownloadUseCase @Inject constructor(
...
@@ -172,30 +174,18 @@ class DownloadUseCase @Inject constructor(
junctionList
junctionList
}
}
.
doOnNext
{
junctionList
->
.
flatMap
{
list
->
db
.
upsertTourFileJunction
(
junctionList
)
?.
subscribe
{
Timber
.
d
(
"junction upserted"
)
}
}
.
doOnNext
{
_
->
tourDbModel
?.
let
{
tourDbModel
?.
let
{
db
.
upsertTourPreview
(
it
)
?.
subscribe
{
Timber
.
d
(
"tour upserted"
)
}
Observable
.
merge
(
db
.
upsertTourPreview
(
it
).
map
{
list
},
db
.
upsertTourFileJunction
(
list
)
)
}
}
}
}
.
flatMapIterable
{
it
}
.
flatMapIterable
{
it
}
.
flatMap
{
junction
->
.
flatMap
{
junction
->
db
.
getFileEntity
(
junction
.
file
)
db
.
getFileEntity
(
junction
.
file
)
.
observable
()
.
observable
()
.
map
{
entity
->
.
map
{
entity
->
TourFileData
(
entity
,
junction
)
}
TourFileData
(
fileUrl
=
junction
.
file
,
tourId
=
junction
.
tour
,
tempDownloadedSize
=
0L
,
tempOverallFileSize
=
entity
.
totalSize
,
fileDownloadedSize
=
entity
.
downloadedSize
,
tempTourTotalDiff
=
0L
,
isDownloaded
=
entity
.
isDownloaded
)
}
}
}
.
toFlowable
(
BackpressureStrategy
.
BUFFER
)
.
toFlowable
(
BackpressureStrategy
.
BUFFER
)
.
parallel
(
4
)
.
parallel
(
4
)
...
@@ -205,41 +195,32 @@ class DownloadUseCase @Inject constructor(
...
@@ -205,41 +195,32 @@ class DownloadUseCase @Inject constructor(
if
(
model
.
isDownloaded
)
if
(
model
.
isDownloaded
)
return
@flatMap
Flowable
.
just
(
model
)
return
@flatMap
Flowable
.
just
(
model
)
var
header
:
HashMap
<
String
,
String
>?
=
null
val
header
:
HashMap
<
String
,
String
>?
=
if
(
model
.
fileDownloadedSize
>
0
){
if
(
model
.
fileDownloadedSize
>
0
)
header
=
hashMapOf
(
Pair
(
"Range"
,
"bytes=${model.fileDownloadedSize}-"
))
hashMapOf
(
Pair
(
"Range"
,
"bytes=${model.fileDownloadedSize}-"
))
Timber
.
w
(
"trying to continue download file "
+
else
null
"url by: ${model.fileUrl}"
+
"size is: ${model.fileDownloadedSize}/${model.tempOverallFileSize}"
+
"and header is: $header"
)
}
api
.
downloadFile
(
model
.
fileUrl
.
revisionUri
(),
header
)
api
.
downloadFile
(
model
.
fileUrl
.
revisionUri
(),
header
)
.
doOnError
{
Timber
.
e
(
it
)
}
.
flatMap
<
TourFileData
>
{
.
flatMap
<
TourFileData
>
{
writeFile
(
it
,
model
,
token
)
writeFile
(
it
,
model
,
token
)
.
toFlowable
(
BackpressureStrategy
.
BUFFER
)
.
toFlowable
(
BackpressureStrategy
.
BUFFER
)
.
doOnCancel
{
Timber
.
d
(
"
CANCELLED"
)
}
.
doOnCancel
{
Timber
.
w
(
"TOUR DOWNLOADING
CANCELLED"
)
}
}
}
.
flatMap
{
downloadInfo
->
.
flatMap
{
downloadInfo
->
db
.
upsertFileEntity
(
db
.
upsertFileEntity
(
FileEntity
().
also
{
FileEntity
().
apply
{
it
.
setUri
(
downloadInfo
.
fileUrl
)
setUri
(
downloadInfo
.
fileUrl
)
it
.
setDownloadedSize
(
downloadInfo
.
fileDownloadedSize
)
setDownloadedSize
(
downloadInfo
.
fileDownloadedSize
)
it
.
setTotalSize
(
downloadInfo
.
tempOverallFileSize
)
setTotalSize
(
downloadInfo
.
tempOverallFileSize
)
it
.
setDownloaded
(
downloadInfo
.
isDownloaded
)})
setDownloaded
(
downloadInfo
.
isDownloaded
)
}
)
.
toFlowable
(
BackpressureStrategy
.
BUFFER
)
.
toFlowable
(
BackpressureStrategy
.
BUFFER
)
.
map
{
downloadInfo
}
.
map
{
downloadInfo
}
}
}
}
}
.
sequential
()
.
sequential
()
.
toObservable
()
.
toObservable
()
// .buffer(15L,TimeUnit.MILLISECONDS)
// .flatMapIterable { it }
.
map
{
model
->
.
map
{
model
->
setDownloadInfo
(
setDownloadInfo
(
model
.
tourId
model
.
tourId
...
@@ -250,8 +231,7 @@ class DownloadUseCase @Inject constructor(
...
@@ -250,8 +231,7 @@ class DownloadUseCase @Inject constructor(
model
.
tempDownloadedSize
=
0
model
.
tempDownloadedSize
=
0
model
.
tourId
model
.
tourId
}
}
.
delay
(
14L
,
TimeUnit
.
MILLISECONDS
)
.
delay
(
12L
,
TimeUnit
.
MILLISECONDS
)
// .sample(37L, TimeUnit.MILLISECONDS)
.
flatMap
{
db
.
upsertTourPreview
(
tourDbModel
!!
)
}
.
flatMap
{
db
.
upsertTourPreview
(
tourDbModel
!!
)
}
...
@@ -273,27 +253,25 @@ class DownloadUseCase @Inject constructor(
...
@@ -273,27 +253,25 @@ class DownloadUseCase @Inject constructor(
private
fun
getMeta
(
tour
:
TourPreviewEntity
)
=
private
fun
getMeta
(
tour
:
TourPreviewEntity
)
=
api
.
getTourMetaAsString
(
tour
.
id
)
api
.
getTourMetaAsString
(
tour
.
id
)
?.
doOnNext
{
meta
->
.
map
{
meta
->
tour
.
let
{
tour
.
apply
{
val
metaUri
=
RevisionString
(
"$META_PREDICTION${tour.id}$META_FILE_TYPE"
)
val
metaUri
=
it
.
setMetaFileEntityId
(
metaUri
)
RevisionString
(
"$META_PREDICTION${tour.id}$META_FILE_TYPE"
)
setMetaFileEntityId
(
metaUri
)
fileModule
.
saveFileToDisk
(
fileModule
.
saveFileToDisk
(
File
(
File
(
FileModule
.
assetsDirectory
(
context
).
plus
(
metaUri
.
uri
()))
FileModule
.
assetsDirectory
(
context
).
plus
(
metaUri
.
uri
())
,
meta
),
meta
)
)
}
}
}
}
?.
map
{
tour
}
.
onErrorReturn
{
?.
onErrorReturn
{
tour
.
isDownloaded
=
DownloadState
.
Crushed
tour
.
isDownloaded
=
DownloadState
.
Crushed
db
.
upsertTourPreview
(
tour
)
?
.
blockingSubscribe
()
db
.
upsertTourPreview
(
tour
).
blockingSubscribe
()
tour
tour
}
}
//#endregion oldMethod
//#endregion oldMethod
private
fun
refreshGallery
(
file
:
File
)
{
private
fun
refreshGallery
(
file
:
File
)
{
MediaScannerConnection
.
scanFile
(
MediaScannerConnection
.
scanFile
(
context
,
arrayOf
(
file
.
path
),
null
context
,
arrayOf
(
file
.
path
),
null
...
@@ -303,15 +281,5 @@ class DownloadUseCase @Inject constructor(
...
@@ -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
)
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 @@
...
@@ -112,6 +112,14 @@
<!--endregion-->
<!--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"
/>
<string
name=
"game_view_content_description"
/>
</resources>
</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