// ================================================================================================ // // 翻訳支援処理を実装するための抽象クラスソース // // // 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.Models; using Honememo.Utilities; using Honememo.Wptscs.Models; using Honememo.Wptscs.Properties; using Honememo.Wptscs.Utilities; using Honememo.Wptscs.Websites; /// /// 翻訳支援処理を実装するための抽象クラスです。 /// public abstract class Translator { #region private変数 /// /// 変換後テキスト。 /// private string text = String.Empty; /// /// ログテキスト生成用ロガー。 /// private Logger logger; #endregion #region コンストラクタ /// /// トランスレータを作成。 /// public Translator() { // ステータス管理については更新イベントを連鎖させる this.Stopwatch = new Stopwatch(); this.Logger = new Logger(); this.StatusManager = new StatusManager(); this.StatusManager.Changed += new EventHandler( delegate { if (this.StatusUpdated != null) { this.StatusUpdated(this, EventArgs.Empty); } }); } #endregion #region イベント /// /// ログ更新伝達イベント。 /// public event EventHandler LogUpdated; /// /// 処理状態更新伝達イベント。 /// public event EventHandler StatusUpdated; #endregion #region 公開プロパティ /// /// 翻訳元言語のサイト。 /// public Website From { get; set; } /// /// 翻訳先言語のサイト。 /// public Website To { get; set; } /// /// 言語間の項目の対訳表。 /// public TranslationDictionary ItemTable { get; set; } /// /// 言語間の見出しの対訳表。 /// public TranslationTable HeadingTable { get; set; } /// /// ログメッセージ。 /// public string Log { get { return this.Logger.ToString(); } } /// /// 処理状態メッセージ。 /// public string Status { get { // 内部的に実際に管理しているのはStatusManager return StringUtils.DefaultString(this.StatusManager.Status); } } /// /// 処理時間ストップウォッチ。 /// 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; } #endregion #region 実装支援用プロパティ /// /// ログテキスト生成用ロガー。 /// /// nullが指定された場合。 protected Logger Logger { get { return this.logger; } set { // nullは不可。また、ロガー変更後はイベントを設定 this.logger = Validate.NotNull(value); this.logger.LogUpdate += new EventHandler( delegate { if (this.LogUpdated != null) { this.LogUpdated(this, EventArgs.Empty); } }); } } /// /// ステータス管理用オブジェクト。 /// /// nullが指定された場合。 protected StatusManager StatusManager { get; private set; } #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 void Run(string name) { // ※必須な情報が設定されていない場合、InvalidOperationExceptionを返す if (this.From == null || this.To == null) { throw new InvalidOperationException("From or To is null"); } // 初期化やストップウォッチの起動といった前処理を実施 this.Initialize(); try { // サーバー接続チェック 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(); // 翻訳支援処理実行部の本体を実行 // ※以降の処理は、継承クラスにて定義 this.RunBody(name); } finally { // 状態のクリアやストップウォッチの停止といった後処理を実施 this.Terminate(); } } #endregion #region テンプレートメソッド /// /// 翻訳支援処理実行部の本体。 /// /// 記事名。 /// 処理を中断する場合。中断の理由はに出力する。 /// テンプレートメソッド的な構造になっています。 protected abstract void RunBody(string name); /// /// 翻訳支援処理実行時の初期化処理。 /// /// /// /// 実行前の初期化のタイミングでコールされる。 /// 本メソッドで例外が発生した場合、, はコールされない。 /// /// /// オーバーライド時は必ず親のメソッドもコールすること。 /// /// protected virtual void Initialize() { // ロガーや処理状態などを初期化、処理時間の測定を開始 this.Logger.Clear(); this.StatusManager.Clear(); this.Stopwatch.Reset(); this.Text = String.Empty; this.CancellationPending = false; this.From.WebProxy.Referer = null; this.To.WebProxy.Referer = null; this.Stopwatch.Start(); } /// /// 翻訳支援処理実行時の終了処理。 /// /// /// /// 実行後の後処理のタイミングでコールされる。 /// ただし、サーバー接続チェックに失敗した場合などでは、 /// が実行されること無く本メソッドが呼ばれる場合もある。 /// /// /// オーバーライド時は必ず親のメソッドもコールすること。 /// /// protected virtual void Terminate() { // 終了後は処理状態やRefererをクリア、処理時間を測定終了 this.StatusManager.Clear(); this.From.WebProxy.Referer = null; this.To.WebProxy.Referer = null; this.Stopwatch.Stop(); } #endregion #region 実装支援用メソッド /// /// ログ出力によるエラー処理を含んだページ取得処理。 /// /// ページタイトル。 /// 取得したページ。ページが存在しない場合は 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; using (var sm = this.StatusManager.Switch(Resources.StatusDownloading)) { success = this.TryGetPageBody(title, out page); } // 通信終了後にも再度終了要求を確認 this.ThrowExceptionIfCanceled(); return success; } /// /// 終了要求が出ている場合、例外を投げる。 /// /// trueの場合。 protected void ThrowExceptionIfCanceled() { if (this.CancellationPending) { throw new ApplicationException("CancellationPending is true"); } } #endregion #region 内部処理用メソッド /// /// サーバー接続チェック。 /// /// サーバー名。 /// true 接続成功。 /// 実行中は処理状態をサーバー接続中に更新する。 private bool Ping(string server) { // サーバー接続チェック、実行中は処理状態を変更 using (var sm = this.StatusManager.Switch(Resources.StatusPinging)) { // サーバー接続チェック 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 (EndPeriodException) { // 末尾がピリオドで終わるページが処理できない既知の不具合への対応、警告メッセージを出す this.Logger.AddResponse(Resources.LogMessageErrorPageName, title); return false; } catch (Exception e) { // その他例外の場合、まずエラー情報を出力 this.Logger.AddError(e); // エラーを無視しない場合、ここで翻訳支援処理を中断する if (!Settings.Default.IgnoreError) { throw new ApplicationException(e.Message, e); } return false; } } #endregion } }