Skip to content

Commit

Permalink
fix: 4066 - predownload and top 1K download as background tasks (#4131)
Browse files Browse the repository at this point in the history
Deleted file:
* `products_preload_helper.dart`

New files:
* `background_task.dart`: Abstract background task.
* `background_task_full_refresh.dart`: Background task about refreshing all the already downloaded products.
* `background_task_offline.dart`: Background task about pre-downloading top n products for offline usage.

Impacted files:
* `background_task_barcode.dart`
* `background_task_crop.dart`: refactored
* `background_task_details.dart`: refactored
* `background_task_hunger_games.dart`: refactored
* `background_task_image.dart`: refactored
* `background_task_manager.dart`
* `background_task_refresh_later.dart`: refactored
* `background_task_unselect.dart`: refactored
* `background_task_upload.dart`: refactored
* `offline_data_page.dart`: now using background task for offline and full refresh tasks
* `offline_tasks_page.dart`: dealing with the "no barcode" case
* `operation_type.dart`: added values `offline` and `fullRefresh`; added `processName` field; added a `BackgroundTask` constructor
* `product_refresher.dart`: bug fix
  • Loading branch information
monsieurtanuki committed Jun 13, 2023
1 parent e1912c8 commit 55b4894
Show file tree
Hide file tree
Showing 17 changed files with 523 additions and 513 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,42 @@ import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:openfoodfacts/openfoodfacts.dart';
import 'package:smooth_app/background/background_task_crop.dart';
import 'package:smooth_app/background/background_task_details.dart';
import 'package:smooth_app/background/background_task_hunger_games.dart';
import 'package:smooth_app/background/background_task_image.dart';
import 'package:smooth_app/background/background_task_manager.dart';
import 'package:smooth_app/background/background_task_refresh_later.dart';
import 'package:smooth_app/background/background_task_unselect.dart';
import 'package:smooth_app/database/local_database.dart';
import 'package:smooth_app/generic_lib/duration_constants.dart';
import 'package:smooth_app/pages/product/common/product_refresher.dart';

/// Abstract background task.
abstract class AbstractBackgroundTask {
const AbstractBackgroundTask({
abstract class BackgroundTask {
const BackgroundTask({
required this.processName,
required this.uniqueId,
required this.barcode,
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.
Expand All @@ -37,27 +50,24 @@ abstract class AbstractBackgroundTask {
/// Generic task identifier, like "details:categories for barcode 1234", needed e.g. for task overwriting".
final String stamp;

final String barcode;
final String languageCode;
final String user;
final String country;

Map<String, dynamic> toJson();
@mustCallSuper
Map<String, dynamic> toJson() => <String, dynamic>{
_jsonTagProcessName: processName,
_jsonTagUniqueId: uniqueId,
_jsonTagLanguageCode: languageCode,
_jsonTagUser: user,
_jsonTagCountry: country,
_jsonTagStamp: stamp,
};

/// Returns the deserialized background task if possible, or null.
static AbstractBackgroundTask? fromJson(final Map<String, dynamic> map) =>
BackgroundTaskDetails.fromJson(map) ??
BackgroundTaskImage.fromJson(map) ??
BackgroundTaskUnselect.fromJson(map) ??
BackgroundTaskHungerGames.fromJson(map) ??
BackgroundTaskCrop.fromJson(map) ??
BackgroundTaskRefreshLater.fromJson(map);
/// Executes the background task: upload, download, update locally.
Future<void> execute(final LocalDatabase localDatabase);

/// Executes the background task: upload, download, update locally.
Future<void> execute(final LocalDatabase localDatabase) async {
await upload();
await _downloadAndRefresh(localDatabase);
}
/// Runs _instantly_ temporary code in order to "fake" the background task.
///
Expand All @@ -80,10 +90,6 @@ abstract class AbstractBackgroundTask {
) async =>
localDatabase.upToDate.terminate(uniqueId);

/// Uploads data changes.
@protected
Future<void> upload();

/// Returns true if the task may run now.
///
/// Most tasks should always run immediately, but some should not, like
Expand Down Expand Up @@ -131,13 +137,6 @@ abstract class AbstractBackgroundTask {
@protected
User getUser() => User.fromJson(jsonDecode(user) as Map<String, dynamic>);

/// Downloads the whole product, updates locally.
Future<void> _downloadAndRefresh(final LocalDatabase localDatabase) async =>
ProductRefresher().silentFetchAndRefresh(
barcode: barcode,
localDatabase: localDatabase,
);

/// Checks that everything is fine and fix things if needed + if possible.
///
/// To be run systematically for each task.
Expand Down
50 changes: 50 additions & 0 deletions packages/smooth_app/lib/background/background_task_barcode.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import 'package:flutter/foundation.dart';
import 'package:smooth_app/background/background_task.dart';
import 'package:smooth_app/database/local_database.dart';
import 'package:smooth_app/pages/product/common/product_refresher.dart';

/// Abstract background task that involves a single barcode.
abstract class BackgroundTaskBarcode extends BackgroundTask {
const BackgroundTaskBarcode({
required super.processName,
required super.uniqueId,
required super.languageCode,
required super.user,
required super.country,
required super.stamp,
required this.barcode,
});

BackgroundTaskBarcode.fromJson(Map<String, dynamic> json)
: barcode = json[_jsonTagBarcode] as String,
super.fromJson(json);

final String barcode;

static const String _jsonTagBarcode = 'barcode';

@override
Map<String, dynamic> toJson() {
final Map<String, dynamic> result = super.toJson();
result[_jsonTagBarcode] = barcode;
return result;
}

/// Uploads data changes.
@protected
Future<void> upload();

/// Executes the background task: upload, download, update locally.
@override
Future<void> execute(final LocalDatabase localDatabase) async {
await upload();
await _downloadAndRefresh(localDatabase);
}

/// Downloads the whole product, updates locally.
Future<void> _downloadAndRefresh(final LocalDatabase localDatabase) async =>
ProductRefresher().silentFetchAndRefresh(
barcode: barcode,
localDatabase: localDatabase,
);
}
63 changes: 11 additions & 52 deletions packages/smooth_app/lib/background/background_task_crop.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:openfoodfacts/openfoodfacts.dart';
import 'package:provider/provider.dart';
import 'package:smooth_app/background/abstract_background_task.dart';
import 'package:smooth_app/background/background_task_barcode.dart';
import 'package:smooth_app/background/background_task_refresh_later.dart';
import 'package:smooth_app/background/background_task_upload.dart';
import 'package:smooth_app/data_models/operation_type.dart';
Expand All @@ -32,62 +32,21 @@ class BackgroundTaskCrop extends BackgroundTaskUpload {
required this.imageId,
});

BackgroundTaskCrop._fromJson(Map<String, dynamic> json)
: this._(
processName: json['processName'] as String,
uniqueId: json['uniqueId'] as String,
barcode: json['barcode'] as String,
languageCode: json['languageCode'] as String,
user: json['user'] as String,
country: json['country'] as String,
imageId: json['imageId'] as int,
imageField: json['imageField'] as String,
croppedPath: json['croppedPath'] as String,
rotationDegrees: json['rotation'] as int,
cropX1: json['x1'] as int? ?? 0,
cropY1: json['y1'] as int? ?? 0,
cropX2: json['x2'] as int? ?? 0,
cropY2: json['y2'] as int? ?? 0,
stamp: json['stamp'] as String,
);
BackgroundTaskCrop.fromJson(Map<String, dynamic> json)
: imageId = json[_jsonTagImageId] as int,
super.fromJson(json);

/// Task ID.
static const String _PROCESS_NAME = 'IMAGE_CROP';
static const String _jsonTagImageId = 'imageId';

static const OperationType _operationType = OperationType.crop;

final int imageId;

@override
Map<String, dynamic> toJson() => <String, dynamic>{
'processName': processName,
'uniqueId': uniqueId,
'barcode': barcode,
'languageCode': languageCode,
'user': user,
'country': country,
'imageId': imageId,
'imageField': imageField,
'croppedPath': croppedPath,
'stamp': stamp,
'rotation': rotationDegrees,
'x1': cropX1,
'y1': cropY1,
'x2': cropX2,
'y2': cropY2,
};

/// Returns the deserialized background task if possible, or null.
static AbstractBackgroundTask? fromJson(final Map<String, dynamic> map) {
try {
final AbstractBackgroundTask result = BackgroundTaskCrop._fromJson(map);
if (result.processName == _PROCESS_NAME) {
return result;
}
} catch (e) {
//
}
return null;
Map<String, dynamic> toJson() {
final Map<String, dynamic> result = super.toJson();
result[_jsonTagImageId] = imageId;
return result;
}

/// Adds the background task about uploading a product image.
Expand All @@ -109,7 +68,7 @@ class BackgroundTaskCrop extends BackgroundTaskUpload {
localDatabase,
barcode,
);
final AbstractBackgroundTask task = _getNewTask(
final BackgroundTaskBarcode task = _getNewTask(
language,
barcode,
imageId,
Expand Down Expand Up @@ -146,7 +105,7 @@ class BackgroundTaskCrop extends BackgroundTaskUpload {
BackgroundTaskCrop._(
uniqueId: uniqueId,
barcode: barcode,
processName: _PROCESS_NAME,
processName: _operationType.processName,
imageId: imageId,
imageField: imageField.offTag,
croppedPath: croppedFile.path,
Expand Down
61 changes: 14 additions & 47 deletions packages/smooth_app/lib/background/background_task_details.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import 'dart:convert';
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:openfoodfacts/openfoodfacts.dart';
import 'package:provider/provider.dart';
import 'package:smooth_app/background/abstract_background_task.dart';
import 'package:smooth_app/background/background_task_barcode.dart';
import 'package:smooth_app/data_models/operation_type.dart';
import 'package:smooth_app/database/local_database.dart';
import 'package:smooth_app/query/product_query.dart';
Expand Down Expand Up @@ -32,7 +32,7 @@ enum BackgroundTaskDetailsStamp {
}

/// Background task that changes product details (data, but no image upload).
class BackgroundTaskDetails extends AbstractBackgroundTask {
class BackgroundTaskDetails extends BackgroundTaskBarcode {
const BackgroundTaskDetails._({
required super.processName,
required super.uniqueId,
Expand All @@ -44,55 +44,22 @@ class BackgroundTaskDetails extends AbstractBackgroundTask {
required this.inputMap,
});

BackgroundTaskDetails._fromJson(Map<String, dynamic> json)
: this._(
processName: json['processName'] as String,
uniqueId: json['uniqueId'] as String,
barcode: json['barcode'] as String,
languageCode: json['languageCode'] as String,
user: json['user'] as String,
country: json['country'] as String,
inputMap: json['inputMap'] as String,
// dealing with when 'stamp' did not exist
stamp: json.containsKey('stamp')
? json['stamp'] as String
: getStamp(
json['barcode'] as String,
'${Random().nextInt(1000000000)}',
),
);

/// Task ID.
static const String _PROCESS_NAME = 'PRODUCT_EDIT';
BackgroundTaskDetails.fromJson(Map<String, dynamic> json)
: inputMap = json[_jsonTagInputMap] as String,
super.fromJson(json);

static const String _jsonTagInputMap = 'inputMap';

static const OperationType _operationType = OperationType.details;

/// Serialized product.
final String inputMap;

@override
Map<String, dynamic> toJson() => <String, dynamic>{
'processName': processName,
'uniqueId': uniqueId,
'barcode': barcode,
'languageCode': languageCode,
'user': user,
'country': country,
'inputMap': inputMap,
'stamp': stamp,
};

/// Returns the deserialized background task if possible, or null.
static BackgroundTaskDetails? fromJson(final Map<String, dynamic> map) {
try {
final BackgroundTaskDetails result = BackgroundTaskDetails._fromJson(map);
if (result.processName == _PROCESS_NAME) {
return result;
}
} catch (e) {
//
}
return null;
Map<String, dynamic> toJson() {
final Map<String, dynamic> result = super.toJson();
result[_jsonTagInputMap] = inputMap;
return result;
}

@override
Expand All @@ -111,7 +78,7 @@ class BackgroundTaskDetails extends AbstractBackgroundTask {
localDatabase,
minimalistProduct.barcode!,
);
final AbstractBackgroundTask task = _getNewTask(
final BackgroundTaskBarcode task = _getNewTask(
minimalistProduct,
uniqueId,
stamp,
Expand All @@ -135,7 +102,7 @@ class BackgroundTaskDetails extends AbstractBackgroundTask {
) =>
BackgroundTaskDetails._(
uniqueId: uniqueId,
processName: _PROCESS_NAME,
processName: _operationType.processName,
barcode: minimalistProduct.barcode!,
languageCode: ProductQuery.getLanguage().code,
inputMap: jsonEncode(minimalistProduct.toJson()),
Expand Down

0 comments on commit 55b4894

Please sign in to comment.