1 /* 2 * Loader.js - shared loader implementation 3 * 4 * Copyright © 2015, 2018-2019, 2021 JEDLSoft 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * 16 * See the License for the specific language governing permissions and 17 * limitations under the License. 18 */ 19 20 var Path = require("./Path.js"); 21 var ilib = require("./ilib.js"); 22 var JSUtils = require("./JSUtils.js"); 23 24 /** 25 * @class 26 * Superclass of the loader classes that contains shared functionality. 27 * 28 * @private 29 * @constructor 30 */ 31 var Loader = function() { 32 // console.log("new Loader instance"); 33 34 this.protocol = "file://"; 35 this.includePath = []; 36 this.addPaths = []; 37 38 this.jsutils =JSUtils; 39 }; 40 41 Loader.prototype = new ilib.Loader(); 42 Loader.prototype.parent = ilib.Loader; 43 Loader.prototype.constructor = Loader; 44 45 Loader.prototype._loadFile = function (pathname, sync, cb) {}; 46 47 Loader.prototype._exists = function(dir, file) { 48 var fullpath = Path.normalize(Path.join(dir, file)); 49 if (this.protocol !== "http://") { 50 var text = this._loadFile(fullpath, true); 51 if (text && this.includePath.indexOf(text) === -1) { 52 this.includePath.push(dir); 53 } 54 } else { 55 // put the dir on the list now assuming it exists, and check for its availability 56 // later so we can avoid the 404 errors eventually 57 if (this.includePath.indexOf(dir) === -1) { 58 this.includePath.push(dir); 59 this._loadFile(fullpath, false, ilib.bind(this, function(text) { 60 if (!text) { 61 //console.log("Loader._exists: removing " + dir + " from the include path because it doesn't exist."); 62 this.includePath = this.includePath.slice(-1); 63 } 64 })); 65 } 66 } 67 }; 68 69 Loader.prototype._loadFileAlongIncludePath = function(includePath, pathname) { 70 var textMerge={}; 71 for (var i = 0; i < includePath.length; i++) { 72 var manifest = this.manifest[includePath[i]]; 73 if (!manifest || Loader.indexOf(manifest, pathname) > -1) { 74 var filepath = Path.join(includePath[i], pathname); 75 //console.log("Loader._loadFileAlongIncludePath: attempting sync load " + filepath); 76 var text = this._loadFile(filepath, true); 77 78 if (text) { 79 80 if (typeof (this.isMultiPaths) !== "undefined" && this.isMultiPaths === true){ 81 if (typeof(text) === "string") { 82 text = JSON.parse(text); 83 } 84 textMerge = this.jsutils.merge(text, textMerge); 85 } else { 86 //console.log("Loader._loadFileAlongIncludePath: succeeded" + filepath); 87 return text; 88 } 89 } 90 //else { 91 //console.log("Loader._loadFileAlongIncludePath: failed"); 92 //} 93 } 94 //else { 95 //console.log("Loader._loadFileAlongIncludePath: " + pathname + " not in manifest for " + this.includePath[i]); 96 //} 97 } 98 99 if (Object.keys(textMerge).length > 0) { 100 //console.log("Loader._loadFileAlongIncludePath: succeeded"); 101 return textMerge; 102 } 103 104 //console.log("Loader._loadFileAlongIncludePath: file not found anywhere along the path."); 105 return undefined; 106 }; 107 108 Loader.prototype.loadFiles = function(paths, sync, params, callback, root) { 109 root = root || (params && params.base); 110 var includePath = []; 111 112 if(this.addPaths && this.addPaths.length > 0){ 113 includePath= includePath.concat(this.addPaths); 114 } 115 if (root) includePath.push(root); 116 includePath = includePath.concat(this.includePath); 117 118 //console.log("Loader loadFiles called"); 119 // make sure we know what we can load 120 if (!paths) { 121 // nothing to load 122 //console.log("nothing to load"); 123 return; 124 } 125 126 //console.log("generic loader: attempting to load these files: " + JSON.stringify(paths)); 127 if (sync) { 128 var ret = []; 129 130 // synchronous 131 this._loadManifests(true); 132 133 for (var i = 0; i < paths.length; i++) { 134 var text = this._loadFileAlongIncludePath(includePath, Path.normalize(paths[i])); 135 ret.push(typeof(text) === "string" ? JSON.parse(text) : text); 136 }; 137 138 // only call the callback at the end of the chain of files 139 if (typeof(callback) === 'function') { 140 callback(ret); 141 } 142 143 return ret; 144 } 145 146 // asynchronous 147 this._loadManifests(false, ilib.bind(this, function() { 148 //console.log("Loader.loadFiles: now loading files asynchronously"); 149 var results = []; 150 this._loadFilesAsync(includePath, paths, results, callback); 151 })); 152 }; 153 154 Loader.prototype._loadFilesAsyncAlongIncludePath = function (includes, filename, cb) { 155 var text = undefined; 156 157 if (includes.length > 0) { 158 var root = includes[0]; 159 includes = includes.slice(1); 160 161 var manifest = this.manifest[root]; 162 if (!manifest || Loader.indexOf(manifest, filename) > -1) { 163 var filepath = Path.join(root, filename); 164 this._loadFile(filepath, false, ilib.bind(this, function(t) { 165 //console.log("Loader._loadFilesAsyncAlongIncludePath: loading " + (t ? " success" : " failed")); 166 if (t) { 167 cb(t); 168 } else { 169 this._loadFilesAsyncAlongIncludePath(includes, filename, cb); 170 } 171 })); 172 } else { 173 //console.log("Loader._loadFilesAsyncAlongIncludePath: " + filepath + " not in manifest for " + root); 174 this._loadFilesAsyncAlongIncludePath(includes, filename, cb); 175 } 176 } else { 177 // file not found in any of the include paths 178 cb(); 179 } 180 }; 181 182 Loader.prototype._loadFilesAsync = function (includePath, paths, results, callback) { 183 if (paths.length > 0) { 184 var filename = paths[0]; 185 paths = paths.slice(1); 186 187 //console.log("Loader._loadFilesAsync: attempting to load " + filename + " along the include path."); 188 this._loadFilesAsyncAlongIncludePath(includePath, filename, ilib.bind(this, function (json) { 189 results.push(typeof(json) === "string" ? JSON.parse(json) : json); 190 this._loadFilesAsync(includePath, paths, results, callback); 191 })); 192 } else { 193 // only call the callback at the end of the chain of files 194 if (typeof(callback) === 'function') { 195 callback(results); 196 } 197 } 198 }; 199 200 Loader.prototype._loadManifestFile = function(i, sync, cb) { 201 //console.log("Loader._loadManifestFile: Checking include path " + i + " " + this.includePath[i]); 202 if (i < this.includePath.length) { 203 var filepath = Path.join(this.includePath[i], "ilibmanifest.json"); 204 //console.log("Loader._loadManifestFile: Loading manifest file " + filepath); 205 var text = this._loadFile(filepath, sync, ilib.bind(this, function(text) { 206 if (text) { 207 //console.log("Loader._loadManifestFile: success!"); 208 this.manifest[this.includePath[i]] = (typeof(text) === "string" ? JSON.parse(text) : text).files; 209 } 210 //else console.log("Loader._loadManifestFile: failed..."); 211 this._loadManifestFile(i+1, sync, cb); 212 })); 213 } else { 214 if (typeof(cb) === 'function') { 215 //console.log("Loader._loadManifestFile: now calling callback function"); 216 cb(); 217 } 218 } 219 }; 220 221 Loader.prototype._loadManifests = function(sync, cb) { 222 //console.log("Loader._loadManifests: called " + (sync ? "synchronously" : "asychronously.")); 223 if (!this.manifest) { 224 //console.log("Loader._loadManifests: attempting to find manifests"); 225 this.manifest = {}; 226 if (typeof(sync) !== 'boolean') { 227 sync = true; 228 } 229 230 this._loadManifestFile(0, sync, cb); 231 } else { 232 //console.log("Loader._loadManifests: already loaded"); 233 if (typeof(cb) === 'function') { 234 //console.log("Loader._loadManifests: now calling callback function"); 235 cb(); 236 } 237 } 238 }; 239 240 Loader.prototype.listAvailableFiles = function(sync, cb) { 241 //console.log("generic loader: list available files called"); 242 this._loadManifests(sync, ilib.bind(this, function () { 243 if (typeof(cb) === 'function') { 244 //console.log("generic loader: now calling caller's callback function"); 245 cb(this.manifest); 246 } 247 })); 248 return this.manifest; 249 }; 250 251 Loader.prototype.addPath = function (paths) { 252 if (!paths) return; 253 254 var newpaths = ilib.isArray(paths) ? paths : [paths]; 255 this.addPaths = this.addPaths.concat(newpaths); 256 this.isMultiPaths = true; 257 }; 258 259 Loader.prototype.removePath = function (paths) { 260 if (!paths) return; 261 paths = ilib.isArray(paths) ? paths : [paths]; 262 263 paths.forEach(ilib.bind(this, function(item){ 264 var index = this.addPaths.indexOf(item); 265 if (index !== -1) { 266 this.addPaths.splice(index, 1); 267 } 268 })); 269 }; 270 271 Loader.indexOf = function(array, obj) { 272 if (!array || !obj) { 273 return -1; 274 } 275 if (typeof(array.indexOf) === 'function') { 276 return array.indexOf(obj); 277 } else { 278 for (var i = 0; i < array.length; i++) { 279 if (array[i] === obj) { 280 return i; 281 } 282 } 283 return -1; 284 } 285 }; 286 287 Loader.prototype.checkAvailability = function(file) { 288 for (var dir in this.manifest) { 289 if (Loader.indexOf(this.manifest[dir], file) !== -1) { 290 return true; 291 } 292 } 293 294 return false; 295 }; 296 297 Loader.prototype.isAvailable = function(file, sync, cb) { 298 //console.log("Loader.isAvailable: called"); 299 if (typeof(sync) !== 'boolean') { 300 sync = true; 301 } 302 if (sync) { 303 this._loadManifests(sync); 304 return this.checkAvailability(file); 305 } 306 307 this._loadManifests(false, ilib.bind(this, function () { 308 // console.log("generic loader: isAvailable " + path + "? "); 309 if (typeof(cb) === 'function') { 310 cb(this.checkAvailability(file)); 311 } 312 })); 313 }; 314 315 module.exports = Loader;