diff options
author | Austin Kirk <askirk@umich.edu> | 2018-07-17 16:31:55 -0400 |
---|---|---|
committer | Austin Kirk <askirk@umich.edu> | 2018-07-17 16:31:55 -0400 |
commit | 81cc083a6447b89121fc2fa5b811e1c76c9129a1 (patch) | |
tree | 8e4649e8a0086a583be9830b2feb46a4e22e5c55 | |
parent | b908dbb05390b8959a664ed8d28eaa6afa781511 (diff) | |
download | sdl_android-81cc083a6447b89121fc2fa5b811e1c76c9129a1.tar.gz |
Alter multiple file upload to match iOS manager
- Multiple file and artwork upload/deletion now uses MultipleFileCompletionListener (which returns map of unsuccesful filenames and error messages)
- ISdl now includes sendRequests (multiple RPCs asynch)
- Refactored contentsOfResource and contentsOfUri methods to minimize code duplication
- Updated FileManagerTests for new multiple upload and deletion methods
7 files changed, 309 insertions, 57 deletions
diff --git a/sdl_android/src/androidTest/java/com/smartdevicelink/api/FileManagerTests.java b/sdl_android/src/androidTest/java/com/smartdevicelink/api/FileManagerTests.java index 73c132624..bc38a6b6b 100644 --- a/sdl_android/src/androidTest/java/com/smartdevicelink/api/FileManagerTests.java +++ b/sdl_android/src/androidTest/java/com/smartdevicelink/api/FileManagerTests.java @@ -21,7 +21,9 @@ import com.smartdevicelink.proxy.rpc.TemplateColorScheme; import com.smartdevicelink.proxy.rpc.enums.AppHMIType; import com.smartdevicelink.proxy.rpc.enums.FileType; import com.smartdevicelink.proxy.rpc.enums.Language; +import com.smartdevicelink.proxy.rpc.enums.Result; import com.smartdevicelink.proxy.rpc.enums.SystemCapabilityType; +import com.smartdevicelink.proxy.rpc.listeners.OnMultipleRequestListener; import com.smartdevicelink.proxy.rpc.listeners.OnRPCNotificationListener; import com.smartdevicelink.streaming.audio.AudioStreamingCodec; import com.smartdevicelink.streaming.audio.AudioStreamingParams; @@ -33,6 +35,7 @@ import com.smartdevicelink.transport.TCPTransportConfig; import java.net.URI; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Vector; import static com.smartdevicelink.api.SdlManagerTests.transport; @@ -122,6 +125,11 @@ public class FileManagerTests extends AndroidTestCase { } @Override + public void sendRequests(List<? extends RPCRequest> rpcs, OnMultipleRequestListener listener) { + + } + + @Override public void addOnRPCNotificationListener(FunctionID notificationId, OnRPCNotificationListener listener) { } @@ -311,16 +319,19 @@ public class FileManagerTests extends AndroidTestCase { } } - public void testMultipleFileUpload(){ + public void testMultipleFileUploadSuccess(){ class TestISdl extends BaseISdl{ @Override - public void sendRPCRequest(RPCRequest message) { - super.sendRPCRequest(message); - if(message instanceof PutFile){ - int correlationId = message.getCorrelationID(); - PutFileResponse putFileResponse = new PutFileResponse(); - putFileResponse.setSuccess(true); - message.getOnRPCResponseListener().onResponse(correlationId, putFileResponse); + public void sendRequests(List<? extends RPCRequest> rpcs, OnMultipleRequestListener listener) { + super.sendRequests(rpcs, listener); + if(rpcs.get(0) instanceof PutFile){ + for(RPCRequest message : rpcs){ + int correlationId = message.getCorrelationID(); + listener.addCorrelationId(correlationId); + PutFileResponse putFileResponse = new PutFileResponse(); + putFileResponse.setSuccess(true); + listener.onResponse(correlationId, putFileResponse); + } } } } @@ -348,29 +359,108 @@ public class FileManagerTests extends AndroidTestCase { sdlFile.setType(FileType.BINARY); filesToUpload.add(sdlFile); - fileManager.uploadFiles(filesToUpload, new CompletionListener() { - @Override - public void onComplete(boolean success) { - assertTrue(success); - } - }); + fileManager.uploadFiles(filesToUpload, + new MultipleFileCompletionListener() { + @Override + public void onComplete(Map<String, String> errors) { + assertNull(errors); + } + }); - List<String> uploadedFileNames = fileManager.getRemoteFileNames(); + List < String > uploadedFileNames = fileManager.getRemoteFileNames(); for(SdlFile file : filesToUpload){ assertTrue(uploadedFileNames.contains(file.getName())); } } - public void testMultipleArtworkUpload(){ + public void testMultipleFileUploadPartialFailure(){ + final String failureReason = "No space available"; + class TestISdl extends BaseISdl{ + private int responseNum = 0; + @Override - public void sendRPCRequest(RPCRequest message) { - super.sendRPCRequest(message); - if(message instanceof PutFile){ - int correlationId = message.getCorrelationID(); - PutFileResponse putFileResponse = new PutFileResponse(); - putFileResponse.setSuccess(true); - message.getOnRPCResponseListener().onResponse(correlationId, putFileResponse); + public void sendRequests(List<? extends RPCRequest> rpcs, OnMultipleRequestListener listener) { + if(rpcs.get(0) instanceof PutFile){ + for(RPCRequest message : rpcs){ + int correlationId = message.getCorrelationID(); + listener.addCorrelationId(correlationId); + PutFileResponse putFileResponse = new PutFileResponse(); + if(responseNum++ % 2 == 0){ + listener.onError(correlationId, Result.OUT_OF_MEMORY, failureReason); + }else{ + putFileResponse.setSuccess(true); + listener.onResponse(correlationId, putFileResponse); + } + } + } + } + } + + FileManager fileManager = new FileManager(new TestISdl(), mTestContext); + while(fileManager.getState() == BaseSubManager.SETTING_UP); + + final String baseFileName = "file"; + int fileNum = 0; + final List<SdlFile> filesToUpload = new ArrayList<>(); + SdlFile sdlFile = new SdlFile(); + sdlFile.setName(baseFileName + fileNum++); + Uri uri = Uri.parse("android.resource://" + mTestContext.getPackageName() + "/drawable/ic_sdl"); + sdlFile.setUri(uri); + filesToUpload.add(sdlFile); + + sdlFile = new SdlFile(); + sdlFile.setName(baseFileName + fileNum++); + sdlFile.setResourceId(com.smartdevicelink.test.R.drawable.ic_sdl); + filesToUpload.add(sdlFile); + + sdlFile = new SdlFile(); + sdlFile.setName(baseFileName + fileNum++); + sdlFile.setFileData(Test.GENERAL_BYTE_ARRAY); + sdlFile.setPersistent(true); + sdlFile.setType(FileType.BINARY); + filesToUpload.add(sdlFile); + + fileManager.uploadFiles(filesToUpload, + new MultipleFileCompletionListener() { + @Override + public void onComplete(Map<String, String> errors) { + assertNotNull(errors); + for(int i = 0; i < filesToUpload.size(); i++){ + if(i % 2 == 0){ + assertTrue(errors.containsKey(filesToUpload.get(i).getName())); + assertEquals(FileManager.buildErrorString(Result.OUT_OF_MEMORY, + failureReason), errors.get(filesToUpload.get(i).getName())); + }else{ + assertFalse(errors.containsKey(filesToUpload.get(i).getName())); + } + } + } + }); + + List <String> uploadedFileNames = fileManager.getRemoteFileNames(); + for(int i = 0; i < filesToUpload.size(); i++){ + if(i % 2 == 0){ + assertFalse(uploadedFileNames.contains(filesToUpload.get(i).getName())); + }else{ + assertTrue(uploadedFileNames.contains(filesToUpload.get(i).getName())); + } + } + } + + public void testMultipleArtworkUploadSuccess(){ + class TestISdl extends BaseISdl{ + @Override + public void sendRequests(List<? extends RPCRequest> rpcs, OnMultipleRequestListener listener) { + super.sendRequests(rpcs, listener); + if(rpcs.get(0) instanceof PutFile){ + for(RPCRequest message : rpcs){ + int correlationId = message.getCorrelationID(); + listener.addCorrelationId(correlationId); + PutFileResponse putFileResponse = new PutFileResponse(); + putFileResponse.setSuccess(true); + listener.onResponse(correlationId, putFileResponse); + } } } } @@ -394,16 +484,17 @@ public class FileManagerTests extends AndroidTestCase { sdlArtwork.setType(FileType.GRAPHIC_PNG); artworkToUpload.add(sdlArtwork); - fileManager.uploadArtworks(artworkToUpload, new CompletionListener() { - @Override - public void onComplete(boolean success) { - assertTrue(success); - } - }); + fileManager.uploadFiles(artworkToUpload, + new MultipleFileCompletionListener() { + @Override + public void onComplete(Map<String, String> errors) { + assertNull(errors); + } + }); - List<String> uploadedArtworkNames = fileManager.getRemoteFileNames(); + List < String > uploadedFileNames = fileManager.getRemoteFileNames(); for(SdlArtwork artwork : artworkToUpload){ - assertTrue(uploadedArtworkNames.contains(artwork.getName())); + assertTrue(uploadedFileNames.contains(artwork.getName())); } } }
\ No newline at end of file diff --git a/sdl_android/src/androidTest/java/com/smartdevicelink/test/proxy/SystemCapabilityManagerTests.java b/sdl_android/src/androidTest/java/com/smartdevicelink/test/proxy/SystemCapabilityManagerTests.java index a7068e051..8833c9323 100644 --- a/sdl_android/src/androidTest/java/com/smartdevicelink/test/proxy/SystemCapabilityManagerTests.java +++ b/sdl_android/src/androidTest/java/com/smartdevicelink/test/proxy/SystemCapabilityManagerTests.java @@ -25,6 +25,7 @@ import com.smartdevicelink.proxy.rpc.VideoStreamingCapability; import com.smartdevicelink.proxy.rpc.enums.HmiZoneCapabilities; import com.smartdevicelink.proxy.rpc.enums.SpeechCapabilities; import com.smartdevicelink.proxy.rpc.enums.SystemCapabilityType; +import com.smartdevicelink.proxy.rpc.listeners.OnMultipleRequestListener; import com.smartdevicelink.proxy.rpc.listeners.OnRPCNotificationListener; import com.smartdevicelink.streaming.audio.AudioStreamingCodec; import com.smartdevicelink.streaming.audio.AudioStreamingParams; @@ -165,6 +166,11 @@ public class SystemCapabilityManagerTests extends AndroidTestCase { public void sendRPCRequest(RPCRequest message) {} @Override + public void sendRequests(List<? extends RPCRequest> rpcs, OnMultipleRequestListener listener) { + + } + + @Override public void addOnRPCNotificationListener(FunctionID notificationId, OnRPCNotificationListener listener) {} @Override diff --git a/sdl_android/src/main/java/com/smartdevicelink/api/FileManager.java b/sdl_android/src/main/java/com/smartdevicelink/api/FileManager.java index cfb2f1e3a..e8f61f6aa 100644 --- a/sdl_android/src/main/java/com/smartdevicelink/api/FileManager.java +++ b/sdl_android/src/main/java/com/smartdevicelink/api/FileManager.java @@ -3,7 +3,9 @@ package com.smartdevicelink.api; import android.content.Context; import android.content.res.Resources; import android.net.Uri; +import android.support.annotation.NonNull; import android.util.Log; +import android.util.SparseArray; import com.smartdevicelink.proxy.RPCResponse; import com.smartdevicelink.proxy.interfaces.ISdl; @@ -13,6 +15,8 @@ import com.smartdevicelink.proxy.rpc.Image; import com.smartdevicelink.proxy.rpc.ListFiles; import com.smartdevicelink.proxy.rpc.ListFilesResponse; import com.smartdevicelink.proxy.rpc.PutFile; +import com.smartdevicelink.proxy.rpc.enums.Result; +import com.smartdevicelink.proxy.rpc.listeners.OnMultipleRequestListener; import com.smartdevicelink.proxy.rpc.listeners.OnPutFileUpdateListener; import com.smartdevicelink.proxy.rpc.listeners.OnRPCResponseListener; @@ -21,7 +25,9 @@ import java.io.IOException; import java.io.InputStream; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * <strong>FileManager</strong> <br> @@ -88,6 +94,9 @@ public class FileManager extends BaseSubManager { // DELETION public void deleteRemoteFileWithName(final String fileName, final CompletionListener listener){ + if(fileName == null){ + return; + } DeleteFile deleteFile = new DeleteFile(); deleteFile.setSdlFileName(fileName); deleteFile.setOnRPCResponseListener(new OnRPCResponseListener() { @@ -102,15 +111,63 @@ public class FileManager extends BaseSubManager { internalInterface.sendRPCRequest(deleteFile); } - public void deleteRemoteFilesWithNames(List<String> fileNames, CompletionListener listener){ + public void deleteRemoteFilesWithNames(List<String> fileNames, final MultipleFileCompletionListener listener){ + if(fileNames == null || fileNames.isEmpty()){ + return; + } + final Map<String, String> errors = new HashMap<>(); + final List<DeleteFile> deleteFileRequests = new ArrayList<>(); + final SparseArray<String> fileNameMap = new SparseArray<>(); for(String fileName : fileNames){ - deleteRemoteFileWithName(fileName, listener); + DeleteFile deleteFile = new DeleteFile(); + deleteFile.setSdlFileName(fileName); + deleteFileRequests.add(deleteFile); } + internalInterface.sendRequests(deleteFileRequests, new OnMultipleRequestListener() { + int fileNum = 0; + + @Override + public void addCorrelationId(int correlationid) { + super.addCorrelationId(correlationid); + fileNameMap.put(correlationid, deleteFileRequests.get(fileNum++).getSdlFileName()); + } + + @Override + public void onUpdate(int remainingRequests) {} + + @Override + public void onFinished() { + if(errors.isEmpty()){ + listener.onComplete(null); + }else{ + listener.onComplete(errors); + } + } + + @Override + public void onError(int correlationId, Result resultCode, String info) { + if(fileNameMap.get(correlationId) != null){ + errors.put(fileNameMap.get(correlationId), resultCode.toString() + " : " + info); + }// else no fileName for given correlation ID + } + + @Override + public void onResponse(int correlationId, RPCResponse response) { + if(response.getSuccess()){ + if(fileNameMap.get(correlationId) != null){ + remoteFiles.remove(fileNameMap.get(correlationId)); + } + } + } + }); } // UPLOAD FILES / ARTWORK - public void uploadFile(final SdlFile file, final CompletionListener listener){ + private PutFile createPutFile(final SdlFile file){ + if(file == null){ + return null; + } PutFile putFile = new PutFile(); if(file.getName() == null){ throw new IllegalArgumentException("You must specify an file name in the SdlFile"); @@ -147,6 +204,12 @@ public class FileManager extends BaseSubManager { } putFile.setPersistentFile(file.isPersistent()); + return putFile; + } + + public void uploadFile(final SdlFile file, final CompletionListener listener){ + PutFile putFile = createPutFile(file); + putFile.setOnPutFileUpdateListener(new OnPutFileUpdateListener() { @Override public void onResponse(int correlationId, RPCResponse response, long totalSize) { @@ -160,24 +223,69 @@ public class FileManager extends BaseSubManager { internalInterface.sendRPCRequest(putFile); } - public void uploadFiles(List<SdlFile> files, CompletionListener listener){ + public void uploadFiles(List<? extends SdlFile> files, final MultipleFileCompletionListener listener){ + if(files ==null || files.isEmpty()){ + return; + } + final Map<String, String> errors = new HashMap<>(); + final List<PutFile> putFileRequests = new ArrayList<>(); + final SparseArray<String> fileNameMap = new SparseArray<>(); for(SdlFile file : files){ - uploadFile(file, listener); + putFileRequests.add(createPutFile(file)); } + internalInterface.sendRequests(putFileRequests, new OnMultipleRequestListener() { + int fileNum = 0; + + @Override + public void addCorrelationId(int correlationid) { + super.addCorrelationId(correlationid); + fileNameMap.put(correlationid, putFileRequests.get(fileNum++).getSdlFileName()); + } + + @Override + public void onUpdate(int remainingRequests) {} + + @Override + public void onFinished() { + if(errors.isEmpty()){ + listener.onComplete(null); + }else{ + listener.onComplete(errors); + } + } + + @Override + public void onError(int correlationId, Result resultCode, String info) { + if(fileNameMap.get(correlationId) != null){ + errors.put(fileNameMap.get(correlationId), buildErrorString(resultCode, info)); + }// else no fileName for given correlation ID + } + + @Override + public void onResponse(int correlationId, RPCResponse response) { + if(response.getSuccess()){ + if(fileNameMap.get(correlationId) != null){ + remoteFiles.add(fileNameMap.get(correlationId)); + } + } + } + }); } public void uploadArtwork(final SdlArtwork file, final CompletionListener listener){ uploadFile(file, listener); } - public void uploadArtworks(List<SdlArtwork> files, CompletionListener listener){ - for(SdlArtwork artwork : files){ - uploadArtwork(artwork, listener); - } + public void uploadArtworks(List<SdlArtwork> files, final MultipleFileCompletionListener listener){ + uploadFiles(files, listener); } // HELPERS + static public String buildErrorString(Result resultCode, String info){ + return resultCode.toString() + " : " + info; + } + /** * Helper method to take resource files and turn them into byte arrays * @param resource Resource file id. @@ -187,15 +295,8 @@ public class FileManager extends BaseSubManager { InputStream is = null; try { is = context.get().getResources().openRawResource(resource); - ByteArrayOutputStream os = new ByteArrayOutputStream(is.available()); - final int bufferSize = 4096; - final byte[] buffer = new byte[bufferSize]; - int available; - while ((available = is.read(buffer)) >= 0) { - os.write(buffer, 0, available); - } - return os.toByteArray(); - } catch (IOException | Resources.NotFoundException e) { + return contentsOfInputStream(is); + } catch (Resources.NotFoundException e) { Log.w(TAG, "Can't read from resource", e); return null; } finally { @@ -218,14 +319,7 @@ public class FileManager extends BaseSubManager { InputStream is = null; try{ is = context.get().getContentResolver().openInputStream(uri); - ByteArrayOutputStream os = new ByteArrayOutputStream(); - final int bufferSize = 4096; - final byte[] buffer = new byte[bufferSize]; - int available; - while ((available = is.read(buffer)) >= 0) { - os.write(buffer, 0, available); - } - return os.toByteArray(); + return contentsOfInputStream(is); } catch (IOException e){ Log.w(TAG, "Can't read from Uri", e); return null; @@ -240,4 +334,23 @@ public class FileManager extends BaseSubManager { } } + private byte[] contentsOfInputStream(InputStream is){ + if(is == null){ + return null; + } + try{ + ByteArrayOutputStream os = new ByteArrayOutputStream(); + final int bufferSize = 4096; + final byte[] buffer = new byte[bufferSize]; + int available; + while ((available = is.read(buffer)) >= 0) { + os.write(buffer, 0, available); + } + return os.toByteArray(); + } catch (IOException e){ + Log.w(TAG, "Can't read from InputStream", e); + return null; + } + } + } diff --git a/sdl_android/src/main/java/com/smartdevicelink/api/MultipleFileCompletionListener.java b/sdl_android/src/main/java/com/smartdevicelink/api/MultipleFileCompletionListener.java new file mode 100644 index 000000000..83ed9252d --- /dev/null +++ b/sdl_android/src/main/java/com/smartdevicelink/api/MultipleFileCompletionListener.java @@ -0,0 +1,14 @@ +package com.smartdevicelink.api; + +import java.util.Map; + +public interface MultipleFileCompletionListener { + + /** + * @param errors - a dictionary (map) property, of type <String: String>, and contains information + * on all failed uploads. The key is the name of the file that did not upload properly, + * the value is an error String describing what went wrong on that particular upload attempt. + * If all files are uploaded successfully, errors is null + */ + void onComplete(Map<String, String> errors); +} diff --git a/sdl_android/src/main/java/com/smartdevicelink/api/SdlManager.java b/sdl_android/src/main/java/com/smartdevicelink/api/SdlManager.java index 9c490b787..25258bdfc 100644 --- a/sdl_android/src/main/java/com/smartdevicelink/api/SdlManager.java +++ b/sdl_android/src/main/java/com/smartdevicelink/api/SdlManager.java @@ -494,6 +494,15 @@ public class SdlManager implements ProxyBridge.LifecycleListener { } @Override + public void sendRequests(List<? extends RPCRequest> rpcs, OnMultipleRequestListener listener) { + try { + proxy.sendRequests(rpcs, listener); + } catch (SdlException e) { + e.printStackTrace(); + } + } + + @Override public void addOnRPCNotificationListener(FunctionID notificationId, OnRPCNotificationListener listener) { proxy.addOnRPCNotificationListener(notificationId,listener); } diff --git a/sdl_android/src/main/java/com/smartdevicelink/proxy/SdlProxyBase.java b/sdl_android/src/main/java/com/smartdevicelink/proxy/SdlProxyBase.java index 610c22d15..92cdbcd73 100644 --- a/sdl_android/src/main/java/com/smartdevicelink/proxy/SdlProxyBase.java +++ b/sdl_android/src/main/java/com/smartdevicelink/proxy/SdlProxyBase.java @@ -316,6 +316,15 @@ public abstract class SdlProxyBase<proxyListenerType extends IProxyListenerBase> }
@Override
+ public void sendRequests(List<? extends RPCRequest> rpcs, OnMultipleRequestListener listener) {
+ try {
+ SdlProxyBase.this.sendRequests(rpcs, listener);
+ } catch (SdlException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
public void addOnRPCNotificationListener(FunctionID notificationId, OnRPCNotificationListener listener) {
SdlProxyBase.this.addOnRPCNotificationListener(notificationId,listener);
}
diff --git a/sdl_android/src/main/java/com/smartdevicelink/proxy/interfaces/ISdl.java b/sdl_android/src/main/java/com/smartdevicelink/proxy/interfaces/ISdl.java index 467099c76..e88c64322 100644 --- a/sdl_android/src/main/java/com/smartdevicelink/proxy/interfaces/ISdl.java +++ b/sdl_android/src/main/java/com/smartdevicelink/proxy/interfaces/ISdl.java @@ -5,11 +5,14 @@ import com.smartdevicelink.protocol.enums.SessionType; import com.smartdevicelink.proxy.RPCRequest; import com.smartdevicelink.proxy.rpc.SdlMsgVersion; import com.smartdevicelink.proxy.rpc.enums.SystemCapabilityType; +import com.smartdevicelink.proxy.rpc.listeners.OnMultipleRequestListener; import com.smartdevicelink.proxy.rpc.listeners.OnRPCNotificationListener; import com.smartdevicelink.streaming.audio.AudioStreamingCodec; import com.smartdevicelink.streaming.audio.AudioStreamingParams; import com.smartdevicelink.streaming.video.VideoStreamingParameters; +import java.util.List; + /* * Copyright (c) 2017 Livio, Inc. * All rights reserved. @@ -126,6 +129,13 @@ public interface ISdl { void sendRPCRequest(RPCRequest message); /** + * Pass a list of RPC requests through the proxy to be sent to core + * @param rpcs List of RPC requests + * @param listener OnMultipleRequestListener that is called between requests and after all are processed + */ + void sendRequests(List<? extends RPCRequest> rpcs, final OnMultipleRequestListener listener); + + /** * Add an OnRPCNotificationListener for specified notification * @param notificationId FunctionID of the notification that is to be listened for * @param listener listener that should be added for the notification ID |