/************************


	file.js		Helpful functions packaged into the File namespace
	
	Copyright (c) 2003-2007 Rich Knopman
// NOTE: this file requires core.js for directory functions.

Parameters
==========
 File.currentDir		the current (default) directory for all file functions
 
 public methods (see the method definition for explaination)
==============
 File.save(path, content, silence)
 File.webSave(path, content, silence)
 File.loadURL(url, silence)
 File.dir(path)
 File.exists(path)
 File.getFileName(path)
 File. getDirName(path)
 
***********/

File = {};

File.mergeOnLoad = 0; // do not merge new files into existing one
File.SILENT = 120; // flag for no confirmation on overwrite.
File.DIR_EXT = "&lt;dir&gt;"; // 'extension' to show for subdirectories (in File.dir())
File.CURRENT_DIR_EXT = "&lt;Select dir&gt;"; // 'extension' to show for subdirectories (in File.dir())
File.UP_DIR_LABEL = "&lt; Parent Folder &gt;"; // label instead of ".."
//*** not used anymore: File.DEFAULT_PATH = ".";
File.saveBackup = 1; // flag to backup the original file.
//  convenience if we need netscape priviledge manager
File.NSPM = window.netscape && netscape.security && netscape.security.PrivilegeManager;
//$$ the following two lines should go in a separate config file
//File.SHEETS_PATH = "sheets"; // sheets local directory. used in File.getDirName
File.webSaveScript = "http://richk.net/cgi-bin/HttpUpload.pl";


