/*
 * LOGICAL-PARADOX.ORG
 * Copyright (C)2006 satoshi akabane(akabane@logical-paradox.org)
 * $Id: RSSLCAgent.java,v 1.3 2006/01/23 02:46:11 rampil Exp $
 */
package org.logical_paradox.rss.lcmgr;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;

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

/**
 * RSSLCAgent
 * ローカルコンテンツ管理システムのエージェント<br>
 * このエージェントプログラムは，他のエージェントと情報を共有しつつ，スレッドで動作する
 * @author satoshi akabane@logical-paradox.org
 * @version $Revision: 1.3 $
 */
public class RSSLCAgent extends Thread {
	/** ロガー */
	private static final Log log = LogFactory.getLog(RSSLCAgent.class);
	private RSSLCConfig lcfg = null;
	private RSSLocalContentsMgr mgr = null;

	protected RSSLCAgent(RSSLCConfig c, RSSLocalContentsMgr m) {
		lcfg = c;
		mgr = m;
	}

	/**
	 * 終了
	 */
	public void done() {
		this.interrupt();
	}

	/**
	 * スレッドとして動作する部分
	 */
	public void run() {
		try {
			while(true) {
				// しかるべき適当なタイミングで，ローカルのコンテンツキャッシュを吐き出す
				sleep(lcfg.getContentsSendingInterval());

				int pSendScale = lcfg.getContentsSendingScale();
				if(lcfg.runningStatus == false) {
					// 探索停止中の場合，問答無用で転送をする
					pSendScale = 0;
				}
				sendContents(pSendScale);

				synchronized(lcfg.fileEntries) {
					if(lcfg.fileEntries.size() < RSSLocalContentsMgr.MAX_FILECACHE_SIZE && lcfg.runningStatus == false) {
						// 転送した結果，所定のファイル数以下にキャッシュファイルが減少したならば
						// 探索を再開する
						lcfg.runningStatus = true;
						log.info("＊＊＊＊＊ キャッシュをフラッシュしました．探索を再開します ＊＊＊＊＊");
						synchronized(mgr) {
							mgr.notifyAll();
						}
					}
				}
			}
		} catch(InterruptedException e) {
			// 何らかの理由で停止した場合
			// 全てのコンテンツをリモートのシステムへ転送する
			try {
				log.info("エージェントが停止信号を受信しました");
				sendContents(0);
			} catch(IOException ie0) {}
		} catch(IOException ie) {
		}
	}

	/**
	 * ローカルに保管されているコンテンツを，リモートのコンテンツ管理システムへ転送する
	 * メモリに格納されているものから順に転送される<br>
	 * <br>
	 * 基本的にキャッシュはメモリに格納されているものから転送される．<br>
	 * メモリに何も存在しない場合，一度ファイルからメモリに読み込んで，それから転送をする<br>
	 * 従って，キャッシュ用のメモリより1ファイル(1ページ)のサイズのほうが大きい場合，その<br>
	 * ページを転送することはできない(仕様)．<br>
	 *<br>
	 * 既にいくつかのキャッシュがメモリに存在する状況下で，ファイルキャッシュをメモリに<br>
	 * ロードできない場合，メモリのほうから優先的に転送される仕組みになっているため，<br>
	 * 結局はメモリが空くまでそのファイルはロードされない(メモリが空けば転送される)
	 * @param numberOfContents 転送するコンテンツ数
	 * @throws IOException 送信エラーが発生した
	 */
	protected void sendContents(int numberOfContents) throws IOException {
		WebContents c = null;
		boolean exitf = numberOfContents == 0 ? true : false;

		while(numberOfContents > 0 || exitf == true) {
			numberOfContents--;
			synchronized(lcfg.memCache) {
				if(lcfg.memCache.size() == 0) {
					// メモリに1つもコンテンツが乗っていない場合
					// ファイルから書き出す処理をする
					if(restoreFromLocalCache(RSSLocalContentsMgr.RESTORE_CNT_FROM_LCACHE) > 0) {
						// 1件以上読み込みができたので，もう一度このループをやり直す
						numberOfContents++;
						continue;
					} else {
						// メモリからロードできない場合
						// 原因としては，
						// (1) 本当にキャッシュファイルが存在しない
						// (2) 必要な空きメモリがない
						// の2点
						// いずれにしても，送信を継続できる状態ではないので終了
						exitf = false;
						break;
					}

				} else {
					// メモリにコンテンツが乗っている場合
					c = (WebContents)lcfg.memCache.removeFirst();		// 先頭のものを取得する
					lcfg.memCacheSize -= c.getContentLength();			// 取り出したサイズを空にする

					// ファイルから1つ読んで，メモリ上に展開する
					// 展開されたファイルは必要ないので，削除してしまってよい
					restoreFromLocalCache();
				}
			}
			if(c != null) {
				log.trace(c.getURL() + "をコンテンツ管理システムへ転送しています");
				// リモートのコンテンツ管理システムへ転送する
				lcfg.cr.registContents(c);
				log.trace(c.getURL() + "をコンテンツ管理システムへ転送しました");
			}
		}
	}

	/**
	 * ローカルのファイルシステムに保存されているキャッシュファイルから指定数読み込んで
	 * メモリに展開する
 	 * 展開できるだけの空き容量がない場合，いくつかのファイルを試してみる
	 * いくつか試した結果，やっぱり展開できない場合は0を返す
	 * 展開できた場合，展開できたサイズを返す
	 */
	private int restoreFromLocalCache(int files) throws IOException {
		final int IM_RETRY_COUNT = 16;			// 取り込みに失敗した場合にリトライする回数

		int ls = 0;
		int failureCnt = 0;
		CacheFileEntry cachef = null;

		while(files > 0 && failureCnt < IM_RETRY_COUNT) {
			files--;

			synchronized(lcfg.fileEntries) {
				if(lcfg.fileEntries.size() == 0) {
					return 0;
				}
				cachef = (CacheFileEntry)lcfg.fileEntries.getFirst();
				if(cachef.size() > (RSSLocalContentsMgr.MAX_MEMCACHE_SIZE - lcfg.memCacheSize)) {
					failureCnt++;
					continue;
				}

				// 取り込めそうなので，削除しておく
				lcfg.fileEntries.removeFirst();
			}

			// キャッシュファイルの読み込み
			File f = new File(cachef.getFileName());
			BufferedReader bf = new BufferedReader(new FileReader(f));
			String contents = null;
			String linebuf = null;
			while((linebuf = bf.readLine()) != null) {
				contents += linebuf + "\n";
			}

			// 全部読み込んだので，ファイルを削除する
			bf.close();
			bf = null;
			f.delete();

			// 読み込んだコンテンツデータを，メモリ上に展開する
			WebContents c = null;
			try {
				c = XMLPersister.unmarshall(contents);
			} catch(Exception e) {
				log.error("キャッシュファイルの復元に失敗:" + ExceptionUtils.stackTraceToString(e));
				throw new IOException(e.getMessage());
			}
			lcfg.memCache.add(c);
			lcfg.memCacheSize += contents.length();

			log.trace("キャッシュファイル[" + cachef.getFileName() + "]を削除しました");
			log.trace("残りメモリ:" + (RSSLocalContentsMgr.MAX_MEMCACHE_SIZE - lcfg.memCacheSize) + "bytes");
		}

		return ls;
	}
	// 上記関数の引数無し版
	// 引数なしの場合は，1件しか読み込みを行なわない
	private int restoreFromLocalCache() throws IOException {
		return restoreFromLocalCache(1);
	}
}

// end of RSSLCAgent.java
