#!/usr/bin/perl -w
#
# gnview 2ch BBS Viewer
# http://gnview.sourceforge.jp/

#--------------------------------------
# ライブラリ読み込み
#--------------------------------------

use utf8;
use strict;
use Config;
BEGIN {
   $Config{useithreads} or die "Recompile perl with ithreads to use this program.";
}

use threads;
use threads::shared;
use Thread::Queue;

use POSIX qw/ locale_h strftime /;
use Encode qw/ encode from_to decode /;
use Encode::Guess qw/ euc-jp shiftjis /;
use Time::HiRes qw/ sleep gettimeofday tv_interval /;
use Time::Local;
use Fcntl qw/ :flock /;

use Gtk2 '-init';
if(Gtk2->CHECK_VERSION(2, 14, 0)) {
   # Gtk2のバージョンが2.14.0以上ならGtk2::Builderを使う・・・予定
   use Gtk2::GladeXML;
}else{
   # Gtk2のバージョンが2.14.0より下ならGtk2::GladeXMLを使う・・・予定
   use Gtk2::GladeXML;
}
use Gtk2::Pango;
use Glib::Object::Subclass Glib::Object::, interfaces => [ Gtk2::TreeModel::, Gtk2::TreeSortable:: ];

use HTML::TokeParser;
use HTML::Entities;
use HTTP::Date;
use Compress::Zlib;
use List::Util qw/ min /;

use LWP::UserAgent;
use LWP::MediaTypes qw/ guess_media_type /;
use HTTP::Cookies;

use Data::Dumper;

#--------------------------------------
# 全体設定
#--------------------------------------

use constant TRUE  => 1;
use constant FALSE => 0;

#--------------------------------------
# グローバル定数
#--------------------------------------
   
    # カーソル作成
    # スレのポップアップメニュー用
    our $gnCursor_Beam = Gtk2::Gdk::Cursor->new('xterm');
    our $gnCursor_Link = Gtk2::Gdk::Cursor->new('hand2');

    # 2ch用定数
    our $gnKeyTime = "1104688508";

    our $gnIdxFileVersion           = "1.01";       # よくわかんないけどFolder.idxのなかに出てくるので入れるようにした
                                                        # ギコナビ互換用
    our $gnGnname                   = "gnview";         # gnviewの名前
    our $gnVersion                  = "0.8";        # gnviewのバージョン
    our $gnUsrAgent : shared        = "Monazilla/1.00 \(${gnGnname}\/${gnVersion}\)";
                                                        # ユーザーエージェント指定

    # デバッグ関連
    our $gnDebugFlag : shared = 0;       # デバッグレベル指定(0 = まったく出力しない, 1-->大きくなるほど詳細な
                                            # デバッグ出力が行われる)
    our $gnDegugLevel_global = 1;       # 各モジュール側のデバッグレベル
                                            # 実際には  
                                            # $gnDebugLevel = $gnDegugLevel_global + $gnDegugLevel_module + $gnDegugLevel_function
                                            #  ^my変数           ^our変数                 ^モジュール全体のmy変数     ^各関数内で指定されるmy
                                            # で指定され、
                                            # $gnDebugFlag > $gnDebugLevel
                                            # の場合にデバッグ出力が行われる
    
    # 共有ファイルパス保管用
    my $LibPath                 = &gnGetLibPath;
    our $gnIconfn               = $LibPath . "\/${gnGnname}.png";               # アイコン
    our $gladefn                = $LibPath . "\/${gnGnname}.glade";             # GladeでデザインしたGUIのXMLファイルの場所
    our $gnEnvABimgfn           = $LibPath . "\/1pix.png";                  # 環境設定で使用される
    our $gnEnvABcenterimgfn     = $LibPath . "\/1pix_glay.png";         # 環境設定で使用される
    my $gnPrg_firstrun          = $LibPath . "\/${gnGnname}_firstrun.pl";   # gnview_firstrun.plの場所
    my $gnPrg_env               = $LibPath . "\/${gnGnname}_env.pl";            # gnview_env.plの場所
    my $gnPrg_lowlevel          = $LibPath . "\/${gnGnname}_lowlevel.pl";   # gnview_lowlevel.plの場所

