Watch local changes

pull/653/merge
Varun Patil 2023-05-22 19:16:04 -07:00
parent 76f3a270c3
commit 79aecab377
3 changed files with 110 additions and 18 deletions

View File

@ -12,6 +12,7 @@ import android.view.WindowInsetsController
import android.webkit.*
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Lifecycle
import androidx.media3.common.MediaItem
import androidx.media3.common.util.UnstableApi
import androidx.media3.common.util.Util
@ -35,6 +36,8 @@ import gallery.memories.databinding.ActivityMainBinding
private var mediaItemIndex = 0
private var playbackPosition = 0L
private var mNeedRefresh = false
private val memoriesRegex = Regex("/apps/memories/.*$")
override fun onCreate(savedInstanceState: Bundle?) {
@ -72,6 +75,9 @@ import gallery.memories.databinding.ActivityMainBinding
if (playerUri != null && (Util.SDK_INT <= 23 || player == null)) {
initializePlayer(playerUri!!, playerUid!!)
}
if (mNeedRefresh) {
refreshTimeline(true)
}
}
public override fun onPause() {
@ -181,15 +187,28 @@ import gallery.memories.databinding.ActivityMainBinding
}
fun ensureStoragePermissions() {
val requestPermissionLauncher =
registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
if (isGranted && !hasMediaPermission()) {
val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()) { isGranted: Boolean ->
if (isGranted) {
val needFullSync = !hasMediaPermission()
// Run DB operations in separate thread
Thread {
// Full sync if this is the first time permission was granted
if (needFullSync) {
nativex.query.syncFullDb()
}
// Run delta sync and register hooks
nativex.query.initialize()
}.start()
}
// Persist that we have it now
setHasMediaPermission(isGranted)
}
// Request media read permission
requestPermissionLauncher.launch(android.Manifest.permission.READ_EXTERNAL_STORAGE)
}
@ -301,4 +320,19 @@ import gallery.memories.databinding.ActivityMainBinding
.putBoolean(getString(R.string.preferences_has_media_permission), v)
.apply()
}
fun refreshTimeline(force: Boolean = false) {
runOnUiThread {
// Check webview is loaded
if (binding?.webview?.url == null) return@runOnUiThread
// Schedule for resume if not active
if (lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED) || force) {
mNeedRefresh = false
binding.webview.evaluateJavascript("window._nc_event_bus?.emit('files:file:created')", null)
} else {
mNeedRefresh = true
}
}
}
}

View File

@ -42,11 +42,6 @@ import java.net.URLDecoder
init {
dlService = DownloadService(mCtx)
// Synchronize the database if possible
if (mCtx.hasMediaPermission()) {
query.syncDeltaDb()
}
}
companion object {
@ -55,6 +50,7 @@ import java.net.URLDecoder
fun destroy() {
dlService = null
query.destroy()
}
fun handleRequest(request: WebResourceRequest): WebResourceResponse {

View File

@ -2,9 +2,11 @@ package gallery.memories.service
import android.annotation.SuppressLint
import android.app.Activity
import android.database.ContentObserver
import android.database.sqlite.SQLiteDatabase
import android.icu.text.SimpleDateFormat
import android.icu.util.TimeZone
import android.net.Uri
import android.os.Build
import android.provider.MediaStore
import android.text.TextUtils
@ -13,9 +15,10 @@ import androidx.activity.result.ActivityResult
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.IntentSenderRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.collection.ArraySet
import androidx.exifinterface.media.ExifInterface
import androidx.media3.common.util.UnstableApi
import gallery.memories.MainActivity
import gallery.memories.R
import gallery.memories.mapper.Fields
import gallery.memories.mapper.SystemImage
@ -26,7 +29,7 @@ import java.io.IOException
import java.time.Instant
import java.util.concurrent.CountDownLatch
class TimelineQuery(private val mCtx: AppCompatActivity) {
@UnstableApi class TimelineQuery(private val mCtx: MainActivity) {
private val mDb: SQLiteDatabase = DbService(mCtx).writableDatabase
private val TAG = "TimelineQuery"
@ -38,6 +41,11 @@ class TimelineQuery(private val mCtx: AppCompatActivity) {
// Caches
var mEnabledBuckets: Set<String>? = null
// Observers
var imageObserver: ContentObserver? = null
var videoObserver: ContentObserver? = null
var refreshPending: Boolean = false
init {
// Register intent launcher for callback
deleteIntentLauncher = mCtx.registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { result: ActivityResult? ->
@ -47,6 +55,57 @@ class TimelineQuery(private val mCtx: AppCompatActivity) {
}
}
fun initialize() {
if (syncDeltaDb() > 0) {
mCtx.refreshTimeline()
}
registerHooks()
}
fun destroy() {
if (imageObserver != null) {
mCtx.contentResolver.unregisterContentObserver(imageObserver!!)
}
if (videoObserver != null) {
mCtx.contentResolver.unregisterContentObserver(videoObserver!!)
}
}
fun registerHooks() {
imageObserver = registerContentObserver(SystemImage.IMAGE_URI)
videoObserver = registerContentObserver(SystemImage.VIDEO_URI)
}
private fun registerContentObserver(uri: Uri): ContentObserver {
val observer = @UnstableApi object : ContentObserver(null) {
override fun onChange(selfChange: Boolean) {
super.onChange(selfChange)
// Debounce refreshes
synchronized(this@TimelineQuery) {
if (refreshPending) return
refreshPending = true
}
// Refresh after 750ms
Thread {
Thread.sleep(750)
synchronized(this@TimelineQuery) {
refreshPending = false
}
// Check if anything to update
if (syncDeltaDb() == 0 || mCtx.isDestroyed || mCtx.isFinishing) return@Thread
mCtx.refreshTimeline()
}.start()
}
}
mCtx.contentResolver.registerContentObserver(uri, true, observer)
return observer
}
@Throws(JSONException::class)
fun getByDayId(dayId: Long): JSONArray {
// Filter for enabled buckets
@ -230,7 +289,7 @@ class TimelineQuery(private val mCtx: AppCompatActivity) {
}
}
private fun syncDb(startTime: Long) {
private fun syncDb(startTime: Long): Int {
// Date modified is in seconds, not millis
val syncTime = Instant.now().toEpochMilli() / 1000;
@ -254,13 +313,16 @@ class TimelineQuery(private val mCtx: AppCompatActivity) {
mCtx.getSharedPreferences(mCtx.getString(R.string.preferences_key), 0).edit()
.putLong(mCtx.getString(R.string.preferences_last_sync_time), syncTime)
.apply()
// Number of updated files
return files.size
}
fun syncDeltaDb() {
fun syncDeltaDb(): Int {
// Get last sync time
val syncTime = mCtx.getSharedPreferences(mCtx.getString(R.string.preferences_key), 0)
.getLong(mCtx.getString(R.string.preferences_last_sync_time), 0L)
syncDb(syncTime)
return syncDb(syncTime)
}
fun syncFullDb() {