1 /*
  2  * Loader.js - shared loader implementation
  3  *
  4  * Copyright © 2015, 2018, 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 
 23 /**
 24  * @class
 25  * Superclass of the loader classes that contains shared functionality.
 26  *
 27  * @private
 28  * @constructor
 29  */
 30 var Loader = function() {
 31     // console.log("new Loader instance");
 32 
 33     this.protocol = "file://";
 34     this.includePath = [];
 35 };
 36 
 37 Loader.prototype = new ilib.Loader();
 38 Loader.prototype.parent = ilib.Loader;
 39 Loader.prototype.constructor = Loader;
 40 
 41 Loader.prototype._loadFile = function (pathname, sync, cb) {};
 42 
 43 Loader.prototype._exists = function(dir, file) {
 44     var fullpath = Path.normalize(Path.join(dir, file));
 45     if (this.protocol !== "http://") {
 46         var text = this._loadFile(fullpath, true);
 47         if (text) {
 48             this.includePath.push(dir);
 49         }
 50     } else {
 51         // put the dir on the list now assuming it exists, and check for its availability
 52         // later so we can avoid the 404 errors eventually
 53         this.includePath.push(dir);
 54         this._loadFile(fullpath, false, ilib.bind(this, function(text) {
 55             if (!text) {
 56                 //console.log("Loader._exists: removing " + dir + " from the include path because it doesn't exist.");
 57                 this.includePath = this.includePath.slice(-1);
 58             }
 59         }));
 60     }
 61 };
 62 
 63 Loader.prototype._loadFileAlongIncludePath = function(includePath, pathname) {
 64     for (var i = 0; i < includePath.length; i++) {
 65         var manifest = this.manifest[includePath[i]];
 66         if (!manifest || Loader.indexOf(manifest, pathname) > -1) {
 67             var filepath = Path.join(includePath[i], pathname);
 68             //console.log("Loader._loadFileAlongIncludePath: attempting sync load " + filepath);
 69             var text = this._loadFile(filepath, true);
 70             if (text) {
 71                 //console.log("Loader._loadFileAlongIncludePath: succeeded");
 72                 return text;
 73             }
 74             //else {
 75                 //console.log("Loader._loadFileAlongIncludePath: failed");
 76             //}
 77         }
 78         //else {
 79             //console.log("Loader._loadFileAlongIncludePath: " + pathname + " not in manifest for " + this.includePath[i]);
 80         //}
 81     }
 82 
 83     //console.log("Loader._loadFileAlongIncludePath: file not found anywhere along the path.");
 84     return undefined;
 85 };
 86 
 87 Loader.prototype.loadFiles = function(paths, sync, params, callback) {
 88     var includePath = params && params.base ? [params.base].concat(this.includePath) : this.includePath;
 89 
 90     //console.log("Loader loadFiles called");
 91     // make sure we know what we can load
 92     if (!paths) {
 93         // nothing to load
 94         //console.log("nothing to load");
 95         return;
 96     }
 97 
 98     //console.log("generic loader: attempting to load these files: " + JSON.stringify(paths) + "\n");
 99     if (sync) {
100         var ret = [];
101 
102         // synchronous
103         this._loadManifests(true);
104 
105         for (var i = 0; i < paths.length; i++) {
106             var text = this._loadFileAlongIncludePath(includePath, Path.normalize(paths[i]));
107             ret.push(typeof(text) === "string" ? JSON.parse(text) : text);
108         };
109 
110         // only call the callback at the end of the chain of files
111         if (typeof(callback) === 'function') {
112             callback(ret);
113         }
114 
115         return ret;
116     }
117 
118     // asynchronous
119     this._loadManifests(false, ilib.bind(this, function() {
120         //console.log("Loader.loadFiles: now loading files asynchronously");
121         var results = [];
122         this._loadFilesAsync(includePath, paths, results, callback);
123     }));
124 };
125 
126 Loader.prototype._loadFilesAsyncAlongIncludePath = function (includes, filename, cb) {
127     var text = undefined;
128 
129     if (includes.length > 0) {
130         var root = includes[0];
131         includes = includes.slice(1);
132 
133         var manifest = this.manifest[root];
134         if (!manifest || Loader.indexOf(manifest, filename) > -1) {
135             var filepath = Path.join(root, filename);
136             this._loadFile(filepath, false, ilib.bind(this, function(t) {
137                 //console.log("Loader._loadFilesAsyncAlongIncludePath: loading " + (t ? " success" : " failed"));
138                 if (t) {
139                     cb(t);
140                 } else {
141                     this._loadFilesAsyncAlongIncludePath(includes, filename, cb);
142                 }
143             }));
144         } else {
145             //console.log("Loader._loadFilesAsyncAlongIncludePath: " + filepath + " not in manifest for " + root);
146             this._loadFilesAsyncAlongIncludePath(includes, filename, cb);
147         }
148     } else {
149         // file not found in any of the include paths
150         cb();
151     }
152 };
153 
154 Loader.prototype._loadFilesAsync = function (includePath, paths, results, callback) {
155     if (paths.length > 0) {
156         var filename = paths[0];
157         paths = paths.slice(1);
158 
159         //console.log("Loader._loadFilesAsync: attempting to load " + filename + " along the include path.");
160         this._loadFilesAsyncAlongIncludePath(includePath, filename, ilib.bind(this, function (json) {
161             results.push(typeof(json) === "string" ? JSON.parse(json) : json);
162             this._loadFilesAsync(includePath, paths, results, callback);
163         }));
164     } else {
165         // only call the callback at the end of the chain of files
166         if (typeof(callback) === 'function') {
167             callback(results);
168         }
169     }
170 };
171 
172 Loader.prototype._loadManifestFile = function(i, sync, cb) {
173     //console.log("Loader._loadManifestFile: Checking include path " + i + " " + this.includePath[i]);
174     if (i < this.includePath.length) {
175         var filepath = Path.join(this.includePath[i], "ilibmanifest.json");
176         //console.log("Loader._loadManifestFile: Loading manifest file " + filepath);
177         var text = this._loadFile(filepath, sync, ilib.bind(this, function(text) {
178             if (text) {
179                 //console.log("Loader._loadManifestFile: success!");
180                 this.manifest[this.includePath[i]] = (typeof(text) === "string" ? JSON.parse(text) : text).files;
181             }
182             //else console.log("Loader._loadManifestFile: failed...");
183             this._loadManifestFile(i+1, sync, cb);
184         }));
185     } else {
186         if (typeof(cb) === 'function') {
187             //console.log("Loader._loadManifestFile: now calling callback function");
188             cb();
189         }
190     }
191 };
192 
193 Loader.prototype._loadManifests = function(sync, cb) {
194     //console.log("Loader._loadManifests: called " + (sync ? "synchronously" : "asychronously."));
195     if (!this.manifest) {
196         //console.log("Loader._loadManifests: attempting to find manifests");
197         this.manifest = {};
198         if (typeof(sync) !== 'boolean') {
199             sync = true;
200         }
201 
202         this._loadManifestFile(0, sync, cb);
203     } else {
204         //console.log("Loader._loadManifests: already loaded");
205         if (typeof(cb) === 'function') {
206             //console.log("Loader._loadManifests: now calling callback function");
207             cb();
208         }
209     }
210 };
211 
212 Loader.prototype.listAvailableFiles = function(sync, cb) {
213     //console.log("generic loader: list available files called");
214     this._loadManifests(sync, ilib.bind(this, function () {
215         if (typeof(cb) === 'function') {
216             //console.log("generic loader: now calling caller's callback function");
217             cb(this.manifest);
218         }
219     }));
220     return this.manifest;
221 };
222 
223 Loader.indexOf = function(array, obj) {
224     if (!array || !obj) {
225         return -1;
226     }
227     if (typeof(array.indexOf) === 'function') {
228         return array.indexOf(obj);
229     } else {
230         for (var i = 0; i < array.length; i++) {
231             if (array[i] === obj) {
232                 return i;
233             }
234         }
235         return -1;
236     }
237 };
238 
239 Loader.prototype.checkAvailability = function(file) {
240     for (var dir in this.manifest) {
241         if (Loader.indexOf(this.manifest[dir], file) !== -1) {
242             return true;
243         }
244     }
245 
246     return false;
247 };
248 
249 Loader.prototype.isAvailable = function(file, sync, cb) {
250     //console.log("Loader.isAvailable: called");
251     if (typeof(sync) !== 'boolean') {
252         sync = true;
253     }
254     if (sync) {
255         this._loadManifests(sync);
256         return this.checkAvailability(file);
257     }
258 
259     this._loadManifests(false, ilib.bind(this, function () {
260         // console.log("generic loader: isAvailable " + path + "? ");
261         if (typeof(cb) === 'function') {
262             cb(this.checkAvailability(file));
263         }
264     }));
265 };
266 
267 module.exports = Loader;