mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-08-25 13:07:02 +00:00
536 lines
16 KiB
JavaScript
536 lines
16 KiB
JavaScript
/*
|
|
* file.js
|
|
* Objective-J
|
|
*
|
|
* Created by Francisco Tolmasky.
|
|
* Copyright 2008, 280 North, Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
// Notes: requests may return immediately (i.e. in IE when a file has been cached). Don't assume
|
|
// One line of code thus follows another, it may call the callback first!
|
|
|
|
// FIXME: If we ever have bundles that replace only some files, we will definitely run into problems.
|
|
// Look inside bundleResponseCallback.
|
|
|
|
|
|
#define DIRECTORY(aPath) (aPath).substr(0, (aPath).lastIndexOf('/') + 1)
|
|
|
|
OBJJFileNotFoundException = "OBJJFileNotFoundException";
|
|
OBJJExecutableNotFoundException = "OBJJExecutableNotFoundException";
|
|
|
|
var objj_files = { },
|
|
objj_bundles = { },
|
|
objj_bundlesForClass = { },
|
|
objj_searches = { };
|
|
|
|
var OBJJ_NO_FILE = {};
|
|
|
|
if (!window.OBJJ_INCLUDE_PATHS)
|
|
var OBJJ_INCLUDE_PATHS = ["Frameworks", "SomethingElse"];
|
|
|
|
var OBJJ_BASE_URI = "";
|
|
|
|
IF (OPERA)
|
|
|
|
var DOMBaseElement = document.getElementsByTagName("base")[0];
|
|
|
|
if (DOMBaseElement)
|
|
OBJJ_BASE_URI = DIRECTORY(DOMBaseElement.getAttribute('href'));
|
|
|
|
ENDIF
|
|
|
|
function objj_file()
|
|
{
|
|
this.path = NULL;
|
|
this.bundle = NULL;
|
|
|
|
this.included = NO;
|
|
|
|
this.contents = NULL;
|
|
this.fragments = NULL;
|
|
}
|
|
|
|
function objj_bundle()
|
|
{
|
|
this.path = NULL;
|
|
this.info = NULL;
|
|
|
|
this.__address = _objj_generateObjectHash();
|
|
}
|
|
|
|
function objj_getBundleWithPath(aPath)
|
|
{
|
|
return objj_bundles[aPath];
|
|
}
|
|
|
|
function objj_bundleForClass(aClass)
|
|
{
|
|
return objj_bundlesForClass[aClass.name];
|
|
}
|
|
|
|
function objj_addClassForBundle(aClass, aBundle)
|
|
{
|
|
objj_bundlesForClass[aClass.name] = aBundle;
|
|
}
|
|
|
|
function objj_request_file(aFilePath, shouldSearchLocally, aCallback)
|
|
{
|
|
new objj_search(aFilePath, shouldSearchLocally, aCallback).attemptNextSearchPath();
|
|
}
|
|
|
|
var objj_search = function(aFilePath, shouldSearchLocally, aCallback)
|
|
{
|
|
this.filePath = aFilePath;
|
|
|
|
this.bundle = NULL;
|
|
this.bundleObservers = [];
|
|
|
|
this.searchPath = NULL;
|
|
this.searchedPaths = [];
|
|
this.includePathsIndex = shouldSearchLocally ? -1 : 0;
|
|
|
|
this.searchRequest = NULL;
|
|
|
|
this.didCompleteCallback = aCallback;
|
|
}
|
|
|
|
objj_search.prototype.nextSearchPath = function()
|
|
{
|
|
// FIXME: Path searching should be more complex (go up directories, etc).
|
|
var path = objj_standardize_path((this.includePathsIndex == -1 ? "" : OBJJ_INCLUDE_PATHS[this.includePathsIndex] + '/') + this.filePath);
|
|
|
|
++this.includePathsIndex;
|
|
|
|
return path;
|
|
}
|
|
|
|
objj_search.prototype.attemptNextSearchPath = function()
|
|
{
|
|
var searchPath = this.nextSearchPath(),
|
|
file = objj_files[searchPath];
|
|
|
|
objj_alert("Will attempt to find " + this.filePath + " at " + searchPath);
|
|
|
|
// If a file for this search path already exists, then it has already been downloaded.
|
|
// If there is a callback, we can just call it, if not, we can simply return.
|
|
if (file)
|
|
{
|
|
objj_alert("The file request at " + this.filePath + " has already been downloaded at " + searchPath);
|
|
if (this.didCompleteCallback)
|
|
this.didCompleteCallback(file);
|
|
|
|
return;
|
|
}
|
|
|
|
var existingSearch = objj_searches[searchPath];
|
|
|
|
// If there is already an ongoing search for this search path, then we can let it find
|
|
// the file for us. Make sure to assign it our callback, if we have one, since only
|
|
// one search can have a callback at a time.
|
|
if (existingSearch)
|
|
{
|
|
if (this.didCompleteCallback)
|
|
existingSearch.didCompleteCallback = this.didCompleteCallback;
|
|
|
|
return;
|
|
}
|
|
|
|
// At this point we have a legitimate search, so we should take note of this search path
|
|
// in our search paths.
|
|
this.searchedPaths.push(this.searchPath = searchPath);
|
|
|
|
// Before we kick off our ajax requests, see whether this directory already has
|
|
// a bundle associated with it.
|
|
var infoPath = objj_standardize_path(DIRECTORY(searchPath) + "Info.plist")
|
|
bundle = objj_bundles[infoPath];
|
|
|
|
// If there is, then simply look for the file in question.
|
|
if (bundle)
|
|
{
|
|
this.bundle = bundle;
|
|
this.request(searchPath, this.didReceiveSearchResponse);
|
|
}
|
|
else
|
|
{
|
|
// If there isn't a bundle associated with this directory, then we also have to
|
|
// search for an Info.plist.
|
|
var existingBundleSearch = objj_searches[infoPath];
|
|
|
|
// Again, if a search already exists for this bundle, then add ourselves to the list
|
|
// of observers for when this bundle is either found or created.
|
|
if (existingBundleSearch)
|
|
{
|
|
--this.includePathsIndex;
|
|
this.searchedPaths.pop();
|
|
if (this.searchedPaths.length)
|
|
this.searchPath = this.searchedPaths[this.searchedPaths.length - 1];
|
|
else
|
|
this.searchPath = NULL;
|
|
|
|
existingBundleSearch.bundleObservers.push(this);
|
|
return;
|
|
}
|
|
// If not, then look for it.
|
|
else
|
|
{
|
|
this.bundleObservers.push(this);
|
|
this.request(infoPath, this.didReceiveBundleResponse);
|
|
|
|
// Requests may return immediately, so by this point, we may have already gotten the bundle and replaced the file.
|
|
if (!this.searchReplaced)
|
|
this.searchRequest = this.request(searchPath, this.didReceiveSearchResponse);
|
|
}
|
|
}
|
|
}
|
|
|
|
IF (ACTIVE_X)
|
|
|
|
objj_search.responseCallbackLock = NO;
|
|
objj_search.responseCallbackQueue = [];
|
|
|
|
objj_search.removeResponseCallbackForFilePath = function(aFilePath)
|
|
{
|
|
var queue = objj_search.responseCallbackQueue,
|
|
index = queue.length;
|
|
|
|
while (index--)
|
|
if (queue[index][3] == aFilePath)
|
|
{
|
|
queue.splice(index, 1);
|
|
return;
|
|
}
|
|
}
|
|
|
|
objj_search.serializeResponseCallback = function(aMethod, aSearch, aResponse, aFilePath)
|
|
{
|
|
var queue = objj_search.responseCallbackQueue;
|
|
|
|
queue.push([aMethod, aSearch, aResponse, aFilePath]);
|
|
|
|
if (objj_search.responseCallbackLock)
|
|
return;
|
|
objj_search.responseCallbackLock = YES;
|
|
|
|
while (queue.length)
|
|
{
|
|
var callback = queue[0];
|
|
queue.splice(0, 1);
|
|
|
|
callback[0].apply(callback[1], [callback[2]]);
|
|
}
|
|
|
|
objj_search.responseCallbackLock = NO;
|
|
}
|
|
|
|
ENDIF
|
|
|
|
objj_search.prototype.request = function(aFilePath, aMethod)
|
|
{
|
|
var search = this,
|
|
isPlist = aFilePath.substr(aFilePath.length - 6, 6) == ".plist",
|
|
request = objj_request_xmlhttp(),
|
|
response = objj_response_xmlhttp();
|
|
|
|
response.filePath = aFilePath;
|
|
|
|
request.onreadystatechange = function()
|
|
{
|
|
if (request.readyState == 4)
|
|
{
|
|
if (response.success = (request.status != 404 && request.responseText && request.responseText.length) ? YES : NO)
|
|
{
|
|
if (window.files_total)
|
|
{
|
|
if (!window.files_loaded)
|
|
window.files_loaded = 0;
|
|
|
|
window.files_loaded += request.responseText.length;
|
|
|
|
if (window.update_progress)
|
|
window.update_progress(window.files_loaded / window.files_total);
|
|
}
|
|
|
|
if (isPlist)
|
|
response.xml = objj_standardize_xml(request);
|
|
else
|
|
response.text = request.responseText;
|
|
}
|
|
|
|
if (ACTIVE_X)
|
|
objj_search.serializeResponseCallback(aMethod, search, response, aFilePath);
|
|
else
|
|
aMethod.apply(search, [response]);
|
|
}
|
|
}
|
|
|
|
objj_searches[aFilePath] = this;
|
|
|
|
if (request.overrideMimeType && isPlist)
|
|
request.overrideMimeType('text/xml');
|
|
|
|
if (OPERA && aFilePath.charAt(0) != '/')
|
|
aFilePath = OBJJ_BASE_URI + aFilePath;
|
|
|
|
try
|
|
{
|
|
request.open("GET", aFilePath, YES);
|
|
request.send("");
|
|
}
|
|
catch (anException)
|
|
{
|
|
response.success = NO;
|
|
|
|
if (ACTIVE_X)
|
|
objj_search.serializeResponseCallback(aMethod, search, response, aFilePath);
|
|
else
|
|
aMethod.apply(search, [response]);
|
|
}
|
|
|
|
return request;
|
|
}
|
|
|
|
objj_search.prototype.didReceiveSearchResponse = function(aResponse)
|
|
{
|
|
// If we do not have a bundle yet, we cannot appropriately
|
|
// handle this response, so wait.
|
|
if (!this.bundle)
|
|
{
|
|
this.cachedSearchResponse = aResponse;
|
|
return;
|
|
}
|
|
|
|
if (aResponse.success)
|
|
{
|
|
file = new objj_file();
|
|
|
|
file.path = aResponse.filePath;
|
|
file.bundle = this.bundle
|
|
file.contents = aResponse.text;
|
|
|
|
this.complete(file);
|
|
}
|
|
else if (this.includePathsIndex < OBJJ_INCLUDE_PATHS.length)
|
|
{
|
|
// Clear out the bundle since it will no longer be valid.
|
|
// FIXME: FIXME: FIXME: WARNING: Wouldn't it be cleaner to just always do this in attemptNextSearchPath?
|
|
this.bundle = NULL;
|
|
this.attemptNextSearchPath();
|
|
}
|
|
else
|
|
objj_exception_throw(new objj_exception(OBJJFileNotFoundException, "*** Could not locate file named \"" + this.filePath + "\" in search paths."));
|
|
}
|
|
|
|
objj_search.prototype.didReceiveBundleResponse = function(aResponse)
|
|
{
|
|
var bundle = new objj_bundle();
|
|
|
|
bundle.path = aResponse.filePath;
|
|
|
|
if (aResponse.success)
|
|
bundle.info = CPPropertyListCreateFromXMLData(aResponse.xml);
|
|
else
|
|
bundle.info = new objj_dictionary();
|
|
|
|
objj_bundles[aResponse.filePath] = bundle;
|
|
|
|
var executablePath = dictionary_getValue(bundle.info, "CPBundleExecutable");
|
|
|
|
if (executablePath)
|
|
{
|
|
this.request(DIRECTORY(aResponse.filePath) + executablePath, this.didReceiveExecutableResponse);
|
|
|
|
// FIXME: Is this the right approach?
|
|
// Request the compiled file regardless of whether our current inquiry
|
|
var directory = DIRECTORY(aResponse.filePath),
|
|
replacedFiles = dictionary_getValue(bundle.info, "CPBundleReplacedFiles"),
|
|
index = 0,
|
|
count = replacedFiles.length;
|
|
|
|
for (; index < count; ++index)
|
|
{
|
|
// Halt any forward searches of these files from taking place.
|
|
objj_searches[directory + replacedFiles[index]] = this;
|
|
|
|
if (directory + replacedFiles[index] == this.searchPath)
|
|
{
|
|
this.searchReplaced = YES;
|
|
|
|
if (!this.cachedSearchResponse && this.searchRequest)
|
|
this.searchRequest.abort();
|
|
|
|
if (ACTIVE_X)
|
|
objj_search.removeResponseCallbackForFilePath(this.searchPath);
|
|
}
|
|
}
|
|
}
|
|
this.bundle = bundle;
|
|
var observers = this.bundleObservers,
|
|
index = 0,
|
|
count = observers.length;
|
|
|
|
for(; index < count; ++index)
|
|
{
|
|
var observer = observers[index];
|
|
|
|
// Force the observer to just attempt the path all over again,
|
|
// since it may have been replaced by the executable.
|
|
// FIXME: we should be just reattempting the ones that collided.
|
|
if (observer != this)
|
|
observer.attemptNextSearchPath();
|
|
|
|
// If we have a cached response and the search has not been
|
|
// replaced by the executable, then let it proceed.
|
|
else if (this.cachedSearchResponse && !this.searchReplaced)
|
|
this.didReceiveSearchResponse(this.cachedSearchResponse);
|
|
}
|
|
|
|
this.bundleObservers = [];
|
|
}
|
|
|
|
objj_search.prototype.didReceiveExecutableResponse = function(aResponse)
|
|
{
|
|
if (!aResponse.success)
|
|
objj_exception_throw(new objj_exception(OBJJExecutableNotFoundException, "*** The specified executable could not be located at \"" + this.filePath + "\"."));
|
|
|
|
var files = objj_decompile(aResponse.text, this.bundle),
|
|
index = 0,
|
|
count = files.length,
|
|
length = this.filePath.length;
|
|
|
|
for (; index < count; ++index)
|
|
{
|
|
var file = files[index],
|
|
path = file.path;
|
|
|
|
if (this.filePath == path.substr(path.length - length))
|
|
this.complete(file);
|
|
else
|
|
objj_files[path] = file;
|
|
}
|
|
}
|
|
|
|
objj_search.prototype.complete = function(aFile)
|
|
{
|
|
var index = 0,
|
|
count = this.searchedPaths.length;
|
|
|
|
for (; index < count; ++index)
|
|
{
|
|
objj_files[this.searchedPaths[index]] = aFile;
|
|
//FIXME: uncomment this:
|
|
//delete objj_inquiries[anInquiry.searchedPaths[index]];
|
|
}
|
|
|
|
if (this.didCompleteCallback)
|
|
this.didCompleteCallback(aFile);
|
|
}
|
|
|
|
// objj_standardize_path
|
|
//
|
|
// Standardizes the input path by removing extrenenous components and resolving
|
|
// references to parent directories.
|
|
|
|
function objj_standardize_path(aPath)
|
|
{
|
|
if (aPath.indexOf("/./") != -1 && aPath.indexOf("//") != -1 && aPath.indexOf("/../") != -1)
|
|
return aPath;
|
|
|
|
var index = 0,
|
|
components = aPath.split('/');
|
|
|
|
for(;index < components.length; ++index)
|
|
if(components[index] == "..")
|
|
{
|
|
components.splice(index - 1, 2);
|
|
index -= 2;
|
|
}
|
|
else if(index != 0 && !components[index].length || components[index] == '.' || components[index] == "..")
|
|
components.splice(index--, 1);
|
|
|
|
return components.join('/');
|
|
}
|
|
|
|
IF (ACTIVE_X)
|
|
|
|
objj_standardize_xml = function(aRequest)
|
|
{
|
|
var XMLData = new ActiveXObject("Microsoft.XMLDOM");
|
|
XMLData.loadXML(aRequest.responseText.substr(aRequest.responseText.indexOf(".dtd\">") + 6));
|
|
|
|
return XMLData;
|
|
}
|
|
|
|
ELSE
|
|
|
|
objj_standardize_xml = function(aRequest)
|
|
{
|
|
return aRequest.responseXML;
|
|
}
|
|
|
|
ENDIF
|
|
|
|
function objj_response_xmlhttp()
|
|
{
|
|
return new Object;
|
|
}
|
|
|
|
// objj_request_xmlhttp()
|
|
//
|
|
// To be used as a browser-independent implementation of XMLHttpRequest.
|
|
// Currently known to support Safari, Firefox, IE 6/7, and Opera.
|
|
//
|
|
// Throws a primitive exception if XMLHttpRequests are not supported on
|
|
// the given browser.
|
|
|
|
IF (NATIVE_XMLHTTPREQUEST)
|
|
|
|
objj_request_xmlhttp = function()
|
|
{
|
|
return new XMLHttpRequest();
|
|
}
|
|
|
|
ELIF (ACTIVE_X)
|
|
|
|
// DON'T try 4.0 and 5.0. Should we be trying anything other than 3.0 and 6.0?
|
|
// http://blogs.msdn.com/xmlteam/archive/2006/10/23/using-the-right-version-of-msxml-in-internet-explorer.aspx
|
|
var MSXML_XMLHTTP_OBJECTS = [ "Microsoft.XMLHTTP", "Msxml2.XMLHTTP", "Msxml2.XMLHTTP.3.0", "Msxml2.XMLHTTP.6.0" ],//"Msxml2.XMLHTTP.4.0", "Msxml2.XMLHTTP.5.0" ],
|
|
index = MSXML_XMLHTTP_OBJECTS.length;
|
|
|
|
while (index--)
|
|
{
|
|
try
|
|
{
|
|
new ActiveXObject(MSXML_XMLHTTP_OBJECTS[index]);
|
|
break;
|
|
}
|
|
catch (anException)
|
|
{
|
|
}
|
|
}
|
|
|
|
var MSXML_XMLHTTP = MSXML_XMLHTTP_OBJECTS[index];
|
|
|
|
delete index;
|
|
delete MSXML_XMLHTTP_OBJECTS;
|
|
|
|
objj_request_xmlhttp = function()
|
|
{
|
|
return new ActiveXObject(MSXML_XMLHTTP);
|
|
}
|
|
|
|
ENDIF
|