// ライセンス: GPL2

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

#include "articlebase.h"
#include "nodetreebase.h"
#include "interface.h"

#include "skeleton/msgdiag.h"

#include "jdlib/miscutil.h"
#include "jdlib/misctime.h"
#include "jdlib/miscmsg.h"
#include "jdlib/jdregex.h"

#include "config/globalconf.h"

#include "httpcode.h"
#include "command.h"
#include "cache.h"
#include "global.h"
#include "login2ch.h"
#include "session.h"
#include "updatemanager.h"

#include <sstream>


using namespace DBTREE;


// 情報ファイルのパスをセット
// デストラクタの中でCACHE::path_article_ext_info()などを呼ぶとabortするので
// メンバ変数としてパスを取得しておく
#define SET_INFOPATH() \
do{ \
m_path_article_info = CACHE::path_article_info( m_url, m_id ); \
m_path_article_ext_info = CACHE::path_article_ext_info( m_url, m_id ); \
} while( false )


// 情報ファイルから値を読み込み
// JDLIB::ConfLoader では遅いので別に作成。オプションの順番に注意すること
#define GET_INFOVALUE(target,targetname) \
do { \
target = std::string(); \
option = targetname; \
it2 = it; \
while( it2 != lines.end() && ( *it2 ).find( option ) != 0 ) ++it2; \
if( it2 != lines.end() ){ \
    target = ( *it2 ).substr( option.length() ); \
    it = ++it2; \
} } while( false )




ArticleBase::ArticleBase( const std::string& datbase, const std::string& id, bool cached )
    : SKELETON::Lockable(),
      m_id ( id ),
      m_key( std::string() ),
      m_since_time( 0 ),
      m_since_date( std::string() ),
      m_str_code( std::string() ),
      m_status( STATUS_UNKNOWN ),
      m_subject( std::string() ),
      m_number( 0 ),
      m_number_new( 0 ),
      m_number_load( 0 ),
      m_number_before_load( 0 ),
      m_number_seen( 0 ),
      m_number_max( MAX_RESNUMBER ),
      m_write_fixname( 0 ),
      m_write_fixmail( 0 ),
      m_abone_transparent( 0 ),
      m_abone_chain( 0 ),
      m_bookmarked_thread( false ),
      m_cached( cached ),
      m_read_info( 0 ),
      m_save_info( 0 ),
      m_enable_load( false )
{
#ifdef _DEBUG
    std::cout << "ArticleBase::ArticleBase : " << m_id << std::endl;
#endif

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

    // m_url にURLセット
    update_datbase( datbase );

    // この段階では移転前の旧ホスト名は分からないのでとりあえず現在のホスト名をセットしておく
    // あとで BoardBase::url_dat()でURLを変換する時に旧ホスト名を教えてもらってinfoファイルに保存しておく。
    // BoardBase::url_dat()も参照せよ
    m_org_host = MISC::get_hostname( m_url );
}


ArticleBase::~ArticleBase()
{
#ifdef _DEBUG
    std::cout << "ArticleBase::~ArticleBase : " << m_id << std::endl;
#endif

    // 参照ロックが外れていない
    assert( get_lock() == 0 );

    // nodetreeのクリアと情報保存
    unlock_impl();
}


const bool ArticleBase::empty()
{
    return  m_url.empty();
}


//
// 移転する前のオリジナルのURL
//
const std::string ArticleBase::get_org_url()
{
    std::string newhost = MISC::get_hostname( m_url );
    return m_org_host + m_url.substr( newhost.length() );
}


// スレ速度
const int ArticleBase::get_speed()
{
    time_t current_t = time( NULL );
    return ( get_number() * 60 * 60 * 24 ) / MAX( 1, current_t - get_since_time() );
}


// キャッシュにあるdatファイルのサイズ
const size_t ArticleBase::get_lng_dat()
{
    return get_nodetree()->get_lng_dat();
}



//
// nodetree の number 番のレスのヘッダノードのポインタを返す
//
NODE* ArticleBase::res_header( int number )
{
    return get_nodetree()->res_header( number );
}


//
// number番の名前
//
const std::string ArticleBase::get_name( int number )
{
    return get_nodetree()->get_name( number );
}


//
// number番の名前の重複数( = 発言数 )
//
int ArticleBase::get_num_name( int number )
{
    return get_nodetree()->get_num_name( number );
}


//
// 指定した発言者の名前のレス番号をリストにして取得
//
std::list< int > ArticleBase::get_res_name( const std::string& name )
{
    return get_nodetree()->get_res_name( name );
}


//
// number番のレスの時刻
// 内部で regex　を使っているので遅い
//
const std::string ArticleBase::get_time( int number )
{
    return get_nodetree()->get_time( number );
}


//
// number番の発言者ID
//
const std::string ArticleBase::get_id_name( int number )
{
    return get_nodetree()->get_id_name( number );
}


// 指定した発言者ID の重複数( = 発言数 )
// 下のnum_id_name( int number )と違って検索するので遅い
int ArticleBase::get_num_id_name( const std::string& id )
{
    return get_nodetree()->get_num_id_name( id );
}



// number番の発言者ID の重複数( = 発言数 )
int ArticleBase::get_num_id_name( int number )
{
    return get_nodetree()->get_num_id_name( number );
}


// 指定した発言者IDを持つレス番号をリストにして取得
std::list< int > ArticleBase::get_res_id_name( const std::string& id_name )
{
    return get_nodetree()->get_res_id_name( id_name );
}


