Commit 6c5d3313 authored by Tatsuhisa Yamaguchi's avatar Tatsuhisa Yamaguchi Committed by Commit Bot

Create zip file in temporary storage.

This will avoid exposing in-progress zip file to the destination folder.
It will resolve the issues like:
- the zip file is synced to Drive with incomplete state, having multiple
  revisions
- when cancelling zip packing, the file appears in the trash of Drive

Bug: 714579,785096,785093,785086,797873
Cq-Include-Trybots: master.tryserver.chromium.linux:closure_compilation
Change-Id: I0b32515d5e896d87e1dee499f1eea40dd7a481f0
Reviewed-on: https://chromium-review.googlesource.com/844442
Commit-Queue: Tatsuhisa Yamaguchi <yamaguchi@chromium.org>
Reviewed-by: default avatarNaoki Fukino <fukino@chromium.org>
Cr-Commit-Position: refs/heads/master@{#547428}
parent bf8191d0
......@@ -880,4 +880,55 @@ unpacker.app = {
else
unpacker.app.onLaunchedWithUnpack(launchData, opt_onSuccess, opt_onError);
},
/**
* Clean all temporary files inside the work directory.
* Those files are usually moved to the destination directory when finished
* or removed when any error happened, but might be left there when the
* extension was aborted by runtime errors or system shutdown.
*/
cleanWorkDirectory: function() {
return new Promise(
webkitRequestFileSystem.bind(null, TEMPORARY, 0 /* size */))
.then((fs) => new Promise(function(resolve, reject) {
const reader = fs.root.createReader();
let allEntries = [];
function scanEntries() {
reader.readEntries((entries) => {
if (entries.length == 0) {
resolve(allEntries);
return;
}
Array.prototype.push.apply(allEntries, entries);
scanEntries();
});
}
scanEntries();
}))
.then((allEntries) => {
allEntries.forEach((entry) => {
if (entry.isDirectory) {
entry.removeRecursively(
function() {
console.info(
'Found directory. ',
'Perhaps the extension had exited abnormally.', entry);
},
function() {
console.error('Failed to remove a directory:', entry);
});
} else {
entry.remove(
function() {
console.info(
'Found a temporary file. ',
'Perhaps the extension had exited abnormally.', entry);
},
function() {
console.error('Failed to remove a temporary file:', entry);
});
}
});
});
}
};
......@@ -30,3 +30,7 @@ unpacker.app.loadNaclModule('module.nmf', 'application/x-pnacl');
// Load translations
unpacker.app.loadStringData();
// Clean all temporary files inside the work directory, just in case the
// extension aborted previously without removing ones.
unpacker.app.cleanWorkDirectory();
......@@ -179,65 +179,51 @@ unpacker.Compressor.prototype.archiveFileEntry = function() {
* @private
*/
unpacker.Compressor.prototype.getArchiveFile_ = function() {
var compressor = this;
var suggestedName = compressor.archiveName_;
var suggestedName = this.archiveName_;
var saveZipFileInParentDir = function(rootEntry) {
var saveZipFile = (rootEntry) => {
// If parent directory of currently selected files is available then we
// deduplicate |suggestedName| and save the zip file.
if (!rootEntry) {
console.error('rootEntry of selected files is undefined');
compressor.onError_(compressor.compressorId_);
this.onErrorInternal_();
return;
}
fileOperationUtils.deduplicateFileName(suggestedName, rootEntry)
.then(function(newName) {
compressor.archiveName_ = newName;
.then((newName) => {
// Create an archive file.
return (new Promise(function(resolve, reject) {
rootEntry.getFile(
newName, {create: true, exclusive: true}, resolve,
reject);
}))
.then(function(zipEntry) {
compressor.archiveFileEntry_ = zipEntry;
compressor.sendCreateArchiveRequest_();
.then((zipEntry) => {
this.archiveFileEntry_ = zipEntry;
this.sendCreateArchiveRequest_();
});
})
.catch(function(error) {
console.error(error);
compressor.onError_(compressor.compressorId_);
.catch((error) => {
console.error('failed to create a ZIP file', error);
this.onErrorInternal_();
});
};
// Get all accessible volumes with their metadata
chrome.fileManagerPrivate.getVolumeMetadataList(function(volumeMetadataList) {
// Here we call chrome.fileSystem.requestFileSystem on each volume's
// metadata entry to be able to sucessfully execute
// resolveIsolatedEntries later.
Promise
.all(compressor.requestAccessPermissionForVolumes_(volumeMetadataList))
.then(function(result) {
chrome.fileManagerPrivate.resolveIsolatedEntries(
[compressor.items_[0].entry], function(result) {
if (result && result.length >= 1) {
result[0].getParent(saveZipFileInParentDir);
} else {
console.error('Failed to resolve isolated entries!');
if (chrome.runtime.lastError)
console.error(chrome.runtime.lastError.message);
compressor.onError_(compressor.compressorId_);
}
});
})
.catch(function(error) {
console.error(error);
compressor.onError_(compressor.compressorId_);
});
});
new Promise(function(resolve, reject) {
navigator.webkitTemporaryStorage
.queryUsageAndQuota(function(used, granted) {
resolve(granted);
}, reject);
})
.then(
(quota) =>
new Promise(webkitRequestFileSystem.bind(null, TEMPORARY, quota)))
.then((fs) => {
saveZipFile(fs.root);
this.fs = fs.root;
})
.catch((domException) => {
console.error(domException);
this.onErrorInternal_();
});
};
/**
......@@ -292,7 +278,7 @@ unpacker.Compressor.prototype.getSingleMetadata_ = function(entry) {
}.bind(this),
function(error) {
console.error('Failed to get metadata: ' + error.message + '.');
this.onError_(this.compressorId_);
this.onErrorInternal_();
}.bind(this));
};
......@@ -320,7 +306,7 @@ unpacker.Compressor.prototype.getDirectoryEntryMetadata_ = function(dir) {
function(error) {
console.error(
'Failed to get directory entries: ' + error.message + '.');
this.onError_(this.compressorId_);
this.onErrorInternal_();
}.bind(this));
}.bind(this);
......@@ -439,7 +425,7 @@ unpacker.Compressor.prototype.onReadFileChunk_ = function(data) {
// If the first argument(length) is negative, it means that an error
// occurred in reading a chunk.
this.sendReadFileChunkDone_(-1, buffer);
this.onError_(this.compressorId_);
this.onErrorInternal_();
return;
}
......@@ -447,7 +433,7 @@ unpacker.Compressor.prototype.onReadFileChunk_ = function(data) {
this.sendReadFileChunkDone_(length, buffer);
}.bind(this);
reader.onerror = function(event) {
reader.onerror = (event) => {
console.error(
'Failed to read file chunk. Name: ' + file.name +
', offset: ' + this.offset_ + ', length: ' + length + '.');
......@@ -455,7 +441,7 @@ unpacker.Compressor.prototype.onReadFileChunk_ = function(data) {
// If the first argument(length) is negative, it means that an error
// occurred in reading a chunk.
this.sendReadFileChunkDone_(-1, new ArrayBuffer(0));
this.onError_(this.compressorId_);
this.onErrorInternal_();
};
reader.readAsArrayBuffer(file);
......@@ -511,11 +497,10 @@ unpacker.Compressor.prototype.writeChunk_ = function(
fileWriter.onerror = function(event) {
console.error(
'Failed to write chunk to ' + this.archiveFileEntry_ + '.');
// If the first argument(length) is negative, it means that an error
// occurred in writing a chunk.
callback(-1 /* length */);
this.onError_(this.compressorId_);
this.onErrorInternal_();
}.bind(this);
// Create a new Blob and append it to the archive file.
......@@ -526,7 +511,7 @@ unpacker.Compressor.prototype.writeChunk_ = function(
function(event) {
console.error(
'Failed to create writer for ' + this.archiveFileEntry_ + '.');
this.onError_(this.compressorId_);
this.onErrorInternal_();
}.bind(this));
};
......@@ -556,6 +541,62 @@ unpacker.Compressor.prototype.onAddToArchiveDone_ = function() {
this.sendAddToArchiveRequest_();
};
/**
* Moves the temporary file to actual destination folder.
*/
unpacker.Compressor.prototype.moveZipFileToActualDestination = function() {
var suggestedName = this.getArchiveName_();
var moveZipFileToParentDir = (rootEntry) => {
// If parent directory of currently selected files is available then we
// deduplicate |suggestedName| and save the zip file.
if (!rootEntry) {
console.error('rootEntry of selected files is undefined');
this.onErrorInternal_();
return;
}
fileOperationUtils.deduplicateFileName(suggestedName, rootEntry)
.then((newName) => {
this.archiveFileEntry_.moveTo(
rootEntry, newName, function() {},
(error) => {
console.error('Failed to move the file to destination.');
this.onErrorInternal_();
});
})
.catch((error) => {
console.error(error);
this.onErrorInternal_();
});
};
// Get all accessible volumes with their metadata
chrome.fileManagerPrivate.getVolumeMetadataList((volumeMetadataList) => {
// Here we call chrome.fileSystem.requestFileSystem on each volume's
// metadata entry to be able to sucessfully execute
// resolveIsolatedEntries later.
Promise.all(this.requestAccessPermissionForVolumes_(volumeMetadataList))
.then((result) => {
chrome.fileManagerPrivate.resolveIsolatedEntries(
[this.items_[0].entry], (result) => {
if (result && result.length >= 1) {
result[0].getParent(moveZipFileToParentDir);
} else {
console.error('Failed to resolve isolated entries!');
if (chrome.runtime.lastError)
console.error(chrome.runtime.lastError.message);
this.onErrorInternal_();
}
});
})
.catch((error) => {
console.error(error);
this.onErrorInternal_();
});
});
};
/**
* A handler of close archive responses.
* Receiving this response means the entire packing process has finished.
......@@ -572,6 +613,7 @@ unpacker.Compressor.prototype.onCloseArchiveDone_ = function() {
*/
unpacker.Compressor.prototype.onCancelArchiveDone_ = function() {
console.warn('Archive for "' + this.compressorId_ + '" has been canceled.');
this.removeTemporaryFileIfExists_();
this.onCancel_(this.compressorId_);
};
......@@ -608,6 +650,7 @@ unpacker.Compressor.prototype.processMessage = function(data, operation) {
break;
case unpacker.request.Operation.CLOSE_ARCHIVE_DONE:
this.moveZipFileToActualDestination();
this.sendReleaseCompressor();
this.onCloseArchiveDone_();
break;
......@@ -623,13 +666,13 @@ unpacker.Compressor.prototype.processMessage = function(data, operation) {
data[unpacker.request.Key.ERROR]); // The error contains
// the '.' at the end.
this.sendReleaseCompressor();
this.onError_(this.compressorId_);
this.onErrorInternal_();
break;
default:
console.error('Invalid NaCl operation: ' + operation + '.');
this.sendReleaseCompressor();
this.onError_(this.compressorId_);
this.onErrorInternal_();
}
};
......@@ -664,3 +707,25 @@ unpacker.Compressor.prototype.requestAccessPermissionForVolumes_ = function(
return promises;
};
/**
* Cleans up the temporary file and notify error.
*/
unpacker.Compressor.prototype.onErrorInternal_ = function() {
this.removeTemporaryFileIfExists_();
this.onError_(this.compressorId_);
};
/**
* Removes the temporary zip file in the local storage.
*/
unpacker.Compressor.prototype.removeTemporaryFileIfExists_ = function() {
if (!this.archiveFileEntry_)
return;
this.archiveFileEntry_ = null;
this.archiveFileEntry_.remove(
function() {},
function(error) {
console.error('failed to remove temporary file.');
});
};
......@@ -23,7 +23,7 @@
]
},
"notifications",
"storage"
"unlimitedStorage"
],
"file_system_provider_capabilities": {
"multipleMounts": true,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment