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;