UserControls/MessageConfirmControl.cs

using System;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
using System.Data.SqlClient;
using ChatworkBulkSender.Forms;
using ChatworkBulkSender.Dtos;
using ChatworkBulkSender.Daos;
using ChatworkBulkSender.Utils;
using ChatworkBulkSender.Services.Chatwork;


namespace ChatworkBulkSender.UserControls
{
    public partial class MessageConfirmControl : AbstractUserControl
    {
        // 親フォーム通知用
        public event EventHandler SwitchToMessageInput;
        public event EventHandler SwitchToMessageResult;

        // 送信処理に関するDto
        private BulkSendJobDto _bulkSendJobDto;
        // 送信中フラグ
        private bool _isSending = false;
        // 親フォーム参照
        private Form _parentForm;


        public MessageConfirmControl()
        {
            InitializeComponent();

            // 親フォームができあがったタイミングで FormClosing をフック
            this.Load += (s, e) =>
            {
                _parentForm = this.FindForm();
                if (_parentForm != null)
                    _parentForm.FormClosing += ParentForm_FormClosing;
            };
        }

        public void SetBulkSendJobDto(BulkSendJobDto dto)
        {
            _bulkSendJobDto = dto;
            destinationAndFilePathAndActualContentsControl1.SetDestinationAndFilePathGrid(dto);
        }

        public BulkSendJobDto GetBulkSendJobDto()
        {
            return _bulkSendJobDto;
        }




        /// <summary>
        /// 親フォームの閉じる操作をフック。
        /// 送信中ならキャンセルする。
        /// </summary>
        private void ParentForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (_isSending)
            {
                // 送信中は閉じさせない
                MessageBoxUtil.Show("送信処理実行中のため、アプリを閉じることはできません。", "送信中");
                e.Cancel = true;
            }
        }

        /// <summary>
        /// 戻るボタン
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnBack_Click(object sender, EventArgs e)
        {
            SwitchToMessageInput?.Invoke(this, EventArgs.Empty);
        }

