Shared Isolate & Protocol DTO Layer
The common package is the shared spine of LocalSend: it defines the wire DTOs (Device, FileDto, register/prepare-upload payloads) that every subsystem passes around, and it implements a parent/child isolate framework that runs discovery (multicast + HTTP scan) and file upload work off the main Flutter isolate. A new engineer should understand IsolateConnector/startIsolate as the generic transport, IsolateTask/IsolateTaskResult as the request-response envelope, and the dart_mappable DTOs as the protocol contract that must stay backward compatible across LocalSend versions.
IsolateSetupAction spins up every child isolate with a shared SyncState snapshot
common/lib/src/isolate/parent/parent_isolate_provider.dart · lines 67–124Spawns the HTTP scan discovery isolate via the generic startIsolate<R,S,P> helper, handing it a fresh InitialData snapshot of the current SyncState and the root logger's level.
Spawns a second, independent isolate for multicast discovery using the same InitialData pattern — each isolate gets its own copy, not a shared reference.
Starts a fixed-size pool of upload isolates (_uploadIsolateCount = 2) and, if a URI content stream resolver was supplied, immediately pushes it into each one with a sentinel task id of -1.
Awaits all upload isolates together and folds every IsolateConnector into a new ParentIsolateState via copyWith — this is the state the rest of the app reads to send tasks to the isolates.
Future<ParentIsolateState> reduce() async { final httpScanDiscovery = await startIsolate<IsolateTaskStreamResult<Device>, SendToIsolateData<IsolateTask<HttpScanTask>>, InitialData>( task: setupHttpScanDiscoveryIsolate, param: InitialData( syncState: state.syncState, logLevel: Logger.root.level, ), ); final multicastDiscovery = await startIsolate<Device, SendToIsolateData<MulticastTask>, InitialData>( task: setupMulticastDiscoveryIsolate, param: InitialData( syncState: state.syncState, logLevel: Logger.root.level, ), ); final httpUploadIsolates = List.generate( _uploadIsolateCount, (index) async { final httpUpload = await startIsolate<IsolateTaskStreamResult<double>, SendToIsolateData<IsolateTask<BaseHttpUploadTask>>, InitialData>( task: setupHttpUploadIsolate, param: InitialData( syncState: state.syncState, logLevel: Logger.root.level, ), ); if (uriContentStreamResolver != null) { httpUpload.sendToIsolate(SendToIsolateData( syncState: null, data: IsolateTask( id: -1, data: HttpUploadSetContentStreamResolverTask(resolver: uriContentStreamResolver!), ), )); } return httpUpload; }, growable: false, ); return state.copyWith( httpScanDiscovery: httpScanDiscovery, multicastDiscovery: multicastDiscovery, httpUpload: await Future.wait(httpUploadIsolates), ); }
class FileDtoMapper extends SimpleMapper<FileDto> { const FileDtoMapper(); @override FileDto decode(dynamic value) { final map = value as Map<String, dynamic>; final String rawFileType = map['fileType'] as String; final FileType fileType; if (rawFileType.contains('/')) { // parse mime fileType = decodeFromMime(rawFileType); } else { // parse legacy enum to internal internal enum fileType = FileType.values.firstWhereOrNull((e) => e.name == rawFileType) ?? FileType.other; } return FileDto( id: map['id'] as String, fileName: map['fileName'] as String, size: map['size'] as int, fileType: fileType, hash: map['hash'] as String?, preview: map['preview'] as String?, metadata: switch (map['metadata']) { Map<String, dynamic> metadata => FileMetadataMapper.fromJson(metadata), _ => null, }, ); } @override dynamic encode(FileDto self) { return { 'id': self.id, 'fileName': self.fileName, 'size': self.size, 'fileType': self.lookupMime(), if (self.hash != null) 'hash': self.hash, if (self.preview != null) 'preview': self.preview, if (self.metadata != null) 'metadata': self.metadata!.toJson(), }; }}
common/lib/model/dto/file_dto.dart · lines 60–102common/lib/model/dto/file_dto.dartCheck your understanding
Answered in place — nothing is graded, everything is explained. 0 / 3 passed
Multiple discovery/upload requests can be in flight on the same child isolate at once. What lets the main isolate route each streamed result back to the correct caller?
Why does FileDto's mapper decode() branch to handle both MIME strings and legacy enum names for fileType?
How does a newly spawned child isolate learn the app's current settings (alias, port, network whitelist) without direct access to the main isolate's providers?