Codebase list feed2imap / 464fafb
Imported Upstream version 1.1 Antonio Terceiro 10 years ago
17 changed file(s) with 323 addition(s) and 87 deletion(s). Raw diff Collapse all Expand all
00 require 'rake/testtask'
1 require 'rake/rdoctask'
1 require 'rdoc/task'
22 require 'rake/packagetask'
33 require 'rake'
44 require 'find'
55
6 task :default => [:package]
6 task :default => [:test]
77
88 PKG_NAME = 'feed2imap'
9 PKG_VERSION = '1.0'
9 PKG_VERSION = '1.1'
1010 PKG_FILES = [ 'ChangeLog', 'README', 'COPYING', 'setup.rb', 'Rakefile']
1111 Find.find('bin/', 'lib/', 'test/', 'data/') do |f|
1212 if FileTest.directory?(f) and f =~ /\.svn/
1818 Rake::TestTask.new do |t|
1919 t.libs << "libs/feed2imap"
2020 t.libs << "test"
21 t.test_files = FileList['test/tc_*.rb']
21 t.test_files = FileList['test/tc_*.rb'] - ['test/tc_httpfetcher.rb']
2222 end
2323
24 Rake::RDocTask.new do |rd|
24 RDoc::Task.new do |rd|
2525 rd.main = 'README'
2626 rd.rdoc_files.include('lib/*.rb', 'lib/feed2imap/*.rb')
2727 rd.options << '--all'
4040
4141 # "Gem" part of the Rakefile
4242 begin
43 require 'rake/gempackagetask'
43 require 'rubygems/package_task'
4444
4545 spec = Gem::Specification.new do |s|
4646 s.platform = Gem::Platform::RUBY
4949 s.version = PKG_VERSION
5050 s.requirements << 'feedparser'
5151 s.require_path = 'lib'
52 s.executables = PKG_FILES.grep(%r{\Abin\/.}).map { |bin|
53 bin.gsub(%r{\Abin/}, '')
54 }
5255 s.files = PKG_FILES
5356 s.description = "RSS/Atom feed aggregator"
57 s.authors = ['Lucas Nussbaum']
5458 end
5559
56 Rake::GemPackageTask.new(spec) do |pkg|
60 Gem::PackageTask.new(spec) do |pkg|
5761 pkg.need_zip = true
5862 pkg.need_tar = true
5963 end
66 # debug-updated: (for debugging purposes) if true, display a lot of information
77 # about the "updated-items" algorithm.
88 # include-images: download images and include them in the mail? (true/false)
9 # reupload-if-updated: when an item is updated, and was previously deleted,
10 # reupload it? (true/false, default true)
911 # default-email: default email address in the format foo@example.com
1012 # disable-ssl-verification: disable SSL certification when connecting
1113 # to IMAPS accounts (true/false)
14 # timeout: time before getting timeout when fetching feeds (default 30) in seconds
1215 #
1316 # Per-feed options:
1417 # name: name of the feed (must be unique)
1922 # feed will be fetched
2023 # disable: if set to something, the feed will be ignored
2124 # include-images: download images and include them in the mail? (true/false)
25 # reupload-if-updated: when an item is updated, and was previously deleted,
26 # reupload it? (true/false, default true)
2227 # always-new: feed2imap tries to use a clever algorithm to determine whether
2328 # an item is new or has been updated. It doesn't work well with some web apps
2429 # like mediawiki. When this flag is enabled, all items which don't match
6267 # - name: test2
6368 # target: [ *target, 'test2' ]
6469 # ...
70
71 # vim: ft=yaml:sts=2:expandtab
192192 @itemstemp.unshift(j)
193193 break
194194 end
195 end
196 next if found
197 if not always_new
198 # Try to find an updated item
199 @items.each do |j|
200 # Do we need a better heuristic ?
201 if j.is_ancestor_of(i)
202 i.cacheditem.index = j.index
203 i.cacheditem.updated = true
204 updateditems.push(i)
205 found = true
206 # let's put j in front of itemstemp
207 @itemstemp.delete(j)
208 @itemstemp.unshift(i.cacheditem)
209 break
210 end
195 # If we didn't find exact match, try to check if we have an update
196 if j.is_ancestor_of(i)
197 i.cacheditem.index = j.index
198 i.cacheditem.updated = true
199 updateditems.push(i)
200 found = true
201 # let's put j in front of itemstemp
202 @itemstemp.delete(j)
203 @itemstemp.unshift(i.cacheditem)
204 break
211205 end
212206 end
213207 next if found
2222 require 'feed2imap/maildir'
2323 require 'etc'
2424 require 'socket'
25 require 'set'
2526
2627 # Default cache file
2728 DEFCACHE = ENV['HOME'] + '/.feed2imap.cache'
3233
3334 # Feed2imap configuration
3435 class F2IConfig
35 attr_reader :imap_accounts, :cache, :feeds, :dumpdir, :updateddebug, :max_failures, :include_images, :default_email, :hostname
36 attr_reader :imap_accounts, :cache, :feeds, :dumpdir, :updateddebug, :max_failures, :include_images, :default_email, :hostname, :reupload_if_updated, :parts, :timeout
3637
3738 # Load the configuration from the IO stream
3839 # TODO should do some sanity check on the data read.
4344 @conf['feeds'] ||= []
4445 @feeds = []
4546 @max_failures = (@conf['max-failures'] || 10).to_i
46 @updateddebug = (@conf['debug-updated'] and @conf['debug-updated'] != 'false')
47 @include_images = (@conf['include-images'] and @conf['include-images'] != 'false')
47
48 @updateddebug = false
49 @updateddebug = @conf['debug-updated'] if @conf.has_key?('debug-updated')
50
51 @parts = %w(text html)
52 @parts = Array(@conf['parts']) if @conf.has_key?('parts') && !@conf['parts'].empty?
53 @parts = Set.new(@parts)
54
55 @include_images = true
56 @include_images = @conf['include-images'] if @conf.has_key?('include-images')
57 @parts << 'html' if @include_images && ! @parts.include?('html')
58
59 @reupload_if_updated = true
60 @reupload_if_updated = @conf['reupload-if-updated'] if @conf.has_key?('reupload-if-updated')
61
62 @timeout = if @conf['timeout'] == nil then 30 else @conf['timeout'].to_i end
63
4864 @default_email = (@conf['default-email'] || "#{LOGNAME}@#{HOSTNAME}")
49 ImapAccount.no_ssl_verify = (@conf['disable-ssl-verification'] and @conf['disable-ssl-verification'] != 'false')
65 ImapAccount.no_ssl_verify = (@conf.has_key?('disable-ssl-verification') and @conf['disable-ssl-verification'] == true)
5066 @hostname = HOSTNAME # FIXME: should this be configurable as well?
5167 @imap_accounts = ImapAccounts::new
5268 maildir_account = MaildirAccount::new
5470 if f['disable'].nil?
5571 uri = URI::parse(f['target'].to_s)
5672 path = URI::unescape(uri.path)
57 path = path[1..-1] if path[0,1] == '/'
5873 if uri.scheme == 'maildir'
5974 @feeds.push(ConfigFeed::new(f, maildir_account, path, self))
6075 else
76 # remove leading slash from IMAP mailbox names
77 path = path[1..-1] if path[0,1] == '/'
6178 @feeds.push(ConfigFeed::new(f, @imap_accounts.add_account(uri), path, self))
6279 end
6380 end
93110
94111 # A configured feed. simple data container.
95112 class ConfigFeed
96 attr_reader :name, :url, :imapaccount, :folder, :always_new, :execurl, :filter, :ignore_hash, :dumpdir, :wrapto, :include_images
113 attr_reader :name, :url, :imapaccount, :folder, :always_new, :execurl, :filter, :ignore_hash, :dumpdir, :wrapto, :include_images, :reupload_if_updated
97114 attr_accessor :body
98115
99116 def initialize(f, imapaccount, folder, f2iconfig)
100117 @name = f['name']
101118 @url = f['url']
102119 @url.sub!(/^feed:/, '') if @url =~ /^feed:/
103 @imapaccount, @folder = imapaccount, folder
120 @imapaccount = imapaccount
121 @folder = encode_utf7 folder
104122 @freq = f['min-frequency']
105 @always_new = (f['always-new'] and f['always-new'] != 'false')
123
124 @always_new = false
125 @always_new = f['always-new'] if f.has_key?('always-new')
126
106127 @execurl = f['execurl']
107128 @filter = f['filter']
108 @ignore_hash = f['ignore-hash'] || false
129
130 @ignore_hash = false
131 @ignore_hash = f['ignore-hash'] if f.has_key?('ignore-hash')
132
109133 @freq = @freq.to_i if @freq
110134 @dumpdir = f['dumpdir'] || nil
111135 @wrapto = if f['wrapto'] == nil then 72 else f['wrapto'].to_i end
136
112137 @include_images = f2iconfig.include_images
113 if f['include-images']
114 @include_images = (f['include-images'] != 'false')
115 end
138 @include_images = f['include-images'] if f.has_key?('include-images')
139
140 @reupload_if_updated = f2iconfig.reupload_if_updated
141 @reupload_if_updated = f['reupload-if-updated'] if f.has_key?('reupload-if-updated')
142
116143 end
117144
118145 def needfetch(lastcheck)
119146 return true if @freq.nil?
120147 return (lastcheck + @freq * 3600) < Time::now
121148 end
149
150 def encode_utf7(s)
151 if "foo".respond_to?(:force_encoding)
152 return Net::IMAP::encode_utf7 s
153 else
154 # this is a copy of the Net::IMAP::encode_utf7 w/o the force_encoding
155 return s.gsub(/(&)|([^\x20-\x7e]+)/u) {
156 if $1
157 "&-"
158 else
159 base64 = [$&.unpack("U*").pack("n*")].pack("m")
160 "&" + base64.delete("=\n").tr("/", ",") + "-"
161 end }
162 end
163 end
122164 end
120120 end
121121 fetch_start = Time::now
122122 if feed.url
123 s = HTTPFetcher::fetch(feed.url, @cache.get_last_check(feed.name))
123 fetcher = HTTPFetcher::new
124 fetcher::timeout = @config.timeout
125 s = fetcher::fetch(feed.url, @cache.get_last_check(feed.name))
124126 elsif feed.execurl
125127 # avoid running more than one command at the same time.
126128 # We need it because the called command might not be
220222 next
221223 end
222224 begin
223 feed = FeedParser::Feed::new(f.body)
225 feed = FeedParser::Feed::new(f.body.force_encoding('UTF-8'))
224226 rescue Exception
225227 n = @cache.parse_failed(f.name)
226228 m = "Error while parsing #{f.name}: #{$!} (failed #{n} times)"
246248 id = "<#{fn}-#{i.cacheditem.index}@#{@config.hostname}>"
247249 email = item_to_mail(@config, i, id, true, f.name, f.include_images, f.wrapto)
248250 f.imapaccount.updatemail(f.folder, email,
249 id, i.date || Time::new)
251 id, i.date || Time::new, f.reupload_if_updated)
250252 end
251253 # reverse is needed to upload older items first (fixes gna#8986)
252254 newitems.reverse.each do |i|
1616 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
1717 =end
1818
19 require 'zlib'
1920 require 'net/http'
2021 # get openssl if available
2122 begin
3233
3334 # Class used to retrieve the feed over HTTP
3435 class HTTPFetcher
35 def HTTPFetcher::fetcher(baseuri, uri, lastcheck, recursion)
36
37 @timeout = 30 # should be enough for everybody...
38
39 def timeout=(value)
40 @timeout = value
41 end
42
43 def fetcher(baseuri, uri, lastcheck, recursion)
3644 proxy_host = nil
3745 proxy_port = nil
3846 proxy_user = nil
4856 proxy_port,
4957 proxy_user,
5058 proxy_pass ).new(uri.host, uri.port)
51 http.read_timeout = 30 # should be enough for everybody...
52 http.open_timeout = 30
59 http.read_timeout = @timeout
60 http.open_timeout = @timeout
5361 if uri.scheme == 'https'
5462 http.use_ssl = true
5563 http.verify_mode = OpenSSL::SSL::VERIFY_NONE
6068 useragent = 'Feed2Imap http://home.gna.org/feed2imap/'
6169 end
6270
63 if lastcheck == Time::at(0)
64 req = Net::HTTP::Get::new(uri.request_uri, {'User-Agent' => useragent })
65 else
66 req = Net::HTTP::Get::new(uri.request_uri, {'User-Agent' => useragent, 'If-Modified-Since' => lastcheck.httpdate})
71 headers = {
72 'User-Agent' => useragent,
73 'Accept-Encoding' => 'gzip',
74 }
75 if lastcheck != Time::at(0)
76 headers.merge!('If-Modified-Since' => lastcheck.httpdate)
6777 end
78 req = Net::HTTP::Get::new(uri.request_uri, headers)
6879 if uri.userinfo
6980 login, pw = uri.userinfo.split(':')
7081 req.basic_auth(login, pw)
8091 end
8192 case response
8293 when Net::HTTPSuccess
83 return response.body
94 case response['Content-Encoding']
95 when 'gzip'
96 return Zlib::GzipReader.new(StringIO.new(response.body)).read
97 else
98 return response.body
99 end
84100 when Net::HTTPRedirection
85101 # if not modified
86102 if Net::HTTPNotModified === response
98114 end
99115 end
100116
101 def HTTPFetcher::fetch(url, lastcheck)
117 def fetch(url, lastcheck)
102118 uri = URI::parse(url)
103 return HTTPFetcher::fetcher(uri, uri, lastcheck, MAXREDIR)
119 return fetcher(uri, uri, lastcheck, MAXREDIR)
104120 end
105121 end
107107 end
108108
109109 # update a mail
110 def updatemail(folder, mail, id, date = Time::now)
110 def updatemail(folder, mail, id, date = Time::now, reupload_if_updated = true)
111111 create_folder_if_not_exists(folder)
112112 @connection.select(folder)
113113 searchres = @connection.search(['HEADER', 'Message-Id', id])
118118 searchres.each { |m| @connection.store(m, "+FLAGS", [:Deleted]) }
119119 @connection.expunge
120120 flags -= [ :Recent ] # avoids errors with dovecot
121 elsif not reupload_if_updated
122 # mail not present, and we don't want to re-upload it
123 return
121124 end
122125 @connection.append(folder, mail.gsub(/\n/, "\r\n"), flags, date)
123126 end
140143 d = f[0].attr['INTERNALDATE']
141144 s = f[0].attr['ENVELOPE'].subject
142145 if s =~ /^=\?utf-8\?b\?/
143 s = Base64::decode64(s.gsub(/^=\?utf-8\?b\?(.*)\?=$/, '\1')).toISO_8859_1('utf-8')
146 s = Base64::decode64(s.gsub(/^=\?utf-8\?b\?(.*)\?=$/, '\1')).force_encoding('utf-8')
147 elsif s =~ /^=\?iso-8859-1\?b\?/
148 s = Base64::decode64(s.gsub(/^=\?iso-8859-1\?b\?(.*)\?=$/, '\1')).force_encoding('iso-8859-1').encode('utf-8')
144149 end
145150 if dryrun
146151 puts "To remove: #{s} (#{d})"
7676 message.header['Subject'] = subj
7777 end
7878 end
79 textpart = RMail::Message::new
80 textpart.header['Content-Type'] = 'text/plain; charset=utf-8; format=flowed'
81 textpart.header['Content-Transfer-Encoding'] = '8bit'
82 textpart.body = item.to_text(true, wrapto, false)
83 htmlpart = RMail::Message::new
84 htmlpart.header['Content-Type'] = 'text/html; charset=utf-8'
85 htmlpart.header['Content-Transfer-Encoding'] = '8bit'
86 htmlpart.body = item.to_html
79 textpart = htmlpart = nil
80 parts = config.parts
81 if parts.include?('text')
82 textpart = parts.size == 1 ? message : RMail::Message::new
83 textpart.header['Content-Type'] = 'text/plain; charset=utf-8; format=flowed'
84 textpart.header['Content-Transfer-Encoding'] = '8bit'
85 textpart.body = item.to_text(true, wrapto, false)
86 end
87 if parts.include?('html')
88 htmlpart = parts.size == 1 ? message : RMail::Message::new
89 htmlpart.header['Content-Type'] = 'text/html; charset=utf-8'
90 htmlpart.header['Content-Transfer-Encoding'] = '8bit'
91 htmlpart.body = item.to_html
92 end
8793
8894 # inline images as attachments
8995 imgs = []
126132 imgs.each do |i|
127133 message.add_part(i)
128134 end
129 else
135 elsif parts.size != 1
130136 message.header['Content-Type'] = 'multipart/alternative'
131137 message.add_part(textpart)
132138 message.add_part(htmlpart)
1818 require 'uri'
1919 require 'fileutils'
2020 require 'fcntl'
21 require 'rmail'
22 require 'socket'
2123
2224 class MaildirAccount
2325 MYHOSTNAME = Socket.gethostname
26
27 @@seq_num = 0
2428
2529 attr_reader :uri
2630
3034 end
3135 end
3236
33 def updatemail(folder, mail, idx, date = Time::now)
37 def updatemail(folder, mail, idx, date = Time::now, reupload_if_updated = true)
3438 dir = folder_dir(folder)
3539 guarantee_maildir(dir)
3640 mail_files = find_mails(dir, idx)
3943 # get the info from the first result and delete everything
4044 info = maildir_file_info(mail_files[0])
4145 mail_files.each { |f| File.delete(File.join(dir, f)) }
46 elsif not reupload_if_updated
47 # mail not present, and we don't want to re-upload it
48 return
4249 end
4350 store_message(dir, date, info) { |f| f.puts(mail) }
4451 end
6168 next if (not flags.index('S') or
6269 flags.index('F') or
6370 mtime > recent_time)
64 File.open(fn) do |f|
65 mail = RMail::Parser.read(f)
71 mail = File.open(fn) do |f|
72 RMail::Parser.read(f)
6673 end
74 subject = mail.header['Subject']
6775 if dryrun
6876 puts "To remove: #{subject} #{mtime}"
6977 else
8391 end
8492
8593 def store_message(dir, date, info, &block)
86 # TODO: handle `date'
8794
8895 guarantee_maildir(dir)
8996
9299 timer = 30
93100 fd = nil
94101 while timer >= 0
95 new_fn = new_maildir_basefn
102 new_fn = new_maildir_basefn(date)
96103 tmp_path = File.join(dir, 'tmp', new_fn)
97104 new_path = File.join(dir, 'new', new_fn)
98105 begin
136143 Dir[File.join(subdir, '*')].each do |fn|
137144 File.open(fn) do |f|
138145 mail = RMail::Parser.read(f)
139 cache_index = mail.header['Message-Id']
140 next if not (cache_index and cache_index == idx)
141 dir_paths.push(File.join(d, File.basename(fn)))
146 cache_index = mail.header['Message-ID']
147 if cache_index && (cache_index == idx || cache_index == "<#{idx}>")
148 dir_paths.push(File.join(d, File.basename(fn)))
149 end
142150 end
143151 end
144152 end
156164 basename = File.basename(file)
157165 colon = basename.rindex(':')
158166
159 return (colon and basename.slice(colon + 1, -1))
167 return (colon and basename[colon + 1 .. -1])
160168 end
161169
162 # Shamelessly taken from
170 # Re-written and no longer shamelessly taken from
163171 # http://gitorious.org/sup/mainline/blobs/master/lib/sup/maildir.rb
164 def new_maildir_basefn
165 Kernel::srand()
166 "#{Time.now.to_i.to_s}.#{$$}#{Kernel.rand(1000000)}.#{MYHOSTNAME}"
172 def new_maildir_basefn(date)
173 fn = "#{date.to_i.to_s}.#{@@seq_num.to_s}.#{MYHOSTNAME}"
174 @@seq_num += 1
175 fn
167176 end
177
178 def maildir_file_info_flags(fn)
179 parts = fn.split(',')
180 if parts.size == 1
181 ''
182 else
183 parts.last
184 end
185 end
186
168187 end
169188
2525 module REXML
2626 module Encoding
2727 def decode(str)
28 return str.toUTF8(@encoding)
28 return str.encode(@encoding)
2929 end
3030
3131 def encode(str)
0 Date: Mon, 12 Aug 2013 16:25:20 +0200
1 From: Antonio Terceiro <terceiro@debian.org>
2 To: terceiro@debian.org
3 Subject: UTF-8 data: =?iso-8859-1?B?4ent8/o=?=
4 Message-ID: <regular-message-id@debian.org>
5 MIME-Version: 1.0
6 Content-Type: text/plain; charset=us-ascii
7 Content-Disposition: inline
8 User-Agent: Mutt/1.5.21 (2010-09-15)
9
10 This is a sample email
0 Date: Mon, 12 Aug 2013 16:52:17 +0200
1 From: Antonio Terceiro <terceiro@debian.org>
2 To: terceiro@debian.org
3 Subject: an unread message
4 Message-ID: <unread-message-id@debian.org>
5 MIME-Version: 1.0
6 Content-Type: text/plain; charset=us-ascii
7 Content-Disposition: inline
8 User-Agent: Mutt/1.5.21 (2010-09-15)
9
10 This message was not read yet
0 Date: Mon, 12 Aug 2013 17:07:02 +0200
1 From: Antonio Terceiro <terceiro@debian.org>
2 To: terceiro@debian.org
3 Subject: a flagged message
4 Message-ID: <flagged-message-id@debian.org>
5 MIME-Version: 1.0
6 Content-Type: text/plain; charset=us-ascii
7 Content-Disposition: inline
8 User-Agent: Mutt/1.5.21 (2010-09-15)
9
10 This message is flagged.
0 Date: Mon, 12 Aug 2013 17:08:19 +0200
1 From: Antonio Terceiro <terceiro@debian.org>
2 To: terceiro@debian.org
3 Subject: a new message
4 Message-ID: <new-message-id@debian.org>
5 MIME-Version: 1.0
6 Content-Type: text/plain; charset=us-ascii
7 Content-Disposition: inline
8 User-Agent: Mutt/1.5.21 (2010-09-15)
9
10 This message is new
3333 url: http://something2
3434 target: imaps://login:pasword@ezaezae/Feeds/B
3535 EOF
36 CONFPARTS = <<EOF
37 parts: text
38 include-images: false
39 feeds:
40 - name: feed1
41 url: http://something
42 target: imap://login:pasword@ezaezae/Feeds/A
43 - name: feed2
44 url: http://something2
45 target: imap://login:pasword@ezaezae/Feeds/B
46 EOF
3647
3748 class ConfigTest < Test::Unit::TestCase
3849 def test_cache
6071 assert_equal('http://something', conf.feeds[0].url)
6172 assert_equal('http://something2', conf.feeds[1].url)
6273 end
74
75 def test_parts
76 sio = StringIO::new CONFPARTS
77 conf = F2IConfig::new(sio)
78 assert conf.parts.include?('text')
79 assert ! conf.parts.include?('html')
80 end
6381 end
+0
-14
test/tc_mail.rb less more
0 #!/usr/bin/ruby -w
1
2 $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
3
4 require 'test/unit'
5 require 'rmail'
6
7 class MailTest < Test::Unit::TestCase
8 def test_require_rmail
9 # let's just test Rubymail is loaded
10 m = RMail::Message::new
11 assert_equal(m.class, RMail::Message)
12 end
13 end
0 require 'test/unit'
1 require 'fileutils'
2 require 'tmpdir'
3 require 'mocha/setup'
4
5 require 'feed2imap/maildir'
6
7 class TestMaildir < Test::Unit::TestCase
8
9 def setup
10 @tmpdirs = []
11 end
12
13 def tear_down
14 @tmpdirs.each do |dir|
15 FileUtils.rm_rf(dir)
16 end
17 end
18
19 def test_cleanup
20 folder = create_maildir
21 msgs = message_count(folder)
22
23 maildir_account.cleanup(folder)
24
25 assert_equal msgs - 1, message_count(folder)
26 end
27
28 def test_putmail
29 folder = create_maildir
30 msgs = message_count(folder)
31
32 mail = RMail::Message.new
33 mail.header['Subject'] = 'a message I just created'
34 mail.body = 'to test maildir'
35 maildir_account.putmail(folder, mail)
36
37 assert_equal msgs + 1, message_count(folder)
38 end
39
40 def test_updatemail
41 folder = create_maildir
42 path = maildir_account.send(
43 :find_mails,
44 folder,
45 'regular-message-id@debian.org'
46 ).first
47 assert_not_nil path
48 mail = RMail::Message.new
49 mail.header['Subject'] = 'a different subject'
50 mail.header['Message-ID'] = 'regular-message-id@debian.org'
51 mail.body = 'This is the body of the message'
52 maildir_account.updatemail(folder, mail, 'regular-message-id@debian.org')
53
54 updated_path = maildir_account.send(
55 :find_mails,
56 folder,
57 'regular-message-id@debian.org'
58 ).first
59 updated_mail = RMail::Parser.read(File.open(File.join(folder, updated_path)))
60
61 assert_equal 'a different subject', updated_mail.header['Subject']
62 end
63
64 def test_find_mails
65 folder = create_maildir
66 assert_equal 0, maildir_account.send(:find_mails, folder, 'SomeRandomMessageID').size
67 end
68
69 private
70
71 def create_maildir
72 parent = Dir.mktmpdir
73 @tmpdirs << parent
74 FileUtils.cp_r('test/maildir', parent)
75 return File.join(parent, 'maildir')
76 end
77
78 def message_count(folder)
79 Dir.glob(File.join(folder, '**', '*')).reject { |f| File.directory?(f) }.size
80 end
81
82 def maildir_account
83 @maildir_account ||=
84 begin
85 MaildirAccount.new.tap do |account|
86 account.stubs(:puts)
87 end
88 end
89 end
90
91 end
92