LCOV - code coverage report
Current view: top level - src - worker.dart Coverage Total Hit
Test: lcov.info Lines: 98.9 % 90 89
Test Date: 2026-04-30 18:23:23 Functions: - 0 0

            Line data    Source code
       1              : import 'package:flutter/foundation.dart';
       2              : import 'dart:ui';
       3              : import 'native_work_manager.dart';
       4              : 
       5              : // Import all worker implementations
       6              : import 'workers.dart';
       7              : 
       8              : // Export all worker implementations
       9              : export 'workers.dart';
      10              : 
      11              : // Per-category part files for NativeWorker factory methods
      12              : part 'workers/native_worker_http.dart';
      13              : part 'workers/native_worker_custom.dart';
      14              : part 'workers/native_worker_file.dart';
      15              : part 'workers/native_worker_crypto.dart';
      16              : part 'workers/native_worker_image.dart';
      17              : part 'workers/native_worker_pdf.dart';
      18              : part 'workers/native_worker_websocket.dart';
      19              : 
      20              : /// HTTP methods for network workers.
      21              : enum HttpMethod { get, post, put, delete, patch }
      22              : 
      23              : /// Compression levels for file compression.
      24              : enum CompressionLevel {
      25              :   /// Low compression (faster, larger file).
      26              :   low,
      27              : 
      28              :   /// Medium compression (balanced).
      29              :   medium,
      30              : 
      31              :   /// High compression (slower, smaller file).
      32              :   high,
      33              : }
      34              : 
      35              : /// Base class for all worker configurations.
      36              : ///
      37              : /// All built-in workers (HttpDownloadWorker, FileCompressWorker, etc.) extend
      38              : /// this class. Custom native workers use [CustomNativeWorker].
      39              : @immutable
      40              : abstract class Worker {
      41           35 :   const Worker();
      42              : 
      43              :   /// Convert to map for platform channel.
      44              :   Map<String, dynamic> toMap();
      45              : 
      46              :   /// Get the worker class name for native side.
      47              :   String get workerClassName;
      48              : }
      49              : 
      50              : // ═══════════════════════════════════════════════════════════════════════════════
      51              : // NATIVE WORKERS (Mode 1) - Zero Flutter Engine
      52              : // ═══════════════════════════════════════════════════════════════════════════════
      53              : 
      54              : /// Built-in native workers that run WITHOUT Flutter Engine.
      55              : ///
      56              : /// These workers execute using KMP native code, saving ~50MB RAM
      57              : /// compared to Dart-based workers.
      58              : class NativeWorker {
      59            0 :   NativeWorker._();
      60              : 
      61              :   /// Validate URL format and throw helpful error if invalid.
      62           17 :   static void _validateUrl(String url) {
      63           17 :     _validateInput(url, 'URL');
      64           17 :     if (url.isEmpty) {
      65            2 :       throw ArgumentError(
      66              :         'URL cannot be empty.\n'
      67              :         'Provide a valid HTTP/HTTPS URL like "https://api.example.com/endpoint"',
      68              :       );
      69              :     }
      70              : 
      71           17 :     final uri = Uri.tryParse(url);
      72              :     if (uri == null ||
      73           85 :         (!uri.hasScheme || (uri.scheme != 'http' && uri.scheme != 'https'))) {
      74            6 :       throw ArgumentError(
      75              :         'Invalid URL format: "$url"\n'
      76              :         'URL must start with http:// or https://\n'
      77              :         'Example: "https://api.example.com/endpoint"',
      78              :       );
      79              :     }
      80              : 
      81              :     // SECURITY: Enforce HTTPS if configured
      82           19 :     if (NativeWorkManager.enforceHttps && uri.scheme == 'http') {
      83            2 :       throw ArgumentError(
      84              :         'Insecure URL blocked: "$url"\n'
      85              :         'HTTPS is enforced by NativeWorkManager.initialize(enforceHttps: true)',
      86              :       );
      87              :     }
      88              : 
      89              :     // SECURITY: Block private IPs if configured
      90           17 :     if (NativeWorkManager.blockPrivateIPs) {
      91            2 :       final host = uri.host.toLowerCase();
      92            1 :       final isPrivate = host == 'localhost' ||
      93            1 :           host == '127.0.0.1' ||
      94            1 :           host == '::1' ||
      95            1 :           host.startsWith('192.168.') ||
      96            1 :           host.startsWith('10.') ||
      97            2 :           RegExp(r'^172\.(1[6-9]|2[0-9]|3[0-1])\.').hasMatch(host);
      98              : 
      99              :       if (isPrivate) {
     100            2 :         throw ArgumentError(
     101              :           'Private IP URL blocked: "$url"\n'
     102              :           'Requests to private/loopback IPs are blocked by '
     103              :           'NativeWorkManager.initialize(blockPrivateIPs: true) to prevent SSRF.',
     104              :         );
     105              :       }
     106              :     }
     107              :   }
     108              : 
     109              :   /// Check input for malicious patterns (Null bytes, injection chars).
     110           27 :   static void _validateInput(String? value, String label) {
     111              :     if (value == null) return;
     112              : 
     113           27 :     if (value.contains('\u0000')) {
     114            2 :       throw ArgumentError(
     115            2 :           '$label contains null bytes which is not allowed for security reasons.');
     116              :     }
     117              :   }
     118              : 
     119              :   /// Validate file path for path traversal or injection.
     120           22 :   static void _validatePath(String path, String label) {
     121           22 :     _validateInput(path, label);
     122              : 
     123              :     // SECURITY: Prevent path traversal attacks
     124           22 :     final normalized = path.toLowerCase();
     125           44 :     if (normalized.contains('..') || normalized.contains('%2e%2e')) {
     126            2 :       throw ArgumentError(
     127            2 :           '$label cannot contain ".." or encoded dot-segments (path traversal attempt blocked).');
     128              :     }
     129              : 
     130           43 :     if (path.contains(';') || path.contains('|')) {
     131            1 :       throw ArgumentError(
     132            1 :           '$label contains illegal shell injection characters.');
     133              :     }
     134              : 
     135              :     // SECURITY: Require absolute paths (relative paths can be manipulated)
     136              :     // EXCEPTION: Allow placeholders in the format {{taskId.key}} for task chains
     137           21 :     if (!path.startsWith('/') &&
     138            3 :         !path.startsWith('{{') &&
     139            3 :         !path.startsWith(r'C:\') &&
     140            3 :         !path.startsWith(r'\\')) {
     141            6 :       throw ArgumentError(
     142              :         'Relative path not allowed in $label: "$path"\n'
     143              :         'Use absolute paths like "/path/to/file.jpg"\n'
     144              :         'Relative paths are blocked to prevent path traversal attacks',
     145              :       );
     146              :     }
     147              :   }
     148              : 
     149              :   /// Validate file path and throw helpful error if invalid.
     150           22 :   static void _validateFilePath(String path, String parameterName) {
     151           22 :     if (path.isEmpty) {
     152           22 :       throw ArgumentError(
     153              :         '$parameterName cannot be empty.\n'
     154              :         'Provide an absolute file path like "/data/user/0/app/files/data.db"',
     155              :       );
     156              :     }
     157           22 :     _validatePath(path, parameterName);
     158              :   }
     159              : 
     160              :   // ── HTTP workers ────────────────────────────────────────────────────────────
     161              : 
     162              :   /// HTTP request worker (GET, POST, PUT, DELETE).
     163              :   /// See [_buildHttpRequest] (in native_worker_http.dart) for full documentation.
     164           13 :   static Worker httpRequest({
     165              :     required String url,
     166              :     HttpMethod method = HttpMethod.get,
     167              :     Map<String, String> headers = const {},
     168              :     String? body,
     169              :     Duration timeout = const Duration(seconds: 30),
     170              :     TokenRefreshConfig? tokenRefresh,
     171              :   }) =>
     172           13 :       _buildHttpRequest(
     173              :         url: url,
     174              :         method: method,
     175              :         headers: headers,
     176              :         body: body,
     177              :         timeout: timeout,
     178              :         tokenRefresh: tokenRefresh,
     179              :       );
     180              : 
     181              :   /// HTTP file upload worker (multipart).
     182              :   /// See [_buildHttpUpload] (in native_worker_http.dart) for full documentation.
     183            9 :   static Worker httpUpload({
     184              :     required String url,
     185              :     required String filePath,
     186              :     String fileFieldName = 'file',
     187              :     String? fileName,
     188              :     String? mimeType,
     189              :     Map<String, String> headers = const {},
     190              :     Map<String, String> additionalFields = const {},
     191              :     Duration timeout = const Duration(minutes: 5),
     192              :     bool useBackgroundSession = false,
     193              :   }) =>
     194            9 :       _buildHttpUpload(
     195              :         url: url,
     196              :         filePath: filePath,
     197              :         fileFieldName: fileFieldName,
     198              :         fileName: fileName,
     199              :         mimeType: mimeType,
     200              :         headers: headers,
     201              :         additionalFields: additionalFields,
     202              :         timeout: timeout,
     203              :         useBackgroundSession: useBackgroundSession,
     204              :       );
     205              : 
     206              :   /// Upload multiple files in a single multipart/form-data HTTP request.
     207              :   /// See [_buildMultiUpload] (in native_worker_http.dart) for full documentation.
     208            1 :   static MultiUploadWorker multiUpload({
     209              :     required String url,
     210              :     required List<UploadFile> files,
     211              :     Map<String, String> headers = const {},
     212              :     Map<String, String> additionalFields = const {},
     213              :     Duration timeout = const Duration(minutes: 10),
     214              :     bool useBackgroundSession = false,
     215              :   }) =>
     216            1 :       _buildMultiUpload(
     217              :         url: url,
     218              :         files: files,
     219              :         headers: headers,
     220              :         additionalFields: additionalFields,
     221              :         timeout: timeout,
     222              :         useBackgroundSession: useBackgroundSession,
     223              :       );
     224              : 
     225              :   /// Move a file from app-private storage to a shared / public location.
     226              :   /// See [_buildMoveToSharedStorage] (in native_worker_http.dart) for full documentation.
     227            1 :   static MoveToSharedStorageWorker moveToSharedStorage({
     228              :     required String sourcePath,
     229              :     required SharedStorageType storageType,
     230              :     String? fileName,
     231              :     String? mimeType,
     232              :     String? subDir,
     233              :   }) =>
     234            1 :       _buildMoveToSharedStorage(
     235              :         sourcePath: sourcePath,
     236              :         storageType: storageType,
     237              :         fileName: fileName,
     238              :         mimeType: mimeType,
     239              :         subDir: subDir,
     240              :       );
     241              : 
     242              :   /// HTTP file download worker.
     243              :   /// See [_buildHttpDownload] (in native_worker_http.dart) for full documentation.
     244           11 :   static Worker httpDownload({
     245              :     required String url,
     246              :     required String savePath,
     247              :     Map<String, String> headers = const {},
     248              :     Duration timeout = const Duration(minutes: 5),
     249              :     bool enableResume = true,
     250              :     String? expectedChecksum,
     251              :     String checksumAlgorithm = 'SHA-256',
     252              :     bool useBackgroundSession = false,
     253              :     bool skipExisting = false,
     254              :     bool allowPause = false,
     255              :     Map<String, String>? cookies,
     256              :     String? authToken,
     257              :     String authHeaderTemplate = 'Bearer {accessToken}',
     258              :     DuplicatePolicy onDuplicate = DuplicatePolicy.overwrite,
     259              :     bool moveToPublicDownloads = false,
     260              :     bool saveToGallery = false,
     261              :     @Deprecated(
     262              :         'Native ZIP support is removed in v1.1.0. Use Dart "archive" package.')
     263              :     bool extractAfterDownload = false,
     264              :     @Deprecated(
     265              :         'Native ZIP support is removed in v1.1.0. Use Dart "archive" package.')
     266              :     String? extractPath,
     267              :     @Deprecated(
     268              :         'Native ZIP support is removed in v1.1.0. Use Dart "archive" package.')
     269              :     bool deleteArchiveAfterExtract = false,
     270              :   }) =>
     271           11 :       _buildHttpDownload(
     272              :         url: url,
     273              :         savePath: savePath,
     274              :         headers: headers,
     275              :         timeout: timeout,
     276              :         enableResume: enableResume,
     277              :         expectedChecksum: expectedChecksum,
     278              :         checksumAlgorithm: checksumAlgorithm,
     279              :         useBackgroundSession: useBackgroundSession,
     280              :         skipExisting: skipExisting,
     281              :         allowPause: allowPause,
     282              :         cookies: cookies,
     283              :         authToken: authToken,
     284              :         authHeaderTemplate: authHeaderTemplate,
     285              :         onDuplicate: onDuplicate,
     286              :         moveToPublicDownloads: moveToPublicDownloads,
     287              :         saveToGallery: saveToGallery,
     288              :         extractAfterDownload: extractAfterDownload,
     289              :         extractPath: extractPath,
     290              :         deleteArchiveAfterExtract: deleteArchiveAfterExtract,
     291              :       );
     292              : 
     293              :   /// Parallel chunked HTTP download worker.
     294              :   /// See [_buildParallelHttpDownload] (in native_worker_http.dart) for full documentation.
     295            1 :   static Worker parallelHttpDownload({
     296              :     required String url,
     297              :     required String savePath,
     298              :     int numChunks = 4,
     299              :     Map<String, String> headers = const {},
     300              :     Duration timeout = const Duration(minutes: 10),
     301              :     String? expectedChecksum,
     302              :     String checksumAlgorithm = 'SHA-256',
     303              :     bool showNotification = false,
     304              :     String? notificationTitle,
     305              :     String? notificationBody,
     306              :     bool skipExisting = false,
     307              :   }) =>
     308            1 :       _buildParallelHttpDownload(
     309              :         url: url,
     310              :         savePath: savePath,
     311              :         numChunks: numChunks,
     312              :         headers: headers,
     313              :         timeout: timeout,
     314              :         expectedChecksum: expectedChecksum,
     315              :         checksumAlgorithm: checksumAlgorithm,
     316              :         showNotification: showNotification,
     317              :         notificationTitle: notificationTitle,
     318              :         notificationBody: notificationBody,
     319              :         skipExisting: skipExisting,
     320              :       );
     321              : 
     322              :   /// Data sync worker (POST JSON, receive JSON).
     323              :   /// See [_buildHttpSync] (in native_worker_http.dart) for full documentation.
     324            7 :   static Worker httpSync({
     325              :     required String url,
     326              :     HttpMethod method = HttpMethod.post,
     327              :     Map<String, String> headers = const {},
     328              :     Map<String, dynamic>? requestBody,
     329              :     Duration timeout = const Duration(seconds: 60),
     330              :     TokenRefreshConfig? tokenRefresh,
     331              :     RequestSigning? requestSigning,
     332              :   }) =>
     333            7 :       _buildHttpSync(
     334              :         url: url,
     335              :         method: method,
     336              :         headers: headers,
     337              :         requestBody: requestBody,
     338              :         timeout: timeout,
     339              :         tokenRefresh: tokenRefresh,
     340              :         requestSigning: requestSigning,
     341              :       );
     342              : 
     343              :   // ── Custom worker ────────────────────────────────────────────────────────────
     344              : 
     345              :   /// Custom native worker for user-defined implementations.
     346              :   /// See [_buildCustom] (in native_worker_custom.dart) for full documentation.
     347            2 :   static Worker custom({
     348              :     required String className,
     349              :     Map<String, dynamic>? input,
     350              :   }) =>
     351            2 :       _buildCustom(className: className, input: input);
     352              : 
     353              :   // ── File workers ─────────────────────────────────────────────────────────────
     354              : 
     355              :   /// File compression worker (ZIP format).
     356              :   /// See [_buildFileCompress] (in native_worker_file.dart) for full documentation.
     357            4 :   @Deprecated(
     358              :     'Native ZIP support is removed in v1.1.0 to achieve Zero Dependencies. '
     359              :     'Please use the Dart "archive" package for ZIP operations instead.',
     360              :   )
     361              :   static Worker fileCompress({
     362              :     required String inputPath,
     363              :     required String outputPath,
     364              :     CompressionLevel level = CompressionLevel.medium,
     365              :     List<String> excludePatterns = const [],
     366              :     bool deleteOriginal = false,
     367              :   }) =>
     368            4 :       _buildFileCompress(
     369              :         inputPath: inputPath,
     370              :         outputPath: outputPath,
     371              :         level: level,
     372              :         excludePatterns: excludePatterns,
     373              :         deleteOriginal: deleteOriginal,
     374              :       );
     375              : 
     376              :   /// File decompression worker (ZIP extraction).
     377              :   /// See [_buildFileDecompress] (in native_worker_file.dart) for full documentation.
     378            4 :   @Deprecated(
     379              :     'Native ZIP support is removed in v1.1.0 to achieve Zero Dependencies. '
     380              :     'Please use the Dart "archive" package for ZIP operations instead.',
     381              :   )
     382              :   static Worker fileDecompress({
     383              :     required String zipPath,
     384              :     required String targetDir,
     385              :     bool deleteAfterExtract = false,
     386              :     bool overwrite = true,
     387              :   }) =>
     388            4 :       _buildFileDecompress(
     389              :         zipPath: zipPath,
     390              :         targetDir: targetDir,
     391              :         deleteAfterExtract: deleteAfterExtract,
     392              :         overwrite: overwrite,
     393              :       );
     394              : 
     395              :   /// Copy file or directory worker.
     396              :   /// See [_buildFileCopy] (in native_worker_file.dart) for full documentation.
     397            3 :   static Worker fileCopy({
     398              :     required String sourcePath,
     399              :     required String destinationPath,
     400              :     bool overwrite = false,
     401              :     bool recursive = true,
     402              :   }) =>
     403            3 :       _buildFileCopy(
     404              :         sourcePath: sourcePath,
     405              :         destinationPath: destinationPath,
     406              :         overwrite: overwrite,
     407              :         recursive: recursive,
     408              :       );
     409              : 
     410              :   /// Move file or directory worker.
     411              :   /// See [_buildFileMove] (in native_worker_file.dart) for full documentation.
     412            3 :   static Worker fileMove({
     413              :     required String sourcePath,
     414              :     required String destinationPath,
     415              :     bool overwrite = false,
     416              :   }) =>
     417            3 :       _buildFileMove(
     418              :         sourcePath: sourcePath,
     419              :         destinationPath: destinationPath,
     420              :         overwrite: overwrite,
     421              :       );
     422              : 
     423              :   /// Delete file or directory worker.
     424              :   /// See [_buildFileDelete] (in native_workmanager_file.dart) for full documentation.
     425            4 :   static Worker fileDelete({required String path, bool recursive = false}) =>
     426            4 :       _buildFileDelete(path: path, recursive: recursive);
     427              : 
     428              :   /// List directory contents worker.
     429              :   /// See [_buildFileList] (in native_worker_file.dart) for full documentation.
     430            2 :   static Worker fileList({
     431              :     required String path,
     432              :     String? pattern,
     433              :     bool recursive = false,
     434              :   }) =>
     435            2 :       _buildFileList(path: path, pattern: pattern, recursive: recursive);
     436              : 
     437              :   /// Create directory worker (mkdir).
     438              :   /// See [_buildFileMkdir] (in native_worker_file.dart) for full documentation.
     439            2 :   static Worker fileMkdir({required String path, bool createParents = true}) =>
     440            2 :       _buildFileMkdir(path: path, createParents: createParents);
     441              : 
     442              :   // ── Crypto workers ───────────────────────────────────────────────────────────
     443              : 
     444              :   /// Cryptographic hash of a file.
     445              :   /// See [_buildHashFile] (in native_worker_crypto.dart) for full documentation.
     446            2 :   static Worker hashFile({
     447              :     required String filePath,
     448              :     HashAlgorithm algorithm = HashAlgorithm.sha256,
     449              :   }) =>
     450            2 :       _buildHashFile(filePath: filePath, algorithm: algorithm);
     451              : 
     452              :   /// Hash string data.
     453              :   /// See [_buildHashString] (in native_worker_crypto.dart) for full documentation.
     454            2 :   static Worker hashString({
     455              :     required String data,
     456              :     HashAlgorithm algorithm = HashAlgorithm.sha256,
     457              :   }) =>
     458            2 :       _buildHashString(data: data, algorithm: algorithm);
     459              : 
     460              :   /// File encryption worker (AES-256-GCM).
     461              :   /// See [_buildCryptoEncrypt] (in native_worker_crypto.dart) for full documentation.
     462            5 :   static Worker cryptoEncrypt({
     463              :     required String inputPath,
     464              :     required String outputPath,
     465              :     required String password,
     466              :   }) =>
     467            5 :       _buildCryptoEncrypt(
     468              :         inputPath: inputPath,
     469              :         outputPath: outputPath,
     470              :         password: password,
     471              :       );
     472              : 
     473              :   /// File decryption worker (AES-256-GCM).
     474              :   /// See [_buildCryptoDecrypt] (in native_worker_crypto.dart) for full documentation.
     475            4 :   static Worker cryptoDecrypt({
     476              :     required String inputPath,
     477              :     required String outputPath,
     478              :     required String password,
     479              :   }) =>
     480            4 :       _buildCryptoDecrypt(
     481              :         inputPath: inputPath,
     482              :         outputPath: outputPath,
     483              :         password: password,
     484              :       );
     485              : 
     486              :   // ── PDF workers ──────────────────────────────────────────────────────────────
     487              : 
     488              :   /// Merge multiple PDF files into one.
     489              :   /// See [_buildPdfMerge] (in native_worker_pdf.dart) for full documentation.
     490            1 :   static Worker pdfMerge({
     491              :     required List<String> inputPaths,
     492              :     required String outputPath,
     493              :   }) =>
     494            1 :       _buildPdfMerge(inputPaths: inputPaths, outputPath: outputPath);
     495              : 
     496              :   /// Re-render a PDF at lower quality to reduce file size.
     497              :   /// See [_buildPdfCompress] (in native_worker_pdf.dart) for full documentation.
     498            1 :   static Worker pdfCompress({
     499              :     required String inputPath,
     500              :     required String outputPath,
     501              :     int quality = 80,
     502              :   }) =>
     503            1 :       _buildPdfCompress(
     504              :           inputPath: inputPath, outputPath: outputPath, quality: quality);
     505              : 
     506              :   /// Convert image files into a PDF (one image per page).
     507              :   /// See [_buildPdfFromImages] (in native_worker_pdf.dart) for full documentation.
     508            1 :   static Worker pdfFromImages({
     509              :     required List<String> imagePaths,
     510              :     required String outputPath,
     511              :     PdfPageSize pageSize = PdfPageSize.a4,
     512              :     int margin = 0,
     513              :   }) =>
     514            1 :       _buildPdfFromImages(
     515              :         imagePaths: imagePaths,
     516              :         outputPath: outputPath,
     517              :         pageSize: pageSize,
     518              :         margin: margin,
     519              :       );
     520              : 
     521              :   // ── WebSocket worker ─────────────────────────────────────────────────────────
     522              : 
     523              :   /// WebSocket worker — connect, send messages, receive responses.
     524              :   /// See [_buildWebSocket] (in native_worker_websocket.dart) for full documentation.
     525              :   /// **Android only** — returns failure on iOS.
     526            1 :   static Worker webSocket({
     527              :     required String url,
     528              :     List<String> messages = const [],
     529              :     Map<String, String> headers = const {},
     530              :     int timeoutSeconds = 30,
     531              :     int receiveMessages = 1,
     532              :     String? storeResponseAt,
     533              :     int? pingIntervalSeconds,
     534              :   }) =>
     535            1 :       _buildWebSocket(
     536              :         url: url,
     537              :         messages: messages,
     538              :         headers: headers,
     539              :         timeoutSeconds: timeoutSeconds,
     540              :         receiveMessages: receiveMessages,
     541              :         storeResponseAt: storeResponseAt,
     542              :         pingIntervalSeconds: pingIntervalSeconds,
     543              :       );
     544              : 
     545              :   // ── Image workers ────────────────────────────────────────────────────────────
     546              : 
     547              :   /// Image processing worker (resize, compress, convert).
     548              :   /// See [_buildImageProcess] (in native_worker_image.dart) for full documentation.
     549            4 :   static Worker imageProcess({
     550              :     required String inputPath,
     551              :     required String outputPath,
     552              :     int? maxWidth,
     553              :     int? maxHeight,
     554              :     bool maintainAspectRatio = true,
     555              :     int quality = 85,
     556              :     ImageFormat? outputFormat,
     557              :     Rect? cropRect,
     558              :     bool deleteOriginal = false,
     559              :   }) =>
     560            4 :       _buildImageProcess(
     561              :         inputPath: inputPath,
     562              :         outputPath: outputPath,
     563              :         maxWidth: maxWidth,
     564              :         maxHeight: maxHeight,
     565              :         maintainAspectRatio: maintainAspectRatio,
     566              :         quality: quality,
     567              :         outputFormat: outputFormat,
     568              :         cropRect: cropRect,
     569              :         deleteOriginal: deleteOriginal,
     570              :       );
     571              : }
        

Generated by: LCOV version 2.4-0