781flyingdutchman / background_downloader

Flutter plugin for file downloads and uploads
Other
154 stars 73 forks source link

Uploading multipart does not return a result #373

Closed jay-athelas closed 5 days ago

jay-athelas commented 1 week ago

Describe the bug I am trying to upload a multipart audio file, the audio upload works fine but does not return any result, I want to test uploads when the app is not on foreground but rather minimized.

This is the code I have to upload.

import 'dart:async';
import 'dart:io';

import 'package:background_downloader/background_downloader.dart';
import 'package:path/path.dart' as path;
import '../../../../services/auth.dart';
import '../../../../utils/audio_cache_mobile.dart';
import '../../../../utils/config.dart';
import '../../../../utils/platformUtils.dart';

class FileUploaderSingleton {
  static final FileUploaderSingleton _instance =
      FileUploaderSingleton._internal();
  late FileDownloader uploader;
  StreamSubscription? _subscription;

  factory FileUploaderSingleton() {
    return _instance;
  }

  FileUploaderSingleton._internal() {
    print('uploader: Initializing FileUploaderSingleton');
    uploader = FileDownloader();
    _initializeListener();
  }

  void _initializeListener() {
    print('uploader: Initializing listener');
    _subscription ??= uploader.updates.listen(
      (result) {
        print('uploader: Upload result: $result');
      },
      onError: (error) {
        print('uploader: Upload error: $error');
      },
    );
  }

  Future<void> uploadFile() async {
    print('uploader: Upload file initiated');
    String rootDir = await AudioCacheMobile.getRootAudioDirectory();
    print('uploader: Root directory: $rootDir');

    final directory = Directory(rootDir);
    final entities = directory.listSync();
    print('uploader: Directory contents: $entities');

    if (entities.isEmpty || entities.first is! File) {
      print('uploader: No files to upload');
      return;
    }

    final String filePath = entities.first.path;
    print('uploader: File path: $filePath');

    String token = await AuthService.getRawJWT();
    print('uploader: Token retrieved');

    final formData = <String, String>{
      'template_id': "65",
      'patient_name': "testios4",
      'date_of_service': DateTime.now().toString(),
      'device_type': PlatformUtils.normandyDeviceType.name,
      'version': "2.2.2",
      'live_transcription': '',
    };
    print('uploader: Form data: $formData');

    (BaseDirectory, String, String) paths =
        await Task.split(filePath: filePath);
    print('uploader: paths ${paths.$1} ${paths.$2} ${paths.$3}');

    final uploadTask = UploadTask(
      url: '$baseUrl/v1/openai/generate_transcription_from_audio',
      filename: paths.$3,
      fields: formData,
      baseDirectory: paths.$1,
      directory: paths.$2,
      fileField: 'audio_file',
      headers: {'Authorization': 'Bearer $token'},
      httpRequestMethod: 'POST',
      updates: Updates.statusAndProgress,
    );
    print('uploader: Upload task created: $uploadTask');

    // await uploader.enqueue(uploadTask);
    TaskStatusUpdate result = await uploader.upload(uploadTask, onProgress: (progress) {
      print("uploader: $progress");
    }, onStatus: (TaskStatus status) {
      print("uploader: ${status}");
    },);
    print("uploader: ${result.status}");
    // print ("uploader: ${result.exception?.description.trimLeft()}");
    // print('uploader: Upload task enqueued ${result.exception?.description}');
  }
}

Expected behavior It should print a status when task is completed print("uploader: ${result.status}");

Logs If possible, include logs that capture the issue:

flutter: uploader: Upload task created: UploadTask{taskId: 1024180590, url: https://rcm-api.athelas.com/v1/openai/generate_transcription_from_audio, filename: recording_f714b010417949b2a45e471ebbb21a93.ogg, headers: {Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9}, httpRequestMethod: POST, post: null, directory: scribe_audio, baseDirectory: BaseDirectory.applicationDocuments, group: default, updates: Updates.statusAndProgress, requiresWiFi: false, retries: 0, retriesRemaining: 0, allowPause: false, priority: 5, metaData: , displayName: } and fileField audio_file, mimeType audio/ogg and fields {template_id: 65, patient_name: testios4, date_of_service: 2024-09-04 09:21:56.822835, device_type: MOBILE_IOS, version: 2.2.2, live_transcription: }

Any help will be highly appreciated! Thanks!

jay-athelas commented 1 week ago

After adding the elapsedTime callback I see that it keeps printing every 5 seconds and never finishes, but the upload has completed

781flyingdutchman commented 1 week ago

Which platform(s) and versions and can you confirm if it happens on one platform (eg Android), it also happens on another (eg iOS)? Can you share the native logs (Console on iOS or Android logs)? And also, any other results from the print statement in your code, or does the upload complete without any progress or status updates?

jay-athelas commented 1 week ago

I am currently testing only in iOS , yes the upload completes without progress or status updates, the elapsedTime callback keeps printing every 5 seconds

jay-athelas commented 1 week ago

so basically nothing is printed after the upload is called, like it never finishes

TaskStatusUpdate result = await uploader.upload(uploadTask, onProgress: (progress) {
      print("uploader: $progress");
    }, onStatus: (TaskStatus status) {
      print("uploader: ${status}");
    },);
    print("uploader: ${result.status}");
781flyingdutchman commented 1 week ago

Can you provide native logs, using the iOS Console app, filtering on Category "Downloader" so I can see what the iOS side is doing?

jay-athelas commented 6 days ago

Hi @781flyingdutchman, it's working now, I wonder how the retry works, the use case is if there is no internet connection it should retry, but can you setup the backoff? , I see in the logs there is no status updates for when is no connection, just says taskStatus.enqued, can you provide more insights into how this works for offline ?

781flyingdutchman commented 5 days ago

Hi, glad it works. The downloader waits for an internet connection before starting a Task, so no retry is required for that scenario. If the internet connection disappears while the task is running, then the task may fail (or pause, on iOS, without user notification as this is handled by the OS), and if it fails and retries have been set it will trigger a retry after an exponentially increasing delay (which cannot be set manually). That means it will then wait for an internet connection before re-attempting the task that failed, so the actual start of the task may be much later. Hope that clarifies, closing this issue.

alopezetf commented 3 days ago

"Hi, glad it works. The downloader waits for an internet connection before starting a Task, so no retry is required for that scenario. If the internet connection disappears while the task is running, then the task may fail (or pause, on iOS, without user notification as this is handled by the OS), and if it fails and retries have been set it will trigger a retry after an exponentially increasing delay (which cannot be set manually). That means it will then wait for an internet connection before re-attempting the task that failed, so the actual start of the task may be much later. Hope that clarifies, closing this issue."

Hello @781flyingdutchman - question... Can this 'exponentially increasing delay' on iOS be avoided by setting retries to 0 and forcing a pause/failure and handle the retry manually by establishing a new upload task for it? We have a similar situation... Thanks

781flyingdutchman commented 2 days ago

If you set retries to 0 the task will still wait for a network connection before starting, and when the network disappears then on Android the task fails, whereas on iOS the task moves to a suspended state where the OS revives it when the network comes back. That is a feature of the iOS implementation of background downloads and not something you can override (you cannot force it to fail when the network disappears, though I believe it does fail if the server does not support resumable downloads).

I guess you could create some mechanism where if the progress updates are not coming through regularly (which would suggest a loss of internet connection) you cancel the task and reschedule it, but this would not be entirely predictable/robust.

alopezetf commented 2 days ago

Got it @781flyingdutchman. Thanks for the quick clarification response!