Implement logic to export messages to json

This commit is contained in:
Moez Bhatti
2018-08-23 17:47:17 -04:00
parent adb6d50cad
commit cb3b1e47a1
13 changed files with 106 additions and 36 deletions
+1
View File
@@ -16,6 +16,7 @@ buildscript {
ext.ktx_version = "1.0.0-alpha1"
ext.lifecycle_version = '2.0.0-alpha1'
ext.mockito_version = '2.18.3'
ext.moshi_version = '1.6.0'
ext.realm_version = '4.3.1'
ext.realm_adapters_version = '2.1.0'
ext.rxandroid_version = '2.0.1'
+1
View File
@@ -74,6 +74,7 @@ dependencies {
implementation "androidx.core:core-ktx:$ktx_version"
implementation "com.f2prateek.rx.preferences2:rx-preferences:$rx_preferences_version"
implementation "com.jakewharton.timber:timber:$timber_version"
implementation "com.squareup.moshi:moshi:$moshi_version"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "me.leolin:ShortcutBadger:1.1.21"
implementation project(":android-smsmms")
@@ -21,36 +21,51 @@ package com.moez.QKSMS.repository
import android.animation.ObjectAnimator
import android.os.Environment
import androidx.core.animation.addListener
import com.moez.QKSMS.model.Backup
import com.moez.QKSMS.model.BackupFile
import com.moez.QKSMS.model.Message
import com.moez.QKSMS.util.QkFileObserver
import com.squareup.moshi.Moshi
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import io.reactivex.subjects.BehaviorSubject
import io.reactivex.subjects.Subject
import io.realm.Realm
import java.io.File
import java.io.FileOutputStream
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class BackupRepositoryImpl @Inject constructor() : BackupRepository {
class BackupRepositoryImpl @Inject constructor(
private val moshi: Moshi
) : BackupRepository {
companion object {
private val BACKUP_DIRECTORY = Environment.getExternalStorageDirectory().toString() + "/QKSMS/Backups"
}
data class Backup(
val messages: List<BackupMessage>
)
data class BackupMessage(
val protocol: Int,
val address: String,
val date: Long,
val type: Int,
val type: String,
val body: String,
val service_center: String?,
val serviceCenter: String?,
val read: Boolean,
val status: Int,
val locked: Boolean,
val date_sent: Long)
val dateSent: Long)
// Subjects to emit our progress events to
private val backupProgress: Subject<BackupRepository.Progress> = BehaviorSubject.createDefault(BackupRepository.Progress.Idle())
private val restoreProgress: Subject<BackupRepository.Progress> = BehaviorSubject.createDefault(BackupRepository.Progress.Idle())
@@ -58,34 +73,75 @@ class BackupRepositoryImpl @Inject constructor() : BackupRepository {
// If a backup or restore is already running, don't do anything
if (isBackupOrRestoreRunning()) return
backupProgress.onNext(BackupRepository.Progress.Running(0))
// Map all the messages into our object we'll use for the Json mapping
val backupMessages = Realm.getDefaultInstance().use { realm ->
// Get the messages from realm
val messages = realm.where(Message::class.java).findAll().createSnapshot()
val messageCount = messages.size
Thread.sleep(1000)
// Map the messages to the new format
val startTime = System.currentTimeMillis()
messages.mapIndexed { index, message ->
val progress = index.toDouble() / messageCount * 100
val elapsed = System.currentTimeMillis() - startTime
val remaining = when {
index > 100 -> TimeUnit.MILLISECONDS.toSeconds((elapsed.toDouble() / index * (messageCount - index)).toLong())
else -> 0
}
val animator = ObjectAnimator.ofInt(0, 100)
animator.duration = 3000
animator.addUpdateListener {
val remaining = TimeUnit.MILLISECONDS.toSeconds(it.duration - it.currentPlayTime) + 1
backupProgress.onNext(BackupRepository.Progress.Running(it.animatedValue as Int, "$remaining seconds remaining"))
// Update the progress
backupProgress.onNext(BackupRepository.Progress.Running(progress.toInt(), "$remaining seconds remaining"))
messageToBackupMessage(message)
}
}
animator.addListener(onEnd = {
backupProgress.onNext(BackupRepository.Progress.Idle())
})
AndroidSchedulers.mainThread().scheduleDirect { animator.start() }
// Update the status, and set the progress to be indeterminate since we can no longer calculate progress
backupProgress.onNext(BackupRepository.Progress.Running(0, "Saving..."))
// Convert the data to json
val adapter = moshi.adapter(Backup::class.java).indent("\t")
val json = adapter.toJson(Backup(backupMessages)).toByteArray()
try {
// Create the directory and file
val dir = File(BACKUP_DIRECTORY).apply { mkdirs() }
val file = File(dir, "backup-${SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault()).format(System.currentTimeMillis())}.json")
// Write the log to the file
FileOutputStream(file, true).use { fileOutputStream -> fileOutputStream.write(json) }
} catch (e: Exception) {
}
backupProgress.onNext(BackupRepository.Progress.Idle())
}
private fun messageToBackupMessage(message: Message): BackupMessage = BackupMessage(
protocol = 0,
address = message.address,
date = message.date,
type = message.type,
body = message.body,
serviceCenter = null,
read = message.read,
status = message.deliveryStatus,
locked = message.locked,
dateSent = message.dateSent)
override fun getBackupProgress(): Observable<BackupRepository.Progress> = backupProgress
override fun getBackups(): Observable<List<Backup>> = QkFileObserver(BACKUP_DIRECTORY)
.observable
.map { path -> File(path).listFiles() }
override fun getBackups(): Observable<List<BackupFile>> = QkFileObserver(BACKUP_DIRECTORY).observable
.map { File(BACKUP_DIRECTORY).listFiles() ?: arrayOf() }
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation())
.map { files ->
files.map { file ->
val date = file.lastModified()
val messages = 0
val size = file.length()
Backup(date, listOf(), size)
BackupFile(date, messages, size)
}
}
.map { files -> files.sortedByDescending { file -> file.date } }
override fun performRestore() {
// If a backup or restore is already running, don't do anything
@@ -22,7 +22,7 @@ import android.os.FileObserver
import io.reactivex.Observable
import io.reactivex.subjects.BehaviorSubject
class QkFileObserver(path: String) : FileObserver(path) {
class QkFileObserver(private val path: String) : FileObserver(path) {
private val subject = BehaviorSubject.createDefault<String>(path).toSerialized()
@@ -18,8 +18,8 @@
*/
package com.moez.QKSMS.model
data class Backup(
data class BackupFile(
val date: Long,
val messages: List<Long>,
val messages: Int,
val size: Long
)
@@ -18,7 +18,7 @@
*/
package com.moez.QKSMS.repository
import com.moez.QKSMS.model.Backup
import com.moez.QKSMS.model.BackupFile
import io.reactivex.Observable
interface BackupRepository {
@@ -35,7 +35,7 @@ interface BackupRepository {
/**
* Returns a list of all local backups
*/
fun getBackups(): Observable<List<Backup>>
fun getBackups(): Observable<List<BackupFile>>
fun performRestore()
+1
View File
@@ -180,6 +180,7 @@ dependencies {
implementation "com.f2prateek.rx.preferences2:rx-preferences:$rx_preferences_version"
implementation "com.google.android:flexbox:0.3.1"
implementation "com.jakewharton.timber:timber:$timber_version"
implementation "com.squareup.moshi:moshi:$moshi_version"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation project(":android-smsmms")
implementation project(":common")
@@ -26,7 +26,7 @@ import com.moez.QKSMS.R
import com.moez.QKSMS.common.base.FlowableAdapter
import com.moez.QKSMS.common.base.QkViewHolder
import com.moez.QKSMS.common.util.DateFormatter
import com.moez.QKSMS.model.Backup
import com.moez.QKSMS.model.BackupFile
import io.reactivex.subjects.PublishSubject
import io.reactivex.subjects.Subject
import kotlinx.android.synthetic.main.backup_list_item.view.*
@@ -35,9 +35,9 @@ import javax.inject.Inject
class BackupAdapter @Inject constructor(
private val context: Context,
private val dateFormatter: DateFormatter
) : FlowableAdapter<Backup>() {
) : FlowableAdapter<BackupFile>() {
val backupSelected: Subject<Backup> = PublishSubject.create()
val backupSelected: Subject<BackupFile> = PublishSubject.create()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
@@ -52,7 +52,7 @@ class BackupAdapter @Inject constructor(
val backup = getItem(position)
val view = holder.itemView
val count = backup.messages.size
val count = backup.messages
view.title.text = dateFormatter.getDetailedTimestamp(backup.date)
view.messages.text = context.resources.getQuantityString(R.plurals.backup_message_count, count, count)
@@ -31,7 +31,7 @@ import com.moez.QKSMS.common.util.extensions.setBackgroundTint
import com.moez.QKSMS.common.util.extensions.setTint
import com.moez.QKSMS.common.widget.PreferenceView
import com.moez.QKSMS.injection.appComponent
import com.moez.QKSMS.model.Backup
import com.moez.QKSMS.model.BackupFile
import com.moez.QKSMS.repository.BackupRepository
import io.reactivex.Observable
import kotlinx.android.synthetic.main.backup_controller.*
@@ -135,7 +135,7 @@ class BackupController : QkController<BackupView, BackupState, BackupPresenter>(
override fun restoreClicks(): Observable<*> = restore.clicks()
override fun restoreFileSelected(): Observable<Backup> = adapter.backupSelected
override fun restoreFileSelected(): Observable<BackupFile> = adapter.backupSelected
.doOnNext { dialog.dismiss() }
override fun fabClicks(): Observable<*> = fab.clicks()
@@ -27,6 +27,7 @@ import com.moez.QKSMS.repository.BackupRepository
import com.uber.autodispose.kotlin.autoDisposable
import io.reactivex.rxkotlin.plusAssign
import io.reactivex.rxkotlin.withLatestFrom
import java.util.concurrent.TimeUnit
import javax.inject.Inject
class BackupPresenter @Inject constructor(
@@ -39,9 +40,13 @@ class BackupPresenter @Inject constructor(
init {
disposables += backupRepo.getBackupProgress()
.sample(16, TimeUnit.MILLISECONDS)
.distinctUntilChanged()
.subscribe { progress -> newState { copy(backupProgress = progress) } }
disposables += backupRepo.getRestoreProgress()
.sample(16, TimeUnit.MILLISECONDS)
.distinctUntilChanged()
.subscribe { progress -> newState { copy(restoreProgress = progress) } }
disposables += backupRepo.getBackups()
@@ -18,13 +18,13 @@
*/
package com.moez.QKSMS.feature.backup
import com.moez.QKSMS.model.Backup
import com.moez.QKSMS.model.BackupFile
import com.moez.QKSMS.repository.BackupRepository
data class BackupState(
val backupProgress: BackupRepository.Progress = BackupRepository.Progress.Idle(),
val restoreProgress: BackupRepository.Progress = BackupRepository.Progress.Idle(),
val lastBackup: Long? = null,
val backups: List<Backup> = listOf(),
val backups: List<BackupFile> = listOf(),
val upgraded: Boolean = false
)
@@ -19,14 +19,13 @@
package com.moez.QKSMS.feature.backup
import com.moez.QKSMS.common.base.QkViewContract
import com.moez.QKSMS.model.Backup
import com.moez.QKSMS.model.BackupFile
import io.reactivex.Observable
import java.io.File
interface BackupView : QkViewContract<BackupState> {
fun restoreClicks(): Observable<*>
fun restoreFileSelected(): Observable<Backup>
fun restoreFileSelected(): Observable<BackupFile>
fun fabClicks(): Observable<*>
fun selectFile()
@@ -67,6 +67,7 @@ import com.moez.QKSMS.repository.ScheduledMessageRepository
import com.moez.QKSMS.repository.ScheduledMessageRepositoryImpl
import com.moez.QKSMS.repository.SyncRepository
import com.moez.QKSMS.repository.SyncRepositoryImpl
import com.squareup.moshi.Moshi
import dagger.Module
import dagger.Provides
import javax.inject.Singleton
@@ -90,6 +91,12 @@ class AppModule(private var application: Application) {
return RxSharedPreferences.create(preferences)
}
@Provides
@Singleton
fun provideMoshi(): Moshi {
return Moshi.Builder().build()
}
@Provides
fun provideViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory = factory