// ライセンス: GPL2

//#define _DEBUG
//#define _TEST_CACHE
#include "jddebug.h"

#include "boardbase.h"
#include "articlebase.h"
#include "articlehash.h"
#include "interface.h"

#include "skeleton/msgdiag.h"

#include "jdlib/jdiconv.h"
#include "jdlib/jdregex.h"
#include "jdlib/miscutil.h"
#include "jdlib/miscmsg.h"
#include "jdlib/loaderdata.h"
#include "jdlib/confloader.h"


#include "global.h"
#include "httpcode.h"
#include "command.h"
#include "cache.h"
#include "config/globalconf.h"
#include "session.h"
#include "boardcolumnsid.h"

#include <sstream>
#include <cstring>


#ifdef _TEST_CACHE
int cache_hit_art = 0;
int cache_nohit_art = 0;
#endif


enum
{
    SIZE_OF_RAWDATA = 2 * 1024 * 1024
};

using namespace DBTREE;


BoardBase::BoardBase( const std::string& root, const std::string& path_board, const std::string& name )
    : SKELETON::Loadable()
    , m_status( STATUS_UNKNOWN )
    , m_list_subject_created( false )
    , m_view_sort_column( -1 )
    , m_view_sort_mode( -1 )
    , m_view_sort_pre_column( -1 )
    , m_view_sort_pre_mode( -1 )
    , m_root( root )
    , m_path_board( path_board )
    , m_name( name )
    , m_samba_sec( 0 )
    , m_live_sec( 0 )
    , m_last_access_time( 0 )
    , m_number_max_res( 0 )
    , m_iconv( NULL )
    , m_rawdata( NULL )
    , m_rawdata_left( NULL )
    , m_read_info( 0 )
    , m_append_articles( false )
    , m_get_article( NULL )
    , m_cancel_remove_abone_thread( false )
    , m_article_null( 0 )
{
    clear();
    clear_load_data();

    memset( &m_write_time, 0, sizeof( struct timeval ) );

    m_hash_article = new ArticleHash();

    // 板情報はクラスが作られた時点ではまだ読まない
    // BoardBase::read_info() の説明を見ること
}


//
// デストラクタで子ArticleBaseクラスを全部削除
//
BoardBase::~BoardBase()
{
#ifdef _DEBUG
    if( m_hash_article->size() ) std::cout << "BoardBase::~BoardBase : " << url_boardbase() << std::endl;
#endif

    clear();

    ArticleHashIterator it = m_hash_article->begin();
    for( ; it != m_hash_article->end(); ++it ) delete ( *it );

    if( m_article_null ) delete m_article_null;

    delete m_hash_article;

#ifdef _TEST_CACHE
    if( m_hash_article->size() ){
        std::cout << "article cache\n"
                  << "hit = " << cache_hit_art << std::endl
                  << "nohit = " << cache_nohit_art << std::endl
                  << "hit/total*100 = " << (double)(cache_hit_art)/(cache_hit_art+cache_nohit_art)*100. << std::endl;
    }
#endif    
}


ArticleBase* BoardBase::get_article_null()
{
    if( ! m_article_null ) m_article_null = new DBTREE::ArticleBase( "", "", false );
    return m_article_null;
}


bool BoardBase::empty()
{
    return m_root.empty();
}


//
// url がこの板のものかどうか
//
bool BoardBase::equal( const std::string& url )
{
    if( url.find( get_root() ) == 0
        && url.find( get_path_board() + "/" ) != std::string::npos ) return true;

    return false;
}


//
// 名前を変更
//
void BoardBase::update_name( const std::string& name )
{
    if( m_name != name ){

        m_name = name;

       // 表示中のviewの板名表示更新
        CORE::core_set_command( "update_boardname", url_boardbase() );
    }
}


// ユーザエージェント
const std::string& BoardBase::get_agent()
{
    return CONFIG::get_agent_for_data();
}


// 読み込み用プロキシ
const std::string BoardBase::get_proxy_host()
{
    const int mode = get_mode_local_proxy();

    if( mode == DBTREE::PROXY_GLOBAL ){

        if( CONFIG::get_use_proxy_for_data() ) return CONFIG::get_proxy_for_data();
    }
    else if( mode == DBTREE::PROXY_LOCAL ) return get_local_proxy();

    return std::string();
}

const int BoardBase::get_proxy_port()
{
    const int mode = get_mode_local_proxy();

    if( mode == DBTREE::PROXY_GLOBAL ) return CONFIG::get_proxy_port_for_data();
    else if( mode == DBTREE::PROXY_LOCAL ) return get_local_proxy_port();

    return 0;
}

const std::string BoardBase::get_proxy_basicauth()
{
    const int mode = get_mode_local_proxy();

    if( mode == DBTREE::PROXY_GLOBAL ) return CONFIG::get_proxy_basicauth_for_data();
    else if( mode == DBTREE::PROXY_LOCAL ) return get_local_proxy_basicauth();

    return std::string();
}


// 書き込み用プロキシ
const std::string BoardBase::get_proxy_host_w()
{
    const int mode = get_mode_local_proxy_w();

    if( mode == DBTREE::PROXY_GLOBAL ){

        if( CONFIG::get_use_proxy_for_data() ) return CONFIG::get_proxy_for_data();
    }
    else if( mode == DBTREE::PROXY_LOCAL ) return get_local_proxy_w();

    return std::string();
}

const int BoardBase::get_proxy_port_w()
{
    const int mode = get_mode_local_proxy_w();

    if( mode == DBTREE::PROXY_GLOBAL ) return CONFIG::get_proxy_port_for_data();
    else if( mode == DBTREE::PROXY_LOCAL ) return get_local_proxy_port_w();

    return 0;
}


const std::string BoardBase::get_proxy_basicauth_w()
{
    const int mode = get_mode_local_proxy_w();

    if( mode == DBTREE::PROXY_GLOBAL ) return CONFIG::get_proxy_basicauth_for_data();
    else if( mode == DBTREE::PROXY_LOCAL ) return get_local_proxy_basicauth_w();

    return std::string();
}


// ローカルルール
const std::string BoardBase::localrule()
{
    return "利用できません";
}


// setting.txt
const std::string BoardBase::settingtxt()
{
    return "利用できません";
}

// デフォルトの名無し名
const std::string BoardBase::default_noname()
{
    return "???";
}


// 最大改行数/2
const int BoardBase::line_number()
{
    return 0;
}


// 最大書き込みバイト数
const int BoardBase::message_count()
{
    return 0;
}


//書き込み用クッキー
const std::string BoardBase::cookie_for_write()
{
    if( m_list_cookies_for_write.empty() ) return std::string();

    return *(m_list_cookies_for_write.begin() );
}


void BoardBase::clear()
{
    if( m_rawdata ) free( m_rawdata );
    m_rawdata = NULL;

    if( m_rawdata_left ) free( m_rawdata_left );
    m_rawdata_left = NULL;

    m_lng_rawdata = 0;
    m_lng_rawdata_left = 0;

    m_get_article_url = std::string();

    if( m_iconv ) delete m_iconv;
    m_iconv = NULL;

    m_list_artinfo.clear();
}


//
// m_url_update_views に登録されている view に update_board コマンドを送る
//
void BoardBase::send_update_board()
{
#ifdef _DEBUG
    std::cout << "BoardBase::send_update_board\n";
#endif

    // ダウンロードを開始したビュー以外のビューの内容を更新する
    //
    // "update_board" コマンドの後に"update_board_item"を送ると
    // ローディングが終了しているため行を二回更新してしまうので注意
    // 詳しくは BoardViewBase::update_item() を参照
    CORE::core_set_command( "update_board_item", url_subject(),
                            std::string() // IDとして空文字を送る
        );


    // ダウンロードを開始したビューの内容を更新する
    std::list< std::string >::iterator it = m_url_update_views.begin();
    for( ; it != m_url_update_views.end(); ++it ){
#ifdef _DEBUG
        std::cout << "update : " << *it << std::endl;
#endif 
        CORE::core_set_command( "update_board", *it );
    }
    m_url_update_views.clear();
}


//
// 新しくArticleBaseクラスを追加してそのポインタを返す
//
ArticleBase* BoardBase::append_article( const std::string& datbase, const std::string& id, const bool cached )
{
    // ベースクラスでは何もしない
    return get_article_null();
}


//
// 書き込み時間更新
//
void BoardBase::update_writetime()
{
    struct timeval tv;
    struct timezone tz;
    if( gettimeofday( &tv, &tz ) == 0 ){

        m_write_time = tv;

#ifdef _DEBUG
        std::cout << "BoardBase::update_writetime : " << m_write_time.tv_sec << std::endl;
#endif
    }
}


//
// 経過時間(秒)
//
const time_t BoardBase::get_write_pass()
{
    time_t ret = 0;
    struct timeval tv;
    struct timezone tz;

    if( m_write_time.tv_sec && gettimeofday( &tv, &tz ) == 0 ) ret = MAX( 0, tv.tv_sec - m_write_time.tv_sec );

    return ret;
}


