import "package:photos/models/ml/face/face.dart"; import "package:photos/utils/parse.dart"; const _faceKey = 'face'; const _clipKey = 'clip'; enum DataType { mlData('mldata'); final String value; const DataType(this.value); static DataType fromString(String type) { return DataType.values.firstWhere( (e) => e.value == type, orElse: () { throw Exception('Unknown type: $type'); }, ); } String toJson() => value; static DataType fromJson(String json) => DataType.fromString(json); } class FileDataEntity { final int fileID; final Map remoteRawData; final DataType type; FileDataEntity( this.fileID, this.remoteRawData, this.type, ); void validate() { if (type == DataType.mlData) { if (remoteRawData[_faceKey] == null) { throw Exception('Face embedding is null'); } if (remoteRawData[_clipKey] == null) { throw Exception('Clip embedding is null'); } } else { throw Exception('Invalid type ${type.value}'); } } factory FileDataEntity.fromRemote( int fileID, String type, Map json, ) { return FileDataEntity( fileID, json, DataType.fromString(type), ); } static FileDataEntity empty(int fileID, DataType type) { final Map json = {}; return FileDataEntity(fileID, json, type); } void putFace(RemoteFaceEmbedding faceEmbedding) { assert(type == DataType.mlData, 'Invalid type ${type.value}'); remoteRawData[_faceKey] = faceEmbedding.toJson(); } void putClip(RemoteClipEmbedding clipEmbedding) { assert(type == DataType.mlData, 'Invalid type ${type.value}'); remoteRawData[_clipKey] = clipEmbedding.toJson(); } RemoteFaceEmbedding? get faceEmbedding => remoteRawData[_faceKey] != null ? RemoteFaceEmbedding.fromJson( remoteRawData[_faceKey] as Map, ) : null; RemoteClipEmbedding? getClipEmbeddingIfCompatible( int minClipMlVersion, ) { final clipData = remoteRawData[_clipKey]; if (clipData == null) return null; final clipEmbedding = RemoteClipEmbedding.fromJson(clipData as Map); return clipEmbedding.version >= minClipMlVersion ? clipEmbedding : null; } } class RemoteFaceEmbedding { final List faces; final int version; // packageName/version final String client; final int height; final int width; RemoteFaceEmbedding( this.faces, this.version, { required this.client, required this.height, required this.width, }); // toJson Map toJson() => { 'faces': faces.map((x) => x.toJson()).toList(), 'version': version, 'client': client, 'height': height, 'width': width, }; // fromJson factory RemoteFaceEmbedding.fromJson(Map json) { return RemoteFaceEmbedding( List.from( json['faces'].map((x) => Face.fromJson(x as Map)), ), json['version'] as int, client: json['client'] as String, height: json['height'] as int, width: json['width'] as int, ); } } class RemoteClipEmbedding { final int version; final String client; final List embedding; RemoteClipEmbedding( this.embedding, { required this.version, required this.client, }); // toJson Map toJson() => { 'embedding': embedding, 'version': version, 'client': client, }; // fromJson factory RemoteClipEmbedding.fromJson(Map json) { return RemoteClipEmbedding( parseAsDoubleList(json['embedding'] as List), version: json['version'] as int, client: json['client'] as String, ); } } // FDStatus represents the status of a file data entry. class FDStatus { final int fileID; final int userID; final String type; final bool isDeleted; final int size; final int updatedAt; final String? objectID; final String? objectNonce; FDStatus({ required this.fileID, required this.userID, required this.type, required this.size, required this.updatedAt, this.isDeleted = false, this.objectID, this.objectNonce, }); factory FDStatus.fromJson(Map json) { return FDStatus( fileID: json['fileID'] as int, userID: json['userID'] as int, type: json['type'] as String, isDeleted: json['isDeleted'] as bool? ?? false, size: json['size'] as int, objectID: json['objectID'] as String?, objectNonce: json['objectNonce'] as String?, updatedAt: json['updatedAt'] as int, ); } } class PreviewInfo { final String objectId; final int objectSize; String? nonce; PreviewInfo({ required this.objectId, required this.objectSize, this.nonce, }); }