mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-03 19:17:11 -05:00 
			
		
		
		
	feat: user & partner sync stream
This commit is contained in:
		
							parent
							
								
									734566db50
								
							
						
					
					
						commit
						3739e86974
					
				@ -92,6 +92,8 @@ custom_lint:
 | 
				
			|||||||
      allowed:
 | 
					      allowed:
 | 
				
			||||||
        # required / wanted
 | 
					        # required / wanted
 | 
				
			||||||
        - lib/repositories/*_api.repository.dart
 | 
					        - lib/repositories/*_api.repository.dart
 | 
				
			||||||
 | 
					        - lib/domain/models/sync/sync_event.model.dart
 | 
				
			||||||
 | 
					        - lib/{domain,infrastructure}/**/sync_stream.*
 | 
				
			||||||
        - lib/infrastructure/repositories/*_api.repository.dart
 | 
					        - lib/infrastructure/repositories/*_api.repository.dart
 | 
				
			||||||
        - lib/infrastructure/utils/*.converter.dart
 | 
					        - lib/infrastructure/utils/*.converter.dart
 | 
				
			||||||
        # acceptable exceptions for the time being
 | 
					        # acceptable exceptions for the time being
 | 
				
			||||||
 | 
				
			|||||||
@ -5,5 +5,7 @@ const double downloadFailed = -2;
 | 
				
			|||||||
// Number of log entries to retain on app start
 | 
					// Number of log entries to retain on app start
 | 
				
			||||||
const int kLogTruncateLimit = 250;
 | 
					const int kLogTruncateLimit = 250;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Duration for backgroun sync
 | 
				
			||||||
 | 
					const Duration kBackgroundSyncDuration = Duration(minutes: 1);
 | 
				
			||||||
const int kBatchHashFileLimit = 128;
 | 
					const int kBatchHashFileLimit = 128;
 | 
				
			||||||
const int kBatchHashSizeLimit = 1024 * 1024 * 1024; // 1GB
 | 
					const int kBatchHashSizeLimit = 1024 * 1024 * 1024; // 1GB
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,9 @@
 | 
				
			|||||||
import 'package:immich_mobile/domain/models/sync/sync_event.model.dart';
 | 
					import 'package:immich_mobile/domain/models/sync/sync_event.model.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
abstract interface class ISyncApiRepository {
 | 
					abstract interface class ISyncApiRepository {
 | 
				
			||||||
  Future<void> ack(String data);
 | 
					  Future<void> ack(List<String> data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Stream<List<SyncEvent>> watchUserSyncEvent();
 | 
					  Stream<List<SyncEvent>> watchUserSyncEvent();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Stream<List<SyncEvent>> watchPartnerSyncEvent();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										10
									
								
								mobile/lib/domain/interfaces/sync_stream.interface.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								mobile/lib/domain/interfaces/sync_stream.interface.dart
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					import 'package:immich_mobile/domain/interfaces/db.interface.dart';
 | 
				
			||||||
 | 
					import 'package:openapi/api.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					abstract interface class ISyncStreamRepository implements IDatabaseRepository {
 | 
				
			||||||
 | 
					  Future<bool> updateUsersV1(SyncUserV1 data);
 | 
				
			||||||
 | 
					  Future<bool> deleteUsersV1(SyncUserDeleteV1 data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<bool> updatePartnerV1(SyncPartnerV1 data);
 | 
				
			||||||
 | 
					  Future<bool> deletePartnerV1(SyncPartnerDeleteV1 data);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,14 +1,13 @@
 | 
				
			|||||||
class SyncEvent {
 | 
					import 'package:openapi/api.dart';
 | 
				
			||||||
  // dynamic
 | 
					 | 
				
			||||||
  final dynamic data;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SyncEvent {
 | 
				
			||||||
 | 
					  final SyncEntityType type;
 | 
				
			||||||
 | 
					  // ignore: avoid-dynamic
 | 
				
			||||||
 | 
					  final dynamic data;
 | 
				
			||||||
  final String ack;
 | 
					  final String ack;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  SyncEvent({
 | 
					  const SyncEvent({required this.type, required this.data, required this.ack});
 | 
				
			||||||
    required this.data,
 | 
					 | 
				
			||||||
    required this.ack,
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  String toString() => 'SyncEvent(data: $data, ack: $ack)';
 | 
					  String toString() => 'SyncEvent(type: $type, data: $data, ack: $ack)';
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,49 +1,96 @@
 | 
				
			|||||||
 | 
					// ignore_for_file: avoid-passing-async-when-sync-expected
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import 'dart:async';
 | 
					import 'dart:async';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import 'package:flutter/foundation.dart';
 | 
					 | 
				
			||||||
import 'package:immich_mobile/domain/interfaces/sync_api.interface.dart';
 | 
					import 'package:immich_mobile/domain/interfaces/sync_api.interface.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/domain/interfaces/sync_stream.interface.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/domain/models/sync/sync_event.model.dart';
 | 
				
			||||||
 | 
					import 'package:logging/logging.dart';
 | 
				
			||||||
import 'package:openapi/api.dart';
 | 
					import 'package:openapi/api.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SyncStreamService {
 | 
					class SyncStreamService {
 | 
				
			||||||
  final ISyncApiRepository _syncApiRepository;
 | 
					  final Logger _logger = Logger('SyncStreamService');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  SyncStreamService(this._syncApiRepository);
 | 
					  final ISyncApiRepository _syncApiRepository;
 | 
				
			||||||
 | 
					  final ISyncStreamRepository _syncStreamRepository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  StreamSubscription? _userSyncSubscription;
 | 
					  StreamSubscription? _userSyncSubscription;
 | 
				
			||||||
 | 
					  Completer<void> _userSyncCompleter = Completer<void>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void syncUsers() {
 | 
					  StreamSubscription? _partnerSyncSubscription;
 | 
				
			||||||
    _userSyncSubscription =
 | 
					  Completer<void> _partnerSyncCompleter = Completer<void>();
 | 
				
			||||||
        _syncApiRepository.watchUserSyncEvent().listen((events) async {
 | 
					 | 
				
			||||||
      for (final event in events) {
 | 
					 | 
				
			||||||
        if (event.data is SyncUserV1) {
 | 
					 | 
				
			||||||
          final data = event.data as SyncUserV1;
 | 
					 | 
				
			||||||
          debugPrint("User Update: $data");
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
          // final user = await _userRepository.get(data.id);
 | 
					  SyncStreamService({
 | 
				
			||||||
 | 
					    required ISyncApiRepository syncApiRepository,
 | 
				
			||||||
 | 
					    required ISyncStreamRepository syncStreamRepository,
 | 
				
			||||||
 | 
					  })  : _syncApiRepository = syncApiRepository,
 | 
				
			||||||
 | 
					        _syncStreamRepository = syncStreamRepository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          // if (user == null) {
 | 
					  // ignore: avoid-dynamic
 | 
				
			||||||
          //   continue;
 | 
					  Future<bool> _handleSyncData(dynamic data) async {
 | 
				
			||||||
          // }
 | 
					    if (data is SyncPartnerV1) {
 | 
				
			||||||
 | 
					      _logger.fine("SyncPartnerV1: $data");
 | 
				
			||||||
 | 
					      return await _syncStreamRepository.updatePartnerV1(data);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          // user.name = data.name;
 | 
					    if (data is SyncUserV1) {
 | 
				
			||||||
          // user.email = data.email;
 | 
					      _logger.fine("SyncUserV1: $data");
 | 
				
			||||||
          // user.updatedAt = DateTime.now();
 | 
					      return await _syncStreamRepository.updateUsersV1(data);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          // await _userRepository.update(user);
 | 
					    if (data is SyncPartnerDeleteV1) {
 | 
				
			||||||
          // await _syncApiRepository.ack(event.ack);
 | 
					      _logger.fine("SyncPartnerDeleteV1: $data");
 | 
				
			||||||
        }
 | 
					      return await _syncStreamRepository.deletePartnerV1(data);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (event.data is SyncUserDeleteV1) {
 | 
					    if (data is SyncUserDeleteV1) {
 | 
				
			||||||
          final data = event.data as SyncUserDeleteV1;
 | 
					      _logger.fine("SyncUserDeleteV1: $data");
 | 
				
			||||||
 | 
					      return await _syncStreamRepository.deleteUsersV1(data);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          debugPrint("User delete: $data");
 | 
					    return false;
 | 
				
			||||||
          // await _syncApiRepository.ack(event.ack);
 | 
					  }
 | 
				
			||||||
        }
 | 
					
 | 
				
			||||||
 | 
					  Future<void> _handleSyncEvents(List<SyncEvent> events) async {
 | 
				
			||||||
 | 
					    Map<SyncEntityType, String> acks = {};
 | 
				
			||||||
 | 
					    for (final event in events) {
 | 
				
			||||||
 | 
					      if (await _handleSyncData(event.data)) {
 | 
				
			||||||
 | 
					        // Only retain the latest ack from each type
 | 
				
			||||||
 | 
					        acks[event.type] = event.ack;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    });
 | 
					    }
 | 
				
			||||||
 | 
					    await _syncApiRepository.ack(acks.values.toList());
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<void> syncUsers() async {
 | 
				
			||||||
 | 
					    _logger.info("Syncing User Changes");
 | 
				
			||||||
 | 
					    _userSyncSubscription = _syncApiRepository.watchUserSyncEvent().listen(
 | 
				
			||||||
 | 
					      _handleSyncEvents,
 | 
				
			||||||
 | 
					      onDone: () {
 | 
				
			||||||
 | 
					        _userSyncCompleter.complete();
 | 
				
			||||||
 | 
					        _userSyncCompleter = Completer<void>();
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    return await _userSyncCompleter.future;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<void> syncPartners() async {
 | 
				
			||||||
 | 
					    _logger.info("Syncing Partner Changes");
 | 
				
			||||||
 | 
					    _partnerSyncSubscription =
 | 
				
			||||||
 | 
					        _syncApiRepository.watchPartnerSyncEvent().listen(
 | 
				
			||||||
 | 
					      _handleSyncEvents,
 | 
				
			||||||
 | 
					      onDone: () {
 | 
				
			||||||
 | 
					        _partnerSyncCompleter.complete();
 | 
				
			||||||
 | 
					        _partnerSyncCompleter = Completer<void>();
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    return await _partnerSyncCompleter.future;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Future<void> dispose() async {
 | 
					  Future<void> dispose() async {
 | 
				
			||||||
    await _userSyncSubscription?.cancel();
 | 
					    await _userSyncSubscription?.cancel();
 | 
				
			||||||
 | 
					    _userSyncCompleter.complete();
 | 
				
			||||||
 | 
					    await _partnerSyncSubscription?.cancel();
 | 
				
			||||||
 | 
					    _partnerSyncCompleter.complete();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,27 +1,31 @@
 | 
				
			|||||||
import 'dart:async';
 | 
					import 'dart:async';
 | 
				
			||||||
import 'dart:convert';
 | 
					import 'dart:convert';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import 'package:flutter/foundation.dart';
 | 
					import 'package:http/http.dart' as http;
 | 
				
			||||||
import 'package:immich_mobile/domain/interfaces/sync_api.interface.dart';
 | 
					import 'package:immich_mobile/domain/interfaces/sync_api.interface.dart';
 | 
				
			||||||
import 'package:immich_mobile/domain/models/sync/sync_event.model.dart';
 | 
					import 'package:immich_mobile/domain/models/sync/sync_event.model.dart';
 | 
				
			||||||
import 'package:immich_mobile/services/api.service.dart';
 | 
					import 'package:immich_mobile/services/api.service.dart';
 | 
				
			||||||
 | 
					import 'package:logging/logging.dart';
 | 
				
			||||||
import 'package:openapi/api.dart';
 | 
					import 'package:openapi/api.dart';
 | 
				
			||||||
import 'package:http/http.dart' as http;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SyncApiRepository implements ISyncApiRepository {
 | 
					class SyncApiRepository implements ISyncApiRepository {
 | 
				
			||||||
 | 
					  final Logger _logger = Logger('SyncApiRepository');
 | 
				
			||||||
  final ApiService _api;
 | 
					  final ApiService _api;
 | 
				
			||||||
  const SyncApiRepository(this._api);
 | 
					  SyncApiRepository(this._api);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  Stream<List<SyncEvent>> watchUserSyncEvent() {
 | 
					  Stream<List<SyncEvent>> watchUserSyncEvent() {
 | 
				
			||||||
    return _getSyncStream(
 | 
					    return _getSyncStream(SyncStreamDto(types: [SyncRequestType.usersV1]));
 | 
				
			||||||
      SyncStreamDto(types: [SyncRequestType.usersV1]),
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  Future<void> ack(String data) {
 | 
					  Stream<List<SyncEvent>> watchPartnerSyncEvent() {
 | 
				
			||||||
    return _api.syncApi.sendSyncAck(SyncAckSetDto(acks: [data]));
 | 
					    return _getSyncStream(SyncStreamDto(types: [SyncRequestType.partnersV1]));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Future<void> ack(List<String> data) {
 | 
				
			||||||
 | 
					    return _api.syncApi.sendSyncAck(SyncAckSetDto(acks: data));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Stream<List<SyncEvent>> _getSyncStream(
 | 
					  Stream<List<SyncEvent>> _getSyncStream(
 | 
				
			||||||
@ -31,7 +35,7 @@ class SyncApiRepository implements ISyncApiRepository {
 | 
				
			|||||||
    final client = http.Client();
 | 
					    final client = http.Client();
 | 
				
			||||||
    final endpoint = "${_api.apiClient.basePath}/sync/stream";
 | 
					    final endpoint = "${_api.apiClient.basePath}/sync/stream";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    final headers = <String, String>{
 | 
					    final headers = {
 | 
				
			||||||
      'Content-Type': 'application/json',
 | 
					      'Content-Type': 'application/json',
 | 
				
			||||||
      'Accept': 'application/jsonlines+json',
 | 
					      'Accept': 'application/jsonlines+json',
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
@ -61,7 +65,8 @@ class SyncApiRepository implements ISyncApiRepository {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      await for (final chunk in response.stream.transform(utf8.decoder)) {
 | 
					      await for (final chunk in response.stream.transform(utf8.decoder)) {
 | 
				
			||||||
        previousChunk += chunk;
 | 
					        previousChunk += chunk;
 | 
				
			||||||
        final parts = previousChunk.split('\n');
 | 
					        // ignore: move-variable-outside-iteration
 | 
				
			||||||
 | 
					        final parts = previousChunk.toString().split('\n');
 | 
				
			||||||
        previousChunk = parts.removeLast();
 | 
					        previousChunk = parts.removeLast();
 | 
				
			||||||
        lines.addAll(parts);
 | 
					        lines.addAll(parts);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -69,44 +74,46 @@ class SyncApiRepository implements ISyncApiRepository {
 | 
				
			|||||||
          continue;
 | 
					          continue;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        yield await compute(_parseSyncResponse, lines);
 | 
					        yield _parseSyncResponse(lines);
 | 
				
			||||||
        lines.clear();
 | 
					        lines.clear();
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    } finally {
 | 
					    } finally {
 | 
				
			||||||
      if (lines.isNotEmpty) {
 | 
					      if (lines.isNotEmpty) {
 | 
				
			||||||
        yield await compute(_parseSyncResponse, lines);
 | 
					        yield _parseSyncResponse(lines);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      client.close();
 | 
					      client.close();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  List<SyncEvent> _parseSyncResponse(List<String> lines) {
 | 
				
			||||||
 | 
					    final List<SyncEvent> data = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (final line in lines) {
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        final jsonData = jsonDecode(line);
 | 
				
			||||||
 | 
					        final type = SyncEntityType.fromJson(jsonData['type'])!;
 | 
				
			||||||
 | 
					        final dataJson = jsonData['data'];
 | 
				
			||||||
 | 
					        final ack = jsonData['ack'];
 | 
				
			||||||
 | 
					        final converter = _kResponseMap[type];
 | 
				
			||||||
 | 
					        if (converter == null) {
 | 
				
			||||||
 | 
					          _logger.warning("[_parseSyncReponse] Unknown type $type");
 | 
				
			||||||
 | 
					          continue;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        data.add(SyncEvent(type: type, data: converter(dataJson), ack: ack));
 | 
				
			||||||
 | 
					      } catch (error, stack) {
 | 
				
			||||||
 | 
					        _logger.severe("[_parseSyncResponse] Error parsing json", error, stack);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return data;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ignore: avoid-dynamic
 | 
				
			||||||
const _kResponseMap = <SyncEntityType, Function(dynamic)>{
 | 
					const _kResponseMap = <SyncEntityType, Function(dynamic)>{
 | 
				
			||||||
  SyncEntityType.userV1: SyncUserV1.fromJson,
 | 
					  SyncEntityType.userV1: SyncUserV1.fromJson,
 | 
				
			||||||
  SyncEntityType.userDeleteV1: SyncUserDeleteV1.fromJson,
 | 
					  SyncEntityType.userDeleteV1: SyncUserDeleteV1.fromJson,
 | 
				
			||||||
 | 
					  SyncEntityType.partnerV1: SyncPartnerV1.fromJson,
 | 
				
			||||||
 | 
					  SyncEntityType.partnerDeleteV1: SyncPartnerDeleteV1.fromJson,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					 | 
				
			||||||
// Need to be outside of the class to be able to use compute
 | 
					 | 
				
			||||||
List<SyncEvent> _parseSyncResponse(List<String> lines) {
 | 
					 | 
				
			||||||
  final List<SyncEvent> data = [];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  for (var line in lines) {
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      final jsonData = jsonDecode(line);
 | 
					 | 
				
			||||||
      final type = SyncEntityType.fromJson(jsonData['type'])!;
 | 
					 | 
				
			||||||
      final dataJson = jsonData['data'];
 | 
					 | 
				
			||||||
      final ack = jsonData['ack'];
 | 
					 | 
				
			||||||
      final converter = _kResponseMap[type];
 | 
					 | 
				
			||||||
      if (converter == null) {
 | 
					 | 
				
			||||||
        debugPrint("[_parseSyncReponse] Unknown type $type");
 | 
					 | 
				
			||||||
        continue;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      data.add(SyncEvent(data: converter(dataJson), ack: ack));
 | 
					 | 
				
			||||||
    } catch (error, stack) {
 | 
					 | 
				
			||||||
      debugPrint("[_parseSyncReponse] Error parsing json $error $stack");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return data;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,84 @@
 | 
				
			|||||||
 | 
					import 'package:drift/drift.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/domain/interfaces/sync_stream.interface.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/infrastructure/entities/partner.entity.drift.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
 | 
				
			||||||
 | 
					import 'package:logging/logging.dart';
 | 
				
			||||||
 | 
					import 'package:openapi/api.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DriftSyncStreamRepository extends DriftDatabaseRepository
 | 
				
			||||||
 | 
					    implements ISyncStreamRepository {
 | 
				
			||||||
 | 
					  final Logger _logger = Logger('DriftSyncStreamRepository');
 | 
				
			||||||
 | 
					  final Drift _db;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  DriftSyncStreamRepository(super.db) : _db = db;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Future<bool> deleteUsersV1(SyncUserDeleteV1 data) async {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      await _db.managers.userEntity
 | 
				
			||||||
 | 
					          .filter((row) => row.id.equals(data.userId))
 | 
				
			||||||
 | 
					          .delete();
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    } catch (e, s) {
 | 
				
			||||||
 | 
					      _logger.severe('Error while processing SyncUserDeleteV1', e, s);
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Future<bool> updateUsersV1(SyncUserV1 data) async {
 | 
				
			||||||
 | 
					    final companion = UserEntityCompanion(
 | 
				
			||||||
 | 
					      name: Value(data.name),
 | 
				
			||||||
 | 
					      email: Value(data.email),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      await _db.userEntity.insertOne(
 | 
				
			||||||
 | 
					        companion.copyWith(id: Value(data.id)),
 | 
				
			||||||
 | 
					        onConflict: DoUpdate((_) => companion),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    } catch (e, s) {
 | 
				
			||||||
 | 
					      _logger.severe('Error while processing SyncUserV1', e, s);
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Future<bool> deletePartnerV1(SyncPartnerDeleteV1 data) async {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      await _db.managers.partnerEntity
 | 
				
			||||||
 | 
					          .filter(
 | 
				
			||||||
 | 
					            (row) =>
 | 
				
			||||||
 | 
					                row.sharedById.id.equals(data.sharedById) &
 | 
				
			||||||
 | 
					                row.sharedWithId.id.equals(data.sharedWithId),
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					          .delete();
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    } catch (e, s) {
 | 
				
			||||||
 | 
					      _logger.severe('Error while processing SyncPartnerDeleteV1', e, s);
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Future<bool> updatePartnerV1(SyncPartnerV1 data) async {
 | 
				
			||||||
 | 
					    final companion =
 | 
				
			||||||
 | 
					        PartnerEntityCompanion(inTimeline: Value(data.inTimeline));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      await _db.partnerEntity.insertOne(
 | 
				
			||||||
 | 
					        companion.copyWith(
 | 
				
			||||||
 | 
					          sharedById: Value(data.sharedById),
 | 
				
			||||||
 | 
					          sharedWithId: Value(data.sharedWithId),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        onConflict: DoUpdate((_) => companion),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    } catch (e, s) {
 | 
				
			||||||
 | 
					      _logger.severe('Error while processing SyncUserV1', e, s);
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -7,6 +7,7 @@ import 'package:immich_mobile/models/backup/backup_state.model.dart';
 | 
				
			|||||||
import 'package:immich_mobile/providers/album/album.provider.dart';
 | 
					import 'package:immich_mobile/providers/album/album.provider.dart';
 | 
				
			||||||
import 'package:immich_mobile/providers/asset.provider.dart';
 | 
					import 'package:immich_mobile/providers/asset.provider.dart';
 | 
				
			||||||
import 'package:immich_mobile/providers/auth.provider.dart';
 | 
					import 'package:immich_mobile/providers/auth.provider.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/providers/background_sync.provider.dart';
 | 
				
			||||||
import 'package:immich_mobile/providers/backup/backup.provider.dart';
 | 
					import 'package:immich_mobile/providers/backup/backup.provider.dart';
 | 
				
			||||||
import 'package:immich_mobile/providers/backup/ios_background_settings.provider.dart';
 | 
					import 'package:immich_mobile/providers/backup/ios_background_settings.provider.dart';
 | 
				
			||||||
import 'package:immich_mobile/providers/backup/manual_upload.provider.dart';
 | 
					import 'package:immich_mobile/providers/backup/manual_upload.provider.dart';
 | 
				
			||||||
@ -113,6 +114,7 @@ class AppLifeCycleNotifier extends StateNotifier<AppLifeCycleEnum> {
 | 
				
			|||||||
        _ref.read(backupProvider.notifier).cancelBackup();
 | 
					        _ref.read(backupProvider.notifier).cancelBackup();
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      _ref.read(websocketProvider.notifier).disconnect();
 | 
					      _ref.read(websocketProvider.notifier).disconnect();
 | 
				
			||||||
 | 
					      _ref.read(backgroundSyncProvider).stop();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    LogService.I.flush();
 | 
					    LogService.I.flush();
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										10
									
								
								mobile/lib/providers/background_sync.provider.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								mobile/lib/providers/background_sync.provider.dart
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/constants/constants.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/utils/background_sync.dart';
 | 
				
			||||||
 | 
					import 'package:riverpod_annotation/riverpod_annotation.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					part 'background_sync.provider.g.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Riverpod(keepAlive: true)
 | 
				
			||||||
 | 
					BackgroundSyncManager backgroundSync(Ref _) =>
 | 
				
			||||||
 | 
					    BackgroundSyncManager(duration: kBackgroundSyncDuration);
 | 
				
			||||||
							
								
								
									
										27
									
								
								mobile/lib/providers/background_sync.provider.g.dart
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								mobile/lib/providers/background_sync.provider.g.dart
									
									
									
										generated
									
									
									
										Normal file
									
								
							@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					// GENERATED CODE - DO NOT MODIFY BY HAND
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					part of 'background_sync.provider.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// **************************************************************************
 | 
				
			||||||
 | 
					// RiverpodGenerator
 | 
				
			||||||
 | 
					// **************************************************************************
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					String _$backgroundSyncHash() => r'c08b6499af18d3fedeb9bb6c4ac0833c656f30dd';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// See also [backgroundSync].
 | 
				
			||||||
 | 
					@ProviderFor(backgroundSync)
 | 
				
			||||||
 | 
					final backgroundSyncProvider = Provider<BackgroundSyncManager>.internal(
 | 
				
			||||||
 | 
					  backgroundSync,
 | 
				
			||||||
 | 
					  name: r'backgroundSyncProvider',
 | 
				
			||||||
 | 
					  debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
 | 
				
			||||||
 | 
					      ? null
 | 
				
			||||||
 | 
					      : _$backgroundSyncHash,
 | 
				
			||||||
 | 
					  dependencies: null,
 | 
				
			||||||
 | 
					  allTransitiveDependencies: null,
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Deprecated('Will be removed in 3.0. Use Ref instead')
 | 
				
			||||||
 | 
					// ignore: unused_element
 | 
				
			||||||
 | 
					typedef BackgroundSyncRef = ProviderRef<BackgroundSyncManager>;
 | 
				
			||||||
 | 
					// ignore_for_file: type=lint
 | 
				
			||||||
 | 
					// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
 | 
				
			||||||
@ -1,5 +1,10 @@
 | 
				
			|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
 | 
				
			||||||
import 'package:isar/isar.dart';
 | 
					import 'package:isar/isar.dart';
 | 
				
			||||||
 | 
					import 'package:riverpod_annotation/riverpod_annotation.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// overwritten in main.dart due to async loading
 | 
					// overwritten in main.dart due to async loading
 | 
				
			||||||
final dbProvider = Provider<Isar>((_) => throw UnimplementedError());
 | 
					final dbProvider = Provider<Isar>((_) => throw UnimplementedError());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Riverpod(keepAlive: true)
 | 
				
			||||||
 | 
					Drift drift(Ref _) => Drift();
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,5 @@
 | 
				
			|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
 | 
				
			||||||
import 'package:isar/isar.dart';
 | 
					import 'package:isar/isar.dart';
 | 
				
			||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
 | 
					import 'package:riverpod_annotation/riverpod_annotation.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -6,3 +7,6 @@ part 'db.provider.g.dart';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
@Riverpod(keepAlive: true)
 | 
					@Riverpod(keepAlive: true)
 | 
				
			||||||
Isar isar(Ref ref) => throw UnimplementedError('isar');
 | 
					Isar isar(Ref ref) => throw UnimplementedError('isar');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Riverpod(keepAlive: true)
 | 
				
			||||||
 | 
					Drift drift(Ref _) => Drift();
 | 
				
			||||||
 | 
				
			|||||||
@ -22,5 +22,21 @@ final isarProvider = Provider<Isar>.internal(
 | 
				
			|||||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
 | 
					@Deprecated('Will be removed in 3.0. Use Ref instead')
 | 
				
			||||||
// ignore: unused_element
 | 
					// ignore: unused_element
 | 
				
			||||||
typedef IsarRef = ProviderRef<Isar>;
 | 
					typedef IsarRef = ProviderRef<Isar>;
 | 
				
			||||||
 | 
					String _$driftHash() => r'21ceb1dbc569b877cb710387b350590e994fe64e';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// See also [drift].
 | 
				
			||||||
 | 
					@ProviderFor(drift)
 | 
				
			||||||
 | 
					final driftProvider = Provider<Drift>.internal(
 | 
				
			||||||
 | 
					  drift,
 | 
				
			||||||
 | 
					  name: r'driftProvider',
 | 
				
			||||||
 | 
					  debugGetCreateSourceHash:
 | 
				
			||||||
 | 
					      const bool.fromEnvironment('dart.vm.product') ? null : _$driftHash,
 | 
				
			||||||
 | 
					  dependencies: null,
 | 
				
			||||||
 | 
					  allTransitiveDependencies: null,
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Deprecated('Will be removed in 3.0. Use Ref instead')
 | 
				
			||||||
 | 
					// ignore: unused_element
 | 
				
			||||||
 | 
					typedef DriftRef = ProviderRef<Drift>;
 | 
				
			||||||
// ignore_for_file: type=lint
 | 
					// ignore_for_file: type=lint
 | 
				
			||||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
 | 
					// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
 | 
				
			||||||
 | 
				
			|||||||
@ -3,22 +3,25 @@ import 'dart:async';
 | 
				
			|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
import 'package:immich_mobile/domain/services/sync_stream.service.dart';
 | 
					import 'package:immich_mobile/domain/services/sync_stream.service.dart';
 | 
				
			||||||
import 'package:immich_mobile/infrastructure/repositories/sync_api.repository.dart';
 | 
					import 'package:immich_mobile/infrastructure/repositories/sync_api.repository.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart';
 | 
				
			||||||
import 'package:immich_mobile/providers/api.provider.dart';
 | 
					import 'package:immich_mobile/providers/api.provider.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
final syncStreamServiceProvider = Provider(
 | 
					final syncStreamServiceProvider = Provider((ref) {
 | 
				
			||||||
  (ref) {
 | 
					  final instance = SyncStreamService(
 | 
				
			||||||
    final instance = SyncStreamService(
 | 
					    syncApiRepository: ref.watch(syncApiRepositoryProvider),
 | 
				
			||||||
      ref.watch(syncApiRepositoryProvider),
 | 
					    syncStreamRepository: ref.watch(syncStreamRepositoryProvider),
 | 
				
			||||||
    );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ref.onDispose(() => unawaited(instance.dispose()));
 | 
					  ref.onDispose(() => unawaited(instance.dispose()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return instance;
 | 
					  return instance;
 | 
				
			||||||
  },
 | 
					});
 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
final syncApiRepositoryProvider = Provider(
 | 
					final syncApiRepositoryProvider = Provider(
 | 
				
			||||||
  (ref) => SyncApiRepository(
 | 
					  (ref) => SyncApiRepository(ref.watch(apiServiceProvider)),
 | 
				
			||||||
    ref.watch(apiServiceProvider),
 | 
					);
 | 
				
			||||||
  ),
 | 
					
 | 
				
			||||||
 | 
					final syncStreamRepositoryProvider = Provider(
 | 
				
			||||||
 | 
					  (ref) => DriftSyncStreamRepository(ref.watch(driftProvider)),
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										58
									
								
								mobile/lib/utils/background_sync.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								mobile/lib/utils/background_sync.dart
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,58 @@
 | 
				
			|||||||
 | 
					// ignore_for_file: avoid-passing-async-when-sync-expected
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'dart:async';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:async/async.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/providers/infrastructure/sync_stream.provider.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/utils/isolate.dart';
 | 
				
			||||||
 | 
					import 'package:logging/logging.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _SyncStreamDriver {
 | 
				
			||||||
 | 
					  final _userSyncCache = AsyncCache.ephemeral();
 | 
				
			||||||
 | 
					  final _partnerSyncCache = AsyncCache.ephemeral();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<void> syncUsers() => _userSyncCache.fetch(
 | 
				
			||||||
 | 
					        () async => runInIsolate(
 | 
				
			||||||
 | 
					          (ref) => ref.read(syncStreamServiceProvider).syncUsers(),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<void> syncPartners() => _partnerSyncCache.fetch(
 | 
				
			||||||
 | 
					        () async => runInIsolate(
 | 
				
			||||||
 | 
					          (ref) => ref.read(syncStreamServiceProvider).syncPartners(),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BackgroundSyncManager {
 | 
				
			||||||
 | 
					  final Logger _logger = Logger('BackgroundSyncManager');
 | 
				
			||||||
 | 
					  Timer? _timer;
 | 
				
			||||||
 | 
					  final Duration _duration;
 | 
				
			||||||
 | 
					  // This allows us to keep synching in the background while allowing ondemand syncs
 | 
				
			||||||
 | 
					  final _driver = _SyncStreamDriver();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  BackgroundSyncManager({required Duration duration}) : _duration = duration;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Timer _createTimer() {
 | 
				
			||||||
 | 
					    return Timer.periodic(_duration, (timer) async {
 | 
				
			||||||
 | 
					      _logger.info('Background sync started');
 | 
				
			||||||
 | 
					      await _driver.syncUsers();
 | 
				
			||||||
 | 
					      await _driver.syncPartners();
 | 
				
			||||||
 | 
					      _logger.info('Background sync completed');
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void start() {
 | 
				
			||||||
 | 
					    _logger.info('Background sync enabled');
 | 
				
			||||||
 | 
					    _timer ??= _createTimer();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void stop() {
 | 
				
			||||||
 | 
					    _logger.info('Background sync disabled');
 | 
				
			||||||
 | 
					    _timer?.cancel();
 | 
				
			||||||
 | 
					    _timer = null;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<void> syncUsers() => _driver.syncUsers();
 | 
				
			||||||
 | 
					  Future<void> syncPartners() => _driver.syncPartners();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										67
									
								
								mobile/lib/utils/isolate.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								mobile/lib/utils/isolate.dart
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,67 @@
 | 
				
			|||||||
 | 
					import 'dart:async';
 | 
				
			||||||
 | 
					import 'dart:isolate';
 | 
				
			||||||
 | 
					import 'dart:ui';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					import 'package:flutter/services.dart';
 | 
				
			||||||
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/providers/db.provider.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/utils/bootstrap.dart';
 | 
				
			||||||
 | 
					import 'package:logging/logging.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class InvalidIsolateUsageException implements Exception {
 | 
				
			||||||
 | 
					  const InvalidIsolateUsageException();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  String toString() =>
 | 
				
			||||||
 | 
					      "IsolateHelper should only be used from the root isolate";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// !! Should be used only from the root isolate
 | 
				
			||||||
 | 
					Future<T?> runInIsolate<T>(
 | 
				
			||||||
 | 
					  FutureOr<T> Function(ProviderContainer ref) computation, {
 | 
				
			||||||
 | 
					  String? debugLabel,
 | 
				
			||||||
 | 
					}) async {
 | 
				
			||||||
 | 
					  final token = RootIsolateToken.instance;
 | 
				
			||||||
 | 
					  if (token == null) {
 | 
				
			||||||
 | 
					    throw const InvalidIsolateUsageException();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return await Isolate.run(() async {
 | 
				
			||||||
 | 
					    BackgroundIsolateBinaryMessenger.ensureInitialized(token);
 | 
				
			||||||
 | 
					    DartPluginRegistrant.ensureInitialized();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    final db = await Bootstrap.initIsar();
 | 
				
			||||||
 | 
					    await Bootstrap.initDomain(db);
 | 
				
			||||||
 | 
					    final ref = ProviderContainer(
 | 
				
			||||||
 | 
					      overrides: [
 | 
				
			||||||
 | 
					        // TODO: Remove once isar is removed
 | 
				
			||||||
 | 
					        dbProvider.overrideWithValue(db),
 | 
				
			||||||
 | 
					        isarProvider.overrideWithValue(db),
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Logger log = Logger("IsolateLogger");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      final result = await computation(ref);
 | 
				
			||||||
 | 
					      // Wait for isolate to end; i.e, logs to be flushed
 | 
				
			||||||
 | 
					      await Future.delayed(Durations.short2);
 | 
				
			||||||
 | 
					      return result;
 | 
				
			||||||
 | 
					    } catch (error, stack) {
 | 
				
			||||||
 | 
					      // Log the error and stack trace
 | 
				
			||||||
 | 
					      log.severe(
 | 
				
			||||||
 | 
					        "Error in runInIsolate${debugLabel == null ? '' : ' for $debugLabel'}",
 | 
				
			||||||
 | 
					        error,
 | 
				
			||||||
 | 
					        stack,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    } finally {
 | 
				
			||||||
 | 
					      // Always close the new database connection on Isolate end
 | 
				
			||||||
 | 
					      ref.read(driftProvider).close();
 | 
				
			||||||
 | 
					      ref.read(dbProvider).close();
 | 
				
			||||||
 | 
					      ref.read(isarProvider).close();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return null;
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user