/*
 * LOGICAL-PARADOX.ORG
 * Copyright (C)2005 satoshi akabane(akabane@logical-paradox.org)
 * $Id: RSSLocalContentsMgr.java,v 1.8 2006/01/26 16:27:35 rampil Exp $
 */
package org.logical_paradox.rss.lcmgr;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.logical_paradox.common.util.ExceptionUtils;
import org.logical_paradox.rss.IllegalObjectStateException;
import org.logical_paradox.rss.RSSIllegalConfigException;
import org.logical_paradox.rss.http.WebContents;
import org.logical_paradox.rss.lcmgr.persister.XMLPersister;

/**
 * RSSLocalContentsMgr
 * コンテンツデータをローカルに保存したり，リモートのコンテンツ管理システムへ転送したりするオブジェクト<br>
 * スレッドとして動作する．
 * 
 * @author satoshi akabane@logical-paradox.org
 * @version $Revision: 1.8 $
 */
public class RSSLocalContentsMgr {
	/** ロガー */
	private static final Log log = LogFactory.getLog(RSSLocalContentsMgr.class);
	public static final String RSSLCMGR_CONF_FILENAME = "../conf/rsslcmgr.conf";

	public static final int MAX_MEMCACHE_SIZE = 10485760;			// メモリに配置しておくコンテンツの最大サイズ(デフォルト=10MB)
	public static final int MAX_FILECACHE_SIZE = 1024;			// この数よりローカルキャッシュファイルが多くなった場合は探索を一時停止する
	public static final int RESTORE_CNT_FROM_LCACHE = 64;			// 一度にメモリに取り込むキャッシュファイル数

	protected RSSLCConfig lcfg = null;								// ローカルコンテンツ管理システムの設定データ
	private RSSLCAgent[] agnts = null;								// コンテンツ転送用のエージェント

	/**
	 * コンストラクタ．
	 * @param navimode true: 分散システム / false: スタンドアローン
	 * @param filename コンフィグファイル名
	 */
	public RSSLocalContentsMgr(boolean navimode, String filename) throws RSSIllegalConfigException {
		log.info("ローカルコンテンツ管理システムを初期化しています");

		try {
			// ローカルコンテンツ管理システムのコンフィグ用データを生成する
			lcfg = new RSSLCConfig(filename != null ? filename : RSSLCMGR_CONF_FILENAME);

		} catch(IOException ioe) {
			log.error("ローカルコンテンツ管理システム用ロガーの作成に失敗しました");
			throw new RSSIllegalConfigException(ioe.getMessage());
		}

		try {
			lcfg.cr = ContentsRegisterFactory.getContentsRegister(lcfg, navimode);
		} catch(Exception ie) {
			log.error("WebコンテンツサーバーからレジスタI/Fを受け取れませんでした");
			throw new RSSIllegalConfigException(ie.getMessage());
		}
		// エージェントを作成する
		int agents = lcfg.getContentsSendingMgrs();
		agnts = new RSSLCAgent[ agents ];
		for(int i = 0; i < agents; i++) {
			agnts[i] = new RSSLCAgent(lcfg, this);
		}

		log.info("ローカルコンテンツ管理システムは，" + agnts.length + "個のエージェントを作成しました");
		log.info("ローカルコンテンツ管理システムを初期化しました");
	}

	/**
	 * コンテンツ転送エージェントを開始する
	 */
	public void start() {
		for(int i = 0; i < agnts.length; i++) {
			agnts[i].start();
		}
		log.info("全てのエージェントが開始されました");
	}

	/**
	 * スレッドを終了する
	 * スレッドを終了する場合，未送信のコンテンツを全てリモートのシステムへ転送する
	 */
	public void done() {
		try {
			for(int i = 0; i < agnts.length; i++) {
				agnts[i].done();
				agnts[i].join();			// スレッド停止の待ち合わせ処理
			}
		} catch(InterruptedException ie) {}

		log.info("全てのエージェントを停止しました");

	}

	/**
	 * コンテンツをローカルのファイルシステムに保管する
	 */
	public void store(WebContents c) throws IOException, IllegalObjectStateException {
		synchronized(lcfg.memCache) {
			int newId = lcfg.nextno;
			lcfg.nextno++;

			if(c.getContentLength() > MAX_MEMCACHE_SIZE) {

				// 格納しようとしているコンテンツのサイズが，キャッシュのサイズより大きい場合
				// メモリに取り込むことができないので，ワーニングを出力して破棄する
				log.info(
						"'" + c.getURL().toString() + "'は，" +
						"キャッシュサイズより大きいため，処理されずに破棄されました．" +
						"改善するにはキャッシュのサイズを拡張してください"
				);

				log.warn("lcmgr: キャッシュより大きい");
				// 強制終了
				return;

			} else if(lcfg.memCacheSize + c.getContentLength() >= MAX_MEMCACHE_SIZE) {

				// メモリに格納できるサイズを超えた場合はファイルへ書き出す
				// IDをファイル名にして，コンテンツファイルを生成する
				String filenameFullPath = lcfg.getContentsCacheDir() + File.separator + Integer.toString(newId);
				PrintWriter w = new PrintWriter(new FileWriter(new File(filenameFullPath)));

				// コンテンツを出力する
				// URLその他の情報については，オンメモリ(fileEntries)で管理される
				String xmldoc = null;
				try {
					xmldoc = XMLPersister.marshall(c); 
				} catch(Exception e) {
					log.error("キャッシュファイルへの出力に失敗しました:" + ExceptionUtils.stackTraceToString(e));
					throw new IllegalObjectStateException(e);
				}
				lcfg.fileEntries.add(new CacheFileEntry(c, filenameFullPath));
				w.print(xmldoc);
				w.flush();
				w.close();

				log.trace("コンテンツをファイルに格納しました");

			} else {

				// メモリに格納できる場合は，ファイルへ書き出さずにメモリに残す
				lcfg.memCache.add(c);
				lcfg.memCacheSize += c.getContentLength();

				log.trace("コンテンツをメモリに格納しました");

			}
		}

		log.trace("コンテンツを格納しました:" + c.getURL().toString() + "(" + c.getContentLength() + ")");
		log.trace("残りメモリ容量:" + (MAX_MEMCACHE_SIZE-lcfg.memCacheSize) + "bytes");

		// 格納したと同時に，キャッシュファイル数が多すぎないかどうかを確認する
		// 多すぎる場合は探索を停止させる必要があるため
		synchronized(lcfg.fileEntries) {
			if(lcfg.runningStatus == true && lcfg.fileEntries.size() >= MAX_FILECACHE_SIZE) {
				// 指定数以上にキャッシュファイルが生成されてしまっている場合
				// 全部を転送するように設定して，探索を一時的に停止する
				lcfg.runningStatus = false;
				log.info("＊＊＊＊＊ キャッシュがフラッシュされるまで，探索を停止します ＊＊＊＊＊");
			}
		}
	}

	/**
	 * 新しいコンテンツを受け付けることができるか
	 * あまりにも多くのローカルキャッシュファイルが存在していて，転送が追いつかない場合は
	 * このフラグがfalseになる．falseになっている間は，RSSWebNavigatorは転送を停止する(＝ロボットも探索を停止する)
	 */
	public boolean isReceivable() {
		return lcfg.runningStatus;
	}
}

// end of RSSLocalContentsMgr.java
