// ================================================================================================
// <summary>
-// 翻訳支援処理を実装するための共通クラスソース</summary>
+// 翻訳支援処理を実装するための抽象クラスソース</summary>
//
// <copyright file="Translator.cs" company="honeplusのメモ帳">
-// Copyright (C) 2010 Honeplus. All rights reserved.</copyright>
+// Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
// <author>
// Honeplus</author>
// ================================================================================================
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;
/// <summary>
- /// 翻訳支援処理を実装するための共通クラスです。
+ /// 翻訳支援処理を実装するための抽象クラスです。
/// </summary>
public abstract class Translator
{
#region private変数
/// <summary>
- /// 改行コード。
+ /// 変換後テキスト。
/// </summary>
- public static readonly string ENTER = "\r\n";
+ private string text = String.Empty;
/// <summary>
- /// ã\83ã\82°ã\83¡ã\83\83ã\82»ã\83¼ã\82¸。
+ /// ã\83ã\82°ã\83\86ã\82ã\82¹ã\83\88ç\94\9fæ\88\90ç\94¨ã\83ã\82¬ã\83¼。
/// </summary>
- private string log;
+ private Logger logger;
+
+ #endregion
+
+ #region コンストラクタ
/// <summary>
- /// 変換後テキスト。
+ /// トランスレータインスタンスを生成する。
/// </summary>
- private string text;
+ public Translator()
+ {
+ // ステータス管理については更新イベントを連鎖させる
+ this.Stopwatch = new Stopwatch();
+ this.Logger = new Logger();
+ this.StatusManager = new StatusManager<string>();
+ this.StatusManager.Changed += new EventHandler(
+ delegate
+ {
+ if (this.StatusUpdated != null)
+ {
+ this.StatusUpdated(this, EventArgs.Empty);
+ }
+ });
+ }
#endregion
-
+
#region イベント
/// <summary>
/// ログ更新伝達イベント。
/// </summary>
- public event EventHandler LogUpdate;
+ public event EventHandler LogUpdated;
+
+ /// <summary>
+ /// 処理状態更新伝達イベント。
+ /// </summary>
+ public event EventHandler StatusUpdated;
#endregion
- #region プロパティ
+ #region 公開プロパティ
+
+ /// <summary>
+ /// 翻訳元言語のサイト。
+ /// </summary>
+ public Website From
+ {
+ get;
+ set;
+ }
+
+ /// <summary>
+ /// 翻訳先言語のサイト。
+ /// </summary>
+ public Website To
+ {
+ get;
+ set;
+ }
/// <summary>
/// 言語間の項目の対訳表。
/// </summary>
public string Log
{
- // ※ 将来的には、ロジックでログメッセージを出すなんて形を止めて
- // データとして保持させてメッセージはビューで・・・としたいが、
- // 手間を考えて当面はこの形のまま実装する。
get
{
- return this.log;
+ return this.Logger.ToString();
}
+ }
- protected set
+ /// <summary>
+ /// 処理状態メッセージ。
+ /// </summary>
+ public string Status
+ {
+ get
{
- this.log = (value != null) ? value : String.Empty;
- if (this.LogUpdate != null)
- {
- this.LogUpdate(this, EventArgs.Empty);
- }
+ // 内部的に実際に管理しているのはStatusManager
+ return StringUtils.DefaultString(this.StatusManager.Status);
}
}
/// <summary>
+ /// 処理時間ストップウォッチ。
+ /// </summary>
+ public Stopwatch Stopwatch
+ {
+ get;
+ private set;
+ }
+
+ /// <summary>
/// 変換後テキスト。
/// </summary>
public string Text
set;
}
+ #endregion
+
+ #region 実装支援用プロパティ
+
/// <summary>
- /// 翻訳元言語のサイト。
+ /// ログテキスト生成用ロガー。
/// </summary>
- public Website From
+ /// <exception cref="ArgumentNullException"><c>null</c>が指定された場合。</exception>
+ protected Logger Logger
{
- get;
- set;
+ 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);
+ }
+ });
+ }
}
/// <summary>
- /// 翻訳先言語のサイト。
+ /// ステータス管理用オブジェクト。
/// </summary>
- public Website To
+ protected StatusManager<string> StatusManager
{
get;
- set;
+ private set;
}
#endregion
#region 静的メソッド
/// <summary>
- /// 翻訳支援処理のインスタンスを作成。
+ /// 指定されたアプリケーション設定から翻訳支援処理のインスタンスを作成する。
/// </summary>
/// <param name="config">アプリケーション設定。</param>
/// <param name="from">翻訳元言語。</param>
/// <param name="to">翻訳先言語。</param>
/// <returns>生成したインスタンス。</returns>
+ /// <exception cref="NotImplementedException">
+ /// アプリケーション設定に指定されているトランスレータに引数無しのコンストラクタが存在しない場合。
+ /// </exception>
/// <remarks>
/// 設定は設定クラスより取得、無ければ一部自動生成する。
- /// インスタンス生成失敗時は例外を投げる。
+ /// インスタンス生成失敗時は各種例外を投げる。
/// </remarks>
public static Translator Create(Config config, string from, string to)
{
#endregion
- #region publicメソッド
+ #region 公開メソッド
/// <summary>
/// 翻訳支援処理実行。
/// </summary>
/// <param name="name">記事名。</param>
- /// <returns><c>true</c> 処理成功</returns>
- public virtual bool Run(string name)
+ /// <exception cref="ApplicationException">処理が中断された場合。中断の理由は<see cref="Logger"/>に出力される。</exception>
+ /// <exception cref="InvalidOperationException"><see cref="From"/>, <see cref="To"/>が設定されていない場合。</exception>
+ public void Run(string name)
{
// ※必須な情報が設定されていない場合、InvalidOperationExceptionを返す
if (this.From == null || this.To == null)
throw new InvalidOperationException("From or To is null");
}
- // å¤\89æ\95°ã\82\92å\88\9dæ\9c\9få\8c\96
+ // å\88\9dæ\9c\9få\8c\96ã\82\84ã\82¹ã\83\88ã\83\83ã\83\97ã\82¦ã\82©ã\83\83ã\83\81ã\81®èµ·å\8b\95ã\81¨ã\81\84ã\81£ã\81\9få\89\8då\87¦ç\90\86ã\82\92å®\9fæ\96½
this.Initialize();
-
- // サーバー接続チェック
- string host = new Uri(this.From.Location).Host;
- if (!String.IsNullOrEmpty(host) && !Settings.Default.IgnoreError)
+ try
{
- if (!this.Ping(host))
+ // サーバー接続チェック
+ string host = new Uri(this.From.Location).Host;
+ if (!String.IsNullOrEmpty(host) && !Settings.Default.IgnoreError)
{
- return false;
+ if (!this.Ping(host))
+ {
+ throw new ApplicationException("ping failed");
+ }
}
- }
- // 翻訳支援処理実行部の本体を実行
- // ※以降の処理は、継承クラスにて定義
- return this.RunBody(name);
+ // ここまでの間に終了要求が出ているかを確認
+ this.ThrowExceptionIfCanceled();
+
+ // 翻訳支援処理実行部の本体を実行
+ // ※以降の処理は、継承クラスにて定義
+ this.RunBody(name);
+ }
+ finally
+ {
+ // 状態のクリアやストップウォッチの停止といった後処理を実施
+ this.Terminate();
+ }
}
#endregion
- #region protectedメソッド
+ #region テンプレートメソッド
/// <summary>
/// 翻訳支援処理実行部の本体。
/// </summary>
/// <param name="name">記事名。</param>
- /// <returns><c>true</c> 処理成功</returns>
+ /// <exception cref="ApplicationException">処理を中断する場合。中断の理由は<see cref="Logger"/>に出力する。</exception>
/// <remarks>テンプレートメソッド的な構造になっています。</remarks>
- protected abstract bool RunBody(string name);
+ protected abstract void RunBody(string name);
/// <summary>
- /// ログメッセージを1行追加出力。
+ /// 翻訳支援処理実行時の初期化処理。
/// </summary>
- /// <param name="log">ログメッセージ。</param>
- protected void LogLine(string log)
+ /// <remarks>
+ /// <para>
+ /// <see cref="RunBody"/>実行前の初期化のタイミングでコールされる。
+ /// 本メソッドで例外が発生した場合、<see cref="RunBody"/>, <see cref="Terminate"/>はコールされない。
+ /// </para>
+ /// <para>
+ /// オーバーライド時は必ず親のメソッドもコールすること。
+ /// </para>
+ /// </remarks>
+ protected virtual void Initialize()
{
- // 直前のログが改行されていない場合、改行して出力
- if (this.Log != String.Empty && this.Log.EndsWith(ENTER) == false)
- {
- this.Log += ENTER + log + ENTER;
- }
- else
- {
- this.Log += log + ENTER;
- }
+ // ロガーや処理状態などを初期化、処理時間の測定を開始
+ 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();
}
/// <summary>
- /// ログメッセージを1行追加出力(入力された文字列を書式化して表示)。
+ /// 翻訳支援処理実行時の終了処理。
/// </summary>
- /// <param name="format">書式項目を含んだログメッセージ。</param>
- /// <param name="args">書式設定対象オブジェクト配列。</param>
- protected void LogLine(string format, params object[] args)
+ /// <remarks>
+ /// <para>
+ /// <see cref="RunBody"/>実行後の後処理のタイミングでコールされる。
+ /// ただし、サーバー接続チェックに失敗した場合などでは、
+ /// <see cref="RunBody"/>が実行されること無く本メソッドが呼ばれる場合もある。
+ /// </para>
+ /// <para>
+ /// オーバーライド時は必ず親のメソッドもコールすること。
+ /// </para>
+ /// </remarks>
+ protected virtual void Terminate()
{
- // オーバーロードメソッドをコール
- this.LogLine(String.Format(format, args));
+ // 終了後は処理状態やRefererをクリア、処理時間を測定終了
+ this.StatusManager.Clear();
+ this.From.WebProxy.Referer = null;
+ this.To.WebProxy.Referer = null;
+ this.Stopwatch.Stop();
}
+ #endregion
+
+ #region 実装支援用メソッド
+
/// <summary>
- /// ログメッセージを出力しつつページを取得。
+ /// ログ出力によるエラー処理を含んだページ取得処理。
/// </summary>
/// <param name="title">ページタイトル。</param>
- /// <param name="notFoundMsg">取得できない場合に出力するメッセージ。</param>
- /// <returns>取得したページ。ページが存在しない場合は <c>null</c> を返す。</returns>
- /// <remarks>通信エラーなど例外が発生した場合は、別途エラーログを出力する。</remarks>
- protected Page GetPage(string title, string notFoundMsg)
+ /// <param name="page">取得したページ。ページが存在しない場合は <c>null</c> を返す。</param>
+ /// <returns>処理が成功した(404も含む)場合<c>true</c>、失敗した(通信エラーなど)の場合<c>false</c>。</returns>
+ /// <exception cref="ApplicationException">
+ /// 想定外の例外が発生した場合でかつアプリケーション設定の<c>IgnoreError</c>が<c>false</c>の場合、
+ /// または<see cref="CancellationPending"/>が<c>true</c>の場合。
+ /// </exception>
+ /// <remarks>
+ /// 本メソッドは、大きく3パターンの動作を行う。
+ /// <list type="number">
+ /// <item><description>正常にページが取得できた → <c>true</c>でページを設定、ログ出力無し</description></item>
+ /// <item><description>404など想定内の例外でページが取得できなかった → <c>true</c>でページ無し、ログ出力無し</description></item>
+ /// <item><description>想定外の例外でページが取得できなかった → <c>false</c>でページ無し、ログ出力有り
+ /// or <see cref="ApplicationException"/>で処理中断(アプリケーション設定の<c>IgnoreError</c>による)。</description></item>
+ /// </list>
+ /// また、実行中は処理状態をサーバー接続中に更新する。
+ /// 実行前後には終了要求のチェックも行う。
+ /// </remarks>
+ protected bool TryGetPage(string title, out Page page)
{
- try
- {
- // 取得できた場合はここで終了
- return this.From.GetPage(title);
- }
- catch (WebException e)
- {
- // 通信エラー
- if (e.Status == WebExceptionStatus.ProtocolError
- && (e.Response as HttpWebResponse).StatusCode == HttpStatusCode.NotFound)
- {
- // 404
- this.Log += notFoundMsg;
- }
- else
- {
- // それ以外のエラー
- this.LogLine(Resources.RightArrow + " " + e.Message);
- if (e.Response != null)
- {
- this.LogLine(Resources.RightArrow + " " + String.Format(Resources.LogMessage_ErrorURL, e.Response.ResponseUri));
- }
- }
- }
- catch (FileNotFoundException)
- {
- // ファイル無し
- this.Log += notFoundMsg;
- }
- catch (Exception e)
+ // 通信開始の前に終了要求が出ているかを確認
+ this.ThrowExceptionIfCanceled();
+
+ // ページ取得処理、実行中は処理状態を変更
+ bool success;
+ using (var sm = this.StatusManager.Switch(Resources.StatusDownloading))
{
- // その他の想定外のエラー
- this.LogLine(Resources.RightArrow + " " + e.Message);
+ success = this.TryGetPageBody(title, out page);
}
- // 取得失敗時いずれの場合もnull
- return null;
+ // 通信終了後にも再度終了要求を確認
+ this.ThrowExceptionIfCanceled();
+ return success;
}
- #endregion
-
- #region privateメソッド
-
/// <summary>
- /// 翻訳æ\94¯æ\8f´å\87¦ç\90\86å®\9fè¡\8cæ\99\82ã\81®å\88\9dæ\9c\9få\8c\96å\87¦ç\90\86。
+ /// çµ\82äº\86è¦\81æ±\82ã\81\8cå\87ºã\81¦ã\81\84ã\82\8bå ´å\90\88ã\80\81ä¾\8bå¤\96ã\82\92æ\8a\95ã\81\92ã\82\8b。
/// </summary>
- private void Initialize()
+ /// <exception cref="ApplicationException"><see cref="CancellationPending"/>が<c>true</c>の場合。</exception>
+ protected void ThrowExceptionIfCanceled()
{
- // 変数を初期化
- this.log = String.Empty;
- this.Text = String.Empty;
- this.CancellationPending = false;
+ if (this.CancellationPending)
+ {
+ throw new ApplicationException("CancellationPending is true");
+ }
}
+ #endregion
+
+ #region 内部処理用メソッド
+
/// <summary>
/// サーバー接続チェック。
/// </summary>
/// <param name="server">サーバー名。</param>
- /// <returns><c>true</c> 接続成功。</returns>
+ /// <returns>接続成功の場合<c>true</c>。</returns>
+ /// <remarks>実行中は処理状態をサーバー接続中に更新する。</remarks>
private bool Ping(string server)
{
- // サーバー接続チェック
- Ping ping = new Ping();
- try
+ // サーバー接続チェック、実行中は処理状態を変更
+ using (var sm = this.StatusManager.Switch(Resources.StatusPinging))
{
- PingReply reply = ping.Send(server);
- if (reply.Status != IPStatus.Success)
+ // サーバー接続チェック
+ Ping ping = new Ping();
+ try
{
- this.LogLine(Resources.ErrorMessageConnectionFailed, reply.Status.ToString());
+ 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;
}
- catch (Exception e)
+ }
+
+ /// <summary>
+ /// ログ出力によるエラー処理を含んだページ取得処理本体。
+ /// </summary>
+ /// <param name="title">ページタイトル。</param>
+ /// <param name="page">取得したページ。ページが存在しない場合は<c>null</c>を返す。</param>
+ /// <returns>処理が成功した(404も含む)場合<c>true</c>、失敗した(通信エラーなど)の場合<c>false</c>。</returns>
+ /// <exception cref="ApplicationException">
+ /// 想定外の例外が発生した場合でかつアプリケーション設定の<c>IgnoreError</c>が<c>false</c>の場合。
+ /// </exception>
+ /// <remarks>
+ /// 本メソッドは、大きく3パターンの動作を行う。
+ /// <list type="number">
+ /// <item><description>正常にページが取得できた → <c>true</c>でページを設定、ログ出力無し</description></item>
+ /// <item><description>404など想定内の例外でページが取得できなかった → <c>true</c>でページ無し、ログ出力無し</description></item>
+ /// <item><description>想定外の例外でページが取得できなかった → <c>false</c>でページ無し、ログ出力有り
+ /// or <see cref="ApplicationException"/>で処理中断(アプリケーション設定の<c>IgnoreError</c>による)。</description></item>
+ /// </list>
+ /// </remarks>
+ private bool TryGetPageBody(string title, out Page page)
+ {
+ page = null;
+ try
+ {
+ // 普通に取得できた場合はここで終了
+ page = this.From.GetPage(title);
+ return true;
+ }
+ catch (FileNotFoundException)
{
- this.LogLine(Resources.ErrorMessageConnectionFailed, e.InnerException.Message);
+ // ページ無しによる例外も正常終了
+ return true;
+ }
+ catch (EndPeriodException)
+ {
+ // 末尾がピリオドで終わるページが処理できない既知の不具合への対応、警告メッセージを出す
+ this.Logger.AddResponse(Resources.LogMessageErrorPageName, title);
return false;
}
+ catch (Exception e)
+ {
+ // その他例外の場合、まずエラー情報を出力
+ this.Logger.AddError(e);
- return true;
+ // エラーを無視しない場合、ここで翻訳支援処理を中断する
+ if (!Settings.Default.IgnoreError)
+ {
+ throw new ApplicationException(e.Message, e);
+ }
+
+ return false;
+ }
}
#endregion