#--------------------------------------
# 共有ライブラリ読み込み
#--------------------------------------

require($gnPrg_lowlevel);
require($gnPrg_firstrun);
require($gnPrg_env);

#--------------------------------------
# グローバル変数
#--------------------------------------

    # スレッド間共有
    our $hold : shared;
    our $gnImgGetSt : shared;
    our $queue : shared = Thread::Queue->new;
    our $fnsize : shared;

    # 設定ファイルパス保管用
    our $gnEnvFile;
    our $gnURLIniFile;
    our $gnSentIniFile;
    our $gnFavFile;

    our $gnLockFileN;                   # 多重起動抑止用ロックファイル
    our %gnEnvArg;                      # gikoNavi.ini保存配列
    our %gnURLIni;                      # url.iniの内容をハッシュ配列に格納
    our %gnInitCfg;                     # 初期設定保存配列
    our $gnCacheTblFile;

    our $boardcfg;                  # 板一覧設定ファイル
    our %gnBoardTbl;
    our $logdir;

    our $gnTmpfn;
    our $tmpdir = &gnGetTmpDir;     # テンポラリディレクトリを取得
    if($gnDebugFlag) { print "$tmpdir\n"; }

    # お気に入り管理用変数
    our $gnFavXML;                  # お気に入り保存用XMLへのリファレンス
    #our $gnFavMenu;                 # メインメニューのお気に入り(Gtk2::Menu)
    our $gnFavTreeModel;           # ツリービューのお気に入り(Gtk2::TreeModel)

    # 内部使用変数
    our %gnFH;                              # ファイルハンドル管理用ハッシュ配列

    our $gnHoverOverLink        = FALSE;    # カーソルがリンクの上にあるかのフラグ
    our $gnPopupText            = "";       # レス表示ウィンドウがポップアップしているかのフラグ
                                            #    ポップアップがなければNULL,
                                            #    ポップアップしていればGtk2::Windowが入っている
    our $gnPopupImg         = undef;        # 画像表示ウィンドウがポップアップしているかのフラグ
                                            # ポップアップがなければundef,
    our $gnPopupImgSt           = 1;
    our %gnImgPopup_info;                   # ポップアップ画像の情報を格納する配列
    our $gnPop2;                            # ポップアップしていればGtk2::Windowが入っている
    our $gnPopupImgBuf;                     # ポップアップ用画像バッファ
    our %gnCacheTbl;                        # 画像キャッシュテーブル保存配列

    our $gnItaLogDir = '';          # 板毎のログ格納ディレクトリ
    our @gnSureViewInfo;                # スレ表示管理用配列
                                        # n(偶数,0スタート): スレファイル名
    our $gnSureViewPageNum = 0;     # スレ表示用ページの最終番号
                                        # n+1(奇数,1スタート): Notebookタブの番号

    our $gnTimeforPopup = 0;            # ポップアップウィンドウ遅延表示用
    our $gnPopupTextCallback;
    our $gnPopupCallback;
    our $gnImgUpdateCallback;
    our $gnPopupCnt = 0;

    our $gnSuppressEventFlg = FALSE;    # コールバック関数を実行するかどうかのフラグ。
                                            # TRUEならコールバック時に何もしない
    our $gnSignalHandler;               # コールバック関数

    our $gnGUIxml;          # gladeで生成したXMLファイル格納用
    our $gnFRxml;               # gladeで生成したXMLファイル格納用
    our $gnWxml = "";           # gladeで生成したXMLファイル格納用
    our $gnIPxml;
    our $gnTPxml;
    our $gnISxml;
    our $gnEnvXML;
    our $gnEnvABXML;

    our $gnFind_Iter;
    our $gnFind_pg;
    our $gnFind_keyst = "";

