pull/653/merge
Varun Patil 2023-05-13 17:46:02 -07:00
parent c7d2d78619
commit 18817f2642
4 changed files with 97 additions and 161 deletions

View File

@ -13,49 +13,62 @@ class SystemImage {
var height = 0L var height = 0L
var width = 0L var width = 0L
var size = 0L var size = 0L
var mtime = 0L
var dataPath = "" var dataPath = ""
var isVideo = false
private var mCollection: Uri = IMAGE_URI var isVideo = false
var videoDuration = 0L
val uri: Uri val uri: Uri
get() { get() {
return ContentUris.withAppendedId(mCollection, fileId) return ContentUris.withAppendedId(mCollection, fileId)
} }
private var mCollection: Uri = IMAGE_URI
companion object { companion object {
val IMAGE_URI = MediaStore.Images.Media.EXTERNAL_CONTENT_URI val IMAGE_URI = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
val VIDEO_URI = MediaStore.Video.Media.EXTERNAL_CONTENT_URI val VIDEO_URI = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
fun getByIds(ctx: Context, ids: List<Long>): List<SystemImage> { fun query(
val images = getByIdsAndCollection(ctx, IMAGE_URI, ids) ctx: Context,
if (images.size == ids.size) return images collection: Uri,
return images + getByIdsAndCollection(ctx, VIDEO_URI, ids) selection: String?,
} selectionArgs: Array<String>?,
sortOrder: String?
fun getByIdsAndCollection(ctx: Context, collection: Uri, ids: List<Long>): List<SystemImage> { ): List<SystemImage> {
val selection = MediaStore.Images.Media._ID + " IN (" + ids.joinToString(",") + ")"
val list = mutableListOf<SystemImage>() val list = mutableListOf<SystemImage>()
// Base fields common for videos and images
val projection = arrayListOf(
MediaStore.Images.Media._ID,
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.MIME_TYPE,
MediaStore.Images.Media.HEIGHT,
MediaStore.Images.Media.WIDTH,
MediaStore.Images.Media.SIZE,
MediaStore.Images.Media.DATE_TAKEN,
MediaStore.Images.Media.DATE_MODIFIED,
MediaStore.Images.Media.DATA
)
// Add video-specific fields
if (collection == VIDEO_URI) {
projection.add(MediaStore.Video.Media.DURATION)
}
// Query content resolver
ctx.contentResolver.query( ctx.contentResolver.query(
collection, collection,
arrayOf( projection.toTypedArray(),
MediaStore.Images.Media._ID,
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.MIME_TYPE,
MediaStore.Images.Media.HEIGHT,
MediaStore.Images.Media.WIDTH,
MediaStore.Images.Media.SIZE,
MediaStore.Images.Media.DATE_TAKEN,
MediaStore.Images.Media.DATA
),
selection, selection,
null, selectionArgs,
null sortOrder
).use { cursor -> ).use { cursor ->
while (cursor!!.moveToNext()) { while (cursor!!.moveToNext()) {
val image = SystemImage() val image = SystemImage()
// Common fields
image.fileId = cursor.getLong(0) image.fileId = cursor.getLong(0)
image.baseName = cursor.getString(1) image.baseName = cursor.getString(1)
image.mimeType = cursor.getString(2) image.mimeType = cursor.getString(2)
@ -63,14 +76,29 @@ class SystemImage {
image.width = cursor.getLong(4) image.width = cursor.getLong(4)
image.size = cursor.getLong(5) image.size = cursor.getLong(5)
image.dateTaken = cursor.getLong(6) image.dateTaken = cursor.getLong(6)
image.dataPath = cursor.getString(7) image.mtime = cursor.getLong(7)
image.isVideo = collection == VIDEO_URI image.dataPath = cursor.getString(8)
image.mCollection = collection image.mCollection = collection
// Video specific fields
image.isVideo = collection == VIDEO_URI
if (image.isVideo) {
image.videoDuration = cursor.getLong(9)
}
// Add to main list
list.add(image) list.add(image)
} }
} }
return list return list
} }
fun getByIds(ctx: Context, ids: List<Long>): List<SystemImage> {
val selection = MediaStore.Images.Media._ID + " IN (" + ids.joinToString(",") + ")"
val images = query(ctx, IMAGE_URI, selection, null, null)
if (images.size == ids.size) return images
return images + query(ctx, VIDEO_URI, selection, null, null)
}
} }
} }

