// ================================================================================================
//
// 翻訳支援処理を実装するための共通クラスソース
//
//
// Copyright (C) 2012 Honeplus. All rights reserved.
//
// Honeplus
// ================================================================================================
namespace Honememo.Wptscs.Logics
{
using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.NetworkInformation;
using System.Reflection;
using Honememo.Utilities;
using Honememo.Wptscs.Models;
using Honememo.Wptscs.Properties;
using Honememo.Wptscs.Websites;
///
/// 翻訳支援処理を実装するための共通クラスです。
///
public abstract class Translator
{
#region private変数
///
/// 処理状態メッセージ。
///
private string status = String.Empty;
///
/// 変換後テキスト。
///
private string text = String.Empty;
///
/// ログテキスト生成用ロガー。
///
private Logger logger;
#endregion
#region コンストラクタ
///
/// インスタンスを生成する。
///
public Translator()
{
this.Stopwatch = new Stopwatch();
this.Logger = new Logger();
}
#endregion
#region デリゲート
///
/// で実行する処理のためのデリゲート。
///
protected delegate void MethodWithChangeStatus();
#endregion
#region イベント
///
/// ログ更新伝達イベント。
///
public event EventHandler LogUpdate;
///
/// 処理状態更新伝達イベント。
///
public event EventHandler StatusUpdate;
#endregion
#region プロパティ
///
/// 言語間の項目の対訳表。
///
public TranslationDictionary ItemTable
{
get;
set;
}
///
/// 言語間の見出しの対訳表。
///
public TranslationTable HeadingTable
{
get;
set;
}
///
/// ログメッセージ。
///
public string Log
{
get
{
return this.Logger.ToString();
}
}
///
/// 処理状態メッセージ。
///
public string Status
{
get
{
return this.status;
}
protected set
{
this.status = StringUtils.DefaultString(value);
if (this.StatusUpdate != null)
{
this.StatusUpdate(this, EventArgs.Empty);
}
}
}
///
/// 処理時間ストップウォッチ。
///
public Stopwatch Stopwatch
{
get;
private set;
}
///
/// 変換後テキスト。
///
public string Text
{
get
{
return this.text;
}
protected set
{
this.text = StringUtils.DefaultString(value);
}
}
///
/// 処理を途中で終了させるためのフラグ。
///
public bool CancellationPending
{
get;
set;
}
///
/// 翻訳元言語のサイト。
///
public Website From
{
get;
set;
}
///
/// 翻訳先言語のサイト。
///
public Website To
{
get;
set;
}
///
/// ログテキスト生成用ロガー。
///
protected Logger Logger
{
get
{
return this.logger;
}
set
{
// nullは不可。また、ロガー変更後はイベントを設定
this.logger = Validate.NotNull(value);
this.logger.LogUpdate += this.GetLogUpdate;
}
}
#endregion
#region 静的メソッド
///
/// 翻訳支援処理のインスタンスを作成。
///
/// アプリケーション設定。
/// 翻訳元言語。
/// 翻訳先言語。
/// 生成したインスタンス。
///
/// 設定は設定クラスより取得、無ければ一部自動生成する。
/// インスタンス生成失敗時は例外を投げる。
///
public static Translator Create(Config config, string from, string to)
{
// 処理対象に応じてTranslatorを継承したオブジェクトを生成
ConstructorInfo constructor = config.Translator.GetConstructor(Type.EmptyTypes);
if (constructor == null)
{
throw new NotImplementedException(config.Translator.FullName + " default constructor is not found");
}
// 設定に指定されたクラスを、引数無しのコンストラクタを用いて生成する
Translator translator = (Translator)constructor.Invoke(null);
// Webサイトの設定
translator.From = config.GetWebsite(from);
translator.To = config.GetWebsite(to);
// 対訳表(項目)の設定
translator.ItemTable = config.GetItemTableNeedCreate(from, to);
// 対訳表(見出し)の設定、使用する言語は決まっているので組み合わせを設定
translator.HeadingTable = config.HeadingTable;
translator.HeadingTable.From = from;
translator.HeadingTable.To = to;
return translator;
}
#endregion
#region publicメソッド
///
/// 翻訳支援処理実行。
///
/// 記事名。
/// 処理が中断された場合。中断の理由はに出力される。
/// , が設定されていない場合。
public void Run(string name)
{
// ※必須な情報が設定されていない場合、InvalidOperationExceptionを返す
if (this.From == null || this.To == null)
{
throw new InvalidOperationException("From or To is null");
}
// 変数を初期化、処理時間を測定開始
this.Initialize();
this.Stopwatch.Start();
// サーバー接続チェック
string host = new Uri(this.From.Location).Host;
if (!String.IsNullOrEmpty(host) && !Settings.Default.IgnoreError)
{
if (!this.Ping(host))
{
throw new ApplicationException("ping failed");
}
}
// ここまでの間に終了要求が出ているかを確認
this.ThrowExceptionIfCanceled();
// 翻訳支援処理実行部の本体を実行
// ※以降の処理は、継承クラスにて定義
try
{
this.RunBody(name);
}
finally
{
// 終了後は処理状態をクリア、処理時間を測定終了
this.Status = String.Empty;
this.Stopwatch.Stop();
}
}
#endregion
#region protectedメソッド
///
/// 翻訳支援処理実行部の本体。
///
/// 記事名。
/// 処理を中断する場合。中断の理由はに出力する。
/// テンプレートメソッド的な構造になっています。
protected abstract void RunBody(string name);
///
/// ログ出力によるエラー処理を含んだページ取得処理。
///
/// ページタイトル。
/// 取得したページ。ページが存在しない場合は null を返す。
/// 処理が成功した(404も含む)場合true、失敗した(通信エラーなど)の場合false。
/// がtrueの場合。
///
/// 本メソッドは、大きく3パターンの動作を行う。
///
/// - 正常にページが取得できた → trueでページを設定、ログ出力無し
/// - 404など想定内の例外でページが取得できなかった → trueでページ無し、ログ出力無し
/// - 想定外の例外でページが取得できなかった → falseでページ無し、ログ出力有り
/// or ApplicationExceptionで処理中断(アプリケーション設定のIgnoreErrorによる)。
///
/// また、実行中は処理状態をサーバー接続中に更新する。
/// 実行前後には終了要求のチェックも行う。
///
protected bool TryGetPage(string title, out Page page)
{
// 通信開始の前に終了要求が出ているかを確認
this.ThrowExceptionIfCanceled();
// ページ取得処理、実行中は処理状態を変更
bool success = false;
Page result = null;
this.ChangeStatusInExecuting(
() => success = this.TryGetPageBody(title, out result),
Resources.StatusDownloading);
page = result;
// 通信終了後にも再度終了要求を確認
this.ThrowExceptionIfCanceled();
return success;
}
///
/// 終了要求が出ている場合、例外を投げる。
///
/// がtrueの場合。
protected void ThrowExceptionIfCanceled()
{
if (this.CancellationPending)
{
throw new ApplicationException("CancellationPending is true");
}
}
///
/// 指定された処理を実行する間、処理状態を渡された値に更新する。
/// 処理終了後は以前の処理状態に戻す。
///
/// 実行する処理。
/// 処理状態。
protected void ChangeStatusInExecuting(MethodWithChangeStatus method, string status)
{
// 現在の処理状態を保存、新しい処理状態をセットし、処理を実行する
string oldStatus = this.Status;
this.Status = status;
try
{
method();
}
finally
{
// 処理状態を以前の状態に戻す
this.Status = oldStatus;
}
}
#endregion
#region privateメソッド
///
/// 翻訳支援処理実行時の初期化処理。
///
private void Initialize()
{
// 変数を初期化
this.Logger.Clear();
this.Status = String.Empty;
this.Stopwatch.Reset();
this.Text = String.Empty;
this.CancellationPending = false;
}
///
/// サーバー接続チェック。
///
/// サーバー名。
/// true 接続成功。
/// 実行中は処理状態をサーバー接続中に更新する。
private bool Ping(string server)
{
// サーバー接続チェック、実行中は処理状態を変更
bool result = false;
this.ChangeStatusInExecuting(
() => result = this.PingBody(server),
Resources.StatusPinging);
return result;
}
///
/// サーバー接続チェック本体。
///
/// サーバー名。
/// true 接続成功。
private bool PingBody(string server)
{
// サーバー接続チェック
Ping ping = new Ping();
try
{
PingReply reply = ping.Send(server);
if (reply.Status != IPStatus.Success)
{
this.Logger.AddMessage(Resources.ErrorMessageConnectionFailed, reply.Status.ToString());
return false;
}
}
catch (Exception e)
{
this.Logger.AddMessage(Resources.ErrorMessageConnectionFailed, e.InnerException.Message);
return false;
}
return true;
}
///
/// ログ出力によるエラー処理を含んだページ取得処理本体。
///
/// ページタイトル。
/// 取得したページ。ページが存在しない場合は null を返す。
/// 処理が成功した(404も含む)場合true、失敗した(通信エラーなど)の場合false。
///
/// 本メソッドは、大きく3パターンの動作を行う。
///
/// - 正常にページが取得できた → trueでページを設定、ログ出力無し
/// - 404など想定内の例外でページが取得できなかった → trueでページ無し、ログ出力無し
/// - 想定外の例外でページが取得できなかった → falseでページ無し、ログ出力有り
/// or ApplicationExceptionで処理中断(アプリケーション設定のIgnoreErrorによる)。
///
///
private bool TryGetPageBody(string title, out Page page)
{
page = null;
try
{
// 普通に取得できた場合はここで終了
page = this.From.GetPage(title);
return true;
}
catch (FileNotFoundException)
{
// ページ無しによる例外も正常終了
return true;
}
catch (Exception e)
{
// その他例外の場合、まずエラー情報を出力
this.Logger.AddResponse(e.Message);
if (e is WebException && ((WebException)e).Response != null)
{
// 出せるならエラーとなったURLも出力
this.Logger.AddResponse(Resources.LogMessageErrorURL, ((WebException)e).Response.ResponseUri);
}
// エラーを無視しない場合、ここで翻訳支援処理を中断する
if (!Settings.Default.IgnoreError)
{
throw new ApplicationException(e.Message, e);
}
return false;
}
}
///
/// ロガーのログ状態更新イベント用。
///
/// イベント発生オブジェクト。
/// 発生したイベント。
private void GetLogUpdate(object sender, EventArgs e)
{
// もともとこのクラスにあったログ通知イベントをロガーに移動したため、入れ子で呼び出す
if (this.LogUpdate != null)
{
this.LogUpdate(this, EventArgs.Empty);
}
}
#endregion
}
}