// str_num で指定したレス番号をリストにして取得
// str_num は "from-to"　の形式 (例) 3から10をセットしたいなら "3-10"
// list_jointは出力で true のスレは前のスレに連結される (例) "3+4" なら 4が3に連結
std::list< int > ArticleBase::get_res_str_num( const std::string& str_num, std::list< bool >& list_joint )
{
    return get_nodetree()->get_res_str_num( str_num, list_joint );
}


// ブックマークをつけたレス番号をリストにして取得
std::list< int > ArticleBase::get_res_bm()
{
    std::list< int > list_resnum;          
    for( int i = 1; i <= m_number_load ; ++i ){
        if( is_bookmarked( i ) ) list_resnum.push_back( i );
    }

    // あぼーんしていてもリストから取り除かない
    return list_resnum;
}


//
// number番のレスを参照しているレス番号をリストにして取得
//
std::list< int > ArticleBase::get_res_reference( int number )
{
    return get_nodetree()->get_res_reference( number );
}


//
// URL を含むレス番号をリストにして取得
//
std::list< int > ArticleBase::get_res_with_url()
{
    return get_nodetree()->get_res_with_url();
}


//
// query を含むレス番号をリストにして取得
//
// mode_or == true なら OR抽出
//
std::list< int > ArticleBase::get_res_query( const std::string& query, bool mode_or )
{
    return get_nodetree()->get_res_query( query, mode_or );
}


//
// number番のレスの文字列を返す
// ref == true なら先頭に ">" を付ける        
//
const std::string ArticleBase::get_res_str( int number, bool ref )
{
    return get_nodetree()->get_res_str( number, ref );
}


//
// number　番のレスの生文字列を返す
//
const std::string ArticleBase::get_raw_res_str( int number )
{
    return get_nodetree()->get_raw_res_str( number );
}


//
// 更新時刻
//
time_t ArticleBase::get_time_modified()
{
    time_t time_out;
    time_out = MISC::datetotime( m_date_modified );
    if( time_out == 0 ) time_out = time( NULL ) - 600;
    return time_out; 
}



// スレが立ってからの経過時間( 時間 )
const int ArticleBase::get_hour()
{
    return ( time( NULL ) - get_since_time() ) / ( 60 * 60 );
}


//
// url の更新
//
// 移転があったときなどに上位クラスのboardbaseから呼ばれる
//
void ArticleBase::update_datbase( const std::string& datbase )
{
    if( m_id.empty() ) return;

#ifdef _DEBUG
    std::string old_url = m_url;
#endif

    // URL 更新
    m_url = datbase + m_id;

    // info ファイルのパスも更新
    if( ! m_path_article_info.empty() ) SET_INFOPATH();

#ifdef _DEBUG
    if( !old_url.empty() ) std::cout << "ArticleBase::update_datbase from "  << old_url
                                     << " to " << m_url << std::endl;
#endif

    if( m_nodetree ) m_nodetree->update_url( m_url );
}



//
// 移転前のオリジナルのホスト名をセット
//
void ArticleBase::set_org_host( const std::string& host )
{
    if( host != m_org_host ){

#ifdef _DEBUG
    std::cout << "ArticleBase::set_org_host : " << m_id << std::endl
              << "m_url = " << m_url << std::endl
              << "host = " << host << std::endl
              << "org_host = " << m_org_host << std::endl;
#endif

        m_org_host = host;
        m_save_info = true;
    }
}




//
// access_time を 文字列に変換して返す
//
const std::string ArticleBase::get_access_time_str()
{
    return MISC::timevaltostr( m_access_time );
}


void ArticleBase::reset_status()
{
#ifdef _DEBUG
    std::cout << "ArticleBase::reset_status\n";
#endif

    m_status = STATUS_UNKNOWN;
}


void ArticleBase::set_subject( const std::string& subject )
{
    if( subject.empty() ) return;

    // 特殊文字の置き換え
    if( subject.find( "&" ) != std::string::npos ){

        std::string subject_tmp = MISC::html_unescape( subject );

        if( subject_tmp != m_subject ){
            m_subject = subject_tmp;
            m_save_info = true;
        }
    }
    else if( subject != m_subject ){

        m_subject = subject;
        m_save_info = true;
    }
}


void ArticleBase::set_number( int number )
{
    if( number && number > m_number ){
        m_number = number;
        if( m_number_load < m_number ) m_enable_load = true;
    }
}


void ArticleBase::set_number_load( int number_load )
{
    if( number_load && number_load != m_number_load ) m_number_load = number_load;
}


void ArticleBase::set_number_seen( int number_seen )
{
    if( number_seen && number_seen != m_number_seen ){
        m_number_seen = number_seen;
        m_save_info = true;
    }
}

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

        m_write_time = tv;
        m_write_time_date = MISC::timettostr( m_write_time.tv_sec );

#ifdef _DEBUG
        std::cout << "ArticleBase::update_writetime : " << m_write_time.tv_sec << " " << m_write_time_date << std::endl;
#endif
        m_save_info = true;

        // BoardViewの行を更新
        CORE::core_set_command( "update_board_item", DBTREE::url_subject( m_url ), m_id );
    }

    // 板の書き込み時間も更新
    DBTREE::board_update_writetime( m_url );
}


//
// 経過時間(秒)
//
const time_t ArticleBase::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;
}


