mirror of
https://github.com/immich-app/immich.git
synced 2025-05-24 01:12:58 -04:00
make the native calls synchronous, pass datetime as long
This commit is contained in:
parent
87599daaac
commit
63ebba671b
@ -9,9 +9,6 @@ import androidx.annotation.RequiresApi
|
||||
import androidx.annotation.RequiresExtension
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.File
|
||||
import java.time.Instant
|
||||
import java.time.ZoneOffset
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
class MediaManager(context: Context) {
|
||||
private val ctx: Context = context.applicationContext
|
||||
@ -30,158 +27,140 @@ class MediaManager(context: Context) {
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.Q)
|
||||
fun shouldFullSync(callback: (Result<Boolean>) -> Unit) {
|
||||
fun shouldFullSync(): Boolean {
|
||||
val prefs = ctx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
||||
val currVersion = MediaStore.getVersion(ctx)
|
||||
val lastVersion = prefs.getString(SHARED_PREF_MEDIA_STORE_VERSION_KEY, null)
|
||||
callback(Result.success(currVersion != lastVersion))
|
||||
return MediaStore.getVersion(ctx) != prefs.getString(SHARED_PREF_MEDIA_STORE_VERSION_KEY, null)
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.Q)
|
||||
@RequiresExtension(extension = Build.VERSION_CODES.R, version = 1)
|
||||
fun checkpointSync(callback: (Result<Unit>) -> Unit) {
|
||||
fun checkpointSync() {
|
||||
val genMap =
|
||||
MediaStore.getExternalVolumeNames(ctx).associateWith { MediaStore.getGeneration(ctx, it) }
|
||||
val prefs = ctx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
||||
prefs.edit().apply {
|
||||
ctx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE).edit().apply {
|
||||
putString(SHARED_PREF_MEDIA_STORE_VERSION_KEY, MediaStore.getVersion(ctx))
|
||||
putString(SHARED_PREF_MEDIA_STORE_GEN_KEY, Json.encodeToString(genMap))
|
||||
apply()
|
||||
}
|
||||
callback(Result.success(Unit))
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.Q)
|
||||
fun getAssetIdsForAlbum(albumId: String, callback: (Result<List<String>>) -> Unit) {
|
||||
try {
|
||||
val uri = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL)
|
||||
val projection = arrayOf(MediaStore.Files.FileColumns._ID)
|
||||
val selection =
|
||||
"${MediaStore.Files.FileColumns.BUCKET_ID} = ? AND (${MediaStore.Files.FileColumns.MEDIA_TYPE} = ? OR ${MediaStore.Files.FileColumns.MEDIA_TYPE} = ?)"
|
||||
val selectionArgs = arrayOf(
|
||||
albumId,
|
||||
MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE.toString(),
|
||||
MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO.toString()
|
||||
)
|
||||
fun getAssetIdsForAlbum(albumId: String): List<String> {
|
||||
val uri = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL)
|
||||
val projection = arrayOf(MediaStore.Files.FileColumns._ID)
|
||||
val selection =
|
||||
"${MediaStore.Files.FileColumns.BUCKET_ID} = ? AND (${MediaStore.Files.FileColumns.MEDIA_TYPE} = ? OR ${MediaStore.Files.FileColumns.MEDIA_TYPE} = ?)"
|
||||
val selectionArgs = arrayOf(
|
||||
albumId,
|
||||
MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE.toString(),
|
||||
MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO.toString()
|
||||
)
|
||||
|
||||
val ids =
|
||||
ctx.contentResolver.query(uri, projection, selection, selectionArgs, null)?.use { cursor ->
|
||||
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID)
|
||||
generateSequence {
|
||||
if (cursor.moveToNext()) cursor.getLong(idColumn).toString() else null
|
||||
}.toList()
|
||||
} ?: emptyList()
|
||||
|
||||
callback(Result.success(ids))
|
||||
} catch (e: Exception) {
|
||||
callback(Result.failure(e))
|
||||
}
|
||||
return ctx.contentResolver.query(uri, projection, selection, selectionArgs, null)
|
||||
?.use { cursor ->
|
||||
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID)
|
||||
generateSequence {
|
||||
if (cursor.moveToNext()) cursor.getLong(idColumn).toString() else null
|
||||
}.toList()
|
||||
} ?: emptyList()
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
@RequiresExtension(extension = Build.VERSION_CODES.R, version = 1)
|
||||
fun getMediaChanges(callback: (Result<SyncDelta>) -> Unit) {
|
||||
try {
|
||||
val genMap = getSavedGenerationMap(ctx)
|
||||
val currentVolumes = MediaStore.getExternalVolumeNames(ctx)
|
||||
val changed = mutableListOf<Asset>()
|
||||
val deleted = mutableListOf<String>()
|
||||
val formatter = DateTimeFormatter.ISO_DATE_TIME.withZone(ZoneOffset.UTC)
|
||||
fun getMediaChanges(): SyncDelta {
|
||||
val genMap = getSavedGenerationMap(ctx)
|
||||
val currentVolumes = MediaStore.getExternalVolumeNames(ctx)
|
||||
val changed = mutableListOf<Asset>()
|
||||
val deleted = mutableListOf<String>()
|
||||
|
||||
var hasChanges = genMap.keys != currentVolumes
|
||||
for (volume in currentVolumes) {
|
||||
val currentGen = MediaStore.getGeneration(ctx, volume)
|
||||
val storedGen = genMap[volume]
|
||||
if (storedGen != null && currentGen <= storedGen) {
|
||||
continue
|
||||
}
|
||||
hasChanges = true
|
||||
var hasChanges = genMap.keys != currentVolumes
|
||||
for (volume in currentVolumes) {
|
||||
val currentGen = MediaStore.getGeneration(ctx, volume)
|
||||
val storedGen = genMap[volume]
|
||||
if (storedGen != null && currentGen <= storedGen) {
|
||||
continue
|
||||
}
|
||||
hasChanges = true
|
||||
|
||||
val uri = MediaStore.Files.getContentUri(volume)
|
||||
val projection = arrayOf(
|
||||
MediaStore.MediaColumns._ID,
|
||||
MediaStore.MediaColumns.DATA,
|
||||
MediaStore.MediaColumns.DISPLAY_NAME,
|
||||
MediaStore.MediaColumns.DATE_TAKEN,
|
||||
MediaStore.MediaColumns.DATE_ADDED,
|
||||
MediaStore.MediaColumns.DATE_MODIFIED,
|
||||
MediaStore.Files.FileColumns.MEDIA_TYPE,
|
||||
MediaStore.MediaColumns.BUCKET_ID,
|
||||
MediaStore.MediaColumns.DURATION
|
||||
)
|
||||
val uri = MediaStore.Files.getContentUri(volume)
|
||||
val projection = arrayOf(
|
||||
MediaStore.MediaColumns._ID,
|
||||
MediaStore.MediaColumns.DATA,
|
||||
MediaStore.MediaColumns.DISPLAY_NAME,
|
||||
MediaStore.MediaColumns.DATE_TAKEN,
|
||||
MediaStore.MediaColumns.DATE_ADDED,
|
||||
MediaStore.MediaColumns.DATE_MODIFIED,
|
||||
MediaStore.Files.FileColumns.MEDIA_TYPE,
|
||||
MediaStore.MediaColumns.BUCKET_ID,
|
||||
MediaStore.MediaColumns.DURATION
|
||||
)
|
||||
|
||||
val selection =
|
||||
"(${MediaStore.Files.FileColumns.MEDIA_TYPE} = ? OR ${MediaStore.Files.FileColumns.MEDIA_TYPE} = ?) AND (${MediaStore.MediaColumns.GENERATION_MODIFIED} > ? OR ${MediaStore.MediaColumns.GENERATION_ADDED} > ?)"
|
||||
val selectionArgs = arrayOf(
|
||||
MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE.toString(),
|
||||
MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO.toString(),
|
||||
storedGen?.toString() ?: "0",
|
||||
storedGen?.toString() ?: "0"
|
||||
)
|
||||
val selection =
|
||||
"(${MediaStore.Files.FileColumns.MEDIA_TYPE} = ? OR ${MediaStore.Files.FileColumns.MEDIA_TYPE} = ?) AND (${MediaStore.MediaColumns.GENERATION_MODIFIED} > ? OR ${MediaStore.MediaColumns.GENERATION_ADDED} > ?)"
|
||||
val selectionArgs = arrayOf(
|
||||
MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE.toString(),
|
||||
MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO.toString(),
|
||||
storedGen?.toString() ?: "0",
|
||||
storedGen?.toString() ?: "0"
|
||||
)
|
||||
|
||||
ctx.contentResolver.query(
|
||||
uri,
|
||||
projection,
|
||||
selection,
|
||||
selectionArgs,
|
||||
null
|
||||
)?.use { cursor ->
|
||||
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID)
|
||||
val dataColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA)
|
||||
val nameColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME)
|
||||
val dateTakenColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_TAKEN)
|
||||
val dateAddedColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_ADDED)
|
||||
val dateModifiedColumn =
|
||||
cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_MODIFIED)
|
||||
val mediaTypeColumn =
|
||||
cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.MEDIA_TYPE)
|
||||
val bucketIdColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.BUCKET_ID)
|
||||
val durationColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DURATION)
|
||||
ctx.contentResolver.query(
|
||||
uri,
|
||||
projection,
|
||||
selection,
|
||||
selectionArgs,
|
||||
null
|
||||
)?.use { cursor ->
|
||||
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID)
|
||||
val dataColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA)
|
||||
val nameColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME)
|
||||
val dateTakenColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_TAKEN)
|
||||
val dateAddedColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_ADDED)
|
||||
val dateModifiedColumn =
|
||||
cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_MODIFIED)
|
||||
val mediaTypeColumn =
|
||||
cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.MEDIA_TYPE)
|
||||
val bucketIdColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.BUCKET_ID)
|
||||
val durationColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DURATION)
|
||||
|
||||
while (cursor.moveToNext()) {
|
||||
val id = cursor.getLong(idColumn).toString()
|
||||
val path = cursor.getString(dataColumn)
|
||||
if (path.isBlank() || !File(path).exists()) {
|
||||
deleted.add(id)
|
||||
continue
|
||||
}
|
||||
|
||||
val mediaType = cursor.getInt(mediaTypeColumn)
|
||||
val name = cursor.getString(nameColumn)
|
||||
// Date taken is milliseconds since epoch, Date added is seconds since epoch
|
||||
val takenAt = cursor.getLong(dateTakenColumn).takeIf { it > 0 } ?: (cursor.getLong(
|
||||
dateAddedColumn
|
||||
) * 1000)
|
||||
val createdAt = formatter.format(Instant.ofEpochMilli(takenAt))
|
||||
// Date modified is seconds since epoch
|
||||
val modifiedAt =
|
||||
formatter.format(Instant.ofEpochMilli(cursor.getLong(dateModifiedColumn) * 1000))
|
||||
// Duration is milliseconds
|
||||
val duration =
|
||||
if (mediaType == MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE) 0 else cursor.getLong(
|
||||
durationColumn
|
||||
) / 1000
|
||||
val bucketId = cursor.getString(bucketIdColumn)
|
||||
|
||||
changed.add(
|
||||
Asset(
|
||||
id,
|
||||
name,
|
||||
mediaType.toLong(),
|
||||
createdAt,
|
||||
modifiedAt,
|
||||
duration,
|
||||
listOf(bucketId)
|
||||
)
|
||||
)
|
||||
while (cursor.moveToNext()) {
|
||||
val id = cursor.getLong(idColumn).toString()
|
||||
val path = cursor.getString(dataColumn)
|
||||
if (path.isBlank() || !File(path).exists()) {
|
||||
deleted.add(id)
|
||||
continue
|
||||
}
|
||||
|
||||
val mediaType = cursor.getInt(mediaTypeColumn)
|
||||
val name = cursor.getString(nameColumn)
|
||||
// Date taken is milliseconds since epoch, Date added is seconds since epoch
|
||||
val createdAt = (cursor.getLong(dateTakenColumn).takeIf { it > 0 }?.div(1000))
|
||||
?: cursor.getLong(dateAddedColumn)
|
||||
// Date modified is seconds since epoch
|
||||
val modifiedAt = cursor.getLong(dateModifiedColumn) * 1000
|
||||
// Duration is milliseconds
|
||||
val duration =
|
||||
if (mediaType == MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE) 0 else cursor.getLong(
|
||||
durationColumn
|
||||
) / 1000
|
||||
val bucketId = cursor.getString(bucketIdColumn)
|
||||
|
||||
changed.add(
|
||||
Asset(
|
||||
id,
|
||||
name,
|
||||
mediaType.toLong(),
|
||||
createdAt,
|
||||
modifiedAt,
|
||||
duration,
|
||||
listOf(bucketId)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
// Unmounted volumes are handled in dart when the album is removed
|
||||
|
||||
callback(Result.success(SyncDelta(hasChanges, changed, deleted)))
|
||||
} catch (e: Exception) {
|
||||
callback(Result.failure(e))
|
||||
}
|
||||
// Unmounted volumes are handled in dart when the album is removed
|
||||
|
||||
return SyncDelta(hasChanges, changed, deleted)
|
||||
}
|
||||
}
|
||||
|
@ -82,8 +82,8 @@ data class Asset (
|
||||
val id: String,
|
||||
val name: String,
|
||||
val type: Long,
|
||||
val createdAt: String? = null,
|
||||
val updatedAt: String? = null,
|
||||
val createdAt: Long? = null,
|
||||
val updatedAt: Long? = null,
|
||||
val durationInSeconds: Long,
|
||||
val albumIds: List<String>
|
||||
)
|
||||
@ -93,8 +93,8 @@ data class Asset (
|
||||
val id = pigeonVar_list[0] as String
|
||||
val name = pigeonVar_list[1] as String
|
||||
val type = pigeonVar_list[2] as Long
|
||||
val createdAt = pigeonVar_list[3] as String?
|
||||
val updatedAt = pigeonVar_list[4] as String?
|
||||
val createdAt = pigeonVar_list[3] as Long?
|
||||
val updatedAt = pigeonVar_list[4] as Long?
|
||||
val durationInSeconds = pigeonVar_list[5] as Long
|
||||
val albumIds = pigeonVar_list[6] as List<String>
|
||||
return Asset(id, name, type, createdAt, updatedAt, durationInSeconds, albumIds)
|
||||
@ -187,13 +187,12 @@ private open class MessagesPigeonCodec : StandardMessageCodec() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
||||
interface ImHostService {
|
||||
fun shouldFullSync(callback: (Result<Boolean>) -> Unit)
|
||||
fun getMediaChanges(callback: (Result<SyncDelta>) -> Unit)
|
||||
fun checkpointSync(callback: (Result<Unit>) -> Unit)
|
||||
fun getAssetIdsForAlbum(albumId: String, callback: (Result<List<String>>) -> Unit)
|
||||
fun shouldFullSync(): Boolean
|
||||
fun getMediaChanges(): SyncDelta
|
||||
fun checkpointSync()
|
||||
fun getAssetIdsForAlbum(albumId: String): List<String>
|
||||
|
||||
companion object {
|
||||
/** The codec used by ImHostService. */
|
||||
@ -209,15 +208,12 @@ interface ImHostService {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.ImHostService.shouldFullSync$separatedMessageChannelSuffix", codec)
|
||||
if (api != null) {
|
||||
channel.setMessageHandler { _, reply ->
|
||||
api.shouldFullSync{ result: Result<Boolean> ->
|
||||
val error = result.exceptionOrNull()
|
||||
if (error != null) {
|
||||
reply.reply(MessagesPigeonUtils.wrapError(error))
|
||||
} else {
|
||||
val data = result.getOrNull()
|
||||
reply.reply(MessagesPigeonUtils.wrapResult(data))
|
||||
}
|
||||
val wrapped: List<Any?> = try {
|
||||
listOf(api.shouldFullSync())
|
||||
} catch (exception: Throwable) {
|
||||
MessagesPigeonUtils.wrapError(exception)
|
||||
}
|
||||
reply.reply(wrapped)
|
||||
}
|
||||
} else {
|
||||
channel.setMessageHandler(null)
|
||||
@ -227,15 +223,12 @@ interface ImHostService {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.ImHostService.getMediaChanges$separatedMessageChannelSuffix", codec, taskQueue)
|
||||
if (api != null) {
|
||||
channel.setMessageHandler { _, reply ->
|
||||
api.getMediaChanges{ result: Result<SyncDelta> ->
|
||||
val error = result.exceptionOrNull()
|
||||
if (error != null) {
|
||||
reply.reply(MessagesPigeonUtils.wrapError(error))
|
||||
} else {
|
||||
val data = result.getOrNull()
|
||||
reply.reply(MessagesPigeonUtils.wrapResult(data))
|
||||
}
|
||||
val wrapped: List<Any?> = try {
|
||||
listOf(api.getMediaChanges())
|
||||
} catch (exception: Throwable) {
|
||||
MessagesPigeonUtils.wrapError(exception)
|
||||
}
|
||||
reply.reply(wrapped)
|
||||
}
|
||||
} else {
|
||||
channel.setMessageHandler(null)
|
||||
@ -245,14 +238,13 @@ interface ImHostService {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.ImHostService.checkpointSync$separatedMessageChannelSuffix", codec)
|
||||
if (api != null) {
|
||||
channel.setMessageHandler { _, reply ->
|
||||
api.checkpointSync{ result: Result<Unit> ->
|
||||
val error = result.exceptionOrNull()
|
||||
if (error != null) {
|
||||
reply.reply(MessagesPigeonUtils.wrapError(error))
|
||||
} else {
|
||||
reply.reply(MessagesPigeonUtils.wrapResult(null))
|
||||
}
|
||||
val wrapped: List<Any?> = try {
|
||||
api.checkpointSync()
|
||||
listOf(null)
|
||||
} catch (exception: Throwable) {
|
||||
MessagesPigeonUtils.wrapError(exception)
|
||||
}
|
||||
reply.reply(wrapped)
|
||||
}
|
||||
} else {
|
||||
channel.setMessageHandler(null)
|
||||
@ -264,15 +256,12 @@ interface ImHostService {
|
||||
channel.setMessageHandler { message, reply ->
|
||||
val args = message as List<Any?>
|
||||
val albumIdArg = args[0] as String
|
||||
api.getAssetIdsForAlbum(albumIdArg) { result: Result<List<String>> ->
|
||||
val error = result.exceptionOrNull()
|
||||
if (error != null) {
|
||||
reply.reply(MessagesPigeonUtils.wrapError(error))
|
||||
} else {
|
||||
val data = result.getOrNull()
|
||||
reply.reply(MessagesPigeonUtils.wrapResult(data))
|
||||
}
|
||||
val wrapped: List<Any?> = try {
|
||||
listOf(api.getAssetIdsForAlbum(albumIdArg))
|
||||
} catch (exception: Throwable) {
|
||||
MessagesPigeonUtils.wrapError(exception)
|
||||
}
|
||||
reply.reply(wrapped)
|
||||
}
|
||||
} else {
|
||||
channel.setMessageHandler(null)
|
||||
|
@ -20,38 +20,27 @@ class MessagesImpl(context: Context) : ImHostService {
|
||||
IllegalStateException("Method not supported on this Android version.")
|
||||
}
|
||||
|
||||
override fun shouldFullSync(callback: (Result<Boolean>) -> Unit) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
mediaManager.shouldFullSync(callback)
|
||||
} else {
|
||||
callback(Result.success(true))
|
||||
override fun shouldFullSync(): Boolean =
|
||||
Build.VERSION.SDK_INT < Build.VERSION_CODES.Q || mediaManager.shouldFullSync()
|
||||
|
||||
override fun getMediaChanges(): SyncDelta {
|
||||
if (!isMediaChangesSupported()) {
|
||||
throw unsupportedFeatureException()
|
||||
}
|
||||
return mediaManager.getMediaChanges()
|
||||
}
|
||||
|
||||
override fun getMediaChanges(callback: (Result<SyncDelta>) -> Unit) {
|
||||
if (isMediaChangesSupported()) {
|
||||
mediaManager.getMediaChanges(callback)
|
||||
} else {
|
||||
callback(Result.failure(unsupportedFeatureException()))
|
||||
override fun checkpointSync() {
|
||||
if (!isMediaChangesSupported()) {
|
||||
return
|
||||
}
|
||||
mediaManager.checkpointSync()
|
||||
}
|
||||
|
||||
override fun checkpointSync(callback: (Result<Unit>) -> Unit) {
|
||||
if (isMediaChangesSupported()) {
|
||||
mediaManager.checkpointSync(callback)
|
||||
} else {
|
||||
callback(Result.success(Unit))
|
||||
}
|
||||
}
|
||||
|
||||
override fun getAssetIdsForAlbum(
|
||||
albumId: String,
|
||||
callback: (Result<List<String>>) -> Unit
|
||||
) {
|
||||
if (isMediaChangesSupported()) {
|
||||
mediaManager.getAssetIdsForAlbum(albumId, callback)
|
||||
} else {
|
||||
callback(Result.failure(unsupportedFeatureException()))
|
||||
override fun getAssetIdsForAlbum(albumId: String): List<String> {
|
||||
if (!isMediaChangesSupported()) {
|
||||
throw unsupportedFeatureException()
|
||||
}
|
||||
return mediaManager.getAssetIdsForAlbum(albumId)
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Photos
|
||||
|
||||
class WrapperAsset: Hashable, Equatable {
|
||||
var asset: Asset
|
||||
struct AssetWrapper: Hashable, Equatable {
|
||||
let asset: Asset
|
||||
|
||||
init(with asset: Asset) {
|
||||
self.asset = asset
|
||||
@ -11,22 +11,22 @@ class WrapperAsset: Hashable, Equatable {
|
||||
hasher.combine(self.asset.id)
|
||||
}
|
||||
|
||||
static func == (lhs: WrapperAsset, rhs: WrapperAsset) -> Bool {
|
||||
static func == (lhs: AssetWrapper, rhs: AssetWrapper) -> Bool {
|
||||
return lhs.asset.id == rhs.asset.id
|
||||
}
|
||||
}
|
||||
|
||||
class MediaManager {
|
||||
private let _defaults: UserDefaults
|
||||
private let _changeTokenKey = "immich:changeToken"
|
||||
private let defaults: UserDefaults
|
||||
private let changeTokenKey = "immich:changeToken"
|
||||
|
||||
init(with defaults: UserDefaults = .standard) {
|
||||
self._defaults = defaults
|
||||
self.defaults = defaults
|
||||
}
|
||||
|
||||
@available(iOS 16, *)
|
||||
func _getChangeToken() -> PHPersistentChangeToken? {
|
||||
guard let encodedToken = _defaults.data(forKey: _changeTokenKey) else {
|
||||
private func getChangeToken() -> PHPersistentChangeToken? {
|
||||
guard let encodedToken = defaults.data(forKey: changeTokenKey) else {
|
||||
print("MediaManager::_getChangeToken: Change token not available in UserDefaults")
|
||||
return nil
|
||||
}
|
||||
@ -40,10 +40,10 @@ class MediaManager {
|
||||
}
|
||||
|
||||
@available(iOS 16, *)
|
||||
func _saveChangeToken(token: PHPersistentChangeToken) -> Void {
|
||||
private func saveChangeToken(token: PHPersistentChangeToken) -> Void {
|
||||
do {
|
||||
let encodedToken = try NSKeyedArchiver.archivedData(withRootObject: token, requiringSecureCoding: true)
|
||||
_defaults.set(encodedToken, forKey: _changeTokenKey)
|
||||
defaults.set(encodedToken, forKey: changeTokenKey)
|
||||
print("MediaManager::_setChangeToken: Change token saved to UserDefaults")
|
||||
} catch {
|
||||
print("MediaManager::_setChangeToken: Failed to persist the token to UserDefaults: \(error)")
|
||||
@ -51,109 +51,96 @@ class MediaManager {
|
||||
}
|
||||
|
||||
@available(iOS 16, *)
|
||||
func checkpointSync(completion: @escaping (Result<Void, any Error>) -> Void) {
|
||||
_saveChangeToken(token: PHPhotoLibrary.shared().currentChangeToken)
|
||||
completion(.success(()))
|
||||
func checkpointSync() {
|
||||
saveChangeToken(token: PHPhotoLibrary.shared().currentChangeToken)
|
||||
}
|
||||
|
||||
@available(iOS 16, *)
|
||||
func shouldFullSync(completion: @escaping (Result<Bool, Error>) -> Void) {
|
||||
func shouldFullSync() -> Bool {
|
||||
guard PHPhotoLibrary.authorizationStatus(for: .readWrite) == .authorized else {
|
||||
// When we do not have access to photo library, return true to fallback to old sync
|
||||
completion(.success(true))
|
||||
return
|
||||
return true
|
||||
}
|
||||
|
||||
guard let storedToken = _getChangeToken() else {
|
||||
guard let storedToken = getChangeToken() else {
|
||||
// No token exists, perform the initial full sync
|
||||
print("MediaManager::shouldUseOldSync: No token found. Full sync required")
|
||||
completion(.success(true))
|
||||
return
|
||||
return true
|
||||
}
|
||||
|
||||
do {
|
||||
_ = try PHPhotoLibrary.shared().fetchPersistentChanges(since: storedToken)
|
||||
completion(.success(false))
|
||||
return false
|
||||
} catch {
|
||||
// fallback to using old sync when we cannot detect changes using the available token
|
||||
print("MediaManager::shouldUseOldSync: fetchPersistentChanges failed with error (\(error))")
|
||||
completion(.success(true))
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@available(iOS 16, *)
|
||||
func getMediaChanges(completion: @escaping (Result<SyncDelta, Error>) -> Void) {
|
||||
func getMediaChanges() throws -> SyncDelta {
|
||||
guard PHPhotoLibrary.authorizationStatus(for: .readWrite) == .authorized else {
|
||||
completion(.failure(PigeonError(code: "NO_AUTH", message: "No photo library access", details: nil)))
|
||||
return
|
||||
throw PigeonError(code: "NO_AUTH", message: "No photo library access", details: nil)
|
||||
}
|
||||
|
||||
guard let storedToken = _getChangeToken() else {
|
||||
guard let storedToken = getChangeToken() else {
|
||||
// No token exists, definitely need a full sync
|
||||
print("MediaManager::getMediaChanges: No token found")
|
||||
completion(.failure(PigeonError(code: "NO_TOKEN", message: "No stored change token", details: nil)))
|
||||
return
|
||||
throw PigeonError(code: "NO_TOKEN", message: "No stored change token", details: nil)
|
||||
}
|
||||
|
||||
let currentToken = PHPhotoLibrary.shared().currentChangeToken
|
||||
if storedToken == currentToken {
|
||||
completion(.success(SyncDelta(hasChanges: false, updates: [], deletes: [])))
|
||||
return
|
||||
return SyncDelta(hasChanges: false, updates: [], deletes: [])
|
||||
}
|
||||
|
||||
do {
|
||||
let result = try PHPhotoLibrary.shared().fetchPersistentChanges(since: storedToken)
|
||||
let dateFormatter = ISO8601DateFormatter()
|
||||
dateFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
||||
|
||||
var updatedArr: Set<WrapperAsset> = []
|
||||
var deletedArr: Set<String> = []
|
||||
|
||||
for changes in result {
|
||||
let details = try changes.changeDetails(for: PHObjectType.asset)
|
||||
|
||||
let updated = details.updatedLocalIdentifiers.union(details.insertedLocalIdentifiers)
|
||||
let deleted = details.deletedLocalIdentifiers
|
||||
let changes = try PHPhotoLibrary.shared().fetchPersistentChanges(since: storedToken)
|
||||
|
||||
let options = PHFetchOptions()
|
||||
options.includeHiddenAssets = false
|
||||
var updatedAssets: Set<AssetWrapper> = []
|
||||
var deletedAssets: Set<String> = []
|
||||
|
||||
for change in changes {
|
||||
guard let details = try? change.changeDetails(for: PHObjectType.asset) else { continue }
|
||||
|
||||
let updatedAssets = PHAsset.fetchAssets(withLocalIdentifiers: Array(updated), options: options)
|
||||
|
||||
updatedAssets.enumerateObjects { (asset, _, _) in
|
||||
|
||||
let updated = details.updatedLocalIdentifiers.union(details.insertedLocalIdentifiers)
|
||||
if (updated.isEmpty) { continue }
|
||||
|
||||
let result = PHAsset.fetchAssets(withLocalIdentifiers: Array(updated), options: nil)
|
||||
for i in 0..<result.count {
|
||||
let asset = result.object(at: i)
|
||||
|
||||
// Asset wrapper only uses the id for comparison. Multiple change can contain the same asset, skip duplicate changes
|
||||
let predicate = Asset(id: asset.localIdentifier, name: "", type: 0, createdAt: nil, updatedAt: nil, durationInSeconds: 0, albumIds: [])
|
||||
if (updatedAssets.contains(AssetWrapper(with: predicate))) {
|
||||
continue
|
||||
}
|
||||
|
||||
let id = asset.localIdentifier
|
||||
let name = PHAssetResource.assetResources(for: asset).first?.originalFilename ?? asset.title()
|
||||
let type: Int64 = Int64(asset.mediaType.rawValue)
|
||||
let createdAt = asset.creationDate.map { dateFormatter.string(from: $0) }
|
||||
let updatedAt = asset.modificationDate.map { dateFormatter.string(from: $0) }
|
||||
let createdAt = asset.creationDate?.timeIntervalSince1970
|
||||
let updatedAt = asset.modificationDate?.timeIntervalSince1970
|
||||
let durationInSeconds: Int64 = Int64(asset.duration)
|
||||
|
||||
let domainAsset = WrapperAsset(with: Asset(
|
||||
id: id,
|
||||
name: name,
|
||||
type: type,
|
||||
createdAt: createdAt,
|
||||
updatedAt: updatedAt,
|
||||
durationInSeconds: durationInSeconds,
|
||||
albumIds: self._getAlbumIds(forAsset: asset)
|
||||
let domainAsset = AssetWrapper(with: Asset(
|
||||
id: id,
|
||||
name: name,
|
||||
type: type,
|
||||
createdAt: createdAt.map { Int64($0) },
|
||||
updatedAt: updatedAt.map { Int64($0) },
|
||||
durationInSeconds: durationInSeconds,
|
||||
albumIds: self._getAlbumIds(forAsset: asset)
|
||||
))
|
||||
|
||||
updatedArr.insert(domainAsset)
|
||||
updatedAssets.insert(domainAsset)
|
||||
}
|
||||
|
||||
deletedArr.formUnion(deleted)
|
||||
deletedAssets.formUnion(details.deletedLocalIdentifiers)
|
||||
}
|
||||
|
||||
let delta = SyncDelta(hasChanges: true, updates: Array(updatedArr.map { $0.asset }), deletes: Array(deletedArr))
|
||||
|
||||
completion(.success(delta))
|
||||
return
|
||||
} catch {
|
||||
print("MediaManager::getMediaChanges: Error fetching persistent changes: \(error)")
|
||||
completion(.failure(PigeonError(code: "3", message: error.localizedDescription, details: nil)))
|
||||
return
|
||||
|
||||
return SyncDelta(hasChanges: true, updates: Array(updatedAssets.map { $0.asset }), deletes: Array(deletedAssets))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -133,8 +133,8 @@ struct Asset: Hashable {
|
||||
var id: String
|
||||
var name: String
|
||||
var type: Int64
|
||||
var createdAt: String? = nil
|
||||
var updatedAt: String? = nil
|
||||
var createdAt: Int64? = nil
|
||||
var updatedAt: Int64? = nil
|
||||
var durationInSeconds: Int64
|
||||
var albumIds: [String]
|
||||
|
||||
@ -144,8 +144,8 @@ struct Asset: Hashable {
|
||||
let id = pigeonVar_list[0] as! String
|
||||
let name = pigeonVar_list[1] as! String
|
||||
let type = pigeonVar_list[2] as! Int64
|
||||
let createdAt: String? = nilOrValue(pigeonVar_list[3])
|
||||
let updatedAt: String? = nilOrValue(pigeonVar_list[4])
|
||||
let createdAt: Int64? = nilOrValue(pigeonVar_list[3])
|
||||
let updatedAt: Int64? = nilOrValue(pigeonVar_list[4])
|
||||
let durationInSeconds = pigeonVar_list[5] as! Int64
|
||||
let albumIds = pigeonVar_list[6] as! [String]
|
||||
|
||||
@ -251,13 +251,12 @@ class MessagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable {
|
||||
static let shared = MessagesPigeonCodec(readerWriter: MessagesPigeonCodecReaderWriter())
|
||||
}
|
||||
|
||||
|
||||
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
|
||||
protocol ImHostService {
|
||||
func shouldFullSync(completion: @escaping (Result<Bool, Error>) -> Void)
|
||||
func getMediaChanges(completion: @escaping (Result<SyncDelta, Error>) -> Void)
|
||||
func checkpointSync(completion: @escaping (Result<Void, Error>) -> Void)
|
||||
func getAssetIdsForAlbum(albumId: String, completion: @escaping (Result<[String], Error>) -> Void)
|
||||
func shouldFullSync() throws -> Bool
|
||||
func getMediaChanges() throws -> SyncDelta
|
||||
func checkpointSync() throws
|
||||
func getAssetIdsForAlbum(albumId: String) throws -> [String]
|
||||
}
|
||||
|
||||
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
|
||||
@ -274,13 +273,11 @@ class ImHostServiceSetup {
|
||||
let shouldFullSyncChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.ImHostService.shouldFullSync\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
||||
if let api = api {
|
||||
shouldFullSyncChannel.setMessageHandler { _, reply in
|
||||
api.shouldFullSync { result in
|
||||
switch result {
|
||||
case .success(let res):
|
||||
reply(wrapResult(res))
|
||||
case .failure(let error):
|
||||
reply(wrapError(error))
|
||||
}
|
||||
do {
|
||||
let result = try api.shouldFullSync()
|
||||
reply(wrapResult(result))
|
||||
} catch {
|
||||
reply(wrapError(error))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -291,13 +288,11 @@ class ImHostServiceSetup {
|
||||
: FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.ImHostService.getMediaChanges\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue)
|
||||
if let api = api {
|
||||
getMediaChangesChannel.setMessageHandler { _, reply in
|
||||
api.getMediaChanges { result in
|
||||
switch result {
|
||||
case .success(let res):
|
||||
reply(wrapResult(res))
|
||||
case .failure(let error):
|
||||
reply(wrapError(error))
|
||||
}
|
||||
do {
|
||||
let result = try api.getMediaChanges()
|
||||
reply(wrapResult(result))
|
||||
} catch {
|
||||
reply(wrapError(error))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -306,13 +301,11 @@ class ImHostServiceSetup {
|
||||
let checkpointSyncChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.ImHostService.checkpointSync\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
||||
if let api = api {
|
||||
checkpointSyncChannel.setMessageHandler { _, reply in
|
||||
api.checkpointSync { result in
|
||||
switch result {
|
||||
case .success:
|
||||
reply(wrapResult(nil))
|
||||
case .failure(let error):
|
||||
reply(wrapError(error))
|
||||
}
|
||||
do {
|
||||
try api.checkpointSync()
|
||||
reply(wrapResult(nil))
|
||||
} catch {
|
||||
reply(wrapError(error))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -325,13 +318,11 @@ class ImHostServiceSetup {
|
||||
getAssetIdsForAlbumChannel.setMessageHandler { message, reply in
|
||||
let args = message as! [Any?]
|
||||
let albumIdArg = args[0] as! String
|
||||
api.getAssetIdsForAlbum(albumId: albumIdArg) { result in
|
||||
switch result {
|
||||
case .success(let res):
|
||||
reply(wrapResult(res))
|
||||
case .failure(let error):
|
||||
reply(wrapError(error))
|
||||
}
|
||||
do {
|
||||
let result = try api.getAssetIdsForAlbum(albumId: albumIdArg)
|
||||
reply(wrapResult(result))
|
||||
} catch {
|
||||
reply(wrapError(error))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -1,39 +1,36 @@
|
||||
import Photos
|
||||
|
||||
class ImHostServiceImpl: ImHostService {
|
||||
let _mediaManager: MediaManager
|
||||
private let mediaManager: MediaManager
|
||||
|
||||
init() {
|
||||
self._mediaManager = MediaManager()
|
||||
self.mediaManager = MediaManager()
|
||||
}
|
||||
|
||||
func shouldFullSync(completion: @escaping (Result<Bool, Error>) -> Void) {
|
||||
if #available(iOS 16, *) {
|
||||
_mediaManager.shouldFullSync(completion: completion)
|
||||
func shouldFullSync() throws -> Bool {
|
||||
return if #available(iOS 16, *) {
|
||||
mediaManager.shouldFullSync()
|
||||
} else {
|
||||
// Always fall back to full sync on older iOS versions
|
||||
completion(.success(true))
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
func getMediaChanges(completion: @escaping (Result<SyncDelta, Error>) -> Void) {
|
||||
func getMediaChanges() throws -> SyncDelta {
|
||||
guard #available(iOS 16, *) else {
|
||||
completion(.failure(PigeonError(code: "UNSUPPORTED_OS", message: "This feature requires iOS 16 or later.", details: nil)))
|
||||
return
|
||||
throw PigeonError(code: "UNSUPPORTED_OS", message: "This feature requires iOS 16 or later.", details: nil)
|
||||
}
|
||||
_mediaManager.getMediaChanges(completion: completion)
|
||||
return try mediaManager.getMediaChanges()
|
||||
}
|
||||
|
||||
func checkpointSync(completion: @escaping (Result<Void, any Error>) -> Void) {
|
||||
func checkpointSync() throws {
|
||||
if #available(iOS 16, *) {
|
||||
_mediaManager.checkpointSync(completion: completion)
|
||||
} else {
|
||||
completion(.success(()))
|
||||
mediaManager.checkpointSync()
|
||||
}
|
||||
}
|
||||
|
||||
func getAssetIdsForAlbum(albumId: String, completion: @escaping (Result<[String], any Error>) -> Void) {
|
||||
func getAssetIdsForAlbum(albumId: String) throws -> [String] {
|
||||
// Android specific, empty list is safe no-op
|
||||
completion(.success([]))
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
@ -354,10 +354,12 @@ extension on platform.Asset {
|
||||
id: id,
|
||||
name: name,
|
||||
type: AssetType.values.elementAtOrNull(type) ?? AssetType.other,
|
||||
createdAt:
|
||||
createdAt == null ? DateTime.now() : DateTime.parse(createdAt!),
|
||||
updatedAt:
|
||||
updatedAt == null ? DateTime.now() : DateTime.parse(updatedAt!),
|
||||
createdAt: createdAt == null
|
||||
? DateTime.now()
|
||||
: DateTime.fromMillisecondsSinceEpoch(createdAt! * 1000),
|
||||
updatedAt: updatedAt == null
|
||||
? DateTime.now()
|
||||
: DateTime.fromMillisecondsSinceEpoch(updatedAt! * 1000),
|
||||
durationInSeconds: durationInSeconds,
|
||||
);
|
||||
}
|
||||
|
@ -16,8 +16,9 @@ class Asset {
|
||||
final String id;
|
||||
final String name;
|
||||
final int type; // follows AssetType enum from base_asset.model.dart
|
||||
final String? createdAt;
|
||||
final String? updatedAt;
|
||||
// Seconds since epoch
|
||||
final int? createdAt;
|
||||
final int? updatedAt;
|
||||
final int durationInSeconds;
|
||||
final List<String> albumIds;
|
||||
|
||||
@ -25,10 +26,10 @@ class Asset {
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.type,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
required this.durationInSeconds,
|
||||
required this.albumIds,
|
||||
this.createdAt,
|
||||
this.updatedAt,
|
||||
this.durationInSeconds = 0,
|
||||
this.albumIds = const [],
|
||||
});
|
||||
}
|
||||
|
||||
@ -45,17 +46,13 @@ class SyncDelta {
|
||||
|
||||
@HostApi()
|
||||
abstract class ImHostService {
|
||||
@async
|
||||
bool shouldFullSync();
|
||||
|
||||
@async
|
||||
@TaskQueue(type: TaskQueueType.serialBackgroundThread)
|
||||
SyncDelta getMediaChanges();
|
||||
|
||||
@async
|
||||
void checkpointSync();
|
||||
|
||||
@async
|
||||
@TaskQueue(type: TaskQueueType.serialBackgroundThread)
|
||||
List<String> getAssetIdsForAlbum(String albumId);
|
||||
}
|
||||
|
73
mobile/lib/platform/messages.g.dart
generated
73
mobile/lib/platform/messages.g.dart
generated
@ -14,22 +14,21 @@ PlatformException _createConnectionError(String channelName) {
|
||||
message: 'Unable to establish connection on channel: "$channelName".',
|
||||
);
|
||||
}
|
||||
|
||||
bool _deepEquals(Object? a, Object? b) {
|
||||
if (a is List && b is List) {
|
||||
return a.length == b.length &&
|
||||
a.indexed
|
||||
.every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]));
|
||||
.every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]));
|
||||
}
|
||||
if (a is Map && b is Map) {
|
||||
return a.length == b.length &&
|
||||
a.entries.every((MapEntry<Object?, Object?> entry) =>
|
||||
(b as Map<Object?, Object?>).containsKey(entry.key) &&
|
||||
_deepEquals(entry.value, b[entry.key]));
|
||||
return a.length == b.length && a.entries.every((MapEntry<Object?, Object?> entry) =>
|
||||
(b as Map<Object?, Object?>).containsKey(entry.key) &&
|
||||
_deepEquals(entry.value, b[entry.key]));
|
||||
}
|
||||
return a == b;
|
||||
}
|
||||
|
||||
|
||||
class Asset {
|
||||
Asset({
|
||||
required this.id,
|
||||
@ -47,9 +46,9 @@ class Asset {
|
||||
|
||||
int type;
|
||||
|
||||
String? createdAt;
|
||||
int? createdAt;
|
||||
|
||||
String? updatedAt;
|
||||
int? updatedAt;
|
||||
|
||||
int durationInSeconds;
|
||||
|
||||
@ -68,8 +67,7 @@ class Asset {
|
||||
}
|
||||
|
||||
Object encode() {
|
||||
return _toList();
|
||||
}
|
||||
return _toList(); }
|
||||
|
||||
static Asset decode(Object result) {
|
||||
result as List<Object?>;
|
||||
@ -77,8 +75,8 @@ class Asset {
|
||||
id: result[0]! as String,
|
||||
name: result[1]! as String,
|
||||
type: result[2]! as int,
|
||||
createdAt: result[3] as String?,
|
||||
updatedAt: result[4] as String?,
|
||||
createdAt: result[3] as int?,
|
||||
updatedAt: result[4] as int?,
|
||||
durationInSeconds: result[5]! as int,
|
||||
albumIds: (result[6] as List<Object?>?)!.cast<String>(),
|
||||
);
|
||||
@ -98,7 +96,8 @@ class Asset {
|
||||
|
||||
@override
|
||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||
int get hashCode => Object.hashAll(_toList());
|
||||
int get hashCode => Object.hashAll(_toList())
|
||||
;
|
||||
}
|
||||
|
||||
class SyncDelta {
|
||||
@ -123,8 +122,7 @@ class SyncDelta {
|
||||
}
|
||||
|
||||
Object encode() {
|
||||
return _toList();
|
||||
}
|
||||
return _toList(); }
|
||||
|
||||
static SyncDelta decode(Object result) {
|
||||
result as List<Object?>;
|
||||
@ -149,9 +147,11 @@ class SyncDelta {
|
||||
|
||||
@override
|
||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||
int get hashCode => Object.hashAll(_toList());
|
||||
int get hashCode => Object.hashAll(_toList())
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
class _PigeonCodec extends StandardMessageCodec {
|
||||
const _PigeonCodec();
|
||||
@override
|
||||
@ -159,10 +159,10 @@ class _PigeonCodec extends StandardMessageCodec {
|
||||
if (value is int) {
|
||||
buffer.putUint8(4);
|
||||
buffer.putInt64(value);
|
||||
} else if (value is Asset) {
|
||||
} else if (value is Asset) {
|
||||
buffer.putUint8(129);
|
||||
writeValue(buffer, value.encode());
|
||||
} else if (value is SyncDelta) {
|
||||
} else if (value is SyncDelta) {
|
||||
buffer.putUint8(130);
|
||||
writeValue(buffer, value.encode());
|
||||
} else {
|
||||
@ -173,9 +173,9 @@ class _PigeonCodec extends StandardMessageCodec {
|
||||
@override
|
||||
Object? readValueOfType(int type, ReadBuffer buffer) {
|
||||
switch (type) {
|
||||
case 129:
|
||||
case 129:
|
||||
return Asset.decode(readValue(buffer)!);
|
||||
case 130:
|
||||
case 130:
|
||||
return SyncDelta.decode(readValue(buffer)!);
|
||||
default:
|
||||
return super.readValueOfType(type, buffer);
|
||||
@ -187,11 +187,9 @@ class ImHostService {
|
||||
/// Constructor for [ImHostService]. The [binaryMessenger] named argument is
|
||||
/// available for dependency injection. If it is left null, the default
|
||||
/// BinaryMessenger will be used which routes to the host platform.
|
||||
ImHostService(
|
||||
{BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
|
||||
ImHostService({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
|
||||
: pigeonVar_binaryMessenger = binaryMessenger,
|
||||
pigeonVar_messageChannelSuffix =
|
||||
messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
||||
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
||||
final BinaryMessenger? pigeonVar_binaryMessenger;
|
||||
|
||||
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
||||
@ -199,10 +197,8 @@ class ImHostService {
|
||||
final String pigeonVar_messageChannelSuffix;
|
||||
|
||||
Future<bool> shouldFullSync() async {
|
||||
final String pigeonVar_channelName =
|
||||
'dev.flutter.pigeon.immich_mobile.ImHostService.shouldFullSync$pigeonVar_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> pigeonVar_channel =
|
||||
BasicMessageChannel<Object?>(
|
||||
final String pigeonVar_channelName = 'dev.flutter.pigeon.immich_mobile.ImHostService.shouldFullSync$pigeonVar_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
pigeonVar_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: pigeonVar_binaryMessenger,
|
||||
@ -229,10 +225,8 @@ class ImHostService {
|
||||
}
|
||||
|
||||
Future<SyncDelta> getMediaChanges() async {
|
||||
final String pigeonVar_channelName =
|
||||
'dev.flutter.pigeon.immich_mobile.ImHostService.getMediaChanges$pigeonVar_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> pigeonVar_channel =
|
||||
BasicMessageChannel<Object?>(
|
||||
final String pigeonVar_channelName = 'dev.flutter.pigeon.immich_mobile.ImHostService.getMediaChanges$pigeonVar_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
pigeonVar_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: pigeonVar_binaryMessenger,
|
||||
@ -259,10 +253,8 @@ class ImHostService {
|
||||
}
|
||||
|
||||
Future<void> checkpointSync() async {
|
||||
final String pigeonVar_channelName =
|
||||
'dev.flutter.pigeon.immich_mobile.ImHostService.checkpointSync$pigeonVar_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> pigeonVar_channel =
|
||||
BasicMessageChannel<Object?>(
|
||||
final String pigeonVar_channelName = 'dev.flutter.pigeon.immich_mobile.ImHostService.checkpointSync$pigeonVar_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
pigeonVar_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: pigeonVar_binaryMessenger,
|
||||
@ -284,16 +276,13 @@ class ImHostService {
|
||||
}
|
||||
|
||||
Future<List<String>> getAssetIdsForAlbum(String albumId) async {
|
||||
final String pigeonVar_channelName =
|
||||
'dev.flutter.pigeon.immich_mobile.ImHostService.getAssetIdsForAlbum$pigeonVar_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> pigeonVar_channel =
|
||||
BasicMessageChannel<Object?>(
|
||||
final String pigeonVar_channelName = 'dev.flutter.pigeon.immich_mobile.ImHostService.getAssetIdsForAlbum$pigeonVar_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
pigeonVar_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: pigeonVar_binaryMessenger,
|
||||
);
|
||||
final Future<Object?> pigeonVar_sendFuture =
|
||||
pigeonVar_channel.send(<Object?>[albumId]);
|
||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[albumId]);
|
||||
final List<Object?>? pigeonVar_replyList =
|
||||
await pigeonVar_sendFuture as List<Object?>?;
|
||||
if (pigeonVar_replyList == null) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user