I am writing a file writer/reader in JavaScript; I want it to work on local files. It tries to use Firefox's XpConnect, falls back on IE activeX, falls back on Java LiveConnect, falls back on a Java class (not included here), falls back on HTML5 local filestorage. I also want to throw in there a GET driver, to communicate with PHP/whatever and be able to use the same API for a web server file browser.
I have never developed in JS and I am having trouble with lexical scoping and inheritance. The code I am presenting is very far from complete, but I am presenting it now because it is not too complex yet, to see if I am on the right track.
I am not asking for a detailed review (yet), I just want to know if I am heading in the right direction before advancing too much.
In order to help read the code, here is the loop:
You create a new jsio
object. Then you can use jsio.save(fileName,content)
or jsio.load(fileName)
. You can specify a driver through jsio.driver(driverInstance)
OR let it detect the driver itself (following the fallback loop described above).
the TestCapabilities()
is there to help determine the right driver.
(function(context){
var javaAppletName = 'myIOJavaApplet';
var testCapabilities = {
xpConnect: function(){
if(window.Components){
try {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
return true;
}catch(err){}
}
return false;
},
activeX: function(){
try {
new ActiveXObject("Scripting.FileSystemObject");
return true;
} catch(err) {}
return false;
},
java: function(){
if(document.applets[javaAppletName] || java.io){return true;}
return false;
},
local:function(){
if(window.requestFileSystem || window.webkitRequestFileSystem){return true;};
return false;
}
}
var _driverBase = function(){}
_driverBase.prototype = {
save : function(url){},
load: function(url){},
listDir:function(url){}
}
var drivers={
xpConnect : function(){
this.test = function(){return testCapabilities.xpConnect();}
this.save = function(filePath,content,success,error){
var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
file.initWithPath(filePath);
if(!file.exists())
file.create(0,0x01B4);// 0x01B4 = 0664
var out = Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream);
out.init(file,0x22,0x04,null);
out.write(content,content.length);
out.flush();
out.close();
return true;
}
this.load = function(filePath,success,error){
var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
file.initWithPath(filePath);
if(!file.exists()){error();}
var inputStream = Components.classes["@mozilla.org/network/file-input-stream;1"].createInstance(Components.interfaces.nsIFileInputStream);
inputStream.init(file,0x01,0x04,null);
var sInputStream = Components.classes["@mozilla.org/scriptableinputstream;1"].createInstance(Components.interfaces.nsIScriptableInputStream);
sInputStream.init(inputStream);
var contents = sInputStream.read(sInputStream.available());
sInputStream.close();
inputStream.close();
return contents;
}
},
activeX: function(){
var _self = this;
var fso = new ActiveXObject("Scripting.FileSystemObject");
this.test = function(){return testCapabilities.activeX();}
this.createPathRecursive = function(path){
//# Remove the filename, if present. Use trailing slash (i.e. "foo\bar\") if no filename.
var pos = path.lastIndexOf("\\");
if(pos==-1){pos = path.lastIndexOf("/");}
if(pos!=-1){path = path.substring(0,pos+1);}
//# Walk up the path until we find a folder that exists
var scan = [path];
var parent = fso.GetParentFolderName(path);
while(parent && !fso.FolderExists(parent)) {
scan.push(parent);
parent = fso.GetParentFolderName(parent);
}
//# Walk back down the path, creating folders
for(i=scan.length-1;i>=0;i--) {
if(!fso.FolderExists(scan[i])) {
fso.CreateFolder(scan[i]);
}
}
return true;
}
this.save = function(filePath,content,success,error){
_self.createPathRecursive(filePath);
var file = fso.OpenTextFile(filePath,2,-1,0);
file.Write(content);
file.Close();
}
this.load = function(filePath,success,error){
var file = fso.OpenTextFile(filePath,1);
var content = file.ReadAll();
file.Close();
return content;
}
this.copy = function(dest,source){
_self.createPathRecursive(dest);
fso.GetFile(source).Copy(dest);
}
},
java: function(){
var applet;
if(document.applets[javaAppletName]){applet = document.applets[javaAppletName];}
this.test = function(){return testCapabilities.java();}
function javaUrlToFilename(url){
var f = "//localhost";
if(url.indexOf(f) == 0)
return url.substring(f.length);
var i = url.indexOf(":");
return i > 0 ? url.substring(i-1) : url;
}
this.save = function(filePath,content,success,error){
if(applet){
return applet.saveFile(javaUrlToFilename(filePath),"UTF-8",content);
}else{
try{
var s = new java.io.PrintStream(new java.io.FileOutputStream(javaUrlToFilename(filePath)));
s.print(content);
s.close();
return true;
}catch(e){}
}
return false;
}
this.load = function(filePath,success,error){
if(applet){
var ret = applet.loadFile(javaUrlToFilename(filePath),"UTF-8");
if(!ret){return null;}
return String(ret);
}else{
try{
var content = [];
var r = new java.io.BufferedReader(new java.io.FileReader(javaUrlToFilename(filePath)));
var line;
while((line = r.readLine()) != null){content.push(String(line));}
r.close();
}catch(e){}
}
return false;
}
},
local:function(){
var _self = this;
window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
function errorHandler(e) {
var msg = '';
switch (e.code) {
case FileError.QUOTA_EXCEEDED_ERR:
msg = 'QUOTA_EXCEEDED_ERR';
break;
case FileError.NOT_FOUND_ERR:
msg = 'NOT_FOUND_ERR';
break;
case FileError.SECURITY_ERR:
msg = 'SECURITY_ERR';
break;
case FileError.INVALID_MODIFICATION_ERR:
msg = 'INVALID_MODIFICATION_ERR';
break;
case FileError.INVALID_STATE_ERR:
msg = 'INVALID_STATE_ERR';
break;
default:
msg = 'Unknown Error';
break;
};
console.log('Error: ' + msg);
}
this.test = function(){return testCapabilities.local();}
this.save = function(filePath,content,success,error){
alert('cannot use local storage');
}
this.load = function(filePath,success,error){
alert('cannot use local storage');
}
window.webkitStorageInfo.requestQuota(PERSISTENT, 1024*1024,
function(grantedBytes) {
window.requestFileSystem(PERSISTENT, grantedBytes,
function(fs){
_self.load = function(filePath,success,error){
fs.root.getFile(filePath, {create: true, exclusive: true},
function(fileEntry) {
fileEntry.file(
function(file) {
var reader = new FileReader();
reader.onloadend = success;
reader.readAsText(file);
},
error
);
},
errorHandler
);
};
_self.save = function(filePath,content,success,error){
fs.root.getFile('log.txt', {create: true},
function(fileEntry) {
fileEntry.createWriter(
function(fileWriter) {
fileWriter.onwriteend = success;
fileWriter.onerror = error;
var bb = new BlobBuilder() || new WebKitBlobBuilder();
bb.append(content);
fileWriter.write(bb.getBlob('text/plain'));
},
error
);
},
error
);
}
},
errorHandler
);
},
errorHandler
);
}
}
drivers.activeX.prototype = _driverBase.prototype;
drivers.activeX.constructor = drivers.activeX;
drivers.java.prototype = _driverBase.prototype;
drivers.java.constructor = drivers.java;
drivers.local.prototype = _driverBase.prototype;
drivers.local.constuctor = drivers.local;
drivers.xpConnect.prototype = _driverBase.prototype;
drivers.xpConnect.constructor = drivers.xpConnect;
_fileObject.prototype = _fileBase.prototype;
_fileObject.constructor = _fileObject;
_dirObject.prototype = _dirBase.prototype;
_dirObject.constructor = _dirObject;
function _jsio(){
this.config = {
userAgent : navigator.userAgent.toLowerCase(),
browser:'',
driverInterface:{
save: function(){},
load: function(){}
},
driver:''
};
var _self = this;
this.config.browser = this.detectBrowser(this.config.userAgent);
}
_jsio.prototype = {
detectBrowser: function(userAgent){
return {
isIE: userAgent.indexOf("msie") != -1 && userAgent.indexOf("opera") == -1,
isGecko: navigator.product == "Gecko" && userAgent.indexOf("WebKit") == -1,
ieVersion: /MSIE (\d.\d)/i.exec(userAgent), // config.browser.ieVersion[1], if it exists, will be the IE version string, eg "6.0"
isSafari: userAgent.indexOf("applewebkit") != -1,
isBadSafari: !((new RegExp("[\u0150\u0170]","g")).test("\u0150")),
firefoxDate: /gecko\/(\d{8})/i.exec(userAgent), // config.browser.firefoxDate[1], if it exists, will be Firefox release date as "YYYYMMDD"
isOpera: userAgent.indexOf("opera") != -1,
isChrome: userAgent.indexOf('chrome') > -1,
isLinux: userAgent.indexOf("linux") != -1,
isUnix: userAgent.indexOf("x11") != -1,
isMac: userAgent.indexOf("mac") != -1,
isWindows: userAgent.indexOf("win") != -1
}
},
detectDriver: function(){
var _driverName = '';
if(testCapabilities.activeX()){
return drivers.activeX();
}else if(testCapabilities.xpConnect()){
return drivers.xpConnect();
}else if(testCapabilities.java()){
return drivers.java();
}else if(testCapabilities.local()){
return drivers.local();
}
return null;
},
checkInterface: function(theObject, theInterface) {
for (var member in theInterface) {
if ( (typeof theObject[member] != typeof theInterface[member]) ) {
alert("object failed to implement interface member " + member);
return false;
}
}
return true;
},
driver: function(driver){
if(arguments.length){
if(this.checkInterface(driver, this.config.driverInterface)){
this.config.driver = driver;
}else{
console.log('wrong driver');
}
return this;
}
if(!this.config.driver){
var _driver = this.detectDriver();
if(_driver && this.checkInterface(_driver, this.config.driverInterface)){
this.config.driver = _driver;
}else{
console.log('could not determin a suitable driver');
return false;
}
}
return this.config.driver;
},
save: function(url,content,success){
this.driver().save(url,content,function(){},this.error);
},
load:function(url,success){
this.driver().load(url,success,this.error);
},
error:function(e){
console.log(e);
}
}
window.jsio = function(){return new _jsio();}
})();