#--------------------------------------
# 初期処理
#--------------------------------------
print STDERR "Starting $gnGnname $gnVersion\n";
my $gnDebugLevel = $gnDegugLevel_global;
  #____________________________________________________
  # 初回起動かどうかをチェック
  our $gnCfgPath = &gnGetPersonalCfgPath;
  if (!(&gnCheckPath($gnCfgPath . "\." . "${gnGnname}config"))) {
     &gnFirstRun($gladefn);
  }else{
     &gnInit;
  }
  #--------------------------------------
  # スレッドの開始
  my $thr = threads->new (\&command_runner);   #<--1000usec程度かかっている

  Gtk2->main;


#--------------------------------------
# 初回起動処理
#--------------------------------------

sub gnInit {

  $gnGUIxml = Gtk2::GladeXML->new($gladefn, 'mW');
  
  # ロックファイルをチェックし、多重起動を抑止する
  my $gnLockFile = $gnCfgPath . "\." . "${gnGnname}lock";
  if(&gnCheckPath($gnLockFile)) {
    if($gnDebugFlag) { print "Lockfile Detected\n"; };
    my $retval = &gnYesNo("多重起動防止用のロックファイル\($gnLockFile\)が見つかりました。\n" .
                              "既にgnviewが起動しているか、前回正しく終了しなかったかもしれません。\n" .
                              "ロックファイルを無視して強制的に起動しますか？");
    if ($retval eq "no") {
        print STDERR "$gnGnname exited by user interuption.\(Lockfile\($gnLockFile\) detected\)\n";
        exit(1);
    }
  }
  
  my $LFN = &gnOpenLocal($gnLockFile, "w");
  &gnCloseLocal($gnLockFile);

  # 初期設定ファイル(.gnconfig)読み込み
  print STDERR "Loading Configration File\n";
  my $gnCfgFile = $gnCfgPath . "\." . "${gnGnname}config";
  if (!(&gnCheckPath($gnCfgFile))) {
     if($gnDebugFlag) { print "gnCfgFileN load failed\n"; }
     &gnRestartInit;
  }
  my $gnInitCfgRef = &gnLoadEnv($gnCfgFile);
  %gnInitCfg = %$gnInitCfgRef;
  if($gnDebugFlag) { print "gnInit_LoadCfg=\n" . Dumper(%gnInitCfg); }
  
  $gnEnvFile = $gnInitCfg{'Main'}{'gikoNavi.ini'};
  if (!(&gnCheckPath($gnEnvFile))) {
     if($gnDebugFlag) { print "gnEnvFile load failed\n"; }
     &gnRestartInit;
  }
  $gnURLIniFile = $gnInitCfg{'Main'}{'url.ini'};
  if (!(&gnCheckPath($gnURLIniFile))) {
     if($gnDebugFlag) { print "gnURLIniFile load failed\n"; }
     &gnRestartInit;
  }
  $gnSentIniFile = $gnInitCfg{'Main'}{'sent.ini'};
  if (!(&gnCheckPath($gnSentIniFile))) {
     if($gnDebugFlag) { print "gnSentIniFile load failed\n"; }
     &gnRestartInit;
  }
  $gnCacheTblFile = $gnInitCfg{'Main'}{'cachetbl.ini'};
  if($gnDebugFlag) { print "cachetbl.ini\n".Dumper($gnCacheTblFile); }
  if ($gnCacheTblFile eq "") {
     if($gnDebugFlag) { print "gnCacheTblFile not defined\n"; }
     &gnRestartInit;
  }
  $logdir = $gnInitCfg{'Main'}{'LogFolder'} . "\/2ch\/";
  if($gnDebugFlag) { print "logdir\: " . $logdir . "\n"; }
  if (!(&gnCheckPath($logdir))) {
     if($gnDebugFlag) { print "logdir load failed\n"; }
     &gnRestartInit;
  }

  
  $gladefn = &gnGetLibPath . $gnGnname . ".glade";
  
  # 環境設定ファイル読み込み(gikoNavi.ini)
  if($gnDebugFlag) { print "gikonaviini=\n" . Dumper($gnEnvFile); }
  my $gnEnvArgRef = &gnLoadEnv($gnEnvFile);
  %gnEnvArg = %$gnEnvArgRef;
  $gnEnvArg{'Folder'}{'LogFolderUnix'} = $gnInitCfg{'Main'}{'LogFolder'};
  
  # お気に入りファイルをチェック
  my $gnFavFlag = 0;
  if(exists($gnEnvArg{'gnFavorite'}{'gnFavFile'}) && (&gnCheckPath($gnEnvArg{'gnFavorite'}{'gnFavFile'}) == 1)) {
     $gnFavFlag = 1;
  }
  if(!$gnFavFlag) {
    if(&gnCheckPath($gnEnvArg{'gnFavorite'}{'gnFavFile'}) != 1 ) {
        my $retval = &gnYesNo("お気に入りファイル\(Favorite.xml\)が見つかりません\nお気に入りファイルの場所を指定しますか？\n\nはい：ファイル選択ダイアログが開きます\nいいえ：お気に入りファイルを初期化します");
        if($retval eq "yes") {
           my $gnFileXML = Gtk2::GladeXML->new($main::gladefn, 'gnFileChoose');
           $gnFileXML->signal_autoconnect_from_package('main');
           my $gnFileChoose = $gnFileXML->get_widget('gnFileChoose');
           $gnFileChoose->set_title("Favorite.xmlの選択");
           my $gnFileFilter = Gtk2::FileFilter->new();
           $gnFileFilter->add_pattern("Favorite.xml");
           $gnFileChoose->add_filter($gnFileFilter);
           my $retval = $gnFileChoose->run;
           if($retval eq "ok") {
               $gnEnvArg{'gnFavorite'}{'gnFavFile'} = &env_gtkstr_to_gtkstr($gnFileChoose->get_filename);
               $gnFileChoose->destroy;
               $gnFileChoose = undef;
               $gnFavFlag = 1;
           }else{
               $gnFileChoose->destroy;
               $gnFileChoose = undef;
               my $retval2 = &gnYesNo("お気に入りファイルがなければgnviewを起動させることができません\nお気に入りファイルを初期化しますか？\n\nはい：お気に入りファイルを初期化\nいいえ：gnviewを終了");
               if($retval2 eq "yes") {
                   $retval = "no";
                }else{
                   delete($gnEnvArg{'gnFavorite'}{'gnFavFile'});
                   &gnExit;
                }
           }
        }else{
           if($gnDebugFlag) { print "Favorite.xml will be initializend.\n"; }
           my $gnfav_from = &gnGetLibPath . "Favorite.xml";
           my $retval2 = &gnFCopy($gnfav_from,
                                    substr($gnInitCfg{'Main'}{'gikoNavi.ini'}, 0, rindex($gnInitCfg{'Main'}{'gikoNavi.ini'}, "\/")+1));
           if($retval2) {
               $gnEnvArg{'gnFavorite'}{'gnFavFile'} = substr($gnInitCfg{'Main'}{'gikoNavi.ini'}, 0,
                                                          rindex($gnInitCfg{'Main'}{'gikoNavi.ini'}, "\/")+1) . "Favorite.xml";
               $gnFavFlag = 1;
           }else{
               &gnWarn("初期化に失敗しました\n端末からgnviewを起動している場合はエラーが出力されていますのでご確認ください\n\ngnviewを終了します");
               delete($gnEnvArg{'gnFavorite'}{'gnFavFile'});
               &gnExit;
           }
        }
    }
  }


  # gnview独自環境変数がセットされていなければシステム・デフォルトをセット
  if(!(exists($gnEnvArg{'Cache'}{'gnCacheFolder'}))) {
     $gnEnvArg{'Cache'}{'gnCacheFolder'} = $tmpdir;
  }
  if(!(exists($gnEnvArg{'Thread'}{'gnLinkColorAlreadyCached'}))) {
     $gnEnvArg{'Thread'}{'gnLinkColorAlreadyCached'} = 'blue';
  }
  if(!(exists($gnEnvArg{'Thread'}{'gnLinkColorNotCached'}))) {
     $gnEnvArg{'Thread'}{'gnLinkColorNotCached'} = 'blue';
  }
  if(!(exists($gnEnvArg{'Browser'}{'AvoidBorderLeft'}))) {
     $gnEnvArg{'Browser'}{'AvoidBorderLeft'} = 0;
     $gnEnvArg{'Browser'}{'AvoidBorderRight'} = 0;
     $gnEnvArg{'Browser'}{'AvoidBorderTop'} = 0;
     $gnEnvArg{'Browser'}{'AvoidBorderBottom'} = 0;
  }
  if(!(exists($gnEnvArg{'Browser'}{'gnBrowserFontUseCustom'}))) {
     $gnEnvArg{'Browser'}{'gnBrowserFontUseCustom'} = 0;
  }
  if(!(exists($gnEnvArg{'Browser'}{'gnWriteWithQuotation'}))) {
     $gnEnvArg{'Browser'}{'gnWriteWithQuotation'} = 0;
  }
  if(!(exists($gnEnvArg{'Browser'}{'gnPermitPopupExt'}))) {
     $gnEnvArg{'Browser'}{'gnPermitPopupExt'} = "jpg,gif,bmp,tif,png";
  }
  # 環境設定ファイル読み込み(url.ini)
  if($gnDebugFlag) { print "urliini=\n" . Dumper($gnURLIniFile); }
  my $gnUrlArgRef = &gnLoadEnv($gnURLIniFile);
  %gnURLIni = %$gnUrlArgRef;
  if($gnDebugFlag) { print "urlini=\n" . Dumper(%gnURLIni); }

  # 環境設定ファイル読み込み(cachetbl.ini)
  if($gnDebugFlag) { print "cachetbl.ini=\n" . Dumper($gnCacheTblFile); }
  my $gnCacheTblRef = &gnLoadEnv($gnCacheTblFile);
  %gnCacheTbl = %$gnCacheTblRef;

  # 起動時に環境を読み込みメインウィンドウを開く
  $gnGUIxml->signal_autoconnect_from_package('main');


  #____________________________________________________
  # 板一覧読み込み
  print STDERR "Building Board List...";
  # 板一覧のファイル名を与えると、TreeStore型のウィジェットが帰ってくる
  my $gnItaStore;
  $boardcfg = $gnCfgPath . "2chboard.lst";
     $gnItaStore = &gnMakeItaTreeStore($boardcfg);
  if(!($gnItaStore)) {
     &gnWarn("板一覧を保存するファイルが見つかりません\nメニューの\[ファイル\]-\[板更新\]を選択して板の一覧を取得してください");
  }
  print STDERR "...Complete";
  
  #____________________________________________________
  # 板一覧作成
  my $gnItaView = $gnGUIxml->get_widget('gnII');
  $gnItaView->set_model($gnItaStore);
  
  my $gnIICol1 = Gtk2::TreeViewColumn->new();
  $gnIICol1->set_title('２ちゃんねる');
  
  my $gnIICol1Rend = Gtk2::CellRendererText->new;
  $gnIICol1->pack_start($gnIICol1Rend, FALSE);
  $gnIICol1->add_attribute($gnIICol1Rend, text => 0);

  $gnItaView->append_column($gnIICol1);
  
  my $gnIS_pos = Gtk2::TreePath->new_from_indices(0);
  $gnItaView->expand_row($gnIS_pos, FALSE);
  
  
  # 板名が選択されたらスレ一覧読み込み
  $gnItaView->signal_connect('cursor-changed' => sub{ &gnSleIchiranSelect($gnItaView,"none"); });
  
  #____________________________________________________
  # スレ一覧初期設定
  #
  my $gnSureLstStore = &gnMakeInitialSureLst;
  &gnSureIchiranInit($gnSureLstStore, undef);
  
  #____________________________________________________
  # スレ表示部初期設定
  my $gnNoteInit = $gnGUIxml->get_widget('gnNote');
  $gnNoteInit->set_property(homogeneous => FALSE);
  $gnSignalHandler->{gnNote}{switch_page} = $gnNoteInit->signal_connect_after('switch-page' => sub{ &on_gnNote_switch_page; });
  if($gnDebugFlag) { print Dumper($gnSignalHandler)."\n"; }

  #____________________________________________________
  # お気に入りを追加

  #   メインメニュー
  my $gnMO_menu = $gnGUIxml->get_widget('gnMO_menu');
  &gnMO_AppendFavorites($gnMO_menu, $gnEnvArg{'gnFavorite'}{'gnFavFile'});
  #   ツリービュー
  my $gnFA = $gnGUIxml->get_widget('gnFA');
  &gnFA_AppendFavorites($gnFA, $gnEnvArg{'gnFavorite'}{'gnFavFile'});

  #--------------------------------------
  # メインウィンドウ表示
  #--------------------------------------

   # iniでサイズが指定されている部分はそれに合わせてリサイズする
   my $mW = $gnGUIxml->get_widget('mW');

   # アイコンをセット
   $mW->set_default_icon(&gnGetgnIconPixbuf());
   $mW->set_icon(&gnGetgnIconPixbuf());

   # メインウィンドウのリサイズ
   $mW->resize($gnEnvArg{'WindowSize'}{'Width'}, $gnEnvArg{'WindowSize'}{'Height'});
   $mW->move($gnEnvArg{'WindowSize'}{'Left'}, $gnEnvArg{'WindowSize'}{'Top'});
   $mW->queue_resize;
   
   # カテゴリ一覧の幅を環境変数から読み取ってセット
   my $gnCatHPan = $gnGUIxml->get_widget('gnCatHPan');
   $gnCatHPan->set_position($gnEnvArg{'CategoryColumnWidth'}{'ID0'});

   # 板一覧の高さを環境変数から読み取ってセット
   my $gnItaVPan = $gnGUIxml->get_widget('gnItaVPan');
   $gnItaVPan->set_position($gnEnvArg{'BoardColumnWidth'}{'ID7'});
   


   # メインウィンドウを表示
   $mW->signal_connect('unrealize' => sub{ &on_mW_unrealize(); });
   $mW->show_all;
   
   print "done. Ready.\n";

}