File.init = function() {
	// public variables	
	// remove filename and any extra_path
	File.BASE_PATH = document.location.href.replace(/[\?\#].+/,"").replace(/[\\\/][^\/\\]+$/,"");
	// set file separator. 
	var path = File.getLocalPath(File.BASE_PATH);
	File.separator = path.match(/^\w\:/) ? "\\" : path.match(/([\/\\])/) ? RegExp.$1 : "\\";
	File.currentDir = File.BASE_PATH +"/";// + File.SHEETS_PATH;
}
	

//==================================

// TOPIC: File
//DESCR: The following public methods are platform agnostic, but then call platform-specific functions.  These methods currently work for Mozilla and Internet Explorer.
// SUBJ: Save content to fileName.  Optional silence supresses error warnings.
File.save = function(content, path, silence) {
	if (path.match(/^http:\/\//)) { // web-based file save
		return File.webSave(content, path, silence);
	}
	path = File.getLocalPath(path); // make sure converted to path on local machine
	if (File.exists(path)) {  // make backup
		var d = new Date();
		//$$ would like to find how to copy files in both ie and Moz.
		//$$ File.copy(path, path.replace(/(\.[^.]+)$/, toDate(d, ".yyyymmddhhnnss$1")))
	}
	var result = File.ie.save(content, path, silence);
	if (result == null) { result = File.mozilla.save(content, path, silence); }
	return result;
}

// TOPIC: File
// SUBJ: Save content to fileName within web directory dir.  This runs in coordination with an _httpupload.py_ script on the server (url defined in ^File.webSaveScript^).
// note: we would prefer to do this asynchronously, but that would not allow us to have a simple File.webSave() function.
File.webSave = function(content, fileName, silence) {
	// this is the POST data string-- just file and content
	// using old escape() function doesn't work!!! encodes "+"s as " "
	var webData = "file="+fileName + "&content=" + encodeURIComponent(content);
	try {
		return File.webPost(File.webSaveScript, webData, silence);
	} catch(_e) {
		// allow to fail noisily or silently
		if (!silence) return("File.webSave ("+fileName+") Problem: "+_e.message);
		return null;
	}
}

// this is just a straigthforward execution of HTTP POST service.
// TOPIC: File
// SUBJ: Post content to url.  This uses HTTP post capability.
//$$ need to include async capability
File.webPost = function(url, content, silence) {
	if (window.ActiveXObject) { xmlhttp=new ActiveXObject("Microsoft.XMLHTTP") }
	else if (window.XMLHttpRequest) { xmlhttp=new XMLHttpRequest() }
	
	xmlhttp.open("POST",url ,false);
	//Send the proper header information along with the request.  This appears to be needed for mozilla.
	xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
	xmlhttp.setRequestHeader("Content-length", content.length);
	xmlhttp.setRequestHeader("Connection", "close");
	xmlhttp.send(content);

	// check response from server
	if (xmlhttp.status == 200) {
		return xmlhttp.responseText;
	} else {
	//$$NEW-- used to be alert(...)
		throw {type:"XMLHttpSend", text:"There was a problem sending the data:\n" +
		xmlhttp.statusText };
	}
}

// TOPIC: File
// SUBJ: Load contents of file specified in url.  url can be a local file or from over the web.  url can be absolute or relative.
File.loadURL = function(url) {
  var xmlhttp = File.getURL(url);
	return xmlhttp.responseText;
}

File.loadXML = function(url) {
  var xmlhttp = File.getURL(url);
	// two ways to retrieve XML from file: direct and old MS method
	if (xmlhttp.responseXML.childNodes.length > 0) { // need another way to get xml
		return xmlhttp.responseXML;
	} else { // old MS method
		xmldoc = new ActiveXObject("Msxml2.DOMDocument");
		xmldoc.loadXML(xmlhttp.responseText);
		return xmldoc;
	}
}

// return xmlhttp object representing url
File.getURL = function(url) {
	var xmlhttp;
	
	// turn windows absolute path into URL.  
	//$$ not sure this is robust across platforms
	//$$$$$$$$$ Need to make this work for local mac files?
	if (url.match(/^[A-Za-z]:/i)) url = "file:\/\/\/"+url;

	// code for Mozilla, ie., etc.  IE7 has bugs with XMLHttpRequest-- so use XMLHTTP
	if (window.ActiveXObject) { xmlhttp=new ActiveXObject("Microsoft.XMLHTTP") }
	else if (window.XMLHttpRequest) { xmlhttp=new XMLHttpRequest() }

	// go for permissions-- mozilla only, and only for non-local files
	if (window.netscape && url.match(/tp:\/\//) && (!document.domain || (url.slice(7, document.domain.length + 7) != document.domain) && File.NSPM)) {
		try {
	//		netscape.security.PrivilegeManager.enablePrivilege( "UniversalXPConnect UniversalBrowserRead"); 
	netscape.security.PrivilegeManager.enablePrivilege( "UniversalXPConnect");
	netscape.security.PrivilegeManager.enablePrivilege( "UniversalBrowserRead");
		} catch(_e) {
			alert("Cross-site requests prohibited. You will only be able to read from the origin site: " + _e);
			return;
		}
	}

	// let caller catch exceptions
	xmlhttp.open("GET",url,false);
	xmlhttp.send(null);
	return xmlhttp;
}

// TOPIC: File
// SUBJ: Rename oldName to newName
File.rename = function(oldName, newName) {
	try {
		netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
		var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
	} catch (_e) {
		return null;
	}
	try {file.initWithPath(path) }
	catch(_e) { alert("File.mozilla.dir (path='"+path+"'):"+_e.message) };

	if (!file.isDirectory()) {
	}
}

// TOPIC: File
// SUBJ: Place information about the directory specified in path into an array of objects.  path may be local or web (this method uses File.webDir or File.localDir).
File.dir = function(path) {
	if (path.match(/^https?:\/\//i)) return File.webDir(path);
	else return File.localDir(path);
}

// TOPIC: File
// SUBJ: Return directory based on web server's index page (uses screen scraping)
File.webDir = function(path) {
	path = path.replace(/[\/\\]$/,"/"); // ensure trailing '/'
	var result = [];
	var dirContent = File.loadURL(path);
	var lines = dirContent.split(/[\n\r]+/);
	var res;
	var li;
	for (var i=0; i<lines.length; i++) {
		res = {};
		li = lines[i];
		if (!li.match(/<a .*?href\s*=\s*"(.+?)"/i)) continue;
		res.name = unescape(RegExp.$1);
		if (li.match(/parent\s*directory/i)) continue; // ignore up dir
		if (res.name.match(/[\/\\]$/)) { res.extension = File.DIR_EXT; }//this romoves trailing subdir slash: res.name = res.name.replace(/[\/\\]$/,"")}// trailing slash means subdirectory
		else { res.extension = res.name.replace(/.+\./,""); }

		// looking for numeric date with possible month name, followed by time
		if (!li.match(/\s([\w-]{6,15}\s+\d{1,2}:\d\d)\s\s/i)
			) continue;// didn't find file date
		// make sure remove chars that new Date(...) doesn't understand
		res.date = prepDate((RegExp.$1).replace(/[^\w]+/," "));
		if (li.match(/\s\s([\d\.]+[kM]?)\s\s/i))	res.size = prepEngu(RegExp.$1,2);
		if (res.name && res.date) result.push(res);
	}
	if (result.length == 0) throw {type:"CantReadWebDirectory", message:"Does not have HTML index or empty."};
	return result;
}

// TOPIC: File
// SUBJ: Place information about the directory specified in path into an array of objects.  Each object in this array has file's date (last modified date, in Javascript internal format), size (in bytes), name, and extension.  Directories are also captured
File.localDir = function(path) {
	path = File.getLocalPath(path); // this function operates on local file path, so make sure correct
	var result = File.ie.dir(path);
	if (result == null) result = File.mozilla.dir(path);
	return result;
}

// TOPIC: File
// SUBJ: Return names of subdirectories of the directory specified in path.  Reuse any existing currentFileObject (mozilla: nsILocalFile or ie: FileSystemObject) file passed in
File.getSubdirs = function(path, currentFileObject) {
	path = File.getLocalPath(path); // this function operates on local file path, so make sure correct
	var result = File.ie.getSubdirs(path, currentFileObject);
	if (result == null) result = File.mozilla.getSubdirs(path);
	return result;
}

// TOPIC: File
// SUBJ: Return true if there is a file or directory specified at path.
File.exists = function(path) {
	path = File.getLocalPath(path); // this function operates on local file path, so make sure correct
	var result = File.ie.exists(path);
	if (result == null) result = File.mozilla.exists(path);
	return result;
}



//============================= IE-specific methods ==============================
File.ie = {};

// constants for reference
File.ie.forWriting = 2;
File.ie.forReading = 1;

//Let caller catch any errors
//File.ie.save = function(content, fileName, dir, silence) {
File.ie.save = function(content, path, silence) {
	// ignore if no path or is directory
	if (!path || path.match(/[\/\\]$/)) return;
	var fso, file;
	var result; // message: success or failure
	
	// try and make ie-style FSO
	try { fso = new ActiveXObject("Scripting.FileSystemObject") }
	catch(_e) { return null; }
	try {
		if (fso.FileExists(path) && !silence) {
			if (!confirm(path+" already exists.  overwrite?")) return;
		}
		file = fso.OpenTextFile(path, File.ie.forWriting, true); // -1 at end forces unicode which simplifies things but it also doubles file size
		// ie. doesn't like unicode in ascii files, so we filter them out.  Do it in chunks since this is slow for large strings
		var chunkSize = 1000;
		for (var ptr=0; ptr< content.length;ptr+=chunkSize) {
			file.Write(File.fixUnicode(content.slice(ptr,ptr+chunkSize)));
		}
		file.Close();
		result = "saved.";
	} catch(_e) {
		 result = "File.ie.save (path='"+path+"'):"+_e.message;
	}
	return result;
}

// Replace or remove unicode chars-- ie vomits with unicode into an ASCII file.
// Warning: this slows for very large strings (> 100k chars)
File.fixUnicode = function(s) {
	for (var c,i=0, res=[]; i<s.toString().length; i++) {
		c = s.charCodeAt(i);
		res += c < 128 ? s.charAt(i) : c==8211 ? "-" : c==183 ? "&middot;" : c== 176 ? "&deg;" : c==8220 || c==8221 ? '"' :"";
	}
	return res;
}
// need to make sure parameters are complete path names
File.ie.rename = function(oldPath, newPath) {
	// try and make ie-style FSO
	try { fso = new ActiveXObject("Scripting.FileSystemObject") }
	catch(_e) { return null; }

	fso.MoveFile(oldPath, newPath);
}
// place file directory info into an object structure
// that can be assimilated into a Table
// uses prepDate() from core.js
File.ie.dir = function(path) {
	// try and make ie-style FSO
	try { fso = new ActiveXObject("Scripting.FileSystemObject") }
	catch(_e) { return null; }

//** FIND a different default:	if (!path) path = File.DEFAULT_PATH;

	var f; // temp storage for file objects
	var item;
	var folder = fso.GetFolder(path);
	var result = File.getSubdirs(path, folder); // first, get subdirectories
	path += "/"; // allow filename to follow path

	// and then, regular files
	var extension;	
	for (var fc = new Enumerator(folder.Files); !fc.atEnd(); fc.moveNext()) {
		f = fc.item();
		extension = f.name.replace(/.+\.([\w$~]+)/, "$1"); // get file extension
		item = {date: prepDate(f.DateLastModified), size:f.size, name: f.name, extension: extension};
		result.push(item);
	}
	return result;
}

File.ie.exists = function(path) {
	try {
		fso = new ActiveXObject("Scripting.FileSystemObject");
	} catch(_e) { return null; }
	return fso.FileExists(path);
}

File.ie.getSubdirs = function(path, fsoFolder) {
	if (!fsoFolder) {
		try {
			fso = new ActiveXObject("Scripting.FileSystemObject");
		} catch(_e) { return null; }
		fsoFolder = fso.GetFolder(path);
		setStatus("Folder '"+path+"' not found.");
	}
	path += "/"; // allow filename to follow path

	var result = [];
	var f, item;
	// first, get directories
	for (var fc = new Enumerator(fsoFolder.SubFolders); !fc.atEnd(); fc.moveNext()) {
		f = fc.item();
		item = {date: prepDate(f.DateLastModified), extension:File.DIR_EXT, size:0, name:f.path.replace(/.+\\/,"")+"/"}; // remove rest of path, add "/"
		result.push(item);
	}
	return result;
}


//==================== Mozilla-specific implementations =================================

File.mozilla = {};
/*
//$$ Note: i tried to separate the priviledge manager, file object creation, and initWithPath into a separate function
but trying to use the instantiated var file causes 
an error, e.g.:"Error: uncaught exception: Permission denied to call method UnnamedClass.isDirectory"
*/
File.mozilla.dir = function(path) {
	
	var result = [];

	try {
		netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
		var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
	} catch (_e) {
		return null;
	}
	try {file.initWithPath(path) }
	catch(_e) { alert("File.mozilla.dir (path='"+path+"'):"+_e.message) };

	if (!file.isDirectory()) {
		alert("File.mozilla.dir (path='"+file.path+") does not seem to be a directory.");
		return;
	}

	var els = file.directoryEntries;
	var f;
	var name; // file name
	while (els.hasMoreElements()) {
		f = els.getNext();
		f.QueryInterface(Components.interfaces.nsIFile);
		name = f.path.replace(/^.+[\\\/]/,""); // cut off everything before last "\" (win) or "/" (unix)
		if (f.isDirectory()) {
			extension = File.DIR_EXT;
			name += "/"; // show trailing slash
		} else {
			extension = name.replace(/.+\.([\w$~]+)/, "$1"); // get file extension
		}
		item = {date:prepDate(f.lastModifiedTime), size:f.fileSize, name: name, extension: extension};
		result.push(item);
	} 
	return result;
}

File.mozilla.getSubdirs = function(path) {
	
	var result = [];
	try {
		netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
		var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
	} catch (_e) {
		return null;
	}
	try {file.initWithPath(path) }
	catch(_e) { alert("File.mozilla.dir (path='"+path+"'):"+_e.message) };

	if (!file.isDirectory()) {
		alert("File.mozilla.dir (path='"+file.path+") does not seem to be a directory.");
		return;
	}
	
	var els = file.directoryEntries;
	var f;
	var item;
	while (els.hasMoreElements()) {
		f = els.getNext();
		f.QueryInterface(Components.interfaces.nsIFile);
		if (!f.isDirectory()) continue;
		item = {date: prepDate(f.lastModifiedTime), extension:File.DIR_EXT, size:0, name:f.path.replace(/.+[\/\\]/,"")};
	result.push(item);
	}
	return result;
}


// Returns null if it can't do it, false if there's an error, true if it saved OK
File.mozilla.save = function(content, path, silence) {
	if(!window.Components) return null;
	try {
		netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
		var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
	} catch(_e) {return null;}

	try {
		file.initWithPath(path);
		if (!file.exists()) file.create(0, 0664);
		var out = Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream);
		out.init(file, 0x20 | 0x02, 00004,null);
		out.write(content, content.length);
		out.flush();
		out.close();
		result = "saved."; // success
	} catch(_e) {
		 result = "File.mozilla.save (path='"+path+"'):"+_e.message;
	}
	return result;
}

// Returns null if it can't do it, false if there's an error, true if it saved OK
File.mozilla.exists = function(path) {
	if (!path) return;
	if(!window.Components) return null;
	try {
		netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
		var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
	} catch(_e) {alert("Problem with Mozilla Security manager"+_e.message);return null;}

	try { // Moz seems to throw errors if doesn't exist
		file.initWithPath(path);
		return file.exists(); // let caller catch any errors
	} catch(_e) {
		return null; // doesn't exist
	}
}

//=================================================
// convert file:/// url into a local file path
// from http://tiddlywiki.com    Thank you to Jeremy Ruston and the TiddlyWiki contributors
// added the last test- if not local file, leave URL alone.
File.getLocalPath = function(originalPath)
{
	// Remove any location or query part of the URL
	var argPos = originalPath.indexOf("?");
	if(argPos != -1)
		originalPath = originalPath.substr(0,argPos);
	var hashPos = originalPath.indexOf("#");
	if(hashPos != -1)
		originalPath = originalPath.substr(0,hashPos);
	// Convert file://localhost/ to file:///
	if(originalPath.indexOf("file://localhost/") == 0)
		originalPath = "file://" + originalPath.substr(16);
	// Convert to a native file format assuming
	// "file:///x:/path/path/path..." - pc local file --> "x:\path\path\path..."
	// "file://///server/share/path/path/path..." - FireFox pc network file --> "\\server\share\path\path\path..."
	// "file:///path/path/path..." - mac/unix local file --> "/path/path/path..."
	// "file://server/share/path/path/path..." - pc network file --> "\\server\share\path\path\path..."
	var localPath;
	if(originalPath.charAt(9) == ":") // pc local file
		localPath = unescape(originalPath.substr(8)).replace(new RegExp("/","g"),"\\");
	else if(originalPath.indexOf("file:\/\/\/\/\/") == 0) // FireFox pc network file
		localPath = "\\\\" + unescape(originalPath.substr(10)).replace(new RegExp("/","g"),"\\");
	else if(originalPath.indexOf("file:\/\/\/") == 0) // mac/unix local file
		localPath = unescape(originalPath.substr(7));
	else if(originalPath.indexOf("file:\/\/") == 0) // pc network file
		localPath = "\\\\" + unescape(originalPath.substr(7)).replace(new RegExp("/","g"),"\\");
	else if(originalPath.indexOf("file:/") == 0) // mac/unix local file
		localPath = unescape(originalPath.substr(5));
	else if(originalPath.indexOf(":\\") == 1) // PC local file
		localPath = originalPath;
	else if(originalPath.indexOf("tp:\/\/") >0 
		|| originalPath.indexOf("\/") ==0 ) // if s not local file or is unix abs path; leave it alone.
		localPath = originalPath;
	return localPath;
}

//TOPIC: File
//SUBJ: return filename portion of path.  also look for portion of path prior to "?" in URL
File.getFileName = function(path) {
	if (path.toString().match(/([^\\\/]+)$/)) { return RegExp.$1; }
	else {return path; }
}

//TOPIC: File
//SUBJ: return directory portion of path, including trailing '/' or '\'
File.getDirName = function(path) {
  path = path || "";
	// looking to match everything up to last separator and up to ? or #
	if (path.toString().match(/(^.+[\\\/]).*[\?#]/)
	|| path.toString().match(/(^.+)[\\\/]/)) { return RegExp.$1; }
	else {
	// get path from http location, remove filename and ?... 
	// only return path if there's a directory implied (i.e., chars before and after '/'
	path = window.location.href.match(/[^\/\\][\/\\][^\/\\]/) 
		? window.location.href.replace(/[\/\\][^\/\\]+(?:\?.+)?$/,"")//+File.SHEETS_PATH
		: "";
	}
	return path;
}

File.upPathRegex = /[^\/\\]+[\/\\]\.\.[\/\\]/g;
// resolve "../"parent dir references.  Also resolve absolute vs. relative paths (prepend File.currentDir to relative paths)
File.makePath = function(p) {
	p = p.toString();
	while (p.match(File.upPathRegex)) p = p.replace(File.upPathRegex,"");
	// if absolute paths (root path, ms drive letter, protocol over tcpip), finished
	if (p.match(/^[\/\\]/) || p.match(/^\w:\\/) || p.match(/^\w{2,8}:\/\//)) return p;
//	var cd = File.currentDir;
//	if (cd.match(/^[\/\\]/) || cd.match(/^\w:\\/) || cd.match(/^\w{2,8}:\/\//)) return cd+p; // relative path: add currentDir
//	return File.BASE_PATH+File.separator+cd+p;
	return File.BASE_PATH+ "/" + p;
}

File.getRelativePath= function(s) {
	return s.replace(new RegExp(File.BASE_PATH+"[\/\\\\]?"),"");
}

File.init();
