More tiny faces (#1925)

## Description

- Some UI alignment changes
- More error logging

## Tests

Tested in debug mode on my pixel phone.
This commit is contained in:
Laurens Priem
2024-05-30 13:34:32 +05:30
committed by GitHub
19 changed files with 67 additions and 122 deletions

View File

@@ -50,8 +50,6 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Enter person name"),
"faceRecognition":
MessageLookupByLibrary.simpleMessage("Face recognition"),
"faceRecognitionIndexingDescription": MessageLookupByLibrary.simpleMessage(
"Please note that this will result in a higher bandwidth and battery usage until all items are indexed."),
"fileTypes": MessageLookupByLibrary.simpleMessage("File types"),
"foundFaces": MessageLookupByLibrary.simpleMessage("Found faces"),
"indexingIsPaused": MessageLookupByLibrary.simpleMessage(

View File

@@ -706,8 +706,6 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Daten exportieren"),
"faceRecognition":
MessageLookupByLibrary.simpleMessage("Face recognition"),
"faceRecognitionIndexingDescription": MessageLookupByLibrary.simpleMessage(
"Please note that this will result in a higher bandwidth and battery usage until all items are indexed."),
"faces": MessageLookupByLibrary.simpleMessage("Gesichter"),
"failedToApplyCode": MessageLookupByLibrary.simpleMessage(
"Der Code konnte nicht aktiviert werden"),
@@ -928,8 +926,6 @@ class MessageLookup extends MessageLookupByLibrary {
"machineLearning":
MessageLookupByLibrary.simpleMessage("Maschinelles Lernen"),
"magicSearch": MessageLookupByLibrary.simpleMessage("Magische Suche"),
"magicSearchDescription": MessageLookupByLibrary.simpleMessage(
"Bitte beachten Sie, dass dies mehr Bandbreite nutzt und zu einem höheren Akkuverbrauch führt, bis alle Elemente indiziert sind."),
"manage": MessageLookupByLibrary.simpleMessage("Verwalten"),
"manageDeviceStorage":
MessageLookupByLibrary.simpleMessage("Gerätespeicher verwalten"),

View File

@@ -704,8 +704,6 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Export your data"),
"faceRecognition":
MessageLookupByLibrary.simpleMessage("Face recognition"),
"faceRecognitionIndexingDescription": MessageLookupByLibrary.simpleMessage(
"Please note that this will result in a higher bandwidth and battery usage until all items are indexed."),
"faces": MessageLookupByLibrary.simpleMessage("Faces"),
"failedToApplyCode":
MessageLookupByLibrary.simpleMessage("Failed to apply code"),
@@ -921,8 +919,6 @@ class MessageLookup extends MessageLookupByLibrary {
"machineLearning":
MessageLookupByLibrary.simpleMessage("Machine learning"),
"magicSearch": MessageLookupByLibrary.simpleMessage("Magic search"),
"magicSearchDescription": MessageLookupByLibrary.simpleMessage(
"Please note that this will result in a higher bandwidth and battery usage until all items are indexed."),
"manage": MessageLookupByLibrary.simpleMessage("Manage"),
"manageDeviceStorage":
MessageLookupByLibrary.simpleMessage("Manage device storage"),
@@ -939,6 +935,8 @@ class MessageLookup extends MessageLookupByLibrary {
"matrix": MessageLookupByLibrary.simpleMessage("Matrix"),
"memoryCount": m33,
"merchandise": MessageLookupByLibrary.simpleMessage("Merchandise"),
"mlIndexingDescription": MessageLookupByLibrary.simpleMessage(
"Please note that ML indexing will result in a higher bandwidth and battery usage until all items are indexed."),
"mobileWebDesktop":
MessageLookupByLibrary.simpleMessage("Mobile, Web, Desktop"),
"moderateStrength": MessageLookupByLibrary.simpleMessage("Moderate"),

View File

@@ -615,8 +615,6 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Exportar tus datos"),
"faceRecognition":
MessageLookupByLibrary.simpleMessage("Face recognition"),
"faceRecognitionIndexingDescription": MessageLookupByLibrary.simpleMessage(
"Please note that this will result in a higher bandwidth and battery usage until all items are indexed."),
"failedToApplyCode":
MessageLookupByLibrary.simpleMessage("Error al aplicar el código"),
"failedToCancel":

View File

@@ -694,8 +694,6 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Exportez vos données"),
"faceRecognition":
MessageLookupByLibrary.simpleMessage("Face recognition"),
"faceRecognitionIndexingDescription": MessageLookupByLibrary.simpleMessage(
"Please note that this will result in a higher bandwidth and battery usage until all items are indexed."),
"faces": MessageLookupByLibrary.simpleMessage("Visages"),
"failedToApplyCode": MessageLookupByLibrary.simpleMessage(
"Impossible d\'appliquer le code"),

View File

@@ -671,8 +671,6 @@ class MessageLookup extends MessageLookupByLibrary {
"exportYourData": MessageLookupByLibrary.simpleMessage("Esporta dati"),
"faceRecognition":
MessageLookupByLibrary.simpleMessage("Face recognition"),
"faceRecognitionIndexingDescription": MessageLookupByLibrary.simpleMessage(
"Please note that this will result in a higher bandwidth and battery usage until all items are indexed."),
"failedToApplyCode": MessageLookupByLibrary.simpleMessage(
"Impossibile applicare il codice"),
"failedToCancel":

View File

@@ -50,8 +50,6 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Enter person name"),
"faceRecognition":
MessageLookupByLibrary.simpleMessage("Face recognition"),
"faceRecognitionIndexingDescription": MessageLookupByLibrary.simpleMessage(
"Please note that this will result in a higher bandwidth and battery usage until all items are indexed."),
"fileTypes": MessageLookupByLibrary.simpleMessage("File types"),
"foundFaces": MessageLookupByLibrary.simpleMessage("Found faces"),
"indexingIsPaused": MessageLookupByLibrary.simpleMessage(

View File

@@ -727,8 +727,6 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Exporteer je gegevens"),
"faceRecognition":
MessageLookupByLibrary.simpleMessage("Face recognition"),
"faceRecognitionIndexingDescription": MessageLookupByLibrary.simpleMessage(
"Please note that this will result in a higher bandwidth and battery usage until all items are indexed."),
"faces": MessageLookupByLibrary.simpleMessage("Gezichten"),
"failedToApplyCode":
MessageLookupByLibrary.simpleMessage("Code toepassen mislukt"),
@@ -952,8 +950,6 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Machine Learning"),
"magicSearch":
MessageLookupByLibrary.simpleMessage("Magische zoekfunctie"),
"magicSearchDescription": MessageLookupByLibrary.simpleMessage(
"Houd er rekening mee dat dit zal resulteren in een hoger internet- en batterijverbruik totdat alle items zijn geïndexeerd."),
"manage": MessageLookupByLibrary.simpleMessage("Beheren"),
"manageDeviceStorage":
MessageLookupByLibrary.simpleMessage("Apparaatopslag beheren"),

View File

@@ -67,8 +67,6 @@ class MessageLookup extends MessageLookupByLibrary {
"Skriv inn e-postadressen din"),
"faceRecognition":
MessageLookupByLibrary.simpleMessage("Face recognition"),
"faceRecognitionIndexingDescription": MessageLookupByLibrary.simpleMessage(
"Please note that this will result in a higher bandwidth and battery usage until all items are indexed."),
"feedback": MessageLookupByLibrary.simpleMessage("Tilbakemelding"),
"fileTypes": MessageLookupByLibrary.simpleMessage("File types"),
"foundFaces": MessageLookupByLibrary.simpleMessage("Found faces"),

View File

@@ -115,8 +115,6 @@ class MessageLookup extends MessageLookupByLibrary {
"Wprowadź swój klucz odzyskiwania"),
"faceRecognition":
MessageLookupByLibrary.simpleMessage("Face recognition"),
"faceRecognitionIndexingDescription": MessageLookupByLibrary.simpleMessage(
"Please note that this will result in a higher bandwidth and battery usage until all items are indexed."),
"feedback": MessageLookupByLibrary.simpleMessage("Informacja zwrotna"),
"fileTypes": MessageLookupByLibrary.simpleMessage("File types"),
"forgotPassword":

View File

@@ -721,8 +721,6 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Exportar seus dados"),
"faceRecognition":
MessageLookupByLibrary.simpleMessage("Reconhecimento facial"),
"faceRecognitionIndexingDescription": MessageLookupByLibrary.simpleMessage(
"Por favor, note que isso resultará em uma largura de banda maior e uso de bateria até que todos os itens sejam indexados."),
"faces": MessageLookupByLibrary.simpleMessage("Rostos"),
"failedToApplyCode":
MessageLookupByLibrary.simpleMessage("Falha ao aplicar o código"),
@@ -950,8 +948,6 @@ class MessageLookup extends MessageLookupByLibrary {
"machineLearning":
MessageLookupByLibrary.simpleMessage("Aprendizagem de máquina"),
"magicSearch": MessageLookupByLibrary.simpleMessage("Busca mágica"),
"magicSearchDescription": MessageLookupByLibrary.simpleMessage(
"Por favor, note que isso resultará em uma largura de banda maior e uso de bateria até que todos os itens sejam indexados."),
"manage": MessageLookupByLibrary.simpleMessage("Gerenciar"),
"manageDeviceStorage": MessageLookupByLibrary.simpleMessage(
"Gerenciar o armazenamento do dispositivo"),

View File

@@ -596,8 +596,6 @@ class MessageLookup extends MessageLookupByLibrary {
"exportYourData": MessageLookupByLibrary.simpleMessage("导出您的数据"),
"faceRecognition":
MessageLookupByLibrary.simpleMessage("Face recognition"),
"faceRecognitionIndexingDescription": MessageLookupByLibrary.simpleMessage(
"Please note that this will result in a higher bandwidth and battery usage until all items are indexed."),
"faces": MessageLookupByLibrary.simpleMessage("人脸"),
"failedToApplyCode": MessageLookupByLibrary.simpleMessage("无法使用此代码"),
"failedToCancel": MessageLookupByLibrary.simpleMessage("取消失败"),
@@ -779,8 +777,6 @@ class MessageLookup extends MessageLookupByLibrary {
"lostDevice": MessageLookupByLibrary.simpleMessage("设备丢失?"),
"machineLearning": MessageLookupByLibrary.simpleMessage("机器学习"),
"magicSearch": MessageLookupByLibrary.simpleMessage("魔法搜索"),
"magicSearchDescription": MessageLookupByLibrary.simpleMessage(
"请注意,在所有项目完成索引之前,这将使用更高的带宽和电量。"),
"manage": MessageLookupByLibrary.simpleMessage("管理"),
"manageDeviceStorage": MessageLookupByLibrary.simpleMessage("管理设备存储"),
"manageFamily": MessageLookupByLibrary.simpleMessage("管理家庭计划"),

View File

@@ -2876,11 +2876,11 @@ class S {
);
}
/// `Please note that this will result in a higher bandwidth and battery usage until all items are indexed.`
String get magicSearchDescription {
/// `Please note that ML indexing will result in a higher bandwidth and battery usage until all items are indexed.`
String get mlIndexingDescription {
return Intl.message(
'Please note that this will result in a higher bandwidth and battery usage until all items are indexed.',
name: 'magicSearchDescription',
'Please note that ML indexing will result in a higher bandwidth and battery usage until all items are indexed.',
name: 'mlIndexingDescription',
desc: '',
args: [],
);
@@ -8764,16 +8764,6 @@ class S {
);
}
/// `Please note that this will result in a higher bandwidth and battery usage until all items are indexed.`
String get faceRecognitionIndexingDescription {
return Intl.message(
'Please note that this will result in a higher bandwidth and battery usage until all items are indexed.',
name: 'faceRecognitionIndexingDescription',
desc: '',
args: [],
);
}
/// `Found faces`
String get foundFaces {
return Intl.message(

View File

@@ -409,7 +409,7 @@
"manageDeviceStorage": "Manage device storage",
"machineLearning": "Machine learning",
"magicSearch": "Magic search",
"magicSearchDescription": "Please note that this will result in a higher bandwidth and battery usage until all items are indexed.",
"mlIndexingDescription": "Please note that ML indexing will result in a higher bandwidth and battery usage until all items are indexed.",
"loadingModel": "Downloading models...",
"waitingForWifi": "Waiting for WiFi...",
"status": "Status",
@@ -1233,7 +1233,6 @@
"autoPair": "Auto pair",
"pairWithPin": "Pair with PIN",
"faceRecognition": "Face recognition",
"faceRecognitionIndexingDescription": "Please note that this will result in a higher bandwidth and battery usage until all items are indexed.",
"foundFaces": "Found faces",
"clusteringProgress": "Clustering progress",
"indexingIsPaused": "Indexing is paused. It will automatically resume when device is ready."

View File

@@ -854,7 +854,7 @@ class FaceMlService {
return true;
} catch (e, s) {
_logger.severe(
"Failed to analyze using FaceML for image with ID: ${enteFile.uploadedFileID}",
"Failed to analyze using FaceML for image with ID: ${enteFile.uploadedFileID}. Not storing any faces, which means it will be automatically retried later.",
e,
s,
);
@@ -871,7 +871,7 @@ class FaceMlService {
if (filePath == null) {
_logger.severe(
"Failed to get any data for enteFile with uploadedFileID ${enteFile.uploadedFileID}",
"Failed to get any data for enteFile with uploadedFileID ${enteFile.uploadedFileID} since its file path is null",
);
throw CouldNotRetrieveAnyFileData();
}
@@ -1018,11 +1018,20 @@ class FaceMlService {
throw ThumbnailRetrievalException(e.toString(), s);
}
} else {
file = await getFile(enteFile, isOrigin: true);
try {
file = await getFile(enteFile, isOrigin: true);
} catch (e, s) {
_logger.severe(
"Could not get file for $enteFile",
e,
s,
);
}
// TODO: This is returning null for Pragadees for all files, so something is wrong here!
}
if (file == null) {
_logger.warning("Could not get file for $enteFile");
_logger
.warning("Could not get file for $enteFile of type ${enteFile.fileType.toString()}");
imagePath = null;
break;
}
@@ -1173,13 +1182,13 @@ class FaceMlService {
/// Checks if the ente file to be analyzed actually can be analyzed: it must be uploaded and in the correct format.
void _checkEnteFileForID(EnteFile enteFile) {
if (_skipAnalysisEnteFile(enteFile, <int, int>{})) {
_logger.warning(
'''Skipped analysis of image with enteFile, it might be the wrong format or has no uploadedFileID, or MLController doesn't allow it to run.
final String logString =
'''Skipped analysis of image with enteFile, it might be the wrong format or has no uploadedFileID, or MLController doesn't allow it to run.
enteFile: ${enteFile.toString()}
''',
);
''';
_logger.warning(logString);
_logStatus();
throw CouldNotRetrieveAnyFileData();
throw GeneralFaceMlException(logString);
}
}

View File

@@ -36,23 +36,19 @@ class MachineLearningSettingsPage extends StatefulWidget {
const MachineLearningSettingsPage({super.key});
@override
State<MachineLearningSettingsPage> createState() =>
_MachineLearningSettingsPageState();
State<MachineLearningSettingsPage> createState() => _MachineLearningSettingsPageState();
}
class _MachineLearningSettingsPageState
extends State<MachineLearningSettingsPage> {
class _MachineLearningSettingsPageState extends State<MachineLearningSettingsPage> {
late InitializationState _state;
final EnteWakeLock _wakeLock = EnteWakeLock();
late StreamSubscription<MLFrameworkInitializationUpdateEvent>
_eventSubscription;
late StreamSubscription<MLFrameworkInitializationUpdateEvent> _eventSubscription;
@override
void initState() {
super.initState();
_eventSubscription =
Bus.instance.on<MLFrameworkInitializationUpdateEvent>().listen((event) {
_eventSubscription = Bus.instance.on<MLFrameworkInitializationUpdateEvent>().listen((event) {
_fetchState();
setState(() {});
});
@@ -95,6 +91,21 @@ class _MachineLearningSettingsPageState
),
],
),
SliverList(
delegate: SliverChildBuilderDelegate(
(delegateBuildContext, index) => Padding(
padding: const EdgeInsets.only(left: 16, right: 16),
child: Text(
S.of(context).mlIndexingDescription,
textAlign: TextAlign.left,
style: getEnteTextTheme(context)
.mini
.copyWith(color: getEnteColorScheme(context).textMuted),
),
),
childCount: 1,
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(delegateBuildContext, index) {
@@ -107,9 +118,7 @@ class _MachineLearningSettingsPageState
children: [
_getMagicSearchSettings(context),
const SizedBox(height: 12),
facesFlag
? _getFacesSearchSettings(context)
: const SizedBox.shrink(),
facesFlag ? _getFacesSearchSettings(context) : const SizedBox.shrink(),
],
),
),
@@ -141,8 +150,7 @@ class _MachineLearningSettingsPageState
);
if (LocalSettings.instance.hasEnabledMagicSearch()) {
unawaited(
SemanticSearchService.instance
.init(shouldSyncImmediately: true),
SemanticSearchService.instance.init(shouldSyncImmediately: true),
);
} else {
await SemanticSearchService.instance.clearQueue();
@@ -154,12 +162,6 @@ class _MachineLearningSettingsPageState
alignCaptionedTextToLeft: true,
isGestureDetectorDisabled: true,
),
const SizedBox(
height: 4,
),
MenuSectionDescriptionWidget(
content: S.of(context).magicSearchDescription,
),
const SizedBox(
height: 12,
),
@@ -209,8 +211,7 @@ class _MachineLearningSettingsPageState
trailingWidget: ToggleSwitchWidget(
value: () => LocalSettings.instance.isFaceIndexingEnabled,
onChanged: () async {
final isEnabled =
await LocalSettings.instance.toggleFaceIndexing();
final isEnabled = await LocalSettings.instance.toggleFaceIndexing();
if (isEnabled) {
unawaited(FaceMlService.instance.ensureInitialized());
} else {
@@ -225,18 +226,10 @@ class _MachineLearningSettingsPageState
alignCaptionedTextToLeft: true,
isGestureDetectorDisabled: true,
),
const SizedBox(
height: 4,
),
MenuSectionDescriptionWidget(
content: S.of(context).faceRecognitionIndexingDescription,
),
const SizedBox(
height: 12,
),
hasEnabled
? const FaceRecognitionStatusWidget()
: const SizedBox.shrink(),
hasEnabled ? const FaceRecognitionStatusWidget() : const SizedBox.shrink(),
],
);
}
@@ -259,8 +252,7 @@ class _ModelLoadingStateState extends State<ModelLoadingState> {
final Map<String, (int, int)> _progressMap = {};
@override
void initState() {
_progressStream =
RemoteAssetsService.instance.progressStream.listen((event) {
_progressStream = RemoteAssetsService.instance.progressStream.listen((event) {
final String url = event.$1;
String title = "";
if (url.contains("clip-image")) {
@@ -338,20 +330,17 @@ class MagicSearchIndexStatsWidget extends StatefulWidget {
});
@override
State<MagicSearchIndexStatsWidget> createState() =>
_MagicSearchIndexStatsWidgetState();
State<MagicSearchIndexStatsWidget> createState() => _MagicSearchIndexStatsWidgetState();
}
class _MagicSearchIndexStatsWidgetState
extends State<MagicSearchIndexStatsWidget> {
class _MagicSearchIndexStatsWidgetState extends State<MagicSearchIndexStatsWidget> {
IndexStatus? _status;
late StreamSubscription<EmbeddingCacheUpdatedEvent> _eventSubscription;
@override
void initState() {
super.initState();
_eventSubscription =
Bus.instance.on<EmbeddingCacheUpdatedEvent>().listen((event) {
_eventSubscription = Bus.instance.on<EmbeddingCacheUpdatedEvent>().listen((event) {
_fetchIndexStatus();
});
_fetchIndexStatus();
@@ -427,12 +416,10 @@ class FaceRecognitionStatusWidget extends StatefulWidget {
});
@override
State<FaceRecognitionStatusWidget> createState() =>
FaceRecognitionStatusWidgetState();
State<FaceRecognitionStatusWidget> createState() => FaceRecognitionStatusWidgetState();
}
class FaceRecognitionStatusWidgetState
extends State<FaceRecognitionStatusWidget> {
class FaceRecognitionStatusWidgetState extends State<FaceRecognitionStatusWidget> {
Timer? _timer;
@override
void initState() {
@@ -446,22 +433,15 @@ class FaceRecognitionStatusWidgetState
Future<(int, int, double, bool)> getIndexStatus() async {
try {
final indexedFiles = await FaceMLDataDB.instance
.getIndexedFileCount(minimumMlVersion: faceMlVersion);
final indexedFiles =
await FaceMLDataDB.instance.getIndexedFileCount(minimumMlVersion: faceMlVersion);
final indexableFiles = (await getIndexableFileIDs()).length;
final showIndexedFiles = min(indexedFiles, indexableFiles);
final pendingFiles = max(indexableFiles - indexedFiles, 0);
final clusteringDoneRatio =
await FaceMLDataDB.instance.getClusteredToIndexableFilesRatio();
final bool deviceIsHealthy =
MachineLearningController.instance.isDeviceHealthy;
final clusteringDoneRatio = await FaceMLDataDB.instance.getClusteredToIndexableFilesRatio();
final bool deviceIsHealthy = MachineLearningController.instance.isDeviceHealthy;
return (
showIndexedFiles,
pendingFiles,
clusteringDoneRatio,
deviceIsHealthy
);
return (showIndexedFiles, pendingFiles, clusteringDoneRatio, deviceIsHealthy);
} catch (e, s) {
_logger.severe('Error getting face recognition status', e, s);
rethrow;
@@ -491,12 +471,10 @@ class FaceRecognitionStatusWidgetState
final int indexedFiles = snapshot.data!.$1;
final int pendingFiles = snapshot.data!.$2;
final double clusteringDoneRatio = snapshot.data!.$3;
final double clusteringPercentage =
(clusteringDoneRatio * 100).clamp(0, 100);
final double clusteringPercentage = (clusteringDoneRatio * 100).clamp(0, 100);
final bool isDeviceHealthy = snapshot.data!.$4;
if (!isDeviceHealthy &&
(pendingFiles > 0 || clusteringPercentage < 99)) {
if (!isDeviceHealthy && (pendingFiles > 0 || clusteringPercentage < 99)) {
return MenuSectionDescriptionWidget(
content: S.of(context).indexingIsPaused,
);
@@ -542,8 +520,7 @@ class FaceRecognitionStatusWidgetState
alignCaptionedTextToLeft: true,
isGestureDetectorDisabled: true,
key: ValueKey(
"clustering_progress_" +
clusteringPercentage.toStringAsFixed(0),
"clustering_progress_" + clusteringPercentage.toStringAsFixed(0),
),
),
],

View File

@@ -49,14 +49,14 @@ Future<dynamic> showAssignPersonAction(
BuildContext context, {
required int clusterID,
PersonActionType actionType = PersonActionType.assignPerson,
bool showOptionToCreateNewAlbum = true,
bool showOptionToAddNewPerson = true,
}) {
return showBarModalBottomSheet(
context: context,
builder: (context) {
return PersonActionSheet(
actionType: actionType,
showOptionToCreateNewPerson: showOptionToCreateNewAlbum,
showOptionToCreateNewPerson: showOptionToAddNewPerson,
cluserID: clusterID,
);
},

View File

@@ -19,6 +19,8 @@ class PersonRowItem extends StatelessWidget {
Widget build(BuildContext context) {
return ListTile(
dense: false,
minLeadingWidth: 0,
contentPadding: const EdgeInsets.symmetric(horizontal: 0),
leading: SizedBox(
width: 56,
height: 56,

View File

@@ -12,7 +12,7 @@ description: ente photos application
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 0.8.124+644
version: 0.8.125+645
publish_to: none
environment: