Line data Source code
1 : import 'package:flutter/foundation.dart';
2 : import '../worker.dart';
3 :
4 : /// Parallel chunked HTTP download worker configuration.
5 : ///
6 : /// Splits a single file download into [numChunks] parallel byte-range
7 : /// requests (HTTP/1.1 `Range` header, RFC 7233), downloads them
8 : /// concurrently on the native side, then merges the parts into a single
9 : /// output file — all without loading the file into memory.
10 : ///
11 : /// **When to use over [HttpDownloadWorker]:**
12 : /// - Files larger than ~50 MB where parallel chunks give real speed-up.
13 : /// - Servers that support `Accept-Ranges: bytes` (most CDNs do).
14 : /// - When you want faster downloads on high-bandwidth connections.
15 : ///
16 : /// **Automatic fallback:** If the server does not advertise
17 : /// `Accept-Ranges: bytes` or does not return a `Content-Length`, the
18 : /// worker automatically falls back to a single sequential download
19 : /// (identical to [HttpDownloadWorker]).
20 : ///
21 : /// ## Example
22 : ///
23 : /// ```dart
24 : /// await NativeWorkManager.enqueue(
25 : /// taskId: 'big-video',
26 : /// trigger: TaskTrigger.oneTime(),
27 : /// worker: NativeWorker.parallelHttpDownload(
28 : /// url: 'https://cdn.example.com/movie.mp4',
29 : /// savePath: '/data/user/0/com.example/files/movie.mp4',
30 : /// numChunks: 4, // default
31 : /// showNotification: true,
32 : /// ),
33 : /// constraints: Constraints.networkRequired,
34 : /// );
35 : /// ```
36 : ///
37 : /// ## Progress events
38 : ///
39 : /// Progress is reported as aggregate bytes across all chunks, so the
40 : /// [TaskProgress.progress] field rises smoothly from 0 to 100 even
41 : /// when chunks finish out of order.
42 : ///
43 : /// ## Resume support
44 : ///
45 : /// Each chunk is saved to `savePath.partN` temporarily. If the task is
46 : /// interrupted, a subsequent enqueue will pick up any completed parts and
47 : /// only re-download missing or incomplete chunks.
48 : @immutable
49 : final class ParallelHttpDownloadWorker extends Worker {
50 1 : const ParallelHttpDownloadWorker({
51 : required this.url,
52 : required this.savePath,
53 : this.numChunks = 4,
54 : this.headers = const {},
55 : this.timeout = const Duration(minutes: 10),
56 : this.expectedChecksum,
57 : this.checksumAlgorithm = 'SHA-256',
58 : this.showNotification = false,
59 : this.notificationTitle,
60 : this.notificationBody,
61 : this.skipExisting = false,
62 3 : }) : assert(numChunks >= 1 && numChunks <= 16,
63 : 'numChunks must be between 1 and 16');
64 :
65 : /// The URL to download from.
66 : final String url;
67 :
68 : /// Absolute path where the merged file will be saved.
69 : final String savePath;
70 :
71 : /// Number of parallel byte-range chunks (1–16, default 4).
72 : ///
73 : /// A value of 1 is equivalent to a sequential download but still goes
74 : /// through the parallel code path. Values above 8 rarely improve
75 : /// throughput and increase connection overhead.
76 : final int numChunks;
77 :
78 : /// Optional HTTP headers sent with every chunk request.
79 : final Map<String, String> headers;
80 :
81 : /// Per-chunk request timeout (default: 10 minutes).
82 : ///
83 : /// Each chunk has its own independent timeout counter.
84 : final Duration timeout;
85 :
86 : /// Expected checksum for the merged file (optional).
87 : ///
88 : /// Verified after all chunks are merged. Download fails if checksums
89 : /// do not match.
90 : final String? expectedChecksum;
91 :
92 : /// Hashing algorithm for checksum verification (default: `'SHA-256'`).
93 : ///
94 : /// Supported: `'MD5'`, `'SHA-1'`, `'SHA-256'`, `'SHA-512'`.
95 : final String checksumAlgorithm;
96 :
97 : /// Show a system notification with aggregate download progress.
98 : final bool showNotification;
99 :
100 : /// Title for the progress notification.
101 : ///
102 : /// Defaults to the file name derived from [url].
103 : final String? notificationTitle;
104 :
105 : /// Body text for the progress notification.
106 : final String? notificationBody;
107 :
108 : /// Skip the download if [savePath] already exists on disk.
109 : ///
110 : /// Same semantics as [HttpDownloadWorker.skipExisting].
111 : /// Default: `false`
112 : final bool skipExisting;
113 :
114 1 : @override
115 : String get workerClassName => 'ParallelHttpDownloadWorker';
116 :
117 1 : @override
118 1 : Map<String, dynamic> toMap() => {
119 1 : 'workerType': 'parallelHttpDownload',
120 2 : 'url': url,
121 2 : 'savePath': savePath,
122 2 : 'numChunks': numChunks,
123 2 : 'headers': headers,
124 3 : 'timeoutMs': timeout.inMilliseconds,
125 1 : if (expectedChecksum != null) 'expectedChecksum': expectedChecksum,
126 2 : 'checksumAlgorithm': checksumAlgorithm,
127 2 : 'skipExisting': skipExisting,
128 2 : 'showNotification': showNotification,
129 1 : if (notificationTitle != null) 'notificationTitle': notificationTitle,
130 1 : if (notificationBody != null) 'notificationBody': notificationBody,
131 : };
132 : }
|