View File

@ -4,7 +4,7 @@ import android.content.Context
import android.database.sqlite.SQLiteOpenHelper import android.database.sqlite.SQLiteOpenHelper
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase
class DbService(context: Context) : SQLiteOpenHelper(context, "memories", null, 25) { class DbService(context: Context) : SQLiteOpenHelper(context, "memories", null, 26) {
override fun onCreate(db: SQLiteDatabase) { override fun onCreate(db: SQLiteDatabase) {
db.execSQL(""" db.execSQL("""
CREATE TABLE images ( CREATE TABLE images (

View File

@ -1,6 +1,11 @@
package gallery.memories.service package gallery.memories.service
class Fields { class Fields {
object Day {
const val DAYID = Photo.DAYID
const val COUNT = "count"
}
object Photo { object Photo {
const val FILEID = "fileid" const val FILEID = "fileid"
const val BASENAME = "basename" const val BASENAME = "basename"

View File

@ -47,7 +47,7 @@ class TimelineQuery(private val mCtx: AppCompatActivity) {
@Throws(JSONException::class) @Throws(JSONException::class)
fun getByDayId(dayId: Long): JSONArray { fun getByDayId(dayId: Long): JSONArray {
// Get list of images from DB // Get list of images from DB
val imageIds: MutableSet<Long?> = ArraySet() val imageIds: MutableSet<Long> = ArraySet()
val datesTaken: MutableMap<Long, Long> = HashMap() val datesTaken: MutableMap<Long, Long> = HashMap()
val sql = "SELECT local_id, date_taken FROM images WHERE dayid = ?" val sql = "SELECT local_id, date_taken FROM images WHERE dayid = ?"
mDb.rawQuery(sql, arrayOf(dayId.toString())).use { cursor -> mDb.rawQuery(sql, arrayOf(dayId.toString())).use { cursor ->
@ -62,75 +62,26 @@ class TimelineQuery(private val mCtx: AppCompatActivity) {
if (imageIds.size == 0) return JSONArray() if (imageIds.size == 0) return JSONArray()
// Filter for given day // Filter for given day
val idColName = MediaStore.Images.Media._ID val photos = JSONArray()
val imageIdsSl = TextUtils.join(",", imageIds) SystemImage.getByIds(mCtx, imageIds.toMutableList()).forEach { image ->
val selection = "$idColName IN ($imageIdsSl)" val obj = JSONObject()
.put(Fields.Photo.FILEID, image.fileId)
.put(Fields.Photo.BASENAME, image.baseName)
.put(Fields.Photo.MIMETYPE, image.mimeType)
.put(Fields.Photo.HEIGHT, image.height)
.put(Fields.Photo.WIDTH, image.width)
.put(Fields.Photo.SIZE, image.size)
.put(Fields.Photo.ETAG, image.mtime.toString())
.put(Fields.Photo.DATETAKEN, datesTaken[image.fileId])
.put(Fields.Photo.DAYID, dayId)
// Make list of files if (image.isVideo) {
val files = ArrayList<JSONObject?>() obj.put(Fields.Photo.ISVIDEO, 1)
mCtx.contentResolver.query( .put(Fields.Photo.VIDEO_DURATION, image.videoDuration / 1000)
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
arrayOf(
MediaStore.Images.Media._ID,
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.MIME_TYPE,
MediaStore.Images.Media.HEIGHT,
MediaStore.Images.Media.WIDTH,
MediaStore.Images.Media.SIZE,
MediaStore.Images.Media.DATE_MODIFIED
),
selection,
null,
null
).use { cursor ->
while (cursor?.moveToNext() == true) {
val fileId = cursor.getLong(0)
imageIds.remove(fileId)
files.add(JSONObject()
.put(Fields.Photo.FILEID, fileId)
.put(Fields.Photo.BASENAME, cursor.getString(1))
.put(Fields.Photo.MIMETYPE, cursor.getString(2))
.put(Fields.Photo.HEIGHT, cursor.getLong(3))
.put(Fields.Photo.WIDTH, cursor.getLong(4))
.put(Fields.Photo.SIZE, cursor.getLong(5))
.put(Fields.Photo.ETAG, java.lang.Long.toString(cursor.getLong(6)))
.put(Fields.Photo.DATETAKEN, datesTaken[fileId])
.put(Fields.Photo.DAYID, dayId))
}
}
mCtx.contentResolver.query(
MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
arrayOf(
MediaStore.Video.Media._ID,
MediaStore.Video.Media.DISPLAY_NAME,
MediaStore.Video.Media.MIME_TYPE,
MediaStore.Video.Media.HEIGHT,
MediaStore.Video.Media.WIDTH,
MediaStore.Video.Media.SIZE,
MediaStore.Video.Media.DATE_MODIFIED,
MediaStore.Video.Media.DURATION
),
selection,
null,
null
).use { cursor ->
while (cursor?.moveToNext() == true) {
// Remove from list of ids
val fileId = cursor.getLong(0)
imageIds.remove(fileId)
files.add(JSONObject()
.put(Fields.Photo.FILEID, fileId)
.put(Fields.Photo.BASENAME, cursor.getString(1))
.put(Fields.Photo.MIMETYPE, cursor.getString(2))
.put(Fields.Photo.HEIGHT, cursor.getLong(3))
.put(Fields.Photo.WIDTH, cursor.getLong(4))
.put(Fields.Photo.SIZE, cursor.getLong(5))
.put(Fields.Photo.ETAG, java.lang.Long.toString(cursor.getLong(6)))
.put(Fields.Photo.DATETAKEN, datesTaken[fileId])
.put(Fields.Photo.DAYID, dayId)
.put(Fields.Photo.ISVIDEO, 1)
.put(Fields.Photo.VIDEO_DURATION, cursor.getLong(7) / 1000))
} }
photos.put(obj)
imageIds.remove(image.fileId)
} }
// Remove files that were not found // Remove files that were not found
@ -139,8 +90,7 @@ class TimelineQuery(private val mCtx: AppCompatActivity) {
mDb.execSQL("DELETE FROM images WHERE local_id IN ($delIds)") mDb.execSQL("DELETE FROM images WHERE local_id IN ($delIds)")
} }
// Return JSON string of files return photos
return JSONArray(files)
} }
@Throws(JSONException::class) @Throws(JSONException::class)
@ -151,11 +101,9 @@ class TimelineQuery(private val mCtx: AppCompatActivity) {
).use { cursor -> ).use { cursor ->
val days = JSONArray() val days = JSONArray()
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
val id = cursor.getLong(0)
val count = cursor.getLong(1)
days.put(JSONObject() days.put(JSONObject()
.put("dayid", id) .put(Fields.Day.DAYID, cursor.getLong(0))
.put("count", count) .put(Fields.Day.COUNT, cursor.getLong(1))
) )
} }
return days return days
@ -269,69 +217,22 @@ class TimelineQuery(private val mCtx: AppCompatActivity) {
private fun fullSyncDb() { private fun fullSyncDb() {
// Flag all images for removal // Flag all images for removal
mDb.execSQL("UPDATE images SET flag = 1") mDb.execSQL("UPDATE images SET flag = 1")
mCtx.contentResolver.query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, // Iterate all images and videos from system store
arrayOf( val files =
MediaStore.Images.Media._ID, SystemImage.query(mCtx, SystemImage.IMAGE_URI, null, null, null) +
MediaStore.Images.Media.DISPLAY_NAME, SystemImage.query(mCtx, SystemImage.VIDEO_URI, null, null, null)
MediaStore.Images.Media.DATE_TAKEN, files.forEach { insertItemDb(it) }
MediaStore.Images.Media.DATE_MODIFIED,
MediaStore.Images.Media.DATA
),
null,
null,
null
).use { cursor ->
while (cursor!!.moveToNext()) {
insertItemDb(
cursor.getLong(0),
cursor.getString(1),
cursor.getLong(2),
cursor.getLong(3),
cursor.getString(4),
false
)
}
}
mCtx.contentResolver.query(
MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
arrayOf(
MediaStore.Video.Media._ID,
MediaStore.Video.Media.DISPLAY_NAME,
MediaStore.Video.Media.DATE_TAKEN,
MediaStore.Video.Media.DATE_MODIFIED,
MediaStore.Video.Media.DATA
),
null,
null,
null
).use { cursor ->
while (cursor!!.moveToNext()) {
insertItemDb(
cursor.getLong(0),
cursor.getString(1),
cursor.getLong(2),
cursor.getLong(3),
cursor.getString(4),
true
)
}
}
// Clean up stale files // Clean up stale files
mDb.execSQL("DELETE FROM images WHERE flag = 1") mDb.execSQL("DELETE FROM images WHERE flag = 1")
} }
@SuppressLint("SimpleDateFormat") @SuppressLint("SimpleDateFormat")
private fun insertItemDb( private fun insertItemDb(image: SystemImage) {
id: Long, var dateTaken = image.dateTaken
name: String, val id = image.fileId
dateTaken: Long, val name = image.baseName
mtime: Long,
uri: String,
isVideo: Boolean,
) {
var dateTaken = dateTaken
// Check if file with local_id and mtime already exists // Check if file with local_id and mtime already exists
mDb.rawQuery("SELECT id FROM images WHERE local_id = ?", arrayOf(id.toString())).use { c -> mDb.rawQuery("SELECT id FROM images WHERE local_id = ?", arrayOf(id.toString())).use { c ->
@ -344,9 +245,9 @@ class TimelineQuery(private val mCtx: AppCompatActivity) {
} }
// Get EXIF date using ExifInterface if image // Get EXIF date using ExifInterface if image
if (!isVideo) { if (!image.isVideo) {
try { try {
val exif = ExifInterface(uri) val exif = ExifInterface(image.dataPath)
val exifDate = exif.getAttribute(ExifInterface.TAG_DATETIME) val exifDate = exif.getAttribute(ExifInterface.TAG_DATETIME)
?: throw IOException() ?: throw IOException()
val sdf = SimpleDateFormat("yyyy:MM:dd HH:mm:ss") val sdf = SimpleDateFormat("yyyy:MM:dd HH:mm:ss")
@ -361,7 +262,7 @@ class TimelineQuery(private val mCtx: AppCompatActivity) {
} }
// No way to get the actual local date, so just assume current timezone // No way to get the actual local date, so just assume current timezone
if (isVideo) { else { // !isVideo
dateTaken += TimeZone.getDefault().getOffset(dateTaken).toLong() dateTaken += TimeZone.getDefault().getOffset(dateTaken).toLong()
} }
@ -372,7 +273,9 @@ class TimelineQuery(private val mCtx: AppCompatActivity) {
// Delete file with same local_id and insert new one // Delete file with same local_id and insert new one
mDb.beginTransaction() mDb.beginTransaction()
mDb.execSQL("DELETE FROM images WHERE local_id = ?", arrayOf(id)) mDb.execSQL("DELETE FROM images WHERE local_id = ?", arrayOf(id))
mDb.execSQL("INSERT OR IGNORE INTO images (local_id, mtime, basename, date_taken, dayid) VALUES (?, ?, ?, ?, ?)", arrayOf(id, mtime, name, dateTaken, dayId)) mDb.execSQL("INSERT OR IGNORE INTO images (local_id, mtime, basename, date_taken, dayid) VALUES (?, ?, ?, ?, ?)", arrayOf(
id, image.mtime, name, dateTaken, dayId
))
mDb.setTransactionSuccessful() mDb.setTransactionSuccessful()
mDb.endTransaction() mDb.endTransaction()
Log.v(TAG, "Inserted file to local DB: $id / $name / $dayId") Log.v(TAG, "Inserted file to local DB: $id / $name / $dayId")