//
// スレ自体のスレ一覧でのブックマーク
//
void ArticleBase::set_bookmarked_thread( bool bookmarked )
{
    m_bookmarked_thread = bookmarked;
    save_info( true );

    // BoardViewの行を更新
    CORE::core_set_command( "update_board_item", DBTREE::url_subject( m_url ), m_id );
}


//
// キャッシュはあるが規定のレス数を越えていて、かつ全てのレスが既読
//
const bool ArticleBase::is_finished()
{
#ifdef _DEBUG
    if( get_number_seen() >= get_number_max() ) std::cout << is_cached() << " " << !enable_load() << " " << get_number_seen() << " " << get_subject() << std::endl;
#endif

    return ( is_cached() && ! enable_load() && get_number_seen() >= get_number_max() );
}


//
// 透明あぼーん
//
const bool ArticleBase::get_abone_transparent()
{
    if( CONFIG::get_abone_transparent() ) return true;

    return m_abone_transparent;
}


//
// 連鎖あぼーん
//
const bool ArticleBase::get_abone_chain()
{
    if( CONFIG::get_abone_chain() ) return true;

    return m_abone_chain;
}


//
// あぼーんしてるか
//
const bool ArticleBase::get_abone( int number )
{
    return get_nodetree()->get_abone( number );
}



//
// 全レスのあぼーん状態の更新
//
// あぼーん情報を変更したら呼び出す
//
// あぼーんしたスレの発言数や参照はカウントしないので、発言数や参照数も更新する
//
void ArticleBase::update_abone()
{
    // nodetreeが作られていないときは更新しない
    if( ! m_nodetree ) return;

    get_nodetree()->copy_abone_info( m_list_abone_id, m_list_abone_name, m_list_abone_word, m_list_abone_regex, m_vec_abone_res,
                                     m_abone_transparent, m_abone_chain );

    get_nodetree()->update_abone_all();
}



//
// あぼーん状態のリセット(情報セットと状態更新)
//
void ArticleBase::reset_abone( std::list< std::string >& ids, std::list< std::string >& names
                               ,std::list< std::string >& words, std::list< std::string >& regexs
                               ,std::vector< char >& vec_abone_res
                               ,bool transparent, bool chain )
{
    if( empty() ) return;

#ifdef _DEBUG
    std::cout << "ArticleBase::reset_abone\n";
#endif

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

    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 );

    if( vec_abone_res.size() ){

        if( ! m_vec_abone_res.size() ) m_vec_abone_res.resize( MAX_RESNUMBER );

        for( int i = 1; i <= MIN( m_number_load, (int)vec_abone_res.size() ) ; ++i ){
            if( vec_abone_res[ i ] ) m_vec_abone_res[ i ] = true;
            else m_vec_abone_res[ i ] = false;
        }
    }
    
    m_abone_transparent = transparent;
    m_abone_chain = chain;

    update_abone();

    m_save_info = true;
}



//
// あぼーんID追加
//
void ArticleBase::add_abone_id( const std::string& id )
{
    if( empty() ) return;
    if( id.empty() ) return;

#ifdef _DEBUG
    std::cout << "ArticleBase::add_abone_id : " << id << std::endl;
#endif    

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

    m_list_abone_id.push_back( id_tmp );

    update_abone();

    m_save_info = true;
}


//
// あぼーん名前追加
//
void ArticleBase::add_abone_name( const std::string& name )
{
    if( empty() ) return;
    if( name.empty() ) return;

#ifdef _DEBUG
    std::cout << "ArticleBase::add_abone_name : " << name << std::endl;
#endif    

    m_list_abone_name.push_back( name );

    update_abone();

    m_save_info = true;
}



//
// あぼーん文字列追加
//
void ArticleBase::add_abone_word( const std::string& word )
{
    if( empty() ) return;
    if( word.empty() ) return;

#ifdef _DEBUG
    std::cout << "ArticleBase::add_abone_word : " << word << std::endl;
#endif    

    m_list_abone_word.push_back( word );

    update_abone();

    m_save_info = true;
}


//
// レスあぼーんのセット
//
void ArticleBase::set_abone_res( const int number, const bool set )
{
    if( empty() ) return;
    if( number <= 0 || number > MAX_RESNUMBER ) return;

#ifdef _DEBUG
    std::cout << "ArticleBase::set_abone_res num = " << number << " set = " << set << std::endl;
#endif    

    if( ! m_vec_abone_res.size() ) m_vec_abone_res.resize( MAX_RESNUMBER );

    m_vec_abone_res[ number ] = set;

    update_abone();

    m_save_info = true;
}



//
// 透明あぼーん更新
//
void ArticleBase::set_abone_transparent( bool set )
{
    if( empty() ) return;

    m_abone_transparent = set;

    update_abone();

    m_save_info = true;
} 


//
// 連鎖あぼーん更新
//
void ArticleBase::set_abone_chain( bool set )
{
    if( empty() ) return;

    m_abone_chain = set;

    update_abone();

    m_save_info = true;
} 



//
// ブックマークの数
//
int ArticleBase::get_num_bookmark()
{
    if( ! m_vec_bookmark.size() ) return 0;

    int ret = 0;
    for( int i = 1; i < MAX_RESNUMBER; ++i ) if( is_bookmarked( i ) ) ++ret;
    return ret;
}


