Module: Mailmate::EmlLookup

Defined in:
lib/mailmate/eml_lookup.rb

Overview

Map an ‘.eml` body-part ID to its absolute on-disk path.

Two implementations:

- The fast path uses MailMate's `#source` index
  (`Database.noindex/Headers/#source.{cache,offsets}`), which carries the
  `imap://account@host/path` URL for every indexed message. O(1) lookup.
- The fallback walks the IMAP tree with `Dir.glob`, the same shape as the
  `find -name <id>.eml` shell-out the original scripts used. Slower
  (seconds on a cold cache) but always works.

The fast path wins ~99% of the time. The fallback exists for messages MailMate hasn’t yet indexed (e.g. immediately after a fresh IMAP push) and for edge cases where ‘#source` is unreadable.

Class Method Summary collapse

Class Method Details

.eml_id_for_message_id(message_id) ⇒ Object

Reverse-lookup: given an RFC Message-ID (with or without angle brackets), return the local eml-id (integer) or nil. O(n) scan of the message-id index — fine for one-shot CLI lookups; cache the result if you need it repeatedly.



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/mailmate/eml_lookup.rb', line 31

def self.eml_id_for_message_id(message_id)
  needle = message_id.to_s.strip
  return nil if needle.empty?

  candidates = needle.start_with?("<") && needle.end_with?(">") ?
                 [needle, needle[1..-2]] :
                 [needle, "<#{needle}>"]

  Mailmate::IndexReader.for("message-id").each_record do |eml_id, value|
    return eml_id if candidates.include?(value)
  end
  nil
rescue ArgumentError
  nil
end

.path_for(eml_id) ⇒ Object

Returns an absolute path string, or nil if not found.



23
24
25
# File 'lib/mailmate/eml_lookup.rb', line 23

def self.path_for(eml_id)
  via_index(eml_id) || via_glob(eml_id)
end

.resolve_id(input) ⇒ Object

Resolve an identifier to a local eml-id. Accepts:

- eml-id (all digits)              e.g. "183715"
- RFC Message-ID, brackets optional e.g. "<abc@example.com>"
- message://… URL                  e.g. "message://%3Cabc%40example.com%3E"
                                        or "message://183715"
- mid:… URL                        e.g. "mid:%3Cabc%40example.com%3E"
                                        or "mid:183715"

Strips the URL wrapper first (message:// or mid:) so the digit check below catches the local-eml-id payload too — same leniency MailMate’s own URL handler offers. The %3C…%3E payload form is portable across machines; the bare-integer form is local-only.



58
59
60
61
62
63
# File 'lib/mailmate/eml_lookup.rb', line 58

def self.resolve_id(input)
  s = input.to_s.strip
  s = URI.decode_www_form_component(s.sub(%r{\A(?:message://|mid:)}, "")) if s.match?(%r{\A(?:message://|mid:)})
  return s.to_i if s =~ /\A\d+\z/
  eml_id_for_message_id(s)
end

.source_url_for(eml_id) ⇒ Object

Lookup the ‘imap://…` URL recorded in `#source` for this eml_id. Returns nil if the index doesn’t have a record for it.



88
89
90
91
92
93
# File 'lib/mailmate/eml_lookup.rb', line 88

def self.source_url_for(eml_id)
  Mailmate::IndexReader.for("#source").value_for(eml_id)
rescue ArgumentError
  # #source index missing (non-default MailMate install? fresh sync?).
  nil
end

.url_to_path(url, eml_id) ⇒ Object

Convert an ‘imap://account@host/mailbox/path` URL to the on-disk absolute path of the .eml file. Internal but exposed for tests.



97
98
99
100
101
102
103
104
# File 'lib/mailmate/eml_lookup.rb', line 97

def self.url_to_path(url, eml_id)
  stripped = url.sub(%r{\Aimap://}, "")
  , mailbox_path = stripped.split("/", 2)
  return nil if .nil? || mailbox_path.nil? || mailbox_path.empty?

  mailbox_dirs = mailbox_path.split("/").map { |seg| "#{seg}.mailbox" }.join("/")
  File.join(Mailmate.config.imap_root, , mailbox_dirs, "Messages", "#{eml_id}.eml")
end

.via_glob(eml_id) ⇒ Object

Force the glob fallback.



81
82
83
84
# File 'lib/mailmate/eml_lookup.rb', line 81

def self.via_glob(eml_id)
  matches = Dir.glob("#{Mailmate.config.imap_root}/*/**/Messages/#{eml_id}.eml")
  matches.first
end

.via_index(eml_id) ⇒ Object

Force the index path (useful for tests and benchmarking).

Returns nil — not just on missing-index — when the indexed path no longer points at an existing file. That happens whenever MailMate’s ‘#source` index lags the filesystem; the common case is right after `mm-modify`’s fast-path move renames the .eml on disk and MailMate hasn’t rescanned yet. Treating stale entries as misses lets ‘path_for` fall through to the glob walker and recover the file at its new home.



73
74
75
76
77
78
# File 'lib/mailmate/eml_lookup.rb', line 73

def self.via_index(eml_id)
  url = source_url_for(eml_id)
  return nil if url.nil?
  path = url_to_path(url, eml_id)
  path if path && File.exist?(path)
end