Skip to content

Commit a717391

Browse files
authored
Merge pull request #484 from smartdevicelink/bugfix/single-file-multiple-uploads
Fix case where file can have multiple upload/delete tasks created for it
2 parents 61e1918 + 94ff1ca commit a717391

File tree

9 files changed

+258
-138
lines changed

9 files changed

+258
-138
lines changed

lib/js/src/manager/file/_DeleteFileOperation.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,14 @@ class _DeleteFileOperation extends _Task {
4040
* @class
4141
* @param {_LifecycleManager} lifecycleManager - An instance of _LifecycleManager.
4242
* @param {String} fileName - Name of the file to be deleted.
43+
* @param {String[]} remoteFileNames - A list of file names uploaded to the system
4344
* @param {Function} completionListener - Listener called when the operation has completed.
4445
*/
45-
constructor (lifecycleManager, fileName, completionListener) {
46+
constructor (lifecycleManager, fileName, remoteFileNames = [], completionListener) {
4647
super('DeleteFilesOperation');
4748
this._lifecycleManager = lifecycleManager;
4849
this._fileName = fileName;
50+
this._remoteFileNames = remoteFileNames;
4951
this._completionListener = completionListener;
5052
}
5153

@@ -66,6 +68,13 @@ class _DeleteFileOperation extends _Task {
6668
if (this.getState() === _Task.CANCELED) {
6769
return;
6870
}
71+
72+
if (!this._remoteFileNames.includes(this._fileName)) {
73+
const errorMessage = 'File to delete is no longer on the head unit, aborting the operation';
74+
console.log(errorMessage);
75+
this._completionListener(false, _FileManagerBase.SPACE_AVAILABLE_MAX_VALUE, this._remoteFileNames, errorMessage);
76+
return this.onFinished();
77+
}
6978
await this._deleteFile();
7079
}
7180

lib/js/src/manager/file/_FileManagerBase.js

Lines changed: 13 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ class _FileManagerBase extends _SubManagerBase {
146146
return;
147147
}
148148

149-
const operation = new _DeleteFileOperation(this._lifecycleManager, fileName, (success, bytesAvailable, fileNames, errorMessage) => {
149+
const operation = new _DeleteFileOperation(this._lifecycleManager, fileName, this._remoteFiles, (success, bytesAvailable, fileNames, errorMessage) => {
150150
if (success) {
151151
this._bytesAvailable = bytesAvailable;
152152
this._remoteFiles = _ArrayTools.arrayRemove(this._remoteFiles, fileName);
@@ -234,21 +234,6 @@ class _FileManagerBase extends _SubManagerBase {
234234
return;
235235
}
236236

237-
const msgVersion = this._lifecycleManager.getSdlMsgVersion();
238-
const rpcVersion = new Version(msgVersion.getMajorVersion(), msgVersion.getMinorVersion(), msgVersion.getPatchVersion());
239-
if (!file.isPersistent() && !this.hasUploadedFile(file) && new Version(4, 0, 0).isNewerThan(rpcVersion) === 1) {
240-
file.setOverwrite(true);
241-
}
242-
243-
if (!file.getOverwrite() && this._remoteFiles.includes(file.getName())) {
244-
const errorMessage = 'Cannot overwrite remote file. The remote file system already has a file of this name, and the file manager is set to not automatically overwrite files.';
245-
console.log(`FileManagerBase: ${errorMessage}`);
246-
if (typeof listener === 'function') {
247-
listener(false, this._bytesAvailable, null, errorMessage);
248-
}
249-
return;
250-
}
251-
252237
this._sdlUploadFilePrivate(file, listener);
253238
}
254239

@@ -258,20 +243,23 @@ class _FileManagerBase extends _SubManagerBase {
258243
* @param {Function} listener - listener called when the upload has finished
259244
*/
260245
_sdlUploadFilePrivate (file, listener) {
261-
const fileName = file.getName();
246+
const fileClone = file.clone();
247+
const fileName = fileClone.getName();
262248

263-
const fileWrapper = new _SdlFileWrapper(file, (success, bytesAvailable, fileNames, errorMessage) => {
249+
const fileWrapper = new _SdlFileWrapper(fileClone, (success, bytesAvailable, fileNames, errorMessage) => {
264250
if (success) {
265251
this._bytesAvailable = bytesAvailable;
266252
this._remoteFiles.push(fileName);
267-
this._uploadedEphemeralFileNames.push(fileName);
268-
} else {
253+
if (!fileClone.isPersistent()) {
254+
this._uploadedEphemeralFileNames.push(fileName);
255+
}
256+
} else if (errorMessage !== _UploadFileOperation.fileManagerCannotOverwriteError) {
269257
this._incrementFailedUploadCountForFileName(fileName, this._failedFileUploadsCount);
270258

271-
const maxUploadCount = (file instanceof SdlArtwork) ? this._maxArtworkUploadAttempts : this._maxFileUploadAttempts;
272-
if (this._canFileBeUploadedAgain(file, maxUploadCount, this._failedFileUploadsCount)) {
273-
console.log(`FileManagerBase: Attempting to resend file with name ${file.getName()} after a failed upload attempt`);
274-
this._sdlUploadFilePrivate(file, listener);
259+
const maxUploadCount = (fileClone instanceof SdlArtwork) ? this._maxArtworkUploadAttempts : this._maxFileUploadAttempts;
260+
if (this._canFileBeUploadedAgain(fileClone, maxUploadCount, this._failedFileUploadsCount)) {
261+
console.log(`FileManagerBase: Attempting to resend file with name ${fileClone.getName()} after a failed upload attempt`);
262+
this._sdlUploadFilePrivate(fileClone, listener);
275263
return;
276264
}
277265
}
@@ -422,5 +410,6 @@ class _FileManagerBase extends _SubManagerBase {
422410
}
423411

424412
const SPACE_AVAILABLE_MAX_VALUE = _FileManagerBase.SPACE_AVAILABLE_MAX_VALUE = 2000000000;
413+
_FileManagerBase.fileManagerCannotOverwriteError = 'Cannot overwrite remote file. The remote file system already has a file of this name, and the file manager is set to not automatically overwrite files.';
425414

426415
export { _FileManagerBase };

lib/js/src/manager/file/_UploadFileOperation.js

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { PutFile } from '../../rpc/messages/PutFile';
44
import { _Task } from '../_Task';
55
import { _FileManagerBase } from './_FileManagerBase';
66
import { _FileUtils } from '../../util/_FileUtils';
7+
import { Version } from '../../util/Version';
78

89
class _UploadFileOperation extends _Task {
910
/**
@@ -38,11 +39,27 @@ class _UploadFileOperation extends _Task {
3839
return;
3940
}
4041

42+
const file = this._fileWrapper.getFile();
43+
const msgVersion = this._lifecycleManager.getSdlMsgVersion();
44+
const rpcVersion = new Version(msgVersion.getMajorVersion(), msgVersion.getMinorVersion(), msgVersion.getPatchVersion());
45+
if (!file.isPersistent() && !this._fileManager.hasUploadedFile(file) && new Version(4, 4, 0).isNewerThan(rpcVersion) === 1) {
46+
file.setOverwrite(true);
47+
}
48+
49+
if (!this._fileManager.fileNeedsUpload(file)) {
50+
const errorMessage = _FileManagerBase.fileManagerCannotOverwriteError;
51+
console.log(errorMessage);
52+
if (typeof this._fileWrapper.getCompletionListener() === 'function') {
53+
this._fileWrapper.getCompletionListener()(false, null, null, errorMessage);
54+
}
55+
return this.onFinished();
56+
}
57+
4158
let mtuSize = 0;
4259
if (this._lifecycleManager !== null) {
4360
mtuSize = this._lifecycleManager.getMtu(_ServiceType.RPC);
4461
}
45-
await this._sendFile(this._fileWrapper.getFile(), mtuSize, this._fileWrapper.getCompletionListener());
62+
await this._sendFile(file, mtuSize, this._fileWrapper.getCompletionListener());
4663
}
4764

4865
/**
@@ -56,25 +73,16 @@ class _UploadFileOperation extends _Task {
5673
let bytesAvailable = _FileManagerBase.SPACE_AVAILABLE_MAX_VALUE;
5774
let highestCorrelationId = -1;
5875

59-
if (this.getState() === _Task.CANCELED) {
60-
const errorMessage = 'The file upload transaction was canceled before it could be completed.';
61-
completionListener(false, bytesAvailable, null, errorMessage);
62-
this.onFinished();
63-
return;
64-
}
65-
6676
if (file === null) {
6777
const errorMessage = 'The file manager was unable to send the file. This could be because the file does not exist at the specified file path or that passed data is invalid.';
6878
completionListener(false, bytesAvailable, null, errorMessage);
69-
this.onFinished();
70-
return;
79+
return this.onFinished();
7180
}
7281

7382
if (file.getName() === null || file.getName() === undefined) {
7483
const errorMessage = 'You must specify a file name in the SdlFile';
7584
completionListener(false, bytesAvailable, null, errorMessage);
76-
this.onFinished();
77-
return;
85+
return this.onFinished();
7886
}
7987

8088
let data;
@@ -84,16 +92,14 @@ class _UploadFileOperation extends _Task {
8492
if (data === null) {
8593
const errorMessage = 'File at path was empty';
8694
completionListener(false, bytesAvailable, null, errorMessage);
87-
this.onFinished();
88-
return;
95+
return this.onFinished();
8996
}
9097
} else if (file.getFileData() !== null && file.getFileData() !== undefined) {
9198
data = file.getFileData();
9299
} else {
93100
const errorMessage = 'The SdlFile to upload does not specify its file data.';
94101
completionListener(false, bytesAvailable, null, errorMessage);
95-
this.onFinished();
96-
return;
102+
return this.onFinished();
97103
}
98104
const fileSize = data.length;
99105

lib/js/src/manager/file/filetypes/SdlArtwork.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,19 @@ class SdlArtwork extends SdlFile {
117117
}
118118
return image;
119119
}
120+
121+
/**
122+
* Creates a deep copy of the object
123+
* @returns {ChoiceCell} - A deep copy of the object
124+
*/
125+
clone () {
126+
const clonedParams = Object.assign({}, this); // shallow copy
127+
128+
if (clonedParams._imageRPC !== null) {
129+
clonedParams._imageRPC = Object.assign(new Image(), clonedParams._imageRPC);
130+
}
131+
return Object.assign(new SdlArtwork(), clonedParams);
132+
}
120133
}
121134

122135
export { SdlArtwork };

lib/js/src/manager/file/filetypes/SdlFile.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,19 @@ class SdlFile {
215215
}
216216
return true;
217217
}
218+
219+
/**
220+
* Creates a deep copy of the object
221+
* @returns {ChoiceCell} - A deep copy of the object
222+
*/
223+
clone () {
224+
const clonedParams = Object.assign({}, this); // shallow copy
225+
226+
if (clonedParams._fileData !== null && clonedParams._fileData !== undefined) {
227+
clonedParams._fileData = Uint8Array.from(clonedParams._fileData);
228+
}
229+
return Object.assign(new SdlFile(), clonedParams);
230+
}
218231
}
219232

220233

tests/managers/file/FileManagerTests.js

Lines changed: 1 addition & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -121,37 +121,6 @@ module.exports = function (appClient) {
121121
});
122122
}
123123

124-
let failEveryOtherRequest = true;
125-
/**
126-
* Handle multiple PutFile RPCs, succeeding in one and failing in the next.
127-
* @returns {Promise} - A promise.
128-
*/
129-
function onMultiplePutFileSuccess () {
130-
const response = new SDL.rpc.messages.PutFileResponse({
131-
functionName: SDL.rpc.enums.FunctionID.PutFiles,
132-
})
133-
.setSpaceAvailable(Test.GENERAL_INT);
134-
if (failEveryOtherRequest) {
135-
failEveryOtherRequest = !failEveryOtherRequest;
136-
response.setSuccess(true);
137-
// _handleRpc triggers the listener
138-
sdlManager._lifecycleManager._handleRpc(response);
139-
140-
return new Promise((resolve, reject) => {
141-
resolve(response);
142-
});
143-
} else {
144-
failEveryOtherRequest = !failEveryOtherRequest;
145-
response.setSuccess(false);
146-
// _handleRpc triggers the listener
147-
sdlManager._lifecycleManager._handleRpc(response);
148-
149-
return new Promise((resolve, reject) => {
150-
resolve(response);
151-
});
152-
}
153-
}
154-
155124
/**
156125
* Handle DeleteFile success.
157126
* @returns {Promise} - A promise.
@@ -411,12 +380,11 @@ module.exports = function (appClient) {
411380

412381
Validator.assertNull(await fileManager.deleteRemoteFilesWithNames([uploadedFileNames[0], uploadedFileNames[1]]));
413382
await new Promise (innerResolve => {
414-
const deleteOperation = new SDL.manager.file._DeleteFileOperation(sdlManager._lifecycleManager, uploadedFileNames[2], (success, bytesAvailable, fileNames, errorMessage) => {
383+
const deleteOperation = new SDL.manager.file._DeleteFileOperation(sdlManager._lifecycleManager, uploadedFileNames[2], [uploadedFileNames[2]], (success, bytesAvailable, fileNames, errorMessage) => {
415384
Validator.assertTrue(success);
416385
Validator.assertNull(errorMessage);
417386
innerResolve();
418387
});
419-
// await deleteOperation.onExecute();
420388
fileManager._addTask(deleteOperation);
421389
});
422390

@@ -426,70 +394,6 @@ module.exports = function (appClient) {
426394
stub.restore();
427395
});
428396

429-
it('testMultipleFileUploadPartialFailure', async function () {
430-
let firstFile = true;
431-
const expectSuccessThenFail = function (response) {
432-
if (firstFile) {
433-
Validator.assertTrue(response.getSuccess());
434-
} else {
435-
Validator.assertTrue(!response.getSuccess());
436-
}
437-
firstFile = !firstFile;
438-
};
439-
const expectSuccess = function (response) {
440-
Validator.assertTrue(response.getSuccess());
441-
};
442-
443-
sdlManager.addRpcListener(SDL.rpc.enums.FunctionID.ListFiles, expectSuccess);
444-
sdlManager.addRpcListener(SDL.rpc.enums.FunctionID.PutFile, expectSuccessThenFail);
445-
446-
const stub = sinon.stub(lifecycleManager, 'sendRpcResolve');
447-
stub.withArgs(sinon.match.instanceOf(SDL.rpc.messages.ListFiles))
448-
.callsFake(onListFilesSuccess);
449-
stub.withArgs(sinon.match.instanceOf(SDL.rpc.messages.PutFile))
450-
.callsFake(onMultiplePutFileSuccess);
451-
452-
await new Promise (resolve => {
453-
const listOperation = new SDL.manager.file._ListFilesOperation(sdlManager._lifecycleManager, (success, bytesAvailable, fileNames, errorMessage) => {
454-
resolve();
455-
});
456-
listOperation.onExecute();
457-
});
458-
459-
const baseFileName = 'file';
460-
let fileNum = 0;
461-
const filesToUpload = [];
462-
let sdlFile = new SDL.manager.file.filetypes.SdlFile();
463-
sdlFile.setName(baseFileName + fileNum++);
464-
sdlFile.setFilePath('./test_icon_1.png');
465-
filesToUpload.push(sdlFile);
466-
467-
sdlFile = new SDL.manager.file.filetypes.SdlFile();
468-
sdlFile.setName(baseFileName + fileNum++);
469-
sdlFile.setFileData(new Array(50));
470-
sdlFile.setPersistent(true);
471-
sdlFile.setType(SDL.rpc.enums.FileType.BINARY);
472-
filesToUpload.push(sdlFile);
473-
let uploadedFileNames = fileManager.getRemoteFileNames();
474-
475-
await fileManager.uploadFiles(filesToUpload[0]);
476-
await new Promise (resolve => {
477-
// this task will run after the previous
478-
const uploadOperation = new SDL.manager.file._UploadFileOperation(sdlManager._lifecycleManager, sdlManager._fileManager, new SDL.manager.file._SdlFileWrapper(filesToUpload[1], (success, bytesAvailable, fileNames, errorMessage) => {
479-
resolve();
480-
}));
481-
sdlManager._fileManager._addTask(uploadOperation);
482-
});
483-
stub.restore();
484-
485-
uploadedFileNames = fileManager.getRemoteFileNames();
486-
Validator.assertTrue(!uploadedFileNames.includes(filesToUpload[0].getName()));
487-
Validator.assertTrue(uploadedFileNames.includes(filesToUpload[1].getName()));
488-
489-
sdlManager.removeRpcListener(SDL.rpc.enums.FunctionID.PutFile, expectSuccessThenFail);
490-
sdlManager.removeRpcListener(SDL.rpc.enums.FunctionID.ListFiles, expectSuccess);
491-
});
492-
493397
it('testMultipleArtworkUploadSuccess', async function () {
494398
const expectSuccess = function (response) {
495399
Validator.assertTrue(response.getSuccess());

0 commit comments

Comments
 (0)