//
// ブックマークされているか
//
bool ArticleBase::is_bookmarked( int number )
{
    if( number <= 0 || number > m_number_load ) return false;

    // まだnodetreeが作られてなくてブックマークの情報が得られてないのでnodetreeを作って情報取得
    if( ! m_vec_bookmark.size() ) get_nodetree();

    if( ! m_vec_bookmark.size() ) return false;

    return ( m_vec_bookmark[ number ] );
}


//
// ブックマークセット
//
void ArticleBase::set_bookmark( int number, bool set )
{
    if( ! m_vec_bookmark.size() ) get_nodetree();
    if( number <= 0 || number > MAX_RESNUMBER ) return;

    if( ! m_vec_bookmark.size() ) m_vec_bookmark.resize( MAX_RESNUMBER );

    m_save_info = true;
    m_vec_bookmark[ number ] = set;
}




//
// NodeTree作成
//
// もしNodeTreeが作られていなかったらここでNewする
//
// this の参照が無くなったら ArticleBase::unlock_impl()が呼ばれて m_nodetree は自動クリアされる
//
JDLIB::ConstPtr< NodeTreeBase >& ArticleBase::get_nodetree()
{
    assert( !empty() );

    if( ! m_nodetree ){

#ifdef _DEBUG
        std::cout << "ArticleBase::get_nodetree create " << m_url << std::endl;
#endif
    
        m_nodetree = create_nodetree();
        assert( m_nodetree );

        // あぼーん情報のコピー
        m_nodetree->copy_abone_info( m_list_abone_id, m_list_abone_name, m_list_abone_word, m_list_abone_regex, m_vec_abone_res,
                                     m_abone_transparent, m_abone_chain );

        m_nodetree->sig_updated().connect( sigc::mem_fun( *this, &ArticleBase::slot_node_updated ) );
        m_nodetree->sig_finished().connect( sigc::mem_fun( *this, &ArticleBase::slot_load_finished ) );

        // キャッシュ読み込み
        m_number_load = 0; // 読み込み数リセット
        m_nodetree->load_cache();
    }
    
    return m_nodetree;
}



//
// this の参照ロックが外れたときに呼ばれる
//
// m_nodetree を deleteする
//
void ArticleBase::unlock_impl()
{
    if( !m_nodetree ) return;

#ifdef _DEBUG
    std::cout << "ArticleBase::unlock_impl  url = " << m_url << std::endl;
#endif

    m_nodetree.clear();

    // スレ情報保存
    save_info( false );
}





//
// ロード中か
//
const bool ArticleBase::is_loading()
{
    if( ! m_nodetree ) return false;
    return m_nodetree->is_loading();
}



//
// ロード停止
//
void ArticleBase::stop_load()
{
    if( ! m_nodetree ) return;
    m_nodetree->stop_load();
}



//
// スレッドのロード開始
//
// DAT落ちの場合はロードしないので、強制的にリロードしたいときは reset_status() で
// ステータスをリセットしてからロードする
//
// check_update == true の時はHEADによる更新チェックをおこなう
//
void ArticleBase::download_dat( const bool check_update )
{
    if( empty() ) return;

#ifdef _DEBUG
    std::cout << "ArticleBase::download_dat " << m_url << " status = " << m_status << " checkupdate = " << check_update << std::endl;
#endif

    struct timeval tv;
    struct timezone tz;
    if( gettimeofday( &tv, &tz ) != 0 ) tv.tv_sec = 0;

    if( check_update ){

        if( ! SESSION::is_online() ) return;

        // 一度更新チェックしたらしばらくは再チェックできないようにする
        time_t passed = 0;
        if( tv.tv_sec ) passed = MAX( 0, tv.tv_sec - m_check_update_time.tv_sec );

#ifdef _DEBUG
        std::cout << "check_update passed = " << passed << std::endl;
#endif

        if( passed <= CHECKUPDATE_MINSEC ) return;
    }

    if( SESSION::is_online() && tv.tv_sec ) m_check_update_time = tv;

    // DAT落ちしていてログイン中で無い時はロードしない
    if( ( m_status & STATUS_OLD ) && ! LOGIN::get_login2ch()->login_now() ){
        CORE::core_set_command( "toggle_favorite_icon", m_url );
        return;
    }

#ifdef _DEBUG
    std::cout << "start\n";
#endif       
    get_nodetree()->download_dat( check_update );
}




//
// ロード中など nodetree の構造が変わったときにnodetreeから呼ばれる slot
//
void ArticleBase::slot_node_updated()
{
    assert( m_nodetree );

    // 更新チェック中
    if( m_nodetree->is_checking_update() ) return;

#ifdef _DEBUG
    std::cout << "ArticleBase::slot_node_updated" << std::endl;
#endif

    // nodetreeから情報取得
    if( ! m_nodetree->get_subject().empty() ) set_subject( m_nodetree->get_subject() );

    // スレが更新している場合
    if( m_number_load != m_nodetree->get_res_number() ){

        // スレの読み込み数更新
        m_number_load = m_nodetree->get_res_number();

        // 対応するarticleビューを更新
        CORE::core_set_command( "update_article", m_url );
    }
}


