blob: 4f6ac8fbf5c682fe7309c531365c0ca167f70042 [file] [log] [blame]
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
/* global cordova */
var exec = require('cordova/exec');
var FileError = require('./FileError');
var FileReader = require('./FileReader');
var ProgressEvent = require('./ProgressEvent');
/**
* This class writes to the mobile device file system.
*
* For Android:
* The root directory is the root of the file system.
* To write to the SD card, the file name is "sdcard/my_file.txt"
*
* @constructor
* @param file {File} File object containing file properties
* @param append if true write to the end of the file, otherwise overwrite the file
*/
var FileWriter = function (file) {
this.fileName = '';
this.length = 0;
if (file) {
this.localURL = file.localURL || file;
this.length = file.size || 0;
}
// default is to write at the beginning of the file
this.position = 0;
this.readyState = 0; // EMPTY
this.result = null;
// Error
this.error = null;
// Event handlers
this.onwritestart = null; // When writing starts
this.onprogress = null; // While writing the file, and reporting partial file data
this.onwrite = null; // When the write has successfully completed.
this.onwriteend = null; // When the request has completed (either in success or failure).
this.onabort = null; // When the write has been aborted. For instance, by invoking the abort() method.
this.onerror = null; // When the write has failed (see errors).
};
// States
FileWriter.INIT = 0;
FileWriter.WRITING = 1;
FileWriter.DONE = 2;
/**
* Abort writing file.
*/
FileWriter.prototype.abort = function () {
// check for invalid state
if (this.readyState === FileWriter.DONE || this.readyState === FileWriter.INIT) {
throw new FileError(FileError.INVALID_STATE_ERR);
}
// set error
this.error = new FileError(FileError.ABORT_ERR);
this.readyState = FileWriter.DONE;
// If abort callback
if (typeof this.onabort === 'function') {
this.onabort(new ProgressEvent('abort', { target: this }));
}
// If write end callback
if (typeof this.onwriteend === 'function') {
this.onwriteend(new ProgressEvent('writeend', { target: this }));
}
};
/**
* Writes data to the file
*
* @param data text or blob to be written
* @param isPendingBlobReadResult {Boolean} true if the data is the pending blob read operation result
*/
FileWriter.prototype.write = function (data, isPendingBlobReadResult) {
var that = this;
var supportsBinary = typeof window.Blob !== 'undefined' && typeof window.ArrayBuffer !== 'undefined';
var isProxySupportBlobNatively = cordova.platformId === 'windows8' || cordova.platformId === 'windows';
var isBinary;
// Check to see if the incoming data is a blob
if (data instanceof File || (!isProxySupportBlobNatively && supportsBinary && data instanceof Blob)) {
var fileReader = new FileReader();
fileReader.onload = function () {
// Call this method again, with the arraybuffer as argument
FileWriter.prototype.write.call(that, this.result, true /* isPendingBlobReadResult */);
};
fileReader.onerror = function () {
// DONE state
that.readyState = FileWriter.DONE;
// Save error
that.error = this.error;
// If onerror callback
if (typeof that.onerror === 'function') {
that.onerror(new ProgressEvent('error', { target: that }));
}
// If onwriteend callback
if (typeof that.onwriteend === 'function') {
that.onwriteend(new ProgressEvent('writeend', { target: that }));
}
};
// WRITING state
this.readyState = FileWriter.WRITING;
if (supportsBinary) {
fileReader.readAsArrayBuffer(data);
} else {
fileReader.readAsText(data);
}
return;
}
// Mark data type for safer transport over the binary bridge
isBinary = supportsBinary && data instanceof ArrayBuffer;
if (isBinary && cordova.platformId === 'windowsphone') {
// create a plain array, using the keys from the Uint8Array view so that we can serialize it
data = Array.apply(null, new Uint8Array(data));
}
// Throw an exception if we are already writing a file
if (this.readyState === FileWriter.WRITING && !isPendingBlobReadResult) {
throw new FileError(FileError.INVALID_STATE_ERR);
}
// WRITING state
this.readyState = FileWriter.WRITING;
var me = this;
// If onwritestart callback
if (typeof me.onwritestart === 'function') {
me.onwritestart(new ProgressEvent('writestart', { target: me }));
}
// Write file
exec(
// Success callback
function (r) {
// If DONE (cancelled), then don't do anything
if (me.readyState === FileWriter.DONE) {
return;
}
// position always increases by bytes written because file would be extended
me.position += r;
// The length of the file is now where we are done writing.
me.length = me.position;
// DONE state
me.readyState = FileWriter.DONE;
// If onwrite callback
if (typeof me.onwrite === 'function') {
me.onwrite(new ProgressEvent('write', { target: me }));
}
// If onwriteend callback
if (typeof me.onwriteend === 'function') {
me.onwriteend(new ProgressEvent('writeend', { target: me }));
}
},
// Error callback
function (e) {
// If DONE (cancelled), then don't do anything
if (me.readyState === FileWriter.DONE) {
return;
}
// DONE state
me.readyState = FileWriter.DONE;
// Save error
me.error = new FileError(e);
// If onerror callback
if (typeof me.onerror === 'function') {
me.onerror(new ProgressEvent('error', { target: me }));
}
// If onwriteend callback
if (typeof me.onwriteend === 'function') {
me.onwriteend(new ProgressEvent('writeend', { target: me }));
}
},
'File',
'write',
[this.localURL, data, this.position, isBinary]
);
};
/**
* Moves the file pointer to the location specified.
*
* If the offset is a negative number the position of the file
* pointer is rewound. If the offset is greater than the file
* size the position is set to the end of the file.
*
* @param offset is the location to move the file pointer to.
*/
FileWriter.prototype.seek = function (offset) {
// Throw an exception if we are already writing a file
if (this.readyState === FileWriter.WRITING) {
throw new FileError(FileError.INVALID_STATE_ERR);
}
if (!offset && offset !== 0) {
return;
}
// See back from end of file.
if (offset < 0) {
this.position = Math.max(offset + this.length, 0);
// Offset is bigger than file size so set position
// to the end of the file.
} else if (offset > this.length) {
this.position = this.length;
// Offset is between 0 and file size so set the position
// to start writing.
} else {
this.position = offset;
}
};
/**
* Truncates the file to the size specified.
*
* @param size to chop the file at.
*/
FileWriter.prototype.truncate = function (size) {
// Throw an exception if we are already writing a file
if (this.readyState === FileWriter.WRITING) {
throw new FileError(FileError.INVALID_STATE_ERR);
}
// WRITING state
this.readyState = FileWriter.WRITING;
var me = this;
// If onwritestart callback
if (typeof me.onwritestart === 'function') {
me.onwritestart(new ProgressEvent('writestart', { target: this }));
}
// Write file
exec(
// Success callback
function (r) {
// If DONE (cancelled), then don't do anything
if (me.readyState === FileWriter.DONE) {
return;
}
// DONE state
me.readyState = FileWriter.DONE;
// Update the length of the file
me.length = r;
me.position = Math.min(me.position, r);
// If onwrite callback
if (typeof me.onwrite === 'function') {
me.onwrite(new ProgressEvent('write', { target: me }));
}
// If onwriteend callback
if (typeof me.onwriteend === 'function') {
me.onwriteend(new ProgressEvent('writeend', { target: me }));
}
},
// Error callback
function (e) {
// If DONE (cancelled), then don't do anything
if (me.readyState === FileWriter.DONE) {
return;
}
// DONE state
me.readyState = FileWriter.DONE;
// Save error
me.error = new FileError(e);
// If onerror callback
if (typeof me.onerror === 'function') {
me.onerror(new ProgressEvent('error', { target: me }));
}
// If onwriteend callback
if (typeof me.onwriteend === 'function') {
me.onwriteend(new ProgressEvent('writeend', { target: me }));
}
},
'File',
'truncate',
[this.localURL, size]
);
};
module.exports = FileWriter;