sub gnRestartInit {
   # 初期設定内容に不備があった場合に、再度初期設定を行うよう
   # 促す関数
   #
   # 引数: なし
   # 返り値: なし
   my $mW = $gnGUIxml->get_widget('mW');
   &gnWarn("初期設定内容に不備があるか、\nバージョンの変更に伴い内容に不足が生じました。\n$gnInitCfg{'Main'}{'gnCfgPath'}\.${gnGnname}configを削除するかリネームしてから${gnGnname}をもう一度起動して\n再度初期設定を行ってください");
   exit(1);
}

sub gnGetLibPath {
   # ライブラリパスをチェック
   #
   # 引数:      なし
   # 返り値: 1: ライブラリへのフルパス
   
   my $gnLibPath = "";
   if ($^O eq "MSWin32") {
      # "perl -V"の"osname"がMSWin32の場合は
      # 実行環境がWindowsであると判断
      $gnLibPath = &gnGetEnvVal('APPDATA') . "\\${gnGnname}\\";
      if(-d $gnLibPath) {
         if($gnDebugFlag) { print "Your gnview library is $gnLibPath from APPDATA environment valiables\n";}
         return($gnLibPath);
      }
   }else{
      # Windowsでない場合はFreedesktop.org Specification準拠に従って
      # ライブラリパスを探索する。
      # ヒットした時点でreturn。
      if(&gnGetEnvVal('XDG_DATA_HOME')) {
         $gnLibPath = &gnGetEnvVal('XDG_DATA_HOME') . "\/gnview\/";
         if(-d $gnLibPath) {
            if($gnDebugFlag) { print "Your gnview library is $gnLibPath from XDG_DATA_HOME environment valiables\n";}
            return($gnLibPath);
         }
      }else{
         $gnLibPath = &gnGetEnvVal('HOME') . "\/.local\/share\/gnview\/";
         if(-d $gnLibPath) {
            if($gnDebugFlag) { print "Your gnview library is $gnLibPath from HOME environment valiables\n";}
            return($gnLibPath);
         }
      }
      if(&gnGetEnvVal('XDG_DATA_DIRS')) {
         my @dvars = split(/:/, &gnGetEnvVal('XDG_DATA_DIRS'));
         foreach my $dvar(@dvars) {
            chomp($dvar);
            $gnLibPath = $dvar . "\/gnview\/";
            if(-d $gnLibPath) {
               if($gnDebugFlag) { print "Your gnview library is $gnLibPath from XDG_DATA_DIRS environment valiables\n";}
               return($gnLibPath);
            }
         }
      }
      my @dvars = ('/usr/local/share/', '/usr/share/');
      foreach my $dvar(@dvars) {
         chomp($dvar);
         $gnLibPath = $dvar . "gnview\/";
         if(-d $gnLibPath) {
            if($gnDebugFlag) { print "Your gnview library is $gnLibPath from fixed path\n";}
            return($gnLibPath);
         }
      }
   }
   
   if($gnLibPath eq "") {
      print STDERR "Fatal. gnview library path not found. check installation.\n";
   }else{
      if(-d $gnLibPath) {
         if($gnDebugFlag) { print "Your gnview library is $gnLibPath....but where did you point from \?\n";}
         return($gnLibPath);
      }else{
         print STDERR "Fatal. gnview library path you choiced is not found $gnLibPath.\n";
      }
   }
}