        /// <summary>
        /// 送信開始ボタン
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private async void btnSend_Click(object sender, EventArgs e)
        {
            // 二重実行防止
            if (_isSending) return;

            // 最終確認
            var result = MessageBoxUtil.ShowYesNo(MessageBoxUtil.ASK_001);
            if (result != DialogResult.Yes) return;

            // 最新のChatworkAPIを取得
            var senderMaster = new SenderMasterDao().GetAll().FirstOrDefault();
            _bulkSendJobDto.ApiToken = senderMaster.SenderApiToken;

            // CancellationTokenSource を作成
            var cts = new CancellationTokenSource();

            // 送信処理(子画面で進捗表示)
            using (var sendProgressForm = new T_SendProgress())
            {
                // 全送信件数をセット
                int total = _bulkSendJobDto.DestinationList.Count;
                sendProgressForm.SetMaximum(total);

                // CTSをセット
                sendProgressForm.CancellationTokenSource = cts;

                // IProgress<int> の実体。UIスレッド上で更新される。
                var progress = new Progress<int>(count => sendProgressForm.ReportProgress(count));

                // ダイアログをモードレスで表示
                sendProgressForm.Show(_parentForm);

                // 送信処理/進捗レポート
                await ExecuteBulkSendAsync(progress, cts.Token);

                // ダイアログを閉じる
                sendProgressForm.Close();
            }

            // 画面切り替え
            SwitchToMessageResult?.Invoke(this, EventArgs.Empty);
        }
        /// <summary>
        /// bulkSendJobDto.DestinationList の各宛先に対して、PDFアップロード+メッセージ送信を逐次実行する。
        /// 送信中はUI操作(再送信/画面切り替え/終了)を抑制する。
        /// トランザクション処理は意図的にしていない。途中でエラーが起こった場合でも、送信済み情報についてはログに残したいため。
        /// </summary>
        /// <param name="progress"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        public async Task ExecuteBulkSendAsync(IProgress<int> progress, CancellationToken token)
        {
            // 1) UI 制御を無効化
            _isSending = true;
            btnSend.Enabled = false;
            btnBack.Enabled = false;

            int successCount = 0, failureCount = 0;
            var destList = _bulkSendJobDto.DestinationList;
            var detailDao = new SendHistoryDetailDao();

            try
            {
                // 2) DB登録(ヘッダー)
                using (var conn = new SqlConnection(new DBAccess().connection_str))
                {
                    await conn.OpenAsync(token);
                    _bulkSendJobDto.SendHistoryId = await new SendHistoryDao().InsertSendHistoryHeaderAsync(_bulkSendJobDto, conn);
                }

                // 3) 宛先ごとに送信&DB登録(明細)
                using (var conn = new SqlConnection(new DBAccess().connection_str))
                {
                    await conn.OpenAsync(token);
                    for (int detailIdx = 0; detailIdx < destList.Count; detailIdx++)
                    {
                        // キャンセルチェック
                        token.ThrowIfCancellationRequested();

                        // 送信
                        var dest = destList[detailIdx];
                        if (string.IsNullOrEmpty(dest.FilePath))
                        {
                            // メッセージ送信
                            dest.Result = await new ChatworkService().SendMessageAsync(dest.RoomId, dest.SendActualContents);
                        }
                        else
                        {
                            // ファイルアップロード+メッセージ送信
                            dest.Result = await new ChatworkService().UploadFileAsync(dest.RoomId, dest.FilePath, dest.SendActualContents);
                        }

                        // カウント集計
                        if (dest.Result.Success == Constants.SEND_RESULT.SUCCESS)
                            successCount++;
                        else
                            failureCount++;

                        // 明細登録
                        await detailDao.InsertSendHistoryDetailAsync((int)_bulkSendJobDto.SendHistoryId, dest, conn);

                        // 進捗更新
                        progress?.Report(detailIdx + 1);
                    }
                }

                MessageBoxUtil.Show("すべての送信が完了しました。", "送信完了");
            }
            catch (OperationCanceledException)
            {
                // 4) キャンセル発生時でも、未送信分を「試行せず」レコードだけ残す
                using (var conn = new SqlConnection(new DBAccess().connection_str))
                {
                    await conn.OpenAsync();
                    for (int j = successCount + failureCount; j < destList.Count; j++)
                    {
                        var dest = destList[j];
                        // キャンセル扱いの未送信は「失敗」としてカウント
                        failureCount++;
                        // 明細登録
                        await detailDao.InsertSendHistoryDetailAsync((int)_bulkSendJobDto.SendHistoryId, dest, conn);
                    }
                }

                MessageBoxUtil.ShowWarning("送信をキャンセルしました。", "中止");
            }
            catch (Exception ex)
            {
                MessageBoxUtil.ShowErr("送信処理でエラーが発生しました。\n" + ex.Message, "エラー");
            }
            finally
            {
                // 5) ヘッダーの成功/失敗件数と完了日時を更新
                using (var conn = new SqlConnection(new DBAccess().connection_str))
                {
                    if(_bulkSendJobDto.SendHistoryId != null)
                    {
                        await conn.OpenAsync();
                        await new SendHistoryDao()
                            .UpdateSendHistoryHeaderCountsAsync((int)_bulkSendJobDto.SendHistoryId, successCount, failureCount, conn);
                    }
                }

                // 6) UI 再有効化
                _isSending = false;
                btnSend.Enabled = true;
                btnBack.Enabled = true;
            }
        }

        private void MessageConfirmControl_Resize(object sender, EventArgs e)
        {
            CenterButton();
        }

        /// <summary>
        /// 送信開始ボタンを左右中央に表示する。
        /// </summary>
        private void CenterButton()
        {
            int x = (this.ClientSize.Width - btnSend.Width) / 2;
            btnSend.Left = x;
        }
    }
}