//
// 書き込み可能までの残り秒
//
time_t BoardBase::get_write_leftsec()
{
    const time_t mrg = 2;

    if( ! m_samba_sec ) return 0;
    if( ! get_write_pass() ) return 0;

    // ログイン中は書き込み規制無し
    if( SESSION::login2ch() ) return 0;

    return MAX( 0, m_samba_sec + mrg - get_write_pass() );
}


//
// 全書き込み履歴クリア
//
void BoardBase::clear_all_post_history()
{
    // キャッシュにあるレスをデータベースに登録
    append_all_article_in_cache();
    if( m_hash_article->size() == 0 ) return;

    ArticleHashIterator it = m_hash_article->begin();
    for( ; it != m_hash_article->end(); ++it ) ( *it )->clear_post_history();
}


//
// 全スレの書き込み時間とスレ立て時間の文字列をリセット
//
void BoardBase::reset_all_write_date()
{
    ArticleHashIterator it = m_hash_article->begin();
    for( ; it != m_hash_article->end(); ++it ) ( *it )->reset_write_date();
}    

void BoardBase::reset_all_since_date()
{
    ArticleHashIterator it = m_hash_article->begin();
    for( ; it != m_hash_article->end(); ++it ) ( *it )->reset_since_date();
}


//
// 最大レス数をセット
//
void BoardBase::set_number_max_res( const int number )
{
#ifdef _DEBUG
    std::cout << "BoardBase::set_number_max_res " << m_number_max_res << " -> " << number << std::endl;
#endif

    m_number_max_res = MAX( 0, MIN( MAX_RESNUMBER, number ) );

    ArticleHashIterator it = m_hash_article->begin();
    for( ; it != m_hash_article->end(); ++it ) ( *it )->set_number_max( m_number_max_res );
}



//
// 板情報の取得
//
// コンストラクタで呼ぶと起動時に全ての板の情報を読まなければならなくなるので、
// Root::get_board()で初めて参照されたときに一度だけ実行
//
void BoardBase::read_info()
{
    if( empty() ) return;

    if( ! m_read_info ){ // 一度読んだらもう処理しない

#ifdef _DEBUG
        std::cout << "BoardBase::read_info : " << m_id << std::endl;
#endif
        
        m_read_info = true; 

        // 板の情報ファイル読み込み
        read_board_info();

        // キャッシュからローカルルールとSETTING.TXT のロード
        load_rule_setting();
    }
}



//
// 移転などで板のルートやパスを変更する
//
void BoardBase::update_url( const std::string& root, const std::string& path_board )
{
#ifdef _DEBUG
    std::cout << "BoardBase::update_url\n"
              << m_root << " -> " << root << std::endl
              << m_path_board << " -> " << path_board << std::endl;
#endif

    m_root = root;
    m_path_board = path_board;

    // modified 時刻をリセット
    // 自動移転処理後に bbsmenu.html を読み込んだときに、bbsmenu.html の
    // アドレスが古いと二度と自動移転処理しなくなる
    set_date_modified( std::string() );

    // 配下の ArticleBase にも知らせてあげる
    const std::string datbase = url_datbase();
    ArticleHashIterator it = m_hash_article->begin();
    for( ; it != m_hash_article->end(); ++it ) ( *it )->update_datbase( datbase );
}


//
// スレの urlをdat型のurlに変換
//
// url がスレッドのURLで無い時はempty()が返る
// もしurlが移転前の旧ホストのものだったら対応するarticlebaseクラスに旧ホスト名を知らせる
//
// (例) url =  "http://www.hoge2ch.net/test/read.cgi/hogeboard/12345/12-15"のとき、
//
// 戻り値 : "http://www.hoge2ch.net/hogeboard/dat/12345.dat",  num_from = 12, num_to = 15
//
const std::string BoardBase::url_dat( const std::string& url, int& num_from, int& num_to, std::string& num_str )
{
    if( empty() ) return std::string();

    JDLIB::Regex regex;
    const size_t offset = 0;
    const bool icase = false;
    const bool newline = true;
    const bool usemigemo = false;
    const bool wchar = false;

    std::string id; // スレッドのID

#ifdef _DEBUG
    std::cout << "BoardBase::url_dat : url = " << url << std::endl;
#endif
    
    num_from = num_to = 0;

    // dat 型
    const std::string datpath = MISC::replace_str( url_datpath(), "?", "\\?" );
    const std::string query_dat = "^ *(http://.+" + datpath  + ")([1234567890]+" + get_ext() + ") *$";

    // read.cgi型
    const std::string cgipath = MISC::replace_str( url_readcgipath(), "?", "\\?" );
    const std::string query_cgi = "^ *(http://.+" + cgipath + ")([1234567890]+)/?r?(l50)?([1234567890]+)?(-)?([1234567890]+)?.*$";

#ifdef _DEBUG
    std::cout << "query_dat = " << query_dat << std::endl;
    std::cout << "query_cgi = " << query_cgi << std::endl;
#endif

    if( regex.exec( query_dat , url, offset, icase, newline, usemigemo, wchar ) ) id = regex.str( 2 );

    else if( regex.exec( query_cgi , url, offset, icase, newline, usemigemo, wchar ) ){

        id = regex.str( 2 ) + get_ext(); 

        if( !regex.str( 3 ).empty() ){ // l50
            num_from = 1;
            num_to = 50;
        }
        else{

            num_from = atoi( regex.str( 4 ).c_str() );
            num_to = atoi( regex.str( 6 ).c_str() );
        }

        if( num_from != 0 ){

            num_from = MAX( 1, num_from );

            // 12- みたいな場合はとりあえず大きい数字を入れとく
            if( !regex.str( 5 ).empty() && !num_to ) num_to = MAX_RESNUMBER + 1;
        }

        // -15 みたいな場合
        else if( num_to != 0 ) num_from = 1;

        num_to = MAX( num_from, num_to );
        num_str = MISC::get_filename( url );
    }

    // どちらでもない(スレのURLでない)場合
    else{

        // 過去ログかどうか
        const std::string pathboard = MISC::replace_str( m_path_board, "?", "\\?" );
        const std::string query_kako = "^ *(http://.+)" + pathboard  + "/kako(/[1234567890]+)?/[1234567890]+/([1234567890]+).html *$";
#ifdef _DEBUG
        std::cout << "query_kako = " << query_kako << std::endl;
#endif
        if( regex.exec( query_kako , url, offset, icase, newline, usemigemo, wchar ) ){

            std::string url_tmp = regex.str( 1 ) + url_datpath() + regex.str( 3 ) + get_ext();
#ifdef _DEBUG
            std::cout << "kako log -> " << url_tmp << std::endl;
#endif
            return url_dat( url_tmp, num_from, num_to, num_str );
        }

        // 外部板の移転の場合は path_board も変わるときがあるので
        // 移転した場合はurlを置換してからもう一度試す
        std::string old_root;
        std::string old_path_board;
        std::string new_root;
        std::string new_path_board;
        std::string new_url = DBTREE::is_board_moved( url, old_root, old_path_board, new_root, new_path_board );

#ifdef _DEBUG
        std::cout << "old_root = " << old_root << " new_root = " << new_root << std::endl;
        std::cout << "old_path_board = " << old_path_board << " new_path_board = " << new_path_board << std::endl;
#endif

        if( ! new_url.empty() ){

            std::string url_tmp = MISC::replace_str( url, old_root, new_root );
            url_tmp = MISC::replace_str( url_tmp, old_path_board + "/", new_path_board + "/" );

#ifdef _DEBUG
            std::cout << "moved -> " << url_tmp << std::endl;
#endif
            return url_dat( url_tmp, num_from, num_to, num_str );
        }


#ifdef _DEBUG
        std::cout << "not found\n";
#endif
        return std::string();
    }        

    const std::string datbase = url_datbase();

#ifdef _DEBUG
    std::cout << "BoardBase::url_dat result : " << url << " ->\n";
    std::cout << "datbase = " << datbase << " id = " << id << " from " << num_from << " to " << num_to
              << " num = " << num_str << std::endl;
#endif

    // もしurl(スレッドのURL)が移転前の旧URLのものだったら対応するarticlebaseクラスに旧ホスト名を教えてあげる
    // ( offlaw による dat落ちスレの読み込み時に使用する )
    if( m_root.find( MISC::get_hostname( url ) ) != 0 ){
#ifdef _DEBUG
        std::cout << "org_host : " << MISC::get_hostname( url ) << std::endl;
#endif
        get_article_create( datbase, id )->set_org_host( MISC::get_hostname( url ) );
    }

    return datbase + id;
}