//
// ロード終了後に nodetree から呼ばれる slot
// nodetree から情報を取得する
//
void ArticleBase::slot_load_finished()
{
    assert( m_nodetree );

#ifdef _DEBUG
    std::cout << "ArticleBase::slot_load_finished" << std::endl;
#endif

    slot_node_updated();

    // HTTPコード取得
    int old_code = m_code;
    m_code = m_nodetree->get_code();

    // 状態更新
    int old_status = m_status;
    if( m_code != HTTP_ERR ){

        // DAT落ち
        if( m_code == HTTP_REDIRECT || m_code == HTTP_NOT_FOUND ){
            m_status &= ~STATUS_NORMAL;
            m_status |= STATUS_OLD;
            CORE::core_set_command( "toggle_favorite_icon", m_url );
        }

        // 既にDAT落ち状態では無いときは通常状態にする
        else if( ! (m_status & STATUS_OLD ) ){
            m_status |= STATUS_NORMAL;
            m_status &= ~STATUS_OLD;
        }
    }

    // 壊れている
    if( m_nodetree->is_broken() ) m_status |= STATUS_BROKEN;

    // 状態が変わっていたら情報保存
    if( old_status != m_status ) m_save_info = true;

    // 更新チェック
    if( m_nodetree->is_checking_update() ){

#ifdef _DEBUG
        std::cout << "check_update code = " << m_code << std::endl;
#endif

        // スレタブとお気に入りのアイコンを更新
        if( m_code == HTTP_OK || m_code == HTTP_PARTIAL_CONTENT ){

            show_updateicon( true );
        }

        // code と modified を戻しておく
        m_code = old_code;
        m_nodetree->set_date_modified( m_date_modified );

        // 次のスレを更新チェック
        CORE::get_checkupdate_manager()->pop_front();

        return;
    }

    // nodetreeから情報取得
    m_str_code = m_nodetree->get_str_code();
    std::string m_old_modified = m_date_modified;
    m_date_modified = m_nodetree->date_modified();
    if( m_number_before_load < m_number_load ) m_number_new = m_number_load - m_number_before_load;
    else m_number_new = 0;
    m_number_before_load = m_number_load;
    m_ext_err = m_nodetree->get_ext_err();

    // スレの数が0ならスレ情報はセーブしない
    if( ! m_number_load ) m_cached = false;

    else{

        // スレが更新している場合はスレ情報を更新
        if( m_number_new
            || m_date_modified != m_old_modified // ときどき modified が誤って返るときがあるので最新の値を保存しておく
            ){

            struct timeval tv;
            struct timezone tz;
            if( gettimeofday( &tv, &tz ) == 0 ) m_access_time = tv;

            if( m_number < m_number_load ) m_number = m_number_load;
            m_number_seen = m_number_load;

            m_cached = true;
            m_read_info = true;
            m_save_info = true;
            m_enable_load = false;

            // 情報ファイルのパスをセット
            if( m_path_article_info.empty() ) SET_INFOPATH();

            show_updateicon( false );
        }
    }

#ifdef _DEBUG
    std::cout << "ArticleBase::slot_load_finished " << std::endl
              << "subject = " << m_subject << std::endl
              << "load = " << m_number_load << std::endl
              << "number = " << m_number << std::endl
              << "new = " << m_number_new << std::endl
              << "date = " << m_date_modified << std::endl
              << "access-time = " << get_access_time_str() << std::endl
              << "code = " << m_code << std::endl
              << "status = " << m_status << std::endl

    ;
#endif

    // 対応するBoardビューの行を更新
    CORE::core_set_command( "update_board_item", DBTREE::url_subject( m_url ), m_id );
    
    // articleビューに終了を知らせる
    CORE::core_set_command( "update_article", m_url );
    CORE::core_set_command( "update_article_finish", m_url );
}



//
// 更新アイコン表示
//
// updated == true の時に表示。falseなら戻す
//
void ArticleBase::show_updateicon( const bool update )
{
#ifdef _DEBUG
    std::cout << "ArticleBase::show_updateicon update = " << update << " status = " << m_status << std::endl;
#endif

    struct timeval tv;
    struct timezone tz;
    if( gettimeofday( &tv, &tz ) != 0 ) m_check_update_time = tv;

    if( update ){

        if( ! ( m_status & STATUS_UPDATE ) ){
            m_status |= STATUS_UPDATE;
            CORE::core_set_command( "toggle_article_icon", m_url);
            CORE::core_set_command( "toggle_favorite_icon", m_url );
        }
    }
    else{

        // お気に入りのアイコン表示を戻す。スレタブのアイコンの方はArticleViewが戻す
        if( m_status & STATUS_UPDATE ){
            m_status &= ~STATUS_UPDATE;
            CORE::core_set_command( "toggle_favorite_icon", m_url );
        }
    }
}


