LCOV - code coverage report
Current view: top level - src/workers - parallel_http_upload_worker.dart Coverage Total Hit
Test: lcov.info Lines: 100.0 % 27 27
Test Date: 2026-04-30 18:23:23 Functions: - 0 0

            Line data    Source code
       1              : import 'package:flutter/foundation.dart';
       2              : import '../worker.dart';
       3              : 
       4              : /// Parallel multi-file HTTP upload worker configuration.
       5              : ///
       6              : /// Uploads each file in [files] as a **separate** concurrent multipart request
       7              : /// (one request per file) with a per-host concurrency limit.  This differs
       8              : /// from [HttpUploadWorker], which bundles all files into a single request.
       9              : ///
      10              : /// **When to use over [HttpUploadWorker]:**
      11              : /// - You need to upload many files independently (each gets its own response).
      12              : /// - You want individual retry-per-file semantics (failed files retry without
      13              : ///   re-sending already-succeeded files).
      14              : /// - The server accepts one file per request (most REST APIs).
      15              : ///
      16              : /// **Per-host concurrency:**
      17              : /// [maxConcurrent] caps how many simultaneous uploads run against the same
      18              : /// host at once, preventing connection-pool exhaustion and rate-limiting.
      19              : ///
      20              : /// ## Example
      21              : ///
      22              : /// ```dart
      23              : /// await NativeWorkManager.enqueue(
      24              : ///   taskId: 'batch-photos',
      25              : ///   trigger: TaskTrigger.oneTime(),
      26              : ///   worker: NativeWorker.parallelHttpUpload(
      27              : ///     url: 'https://api.example.com/photos',
      28              : ///     files: [
      29              : ///       UploadFile(filePath: '/data/user/0/.../img1.jpg'),
      30              : ///       UploadFile(filePath: '/data/user/0/.../img2.jpg', fieldName: 'photo'),
      31              : ///     ],
      32              : ///     maxConcurrent: 3,
      33              : ///     maxRetries: 2,
      34              : ///   ),
      35              : ///   constraints: Constraints.networkRequired,
      36              : /// );
      37              : /// ```
      38              : ///
      39              : /// ## Progress events
      40              : ///
      41              : /// One [TaskProgress] event is emitted per uploaded chunk across all files, so
      42              : /// [TaskProgress.progress] rises smoothly from 0 to 100. The message field
      43              : /// includes per-file counts (`"Uploaded 2/5 files"`).
      44              : ///
      45              : /// ## Result data
      46              : ///
      47              : /// The success result (`TaskEvent.resultData`) map contains:
      48              : /// - `uploadedCount` — number of files successfully uploaded.
      49              : /// - `failedCount`   — number of files that ultimately failed.
      50              : /// - `totalBytes`    — aggregate bytes sent across all files.
      51              : /// - `fileResults`   — list of per-file result maps.
      52              : @immutable
      53              : final class ParallelHttpUploadWorker extends Worker {
      54              :   // NET-028: use RangeError / ArgumentError instead of assert() so validation
      55              :   // fires in release builds too (assert is stripped when asserts are disabled).
      56            2 :   ParallelHttpUploadWorker({
      57              :     required this.url,
      58              :     required this.files,
      59              :     this.headers = const {},
      60              :     this.fields = const {},
      61              :     this.maxConcurrent = 3,
      62              :     this.maxRetries = 1,
      63              :     this.timeout = const Duration(minutes: 5),
      64              :     this.showNotification = false,
      65              :     this.notificationTitle,
      66              :     this.notificationBody,
      67              :   }) {
      68            4 :     if (files.isEmpty) {
      69            4 :       throw ArgumentError.value(files, 'files', 'must not be empty');
      70              :     }
      71            8 :     if (maxConcurrent < 1 || maxConcurrent > 16) {
      72            4 :       throw RangeError.range(maxConcurrent, 1, 16, 'maxConcurrent');
      73              :     }
      74            8 :     if (maxRetries < 0 || maxRetries > 5) {
      75            4 :       throw RangeError.range(maxRetries, 0, 5, 'maxRetries');
      76              :     }
      77              :   }
      78              : 
      79              :   /// The endpoint URL that receives each file upload.
      80              :   final String url;
      81              : 
      82              :   /// List of files to upload.
      83              :   final List<UploadFile> files;
      84              : 
      85              :   /// HTTP headers added to every upload request.
      86              :   ///
      87              :   /// Typical use: `{'Authorization': 'Bearer token'}`.
      88              :   final Map<String, String> headers;
      89              : 
      90              :   /// Additional form fields added to every multipart request alongside the
      91              :   /// file part (e.g. `{'albumId': '42'}`).
      92              :   final Map<String, String> fields;
      93              : 
      94              :   /// Maximum simultaneous uploads per host (1–16, default 3).
      95              :   ///
      96              :   /// Uploads beyond this limit are queued until a slot opens.
      97              :   final int maxConcurrent;
      98              : 
      99              :   /// How many times to retry a failed individual file upload (0–5, default 1).
     100              :   ///
     101              :   /// A retry is attempted only when the server returns a 5xx response or when
     102              :   /// a network error occurs. 4xx responses (e.g. 400, 401) are not retried.
     103              :   final int maxRetries;
     104              : 
     105              :   /// Per-file request timeout (default: 5 minutes).
     106              :   final Duration timeout;
     107              : 
     108              :   /// Show a system notification with aggregate upload progress.
     109              :   final bool showNotification;
     110              : 
     111              :   /// Title for the progress notification.
     112              :   ///
     113              :   /// Defaults to `"Uploading N files"` derived from [files].
     114              :   final String? notificationTitle;
     115              : 
     116              :   /// Body text for the progress notification.
     117              :   final String? notificationBody;
     118              : 
     119            1 :   @override
     120              :   String get workerClassName => 'ParallelHttpUploadWorker';
     121              : 
     122            2 :   @override
     123            2 :   Map<String, dynamic> toMap() => {
     124            2 :         'workerType': 'parallelHttpUpload',
     125            4 :         'url': url,
     126            4 :         'files': files
     127            6 :             .map((f) => {
     128            4 :                   'filePath': f.filePath,
     129            4 :                   'fieldName': f.fieldName,
     130            4 :                   if (f.fileName != null) 'fileName': f.fileName,
     131            4 :                   if (f.mimeType != null) 'mimeType': f.mimeType,
     132              :                 })
     133            2 :             .toList(),
     134            4 :         'headers': headers,
     135            4 :         'fields': fields,
     136            4 :         'maxConcurrent': maxConcurrent,
     137            4 :         'maxRetries': maxRetries,
     138            6 :         'timeoutMs': timeout.inMilliseconds,
     139            4 :         'showNotification': showNotification,
     140            4 :         if (notificationTitle != null) 'notificationTitle': notificationTitle,
     141            4 :         if (notificationBody != null) 'notificationBody': notificationBody,
     142              :       };
     143              : }
     144              : 
     145              : // UploadFile is defined in multi_upload_worker.dart and re-exported via worker.dart.
        

Generated by: LCOV version 2.4-0