//
// スレの url を read.cgi型のurlに変換
//
// url がスレッドのURLで無い時はempty()が返る
// num_from と num_to が 0 で無い時はスレ番号を付ける
//
// (例) "http://www.hoge2ch.net/hogeboard/dat/12345.dat",  num_from = 12, num_to = 15 のとき
//
// 戻り値 : "http://www.hoge2ch.net/test/read.cgi/hogeboard/12345/12-15"
//
const std::string BoardBase::url_readcgi( const std::string& url, int num_from, int num_to )
{
    if( empty() ) return std::string();

#ifdef _DEBUG
    std::cout << "BoardBase::url_readcgi : " << url  << " from " << num_from << " to " << num_to << std::endl;
#endif

    ArticleBase* article = get_article_fromURL( url );
    if( !article ) return std::string();
    if( article->empty() ) return std::string();

    std::string readcgi = url_readcgibase() + article->get_key() + "/";

#ifdef _DEBUG
    std::cout << "BoardBase::::url_readcgi : to " << readcgi << std::endl;
#endif

    if( num_from > 0 || num_to > 0 ){

        std::ostringstream ss;
        ss << readcgi;

        if( num_from ) ss << num_from;
        if( num_to > num_from ) ss << "-" << num_to;
        return ss.str();
    }

    return readcgi;
}



//
// subject.txt の URLを取得
//
// (例) "http://www.hoge2ch.net/hogeboard/subject.txt"
//
const std::string BoardBase::url_subject()
{
    if( empty() ) return std::string();

    return url_boardbase() + m_subjecttxt;
}



//
//  ルートアドレス
//
// (例) "http://www.hoge2ch.net/"  (最後に '/' がつく)
//
const std::string BoardBase::url_root()
{
    if( empty() ) return std::string();

    return m_root + "/";
}


//
//  板のベースアドレス
//
// (例) "http://www.hoge2ch.net/hogeboard/"  (最後に '/' がつく)
//
const std::string BoardBase::url_boardbase()
{
    if( empty() ) return std::string();

    return m_root + m_path_board + "/";
}


//
// dat ファイルのURLのベースアドレスを返す
//
// (例) "http://www.hoge2ch.net/hogeboard/dat/" (最後に '/' がつく)
//
const std::string BoardBase::url_datbase()
{
    if( empty() ) return std::string();

    return m_root + url_datpath();
}


//
// dat ファイルのURLのパスを返す
//
// (例) "/hogeboard/dat/"  (最初と最後に '/' がつく)
//
const std::string BoardBase::url_datpath()
{
    if( empty() ) return std::string();

    return m_path_board + m_path_dat + "/";
}



//
// read.cgi のURLのベースアドレスを返す
//
// (例) "http://www.hoge2ch.net/test/read.cgi/hogeboard/"  (最後に '/' がつく)
//
const std::string BoardBase::url_readcgibase()
{
    if( empty() ) return std::string();

    return m_root + url_readcgipath();
}


//
// read.cgi のURLのパスを返す
//
// (例) "/test/read.cgi/hogeboard/"   (最初と最後に '/' がつく)
//
const std::string BoardBase::url_readcgipath()
{
    if( empty() ) return std::string();

    return m_path_readcgi + m_path_board + "/";
}


//
// bbscgi のURLのベースアドレス
//
// (例) "http://www.hoge2ch.net/test/bbs.cgi/" ( 最後に '/' がつく )
//
//
const std::string BoardBase::url_bbscgibase()
{
    if( empty() ) return std::string();

    return m_root + m_path_bbscgi + "/";
}


//
// subbbscgi のURLのベースアドレス
//
// (例) "http://www.hoge2ch.net/test/subbbs.cgi/"  ( 最後に '/' がつく )
//
const std::string BoardBase::url_subbbscgibase()
{
    if( empty() ) return std::string();

    return m_root + m_path_subbbscgi + "/";
}



//
// article のID を渡してハッシュから article のポインタを検索して返すだけ
//
// 無ければNULLクラスを返す
//
ArticleBase* BoardBase::get_article( const std::string& datbase, const std::string& id )
{
    if( id.empty() ) return get_article_null();

    // キャッシュにあるレスをデータベースに登録
    append_all_article_in_cache();

    ArticleBase* art = m_hash_article->find( datbase, id );
    if( art ) return art;

    return get_article_null();
}



//
// articleの IDを渡して ハッシュから article のポインタを検索して返す
//
// ポインタがあった場合は情報ファイルを読み込む
// さらにデータベースにArticleBaseクラスが登録されてない場合はクラスを作成して登録する
//
ArticleBase* BoardBase::get_article_create( const std::string& datbase, const std::string& id )
{
#ifdef _DEBUG
    std::cout << "BoardBase::get_article_create id = " << id << std::endl;
#endif

    ArticleBase* art = get_article( datbase, id );

    // データベースに無いので新規登録
    // get_article() の中で append_all_article_in_cache() を
    // 呼び出しているので、スレがキャッシュ内にない場合 art->empty() == TRUE になる
    if( art->empty() ){

#ifdef _DEBUG        
        std::cout << "BoardBase::get_article_create : append_article id =  " << id << std::endl;
#endif
        art = append_article( datbase, id,
                              false // キャッシュ無し
            );

        assert( art );
    }

    if( art->is_cached() ) art->read_info();

    return art;
}




//
// article の URL を渡してハッシュから　article のポインタを検索して返す
//
// さらにデータベースにArticleBaseクラスが登録されてない場合はクラスを作成して登録する
//
ArticleBase* BoardBase::get_article_fromURL( const std::string& url )
{
    if( empty() ) return get_article_null();

    // キャッシュ
    if( url == m_get_article_url ){

#ifdef _TEST_CACHE
        ++cache_hit_art;
#endif
        return m_get_article;
    }

    m_get_article_url = url;
    m_get_article = get_article_null();

#ifdef _DEBUG
    std::cout << "BoardBase::get_article_fromURL url = " << url << std::endl;
#endif

    // dat型のURLにしてdatbaseとidを取得
    int num_from, num_to;
    std::string num_str;
    const std::string urldat = url_dat( url, num_from, num_to, num_str );
    if( urldat.empty() ) return m_get_article;

    const std::string datbase = url_datbase();
    const std::string id = urldat.substr( datbase.length() );
    if( id.empty() ) return m_get_article;

#ifdef _DEBUG
    std::cout << "datbase = " << datbase << std::endl
              << "id = " << id << std::endl;
#endif

#ifdef _TEST_CACHE
    ++cache_nohit_art;
#endif

    // get_article_create() 経由で ArticleBase::read_info() から get_article_fromURL()が
    // 再帰呼び出しされることもあるので m_get_article_url を空にしておく
    m_get_article_url = std::string(); 

    m_get_article = get_article_create( datbase, id );
    m_get_article_url = url;
    return m_get_article;
}



//
// subject.txt ダウンロード
//
// url_update_view : CORE::core_set_command( "update_board" ) を送信するビューのアドレス
// read_from_cache : まだスレ一覧を開いていないときにキャッシュのsubject.txtを読み込む
//
void BoardBase::download_subject( const std::string& url_update_view, const bool read_from_cache )
{
#ifdef _DEBUG
    std::cout << "BoardBase::download_subject " << url_subject() << std::endl
              << "url_update_view = " << url_update_view << std::endl
              << "read_from_cache = " << read_from_cache << std::endl
              << "empty = " << empty() << std::endl
              << "loading = " << is_loading() << std::endl
              << "views = " << m_url_update_views.size() << std::endl;
#endif

    // ダウンロード中に他のビューから再びダウンロード依頼が来たら
    // ダウンロード終了時にまとめてビューにupdateコマンドを送る
    if( ! url_update_view.empty() ) m_url_update_views.push_back( url_update_view );
    if( m_url_update_views.size() >= 2 ) return;

    if( empty() ) return;
    if( is_loading() ) return;
    if( read_from_cache && m_list_subject_created ) return;

    clear();
    m_read_url_boardbase = false;

    if( read_from_cache ) m_is_online = false;
    else m_is_online = SESSION::is_online();

    m_is_booting = SESSION::is_booting();

    // オフライン
    if( ! m_is_online  ){

        // まだスレ一覧を開いていないときはディスパッチャを通さないで直接subject.txtを読み込む
        if( read_from_cache ) receive_finish();

        // 一度でもスレ一覧を開いている場合はディスパッチャ経由で receive_finish() を呼び出す
        else{

            // HTTP コードは Loadable::callback_dispatch() の中で HTTP_INIT にセットされる
            set_str_code( "" );

            finish();
        }

        return;
    }

    // オンライン

    // subject.txtのキャッシュが無かったら modified をリセット
    std::string path_subject = CACHE::path_board_root_fast( url_boardbase() ) + m_subjecttxt;
    if( CACHE::file_exists( path_subject ) != CACHE::EXIST_FILE ) set_date_modified( std::string() );

    JDLIB::LOADERDATA data;    
    create_loaderdata( data );
    if( ! start_load( data ) ){
        send_update_board();
        clear();
    }
}



