extjs-gpl
Version:
GPL licensed version of Sencha Ext JS
924 lines (864 loc) • 38.4 kB
JavaScript
/**
* @private
*/
Ext.define('Ext.device.filesystem.HTML5', {
extend: 'Ext.device.filesystem.Abstract',
/**
* Requests a {@link Ext.device.filesystem.FileSystem} instance.
*
* var me = this;
* var fs = Ext.create("Ext.device.FileSystem", {});
* fs.requestFileSystem({
* type: window.PERSISTENT,
* size: 1024 * 1024,
* success: function(fileSystem) {
* me.fs = fileSystem;
* },
* failure: function(err) {
* console.log("FileSystem Failure: " + err.code);
* }
* });
*
* @param {Object} config
* The object which contains the following config options:
*
* @param {Number} config.type
* window.TEMPORARY (0) or window.PERSISTENT (1)
*
* @param {Number} config.size
* Storage space, in Bytes, needed by the application
*
* @param {Function} config.success This is required.
* The callback to be called when the file system has been successfully created.
*
* @param {Ext.device.filesystem.FileSystem} config.success.fileSystem
* The created file system.
*
* @param {Function} config.failure This is optional.
* The callback to be called when an error occurred.
*
* @param {Object} config.failure.error
* The occurred error.
*
* @param {Object} config.scope
* The scope object
*/
requestFileSystem: function(config) {
if (!config.success) {
Ext.Logger.error('Ext.device.filesystem#requestFileSystem: You must specify a `success` callback.');
return null;
}
var me = this;
var successCallback = function(fs) {
var fileSystem = Ext.create('Ext.device.filesystem.FileSystem', fs);
config.success.call(config.scope || me, fileSystem);
};
window.requestFileSystem(
config.type,
config.size,
successCallback,
config.failure || Ext.emptyFn
);
}
}, function() {
/**
* The FileSystem class which is used to represent a file system.
*/
Ext.define('Ext.device.filesystem.FileSystem', {
fs: null,
root: null,
constructor: function(fs) {
this.fs = fs;
this.root = Ext.create('Ext.device.filesystem.DirectoryEntry', '/', this);
},
/**
* Returns a {@link Ext.device.filesystem.DirectoryEntry} instance for the root of the file system.
*
* @return {Ext.device.filesystem.DirectoryEntry}
* The file system root directory.
*/
getRoot: function() {
return this.root;
}
}, function() {
/**
* The Entry class which is used to represent entries in a file system,
* each of which may be a {@link Ext.device.filesystem.FileEntry} or a {@link Ext.device.filesystem.DirectoryEntry}.
*
* This is an abstract class.
* @abstract
*/
Ext.define('Ext.device.filesystem.Entry', {
directory: false,
path: 0,
fileSystem: null,
entry: null,
constructor: function(directory, path, fileSystem) {
this.directory = directory;
this.path = path;
this.fileSystem = fileSystem;
},
/**
* Returns whether the entry is a file.
*
* @return {Boolean}
* The entry is a file.
*/
isFile: function() {
return !this.directory;
},
/**
* Returns whether the entry is a directory.
*
* @return {Boolean}
* The entry is a directory.
*/
isDirectory: function() {
return this.directory;
},
/**
* Returns the name of the entry, excluding the path leading to it.
*
* @return {String}
* The entry name.
*/
getName: function() {
var components = this.path.split('/');
for (var i = components.length - 1; i >= 0; --i) {
if (components[i].length > 0) {
return components[i];
}
}
return '/';
},
/**
* Returns the full absolute path from the root to the entry.
*
* @return {String}
* The entry full path.
*/
getFullPath: function() {
return this.path;
},
/**
* Returns the file system on which the entry resides.
*
* @return {Ext.device.filesystem.FileSystem}
* The entry file system.
*/
getFileSystem: function() {
return this.fileSystem;
},
getEntry: function() {
return null;
},
/**
* Moves the entry to a different location on the file system.
*
* @param {Object} config
* The object which contains the following config options:
*
* @param {Ext.device.filesystem.DirectoryEntry} config.parent This is required.
* The directory to which to move the entry.
*
* @param {String} config.newName This is optional.
* The new name of the entry to move. Defaults to the entry's current name if unspecified.
*
* @param {Function} config.success This is optional.
* The callback to be called when the entry has been successfully moved.
*
* @param {Ext.device.filesystem.Entry} config.success.entry
* The entry for the new location.
*
* @param {Function} config.failure This is optional.
* The callback to be called when an error occurred.
*
* @param {Object} config.failure.error
* The occurred error.
*
* @param {Object} config.scope
* The scope object
*/
moveTo: function(config) {
if (config.parent == null) {
Ext.Logger.error('Ext.device.filesystem.Entry#moveTo: You must specify a new `parent` of the entry.');
return null;
}
var me = this;
this.getEntry(
{
options: config.options || {},
success: function(sourceEntry) {
config.parent.getEntry(
{
options: config.options || {},
success: function(destinationEntry) {
if (config.copy) {
sourceEntry.copyTo(destinationEntry, config.newName, function(entry) {
config.success.call(
config.scope || me,
entry.isDirectory ? Ext.create('Ext.device.filesystem.DirectoryEntry', entry.fullPath, me.fileSystem) : Ext.create('Ext.device.filesystem.FileEntry', entry.fullPath, me.fileSystem)
);
}, config.failure);
} else {
sourceEntry.moveTo(destinationEntry, config.newName, function(entry) {
config.success.call(
config.scope || me,
entry.isDirectory ? Ext.create('Ext.device.filesystem.DirectoryEntry', entry.fullPath, me.fileSystem) : Ext.create('Ext.device.filesystem.FileEntry', entry.fullPath, me.fileSystem)
);
}, config.failure);
}
},
failure: config.failure
}
);
},
failure: config.failure
}
);
},
/**
* Works the same way as {@link Ext.device.filesystem.Entry#moveTo}, but copies the entry.
*/
copyTo: function(config) {
this.moveTo(Ext.apply(config, {
copy: true
}));
},
/**
* Removes the entry from the file system.
*
* @param {Object} config
* The object which contains the following config options:
*
* @param {Boolean} config.recursively This is optional
* Deletes a directory and all of its contents
*
* @param {Function} config.success This is optional.
* The callback to be called when the entry has been successfully removed.
*
* @param {Function} config.failure This is optional.
* The callback to be called when an error occurred.
*
* @param {Object} config.failure.error
* The occurred error.
*
* @param {Object} config.scope
* The scope object
*/
remove: function(config) {
this.getEntry(
{
success: function(entry) {
if (config.recursively && this.directory) {
entry.removeRecursively(config.success, config.failure)
} else {
entry.remove(config.success, config.failure)
}
},
failure: config.failure
}
)
},
/**
* Looks up the parent directory containing the entry.
*
* @param {Object} config
* The object which contains the following config options:
*
* @param {Function} config.success This is required.
* The callback to be called when the parent directory has been successfully selected.
*
* @param {Ext.device.filesystem.DirectoryEntry} config.success.entry
* The parent directory of the entry.
*
* @param {Function} config.failure This is optional.
* The callback to be called when an error occurred.
*
* @param {Object} config.failure.error
* The occurred error.
*
* @param {Object} config.scope
* The scope object
*/
getParent: function(config) {
if (!config.success) {
Ext.Logger.error('Ext.device.filesystem.Entry#getParent: You must specify a `success` callback.');
return null;
}
var me = this;
this.getEntry(
{
options: config.options || {},
success: function(entry) {
entry.getParent(
function(parentEntry) {
config.success.call(
config.scope || me,
parentEntry.isDirectory
? Ext.create('Ext.device.filesystem.DirectoryEntry', parentEntry.fullPath, me.fileSystem)
: Ext.create('Ext.device.filesystem.FileEntry', parentEntry.fullPath, me.fileSystem)
)
},
config.failure
)
},
failure: config.failure
}
)
}
});
/**
* The DirectoryEntry class which is used to represent a directory on a file system.
*/
Ext.define('Ext.device.filesystem.DirectoryEntry', {
extend: 'Ext.device.filesystem.Entry',
cachedDirectory: null,
constructor: function(path, fileSystem) {
this.callParent([true, path, fileSystem]);
},
/**
* Requests a Directory from the Local File System
*
* @param {Object} config
*
* @param {Object} config.options
* File creation options {create:true, exclusive:false}
*
* @param {Boolean} config.options.create
* Indicates if the directory should be created if it doesn't exist
*
* @param {Boolean} config.options.exclusive
* Used with the create option only indicates whether a creation causes an error if the directory already exists
*
* @param {Function} config.success
* The function called when the Directory is returned successfully
*
* @param {Ext.device.filesystem.DirectoryEntry} config.success.directory
* DirectoryEntry Object
*
* @param {Function} config.failure
* The function called when the Directory request causes an error
*
* @param {FileError} config.failure.error
*/
getEntry: function(config) {
var me = this;
var callback = config.success;
if ((config.options && config.options.create) && this.path) {
var folders = this.path.split("/");
if (folders[0] == '.' || folders[0] == '') {
folders = folders.slice(1);
}
var recursiveCreation = function(dirEntry) {
if (folders.length) {
dirEntry.getDirectory(folders.shift(), config.options, recursiveCreation, config.failure);
} else {
callback(dirEntry);
}
};
recursiveCreation(this.fileSystem.fs.root);
} else {
this.fileSystem.fs.root.getDirectory(this.path, config.options,
function(directory) {
config.success.call(config.scope || me, directory);
},
config.failure
);
}
},
/**
* Lists all the entries in the directory.
*
* @param {Object} config
* The object which contains the following config options:
*
* @param {Function} config.success This is required.
* The callback to be called when the entries has been successfully read.
*
* @param {Ext.device.filesystem.Entry[]} config.success.entries
* The array of entries of the directory.
*
* @param {Function} config.failure This is optional.
* The callback to be called when an error occurred.
*
* @param {Object} config.failure.error
* The occurred error.
*
* @param {Object} config.scope
* The scope object
*/
readEntries: function(config) {
if (!config.success) {
Ext.Logger.error('Ext.device.filesystem.DirectoryEntry#readEntries: You must specify a `success` callback.');
return null;
}
var me = this;
this.getEntry(
{
success: function(dirEntry) {
var directoryReader = dirEntry.createReader();
directoryReader.readEntries(
function(entryInfos) {
var entries = [],
i = 0,
len = entryInfos.length;
for (; i < len; i++) {
entryInfo = entryInfos[i];
entries[i] = entryInfo.isDirectory
? Ext.create('Ext.device.filesystem.DirectoryEntry', entryInfo.fullPath, me.fileSystem)
: Ext.create('Ext.device.filesystem.FileEntry', entryInfo.fullPath, me.fileSystem);
}
config.success.call(config.scope || this, entries);
},
function(error) {
if (config.failure) {
config.failure.call(config.scope || this, error);
}
}
);
},
failure: config.failure
}
);
},
/**
* Creates or looks up a file.
*
* @param {Object} config
* The object which contains the following config options:
*
* @param {String} config.path This is required.
* The absolute path or relative path from the entry to the file to create or select.
*
* @param {Object} config.options This is optional.
* The object which contains the following options:
*
* @param {Boolean} config.options.create This is optional.
* Indicates whether to create a file, if path does not exist.
*
* @param {Boolean} config.options.exclusive This is optional. Used with 'create', by itself has no effect.
* Indicates that method should fail, if path already exists.
*
* @param {Function} config.success This is optional.
* The callback to be called when the file has been successfully created or selected.
*
* @param {Ext.device.filesystem.Entry} config.success.entry
* The created or selected file.
*
* @param {Function} config.failure This is optional.
* The callback to be called when an error occurred.
*
* @param {Object} config.failure.error
* The occurred error.
*
* @param {Object} config.scope
* The scope object
*/
getFile: function(config) {
if (config.path == null) {
Ext.Logger.error('Ext.device.filesystem.DirectoryEntry#getFile: You must specify a `path` of the file.');
return null;
}
var me = this;
var fullPath = this.path + config.path;
var fileEntry = Ext.create('Ext.device.filesystem.FileEntry', fullPath, this.fileSystem);
fileEntry.getEntry(
{
success: function() {
config.success.call(config.scope || me, fileEntry);
},
options: config.options || {},
failure: config.failure
}
)
},
/**
* Works the same way as {@link Ext.device.filesystem.DirectoryEntry#getFile},
* but creates or looks up a directory.
*/
getDirectory: function(config) {
if (config.path == null) {
Ext.Logger.error('Ext.device.filesystem.DirectoryEntry#getFile: You must specify a `path` of the file.');
return null;
}
var me = this;
var fullPath = this.path + config.path;
var directoryEntry = Ext.create('Ext.device.filesystem.DirectoryEntry', fullPath, this.fileSystem);
directoryEntry.getEntry(
{
success: function() {
config.success.call(config.scope || me, directoryEntry);
},
options: config.options || {},
failure: config.failure
}
)
},
/**
* Works the same way as {@link Ext.device.filesystem.Entry#remove},
* but removes the directory and all of its contents, if any.
*/
removeRecursively: function(config) {
this.remove(Ext.apply(config, {
recursively: true
}));
}
});
/**
* The FileEntry class which is used to represent a file on a file system.
*/
Ext.define('Ext.device.filesystem.FileEntry', {
extend: 'Ext.device.filesystem.Entry',
length: 0,
offset: 0,
constructor: function(path, fileSystem) {
this.callParent([false, path, fileSystem]);
this.offset = 0;
this.length = 0;
},
/**
* Requests a File Handle from the Local File System
*
* @param {Object} config
*
* @param {String} config.file
* Filename optionally including path in string format '/tmp/debug.txt' or a File Object
*
* @param {Object} config.options
* File creation options {create:true, exclusive:false}
*
* @param {Boolean} config.options.create
* Indicates if the file should be created if it doesn't exist
*
* @param {Boolean} config.options.exclusive
* Used with the create option only indicates whether a creation causes an error if the file already exists
*
* @param {Function} config.success
* The function called when the filesystem is returned successfully
*
* @param {FileSystem} config.success.entry
*
* @param {Function} config.failure
* The function called when the filesystem request causes and error
*
* @param {FileError} config.failure.error
*
*/
getEntry: function(config) {
var me = this;
var originalConfig = Ext.applyIf({}, config);
if (this.fileSystem) {
var failure = function(evt) {
if ((config.options && config.options.create) && Ext.isString(this.path)) {
var folders = this.path.split("/");
if (folders[0] == '.' || folders[0] == '') {
folders = folders.slice(1);
}
if (folders.length > 1 && !config.recursive === true) {
folders.pop();
var dirEntry = Ext.create('Ext.device.filesystem.DirectoryEntry', folders.join("/"), me.fileSystem);
dirEntry.getEntry(
{
options: config.options,
success: function() {
originalConfig.recursive = true;
me.getEntry(originalConfig);
},
failure: config.failure
}
);
} else {
if (config.failure) {
config.failure.call(config.scope || me, evt);
}
}
} else {
if (config.failure) {
config.failure.call(config.scope || me, evt);
}
}
};
this.fileSystem.fs.root.getFile(this.path, config.options || null,
function(fileEntry) {
fileEntry.file(
function(file) {
me.length = file.size;
originalConfig.success.call(config.scope || me, fileEntry);
},
function(error) {
failure.call(config.scope || me, error);
}
);
},
function(error) {
failure.call(config.scope || me, error);
}
);
} else {
config.failure({code: -1, message: "FileSystem not Initialized"});
}
},
/**
* Returns the byte offset into the file at which the next read/write will occur.
*
* @return {Number}
* The file offset.
*/
getOffset: function() {
return this.offset;
},
/**
* Sets the byte offset into the file at which the next read/write will occur.
*
* @param {Object} config
* The object which contains the following config options:
*
* @param {Number} config.offset This is required.
* The file offset to set. If negative, the offset back from the end of the file.
*
* @param {Function} config.success This is optional.
* The callback to be called when the file offset has been successfully set.
*
* @param {Function} config.failure This is optional.
* The callback to be called when an error occurred.
*
* @param {Object} config.failure.error
* The occurred error.
*
* @param {Object} config.scope
* The scope object
*/
seek: function(config) {
if (config.offset == null) {
Ext.Logger.error('Ext.device.filesystem.FileEntry#seek: You must specify an `offset` in the file.');
return null;
}
this.offset = config.offset || 0;
if (config.success) {
config.success.call(config.scope || this);
}
},
/**
* Reads the data from the file starting at the file offset.
*
* @param {Object} config
* The object which contains the following config options:
*
* @param {Number} config.length This is optional.
* The length of bytes to read from the file. Defaults to the file's current size if unspecified.
*
* @param {String} config.encoding
* Optional encoding type used only for reading as Text
*
* @param {String} config.type
* Type of reading to use options are "text" (default), "dataURL", "binaryString" and "arrayBuffer"
*
* @param {Object} config.reader
* Optional config params to be applied to a File Reader
*
* @param {Function} config.reader.onloadstart
* @param {Function} config.reader.onloadprogress
* @param {Function} config.reader.onload
* @param {Function} config.reader.onabort
* @param {Function} config.reader.onerror
* @param {Function} config.reader.onloadend
*
* @param {Function} config.success This is optional.
* The callback to be called when the data has been successfully read.
*
* @param {Object} config.success.data
* The read data.
*
* @param {Function} config.failure This is optional.
* The callback to be called when an error occurred.
*
* @param {Object} config.failure.error
* The occurred error.
*
* @param {Object} config.scope
* The scope object
*/
read: function(config) {
var me = this;
this.getEntry(
{
success: function(fileEntry) {
fileEntry.file(
function(file) {
if (Ext.isNumber(config.length)) {
if (Ext.isFunction(file.slice)) {
file = file.slice(me.offset, config.length);
} else {
if (config.failure) {
config.failure.call(config.scope || me, {code: -2, message: "File missing slice functionality"});
}
return;
}
}
var reader = new FileReader();
reader.onloadend = function(evt) {
config.success.call(config.scope || me, evt.target.result);
};
reader.onerror = function(error) {
config.failure.call(config.scope || me, error);
};
if (config.reader) {
reader = Ext.applyIf(reader, config.reader);
}
config.encoding = config.encoding || "UTF8";
switch (config.type) {
default:
case "text":
reader.readAsText(file, config.encoding);
break;
case "dataURL":
reader.readAsDataURL(file);
break;
case "binaryString":
reader.readAsBinaryString(file);
break;
case "arrayBuffer":
reader.readAsArrayBuffer(file);
break;
}
},
function(error) {
config.failure.call(config.scope || me, error)
}
);
},
failure: function(error) {
config.failure.call(config.scope || me, error)
}
}
)
},
/**
* Writes the data to the file starting at the file offset.
*
* @param {Object} config
* The object which contains the following config options:
*
* @param {Object} config.data This is required.
* The data to write to the file.
*
* @param {Boolean} config.append This is optional.
* Append to the end of the file
*
* @param {Object} config.writer
* Optional config params to be applied to a File Reader
*
* @param {Function} config.writer.onwritestart
* @param {Function} config.writer.onprogress
* @param {Function} config.writer.onwrite
* @param {Function} config.writer.onabort
* @param {Function} config.writer.onerror
* @param {Function} config.writer.onwriteend
*
* @param {Function} config.success This is optional.
* The callback to be called when the data has been successfully written.
*
* @param {Function} config.failure This is optional.
* The callback to be called when an error occurred.
*
* @param {Object} config.failure.error
* The occurred error.
*
* @param {Object} config.scope
* The scope object
*/
write: function(config) {
if (config.data == null) {
Ext.Logger.error('Ext.device.filesystem.FileEntry#write: You must specify `data` to write into the file.');
return null;
}
var me = this;
this.getEntry(
{
options: config.options || {},
success: function(fileEntry) {
fileEntry.createWriter(
function(writer) {
writer.onwriteend = function(evt) {
me.length = evt.target.length;
config.success.call(config.scope || me, evt.result);
};
writer.onerror = function(error) {
config.failure.call(config.scope || me, error);
};
if (config.writer) {
writer = Ext.applyIf(writer, config.writer);
}
if (me.offset) {
writer.seek(me.offset);
} else if (config.append) {
writer.seek(me.length);
}
me.writeData (writer, config.data);
},
function(error) {
config.failure.call(config.scope || me, error)
}
)
},
failure: function(error) {
config.failure.call(config.scope || me, error)
}
}
)
},
writeData: function(writer, data) {
writer.write(new Blob([data]));
},
/**
* Truncates or extends the file to the specified size in bytes.
* If the file is extended, the added bytes are null bytes.
*
* @param {Object} config
* The object which contains the following config options:
*
* @param {Number} config.size This is required.
* The new file size.
*
* @param {Function} config.success This is optional.
* The callback to be called when the file size has been successfully changed.
*
* @param {Function} config.failure This is optional.
* The callback to be called when an error occurred.
*
* @param {Object} config.failure.error
* The occurred error.
*
* @param {Object} config.scope
* The scope object
*/
truncate: function(config) {
if (config.size == null) {
Ext.Logger.error('Ext.device.filesystem.FileEntry#write: You must specify a `size` of the file.');
return null;
}
var me = this;
//noinspection JSValidateTypes
this.getEntry(
{
success: function(fileEntry) {
fileEntry.createWriter(
function(writer) {
writer.truncate(config.size);
config.success.call(config.scope || me, me);
},
function(error) {
config.failure.call(config.scope || me, error)
}
)
},
failure: function(error) {
config.failure.call(config.scope || me, error)
}
}
)
}
});
});
});