2013年3月15日 星期五

關於 Thunderbird 在 IMAP 上使用郵件篩選中的自訂標頭的問題

過去一直很困擾這個問題,不過今天找到了一些解決方法,在此記錄一下。

首先來看 thunderbird-17.0.4+build1/mailnews/imap/src/nsImapProtocol.cpp 中的一段程式碼

// read in the accept languages preference
if (prefBranch)
{
  if (!gInitialized)
    GlobalInitialization(prefBranch);

  nsCOMPtr prefString;
  prefBranch->GetComplexValue("intl.accept_languages",
                              NS_GET_IID(nsIPrefLocalizedString),
                              getter_AddRefs(prefString));
  if (prefString)
    prefString->ToString(getter_Copies(mAcceptLanguages));

  nsCString customDBHeaders;
  prefBranch->GetCharPref("mailnews.customDBHeaders",
                          getter_Copies(customDBHeaders));

  ParseString(customDBHeaders, ' ', mCustomDBHeaders);                             
  prefBranch->GetBoolPref("mailnews.display.prefer_plaintext", &m_preferPlainText);
}

這段程式碼裡面使用的是 mailnews.customDBHeaders 但是在使用自訂標頭後是卻是儲存在 mailnews.customHeaders

然後再看一下同一個檔案中的另外一段程式碼

if (!downloadAllHeaders)  // if it's ok -- no filters on any header, etc.
{                                                                                        
  char *headersToDL = nullptr;
  char *what = nullptr;
  const char *dbHeaders = (gUseEnvelopeCmd) ? IMAP_DB_HEADERS : IMAP_ENV_AND_DB_HEADERS;
  nsCString arbitraryHeaders;
  GetArbitraryHeadersToDownload(arbitraryHeaders);
  for (uint32_t i = 0; i < mCustomDBHeaders.Length(); i++)
  {
    if (arbitraryHeaders.Find(mCustomDBHeaders[i], CaseInsensitiveCompare) == kNotFound)
    {
      if (!arbitraryHeaders.IsEmpty())
        arbitraryHeaders.Append(' ');
      arbitraryHeaders.Append(mCustomDBHeaders[i]);
    }
  }
  if (arbitraryHeaders.IsEmpty())
    headersToDL = strdup(dbHeaders);
  else
    headersToDL = PR_smprintf("%s %s",dbHeaders, arbitraryHeaders.get());

  if (gUseEnvelopeCmd)
    what = PR_smprintf(" ENVELOPE BODY.PEEK[HEADER.FIELDS (%s)])", headersToDL);
  else
    what = PR_smprintf(" BODY.PEEK[HEADER.FIELDS (%s)])",headersToDL);
  NS_Free(headersToDL);
  if (what)
  {
    commandString.Append(" %s (UID ");
     if (m_isGmailServer)
      commandString.Append("X-GM-MSGID X-GM-THRID X-GM-LABELS ");
    if (aolImapServer)
      commandString.Append(" XAOL.SIZE") ;
    else
      commandString.Append("RFC822.SIZE");
    commandString.Append(" FLAGS");
    commandString.Append(what);
    PR_Free(what);
  }
  else
  {
    commandString.Append(" %s (UID RFC822.SIZE BODY.PEEK[HEADER] FLAGS)");
  }
}

搭配前一段程式碼一起來看,應該是使用空白當分隔符號來進行不分大小寫的比對,於是我就手動將 mailnews.customHeaders 的內容複製到 mailnews.customDBHeaders 裡面並且去掉了冒號,接著重新啟動 Thunderbird 了。

不過卻發現收件匣當中的郵件還是無法篩選到,於是就注意到前面第二段程式碼最前面的 downloadAllHeaders,想說會不會是因為 IMAP 的關係,之前已經儲存在本地端的郵件的 DB 並沒有儲存這些資訊,應該要設法再讓 Thunderbird 重新抓取郵件的標頭, 於是我就將郵件移往其它的目錄再做篩選,果然就可以正常運作了。

另外我找到了一個似乎有相關的修正 Bug 363238 - saved searches fail for searches on x-headers, r=irving,只是目前它並不存在 Thunderbird 裡面就是了。

2013年3月4日 星期一

關於 Bash 的 Redirection 使用的心得

一般常見的方式是把標準輸出 (stdout) 訊息跟標準錯誤 (stderr) 訊息全部都導進一個檔案裡面,例如以下指令:

$ sudo fdisk -l >fdisk.log 2>&1

如果是寫在一個 Shell Script 的檔案裡面,就可以寫成像是下面這樣:

#!/bin/sh

exec >fdisk.log # 0 (stdin) 1 (fdisk.log) 2 (stderr)
exec 2>&1       # 0 (stdin) 1 (fdisk.log) 2 (fdisk.log)

sudo fdisk -l

但是其實我們也可以使用更複雜的 Redirection,從 bash need STDOUT+STDERR in log, and STDERR to its normal destination 改出來的例子:

$ ((sudo fdisk -l 3>&1 1>&2 2>&3- | tee /dev/fd/2) 3>&1 1>&2 2>&3-) | cat >fdisk.log

這個例子的結果是將標準輸出訊息跟標準錯誤訊息都導進 fdisk.log 裡面,然後將標準錯誤訊息顯示出來。

但是它做了什麼事情?

首先是

sudo fdisk -l 3>&1 1>&2 2>&3-

如果直接用 Shell Script 檔案來寫,會看起來像是這樣:

#!/bin/sh

exec 3>&1  # 0 (stdin) 1 (stdout) 2 (stderr) 3 (stdout)
exec 1>&2  # 0 (stdin) 1 (stderr) 2 (stderr) 3 (stdout)
exec 2>&3- # 0 (stdin) 1 (stderr) 2 (stdout) 3 (closed)

sudo fdisk -l

看起來像是

`sudo fdisk -l` --> stdout ` '-> stdout
                |           X
                `-> stderr ' `-> stderr

它將標準輸出訊息跟標準錯誤訊息互換了,接下來

| tee /dev/fd/2
會收到 pipe 符號前面傳來的標準輸出訊息也就是 `sudo fdisk -l` 的 2 (stdout) 會變成 `tee` 的 0 (stdin),然後 `tee` 會將其內容導到 `tee` 的 2 (stderr) 並且顯示在 `tee` 的 1 (stdout) 於是乎 `sudo fdisk -l` 的標準輸出訊息跟標準錯誤訊息都會被導到 stderr 而標準錯誤訊息則被導到 stdout

看起來像是

                                       `tee`
`sudo fdisk -l` --> stdout ` '-> stdout --> stdout
                |           X           `-> stderr
                `-> stderr ' `-> stderr

接下來再經歷一次的標準輸出訊息跟標準錯誤訊息互換

((...) 3>&1 1>&2 2>&3-)

看起來像是

                                       `tee`
`sudo fdisk -l` --> stdout    -> stdout --> stdout ` '-> stdout
                |          ` '          |           X
                |           X           `-> stderr ' `-> stderr
                `-> stderr ' `-> stderr --> stdout

從最後一張圖就可以看出最後的兩個 stdout 分別來自於 `sudo fdisk -l` 的 stdout 跟 stderr 而最後的一個 stderr 還是來自於 `sudo fdisk -l` 的 stderr

最後一步就是透過 `| cat >fdisk.log` 將兩個 stdout 都導進 fdisk.log 檔案就大功告成啦!:D