sub gnGetEnvVal {
   # 環境変数を取得して、UTF-8フラグをつけて返す関数
   #
   # 引数：文字列(環境変数のキー)
   # 返り値：文字列(環境変数の値。Encode::decodeされた値。存在しなければundefが返る)
   my $key = $_[0];
   
   my $val = Encode::decode(&env_locale_to_encstr($ENV{'LANG'}), $ENV{$key});
   return($val);

}

sub env_locale_to_encstr {
    # ロケール名からEncodeモジュールが解釈できる
    # 文字エンコード名を返す関数
    # 引数=文字列(ロケール名)
    
    # ロケール名を取得
    my $myLocVal = $_[0];

    # Windowsでは"Japanese_Japan"という文字列が使われているので
    # 一括変換しやすいように"ja_JPと置換"
    $myLocVal =~ s/Japanese_Japan/ja_JP/g;
    
    # "ja_JP.eucJP"の"ja_JP"部はいらないので切り取る
    my @myLocStr  = split(/\./, $myLocVal);

    # 後ろの"eucJP"の部分は小文字に置き換える
    # (実際にも大文字・小文字を区別しないよう推奨されている)
    if($myLocStr[1]) {
       $myLocStr[1] = lc($myLocStr[1]);
    }else{
        # SolarisでLocaleの２番目がなかったらeucに決めうち
       if($^O eq 'solaris') {
          push(@myLocStr, "eucjp");
        }
     }
    
    # あとはパターンマッチ
    if    ($myLocStr[1] eq "932")           { return('cp932');
    }elsif($myLocStr[1] eq "pck")           { return('cp932');
    }elsif($myLocStr[1] eq "sjis")          { return('shiftjis');
    }elsif($myLocStr[1] eq "20932")     { return('euc-jp');
    }elsif($myLocStr[1] eq "eucjp")     { return('euc-jp');
    }elsif($myLocStr[1] eq "euc-jp")        { return('euc-jp');
    }elsif($myLocStr[1] eq "euc")           { return('euc-jp');
    }elsif($myLocStr[1] eq "ujis")          { return('euc-jp');
    }elsif($myLocStr[1] eq "iso-2022-jp")   { return('iso-2022-jp');
    }elsif($myLocStr[1] eq "utf-8")     { return('utf8');
    }elsif($myLocStr[1] eq "utf8")          { return('utf8');
    }else                                       { return('ascii'); # エンコードが判別できなかったらシングルバイトエンコード(単なるバイト列)として解釈する
    }

    
}