//
// キャッシュ削除
//
void ArticleBase::delete_cache( const bool cache_only )
{
#ifdef _DEBUG
    std::cout << "ArticleBase::delete_cache  url = " << m_url << std::endl;
#endif

    if( empty() ) return;

    if( ! cache_only && m_bookmarked_thread ){
        SKELETON::MsgDiag mdiag( NULL, "「" + get_subject() + "」にはしおりが付けられています。\n\nスレを削除しますか？\n\nしおりを解除するにはスレの上で右クリックしてしおり解除を選択してください。"
                                  ,false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO );
        mdiag.set_default_response( Gtk::RESPONSE_YES );
        if( mdiag.run() == Gtk::RESPONSE_NO ) return;
    }

    m_number_load = m_number_seen = m_number_before_load = 0;
    m_cached = false;
    m_status = STATUS_UNKNOWN;
    m_date_modified.clear();
    memset( &m_access_time, 0, sizeof( struct timeval ) );
    memset( &m_check_update_time, 0, sizeof( struct timeval ) );

    if( ! cache_only ){

        memset( &m_write_time, 0, sizeof( struct timeval ) );
        m_write_time_date.clear();

        m_write_name.clear();
        m_write_mail.clear();
        m_write_fixname = false;
        m_write_fixmail = false;

        m_vec_bookmark.clear();
        m_list_abone_id.clear();
        m_list_abone_name.clear();
        m_list_abone_word.clear();
        m_list_abone_regex.clear();
        m_vec_abone_res.clear();
        m_abone_transparent = false;
        m_abone_chain = false;
        m_read_info = false;
        m_save_info = false;
        m_enable_load = false;
        m_bookmarked_thread = false;
    
        // info
        if( CACHE::file_exists( m_path_article_info ) == CACHE::EXIST_FILE ) unlink( m_path_article_info.c_str() );

        // 拡張info
        if( CACHE::file_exists( m_path_article_ext_info ) == CACHE::EXIST_FILE ) unlink( m_path_article_ext_info.c_str() );

    }

    // キャッシュ
    std::string path_dat = CACHE::path_dat( m_url );
    if( CACHE::file_exists( path_dat ) == CACHE::EXIST_FILE ) unlink( path_dat.c_str() );

    // BoardViewの行を更新
    CORE::core_set_command( "update_board_item", DBTREE::url_subject( m_url ), m_id );

    // お気に入りのアイコン表示を戻す
    CORE::core_set_command( "toggle_favorite_icon", m_url );
}



//
// キャッシュを名前を付けて保存
//
// path_to はデフォルトのファイル名
//
bool ArticleBase::save_dat( const std::string& path_to )
{
    if( is_loading() ) return false;

    std::string dir = MISC::get_dir( path_to );
    if( dir.empty() ) dir = SESSION::dir_dat_save();

    std::string name = MISC::get_filename( path_to );
    if( name.empty() ) name = MISC::get_filename( m_url );    

    std::string save_to = CACHE::copy_file( NULL, CACHE::path_dat( m_url ), dir + name, CACHE::FILE_TYPE_DAT );

    if( ! save_to.empty() ){
        SESSION::set_dir_dat_save( MISC::get_dir( save_to ) );
        return true;
    }

    return false;
}



