// ================================================================================================ // // 翻訳支援処理を実装するための共通クラスソース // // // 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 } }