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
-
.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.
-
.path_for(eml_id) ⇒ Object
Returns an absolute path string, or nil if not found.
-
.resolve_id(input) ⇒ Object
Resolve an identifier to a local eml-id.
-
.source_url_for(eml_id) ⇒ Object
Lookup the ‘imap://…` URL recorded in `#source` for this eml_id.
-
.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.
-
.via_glob(eml_id) ⇒ Object
Force the glob fallback.
-
.via_index(eml_id) ⇒ Object
Force the index path (useful for tests and benchmarking).
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.() needle = .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/ (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://}, "") account, mailbox_path = stripped.split("/", 2) return nil if account.nil? || mailbox_path.nil? || mailbox_path.empty? mailbox_dirs = mailbox_path.split("/").map { |seg| "#{seg}.mailbox" }.join("/") File.join(Mailmate.config.imap_root, account, 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 |