サイト内で配布中のライブラリについてのスレッド。 バグ報告・質問など。 Atom 1.0

※ブラウザによっては新しい書き込みが表示されない場合があるようなので、返信が表示されていない場合はF5または更新ボタンでページを更新してください。

:ID:cipeco0y

配布中のライブラリについてのバグ報告・要望・質問用のスレッドです。
書き込みの際は再現用のコードやログなどもあわせて書き込んで頂けると助かります。

:ID:UiXMWVVZ

不具合あり

:ID:6971EjnS

>>196
100 Continue

:ID:hHO83Hsp

大変ご無沙汰しております。
先般は大変お世話になりました。

いつも、便利なライブラリを利用させていただき、
ありがとうございます。

今回は、メールの削除処理の効率化について教えてください。

現在、処理の終わったメールのMessage-IDを配列に保存しておき、
メールフォルダから、GetMessagesでメール一覧を取得したのち、
配列のMessage-IDに一致するものにDeletedフラグをつけています。

これでは、メール件数だけIMAP通信が発生してしまうため、
最初からMessage-ID配列に含まれるメール一覧を取得し、
まとめて、Deletedフラグを付けられたらいいのに、と考えています。

具体的には、
ImapMessageInfoList messages

= openedMailboxInfo.GetMessages
  (ImapSearchCriteria.Header("Message-ID", msgIdList[]);

messages.AddFlags(ImapMessageFlag.Deleted);
のようなことをしたいと考えています。

<質問1>
上記のように、msgIdList[]のように文字列の配列は指定できないようですが、
何か他の検索条件の設定方法はありますでしょうか?

<質問2>
Message-IDの配列による検索が難しいのであれば、
UIDの配列でも構わないと考えています。
実際、
ImapMessageInfoList messages

= openedMailboxInfo.GetMessages
  (ImapSearchCriteria.Uid(ImapSequenceSet uidSet);

messages.AddFlags(ImapMessageFlag.Deleted);
のような方法があるようですが、
処理が終わったメールのUIDをこのuidSetに、
追加登録していく具体的な方法を教えてください。

以上、よろしくお願いいたします。

:ID:QEeocG2o

>>198
追加情報です。
<質問1>について、ドキュメント・サンプルより、
以下の方法があることを見つけました。

ImapSearchCriteria isc = ImapSearchCriteria.New;
foreach (string msgId in m_msgIdList){

 isc |= ImapSearchCriteria.Header("Message-ID", msgId);

}
ImapMessageInfoList messages = openedMailboxInfo.GetMessages(isc);
messages.AddFlags(ImapMessageFlag.Deleted);

ただし、StackOverFlowExceptionがライブラリ内部で発生することがあるようです。
(条件が6000件だった場合)
回避策がありましたら、ご教示ください。

上記の方法では、問題があるようでしたら、
<質問2>の方法も考える必要がありますので、
こちらの質問についても引き続きよろしくお願いします。

:ID:5U/9Z0Js

>>198
>>199

引き続きライブラリをご利用いただいてありがとうございます。

先に目的を実現するための方法を書くと、<質問2>のようにUIDを用いる必要があります。

UIDを格納したlong型の配列をImapOpenedMailboxInfo.GetMessages()メソッドに渡すことで、
そのUIDに該当するメッセージを取得することができます。
その後、AddFlags(ImapMessageFlag.Deleted)することにより、まとめてDeleteフラグを付けることができます。

以下で個々の質問に対する回答と実装例を掲載します。

<質問1>への回答

質問にて指摘されているとおり、ImapSearchCriteria.Headerメソッドでは配列(=複数の値)を検索条件として
設定することはできません。 これはIMAPのSEARCHコマンドの仕様に基づく制限です。

従って、複数のMessage-IDからそれに該当するメッセージすべてを取得したい場合は、>>199
書かれているようにOR演算子によって条件式を結合する必要があります。
求められている動作を実現するコードも>>199のとおりとなります。

一方、StackOverFlowExceptionは発生するのはライブラリ側の問題で、まさに今回のように多数の条件式を
連結しようとする際に起こり得ます。 これは、コマンド送信の際に条件式の展開が再帰的に
行われることが原因です。 現時点での実装では、特に条件式が長大になるような場合において
StackOverFlowExceptionを回避することは難しいです。

なお、仮にStackOverFlowExceptionが起きない程度に処理するメール数を減らした場合でも、
サーバー側にかかる検索負荷を考慮するとこの方法は最善ではありません。
UIDが既知の状態であれば<質問2>の方法の方がよいでしょう。

<質問2>への回答

冒頭で書いたとおり、UIDを格納したlong型の配列(あるいはList<long>など)を、ImapOpenedMailboxInfo.GetMessages()メソッドに
渡すことにより、そのUIDに対応する全てのメールを取得することができます。
(ImapSearchCriteria.Uidおよび、ImapSequenceSetを使う必要はありません)

具体的なコードは次のようになります。 例示の簡単化のため配列ではなくList<long>を用いています。

// メールボックスを開く
ImapOpenedMailboxInfo openedMailboxInfo = client.OpenMailbox("MyMailbox");

// 処理が終わったメールのUIDを保持しておくためのList<long>を作成
List<long> deleteUidList = new List<long>();

// メールボックスから「処理」したいメールを取得する
foreach (ImapMessageInfo msg in openedMailboxInfo.GetMessages(ImapSearchCriteria.All)) {
  /*
   * msgに対する「処理」を行う
   */

  // 「処理」が終わった場合、そのメールのUIDをdeleteUidListに保持しておく
  if ( ... ) {
    deleteUidList.Add(msg.Uid);
  }
}

// 削除したいメール(処理済みのメール)の一覧を取得する
ImapMessageInfoList messages = openedMailboxInfo.GetMessages(deleteUidList);

// 取得した一覧に対して、まとめてDeletedフラグを付ける
messages.AddFlags(ImapMessageFlag.Deleted);
:ID:3E/r50lU

>>200

さっそくのご回答ありがとうございます。

UIDを格納したlong型の配列またはListを
ImapOpenedMailboxInfo.GetMessages()メソッドに
渡すことで、一括取得ができること、了解しました。

便利な機能ですね。
さっそく利用しようとしたのですが、
当方の処理の都合で問題があることがわかりました。

処理の流れは以下のとおりです。

1.INBOXフォルダのメールをすべて、作業フォルダにコピーする
  (ただし、削除フラグ有りのものを除く)
2.作業フォルダのメールについて、繰り返し処理を行う。
  処理済みのメールのMessage-IDを配列に追加する
3.INBOXフォルダのメールのうち、処理済のものを削除する

2の処理の部分で、取得できるUIDは、作業フォルダのものなので、
このUIDでは、3の処理で、INBOXフォルダのメールが削除できず、
メールを特定する情報として、Message-IDを使っています。

INBOXフォルダから作業フォルダにメールをコピーする際、
双方のフォルダのUIDを紐付けることができればいいのですが。
実際、1の処理は、以下のようにしています。

openedMailboxInfo = client.OpenInbox();
ImapMessageInfoList undeletedMessages = openedMailboxInfo.GetMessages(ImapSearchCriteria.Undeleted);
undeletedMessages.CopyTo(workFolderName);

この際のIMAPログを出力したところ、以下のようになっています。

C: 0009 SELECT INBOX
S: * FLAGS (\Answered \Flagged \Deleted \Seen \Draft $MDNSent)

1800 EXISTS

0 RECENT

OK [UIDVALIDITY 1470805234] UIDs valid

OK [UIDNEXT 19667] Predicted next UID

OK [UNSEEN 1] message 1 is first unseen

OK [PERMANENTFLAGS (\Answered \Flagged \Deleted \Seen \Draft $MDNSent)] limited

0009 OK [READ-WRITE] select completed
C: 000a SEARCH UNDELETED
S: * SEARCH 1 2 3 ・・・ 1800
000a OK search completed
C: 000b COPY 1,2,3,・・・,1800 201608101700 ←作業フォルダ名
S: 000b OK [COPYUID 1470816059 17867:19666 1:1800] copy completed

双方のフォルダのUIDが出力されているようです。

<質問3>

openedMailboxInfo = client.OpenInbox();
ImapMessageInfoList undeletedMessages = openedMailboxInfo.GetMessages(ImapSearchCriteria.Undeleted);
undeletedMessages.CopyTo(workFolderName);

上記のようにコピーを実施する際、
コピー元とコピー先のUIDを紐づいた形で
取得する方法がありますでしょうか?

以上、よろしくお願いします。

:ID:CBAR8jVx

>>201

追加情報です。

とりあえず、<質問3>の方法を使わずに、
処理を早くすることができましたので、ご報告します。

<質問3>につきましては、いったんご放念ください。

まず、INBOXフォルダのメールをすべて、作業フォルダにコピーする際に、
UIDとMessage-IDの関連を連想配列で紐付けておきます。

       ImapMessageInfoList undeletedMessages = openedMailboxInfo.GetMessages(ImapSearchCriteria.Undeleted);
       //INBOXのうち、削除フラグのないメールをすべて取得する
       foreach (ImapMessageInfo message in undeletedMessages) { //対象メールについて繰り返し
           KeyValuePair<String, String>[] val = message.GetHeader("Message-ID");
           string msgId = val[0].Value;
           long inboxUid = message.Uid;
           UidMsgID.Add(inboxUid, msgId); //INBOX上のUIDとMessage-IDのペアを連想配列に格納する
       }
       undeletedMessages.CopyTo(workFolderName); //メールをまとめてコピー【IMAPのCOPYコマンドは1回のみ】
       countCopy = undeletedMessages.Count();

削除する際には、Message-IDを元に、
連想配列か元となるUIDを見つけ、UIDの配列に追加して、
ご教示のあった方法で、まとめて削除フラグを付与します。

       foreach (long uid in UidMsgID.Keys) {
           string msgId = (string)UidMsgID[uid];
           if (m_msgIdList.Contains(msgId)){
               deleteUidList.Add(uid);
           }
       }
       // 削除したいメール(処理済みのメール)の一覧を取得する
       ImapMessageInfoList messages = openedMailboxInfo.GetMessages(deleteUidList);
       // 取得した一覧に対して、まとめてDeletedフラグを付ける
       messages.AddFlags(ImapMessageFlag.Deleted);

以上簡単ですが、今後ともよろしくお願いします。

:ID:5U/9Z0Js

>>201
>>202

目的とする処理に関してはすでに実装まで解決されておられるようですが、
コピー前後でのUIDの紐付けに関して、以下参考としてご覧ください。

作業フォルダへのコピー時のレスポンスは、ご推察のとおり「コピー元のフォルダにあるUIDが
17867〜19666のメールを、コピー先フォルダにコピーし、UIDとして1〜1800を割り当てた」という
意味になっています。

S: 000b OK [COPYUID 1470816059 17867:19666 1:1800] copy completed

従って、プロトコル上はこれを元にしてコピー前後のUIDを紐付けできるようになっています。

しかし、現状のCopyToメソッドではこの情報をもとにしたUIDの紐付けは行えません。
これはCOPYUIDがIMAPの基本機能ではなく拡張機能であり(=この情報を返送しないサーバーも
存在しうる)、ライブラリの機能としてもいかに実装するか決めかねているためです。

将来的にはコピー前後で紐付けできるようにするか、少なくとも可能ならコピー後のUIDを
取得できるように改善しようと考えていますが、現時点では、コピー前後のメールを紐付け
する場合はMessage-IDによって同定していただく必要があります。

:ID:F+6R0FE3

>>203
ご参考情報をいただき、ありがとうございました。
たしかに、ExpressMailでは、Move拡張コマンドには対応してないようです。

いずれにしましても、GetMessages(UID配列)により、
対象のメールをまとめて取得することができ、
処理時間が劇的に改善でき、嬉しく思っています。

以上簡単ですが、今後ともよろしくお願いします。

:ID:ucRfKAEb

>>204
いつもお世話になっております。

既知のUIDValidityとUIDから、メッセージを取得する方法について、
処理時間の短縮を検討しています。

現在は、GetMailBoxesメソッドにより、全フォルダを取得した上、
各フォルダについてループさせ、UIDValidityが一致した場合、
そのフォルダから、GetMessageByUidによりメッセージを取得しています。

 foreach (ImapMailboxInfo mailboxInfo in client.GetMailboxes()) {
   ImapOpenedMailboxInfo openedMailbox = client.OpenMailbox(mailboxInfo.FullName);
   if (openedMailbox.UidValidity.ToString().Equals(uidValidity)) {
     ImapMessageInfo message = openedMailbox.GetMessageByUid(int.Parse(uid));
     //メッセージに関する処理
     break;
   }
 }

上記の処理においてフォルダをループさせずに、
直接、UIDValidityから対象フォルダにアクセスする方法はないでしょうか?

 ImapMailboxInfo mailboxInfo in client.GetMailbox(uidValidity);
 

GetMailboxに指定できる引数は、フォルダ名なので、上記は失敗しますが、
このような形で、できないでしょうか?

以上、よろしくお願いします。

:ID:5U/9Z0Js

>>205

UidValidity値は、IDやエイリアスのようなメールボックスを特定するための値ではないため、
UidValidityから対応するメールボックスを取得するような目的には使えません。
ライブラリとしてもそういった機能は提供していません。

従って、挙げられたコードのようにメールボックスをひとつずつ検査してUidValidityを
チェックしていく必要があります。

(UidValidity値は、前回アクセスした時のUIDが現在も有効であるか、つまりメールとUIDの
対応関係が変化していないかどうかをチェックするための値です。 サーバー側の動作により
メールボックスのUidValidity値が変わる場合があります。)

ところで、コード中ではUidValidityの取得のために各メールボックスをOpenしているように
見えますが、GetMailboxesではオプションでメールボックスのステータスも同時に取得することができます。

これによりUidValidityの検証のためにひとつずつメールボックスをOpenする必要がなくなるため、
処理時間の短縮が図れるかもしれません。

 // すべてのメールボックスと、そのステータスを同時に取得する
 foreach (ImapMailboxInfo mailboxInfo in client.GetMailboxes(ImapMailboxListOptions.RequestStatus)) {
   // UidValidity値を検証する
   if (mailboxInfo.UidValidity.ToString().Equals(uidValidity)) {
     // 対象フォルダだった場合は、Openしてメッセージを取得する
     ImapOpenedMailboxInfo openedMailbox = client.OpenMailbox(mailboxInfo.FullName);
     ImapMessageInfo message = openedMailbox.GetMessageByUid(int.Parse(uid));
     //メッセージに関する処理
     break;
   }
 }
:ID:UALUQXu4

>>206

いつも迅速で的確なアドバイスをいただき、
ありがとうございます。

ご教示いただいた方法で、処理時間が半減しました。

また、UidValidity値について、私の認識が誤っていたことも
ご指摘いただき、ありがとうございました。

取り急ぎ、以上です。

:ID:hHO83Hsp

いつもお世話になっております。

今回は、Content-Dispositionヘッダに関して、
各メールソフトでの実装状況についての質問です。

添付ファイル名の判定にあたっては、
Content-Disposition: attachment; の
filenameパラメータを取得するのが基本だと考えています。

ただし、メールソフトによっては、filenameパラメータがなく、
他のパラメータを参考にする必要になる場合があります。

Content-Disposition: attachment; のパラメータには、
filenameパラメータの他に、下記のものがあるようです。

creation-date [RFC2183]
modification-date [RFC2183]
read-date [RFC2183]
size [RFC2183]
name [RFC7578]
voice [RFC2421]
handling [RFC3204]
preview-type [RFC7763]

調べた範囲では、以下のようでした。

【Mozilla Thunderbird】
filenameパラメータのみ

【Microsoft Outlook】
filenameパラメータ
cteation-dateパラメータ
modification-dateパラメータ
sizeパラメータ

他のメールソフトについて、
Content-Disposition: attachment; の実装状況を
ご教示いただければ幸いです。

あくまでも、現在ご存じの情報だけで構いません。

勝手なお願いで恐縮ですが、よろしくお願いします。

:ID:5U/9Z0Js

>>208
ご質問の「Content-Disposition: attachment; の実装状況」とは「filenameパラメータがない場合において、
添付ファイル名の代替として使用されるContent-Dispositionのヘッダパラメータにはどのようなものがあるか」という
趣旨かと推察いたしますが、各メールソフトの添付ファイル名の扱いがどうなっているか、
特にContent-Dispositionパラメータの利用例となると情報を持っていませんので、残念ながら
ご期待に添える回答ができません。

直接の回答にはなりませんが、ライブラリの機能で添付ファイル名を代替的に決定する場合、という観点で
述べると >>156 にてひとつ例示していますので、改めてご覧いただければと思います。

(この例ではサブジェクトをファイル名に使用していますが、ユニークであることの保証が必要なのであれば
Message-IDヘッダやContent-Typeヘッダのboundaryパラメータなどを組み合わせる、あるいは組み合わせた
値からハッシュ値を求めてそれを使う、などがよいかと思います。 ユーザーフレンドリな形式ではありませんが。)

以上、趣意と異なる回答となっていましたらすみません。

:ID:H1apoqUs

>>209

さっそくのご回答ありがとうございます。

各メールソフトの添付ファイル名の扱いについて、
情報をお持ちでないこと、了解しました。

また、根本的な改修の際には、ぜひとも、
>>156 のサンプルを参考にさせていただく所存です。

取り急ぎ以上簡単ですが、今後ともよろしくお願いします。

  • >>1と入力すると1番へのアンカーになります。
  • 投稿内容はPukiWiki記法で整形されます。
    • 以下のPukiWiki記法が使えます。
      • 引用文
      • 番号付きリスト、番号なしリスト、定義リスト
      • 整形済みテキスト
        • 複数行のコードブロック・コマンド出力を書き込むには#code{{〜}}、#prompt{{〜}}と記入してください。
        • AAを書き込むには#aa{{〜}}と記入してください。
      • 表組み
      • 見出し
      • 強調・斜体、取り消し線・下線
      • 文字色(&color)、文字サイズ(&size)
      • 注釈
    • URL・メールアドレスは自動的にリンクになります。
    • 詳しくはPukiWikiのFormattingRulesを参照してください。
  • 書き込み後に投稿内容を編集することは出来ません。