UNPKG

waffle

Version:

シンプルなWEBアプリケーションフレームワークです。(ALL YOUR NODE ARE BELONG TO US)

332 lines (293 loc) 9.5 kB
/* * Copyright 2012 Katsunori Koyanagi * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ /** * @overview モジュール及びリソースの検索、更新の監視、パスの解決機能を提供します。 */ "use strict"; var FSWatcher = require("../utils/FSWatcher"); var ResourceBundle = require("../utils/ResourceBundle"); var lang = require("../utils/Lang"); var log = require("../utils/Log"); var fs = require("fs"); /** * デプロイヤのクラスです。 * <p> * アプリケーションルートを起点したリソースの管理と、自動再読み込みの機能を提供します。 * このクラスのインスタンスは、アプリケーションによって内部で使用されます。 * </p> * * @class デプロイヤのクラスです。 * @constructor */ var Deployer = function() { }; // // デプロイヤのプロトタイプ設定 // /** * デプロイヤのサービスを開始します。 * * @param {String} * approot アプリケーションルートのパス */ Deployer.prototype.start = function(approot) { this.resourceFiles = {}; this.messages = {}; this.root = approot; this.watcher = new FSWatcher(); var onChange = this.onChange.bind(this); this.watcher.on("change", onChange) this.watcher.on("remove", onChange) var np = process.env['NODE_PATH']; if (np) { np += ";" + approot; } else { np = approot; } process.env['NODE_PATH'] = np; require("module")._initPaths(); }; /** * @private */ Deployer.prototype.onChange = function(file, type) { if (type == "change") { log("file changed(%s)", file); } else { log("file removed(%s)", file); } delete this.resourceFiles[file]; }; /** * デプロイヤのサービスを停止して、内部状態を破棄します。 */ Deployer.prototype.destroy = function() { var np = process.env['NODE_PATH']; np = np.replace(this.root, ""); process.env['NODE_PATH'] = np; var key; for (key in this.resourceFiles) { delete this.resourceFiles[key]; } for (key in this.messages) { var bundle = this.messages[key]; bundle.watcher && bundle.watcher.destroy(); delete this.messages[key]; } this.watcher.destroy(); this.__destroyed__ = true; }; /** * アプリケーションルートを起点とした相対パスでrequireを行った結果を返します。 * * @see Waffle#require() * @param {String} * path モジュールもしくはJSファイル等のパス * @return モジュール * @throws {Error} * モジュールが見つからない場合 */ Deployer.prototype.require = function(path) { return require(fs.join(this.root, path)); }; /** * アプリケーションルートを起点とした相対パスでバイナリのリソースを取得して返します。 * <p> * この関数によって取得されるリソースは内部でキャッシュされます。 callback関数を省略した場合は同期処理となり、取得されたリソースが返されます。 * callback関数を設定した場合は、通常非同期で第1引数にリソース、第2引数にキャッシュ済みのリソースを取得したのかを示すフラグが渡されます。 * キャッシュ済みの場合は同期処理によるコールバックになります。 取得に失敗した場合はnullが返されるか、callback関数にnullが渡されます。 * </p> * * @see Waffle#getFile() * @param {String} * path リソースのパス * @param {Function} * callback コールバック関数(省略可能)、引数にはリソースと、キャッシュ済みのリソースを取得した場合はtrueが渡されます。 * @return {Buffer} バイナリ(コールバック関数が省略された場合) */ Deployer.prototype.getFile = function(path, callback) { name = fs.join(this.root, path); if (callback) { getResourceAsync.call(this, path, null, this.watcher, callback); } else { return getResourceSync.call(this, path, null, this.watcher) } }; /** * 指定のパスに該当するリソースがキャッシュ済みであるかを返します。 * * @param {String} * path リソースのパス * @return キャッシュ済みならtrue */ Deployer.prototype.isResourceInCache = function(path) { return fs.join(this.root, path) in this.resourceFiles; }; /** * アプリケーションルートを起点とした相対パスで多言語メッセージのリソースを取得して返します。 * <p> * この関数によって取得されるリソースは内部でキャッシュされます。 * </p> * * @see Waffle#getMessages() * @param {String} * path リソースのパス * @return {ResourceBundle} メッセージリソース */ Deployer.prototype.getMessages = function(path) { name = fs.join(this.root, path); if (name in this.messages) { return this.messages[name]; } var bundle = new ResourceBundle(name); bundle.watcher = new FSWatcher(); this.messages[name] = bundle; return bundle; }; /** * アプリケーションルートを起点とした相対パスでテキストのリソースを取得して返します。 * <p> * この関数によって取得されるリソースは内部でキャッシュされます。 callback関数を省略した場合は同期処理となり、取得されたリソースが返されます。 * callback関数を設定した場合は、通常非同期で第1引数にリソース、第2引数にキャッシュ済みのリソースを取得したのかを示すフラグが渡されます。 * キャッシュ済みの場合は同期処理によるコールバックになります。 取得に失敗した場合はnullが返されるか、callback関数にnullが渡されます。 * </p> * * @see Waffle#getText() * @param {String} * path リソースのパス * @param {String} * encoding エンコーディング(省略可能、省略時はUTF-8とみなされる) * @param {Function} * callback コールバック関数(省略可能)、引数にはリソースと、キャッシュ済みのリソースを取得した場合はtrueが渡されます。 * @return テキスト(コールバック関数が省略された場合) */ Deployer.prototype.getText = function(path, encoding, callback) { path = fs.join(this.root, path); switch (arguments.length) { case 0: return; case 1: encoding = null; callback = null; break; case 2: if (!lang.isFunction(encoding)) { encoding = encoding ? encoding : null; callback = null; } else { callback = encoding; encoding = null; } break; case 3: encoding = encoding ? encoding : null; callback = callback ? callback : null; break; default: } if (!encoding) { encoding = "utf-8"; } if (callback) { getResourceAsync.call(this, path, encoding, this.watcher, callback); return; } return getResourceSync.call(this, path, encoding, this.watcher); }; /** * @private */ function getResourceSync(path, encoding, watcher) { var info = this.resourceFiles[path]; if (info != null && info.loaded) { return info.data; } if (!fs.existsSync(path)) { return null; } var data = null; try { var stats = fs.statSync(path); if (encoding) { data = fs.readFileSync(path, encoding); } else { data = fs.readFileSync(path); } watcher.add(path); log("file loaded(%s)", path); if (info == null) { info = {}; this.resourceFiles[path] = info; } info.timestamp = stats.mtime.getTime(); info.loaded = true; info.data = data; } catch (e) { } return data; } /** * @private */ function getResourceAsync(path, encoding, watcher, callback) { var self = this; var info = this.resourceFiles[path]; if (info != null && info.loaded) { callback(info.data, true); return; } if (info != null) { process.nextTick(next); return; } function next() { getResourceAsync.call(self, path, watcher, callback); } function onStat(err, stats) { if (err) { callback(null, false); return; } var info = {}; info.timestamp = stats.mtime.getTime(); info.loaded = false; info.data = null; self.resourceFiles[path] = info; if (encoding) { fs.readFile(path, encoding, onRead); } else { fs.readFile(path, onRead); } } function onRead(err, data) { if (err) { delete self.resourceFiles[path]; callback(null, false); return; } watcher.add(path); log("file loaded(%s)", path); var info = self.resourceFiles[path]; info.loaded = true; info.data = data; callback(data, false); } fs.stat(path, onStat); } // // expose // module.exports = Deployer;