waffle
Version:
シンプルなWEBアプリケーションフレームワークです。(ALL YOUR NODE ARE BELONG TO US)
332 lines (293 loc) • 9.5 kB
JavaScript
/*
* 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;