//
// ロード用データ作成
//
void BoardBase::create_loaderdata( JDLIB::LOADERDATA& data )
{
    data.url = url_subject();
    data.agent = get_agent();
    data.host_proxy = get_proxy_host();
    data.port_proxy = get_proxy_port();
    data.basicauth_proxy = get_proxy_basicauth();
    data.size_buf = CONFIG::get_loader_bufsize_board();
    data.timeout = CONFIG::get_loader_timeout();
    data.basicauth = get_basicauth();
    data.modified = get_date_modified();
}


//
// ローダよりsubject.txt受信
//
void BoardBase::receive_data( const char* data, size_t size )
{
    if( ! size ) return;

    if( ! m_rawdata ) m_rawdata = ( char* )malloc( SIZE_OF_RAWDATA );

    memcpy( m_rawdata + m_lng_rawdata , data, size );
    m_lng_rawdata += size;
    m_rawdata[ m_lng_rawdata ] = '\0';

    if( m_read_url_boardbase ) return; // url_boardbase をロードして移転が起きたかチェック中


    //
    // 改行ごとに区切ってUTF8に文字コード変換して解析
    //

    if( ! m_rawdata_left ) m_rawdata_left = ( char* )malloc( SIZE_OF_RAWDATA );
    if( ! m_iconv ) m_iconv = new JDLIB::Iconv( m_charset, "UTF-8" );

    memcpy( m_rawdata_left + m_lng_rawdata_left, data, size );

    size_t byte_in = size + m_lng_rawdata_left;
    m_lng_rawdata_left = byte_in;
    while( byte_in && m_rawdata_left[ byte_in -1 ] != '\n' ) --byte_in;
    if( byte_in ){

        int byte_out;
        const char* rawdata_utf8 = m_iconv->convert( m_rawdata_left,  byte_in,  byte_out );

        parse_subject( rawdata_utf8 );

        // 残りを先頭に移動
        m_lng_rawdata_left -= byte_in;
        memmove( m_rawdata_left, m_rawdata_left + byte_in, m_lng_rawdata_left );

#ifdef _DEBUG
        std::cout << "BoardBase::receive_data lng_rawdata = " << m_lng_rawdata << " size = " << size
                  << " byte_in = " << byte_in << " byte_out = " << byte_out << " lng_rawdata_left = " << m_lng_rawdata_left << std::endl;
#endif
    }
}


//
// ロード完了
//
void BoardBase::receive_finish()
{
    // 別スレッドでローカルルールとSETTING.TXT のダウンロード開始
    if( m_is_online ) download_rule_setting();

    m_list_subject.clear();
    m_list_abone_thread_remove.clear();
    m_status &= ~STATUS_UPDATED;

    bool read_from_cache = false;
    const std::string path_subject = CACHE::path_board_root_fast( url_boardbase() ) + m_subjecttxt;
    const std::string path_oldsubject = CACHE::path_board_root_fast( url_boardbase() ) + "old-" + m_subjecttxt;

#ifdef _DEBUG
    std::cout << "----------------------------------\nBoardBase::receive_finish code = " << get_str_code() << std::endl;
    std::cout << "lng_rawdata = " << m_lng_rawdata << std::endl;
#endif

    ///////////////////////////////////////////////////////
    //
    // url_boardbase をロードして移転が起きたかチェック
    if( m_read_url_boardbase ){

#ifdef _DEBUG
        std::cout << "move check\n";
#endif
        set_date_modified( std::string() );
        send_update_board();

        if( m_lng_rawdata && get_code() == HTTP_OK && std::string( m_rawdata ).find( "window.location.href" ) != std::string::npos ){

#ifdef _DEBUG
            std::cout << m_rawdata << std::endl;
#endif
            JDLIB::Regex regex;
            const size_t offset = 0;
            const bool icase = false;
            const bool newline = true;
            const bool usemigemo = false;
            const bool wchar = false;

            std::string query = ".*window.location.href=\"([^\"]*)\".*";
            if( regex.exec( query, m_rawdata, offset, icase, newline, usemigemo, wchar ) ){

                const std::string new_url = regex.str( 1 );
                int ret = Gtk::RESPONSE_YES;

                if( CONFIG::get_show_movediag() ){

                    const std::string msg = "「" + get_name() + "」は\n\n" + new_url + " に移転しました。\n\nデータベースを更新しますか？";

                    SKELETON::MsgCheckDiag mdiag( NULL,
                                                  msg,
                                                  "今後表示しない(常に更新)(_D)",
                                                  Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO );

                    mdiag.set_title( "更新確認" );
                    ret = mdiag.run();
                    if( ret == Gtk::RESPONSE_YES && mdiag.get_chkbutton().get_active() ) CONFIG::set_show_movediag( false );
                }

                if( ret == Gtk::RESPONSE_YES ){

                    if( DBTREE::move_board( url_boardbase(), new_url ) ){

                        // 再読み込み
                        const std::string str_tab = "false";
                        CORE::core_set_command( "open_board", url_subject(), str_tab );
                    }
                }
            }
        }
        else{
            SKELETON::MsgDiag mdiag( NULL, "移転しました\n\n板一覧を更新しますか？", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO );
            mdiag.set_default_response( Gtk::RESPONSE_YES );
            if( mdiag.run() == Gtk::RESPONSE_YES ) CORE::core_set_command( "reload_bbsmenu" );
        }

        clear();
        return;
    }
    // 移転チェック終わり
    // 
    ///////////////////////////////////////////////////////


    // サーバーからtimeoutなどのエラーが返った or 移転
    if( get_code() != HTTP_ERR // HTTP_ERR はローダでの内部のエラー
        && get_code() != HTTP_OK
        && get_code() != HTTP_NOT_MODIFIED ){

        //
        // リダイレクト(302)の場合は移転確認
        //
        // ちなみにdatの読み込みでリダイレクト(302)が返ってきたときは、移転かdat落ちか判断出来ないので注意
        // NodeTree2ch::receive_finish()も参照せよ
        //
        if( get_code() == HTTP_REDIRECT || get_code() == HTTP_MOVED_PERM ){

            set_date_modified( std::string() );

            if( start_checkking_if_board_moved() ) return;

            send_update_board();
            clear();
            return;
        }

        // サーバからエラーが返ったらキャッシュからデータをロード
        read_from_cache = true;
    }

    // ローダがエラーを返した 又は not modified 又はオフラインならキャッシュからデータをロード
    if( get_code() == HTTP_ERR
        || get_code() == HTTP_NOT_MODIFIED
        || ! m_is_online ) read_from_cache = true;

    // キャッシュから読み込み
    if( read_from_cache ){

#ifdef _DEBUG
        std::cout << "read from cache " << path_subject << std::endl;
#endif
        m_lng_rawdata = 0;
        m_lng_rawdata_left = 0;

        char* rawdata = ( char* )malloc( SIZE_OF_RAWDATA );
        size_t lng = CACHE::load_rawdata( path_subject, rawdata, SIZE_OF_RAWDATA );
        receive_data( rawdata, lng );
        free( rawdata );
    }

#ifdef _DEBUG
    std::cout << "size = " << m_list_artinfo.size() << " lng_rawdata = " << m_lng_rawdata << " left = " << m_lng_rawdata_left << std::endl;
#endif

    // データが無い
    if( ! m_list_artinfo.size() ){

        set_date_modified( std::string() );

        // 移転した可能性があるので url_boardbase をロードして解析
        if( m_is_online && get_code() == HTTP_OK ){
            if( start_checkking_if_board_moved() ) return;
        }

        send_update_board();
        clear();
        return;
    }

    //////////////////////////////
    // データベース更新
    // subject.txtを解析して現行スレだけsubjectリスト(m_list_subject)に加える

    // キャッシュにあるレスをデータベースに登録
    append_all_article_in_cache();

    // 一度全てのarticleをdat落ち状態にして subject.txt に
    // 含まれているものだけ regist_article()の中で通常状態にする
    if( m_is_online
        || m_is_booting // ブート中の時も状態を変えないと起動直後にスレ一覧を復元した時にdat落ちしたスレが表示されない
        ){  

        ArticleHashIterator it = m_hash_article->begin();
        for( ; it != m_hash_article->end(); ++it ){

            int status = ( *it )->get_status();
            status &= ~STATUS_NORMAL;
            status |= STATUS_OLD;
            ( *it )->set_status( status );
        }
    }

    m_is_booting = false;

    regist_article( m_is_online );

    // list_subject 更新
    if( ! m_list_subject.empty() ){

#ifdef _DEBUG
        std::cout << "list_subject was updated\n";
#endif

        if( m_is_online ){

            // 既読スレに更新があったかチェック
            std::vector< ArticleBase* >::iterator it_art;
            for( it_art = m_list_subject.begin(); it_art != m_list_subject.end(); ++it_art ){

                if( ( *it_art )->is_cached() && ( *it_art )->get_number() > ( *it_art )->get_number_load() ){

                    m_status |= STATUS_UPDATED;
                    break;
                }
            }

            show_updateicon( false );
        }

        // DAT落ちなどでsubject.txtに無いスレもsubjectリストに加える
        if( CONFIG::get_show_oldarticle() || m_show_oldlog ){

            ArticleHashIterator it = m_hash_article->begin();
            for( ; it != m_hash_article->end(); ++it ){

                if( ( *it )->is_cached()
                    && ( ( *it )->get_status() & STATUS_OLD )
                    ){


                    // サブジェクトやロード数などの情報が無いのでスレのinfoファイルから
                    // 取得する必要がある
                    // TODO : 数が多いとboardビューを開くまで時間がかかるのをなんとかする
#ifdef _DEBUG
                    std::cout << "read article_info : " << ( *it )->get_url() << std::endl;
#endif                
                    ( *it )->read_info();

                    if( ! is_abone_thread( *it ) ) m_list_subject.push_back( *it );
                }
            }
        }

        // オンライン、かつcodeが200か304なら最終アクセス時刻を更新
        if( m_is_online && ( get_code() == HTTP_OK || get_code() == HTTP_NOT_MODIFIED ) ){

            m_last_access_time = time( NULL );

#ifdef _DEBUG
            std::cout << "access time " << m_last_access_time << std::endl;
#endif
        }

        // オンライン、かつcodeが200なら情報を更新・保存して subject.txt をキャッシュに保存
        if( m_is_online && get_code() == HTTP_OK ){

            m_last_access_time = time( NULL );

#ifdef _DEBUG
            std::cout << "save info and subject.txt\n";
            std::cout << "rename " << path_subject << " " << path_oldsubject << std::endl;
            std::cout << "save " << path_subject << std::endl;    
#endif

            save_info();

            // subject.txtをキャッシュ
            if( CACHE::mkdir_boardroot( url_boardbase() ) ){

                // 古いファイルをrename
                if( CACHE::file_exists( path_subject ) == CACHE::EXIST_FILE ){
#ifdef _WIN32
                    // not overwrite by rename() on windows
                    if (CACHE::file_exists( path_oldsubject ) == CACHE::EXIST_FILE ){
                        remove( path_oldsubject.c_str() );
                    }
#endif
                    if( rename( path_subject.c_str(), path_oldsubject.c_str() ) != 0 ){
                        MISC::ERRMSG( "rename failed " + path_subject );
                    }
                }

                // subject.txt セーブ
                CACHE::save_rawdata( path_subject, m_rawdata, m_lng_rawdata );
            }
        }

        m_list_subject_created = true;

    } //  if( ! m_list_subject.empty() )

    // list_subject が更新されなかった
    else{

#ifdef _DEBUG
        std::cout << "list_subject was NOT updated\n";
#endif

        set_date_modified( std::string() );
    }

    // コアにデータベース更新を知らせる
    send_update_board();

    clear();
}


