mournmail 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
 - data/README.md +46 -27
 - data/lib/mournmail.rb +2 -0
 - data/lib/mournmail/commands.rb +7 -4
 - data/lib/mournmail/config.rb +2 -4
 - data/lib/mournmail/draft_mode.rb +56 -13
 - data/lib/mournmail/mail_encoded_word_patch.rb +73 -0
 - data/lib/mournmail/message_mode.rb +3 -2
 - data/lib/mournmail/message_rendering.rb +22 -15
 - data/lib/mournmail/search_result_mode.rb +144 -0
 - data/lib/mournmail/summary.rb +63 -8
 - data/lib/mournmail/summary_mode.rb +406 -38
 - data/lib/mournmail/utils.rb +279 -38
 - data/lib/mournmail/version.rb +1 -1
 - data/mournmail.gemspec +1 -1
 - metadata +6 -5
 
    
        data/lib/mournmail/utils.rb
    CHANGED
    
    | 
         @@ -1,11 +1,13 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
3 
     | 
    
         
             
            require "mail"
         
     | 
| 
       4 
     | 
    
         
            -
            require "mail-iso-2022-jp"
         
     | 
| 
       5 
4 
     | 
    
         
             
            require "net/imap"
         
     | 
| 
       6 
5 
     | 
    
         
             
            require "time"
         
     | 
| 
      
 6 
     | 
    
         
            +
            require "tempfile"
         
     | 
| 
       7 
7 
     | 
    
         
             
            require "fileutils"
         
     | 
| 
       8 
8 
     | 
    
         
             
            require "timeout"
         
     | 
| 
      
 9 
     | 
    
         
            +
            require "digest"
         
     | 
| 
      
 10 
     | 
    
         
            +
            require "groonga"
         
     | 
| 
       9 
11 
     | 
    
         | 
| 
       10 
12 
     | 
    
         
             
            module Mournmail
         
     | 
| 
       11 
13 
     | 
    
         
             
              begin
         
     | 
| 
         @@ -15,23 +17,37 @@ module Mournmail 
     | 
|
| 
       15 
17 
     | 
    
         
             
                HAVE_MAIL_GPG = false
         
     | 
| 
       16 
18 
     | 
    
         
             
              end
         
     | 
| 
       17 
19 
     | 
    
         | 