sub gnJoinThreads {
    # 終了後のスレッドをjoinする関数
    # 引数: なし
    # 返り値: なし
    $thr->join();
}

sub command_runner {
    ## 別スレッドで動く関数
    # goggify.pl(http://spr.mahonri5.net/oggify/)より使用
    if ($gnDebugFlag) { print STDERR "thread-start\n"; }
    my $run = 1;
    my $run2 = 1; 
    my $gnUA_res;
    my $gnUA;
    my $job;
    my $url;
    my $fn;
    my $proxy_flg;
    my $proxy_addr;
    my $proxy_port;
    my $proxy_uname;
    my $proxy_pass;
    # run until return
    while ($run) {

        $job = $queue->dequeue;
        if ($job eq "geturl") {
           $url = $queue->dequeue_nb;
           $fn = $queue->dequeue_nb;
           $proxy_flg = $queue->dequeue;
              if ($gnDebugFlag>1) { print STDERR "proxyflg\: " . $proxy_flg . "\n"; }
           $proxy_addr = $queue->dequeue_nb;
              if ($gnDebugFlag>1) { print STDERR "proxyaddr\: " . $proxy_addr . "\n"; }
           $proxy_port = $queue->dequeue;
              if ($gnDebugFlag>1) { print STDERR "proxyport\: " . $proxy_port . "\n"; }
           $proxy_uname = $queue->dequeue;
              if ($gnDebugFlag>1) { print STDERR "proxyUserID\: " . $proxy_uname . "\n"; }
           $proxy_pass = $queue->dequeue;
              if ($gnDebugFlag>1) { print STDERR "proxyPass\: " . $proxy_pass . "\n"; }
           {
              lock $gnImgGetSt;
              $gnImgGetSt = TRUE;
           }

           if ($run2 == 1) {
              { lock $fnsize; $fnsize = 0; }
              $gnUA = LWP::UserAgent->new;
              $gnUA->agent($gnUsrAgent);
              $gnUA->timeout(15); # 15秒間無反応だったら中断する
              $run2 = 0;
           }

           # プロキシの設定
           if(!($proxy_flg)) {
               $gnUA->proxy('http', undef);
           }elsif($proxy_flg == 1) {
               my $gnUA_proxystr = "http\:\/\/" . $proxy_addr . "\:" . $proxy_port . "\/";
               $gnUA->proxy('http', $gnUA_proxystr);
           }else{
               $gnUA->proxy('http', undef);
           }
           if ($gnDebugFlag>1) { print STDERR "proxy\: " . $gnUA->proxy('http') . "\n"; }
           
           my $gnTotSz = 0;
           $gnUA_res = $gnUA->request(HTTP::Request->new(GET => $url),
                                            sub {
                                               my($chunk, $res) = @_;
                                               my $gnFN = &gnOpenLocal($fn, "a");
                                                  binmode $gnFN;
                                                  syswrite $gnFN, $chunk, length($chunk);
                                               &gnCloseLocal($fn);
                                               $gnTotSz += length($chunk);
                                               if ($gnDebugFlag>1) { print STDERR "thread-write\: " . $gnTotSz . "\/$fnsize bytes\n"; }
                                            });
           {
              lock $gnImgGetSt;
              $gnImgGetSt = FALSE;
           }
        }elsif($job eq "exit") {
           $run = 0;
        }
        { lock $hold; $hold = 0; } 
    }    
    
}