//
// url_boardbase をロードして移転したかどうか解析開始
//
// 移転するとコード200で
//
// <script language="javascript">window.location.href="http://hoge.2ch.net/hoge/"</script>
//
// の様なHTML本文が送られて来るので移転先が分かる
//
bool BoardBase::start_checkking_if_board_moved()
{
#ifdef _DEBUG
    std::cout << "BoardBase::start_checkking_if_board_moved " << url_boardbase() << std::endl;
#endif

    m_lng_rawdata = 0;

    JDLIB::LOADERDATA data;
    data.init_for_data();
    data.url = url_boardbase();

    if( start_load( data ) ){
        m_read_url_boardbase = true;
        return true;
    }

    return false;
}


//
// キャッシュのディレクトリ内にあるスレのファイル名のリストを取得
//
std::list< std::string > BoardBase::get_filelist_in_cache()
{
    std::list< std::string >list_out;
    if( empty() ) return list_out;

    std::list< std::string >list_file;
    std::string path_board_root = CACHE::path_board_root_fast( url_boardbase() );

    list_file = CACHE::get_filelist( path_board_root );
    if( ! list_file.size() ) return list_out;

    std::list< std::string >::iterator it = list_file.begin();
    for(; it != list_file.end(); ++it ){

        std::string& file = ( *it );
        if( is_valid( file ) ) list_out.push_back( file );
    }

    return list_out;
}


//
// キャッシュのディレクトリ内にあるスレのファイル名を取得してDBにすべてを登録
//
// 全てのスレのinfoファイルを読むと遅くなるのでこの段階では読まない(DBへの登録のみ)
//
// boardビューに一覧表示するためBoardBaseの派生クラスのparse_subject()を呼び出したり、
// BoardBase::get_article_fromURL()で参照されたときに初めてスレのinfoファイルを読み込む
//
void BoardBase::append_all_article_in_cache()
{
    if( m_append_articles ) return;
    m_append_articles = true;

    const std::string datbase = url_datbase();

#ifdef _DEBUG
    std::cout << "BoardBase::append_all_article_in_cache url = " << datbase << std::endl;
#endif
    
    std::list< std::string >list_file = get_filelist_in_cache();

    std::list< std::string >::iterator it = list_file.begin();
    for(; it != list_file.end(); ++it ){

#ifdef _DEBUG
        std::cout << "append id = " << ( *it ) << std::endl;
#endif

        // キャッシュあり( cached = true ) 指定でDBに登録
        // キャッシュに無いスレはsubject.txtを読み込んだ時に
        // 派生クラスのparse_subject()で登録する。
        append_article( datbase, ( *it ),
                        true  // キャッシュあり
            );
    }
}



//
// 配下の全articlebaseクラスのあぼーん状態の更新
//
void BoardBase::update_abone_all_article()
{
    ArticleHashIterator it = m_hash_article->begin();
    for( ; it != m_hash_article->end(); ++it ) ( *it )->update_abone();
}




//
// スレあぼーん判定
//
const bool BoardBase::is_abone_thread( ArticleBase* article )
{
    if( ! article ) return false;
    if( article->empty() ) return false;

    const int check_number = article->get_number_load() ? 0: ( m_abone_number_thread ? m_abone_number_thread : get_abone_number_global() );
    const int check_hour = article->get_number_load() ? 0: ( m_abone_hour_thread ? m_abone_hour_thread : CONFIG::get_abone_hour_thread() );
    const bool check_thread = ! m_list_abone_thread.empty();
    const bool check_word = ! m_list_abone_word_thread.empty();
    const bool check_regex = ! m_list_abone_regex_thread.empty();
    const bool check_word_global = ! CONFIG::get_list_abone_word_thread().empty();
    const bool check_regex_global = ! CONFIG::get_list_abone_regex_thread().empty();

    if( !check_number && !check_hour && !check_thread && !check_word && !check_regex && !check_word_global && !check_regex_global ) return false;

    JDLIB::Regex regex;
    const size_t offset = 0;
    const bool icase = false;
    const bool newline = true;
    const bool usemigemo = false;
    const bool wchar = false;

    // レスの数であぼーん
    if( check_number ) if( article->get_number() >= check_number ) return true;

    // スレ立てからの時間であぼーん
    if( check_hour ) if( article->get_hour() >= check_hour ) return true;
    
    // スレあぼーん
    if( check_thread ){
        std::list< std::string >::iterator it = m_list_abone_thread.begin();
        for( ; it != m_list_abone_thread.end(); ++it ){
            if( MISC::remove_space( article->get_subject() ) == MISC::remove_space(*it) ){

                // 対象スレがDat落ちした場合はあぼーんしなかったスレ名をリストから消去する
                // remove_old_abone_thread() も参照
                m_list_abone_thread_remove.push_back( *it );
                return true;
            }
        }
    }

    // ローカル NG word
    if( check_word ){
        std::list< std::string >::iterator it = m_list_abone_word_thread.begin();
        for( ; it != m_list_abone_word_thread.end(); ++it ){
            if( article->get_subject().find( *it ) != std::string::npos ) return true;
        }
    }

    // ローカル NG regex
    if( check_regex ){
        std::list< std::string >::iterator it = m_list_abone_regex_thread.begin();
        for( ; it != m_list_abone_regex_thread.end(); ++it ){
            if( regex.exec( *it, article->get_subject(), offset, icase, newline, usemigemo, wchar ) ) return true;
        }
    }

    // 全体 NG word
    if( check_word_global ){
        std::list< std::string >::iterator it = CONFIG::get_list_abone_word_thread().begin();
        for( ; it != CONFIG::get_list_abone_word_thread().end(); ++it ){
            if( article->get_subject().find( *it ) != std::string::npos ) return true;
        }
    }

    // 全体 NG regex
    if( check_regex_global ){
        std::list< std::string >::iterator it = CONFIG::get_list_abone_regex_thread().begin();
        for( ; it != CONFIG::get_list_abone_regex_thread().end(); ++it ){
            if( regex.exec( *it, article->get_subject(), offset, icase, newline, usemigemo, wchar ) ) return true;
        }
    }

    return false;
}


