diff --git a/CHANGELOG.md b/CHANGELOG.md index f75a1960..fdaf1ad0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## [4.1.2](https://github.com/spoonconsulting/cordova-plugin-background-upload/compare/4.1.1...4.1.2) (2024-11-05) +* **android:** Return upload start and end time in upload response +* **iOS:** Return upload start and end time in upload response + ## [4.1.1](https://github.com/spoonconsulting/cordova-plugin-background-upload/compare/4.1.0...4.1.1) (2024-09-23) * **android:** Update cordova plugin file version to 8.1.0 * **iOS:** Update cordova plugin file version to 8.1.0 diff --git a/package-lock.json b/package-lock.json index fe1a34e8..99d0eae6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@spoonconsulting/cordova-plugin-background-upload", - "version": "4.1.1", + "version": "4.1.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@spoonconsulting/cordova-plugin-background-upload", - "version": "4.1.1", + "version": "4.1.2", "license": "Apache-2.0", "devDependencies": { "cordova-paramedic": "git+https://github.com/apache/cordova-paramedic.git", diff --git a/package.json b/package.json index 8efaf55d..d02fac77 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@spoonconsulting/cordova-plugin-background-upload", - "version": "4.1.1", + "version": "4.1.2", "description": "Cordova plugin for uploading file in the background", "cordova": { "id": "@spoonconsulting/cordova-plugin-background-upload", diff --git a/plugin.xml b/plugin.xml index 599588f1..69290c1d 100644 --- a/plugin.xml +++ b/plugin.xml @@ -1,5 +1,5 @@ - + Cordova Background Upload Plugin Background Upload plugin for Cordova ISC diff --git a/src/android/FileTransferBackground.java b/src/android/FileTransferBackground.java index 8cb6c393..25e98188 100644 --- a/src/android/FileTransferBackground.java +++ b/src/android/FileTransferBackground.java @@ -32,6 +32,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -81,7 +82,13 @@ private void sendProgress(final String id, int progressPercent) { } } - private void sendSuccess(final String id, final String response, int statusCode) { + private void sendSuccess(HashMap uploadData) { + String id = (String) uploadData.get("outputId"); + String response = (String) uploadData.get("response"); + int statusCode = (int) uploadData.get("statusCode"); + long uploadDuration = (long) uploadData.get("uploadDuration"); + long finishUploadTime = (long) uploadData.get("finishUploadTime"); + if (response != null && !response.isEmpty()) { logMessage("eventLabel='Uploader onSuccess' uploadId='" + id + "' response='" + response.substring(0, Math.min(2000, response.length() - 1)) + "'"); } else { @@ -95,6 +102,8 @@ private void sendSuccess(final String id, final String response, int statusCode) .put("state", "UPLOADED") .put("serverResponse", response) .put("statusCode", statusCode) + .put("uploadDuration", uploadDuration) + .put("finishUploadTime", finishUploadTime) ); } catch (JSONException e) { // Can't really happen but just in case @@ -401,26 +410,28 @@ private void acknowledgeEvent(String eventId, CallbackContext context) { * Handle ACK data and send it to the JS. */ private void handleAck(final Data ackData) { - // If upload was successful if (!ackData.getBoolean(UploadTask.KEY_OUTPUT_IS_ERROR, false)) { - // Read response from file if present String response = null; if (ackData.getString(UploadTask.KEY_OUTPUT_RESPONSE_FILE) != null) { response = readFileToStringNoThrow(ackData.getString(UploadTask.KEY_OUTPUT_RESPONSE_FILE)); } - sendSuccess( - ackData.getString(UploadTask.KEY_OUTPUT_ID), - response, - ackData.getInt(UploadTask.KEY_OUTPUT_STATUS_CODE, -1 /* If this is sent, something is really wrong */) - ); - + long startUploadTime = ackData.getLong(UploadTask.KEY_OUTPUT_UPLOAD_START_TIME, 0); + long finishUploadTime = ackData.getLong(UploadTask.KEY_OUTPUT_UPLOAD_FINISH_TIME, 0); + long uploadDuration = finishUploadTime - startUploadTime; + + HashMap uploadData = new HashMap<>(); + uploadData.put("outputId", ackData.getString(UploadTask.KEY_OUTPUT_ID)); + uploadData.put("response", response); + uploadData.put("statusCode", ackData.getInt(UploadTask.KEY_OUTPUT_STATUS_CODE, -1)); + uploadData.put("uploadDuration", uploadDuration); + uploadData.put("finishUploadTime", finishUploadTime); + sendSuccess(uploadData); } else { - // The upload was a failure sendError( - ackData.getString(UploadTask.KEY_OUTPUT_ID), - ackData.getString(UploadTask.KEY_OUTPUT_FAILURE_REASON), - ackData.getBoolean(UploadTask.KEY_OUTPUT_FAILURE_CANCELED, false) + ackData.getString(UploadTask.KEY_OUTPUT_ID), + ackData.getString(UploadTask.KEY_OUTPUT_FAILURE_REASON), + ackData.getBoolean(UploadTask.KEY_OUTPUT_FAILURE_CANCELED, false) ); } } diff --git a/src/android/UploadTask.java b/src/android/UploadTask.java index 919f6e9f..9fd1c5c0 100644 --- a/src/android/UploadTask.java +++ b/src/android/UploadTask.java @@ -79,6 +79,8 @@ public final class UploadTask extends Worker { public static final String KEY_OUTPUT_STATUS_CODE = "output_status_code"; public static final String KEY_OUTPUT_FAILURE_REASON = "output_failure_reason"; public static final String KEY_OUTPUT_FAILURE_CANCELED = "output_failure_canceled"; + public static final String KEY_OUTPUT_UPLOAD_START_TIME = "output_upload_start_time"; + public static final String KEY_OUTPUT_UPLOAD_FINISH_TIME = "output_upload_finish_time"; // private static UploadNotification uploadNotification = null; @@ -96,6 +98,8 @@ public void release() { } private static int concurrency = 1; private static Semaphore concurrentUploads = new Semaphore(concurrency, true); private static Mutex concurrencyLock = new Mutex(); + long startTime = 0; + long endTime = 0; public UploadTask(@NonNull Context context, @NonNull WorkerParameters workerParams) { @@ -189,6 +193,7 @@ public Result doWork() { return Result.retry(); } + startTime = System.currentTimeMillis(); // Register me uploadForegroundNotification.progress(getId(), 0f); handleNotification(); @@ -246,6 +251,7 @@ public Result doWork() { return Result.retry(); } } finally { + endTime = System.currentTimeMillis(); // Always remove ourselves from the notification uploadForegroundNotification.done(getId()); } @@ -254,7 +260,9 @@ public Result doWork() { final Data.Builder outputData = new Data.Builder() .putString(KEY_OUTPUT_ID, id) .putBoolean(KEY_OUTPUT_IS_ERROR, false) - .putInt(KEY_OUTPUT_STATUS_CODE, (!DEBUG_SKIP_UPLOAD) ? response.code() : 200); + .putInt(KEY_OUTPUT_STATUS_CODE, (!DEBUG_SKIP_UPLOAD) ? response.code() : 200) + .putLong(KEY_OUTPUT_UPLOAD_START_TIME, startTime) + .putLong(KEY_OUTPUT_UPLOAD_FINISH_TIME, endTime); // Try read the response body, if any try { diff --git a/src/ios/FileUploader.h b/src/ios/FileUploader.h index aa17e3b0..139e321c 100644 --- a/src/ios/FileUploader.h +++ b/src/ios/FileUploader.h @@ -1,6 +1,7 @@ #import #import "UploadEvent.h" #import +#import NS_ASSUME_NONNULL_BEGIN @protocol FileUploaderDelegate @optional diff --git a/src/ios/FileUploader.m b/src/ios/FileUploader.m index 6471a3c4..4e880c7f 100644 --- a/src/ios/FileUploader.m +++ b/src/ios/FileUploader.m @@ -1,6 +1,7 @@ #import "FileUploader.h" @interface FileUploader() -@property (nonatomic, strong) NSMutableDictionary* responsesData; +@property (nonatomic, strong) NSMutableDictionary *uploadStartTimes; +@property (nonatomic, strong) NSMutableDictionary *responsesData; @property (nonatomic, strong) AFURLSessionManager *manager; @end @@ -20,6 +21,7 @@ -(id)init{ return nil; [UploadEvent setupStorage]; self.responsesData = [[NSMutableDictionary alloc] init]; + self.uploadStartTimes = [[NSMutableDictionary alloc] init]; NSURLSessionConfiguration* configuration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:[[NSBundle mainBundle] bundleIdentifier]]; configuration.HTTPMaximumConnectionsPerHost = FileUploader.parallelUploadsLimit; configuration.sessionSendsLaunchEvents = NO; @@ -27,23 +29,36 @@ -(id)init{ __weak FileUploader *weakSelf = self; [self.manager setTaskDidCompleteBlock:^(NSURLSession * _Nonnull session, NSURLSessionTask * _Nonnull task, NSError * _Nullable error) { NSString* uploadId = [NSURLProtocol propertyForKey:kUploadUUIDStrPropertyKey inRequest:task.originalRequest]; + NSDate *startTime = weakSelf.uploadStartTimes[uploadId]; + NSDate *endUploadTime = [NSDate date]; + NSTimeInterval timeInterval = [endUploadTime timeIntervalSince1970]; + long long endUploadTimeInMS = (long long)(timeInterval * 1000); + NSTimeInterval duration = [endUploadTime timeIntervalSinceDate:startTime]; NSLog(@"[BackgroundUpload] Task %@ completed with error %@", uploadId, error); if (!error){ NSData* serverData = weakSelf.responsesData[@(task.taskIdentifier)]; NSString* serverResponse = serverData ? [[NSString alloc] initWithData:serverData encoding:NSUTF8StringEncoding] : @""; [weakSelf.responsesData removeObjectForKey:@(task.taskIdentifier)]; - [weakSelf saveAndSendEvent:@{ - @"id" : uploadId, - @"state" : @"UPLOADED", - @"statusCode" : @(((NSHTTPURLResponse *)task.response).statusCode), - @"serverResponse" : serverResponse - }]; + NSMutableDictionary *event = [@{ + @"id" : uploadId, + @"state" : @"UPLOADED", + @"statusCode" : @(((NSHTTPURLResponse *)task.response).statusCode), + @"serverResponse" : serverResponse + } mutableCopy]; + + if (!isnan(duration)) { + event[@"uploadDuration"] = @(duration * 1000); + event[@"finishUploadTime"] = @(endUploadTimeInMS); + } + + [weakSelf saveAndSendEvent:event]; } else { + [weakSelf.responsesData removeObjectForKey:@(task.taskIdentifier)]; [weakSelf saveAndSendEvent:@{ @"id" : uploadId, @"state" : @"FAILED", @"error" : error.localizedDescription, - @"errorCode" : @(error.code) + @"errorCode" : @(error.code), }]; } }]; @@ -60,7 +75,7 @@ -(id)init{ } -(void)saveAndSendEvent:(NSDictionary*)data{ - UploadEvent*event = [UploadEvent create:data]; + UploadEvent* event = [UploadEvent create:data]; [self sendEvent:[event dataRepresentation]]; } @@ -89,6 +104,9 @@ -(void)addUpload:(NSDictionary *)payload completionHandler:(void (^)(NSError* er completionHandler:^(NSError *error, NSMutableURLRequest *request) { if (error) return handler(error); + + weakSelf.uploadStartTimes[payload[@"id"]] = [NSDate date]; + __block double lastProgressTimeStamp = 0; [[weakSelf.manager uploadTaskWithRequest:request