//
// infoファイル読み込み
//
// インスタンスが出来るたびに呼んでいると重くなるので、BoardBase::get_article_fromURL()
// で初めて参照されたときや、Boardビューに表示するときに一回だけ読み込む
//
void ArticleBase::read_info()
{
    if( m_read_info ) return; // 一度読んだら2度読みしない
    if( empty() ) return;
    if( ! m_cached ) return;  // キャッシュがないなら読まない

#ifdef _DEBUG
    std::cout << "ArticleBase::read_info :  url = " << m_url << std::endl;
#endif

    m_read_info = true;

    // 情報ファイルのパスをセット
    if( m_path_article_info.empty() ) SET_INFOPATH();

    int ret = CACHE::file_exists( m_path_article_ext_info );
    if( ret == CACHE::EXIST_FILE ){

        std::string str_info, str_tmp;
        std::list< std::string > list_tmp;
        std::list< std::string >::iterator it_tmp;
        CACHE::load_rawdata( m_path_article_ext_info, str_info );

        std::list< std::string > lines = MISC::get_lines( str_info );
        std::list < std::string >::iterator it = lines.begin(), it2;
        std::string option; // GET_INFOVALUE　で使用

        // subject
        GET_INFOVALUE( m_subject, "subject = " );

        // 旧ホスト名
        GET_INFOVALUE( m_org_host, "org_host = " );
        if( m_org_host.empty() ) m_org_host = MISC::get_hostname( m_url );

        // 取得数
        m_number_load = 0;
        GET_INFOVALUE( str_tmp, "load = " );
        if( ! str_tmp.empty() ) m_number_load = atoi( str_tmp.c_str() );
        m_number_before_load = m_number_load;

        // 見た場所
        m_number_seen = 0;
        GET_INFOVALUE( str_tmp, "seen = " );
        if( ! str_tmp.empty() ) m_number_seen = atoi( str_tmp.c_str() );

        // 更新時間 (time)
        GET_INFOVALUE( m_date_modified, "modified = " );

        // access time
        GET_INFOVALUE( str_tmp, "access = " );
        if( ! str_tmp.empty() ){
            list_tmp = MISC::split_line( str_tmp );
            if( list_tmp.size() == 3 ){
                it_tmp = list_tmp.begin();
                m_access_time.tv_sec = ( atoi( ( *(it_tmp++) ).c_str() ) << 16 ) + atoi( ( *(it_tmp++) ).c_str() );
                m_access_time.tv_usec = atoi( ( *(it_tmp++) ).c_str() );
            }
        }

        // write time
        GET_INFOVALUE( str_tmp, "writetime = " );
        if( ! str_tmp.empty() ){
            list_tmp = MISC::split_line( str_tmp );
            if( list_tmp.size() == 3 ){
                it_tmp = list_tmp.begin();
                m_write_time.tv_sec = ( atoi( ( *(it_tmp++) ).c_str() ) << 16 ) + atoi( ( *(it_tmp++) ).c_str() );
                m_write_time.tv_usec = atoi( ( *(it_tmp++) ).c_str() );
            }

            m_write_time_date = MISC::timettostr( m_write_time.tv_sec );
        }

        // write name
        m_write_name = std::string();
        GET_INFOVALUE( m_write_name, "writename = " );

        // write mail
        m_write_mail = std::string();
        GET_INFOVALUE( m_write_mail, "writemail = " );

        // name 固定
        m_write_fixname = false;
        GET_INFOVALUE( str_tmp, "writefixname = " );
        if( ! str_tmp.empty() ) m_write_fixname = atoi( str_tmp.c_str() );

        // mail 固定
        m_write_fixmail = false;
        GET_INFOVALUE( str_tmp, "writefixmail = " );
        if( ! str_tmp.empty() ) m_write_fixmail = atoi( str_tmp.c_str() );

        // 状態
        m_status = STATUS_UNKNOWN;
        GET_INFOVALUE( str_tmp, "status = " );
        if( ! str_tmp.empty() ) m_status = atoi( str_tmp.c_str() );
        m_status &= ~STATUS_UPDATE; // 更新可能状態解除

        // あぼーん ID
        GET_INFOVALUE( str_tmp, "aboneid = " );
        if( ! str_tmp.empty() ) m_list_abone_id = MISC::strtolist( str_tmp );

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

        // レスブックマーク
        GET_INFOVALUE( str_tmp, "bookmark = " );
        if( ! str_tmp.empty() ){

            if( ! m_vec_bookmark.size() ) m_vec_bookmark.resize( MAX_RESNUMBER );

            list_tmp = MISC::split_line( str_tmp );
            it_tmp = list_tmp.begin();
            for( ; it_tmp != list_tmp.end(); ++it_tmp ){
                int number = atoi( (*it_tmp).c_str() );
                if( !(*it_tmp).empty() ) m_vec_bookmark[ number ] = true;
                else m_vec_bookmark[ number ] = false;
            }
        }

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

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

        // 透明あぼーん
        m_abone_transparent = false;
        GET_INFOVALUE( str_tmp, "abonetrp = " );
        if( ! str_tmp.empty() ) m_abone_transparent = atoi( str_tmp.c_str() );

        // 連鎖あぼーん
        m_abone_chain = false;
        GET_INFOVALUE( str_tmp, "abonechain = " );
        if( ! str_tmp.empty() ) m_abone_chain = atoi( str_tmp.c_str() );

        // レス番号あぼーん
        m_vec_abone_res.clear();
        GET_INFOVALUE( str_tmp, "aboneres = " );
        if( ! str_tmp.empty() ){

            if( ! m_vec_abone_res.size() ) m_vec_abone_res.resize( MAX_RESNUMBER );

            list_tmp = MISC::split_line( str_tmp );
            it_tmp = list_tmp.begin();
            for( ; it_tmp != list_tmp.end(); ++it_tmp ){
                int number = atoi( (*it_tmp).c_str() );
                if( !(*it_tmp).empty() ) m_vec_abone_res[ number ] = true;
                else m_vec_abone_res[ number ] = false;
            }
        }

        // スレ一覧でブックマークされているか
        m_bookmarked_thread = false;
        GET_INFOVALUE( str_tmp, "bkmark_thread = " );
        if( ! str_tmp.empty() ) m_bookmarked_thread = atoi( str_tmp.c_str() );
    }

    // キャッシュはあるけど情報ファイルが無い場合
    // 一時的にnodetreeを作って情報を取得して保存
    else{

#ifdef _DEBUG
        std::cout << "ArticleBase::read_info : update info " << m_url << std::endl;
        std::cout << "load = " << m_number_load << " subject = " << m_subject << std::endl;
        std::cout << "ret = " << ret << std::endl;
        std::cout << "path = " << m_path_article_ext_info << std::endl;
#endif

        CORE::core_set_command( "set_status","", "スレ情報更新中・・・しばらくお待ち下さい" );
        MISC::MSG( "updating " + m_url );

        set_subject( get_nodetree()->get_subject() );
        m_number_load = get_nodetree()->get_res_number();

        if( !m_number_load ){
            m_number_load = 1;
            m_status |= STATUS_BROKEN;
            MISC::MSG( "updating failed" );
        }
        
        m_number_before_load = m_number_load;
        m_save_info = true;
        unlock_impl();

#ifdef _DEBUG
        std::cout << "\nArticleBase::read_info : update done.\n\n";
#endif
    }

    if( m_number < m_number_load ) m_number = m_number_load;

#ifdef _DEBUG
    std::cout << "ArticleBase::read_info file = " << m_path_article_ext_info << std::endl;

    std::cout << "subject = " << m_subject << std::endl
              << "org_host = " << m_org_host << std::endl
              << "load = " << m_number_load << std::endl
              << "seen = " << m_number_seen << std::endl
              << "modified = " << m_date_modified << std::endl
              << "writetime = " << m_write_time_date << std::endl
              << "writename = " << m_write_name << std::endl
              << "writemail = " << m_write_mail << std::endl
              << "writefixname = " << m_write_fixname << std::endl
              << "writefixmail = " << m_write_fixmail << std::endl
              << "status = " << m_status << std::endl
              << "transparent_abone = " << m_abone_transparent << std::endl
              << "bookmarked_thread = " << m_bookmarked_thread << std::endl
    ;

    std::cout << "abone-id\n"; std::list < std::string >::iterator it = m_list_abone_id.begin();
    for( ; it != m_list_abone_id.end(); ++it ) std::cout << (*it) << std::endl;
    std::cout << "abone-name\n"; it = m_list_abone_name.begin();
    for( ; it != m_list_abone_name.end(); ++it ) std::cout << (*it) << std::endl;
    std::cout << "abone-word\n"; it = m_list_abone_word.begin();
    for( ; it != m_list_abone_word.end(); ++it ) std::cout << (*it) << std::endl;
    std::cout << "abone-regex\n"; it = m_list_abone_regex.begin();
    for( ; it != m_list_abone_regex.end(); ++it ) std::cout << (*it) << std::endl;

    if( m_vec_abone_res.size() ){
        std::cout << "abone-res =";
        for( int i = 1; i <= m_number_load; ++i ) if( m_vec_abone_res[ i ] ) std::cout << " " << i;
        std::cout << std::endl;
    }

    if( m_vec_bookmark.size() ){
        std::cout << "bookmark =";
        for( int i = 1; i <= m_number_load; ++i ) if( m_vec_bookmark[ i ] ) std::cout << " " << i;
        std::cout << std::endl;
    }
#endif

}