//
// subject.txtのロード後にdat落ちしたスレッドをスレあぼーんのリストから取り除く
//
// is_abone_thread() も参照せよ
//
void BoardBase::remove_old_abone_thread()
{
    if( m_cancel_remove_abone_thread ) return;
    if( CONFIG::get_remove_old_abone_thread() == 2 ) return;
    if( m_list_abone_thread.empty() ) return;
    if( m_list_abone_thread.size() == m_list_abone_thread_remove.size() ) return;

#ifdef _DEBUG
    std::cout << "BoardBase::remove_old_abone_thread\n";
    std::list< std::string >::const_iterator it = m_list_abone_thread.begin();
    for( ; it != m_list_abone_thread.end(); ++it ) std::cout << ( *it ) << std::endl;
    std::cout << "->\n";
    std::list< std::string >::const_iterator it2 = m_list_abone_thread_remove.begin();
    for( ; it2 != m_list_abone_thread_remove.end(); ++it2 ) std::cout << ( *it2 ) << std::endl;
#endif

    if( CONFIG::get_remove_old_abone_thread() == 1 ){
        m_list_abone_thread = m_list_abone_thread_remove;
        return;
    }

    SKELETON::MsgCheckDiag mdiag( NULL,
                             "NGスレタイトルに登録したスレがdat落ちしました。\n\nNGスレタイトルから除外しますか？\n後で板のプロパティから削除する事も可能です。",
                             "今後表示しない(_D)",
                                  Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_NONE );

    mdiag.add_default_button( Gtk::Stock::NO, Gtk::RESPONSE_NO );
    mdiag.add_button( Gtk::Stock::YES, Gtk::RESPONSE_YES );

    const int ret = mdiag.run();
    if( ret == Gtk::RESPONSE_YES ) m_list_abone_thread = m_list_abone_thread_remove;

    // 一度いいえを選択したらあとは再起動するまでダイアログを表示しない
    else m_cancel_remove_abone_thread = true; 

    if( mdiag.get_chkbutton().get_active() ){

        if( ret == Gtk::RESPONSE_YES ) CONFIG::set_remove_old_abone_thread( 1 );
        else CONFIG::set_remove_old_abone_thread( 2 );
    }
}


//
// スレあぼーん情報を更新した時に対応するスレ一覧の表示を更新する
//
// CONFIG::set_abone_number_thread() などでグローバル設定をした後などに呼び出す
//
// redraw : スレ一覧の表示更新を行う
//
void BoardBase::update_abone_thread( const bool redraw )
{
#ifdef _DEBUG
    std::cout << "BoardBase::update_abone_thread\n";
#endif

    // まだスレ一覧にスレを表示していないBoardBaseクラスは更新しない
    if( ! m_list_subject_created ) return;

    // オフラインに切り替えてからキャッシュにあるsubject.txtを再読み込みする
    const bool online = SESSION::is_online();
    SESSION::set_online( false );

    download_subject( ( redraw ? url_subject() : std::string() ), false );

    SESSION::set_online( online );
}



//
// 板レベルでのあぼーん状態のリセット(情報セットとスレビューの表示更新を同時におこなう)
//
void BoardBase::reset_abone_board( const std::list< std::string >& ids,
                                   const std::list< std::string >& names,
                                   const std::list< std::string >& words,
                                   const std::list< std::string >& regexs )
{
    if( empty() ) return;

    // 前後の空白と空白行を除く

    m_list_abone_id = MISC::remove_space_from_list( ids );
    m_list_abone_id = MISC::remove_nullline_from_list( m_list_abone_id );

    m_list_abone_name = MISC::remove_space_from_list( names );
    m_list_abone_name = MISC::remove_nullline_from_list( m_list_abone_name );

    m_list_abone_word = MISC::remove_space_from_list( words );
    m_list_abone_word = MISC::remove_nullline_from_list( m_list_abone_word );

    m_list_abone_regex = MISC::remove_space_from_list( regexs );
    m_list_abone_regex = MISC::remove_nullline_from_list( m_list_abone_regex );

    update_abone_all_article();
    CORE::core_set_command( "relayout_all_article" );
}


// 板レベルでのあぼ〜ん状態更新(reset_abone()と違って各項目ごと個別におこなう。スレビューの表示更新も同時におこなう)
void BoardBase::add_abone_id_board( const std::string& id )
{
    if( empty() ) return;
    if( id.empty() ) return;

    std::string id_tmp = id.substr( strlen( PROTO_ID ) );

    m_list_abone_id.push_back( id_tmp );

    update_abone_all_article();
    CORE::core_set_command( "relayout_all_article" );
}

void BoardBase::add_abone_name_board( const std::string& name )
{
    if( empty() ) return;
    if( name.empty() ) return;

    m_list_abone_name.push_back( name );

    update_abone_all_article();
    CORE::core_set_command( "relayout_all_article" );
}

void BoardBase::add_abone_word_board( const std::string& word )
{
    if( empty() ) return;
    if( word.empty() ) return;

    m_list_abone_word.push_back( word );

    update_abone_all_article();
    CORE::core_set_command( "relayout_all_article" );
}


//
// スレあぼーん状態のリセット
//
// redraw : スレ一覧の表示更新を行う
//
void BoardBase::reset_abone_thread( const std::list< std::string >& threads,
                                    const std::list< std::string >& words,
                                    const std::list< std::string >& regexs,
                                    const int number,
                                    const int hour,
                                    const bool redraw
    )
{
    if( empty() ) return;

#ifdef _DEBUG
    std::cout << "BoardBase::reset_abone_thread\n";
#endif

    // 前後の空白と空白行を除く

    m_list_abone_thread = MISC::remove_space_from_list( threads );
    m_list_abone_thread = MISC::remove_nullline_from_list( m_list_abone_thread );

    m_list_abone_word_thread = MISC::remove_space_from_list( words );
    m_list_abone_word_thread = MISC::remove_nullline_from_list( m_list_abone_word_thread );

    m_list_abone_regex_thread = MISC::remove_space_from_list( regexs );
    m_list_abone_regex_thread = MISC::remove_nullline_from_list( m_list_abone_regex_thread );

    m_abone_number_thread = number;
    m_abone_hour_thread = hour;

    update_abone_thread( redraw );
}


//
// 読み込み用ローカルプロキシ設定
//
void BoardBase::set_local_proxy( const std::string& proxy )
{
    m_local_proxy = proxy;
    m_local_proxy_basicauth = std::string();
    if( proxy.empty() ) return;

    // basic認証
    JDLIB::Regex regex;
    const size_t offset = 0;
    const bool icase = false;
    const bool newline = true;
    const bool usemigemo = false;
    const bool wchar = false;

    if( regex.exec( "([^/]+:[^/]+@)(.+)$" , proxy, offset, icase, newline, usemigemo, wchar ) )
    {
        m_local_proxy_basicauth = regex.str( 1 ).substr( 0, regex.str( 1 ).length() - 1 );
        m_local_proxy = regex.str( 2 );
    }
}


//
// 書き込み用ローカルプロキシ設定
//
void BoardBase::set_local_proxy_w( const std::string& proxy )
{
    m_local_proxy_w = proxy;
    m_local_proxy_basicauth_w = std::string();
    if( proxy.empty() ) return;

    // basic認証
    JDLIB::Regex regex;
    const size_t offset = 0;
    const bool icase = false;
    const bool newline = true;
    const bool usemigemo = false;
    const bool wchar = false;

    if( regex.exec( "([^/]+:[^/]+@)(.+)$" , proxy, offset, icase, newline, usemigemo, wchar ) )
    {
        m_local_proxy_basicauth_w = regex.str( 1 ).substr( 0, regex.str( 1 ).length() - 1 );
        m_local_proxy_w = regex.str( 2 );
    }
}


