forked from upstream-mirrors/quik
Implement logic to export messages to json
This commit is contained in:
@@ -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'
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
+2
-2
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user