//
// infoファイル書き込み
//
// キャッシュがある( m_cached = true )　かつ
// m_save_info = true の時に保存。save_info()を呼ぶ前にm_save_infoをセットすること。
//
// キャッシュがあって、force = true の時は強制書き込み
//
void ArticleBase::save_info( bool force )
{
    if( empty() ) return;
    if( ! m_cached ) return;
    if( ! force && ! m_save_info ) return;
    m_save_info = false;

    if( m_path_article_ext_info.empty() ) return;

#ifdef _DEBUG
    std::cout << "ArticleBase::save_info force = " << force << std::endl;
    std::cout << "path_article_info = " << m_path_article_info << std::endl;
    std::cout << "path_article_ext_info = " << m_path_article_ext_info << std::endl;
#endif

    // 書き込み時間
    std::ostringstream ss_write;
    ss_write << ( m_write_time.tv_sec >> 16 ) << " " << ( m_write_time.tv_sec & 0xffff ) << " " << m_write_time.tv_usec;

    // あぼーん情報
    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::ostringstream ss_abone_res;
    if( m_vec_abone_res.size() ){
        for( int i = 1; i <= m_number_load; ++i ) if( m_vec_abone_res[ i ] ) ss_abone_res << " " << i;
    }

    // レスのブックマーク
    std::ostringstream ss_bookmark;
    if( m_vec_bookmark.size() ){
        for( int i = 1; i <= m_number_load; ++i ) if( m_vec_bookmark[ i ] ) ss_bookmark << " " << i;
    }

    std::ostringstream sstr;
    sstr << "subject = " << m_subject << std::endl
         << "org_host = " << m_org_host << std::endl
         << "load = " << m_number_load << std::endl
         << "seen = " << m_number_seen << std::endl
         << "modified = " << m_date_modified << std::endl
         << "access = " << get_access_time_str() << std::endl
         << "writetime = " << ss_write.str() << std::endl
         << "writename = " << m_write_name << std::endl
         << "writemail = " << m_write_mail << std::endl
         << "writefixname = " << m_write_fixname << std::endl
         << "writefixmail = " << m_write_fixmail << std::endl
         << "status = " << m_status << std::endl
         << "aboneid = " << str_abone_id << std::endl
         << "abonename = " << str_abone_name << std::endl
         << "bookmark = " << ss_bookmark.str() << std::endl
         << "aboneword = " << str_abone_word << std::endl
         << "aboneregex = " << str_abone_regex << std::endl
         << "abonetrp = " << m_abone_transparent << std::endl
         << "abonechain = " << m_abone_chain << std::endl
         << "aboneres = " << ss_abone_res.str() << std::endl
         << "bkmark_thread = " << m_bookmarked_thread << std::endl
    ;

#ifdef _DEBUG
    std::cout << "ArticleBase::save_info file = " << m_path_article_ext_info << std::endl;
    std::cout << sstr.str() << std::endl;
#endif

    CACHE::save_rawdata( m_path_article_ext_info, sstr.str() );

    // 互換性のため
    save_navi2ch_info();
}



//
// navi2ch互換情報ファイル書き込み
//
// 互換性のため書き出すだけで実際にはこの中の情報は使わない
//
void ArticleBase::save_navi2ch_info()
{
    if( empty() ) return;
    if( ! m_cached ) return;
    if( m_path_article_info.empty() ) return;

    std::string name = "nil";
    std::string hide = "nil";
    std::string important = "nil";
    std::string unfilter = "nil";
    std::string mail = "nil";
    std::string kako = "nil";

    // 保存してあるinfoから扱ってない情報をコピー
    if( CACHE::file_exists( m_path_article_info ) == CACHE::EXIST_FILE ){

        std::string str_info;
        CACHE::load_rawdata( m_path_article_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();
        ++it;
        name = *( it++ );
        ++it;
        hide = *( it++ );
        important = *( it++ );
        unfilter = *( it++ );
        mail = *( it++ );
        kako = *( it++ );        
    }

    std::ostringstream sstr;
    sstr << "("
         << "(number . " << m_number_load << ")"
         << " " << name
         << " " << "(time . \"" << m_date_modified  << "\")"
         << " " << hide
         << " " << important
         << " " << unfilter
         << " " << mail
         << " " << kako
         << ")";

#ifdef _DEBUG
    std::cout << "ArticleBase::save_navi2ch_info file = " << m_path_article_info << std::endl;
    std::cout << sstr.str() << std::endl;
#endif

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