//
// キャッシュ内のログ検索
//
// ArticleBase のアドレスをリスト(list_article)にセットして返す
// query が空の時はキャッシュにあるログを全てヒットさせる
// bm がtrueの時、しおりが付いている(スレ一覧でしおりを付けた or レスに一つでもしおりが付いている)スレのみを対象に検索する
//
void BoardBase::search_cache( std::vector< DBTREE::ArticleBase* >& list_article,
                              const std::string& query,
                              const bool mode_or, // 今のところ無視
                              const bool bm,
                              const bool& stop // 呼出元のスレッドで true にセットすると検索を停止する
    )
{
#ifdef _DEBUG
    std::cout << "BoardBase::search_cache " << url_subject() << std::endl;
#endif

    if( empty() ) return;

    // キャッシュにあるレスをデータベースに登録
    append_all_article_in_cache();
    if( m_hash_article->size() == 0 ) return;

    const bool append_all = query.empty();
    const std::string query_local = MISC::Iconv( query, "UTF-8", get_charset() );
    const std::list< std::string > list_query = MISC::split_line( query_local );

    const std::string path_board_root = CACHE::path_board_root_fast( url_boardbase() );

    ArticleHashIterator it = m_hash_article->begin();
    for( ; it != m_hash_article->end(); ++it ){

        if( stop ) break;

        ArticleBase* article = ( *it );
        if( ! article->is_cached() ) continue;

        // しおりがついているスレだけ追加
        if( bm ){
            article->read_info();
            if( ! article->is_bookmarked_thread() && ! article->get_num_bookmark() ) continue;
        }

        // 全て追加
        if( append_all ){

            article->read_info();

#ifdef _DEBUG
            std::cout << "append " << article->get_subject() << " bm = " << article->get_num_bookmark() << std::endl;
#endif
            list_article.push_back( article );
            continue;
        }

        const std::string path = path_board_root + article->get_id();
        std::string rawdata;

        if( CACHE::load_rawdata( path, rawdata ) > 0 ){

            bool apnd = true;

#ifdef _DEBUG
            std::cout << "load " << path << " size = " << rawdata.size() << " byte" << std::endl;
#endif

            std::list< std::string >::const_iterator it_query = list_query.begin();
            for( ; it_query != list_query.end(); ++it_query ){

                // 今の所 AND だけ対応
                if( rawdata.find( *it_query ) == std::string::npos ){
                    apnd = false;
                    break;
                }
            }

            if( apnd ){

                article->read_info();

#ifdef _DEBUG
                std::cout << "found word in " << url_readcgi( article->get_url(), 0, 0 ) << std::endl
                          << article->get_subject() << std::endl;
#endif
                list_article.push_back( article );
            }
        }
    }
}


// datファイルのインポート
// 成功したらdat型のurlを返す
const std::string BoardBase::import_dat( const std::string& filename )
{
    if( empty() ) return std::string();
    if( CACHE::file_exists( filename ) != CACHE::EXIST_FILE ){
        SKELETON::MsgDiag mdiag( NULL, "datファイルが存在しません" );
        mdiag.run();
        return std::string();
    }

    const std::string dir = MISC::get_dir( filename );
    const std::string id = MISC::get_filename( filename );
    if( id.empty() || ! is_valid( id ) ){
        SKELETON::MsgDiag mdiag( NULL, "ファイル名が正しくありません" );
        mdiag.run();
        return std::string();
    }

    const std::string file_to = CACHE::path_board_root_fast( url_boardbase() ) + id;

#ifdef _DEBUG
    std::cout << "BoardBase::import_dat cp " << filename
              << " " << file_to << std::endl;
#endif

    const std::string datbase = url_datbase();
    ArticleBase* art = get_article_create( datbase, id );
    if( ! art->is_cached() ){

        CACHE::jdcopy( filename, file_to );
        if( CACHE::file_exists( file_to ) == CACHE::EXIST_FILE ){
            art->set_cached( true );
            art->read_info();
        }
        else{
            SKELETON::MsgDiag mdiag( NULL, "datファイルのコピーに失敗しました" );
            mdiag.run();
            return std::string();
        }
    }

    return art->get_url();
}


//
// board.info( jd用 ) を読む
// BoardBase::read_info() も参照すること
//
void BoardBase::read_board_info()
{
    if( empty() ) return;
    
    std::string path_info = CACHE::path_jdboard_info( url_boardbase() );

#ifdef _DEBUG
    std::cout << "BoardBase::read_board_info " << path_info << std::endl;
#endif    

    JDLIB::ConfLoader cf( path_info, std::string() );

    std::string modified = cf.get_option_str( "modified", "" );
    set_date_modified( modified );

    m_modified_localrule = cf.get_option_str( "modified_localrule", std::string() );
    m_modified_setting = cf.get_option_str( "modified_setting", std::string() );

    m_view_sort_column = cf.get_option_int( "view_sort_column", -1, -1, COL_VISIBLE_END-1 );
    m_view_sort_mode = cf.get_option_int( "view_sort_mode", SORTMODE_ASCEND, 0, SORTMODE_NUM -1 );
    m_view_sort_pre_column = cf.get_option_int( "view_sort_pre_column", -1, -1, COL_VISIBLE_END-1 );
    m_view_sort_pre_mode = cf.get_option_int( "view_sort_pre_mode", SORTMODE_ASCEND, 0, SORTMODE_NUM -1 );

    m_check_noname = cf.get_option_bool( "check_noname", false );

    m_show_oldlog = cf.get_option_bool( "show_oldlog", false );

    std::string str_tmp;

    // あぼーん id は再起動ごとにリセット
//    str_tmp = cf.get_option( "aboneid", std::string() );
//    if( ! str_tmp.empty() ) m_list_abone_id = MISC::strtolist( str_tmp );

    // あぼーん name
    str_tmp = cf.get_option_str( "abonename", "" );
    if( ! str_tmp.empty() ) m_list_abone_name = MISC::strtolist( str_tmp );

    // あぼーん word
    str_tmp = cf.get_option_str( "aboneword", "" );
    if( ! str_tmp.empty() ) m_list_abone_word = MISC::strtolist( str_tmp );

    // あぼーん regex
    str_tmp = cf.get_option_str( "aboneregex", "" );
    if( ! str_tmp.empty() ) m_list_abone_regex = MISC::strtolist( str_tmp );

    // スレ あぼーん
    str_tmp = cf.get_option_str( "abonethread", "" );
    if( ! str_tmp.empty() ) m_list_abone_thread = MISC::strtolist( str_tmp );

    // スレ あぼーん word
    str_tmp = cf.get_option_str( "abonewordthread", "" );
    if( ! str_tmp.empty() ) m_list_abone_word_thread = MISC::strtolist( str_tmp );

    // スレ あぼーん regex
    str_tmp = cf.get_option_str( "aboneregexthread", "" );
    if( ! str_tmp.empty() ) m_list_abone_regex_thread = MISC::strtolist( str_tmp );

    // レス数であぼーん
    m_abone_number_thread = cf.get_option_int( "abonenumberthread", 0, 0, 9999 );

    // スレ立てからの経過時間であぼーん
    m_abone_hour_thread = cf.get_option_int( "abonehourthread", 0, 0, 9999 );

    // ローカルプロキシ
    m_mode_local_proxy = cf.get_option_int( "mode_local_proxy", PROXY_GLOBAL, 0, PROXY_NUM -1 );
    str_tmp = cf.get_option_str( "local_proxy", "" );
    set_local_proxy( str_tmp );
    m_local_proxy_port = cf.get_option_int( "local_proxy_port", 8080, 1, 65535 );
 
    m_mode_local_proxy_w = cf.get_option_int( "mode_local_proxy_w", PROXY_GLOBAL, 0, PROXY_NUM -1 );
    str_tmp = cf.get_option_str( "local_proxy_w", "" );
    set_local_proxy_w( str_tmp );
    m_local_proxy_port_w = cf.get_option_int( "local_proxy_port_w", 8080, 1, 65535 );

    // 書き込み時のデフォルトの名前とメアド
    m_write_name = cf.get_option_str( "write_name", "" );
    m_write_mail = cf.get_option_str( "write_mail", "" );

    // samba24
    m_samba_sec = cf.get_option_int( "samba_sec", 0, 0, 65535 );

    // 実況の秒数
    m_live_sec = cf.get_option_int( "live_sec", 0, 0, 65535 );

    // 最終アクセス時刻
    m_last_access_time = cf.get_option_int( "last_access_time", 0, 0, 2147483647 );

    // ステータス
    m_status = cf.get_option_int( "status", STATUS_UNKNOWN, 0, 8192 );

    // 最大レス数
    m_number_max_res = cf.get_option_int( "max_res", get_default_number_max_res(), 0, MAX_RESNUMBER );

#ifdef _DEBUG
    std::cout << "modified = " << get_date_modified() << std::endl;
#endif
}


//
// 情報保存
//
void BoardBase::save_info()
{
    if( empty() ) return;

    if( ! CACHE::mkdir_boardroot( url_boardbase() ) ) return;

    save_jdboard_info();
    save_summary();
    save_board_info();
}
    


