Implement BUID
parent
c04cc12e88
commit
55e5c05d54
|
@ -47,14 +47,14 @@ class NativeX(private val mCtx: MainActivity) {
|
||||||
val DAY = Regex("^/api/days/\\d+$")
|
val DAY = Regex("^/api/days/\\d+$")
|
||||||
|
|
||||||
val IMAGE_INFO = Regex("^/api/image/info/\\d+$")
|
val IMAGE_INFO = Regex("^/api/image/info/\\d+$")
|
||||||
val IMAGE_DELETE = Regex("^/api/image/delete/\\d+(,\\d+)*$")
|
val IMAGE_DELETE = Regex("^/api/image/delete/[0-9a-f]+(,[0-9a-f]+)*$")
|
||||||
|
|
||||||
val IMAGE_PREVIEW = Regex("^/image/preview/\\d+$")
|
val IMAGE_PREVIEW = Regex("^/image/preview/\\d+$")
|
||||||
val IMAGE_FULL = Regex("^/image/full/\\d+$")
|
val IMAGE_FULL = Regex("^/image/full/[0-9a-f]+$")
|
||||||
|
|
||||||
val SHARE_URL = Regex("^/api/share/url/.+$")
|
val SHARE_URL = Regex("^/api/share/url/.+$")
|
||||||
val SHARE_BLOB = Regex("^/api/share/blob/.+$")
|
val SHARE_BLOB = Regex("^/api/share/blob/.+$")
|
||||||
val SHARE_LOCAL = Regex("^/api/share/local/\\d+$")
|
val SHARE_LOCAL = Regex("^/api/share/local/[0-9a-f]+$")
|
||||||
|
|
||||||
val CONFIG_ALLOW_MEDIA = Regex("^/api/config/allow_media/\\d+$")
|
val CONFIG_ALLOW_MEDIA = Regex("^/api/config/allow_media/\\d+$")
|
||||||
}
|
}
|
||||||
|
@ -118,7 +118,7 @@ class NativeX(private val mCtx: MainActivity) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@JavascriptInterface
|
@JavascriptInterface
|
||||||
fun playVideo(auid: Long, fileid: Long, urlsArray: String) {
|
fun playVideo(auid: String, fileid: Long, urlsArray: String) {
|
||||||
mCtx.threadPool.submit {
|
mCtx.threadPool.submit {
|
||||||
// Get URI of remote videos
|
// Get URI of remote videos
|
||||||
val urls = JSONArray(urlsArray)
|
val urls = JSONArray(urlsArray)
|
||||||
|
@ -169,11 +169,16 @@ class NativeX(private val mCtx: MainActivity) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@JavascriptInterface
|
@JavascriptInterface
|
||||||
fun setHasRemote(auids: String, value: Boolean) {
|
fun setHasRemote(auids: String, buids: String, value: Boolean) {
|
||||||
|
Log.v(TAG, "setHasRemote: auids=$auids, buids=$buids, value=$value")
|
||||||
mCtx.threadPool.submit {
|
mCtx.threadPool.submit {
|
||||||
val parsed = JSONArray(auids)
|
val auidArray = JSONArray(auids)
|
||||||
val list = List(parsed.length()) { parsed.getLong(it) }
|
val buidArray = JSONArray(buids)
|
||||||
query.setHasRemote(list, value)
|
query.setHasRemote(
|
||||||
|
List(auidArray.length()) { auidArray.getString(it) },
|
||||||
|
List(buidArray.length()) { buidArray.getString(it) },
|
||||||
|
value
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,7 +204,7 @@ class NativeX(private val mCtx: MainActivity) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.w(TAG, "handleRequest: ", e)
|
Log.w(TAG, "handleRequest: " + e.message)
|
||||||
makeErrorResponse()
|
makeErrorResponse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,13 +243,13 @@ class NativeX(private val mCtx: MainActivity) {
|
||||||
} else if (path.matches(API.IMAGE_PREVIEW)) {
|
} else if (path.matches(API.IMAGE_PREVIEW)) {
|
||||||
makeResponse(image.getPreview(parts[3].toLong()), "image/jpeg")
|
makeResponse(image.getPreview(parts[3].toLong()), "image/jpeg")
|
||||||
} else if (path.matches(API.IMAGE_FULL)) {
|
} else if (path.matches(API.IMAGE_FULL)) {
|
||||||
makeResponse(image.getFull(parts[3].toLong()), "image/jpeg")
|
makeResponse(image.getFull(parts[3]), "image/jpeg")
|
||||||
} else if (path.matches(API.SHARE_URL)) {
|
} else if (path.matches(API.SHARE_URL)) {
|
||||||
makeResponse(dlService!!.shareUrl(URLDecoder.decode(parts[4], "UTF-8")))
|
makeResponse(dlService!!.shareUrl(URLDecoder.decode(parts[4], "UTF-8")))
|
||||||
} else if (path.matches(API.SHARE_BLOB)) {
|
} else if (path.matches(API.SHARE_BLOB)) {
|
||||||
makeResponse(dlService!!.shareBlobFromUrl(URLDecoder.decode(parts[4], "UTF-8")))
|
makeResponse(dlService!!.shareBlobFromUrl(URLDecoder.decode(parts[4], "UTF-8")))
|
||||||
} else if (path.matches(API.SHARE_LOCAL)) {
|
} else if (path.matches(API.SHARE_LOCAL)) {
|
||||||
makeResponse(dlService!!.shareLocal(parts[4].toLong()))
|
makeResponse(dlService!!.shareLocal(parts[4]))
|
||||||
} else if (path.matches(API.CONFIG_ALLOW_MEDIA)) {
|
} else if (path.matches(API.CONFIG_ALLOW_MEDIA)) {
|
||||||
permissions.setAllowMedia(true)
|
permissions.setAllowMedia(true)
|
||||||
if (permissions.requestMediaPermissionSync()) {
|
if (permissions.requestMediaPermissionSync()) {
|
||||||
|
@ -276,8 +281,8 @@ class NativeX(private val mCtx: MainActivity) {
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseIds(ids: String): List<Long> {
|
private fun parseIds(ids: String): List<String> {
|
||||||
return ids.trim().split(",").map { it.toLong() }
|
return ids.trim().split(",")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun doMediaSync(forceFull: Boolean) {
|
fun doMediaSync(forceFull: Boolean) {
|
||||||
|
|
|
@ -9,7 +9,7 @@ import gallery.memories.R
|
||||||
import gallery.memories.mapper.Photo
|
import gallery.memories.mapper.Photo
|
||||||
|
|
||||||
|
|
||||||
@Database(entities = [Photo::class], version = 11)
|
@Database(entities = [Photo::class], version = 34)
|
||||||
abstract class AppDatabase : RoomDatabase() {
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
abstract fun photoDao(): PhotoDao
|
abstract fun photoDao(): PhotoDao
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ interface PhotoDao {
|
||||||
fun getPhotosByFileIds(fileIds: List<Long>): List<Photo>
|
fun getPhotosByFileIds(fileIds: List<Long>): List<Photo>
|
||||||
|
|
||||||
@Query("SELECT * FROM photos WHERE auid IN (:auids)")
|
@Query("SELECT * FROM photos WHERE auid IN (:auids)")
|
||||||
fun getPhotosByAUIDs(auids: List<Long>): List<Photo>
|
fun getPhotosByAUIDs(auids: List<String>): List<Photo>
|
||||||
|
|
||||||
@Query("UPDATE photos SET flag=1")
|
@Query("UPDATE photos SET flag=1")
|
||||||
fun flagAll()
|
fun flagAll()
|
||||||
|
@ -42,6 +42,6 @@ interface PhotoDao {
|
||||||
@Query("SELECT bucket_id, bucket_name FROM photos GROUP BY bucket_id")
|
@Query("SELECT bucket_id, bucket_name FROM photos GROUP BY bucket_id")
|
||||||
fun getBuckets(): List<Bucket>
|
fun getBuckets(): List<Bucket>
|
||||||
|
|
||||||
@Query("UPDATE photos SET has_remote=:v WHERE auid IN (:auids)")
|
@Query("UPDATE photos SET has_remote=:v WHERE auid IN (:auids) OR buid IN (:buids)")
|
||||||
fun setHasRemote(auids: List<Long>, v: Boolean)
|
fun setHasRemote(auids: List<String>, buids: List<String>, v: Boolean)
|
||||||
}
|
}
|
|
@ -19,6 +19,7 @@ class Fields {
|
||||||
const val DATETAKEN = "datetaken"
|
const val DATETAKEN = "datetaken"
|
||||||
const val EPOCH = "epoch"
|
const val EPOCH = "epoch"
|
||||||
const val AUID = "auid"
|
const val AUID = "auid"
|
||||||
|
const val BUID = "buid"
|
||||||
const val DAYID = "dayid"
|
const val DAYID = "dayid"
|
||||||
const val ISVIDEO = "isvideo"
|
const val ISVIDEO = "isvideo"
|
||||||
const val VIDEO_DURATION = "video_duration"
|
const val VIDEO_DURATION = "video_duration"
|
||||||
|
|
|
@ -9,6 +9,7 @@ import androidx.room.PrimaryKey
|
||||||
tableName = "photos", indices = [
|
tableName = "photos", indices = [
|
||||||
Index(value = ["local_id"]),
|
Index(value = ["local_id"]),
|
||||||
Index(value = ["auid"]),
|
Index(value = ["auid"]),
|
||||||
|
Index(value = ["buid"]),
|
||||||
Index(value = ["dayid"]),
|
Index(value = ["dayid"]),
|
||||||
Index(value = ["flag"]),
|
Index(value = ["flag"]),
|
||||||
Index(value = ["bucket_id"]),
|
Index(value = ["bucket_id"]),
|
||||||
|
@ -18,7 +19,8 @@ import androidx.room.PrimaryKey
|
||||||
data class Photo(
|
data class Photo(
|
||||||
@PrimaryKey(autoGenerate = true) val id: Int? = null,
|
@PrimaryKey(autoGenerate = true) val id: Int? = null,
|
||||||
@ColumnInfo(name = "local_id") val localId: Long,
|
@ColumnInfo(name = "local_id") val localId: Long,
|
||||||
@ColumnInfo(name = "auid") val auid: Long,
|
@ColumnInfo(name = "auid") val auid: String,
|
||||||
|
@ColumnInfo(name = "buid") val buid: String,
|
||||||
@ColumnInfo(name = "mtime") val mtime: Long,
|
@ColumnInfo(name = "mtime") val mtime: Long,
|
||||||
@ColumnInfo(name = "date_taken") val dateTaken: Long,
|
@ColumnInfo(name = "date_taken") val dateTaken: Long,
|
||||||
@ColumnInfo(name = "dayid") val dayId: Long,
|
@ColumnInfo(name = "dayid") val dayId: Long,
|
||||||
|
|
|
@ -10,9 +10,11 @@ import android.util.Log
|
||||||
import androidx.exifinterface.media.ExifInterface
|
import androidx.exifinterface.media.ExifInterface
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import java.math.BigInteger
|
||||||
|
import java.security.MessageDigest
|
||||||
|
|
||||||
class SystemImage {
|
class SystemImage {
|
||||||
var fileId = 0L;
|
var fileId = 0L
|
||||||
var baseName = ""
|
var baseName = ""
|
||||||
var mimeType = ""
|
var mimeType = ""
|
||||||
var dateTaken = 0L
|
var dateTaken = 0L
|
||||||
|
@ -163,7 +165,6 @@ class SystemImage {
|
||||||
.put(Fields.Photo.SIZE, size)
|
.put(Fields.Photo.SIZE, size)
|
||||||
.put(Fields.Photo.ETAG, mtime.toString())
|
.put(Fields.Photo.ETAG, mtime.toString())
|
||||||
.put(Fields.Photo.EPOCH, epoch)
|
.put(Fields.Photo.EPOCH, epoch)
|
||||||
.put(Fields.Photo.AUID, auid)
|
|
||||||
|
|
||||||
if (isVideo) {
|
if (isVideo) {
|
||||||
obj.put(Fields.Photo.ISVIDEO, 1)
|
obj.put(Fields.Photo.ISVIDEO, 1)
|
||||||
|
@ -179,13 +180,22 @@ class SystemImage {
|
||||||
return dateTaken / 1000
|
return dateTaken / 1000
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The UTC dateTaken timestamp of the image. */
|
val exifInterface
|
||||||
val utcDate
|
get() : ExifInterface? {
|
||||||
get(): Long {
|
if (isVideo) return null
|
||||||
// Get EXIF date using ExifInterface if image
|
try {
|
||||||
if (!isVideo) {
|
return ExifInterface(dataPath)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Failed to read EXIF daddta: " + e.message)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The UTC dateTaken timestamp of the image. */
|
||||||
|
fun utcDate(exif: ExifInterface?): Long {
|
||||||
|
// Get EXIF date using ExifInterface if image
|
||||||
|
if (exif != null) {
|
||||||
try {
|
try {
|
||||||
val exif = ExifInterface(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")
|
||||||
|
@ -194,7 +204,7 @@ class SystemImage {
|
||||||
return it.time / 1000
|
return it.time / 1000
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Failed to read EXIF data: " + e.message)
|
Log.e(TAG, "Failed to read EXIF datetime: " + e.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,24 +212,38 @@ class SystemImage {
|
||||||
return (dateTaken + TimeZone.getDefault().getOffset(dateTaken).toLong()) / 1000
|
return (dateTaken + TimeZone.getDefault().getOffset(dateTaken).toLong()) / 1000
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The auid of the image. */
|
fun auid(): String {
|
||||||
val auid
|
return md5("$epoch$size")
|
||||||
get(): Long {
|
|
||||||
val crc = java.util.zip.CRC32()
|
|
||||||
|
|
||||||
// pass date taken + size as decimal string
|
|
||||||
crc.update((epoch.toString() + size.toString()).toByteArray())
|
|
||||||
|
|
||||||
return crc.value
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The database Photo object corresponding to the SystemImage. */
|
fun buid(exif: ExifInterface?): String {
|
||||||
|
var imageUniqueId = "size=$size"
|
||||||
|
if (exif != null) {
|
||||||
|
try {
|
||||||
|
val iuid = exif.getAttribute(ExifInterface.TAG_IMAGE_UNIQUE_ID)
|
||||||
|
?: throw IOException()
|
||||||
|
imageUniqueId = "iuid=$iuid"
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Failed to read EXIF unique ID ($baseName): " + e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return md5("$baseName$imageUniqueId");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The database Photo object corresponding to the SystemImage.
|
||||||
|
* This should ONLY be used for insertion into the database.
|
||||||
|
*/
|
||||||
val photo
|
val photo
|
||||||
get(): Photo {
|
get(): Photo {
|
||||||
val dateCache = utcDate
|
val exif = exifInterface
|
||||||
|
val dateCache = utcDate(exif)
|
||||||
|
|
||||||
return Photo(
|
return Photo(
|
||||||
localId = fileId,
|
localId = fileId,
|
||||||
auid = auid,
|
auid = auid(),
|
||||||
|
buid = buid(exif),
|
||||||
mtime = mtime,
|
mtime = mtime,
|
||||||
dateTaken = dateCache,
|
dateTaken = dateCache,
|
||||||
dayId = dateCache / 86400,
|
dayId = dateCache / 86400,
|
||||||
|
@ -230,4 +254,9 @@ class SystemImage {
|
||||||
hasRemote = false
|
hasRemote = false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun md5(input: String): String {
|
||||||
|
val md = MessageDigest.getInstance("MD5")
|
||||||
|
return BigInteger(1, md.digest(input.toByteArray())).toString(16).padStart(32, '0')
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -108,7 +108,7 @@ import java.util.concurrent.CountDownLatch
|
||||||
* @return True if the image was shared
|
* @return True if the image was shared
|
||||||
*/
|
*/
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
fun shareLocal(auid: Long): Boolean {
|
fun shareLocal(auid: String): Boolean {
|
||||||
val sysImgs = query.getSystemImagesByAUIDs(listOf(auid))
|
val sysImgs = query.getSystemImagesByAUIDs(listOf(auid))
|
||||||
if (sysImgs.isEmpty()) throw Exception("Image not found locally")
|
if (sysImgs.isEmpty()) throw Exception("Image not found locally")
|
||||||
val uri = sysImgs[0].uri
|
val uri = sysImgs[0].uri
|
||||||
|
|
|
@ -48,7 +48,7 @@ import java.io.ByteArrayOutputStream
|
||||||
* @return The full image as a JPEG byte array
|
* @return The full image as a JPEG byte array
|
||||||
*/
|
*/
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
fun getFull(auid: Long): ByteArray {
|
fun getFull(auid: String): ByteArray {
|
||||||
val sysImgs = query.getSystemImagesByAUIDs(listOf(auid))
|
val sysImgs = query.getSystemImagesByAUIDs(listOf(auid))
|
||||||
if (sysImgs.isEmpty()) {
|
if (sysImgs.isEmpty()) {
|
||||||
throw Exception("Image not found")
|
throw Exception("Image not found")
|
||||||
|
|
|
@ -130,7 +130,7 @@ class TimelineQuery(private val mCtx: MainActivity) {
|
||||||
* @param auids List of AUIDs
|
* @param auids List of AUIDs
|
||||||
* @return List of SystemImage
|
* @return List of SystemImage
|
||||||
*/
|
*/
|
||||||
fun getSystemImagesByAUIDs(auids: List<Long>): List<SystemImage> {
|
fun getSystemImagesByAUIDs(auids: List<String>): List<SystemImage> {
|
||||||
val photos = mPhotoDao.getPhotosByAUIDs(auids)
|
val photos = mPhotoDao.getPhotosByAUIDs(auids)
|
||||||
if (photos.isEmpty()) return listOf()
|
if (photos.isEmpty()) return listOf()
|
||||||
return SystemImage.getByIds(mCtx, photos.map { it.localId })
|
return SystemImage.getByIds(mCtx, photos.map { it.localId })
|
||||||
|
@ -157,23 +157,32 @@ class TimelineQuery(private val mCtx: MainActivity) {
|
||||||
@Throws(JSONException::class)
|
@Throws(JSONException::class)
|
||||||
fun getDay(dayId: Long): JSONArray {
|
fun getDay(dayId: Long): JSONArray {
|
||||||
// Get the photos for the day from DB
|
// Get the photos for the day from DB
|
||||||
val fileIds = mPhotoDao.getPhotosByDay(dayId, mConfigService.enabledBucketIds)
|
val photos = mPhotoDao.getPhotosByDay(dayId, mConfigService.enabledBucketIds)
|
||||||
.map { it.localId }.toMutableList()
|
.map { it.localId to it }.toMap()
|
||||||
if (fileIds.isEmpty()) return JSONArray()
|
|
||||||
|
if (photos.isEmpty()) return JSONArray()
|
||||||
|
val fileIds = photos.keys.toMutableList()
|
||||||
|
|
||||||
// Get latest metadata from system table
|
// Get latest metadata from system table
|
||||||
val photos = SystemImage.getByIds(mCtx, fileIds).map { image ->
|
val response = SystemImage.getByIds(mCtx, fileIds).map { image ->
|
||||||
// Mark file exists
|
// Mark file exists
|
||||||
fileIds.remove(image.fileId)
|
fileIds.remove(image.fileId)
|
||||||
|
|
||||||
// Add missing dayId to JSON
|
// Add missing fields to JSON
|
||||||
image.json.put(Fields.Photo.DAYID, dayId)
|
val json = image.json
|
||||||
|
photos[image.fileId]?.let { photo ->
|
||||||
|
json.put(Fields.Photo.AUID, photo.auid)
|
||||||
|
.put(Fields.Photo.BUID, photo.buid)
|
||||||
|
.put(Fields.Photo.DAYID, dayId)
|
||||||
|
}
|
||||||
|
|
||||||
|
json
|
||||||
}.let { JSONArray(it) }
|
}.let { JSONArray(it) }
|
||||||
|
|
||||||
// Remove files that were not found
|
// Remove files that were not found
|
||||||
mPhotoDao.deleteFileIds(fileIds)
|
mPhotoDao.deleteFileIds(fileIds)
|
||||||
|
|
||||||
return photos
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -222,7 +231,7 @@ class TimelineQuery(private val mCtx: MainActivity) {
|
||||||
* @return JSON response
|
* @return JSON response
|
||||||
*/
|
*/
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
fun delete(auids: List<Long>, dry: Boolean): JSONObject {
|
fun delete(auids: List<String>, dry: Boolean): JSONObject {
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
if (deleting) throw Exception("Already deleting another set of images")
|
if (deleting) throw Exception("Already deleting another set of images")
|
||||||
deleting = true
|
deleting = true
|
||||||
|
@ -405,10 +414,13 @@ class TimelineQuery(private val mCtx: MainActivity) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert to photo
|
||||||
|
val photo = image.photo
|
||||||
|
|
||||||
// Delete file with same local_id and insert new one
|
// Delete file with same local_id and insert new one
|
||||||
mPhotoDao.deleteFileIds(listOf(fileId))
|
mPhotoDao.deleteFileIds(listOf(fileId))
|
||||||
mPhotoDao.insert(image.photo)
|
mPhotoDao.insert(photo)
|
||||||
Log.v(TAG, "Inserted file to local DB: $fileId / $baseName")
|
Log.v(TAG, "Inserted file to local DB: $fileId / $baseName / $photo")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -416,8 +428,8 @@ class TimelineQuery(private val mCtx: MainActivity) {
|
||||||
* @param auids List of AUIDs
|
* @param auids List of AUIDs
|
||||||
* @param value Value to set
|
* @param value Value to set
|
||||||
*/
|
*/
|
||||||
fun setHasRemote(auids: List<Long>, value: Boolean) {
|
fun setHasRemote(auids: List<String>, buids: List<String>, value: Boolean) {
|
||||||
mPhotoDao.setHasRemote(auids, value)
|
mPhotoDao.setHasRemote(auids, buids, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue