commit 7a035e6a53449b77cceae1d8045c51c4063b0ab6
parent 4255437a5b252c7268c7ec2743b182982334ccb5
Author: Dan Callaghan <djc@djc.id.au>
Date: Sun, 12 May 2024 14:07:11 +1000
use a fallback date if Date header is missing
If a message doesn't have a usable Date header, we can fall back to
using the modtime of the file for Maildir, or the delivery time which
should have been written to the From line in mbox.
If all else fails, use a fixed date in the past, rather than Time.now.
This will prevent the mails from popping back to the top of the thread
list every time Sup reloads them.
Fixes #596.
Diffstat:
6 files changed, 61 insertions(+), 10 deletions(-)
diff --git a/lib/sup/maildir.rb b/lib/sup/maildir.rb
@@ -114,6 +114,10 @@ class Maildir < Source
with_file_for(id) { |f| f.read }
end
+ def fallback_date_for_message id
+ File.mtime File.join(@dir, id)
+ end
+
## XXX use less memory
def poll
added = []
diff --git a/lib/sup/mbox.rb b/lib/sup/mbox.rb
@@ -1,5 +1,6 @@
require 'uri'
require 'set'
+require 'time'
module Redwood
@@ -137,6 +138,26 @@ class MBox < Source
end
end
+ def fallback_date_for_message offset
+ ## This is a bit awkward... We treat the From line as a delimiter,
+ ## not part of the message. So the offset is pointing *after* the
+ ## From line for the desired message. With a bit of effort we can
+ ## scan backwards to find its From line and extract a date from it.
+ buf = @mutex.synchronize do
+ ensure_open
+ start = offset
+ loop do
+ start = (start - 200).clamp 0, 2**64
+ @f.seek start
+ buf = @f.read (offset - start)
+ break buf if buf.include? ?\n or start == 0
+ end
+ end
+ BREAK_RE.match buf.lines.last do |m|
+ Time.strptime m[1], "%a %b %d %H:%M:%S %Y"
+ end
+ end
+
def default_labels
[:inbox, :unread]
end
diff --git a/lib/sup/message.rb b/lib/sup/message.rb
@@ -103,16 +103,10 @@ class Message
when Time
date
when String
- begin
- Time.parse date
- rescue ArgumentError
- #debug "faking mangled date header for #{@id} (orig #{header['date'].inspect} gave error: #{e.message})"
- Time.now
- end
- else
- #debug "faking non-existent date header for #{@id}"
- Time.now
+ Time.parse date rescue nil
end
+ @date = location.fallback_date if @date.nil?
+ @date = Time.utc 1970, 1, 1 if @date.nil?
subj = header["subject"]
subj = subj ? subj.fix_encoding! : nil
@@ -808,6 +802,10 @@ class Location
source.load_message info
end
+ def fallback_date
+ source.fallback_date_for_message info
+ end
+
def valid?
source.valid? info
end
diff --git a/test/dummy_source.rb b/test/dummy_source.rb
@@ -59,6 +59,10 @@ class DummySource < Source
end
end
end
+
+ def fallback_date_for_message id
+ Time.utc 2001, 2, 3, 4, 56, 57
+ end
end
end
diff --git a/test/integration/test_maildir.rb b/test/integration/test_maildir.rb
@@ -34,7 +34,9 @@ EOS
end
def create_a_maildir_email(folder, content)
- File.write(File.join(folder, "#{Time.now.to_f}.hostname:2,S"), content)
+ filename = File.join folder, "#{Time.now.to_f}.hostname:2,S"
+ File.write filename, content
+ filename
end
def start_sup_and_add_source(source)
@@ -74,5 +76,17 @@ EOS
end
+ def test_missing_date_header
+ ## The message is missing a Date header so we should use its modtime
+ ## as a fallback.
+ fallback_date = Time.new 2004, 4, 19, 11, 12, 13
+ maildir = create_a_maildir
+ filename = create_a_maildir_email(File.join(maildir, 'cur'), @test_message_1)
+ File.utime fallback_date, fallback_date, filename
+ start_sup_and_add_source Maildir.new "maildir:#{maildir}"
+
+ messages_in_index = Index.instance.enum_for(:each_message).to_a
+ assert_equal fallback_date, messages_in_index.first.date
+ end
end
diff --git a/test/integration/test_mbox.rb b/test/integration/test_mbox.rb
@@ -69,4 +69,14 @@ EOS
end
+ def test_missing_date_header
+ ## The message is missing a Date header so we should use envelope date
+ ## stored in the From line as a fallback.
+ fallback_date = Time.new 2009, 4, 27, 12, 56, 18
+ mbox = create_a_mbox
+ start_sup_and_add_source MBox.new "mbox:#{mbox}"
+
+ messages_in_index = Index.instance.enum_for(:each_message).to_a
+ assert_equal fallback_date, messages_in_index.first.date
+ end
end