//
// board.info( jd用 ) セーブ
//
void BoardBase::save_jdboard_info()
{
    std::string path_info = CACHE::path_jdboard_info( url_boardbase() );

#ifdef _DEBUG
    std::cout << "BoardBase::save_jdboard_info file = " << path_info << std::endl;
#endif

    // あぼーん情報
    std::string str_abone_id = MISC::listtostr( m_list_abone_id );
    std::string str_abone_name = MISC::listtostr( m_list_abone_name );
    std::string str_abone_word = MISC::listtostr( m_list_abone_word );
    std::string str_abone_regex = MISC::listtostr( m_list_abone_regex );

    // スレあぼーん情報
    std::string str_abone_thread = MISC::listtostr( m_list_abone_thread );
    std::string str_abone_word_thread = MISC::listtostr( m_list_abone_word_thread );
    std::string str_abone_regex_thread = MISC::listtostr( m_list_abone_regex_thread );

    // ローカルプロキシ
    std::string local_proxy;
    if( m_local_proxy_basicauth.empty() ) local_proxy = m_local_proxy;
    else local_proxy = m_local_proxy_basicauth + "@" + m_local_proxy;

    std::string local_proxy_w;
    if( m_local_proxy_basicauth_w.empty() ) local_proxy_w = m_local_proxy_w;
    else local_proxy_w = m_local_proxy_basicauth_w + "@" + m_local_proxy_w;
    
    std::ostringstream sstr;
    sstr << "modified = " << get_date_modified() << std::endl
         << "modified_localrule = " << m_modified_localrule << std::endl
         << "modified_setting = " << m_modified_setting << std::endl
         << "view_sort_column = " << m_view_sort_column << std::endl
         << "view_sort_mode = " << m_view_sort_mode << std::endl
         << "view_sort_pre_column = " << m_view_sort_pre_column << std::endl
         << "view_sort_pre_mode = " << m_view_sort_pre_mode << std::endl
         << "check_noname = " << m_check_noname << std::endl
         << "show_oldlog = " << m_show_oldlog << std::endl

    // IDは再起動ごとにリセット
//         << "aboneid = " << str_abone_id << std::endl
         << "abonename = " << str_abone_name << std::endl
         << "aboneword = " << str_abone_word << std::endl
         << "aboneregex = " << str_abone_regex << std::endl

         << "abonethread = " << str_abone_thread << std::endl
         << "abonewordthread = " << str_abone_word_thread << std::endl
         << "aboneregexthread = " << str_abone_regex_thread << std::endl
         << "abonenumberthread = " << m_abone_number_thread << std::endl
         << "abonehourthread = " << m_abone_hour_thread << std::endl
         << "mode_local_proxy = " << m_mode_local_proxy << std::endl

         << "local_proxy = " << local_proxy << std::endl
         << "local_proxy_port = " << m_local_proxy_port << std::endl
         << "mode_local_proxy_w = " << m_mode_local_proxy_w << std::endl
         << "local_proxy_w = " << local_proxy_w << std::endl
         << "local_proxy_port_w = " << m_local_proxy_port_w << std::endl
         << "write_name = " << m_write_name << std::endl
         << "write_mail = " << m_write_mail << std::endl
         << "samba_sec = " << m_samba_sec << std::endl
         << "live_sec = " << m_live_sec << std::endl
         << "last_access_time = " << m_last_access_time << std::endl
         << "status = " << m_status << std::endl
         << "max_res = " << m_number_max_res << std::endl
    ;

    CACHE::save_rawdata( path_info, sstr.str() );
}



//
// article-summary( navi2ch互換 )保存
//
// navi2chとの互換性のために保存しているだけで article-summary の情報は使わない
//
void BoardBase::save_summary()
{
    std::string path_summary = CACHE::path_article_summary( url_boardbase() );

#ifdef _DEBUG
    std::cout << "BoardBase::save_summary file = " << path_summary << std::endl;
#endif

    int count = 0;
    std::ostringstream sstr_out;
    sstr_out << "(";

    std::vector< ArticleBase* >::iterator it;
    for( it = m_list_subject.begin(); it != m_list_subject.end(); ++it ){

        ArticleBase* art = *( it );
        if( art->is_cached()
            && ( art->get_status() & STATUS_NORMAL ) ){
            if( count ) sstr_out << " ";
            ++count;

            // key
            sstr_out << "(\"" << art->get_key() << "\"";

            // 読んだ位置
            sstr_out << " :seen " << art->get_number_seen();

            // access-time
            sstr_out << " :access-time (" << art->get_access_time_str() << "))";
        }
    }

    sstr_out << ")";

#ifdef _DEBUG
//    std::cout << sstr_out.str() << std::endl;
#endif

    if( count ) CACHE::save_rawdata( path_summary, sstr_out.str() );
}



//
// board.info( navi2ch 互換 ) セーブ
//
// navi2chとの互換性のために保存しているだけで情報は使わない
// 
void BoardBase::save_board_info()
{
    std::string path_info = CACHE::path_board_info( url_boardbase() );

#ifdef _DEBUG
    std::cout << "BoardBase::save_board_info file = " << path_info << std::endl;
#endif
    
    // time だけ更新する(他の情報は使わない)
    std::string bookmark = "nil";
    std::string hide = "nil";
    std::string time = "(time . \"" + get_date_modified() + "\")";
    std::string logo = "nil";

    // board.info 読み込み
    if( CACHE::file_exists( path_info ) == CACHE::EXIST_FILE ){

        std::string str_info;
        CACHE::load_rawdata( path_info, str_info );
#ifdef _DEBUG
        std::cout << "str_info " << str_info << std::endl;
#endif

        std::list< std::string > lists = MISC::get_elisp_lists( str_info );
        std::list< std::string >::iterator it = lists.begin();
        for( ; it != lists.end(); ++it ){
            if( ( *it ).find( "bookmark" ) != std::string::npos ) bookmark = *it;
            if( ( *it ).find( "hide" ) != std::string::npos ) hide = *it;
            if( ( *it ).find( "logo" ) != std::string::npos ) logo = *it;
        }
    }

    std::string str_out = "(" + bookmark + " " + hide + " " + time + " " + logo + ")";

#ifdef _DEBUG
    std::cout << "info = " << str_out << std::endl;
#endif

    CACHE::save_rawdata( path_info, str_out );
}


// 更新可能状態にしてお気に入りやスレ一覧のタブのアイコンに更新マークを表示
// update == true の時に表示。falseなら戻す
void BoardBase::show_updateicon( const bool update )
{
#ifdef _DEBUG
    std::cout << "BoardBase::show_updateicon url = " << url_boardbase()
              << " update = " << update << " status = " << ( m_status & STATUS_UPDATE ) << std::endl;
#endif

    if( update ){

        if( ! ( m_status & STATUS_UPDATE ) ){

#ifdef _DEBUG
            std::cout << "toggle_icon on\n";
#endif

            m_status |= STATUS_UPDATE;

            // スレ一覧のタブのアイコン表示を更新
            CORE::core_set_command( "toggle_board_icon", url_subject() );

            // サイドバーのアイコン表示を更新
            CORE::core_set_command( "toggle_sidebar_boardicon", url_datbase() );

            save_info();
        }
    }
    else{

        if( m_status & STATUS_UPDATE ){

#ifdef _DEBUG
            std::cout << "toggle_icon off\n";
#endif

            m_status &= ~STATUS_UPDATE;

            // サイドバーのアイコン表示を戻す
            // スレ一覧のタブのアイコンはBoardViewがロード終了時に自動的に戻す
            CORE::core_set_command( "toggle_sidebar_boardicon", url_subject() );

            save_info();
        }
    }
}


// 板の更新チェック時に、更新チェックを行うスレのアドレスのリスト
// キャッシュが存在し、かつdat落ちしていないで新着数が0のスレを速度の順でソートして返す
const std::list< std::string > BoardBase::get_check_update_articles()
{
    std::list< std::string > list_url;

    if( empty() ) return list_url;
    if( is_loading() ) return list_url;
    if( m_status & STATUS_UPDATE ) return list_url;
    if( ! m_list_subject_created ) download_subject( std::string(), true );
    if( ! m_list_subject_created ) return list_url;

#ifdef _DEBUG
    std::cout << "BoardBase::get_check_update_articles url = " << url_boardbase() << std::endl;
#endif

    std::list< int > list_speed;

    ArticleHashIterator it = m_hash_article->begin();
    for( ; it != m_hash_article->end(); ++it ){

        if( ( *it )->is_cached()
            && ( *it )->get_number()
            && ! ( ( *it )->get_status() & STATUS_UPDATE )
            && ! ( ( *it )->get_status() & STATUS_OLD )
            && ( *it )->get_number() == ( *it )->get_number_load()
            ){

            const std::string& url = ( *it )->get_url();
            const int speed = ( *it )->get_speed();

#ifdef _DEBUG
            std::cout << "added " << url
                      << " number = " << ( *it )->get_number()
                      << " load = " << ( *it )->get_number_load()
                      << " speed = " << speed
                      << " " << ( *it )->get_subject() << std::endl;
#endif

            // 挿入ソート
            std::list< std::string >::iterator it_url = list_url.begin();
            std::list< int >::iterator it_speed = list_speed.begin();
            for( ; it_url != list_url.end(); ++it_url, ++it_speed ) if( ( *it_speed ) < speed ) break;
            list_url.insert( it_url, url );
            list_speed.insert( it_speed, speed );
        }
    }

#ifdef _DEBUG
    std::cout << "result of insert sorting\n";
    std::list< std::string >::const_iterator it_url = list_url.begin();
    std::list< int >::const_iterator it_speed = list_speed.begin();
    for( ; it_url != list_url.end(); ++it_url, ++it_speed ) std::cout << ( *it_speed ) << " / " << ( *it_url ) << std::endl;
#endif 

    return list_url;
}
