main
 1# frozen_string_literal: true
 2
 3module Minbox
 4  class Inbox
 5    include Enumerable
 6
 7    def self.instance(root_dir:)
 8      @instances ||= {}
 9      @instances[root_dir] ||= new(root_dir: root_dir)
10    end
11
12    def initialize(root_dir:)
13      @semaphore = Mutex.new
14      start_listening(root_dir)
15      empty!
16    end
17
18    def emails(count: 0)
19      wait_until { |x| x.count >= count } if count.positive?
20      with_lock(&:values)
21    end
22
23    def wait_until(seconds: 10, wait: 0.1)
24      iterations = (seconds / wait).to_i
25      iterations.times do
26        result = yield(self)
27        return result if result
28
29        sleep wait
30      end
31      nil
32    end
33
34    def wait_until!(seconds: 10, wait: 0.1, &block)
35      raise "timeout: expired" unless wait_until(seconds: seconds, wait: wait, &block)
36    end
37
38    def open(subject:)
39      wait_until do
40        emails.find do |email|
41          subject.is_a?(String) ? email.subject == subject : email.subject.match?(subject)
42        end
43      end
44    end
45
46    def empty!
47      with_lock do
48        @emails = {}
49      end
50    end
51
52    def each
53      @emails.each do |_id, email|
54        yield email
55      end
56    end
57
58    private
59
60    def changed(_modified, added, removed)
61      with_lock do |emails|
62        added.each do |file|
63          mail = Mail.read(file)
64          Minbox.logger.debug("Received: #{mail.subject}")
65          emails[File.basename(file)] = mail
66        end
67        removed.each do |file|
68          emails.delete(File.basename(file))
69        end
70      end
71    end
72
73    def listener_for(dir)
74      ::Listen.to(File.expand_path(dir), only: /\.eml$/, &method(:changed))
75    end
76
77    def start_listening(root_dir)
78      listener_for(root_dir).start
79    end
80
81    def with_lock
82      @semaphore.synchronize do
83        yield @emails if block_given?
84      end
85    end
86  end
87end