Files
monsieurtanuki 9829a10cc8 feat: 6591 - update OxF data with Prices data (#6667)
* feat: 6591 - update OxF data with Prices data

New file:
* `price_to_oxf.dart`: Tool to update OxF data from Prices data.

Impacted files:
* `background_task.dart`: unrelated minor refactoring
* `background_task_details.dart`: minor refactoring
* `background_task_price.dart`: now updating OxF products with Prices data
* `simple_input_page_helpers.dart`: minor refactoring

* merging

* format

* sorry about that, couldn't merge "develop"

* effing stupid dart format

* effing stupid dart format, again

---------

Co-authored-by: Pierre Slamich <pierre@openfoodfacts.org>
2025-07-14 16:34:01 +02:00

186 lines
6.5 KiB
Dart

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:openfoodfacts/openfoodfacts.dart';
import 'package:smooth_app/background/background_task_manager.dart';
import 'package:smooth_app/background/background_task_queue.dart';
import 'package:smooth_app/background/background_task_refresh_later.dart';
import 'package:smooth_app/database/local_database.dart';
import 'package:smooth_app/generic_lib/duration_constants.dart';
import 'package:smooth_app/l10n/app_localizations.dart';
import 'package:smooth_app/query/product_query.dart';
import 'package:smooth_app/widgets/smooth_floating_message.dart';
/// Abstract background task.
abstract class BackgroundTask {
BackgroundTask({
required this.processName,
required this.uniqueId,
required this.stamp,
final OpenFoodFactsLanguage? language,
}) // TODO(monsieurtanuki): don't store the password in a clear format...
// TODO(monsieurtanuki): store the uriProductHelper as well
: user = jsonEncode(ProductQuery.getWriteUser().toJson()),
country = ProductQuery.getCountry().offTag,
languageCode = (language ?? ProductQuery.getLanguage()).offTag;
BackgroundTask._({
required this.processName,
required this.uniqueId,
required this.languageCode,
required this.user,
required this.country,
required this.stamp,
});
BackgroundTask.fromJson(Map<String, dynamic> json)
: this._(
processName: json[_jsonTagProcessName] as String,
uniqueId: json[_jsonTagUniqueId] as String,
languageCode: json[_jsonTagLanguageCode] as String,
user: json[_jsonTagUser] as String,
country: json[_jsonTagCountry] as String,
stamp: json[_jsonTagStamp] as String,
);
static const String _jsonTagProcessName = 'processName';
static const String _jsonTagUniqueId = 'uniqueId';
static const String _jsonTagLanguageCode = 'languageCode';
static const String _jsonTagUser = 'user';
static const String _jsonTagCountry = 'country';
static const String _jsonTagStamp = 'stamp';
static String getProcessName(final Map<String, dynamic> map) =>
map[_jsonTagProcessName] as String;
/// Typically, similar to the name of the class that extends this one.
///
/// To be used when deserializing, in order to check who is who.
final String processName;
/// Unique task identifier, needed e.g. for task overwriting.
final String uniqueId;
/// Generic task identifier, like "details:categories for barcode 1234", needed e.g. for task overwriting".
final String stamp;
final String languageCode;
final String user;
final String country;
@mustCallSuper
Map<String, dynamic> toJson() => <String, dynamic>{
_jsonTagProcessName: processName,
_jsonTagUniqueId: uniqueId,
_jsonTagLanguageCode: languageCode,
_jsonTagUser: user,
_jsonTagCountry: country,
_jsonTagStamp: stamp,
};
/// Executes the background task: upload, download, update locally.
Future<void> execute(final LocalDatabase localDatabase);
/// Executes the background task: upload, download, update locally.
/// Runs _instantly_ temporary code in order to "fake" the background task.
///
/// For instance, here we can pretend that we've changed the product name
/// by doing it locally, but the background task that talks to the server
/// is not even started.
Future<void> preExecute(final LocalDatabase localDatabase);
/// To be executed _after_ the actual run.
///
/// Mostly, cleans the temporary data changes performed in [preExecute].
/// [success] indicates (if `true`) that so far the operation was a success.
/// With that `bool` we're able to deal with 2 cases:
/// 1. everything is fine and we may have to do something more than cleaning
/// 2. something bad happened and we just need to clear the task
@mustCallSuper
Future<void> postExecute(
final LocalDatabase localDatabase,
final bool success,
) async => localDatabase.upToDate.terminate(uniqueId);
/// Returns true if the task may run now.
///
/// Most tasks should always run immediately, but some should not, like
/// [BackgroundTaskRefreshLater].
bool mayRunNow() => true;
/// Floating message when we add the task, like "Added to the task queue!"
/// Also pass an [AlignmentGeometry] to express where it should be displayed
///
/// Null if no message wanted (like, stealth mode).
@protected
(String, AlignmentGeometry)? getFloatingMessage(
final AppLocalizations appLocalizations,
);
/// Adds this task to the [BackgroundTaskManager].
@protected
Future<void> addToManager(
final LocalDatabase localDatabase, {
required final BackgroundTaskQueue queue,
final BuildContext? context,
final bool showSnackBar = true,
}) async {
await BackgroundTaskManager.getInstance(
localDatabase,
queue: queue,
).add(this);
if (context == null || !context.mounted) {
return;
}
if (!showSnackBar) {
return;
}
if (getFloatingMessage(AppLocalizations.of(context)) case (
final String message,
final AlignmentGeometry alignment,
)) {
SmoothFloatingMessage.loading(
message: message,
).show(context, duration: SnackBarDuration.medium, alignment: alignment);
}
}
@protected
OpenFoodFactsLanguage getLanguage() => LanguageHelper.fromJson(languageCode);
@protected
OpenFoodFactsCountry? getCountry() =>
OpenFoodFactsCountry.fromOffTag(country);
@protected
User getUser() {
final User storedUser = User.fromJson(
jsonDecode(user) as Map<String, dynamic>,
);
final User currentUser = ProductQuery.getWriteUser();
if (storedUser.userId == currentUser.userId) {
// with a latest password.
return currentUser;
}
return storedUser;
}
/// Checks that everything is fine and fix things if needed + if possible.
///
/// To be run systematically for each task.
/// Especially useful for transient files: if a user closed the app before
/// successfully completing the upload task, the transient file - that is just
/// a static variable - won't be there at app restart. Unless you recover.
Future<void> recover(final LocalDatabase localDatabase) async {}
/// Returns true if the task ends calling another task for immediate exec.
///
/// We return true only in rare cases. Typically, when we split an task in
/// subtasks that call the next one at the end.
bool get hasImmediateNextTask => false;
/// Returns true if tasks with the same stamp would overwrite each-other.
bool isDeduplicable() => true;
}