| 
       18 
     | 
    
         
            -
              def self.define_variable(name,  
     | 
| 
      
 20 
     | 
    
         
            +
              def self.define_variable(name, initial_value: nil, attr: nil)
         
     | 
| 
       19 
21 
     | 
    
         
             
                var_name = "@" + name.to_s
         
     | 
| 
       20 
22 
     | 
    
         
             
                if !instance_variable_defined?(var_name)
         
     | 
| 
       21 
     | 
    
         
            -
                  instance_variable_set(var_name,  
     | 
| 
      
 23 
     | 
    
         
            +
                  instance_variable_set(var_name, initial_value)
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
                case attr
         
     | 
| 
      
 26 
     | 
    
         
            +
                when :accessor
         
     | 
| 
      
 27 
     | 
    
         
            +
                  singleton_class.send(:attr_accessor, name)
         
     | 
| 
      
 28 
     | 
    
         
            +
                when :reader
         
     | 
| 
      
 29 
     | 
    
         
            +
                  singleton_class.send(:attr_reader, name)
         
     | 
| 
      
 30 
     | 
    
         
            +
                when :writer
         
     | 
| 
      
 31 
     | 
    
         
            +
                  singleton_class.send(:attr_writer, name)
         
     | 
| 
       22 
32 
     | 
    
         
             
                end
         
     | 
| 
       23 
     | 
    
         
            -
                singleton_class.send(:attr_accessor, name)
         
     | 
| 
       24 
33 
     | 
    
         
             
              end
         
     | 
| 
       25 
34 
     | 
    
         | 
| 
       26 
     | 
    
         
            -
              define_variable :current_mailbox
         
     | 
| 
       27 
     | 
    
         
            -
              define_variable :current_summary
         
     | 
| 
       28 
     | 
    
         
            -
              define_variable :current_uid
         
     | 
| 
       29 
     | 
    
         
            -
              define_variable :current_mail
         
     | 
| 
       30 
     | 
    
         
            -
              define_variable :background_thread
         
     | 
| 
      
 35 
     | 
    
         
            +
              define_variable :current_mailbox, attr: :accessor
         
     | 
| 
      
 36 
     | 
    
         
            +
              define_variable :current_summary, attr: :accessor
         
     | 
| 
      
 37 
     | 
    
         
            +
              define_variable :current_uid, attr: :accessor
         
     | 
| 
      
 38 
     | 
    
         
            +
              define_variable :current_mail, attr: :accessor
         
     | 
| 
      
 39 
     | 
    
         
            +
              define_variable :background_thread, attr: :accessor
         
     | 
| 
      
 40 
     | 
    
         
            +
              define_variable :keep_alive_thread, attr: :accessor
         
     | 
| 
      
 41 
     | 
    
         
            +
              define_variable :imap
         
     | 
| 
      
 42 
     | 
    
         
            +
              define_variable :imap_mutex, initial_value: Mutex.new
         
     | 
| 
      
 43 
     | 
    
         
            +
              define_variable :mailboxes, initial_value: []
         
     | 
| 
      
 44 
     | 
    
         
            +
              define_variable :current_account
         
     | 
| 
      
 45 
     | 
    
         
            +
              define_variable :account_config
         
     | 
| 
      
 46 
     | 
    
         
            +
              define_variable :groonga_db
         
     | 
| 
       31 
47 
     | 
    
         | 
| 
       32 
     | 
    
         
            -
              def self.background
         
     | 
| 
      
 48 
     | 
    
         
            +
              def self.background(skip_if_busy: false)
         
     | 
| 
       33 
49 
     | 
    
         
             
                if background_thread&.alive?
         
     | 
| 
       34 
     | 
    
         
            -
                   
     | 
| 
      
 50 
     | 
    
         
            +
                  return if skip_if_busy
         
     | 
| 
       35 
51 
     | 
    
         
             
                end
         
     | 
| 
       36 
52 
     | 
    
         
             
                self.background_thread = Utils.background {
         
     | 
| 
       37 
53 
     | 
    
         
             
                  begin
         
     | 
| 
         @@ -42,6 +58,33 @@ module Mournmail 
     | 
|
| 
       42 
58 
     | 
    
         
             
                }
         
     | 
| 
       43 
59 
     | 
    
         
             
              end
         
     | 
| 
       44 
60 
     | 
    
         | 
| 
      
 61 
     | 
    
         
            +
              def self.start_keep_alive_thread
         
     | 
| 
      
 62 
     | 
    
         
            +
                if keep_alive_thread
         
     | 
| 
      
 63 
     | 
    
         
            +
                  raise EditorError, "Keep alive thread already running"
         
     | 
| 
      
 64 
     | 
    
         
            +
                end
         
     | 
| 
      
 65 
     | 
    
         
            +
                self.keep_alive_thread = Thread.start {
         
     | 
| 
      
 66 
     | 
    
         
            +
                  loop do
         
     | 
| 
      
 67 
     | 
    
         
            +
                    sleep(CONFIG[:mournmail_keep_alive_interval])
         
     | 
| 
      
 68 
     | 
    
         
            +
                    background(skip_if_busy: true) do
         
     | 
| 
      
 69 
     | 
    
         
            +
                      begin
         
     | 
| 
      
 70 
     | 
    
         
            +
                        imap_connect do |imap|
         
     | 
| 
      
 71 
     | 
    
         
            +
                          imap.noop
         
     | 
| 
      
 72 
     | 
    
         
            +
                        end
         
     | 
| 
      
 73 
     | 
    
         
            +
                      rescue => e
         
     | 
| 
      
 74 
     | 
    
         
            +
                        message("Error in IMAP NOOP: #{e.class}: #{e.message}")
         
     | 
| 
      
 75 
     | 
    
         
            +
                      end
         
     | 
| 
      
 76 
     | 
    
         
            +
                    end
         
     | 
| 
      
 77 
     | 
    
         
            +
                  end
         
     | 
| 
      
 78 
     | 
    
         
            +
                }
         
     | 
| 
      
 79 
     | 
    
         
            +
              end
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
              def self.stop_keep_alive_thread
         
     | 
| 
      
 82 
     | 
    
         
            +
                if keep_alive_thread
         
     | 
| 
      
 83 
     | 
    
         
            +
                  keep_alive_thread&.kill
         
     | 
| 
      
 84 
     | 
    
         
            +
                  self.keep_alive_thread = nil
         
     | 
| 
      
 85 
     | 
    
         
            +
                end
         
     | 
| 
      
 86 
     | 
    
         
            +
              end
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
       45 
88 
     | 
    
         
             
              def self.message_window
         
     | 
| 
       46 
89 
     | 
    
         
             
                if Window.list.size == 1
         
     | 
| 
       47 
90 
     | 
    
         
             
                  split_window
         
     | 
| 
         @@ -74,19 +117,46 @@ module Mournmail 
     | 
|
| 
       74 
117 
     | 
    
         
             
                escape_binary(s)
         
     | 
| 
       75 
118 
     | 
    
         
             
              end
         
     | 
| 
       76 
119 
     | 
    
         | 
| 
       77 
     | 
    
         
            -
               
     | 
| 
       78 
     | 
    
         
            -
             
     | 
| 
      
 120 
     | 
    
         
            +
              def self.current_account
         
     | 
| 
      
 121 
     | 
    
         
            +
                init_current_account
         
     | 
| 
      
 122 
     | 
    
         
            +
                @current_account
         
     | 
| 
      
 123 
     | 
    
         
            +
              end
         
     | 
| 
      
 124 
     | 
    
         
            +
             
     | 
| 
      
 125 
     | 
    
         
            +
              def self.account_config
         
     | 
| 
      
 126 
     | 
    
         
            +
                init_current_account
         
     | 
| 
      
 127 
     | 
    
         
            +
                @account_config
         
     | 
| 
      
 128 
     | 
    
         
            +
              end
         
     | 
| 
      
 129 
     | 
    
         
            +
             
     | 
| 
      
 130 
     | 
    
         
            +
              def self.init_current_account
         
     | 
| 
      
 131 
     | 
    
         
            +
                if @current_account.nil?
         
     | 
| 
      
 132 
     | 
    
         
            +
                  @current_account, @account_config = CONFIG[:mournmail_accounts].first
         
     | 
| 
      
 133 
     | 
    
         
            +
                end
         
     | 
| 
      
 134 
     | 
    
         
            +
              end
         
     | 
| 
      
 135 
     | 
    
         
            +
             
     | 
| 
      
 136 
     | 
    
         
            +
              def self.current_account=(name)
         
     | 
| 
      
 137 
     | 
    
         
            +
                unless CONFIG[:mournmail_accounts].key?(name)
         
     | 
| 
      
 138 
     | 
    
         
            +
                  raise ArgumentError, "No such account: #{name}"
         
     | 
| 
      
 139 
     | 
    
         
            +
                end
         
     | 
| 
      
 140 
     | 
    
         
            +
                @current_account = name
         
     | 
| 
      
 141 
     | 
    
         
            +
                @account_config = CONFIG[:mournmail_accounts][name]
         
     | 
| 
      
 142 
     | 
    
         
            +
              end
         
     | 
| 
       79 
143 
     | 
    
         | 
| 
       80 
144 
     | 
    
         
             
              def self.imap_connect
         
     | 
| 
       81 
145 
     | 
    
         
             
                @imap_mutex.synchronize do
         
     | 
| 
      
 146 
     | 
    
         
            +
                  if keep_alive_thread.nil?
         
     | 
| 
      
 147 
     | 
    
         
            +
                    start_keep_alive_thread
         
     | 
| 
      
 148 
     | 
    
         
            +
                  end
         
     | 
| 
       82 
149 
     | 
    
         
             
                  if @imap.nil? || @imap.disconnected?
         
     | 
| 
      
 150 
     | 
    
         
            +
                    conf = account_config
         
     | 
| 
       83 
151 
     | 
    
         
             
                    Timeout.timeout(CONFIG[:mournmail_imap_connect_timeout]) do
         
     | 
| 
       84 
     | 
    
         
            -
                      @imap = Net::IMAP.new( 
     | 
| 
       85 
     | 
    
         
            -
                                             
     | 
| 
       86 
     | 
    
         
            -
                      @imap.authenticate( 
     | 
| 
       87 
     | 
    
         
            -
                                          
     | 
| 
       88 
     | 
    
         
            -
                                          
     | 
| 
       89 
     | 
    
         
            -
             
     | 
| 
      
 152 
     | 
    
         
            +
                      @imap = Net::IMAP.new(conf[:imap_host],
         
     | 
| 
      
 153 
     | 
    
         
            +
                                            conf[:imap_options])
         
     | 
| 
      
 154 
     | 
    
         
            +
                      @imap.authenticate(conf[:imap_options][:auth_type] || "PLAIN",
         
     | 
| 
      
 155 
     | 
    
         
            +
                                         conf[:imap_options][:user_name],
         
     | 
| 
      
 156 
     | 
    
         
            +
                                         conf[:imap_options][:password])
         
     | 
| 
      
 157 
     | 
    
         
            +
                      @mailboxes = @imap.list("", "*").map { |mbox|
         
     | 
| 
      
 158 
     | 
    
         
            +
                        Net::IMAP.decode_utf7(mbox.name)
         
     | 
| 
      
 159 
     | 
    
         
            +
                      }
         
     | 
| 
       90 
160 
     | 
    
         
             
                      if Mournmail.current_mailbox
         
     | 
| 
       91 
161 
     | 
    
         
             
                        @imap.select(Mournmail.current_mailbox)
         
     | 
| 
       92 
162 
     | 
    
         
             
                      end
         
     | 
| 
         @@ -101,6 +171,7 @@ module Mournmail 
     | 
|
| 
       101 
171 
     | 
    
         | 
| 
       102 
172 
     | 
    
         
             
              def self.imap_disconnect
         
     | 
| 
       103 
173 
     | 
    
         
             
                @imap_mutex.synchronize do
         
     | 
| 
      
 174 
     | 
    
         
            +
                  stop_keep_alive_thread
         
     | 
| 
       104 
175 
     | 
    
         
             
                  if @imap
         
     | 
| 
       105 
176 
     | 
    
         
             
                    @imap.disconnect rescue nil
         
     | 
| 
       106 
177 
     | 
    
         
             
                    @imap = nil
         
     | 
| 
         @@ -116,6 +187,17 @@ module Mournmail 
     | 
|
| 
       116 
187 
     | 
    
         
             
                  else
         
     | 
| 
       117 
188 
     | 
    
         
             
                    summary = Mournmail::Summary.load_or_new(mailbox)
         
     | 
| 
       118 
189 
     | 
    
         
             
                  end
         
     | 
| 
      
 190 
     | 
    
         
            +
                  uidvalidity = imap.responses["UIDVALIDITY"].last
         
     | 
| 
      
 191 
     | 
    
         
            +
                  if summary.uidvalidity.nil?
         
     | 
| 
      
 192 
     | 
    
         
            +
                    summary.uidvalidity = uidvalidity
         
     | 
| 
      
 193 
     | 
    
         
            +
                  elsif uidvalidity && uidvalidity != summary.uidvalidity
         
     | 
| 
      
 194 
     | 
    
         
            +
                    clear = next_tick {
         
     | 
| 
      
 195 
     | 
    
         
            +
                      yes_or_no?("UIDVALIDITY has been changed; Clear cache?")
         
     | 
| 
      
 196 
     | 
    
         
            +
                    }
         
     | 
| 
      
 197 
     | 
    
         
            +
                    if clear
         
     | 
| 
      
 198 
     | 
    
         
            +
                      summary = Mournmail::Summary.new(mailbox)
         
     | 
| 
      
 199 
     | 
    
         
            +
                    end
         
     | 
| 
      
 200 
     | 
    
         
            +
                  end
         
     | 
| 
       119 
201 
     | 
    
         
             
                  first_uid = (summary.last_uid || 0) + 1
         
     | 
| 
       120 
202 
     | 
    
         
             
                  data = imap.uid_fetch(first_uid..-1, ["UID", "ENVELOPE", "FLAGS"])
         
     | 
| 
       121 
203 
     | 
    
         
             
                  summary.synchronize do
         
     | 
| 
         @@ -134,33 +216,192 @@ module Mournmail 
     | 
|
| 
       134 
216 
     | 
    
         
             
              end
         
     | 
| 
       135 
217 
     | 
    
         | 
| 
       136 
218 
     | 
    
         
             
              def self.mailbox_cache_path(mailbox)
         
     | 
| 
       137 
     | 
    
         
            -
                 
     | 
| 
       138 
     | 
    
         
            -
             
     | 
| 
       139 
     | 
    
         
            -
             
     | 
| 
      
 219 
     | 
    
         
            +
                File.expand_path("cache/#{current_account}/mailboxes/#{mailbox}",
         
     | 
| 
      
 220 
     | 
    
         
            +
                                 CONFIG[:mournmail_directory])
         
     | 
| 
      
 221 
     | 
    
         
            +
              end
         
     | 
| 
      
 222 
     | 
    
         
            +
             
     | 
| 
      
 223 
     | 
    
         
            +
              def self.mail_cache_path(cache_id)
         
     | 
| 
      
 224 
     | 
    
         
            +
                dir = cache_id[0, 2]
         
     | 
| 
      
 225 
     | 
    
         
            +
                File.expand_path("cache/#{current_account}/mails/#{dir}/#{cache_id}",
         
     | 
| 
      
 226 
     | 
    
         
            +
                                 CONFIG[:mournmail_directory])
         
     | 
| 
       140 
227 
     | 
    
         
             
              end
         
     | 
| 
       141 
228 
     | 
    
         | 
| 
       142 
     | 
    
         
            -
              def self. 
     | 
| 
       143 
     | 
    
         
            -
                path =  
     | 
| 
      
 229 
     | 
    
         
            +
              def self.read_mail_cache(cache_id)
         
     | 
| 
      
 230 
     | 
    
         
            +
                path = Mournmail.mail_cache_path(cache_id)
         
     | 
| 
      
 231 
     | 
    
         
            +
                File.read(path)
         
     | 
| 
      
 232 
     | 
    
         
            +
              end
         
     | 
| 
      
 233 
     | 
    
         
            +
             
     | 
| 
      
 234 
     | 
    
         
            +
              def self.write_mail_cache(s)
         
     | 
| 
      
 235 
     | 
    
         
            +
                header = s.slice(/.*\r\n\r\n/m)
         
     | 
| 
      
 236 
     | 
    
         
            +
                cache_id = Digest::SHA256.hexdigest(header)
         
     | 
| 
      
 237 
     | 
    
         
            +
                path = mail_cache_path(cache_id)
         
     | 
| 
      
 238 
     | 
    
         
            +
                dir = File.dirname(path)
         
     | 
| 
      
 239 
     | 
    
         
            +
                base = File.basename(path)
         
     | 
| 
       144 
240 
     | 
    
         
             
                begin
         
     | 
| 
       145 
     | 
    
         
            -
                   
     | 
| 
       146 
     | 
    
         
            -
             
     | 
| 
       147 
     | 
    
         
            -
                     
     | 
| 
      
 241 
     | 
    
         
            +
                  f = Tempfile.create(["#{base}-", ".tmp"], dir)
         
     | 
| 
      
 242 
     | 
    
         
            +
                  begin
         
     | 
| 
      
 243 
     | 
    
         
            +
                    f.write(s)
         
     | 
| 
      
 244 
     | 
    
         
            +
                  ensure
         
     | 
| 
      
 245 
     | 
    
         
            +
                    f.close
         
     | 
| 
       148 
246 
     | 
    
         
             
                  end
         
     | 
| 
       149 
247 
     | 
    
         
             
                rescue Errno::ENOENT
         
     | 
| 
       150 
     | 
    
         
            -
                   
     | 
| 
       151 
     | 
    
         
            -
             
     | 
| 
       152 
     | 
    
         
            -
             
     | 
| 
       153 
     | 
    
         
            -
             
     | 
| 
       154 
     | 
    
         
            -
             
     | 
| 
      
 248 
     | 
    
         
            +
                  FileUtils.mkdir_p(File.dirname(path))
         
     | 
| 
      
 249 
     | 
    
         
            +
                  retry
         
     | 
| 
      
 250 
     | 
    
         
            +
                end
         
     | 
| 
      
 251 
     | 
    
         
            +
                File.rename(f.path, path)
         
     | 
| 
      
 252 
     | 
    
         
            +
                cache_id
         
     | 
| 
      
 253 
     | 
    
         
            +
              end
         
     | 
| 
      
 254 
     | 
    
         
            +
             
     | 
| 
      
 255 
     | 
    
         
            +
              def self.index_mail(cache_id, mail)
         
     | 
| 
      
 256 
     | 
    
         
            +
                messages_db = Groonga["Messages"]
         
     | 
| 
      
 257 
     | 
    
         
            +
                unless messages_db.has_key?(cache_id)
         
     | 
| 
      
 258 
     | 
    
         
            +
                  thread_id = find_thread_id(mail, messages_db)
         
     | 
| 
      
 259 
     | 
    
         
            +
                  list_id = (mail["List-Id"] || mail["X-ML-Name"])
         
     | 
| 
      
 260 
     | 
    
         
            +
                  messages_db.add(cache_id,
         
     | 
| 
      
 261 
     | 
    
         
            +
                                  message_id: header_text(mail.message_id),
         
     | 
| 
      
 262 
     | 
    
         
            +
                                  thread_id: header_text(thread_id),
         
     | 
| 
      
 263 
     | 
    
         
            +
                                  date: mail.date&.to_time,
         
     | 
| 
      
 264 
     | 
    
         
            +
                                  subject: header_text(mail.subject),
         
     | 
| 
      
 265 
     | 
    
         
            +
                                  from: header_text(mail["From"]),
         
     | 
| 
      
 266 
     | 
    
         
            +
                                  to: header_text(mail["To"]),
         
     | 
| 
      
 267 
     | 
    
         
            +
                                  cc: header_text(mail["Cc"]),
         
     | 
| 
      
 268 
     | 
    
         
            +
                                  list_id: header_text(list_id),
         
     | 
| 
      
 269 
     | 
    
         
            +
                                  body: body_text(mail))
         
     | 
| 
      
 270 
     | 
    
         
            +
                end
         
     | 
| 
      
 271 
     | 
    
         
            +
              end
         
     | 
| 
      
 272 
     | 
    
         
            +
             
     | 
| 
      
 273 
     | 
    
         
            +
              class << self
         
     | 
| 
      
 274 
     | 
    
         
            +
                private
         
     | 
| 
      
 275 
     | 
    
         
            +
             
     | 
| 
      
 276 
     | 
    
         
            +
                def find_thread_id(mail, messages_db)
         
     | 
| 
      
 277 
     | 
    
         
            +
                  references = Array(mail.references) | Array(mail.in_reply_to)
         
     | 
| 
      
 278 
     | 
    
         
            +
                  if references.empty?
         
     | 
| 
      
 279 
     | 
    
         
            +
                    mail.message_id
         
     | 
| 
      
 280 
     | 
    
         
            +
                  else
         
     | 
| 
      
 281 
     | 
    
         
            +
                    parent = messages_db.select { |m|
         
     | 
| 
      
 282 
     | 
    
         
            +
                      references.inject(nil) { |cond, ref|
         
     | 
| 
      
 283 
     | 
    
         
            +
                        if cond.nil?
         
     | 
| 
      
 284 
     | 
    
         
            +
                          m.message_id == ref
         
     | 
| 
      
 285 
     | 
    
         
            +
                        else
         
     | 
| 
      
 286 
     | 
    
         
            +
                          cond | (m.message_id == ref)
         
     | 
| 
      
 287 
     | 
    
         
            +
                        end
         
     | 
| 
      
 288 
     | 
    
         
            +
                      }
         
     | 
| 
      
 289 
     | 
    
         
            +
                    }.first
         
     | 
| 
      
 290 
     | 
    
         
            +
                    if parent
         
     | 
| 
      
 291 
     | 
    
         
            +
                      parent.thread_id
         
     | 
| 
      
 292 
     | 
    
         
            +
                    else
         
     | 
| 
      
 293 
     | 
    
         
            +
                      mail.message_id
         
     | 
| 
       155 
294 
     | 
    
         
             
                    end
         
     | 
| 
       156 
     | 
    
         
            -
             
     | 
| 
       157 
     | 
    
         
            -
             
     | 
| 
       158 
     | 
    
         
            -
             
     | 
| 
       159 
     | 
    
         
            -
             
     | 
| 
       160 
     | 
    
         
            -
             
     | 
| 
      
 295 
     | 
    
         
            +
                  end
         
     | 
| 
      
 296 
     | 
    
         
            +
                end
         
     | 
| 
      
 297 
     | 
    
         
            +
             
     | 
| 
      
 298 
     | 
    
         
            +
                def header_text(s)
         
     | 
| 
      
 299 
     | 
    
         
            +
                  force_utf8(s.to_s)
         
     | 
| 
      
 300 
     | 
    
         
            +
                end
         
     | 
| 
      
 301 
     | 
    
         
            +
                
         
     | 
| 
      
 302 
     | 
    
         
            +
                def body_text(mail)
         
     | 
| 
      
 303 
     | 
    
         
            +
                  if mail.multipart?
         
     | 
| 
      
 304 
     | 
    
         
            +
                    mail.parts.map { |part|
         
     | 
| 
      
 305 
     | 
    
         
            +
                      part_text(part)
         
     | 
| 
      
 306 
     | 
    
         
            +
                    }.join("\n")
         
     | 
| 
      
 307 
     | 
    
         
            +
                  else
         
     | 
| 
      
 308 
     | 
    
         
            +
                    s = mail.body.decoded
         
     | 
| 
      
 309 
     | 
    
         
            +
                    to_utf8(s, mail.charset).gsub(/\r\n/, "\n")
         
     | 
| 
      
 310 
     | 
    
         
            +
                  end
         
     | 
| 
      
 311 
     | 
    
         
            +
                rescue
         
     | 
| 
      
 312 
     | 
    
         
            +
                  ""
         
     | 
| 
      
 313 
     | 
    
         
            +
                end
         
     | 
| 
      
 314 
     | 
    
         
            +
                
         
     | 
| 
      
 315 
     | 
    
         
            +
                def part_text(part)
         
     | 
| 
      
 316 
     | 
    
         
            +
                  if part.multipart?
         
     | 
| 
      
 317 
     | 
    
         
            +
                    part.parts.map { |part|
         
     | 
| 
      
 318 
     | 
    
         
            +
                      part_text(part)
         
     | 
| 
      
 319 
     | 
    
         
            +
                    }.join("\n")
         
     | 
| 
      
 320 
     | 
    
         
            +
                  elsif part.main_type == "message" && part.sub_type == "rfc822"
         
     | 
| 
      
 321 
     | 
    
         
            +
                    mail = Mail.new(part.body.raw_source)
         
     | 
| 
      
 322 
     | 
    
         
            +
                    body_text(mail)
         
     | 
| 
      
 323 
     | 
    
         
            +
                  elsif part.attachment?
         
     | 
| 
      
 324 
     | 
    
         
            +
                    force_utf8(part.filename.to_s)
         
     | 
| 
      
 325 
     | 
    
         
            +
                  else
         
     | 
| 
      
 326 
     | 
    
         
            +
                    if part.main_type == "text" && part.sub_type == "plain"
         
     | 
| 
      
 327 
     | 
    
         
            +
                      force_utf8(part.decoded).sub(/(?<!\n)\z/, "\n").gsub(/\r\n/, "\n")
         
     | 
| 
      
 328 
     | 
    
         
            +
                    else
         
     | 
| 
      
 329 
     | 
    
         
            +
                      ""
         
     | 
| 
       161 
330 
     | 
    
         
             
                    end
         
     | 
| 
       162 
     | 
    
         
            -
                    [s, true]
         
     | 
| 
       163 
331 
     | 
    
         
             
                  end
         
     | 
| 
      
 332 
     | 
    
         
            +
                rescue
         
     | 
| 
      
 333 
     | 
    
         
            +
                  ""
         
     | 
| 
      
 334 
     | 
    
         
            +
                end
         
     | 
| 
      
 335 
     | 
    
         
            +
              end
         
     | 
| 
      
 336 
     | 
    
         
            +
             
     | 
| 
      
 337 
     | 
    
         
            +
              def self.read_mailbox_name(prompt, **opts)
         
     | 
| 
      
 338 
     | 
    
         
            +
                f = ->(s) {
         
     | 
| 
      
 339 
     | 
    
         
            +
                  complete_for_minibuffer(s, @mailboxes)
         
     | 
| 
      
 340 
     | 
    
         
            +
                }
         
     | 
| 
      
 341 
     | 
    
         
            +
                mailbox = read_from_minibuffer(prompt, completion_proc: f, **opts)
         
     | 
| 
      
 342 
     | 
    
         
            +
                Net::IMAP.encode_utf7(mailbox)
         
     | 
| 
      
 343 
     | 
    
         
            +
              end
         
     | 
| 
      
 344 
     | 
    
         
            +
             
     | 
| 
      
 345 
     | 
    
         
            +
              def self.force_utf8(s)
         
     | 
| 
      
 346 
     | 
    
         
            +
                s.force_encoding(Encoding::UTF_8).scrub("?")
         
     | 
| 
      
 347 
     | 
    
         
            +
              end
         
     | 
| 
      
 348 
     | 
    
         
            +
             
     | 
| 
      
 349 
     | 
    
         
            +
              def self.to_utf8(s, charset)
         
     | 
| 
      
 350 
     | 
    
         
            +
                if /\Autf-8\z/i =~ charset
         
     | 
| 
      
 351 
     | 
    
         
            +
                  force_utf8(s)
         
     | 
| 
      
 352 
     | 
    
         
            +
                else
         
     | 
| 
      
 353 
     | 
    
         
            +
                  begin
         
     | 
| 
      
 354 
     | 
    
         
            +
                    s.encode(Encoding::UTF_8, charset, replace: "?")
         
     | 
| 
      
 355 
     | 
    
         
            +
                  rescue Encoding::ConverterNotFoundError
         
     | 
| 
      
 356 
     | 
    
         
            +
                    force_utf8(s)
         
     | 
| 
      
 357 
     | 
    
         
            +
                  end
         
     | 
| 
      
 358 
     | 
    
         
            +
                end.gsub(/\r\n/, "\n")
         
     | 
| 
      
 359 
     | 
    
         
            +
              end
         
     | 
| 
      
 360 
     | 
    
         
            +
             
     | 
| 
      
 361 
     | 
    
         
            +
              def self.open_groonga_db
         
     | 
| 
      
 362 
     | 
    
         
            +
                db_path = File.expand_path("groonga/#{current_account}/messages.db",
         
     | 
| 
      
 363 
     | 
    
         
            +
                                           CONFIG[:mournmail_directory])
         
     | 
| 
      
 364 
     | 
    
         
            +
                if File.exist?(db_path)
         
     | 
| 
      
 365 
     | 
    
         
            +
                  @groonga_db = Groonga::Database.open(db_path)
         
     | 
| 
      
 366 
     | 
    
         
            +
                else
         
     | 
| 
      
 367 
     | 
    
         
            +
                  @groonga_db = create_groonga_db(db_path)
         
     | 
| 
      
 368 
     | 
    
         
            +
                end
         
     | 
| 
      
 369 
     | 
    
         
            +
              end
         
     | 
| 
      
 370 
     | 
    
         
            +
             
     | 
| 
      
 371 
     | 
    
         
            +
              def self.create_groonga_db(db_path)
         
     | 
| 
      
 372 
     | 
    
         
            +
                FileUtils.mkdir_p(File.dirname(db_path), mode: 0700)
         
     | 
| 
      
 373 
     | 
    
         
            +
                db = Groonga::Database.create(path: db_path)
         
     | 
| 
      
 374 
     | 
    
         
            +
             
     | 
| 
      
 375 
     | 
    
         
            +
                Groonga::Schema.create_table("Messages", :type => :hash) do |table|
         
     | 
| 
      
 376 
     | 
    
         
            +
                  table.short_text("message_id")
         
     | 
| 
      
 377 
     | 
    
         
            +
                  table.short_text("thread_id")
         
     | 
| 
      
 378 
     | 
    
         
            +
                  table.time("date")
         
     | 
| 
      
 379 
     | 
    
         
            +
                  table.short_text("subject")
         
     | 
| 
      
 380 
     | 
    
         
            +
                  table.short_text("from")
         
     | 
| 
      
 381 
     | 
    
         
            +
                  table.short_text("to")
         
     | 
| 
      
 382 
     | 
    
         
            +
                  table.short_text("cc")
         
     | 
| 
      
 383 
     | 
    
         
            +
                  table.short_text("list_id")
         
     | 
| 
      
 384 
     | 
    
         
            +
                  table.text("body")
         
     | 
| 
      
 385 
     | 
    
         
            +
                end
         
     | 
| 
      
 386 
     | 
    
         
            +
                
         
     | 
| 
      
 387 
     | 
    
         
            +
                Groonga::Schema.create_table("Terms",
         
     | 
| 
      
 388 
     | 
    
         
            +
                                             type: :patricia_trie,
         
     | 
| 
      
 389 
     | 
    
         
            +
                                             normalizer: :NormalizerAuto,
         
     | 
| 
      
 390 
     | 
    
         
            +
                                             default_tokenizer: "TokenBigram") do |table|
         
     | 
| 
      
 391 
     | 
    
         
            +
                  table.index("Messages.subject")
         
     | 
| 
      
 392 
     | 
    
         
            +
                  table.index("Messages.from")
         
     | 
| 
      
 393 
     | 
    
         
            +
                  table.index("Messages.to")
         
     | 
| 
      
 394 
     | 
    
         
            +
                  table.index("Messages.cc")
         
     | 
| 
      
 395 
     | 
    
         
            +
                  table.index("Messages.list_id")
         
     | 
| 
      
 396 
     | 
    
         
            +
                  table.index("Messages.body")
         
     | 
| 
      
 397 
     | 
    
         
            +
                end
         
     | 
| 
      
 398 
     | 
    
         
            +
             
     | 
| 
      
 399 
     | 
    
         
            +
                db
         
     | 
| 
      
 400 
     | 
    
         
            +
              end
         
     | 
| 
      
 401 
     | 
    
         
            +
             
     | 
| 
      
 402 
     | 
    
         
            +
              def self.close_groonga_db
         
     | 
| 
      
 403 
     | 
    
         
            +
                if @groonga_db
         
     | 
| 
      
 404 
     | 
    
         
            +
                  @groonga_db.close
         
     | 
| 
       164 
405 
     | 
    
         
             
                end
         
     | 
| 
       165 
406 
     | 
    
         
             
              end
         
     | 
| 
       166 
407 
     | 
    
         
             
            end
         
     | 
    
        data/lib/mournmail/version.rb
    CHANGED
    
    
    
        data/mournmail.gemspec
    CHANGED
    
    | 
         @@ -23,7 +23,7 @@ Gem::Specification.new do |spec| 
     | 
|
| 
       23 
23 
     | 
    
         | 
| 
       24 
24 
     | 
    
         
             
              spec.add_runtime_dependency "textbringer"
         
     | 
| 
       25 
25 
     | 
    
         
             
              spec.add_runtime_dependency "mail"
         
     | 
| 
       26 
     | 
    
         
            -
              spec.add_runtime_dependency " 
     | 
| 
      
 26 
     | 
    
         
            +
              spec.add_runtime_dependency "rroonga"
         
     | 
| 
       27 
27 
     | 
    
         | 
| 
       28 
28 
     | 
    
         
             
              spec.add_development_dependency "bundler", "~> 1.14"
         
     | 
| 
       29 
29 
     | 
    
         
             
              spec.add_development_dependency "rake", "~> 10.0"
         
     | 
    
        metadata
    CHANGED
    
    | 
         @@ -1,14 +1,14 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            --- !ruby/object:Gem::Specification
         
     | 
| 
       2 
2 
     | 
    
         
             
            name: mournmail
         
     | 
| 
       3 
3 
     | 
    
         
             
            version: !ruby/object:Gem::Version
         
     | 
| 
       4 
     | 
    
         
            -
              version: 0. 
     | 
| 
      
 4 
     | 
    
         
            +
              version: 0.2.0
         
     | 
| 
       5 
5 
     | 
    
         
             
            platform: ruby
         
     | 
| 
       6 
6 
     | 
    
         
             
            authors:
         
     | 
| 
       7 
7 
     | 
    
         
             
            - Shugo Maeda
         
     | 
| 
       8 
8 
     | 
    
         
             
            autorequire: 
         
     | 
| 
       9 
9 
     | 
    
         
             
            bindir: exe
         
     | 
| 
       10 
10 
     | 
    
         
             
            cert_chain: []
         
     | 
| 
       11 
     | 
    
         
            -
            date:  
     | 
| 
      
 11 
     | 
    
         
            +
            date: 2018-05-03 00:00:00.000000000 Z
         
     | 
| 
       12 
12 
     | 
    
         
             
            dependencies:
         
     | 
| 
       13 
13 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       14 
14 
     | 
    
         
             
              name: textbringer
         
     | 
| 
         @@ -39,7 +39,7 @@ dependencies: 
     | 
|
| 
       39 
39 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       40 
40 
     | 
    
         
             
                    version: '0'
         
     | 
| 
       41 
41 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       42 
     | 
    
         
            -
              name:  
     | 
| 
      
 42 
     | 
    
         
            +
              name: rroonga
         
     | 
| 
       43 
43 
     | 
    
         
             
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
       44 
44 
     | 
    
         
             
                requirements:
         
     | 
| 
       45 
45 
     | 
    
         
             
                - - ">="
         
     | 
| 
         @@ -99,8 +99,10 @@ files: 
     | 
|
| 
       99 
99 
     | 
    
         
             
            - lib/mournmail/config.rb
         
     | 
| 
       100 
100 
     | 
    
         
             
            - lib/mournmail/draft_mode.rb
         
     | 
| 
       101 
101 
     | 
    
         
             
            - lib/mournmail/faces.rb
         
     | 
| 
      
 102 
     | 
    
         
            +
            - lib/mournmail/mail_encoded_word_patch.rb
         
     | 
| 
       102 
103 
     | 
    
         
             
            - lib/mournmail/message_mode.rb
         
     | 
| 
       103 
104 
     | 
    
         
             
            - lib/mournmail/message_rendering.rb
         
     | 
| 
      
 105 
     | 
    
         
            +
            - lib/mournmail/search_result_mode.rb
         
     | 
| 
       104 
106 
     | 
    
         
             
            - lib/mournmail/summary.rb
         
     | 
| 
       105 
107 
     | 
    
         
             
            - lib/mournmail/summary_mode.rb
         
     | 
| 
       106 
108 
     | 
    
         
             
            - lib/mournmail/utils.rb
         
     | 
| 
         @@ -127,9 +129,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement 
     | 
|
| 
       127 
129 
     | 
    
         
             
                  version: '0'
         
     | 
| 
       128 
130 
     | 
    
         
             
            requirements: []
         
     | 
| 
       129 
131 
     | 
    
         
             
            rubyforge_project: 
         
     | 
| 
       130 
     | 
    
         
            -
            rubygems_version: 2.6 
     | 
| 
      
 132 
     | 
    
         
            +
            rubygems_version: 2.7.6
         
     | 
| 
       131 
133 
     | 
    
         
             
            signing_key: 
         
     | 
| 
       132 
134 
     | 
    
         
             
            specification_version: 4
         
     | 
| 
       133 
135 
     | 
    
         
             
            summary: A message user agent for Textbringer.
         
     | 
| 
       134 
136 
     | 
    
         
             
            test_files: []
         
     | 
| 
       135 
     | 
    
         
            -
            has_rdoc: 
         
     |