sup 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of sup might be problematic. Click here for more details.
- data/History.txt +7 -0
- data/Manifest.txt +2 -0
- data/README.txt +25 -21
- data/bin/sup +29 -25
- data/bin/sup-import +43 -13
- data/doc/FAQ.txt +8 -6
- data/doc/Philosophy.txt +17 -18
- data/doc/TODO +6 -0
- data/lib/sup.rb +7 -6
- data/lib/sup/buffer.rb +28 -17
- data/lib/sup/draft.rb +27 -23
- data/lib/sup/imap.rb +107 -0
- data/lib/sup/index.rb +47 -55
- data/lib/sup/logger.rb +1 -2
- data/lib/sup/mbox.rb +2 -2
- data/lib/sup/mbox/loader.rb +63 -66
- data/lib/sup/message.rb +45 -25
- data/lib/sup/mode.rb +1 -0
- data/lib/sup/modes/buffer-list-mode.rb +4 -4
- data/lib/sup/modes/edit-message-mode.rb +4 -4
- data/lib/sup/modes/inbox-mode.rb +2 -0
- data/lib/sup/modes/label-list-mode.rb +1 -1
- data/lib/sup/modes/log-mode.rb +1 -1
- data/lib/sup/modes/poll-mode.rb +2 -2
- data/lib/sup/modes/reply-mode.rb +6 -3
- data/lib/sup/modes/resume-mode.rb +25 -0
- data/lib/sup/modes/scroll-mode.rb +7 -0
- data/lib/sup/modes/thread-index-mode.rb +8 -11
- data/lib/sup/modes/thread-view-mode.rb +31 -3
- data/lib/sup/poll.rb +24 -10
- data/lib/sup/sent.rb +8 -8
- data/lib/sup/source.rb +64 -0
- data/lib/sup/thread.rb +3 -0
- data/lib/sup/util.rb +2 -2
- metadata +4 -2
data/doc/TODO
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
search for other messages from author in thread-view-mode
|
1
2
|
forward attachments
|
2
3
|
tab completion on labels, contacts
|
3
4
|
within-buffer search
|
@@ -8,7 +9,12 @@ maybe: rangefilter on the initial inbox to only consider the most recent 1000 me
|
|
8
9
|
select all, starred, to me, etc
|
9
10
|
editing of arbitrary messages
|
10
11
|
annotations on messages
|
12
|
+
gmail
|
13
|
+
pop
|
14
|
+
move sup-import argument handling to getopt or something
|
11
15
|
|
16
|
+
x handle broken sources better
|
17
|
+
x imap
|
12
18
|
x word wrap
|
13
19
|
x background indexing
|
14
20
|
x auto-insertion of draft messages
|
data/lib/sup.rb
CHANGED
@@ -3,18 +3,19 @@ require 'yaml'
|
|
3
3
|
require 'zlib'
|
4
4
|
require 'thread'
|
5
5
|
require 'fileutils'
|
6
|
+
|
6
7
|
Thread.abort_on_exception = true # make debugging possible
|
7
8
|
|
8
9
|
class Object
|
9
|
-
## this is for debugging purposes because i keep calling
|
10
|
-
## i want it to throw an exception
|
11
|
-
def id
|
10
|
+
## this is for debugging purposes because i keep calling #id on the
|
11
|
+
## wrong object and i want it to throw an exception
|
12
|
+
def id
|
12
13
|
raise "wrong id called"
|
13
14
|
end
|
14
15
|
end
|
15
16
|
|
16
17
|
module Redwood
|
17
|
-
VERSION = "0.0.
|
18
|
+
VERSION = "0.0.2"
|
18
19
|
|
19
20
|
BASE_DIR = File.join(ENV["HOME"], ".sup")
|
20
21
|
CONFIG_FN = File.join(BASE_DIR, "config.yaml")
|
@@ -28,7 +29,6 @@ module Redwood
|
|
28
29
|
YAML_DATE = "2006-10-01"
|
29
30
|
|
30
31
|
## one-stop shop for yamliciousness
|
31
|
-
|
32
32
|
def register_yaml klass, props
|
33
33
|
vars = props.map { |p| "@#{p}" }
|
34
34
|
path = klass.name.gsub(/::/, "/")
|
@@ -65,7 +65,6 @@ module Redwood
|
|
65
65
|
end
|
66
66
|
|
67
67
|
## set up default configuration file
|
68
|
-
|
69
68
|
if File.exists? Redwood::CONFIG_FN
|
70
69
|
$config = Redwood::load_yaml_obj Redwood::CONFIG_FN
|
71
70
|
else
|
@@ -92,7 +91,9 @@ end
|
|
92
91
|
require "sup/util"
|
93
92
|
require "sup/update"
|
94
93
|
require "sup/message"
|
94
|
+
require "sup/source"
|
95
95
|
require "sup/mbox"
|
96
|
+
require "sup/imap"
|
96
97
|
require "sup/person"
|
97
98
|
require "sup/account"
|
98
99
|
require "sup/thread"
|
data/lib/sup/buffer.rb
CHANGED
@@ -132,7 +132,7 @@ class BufferManager
|
|
132
132
|
@minibuf_stack = []
|
133
133
|
@textfields = {}
|
134
134
|
@flash = nil
|
135
|
-
@
|
135
|
+
@freeze = false
|
136
136
|
|
137
137
|
self.class.i_am_the_instance self
|
138
138
|
end
|
@@ -178,14 +178,14 @@ class BufferManager
|
|
178
178
|
end
|
179
179
|
|
180
180
|
def completely_redraw_screen
|
181
|
-
return if @
|
181
|
+
return if @freeze
|
182
182
|
Ncurses.clear
|
183
183
|
@dirty = true
|
184
184
|
draw_screen
|
185
185
|
end
|
186
186
|
|
187
187
|
def handle_resize
|
188
|
-
return if @
|
188
|
+
return if @freeze
|
189
189
|
rows, cols = Ncurses.rows, Ncurses.cols
|
190
190
|
@buffers.each { |b| b.resize rows - 1, cols }
|
191
191
|
completely_redraw_screen
|
@@ -193,7 +193,7 @@ class BufferManager
|
|
193
193
|
end
|
194
194
|
|
195
195
|
def draw_screen skip_minibuf=false
|
196
|
-
return if @
|
196
|
+
return if @freeze
|
197
197
|
|
198
198
|
## disabling this for the time being, to help with debugging
|
199
199
|
## (currently we only have one buffer visible at a time).
|
@@ -216,7 +216,7 @@ class BufferManager
|
|
216
216
|
def spawn_unless_exists title, opts={}
|
217
217
|
if @name_map.member? title
|
218
218
|
Redwood::log "buffer '#{title}' already exists, raising to front"
|
219
|
-
raise_to_front @name_map[title]
|
219
|
+
raise_to_front @name_map[title] unless opts[:hidden]
|
220
220
|
else
|
221
221
|
mode = yield
|
222
222
|
spawn title, mode, opts
|
@@ -291,11 +291,13 @@ class BufferManager
|
|
291
291
|
tf.activate question, default
|
292
292
|
@dirty = true
|
293
293
|
draw_screen true
|
294
|
-
tf.position_cursor
|
295
|
-
Ncurses.refresh
|
296
294
|
|
297
295
|
ret = nil
|
296
|
+
@freeze = true
|
297
|
+
tf.position_cursor
|
298
|
+
Ncurses.refresh
|
298
299
|
while tf.handle_input(Ncurses.nonblocking_getch); end
|
300
|
+
@freeze = false
|
299
301
|
|
300
302
|
ret = tf.value
|
301
303
|
tf.deactivate
|
@@ -315,6 +317,7 @@ class BufferManager
|
|
315
317
|
|
316
318
|
ret = nil
|
317
319
|
done = false
|
320
|
+
@freeze = true
|
318
321
|
until done
|
319
322
|
key = Ncurses.nonblocking_getch
|
320
323
|
if key == Ncurses::KEY_CANCEL
|
@@ -324,7 +327,7 @@ class BufferManager
|
|
324
327
|
done = true
|
325
328
|
end
|
326
329
|
end
|
327
|
-
|
330
|
+
@freeze = false
|
328
331
|
Ncurses.curs_set 0
|
329
332
|
erase_flash
|
330
333
|
draw_screen
|
@@ -334,7 +337,15 @@ class BufferManager
|
|
334
337
|
end
|
335
338
|
|
336
339
|
def ask_yes_or_no question
|
337
|
-
|
340
|
+
r = ask_getch(question, "ynYN")
|
341
|
+
case r
|
342
|
+
when ?y, ?Y
|
343
|
+
true
|
344
|
+
when nil
|
345
|
+
nil
|
346
|
+
else
|
347
|
+
false
|
348
|
+
end
|
338
349
|
end
|
339
350
|
|
340
351
|
def draw_minibuf
|
@@ -348,8 +359,8 @@ class BufferManager
|
|
348
359
|
def say s, id=nil
|
349
360
|
id ||= @minibuf_stack.length
|
350
361
|
@minibuf_stack[id] = s
|
351
|
-
unless @
|
352
|
-
|
362
|
+
unless @freeze
|
363
|
+
draw_screen
|
353
364
|
Ncurses.refresh
|
354
365
|
end
|
355
366
|
id
|
@@ -359,8 +370,8 @@ class BufferManager
|
|
359
370
|
|
360
371
|
def flash s
|
361
372
|
@flash = s
|
362
|
-
unless @
|
363
|
-
|
373
|
+
unless @freeze
|
374
|
+
draw_screen
|
364
375
|
Ncurses.refresh
|
365
376
|
end
|
366
377
|
end
|
@@ -373,19 +384,19 @@ class BufferManager
|
|
373
384
|
@minibuf_stack.delete_at i
|
374
385
|
end
|
375
386
|
end
|
376
|
-
unless @
|
377
|
-
|
387
|
+
unless @freeze
|
388
|
+
draw_screen
|
378
389
|
Ncurses.refresh
|
379
390
|
end
|
380
391
|
end
|
381
392
|
|
382
393
|
def shell_out command
|
383
|
-
@
|
394
|
+
@freeze = true
|
384
395
|
Ncurses.endwin
|
385
396
|
system command
|
386
397
|
Ncurses.refresh
|
387
398
|
Ncurses.curs_set 0
|
388
|
-
@
|
399
|
+
@freeze = false
|
389
400
|
end
|
390
401
|
end
|
391
402
|
end
|
data/lib/sup/draft.rb
CHANGED
@@ -10,9 +10,9 @@ class DraftManager
|
|
10
10
|
self.class.i_am_the_instance self
|
11
11
|
end
|
12
12
|
|
13
|
-
def self.source_name; "drafts"; end
|
13
|
+
def self.source_name; "drafts://"; end
|
14
14
|
def self.source_id; 9999; end
|
15
|
-
def new_source; @source = DraftLoader.new
|
15
|
+
def new_source; @source = DraftLoader.new; end
|
16
16
|
|
17
17
|
def write_draft
|
18
18
|
offset = @source.gen_offset
|
@@ -20,7 +20,7 @@ class DraftManager
|
|
20
20
|
File.open(fn, "w") { |f| yield f }
|
21
21
|
|
22
22
|
@source.each do |offset, labels|
|
23
|
-
m = Message.new @source, offset, labels
|
23
|
+
m = Message.new :source => @source, :source_info => offset, :labels => labels
|
24
24
|
Index.add_message m
|
25
25
|
UpdateManager.relay :add, m
|
26
26
|
end
|
@@ -36,25 +36,30 @@ class DraftManager
|
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
-
class DraftLoader
|
40
|
-
attr_accessor :dir
|
41
|
-
bool_reader :dirty
|
39
|
+
class DraftLoader < Source
|
40
|
+
attr_accessor :dir
|
42
41
|
|
43
|
-
def initialize
|
42
|
+
def initialize cur_offset=0
|
43
|
+
dir = Redwood::DRAFT_DIR
|
44
44
|
Dir.mkdir dir unless File.exists? dir
|
45
|
+
super "draft://#{dir}", cur_offset, true, false
|
45
46
|
@dir = dir
|
46
|
-
@end_offset = end_offset
|
47
|
-
@dirty = false
|
48
47
|
end
|
49
48
|
|
50
|
-
def done?; !File.exists? fn_for_offset(@end_offset); end
|
51
|
-
def usual?; true; end
|
52
49
|
def id; DraftManager.source_id; end
|
53
50
|
def to_s; DraftManager.source_name; end
|
54
|
-
|
51
|
+
|
52
|
+
def next
|
53
|
+
ret = nil
|
54
|
+
begin
|
55
|
+
ret = cur_offset
|
56
|
+
self.cur_offset = cur_offset + 1
|
57
|
+
end until File.exists? fn_for_offset(ret)
|
58
|
+
[ret, [:draft]]
|
59
|
+
end
|
55
60
|
|
56
61
|
def gen_offset
|
57
|
-
i =
|
62
|
+
i = cur_offset
|
58
63
|
while File.exists? fn_for_offset(i)
|
59
64
|
i += 1
|
60
65
|
end
|
@@ -77,8 +82,7 @@ class DraftLoader
|
|
77
82
|
end
|
78
83
|
end
|
79
84
|
|
80
|
-
|
81
|
-
def load_header_text offset
|
85
|
+
def raw_header offset
|
82
86
|
ret = ""
|
83
87
|
File.open fn_for_offset(offset) do |f|
|
84
88
|
until f.eof? || (l = f.gets) =~ /^$/
|
@@ -88,18 +92,18 @@ class DraftLoader
|
|
88
92
|
ret
|
89
93
|
end
|
90
94
|
|
91
|
-
def
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
@dirty = true
|
95
|
+
def raw_full_message offset
|
96
|
+
ret = ""
|
97
|
+
File.open fn_for_offset(offset) do |f|
|
98
|
+
ret += l until f.eof?
|
96
99
|
end
|
100
|
+
ret
|
97
101
|
end
|
98
102
|
|
99
|
-
def
|
100
|
-
def
|
103
|
+
def start_offset; 0; end
|
104
|
+
def end_offset; Dir.new(@dir).entries.sort.last.to_i + 1; end
|
101
105
|
end
|
102
106
|
|
103
|
-
Redwood::register_yaml(DraftLoader, %w(
|
107
|
+
Redwood::register_yaml(DraftLoader, %w(cur_offset))
|
104
108
|
|
105
109
|
end
|
data/lib/sup/imap.rb
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'net/imap'
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
module Redwood
|
6
|
+
|
7
|
+
class IMAP < Source
|
8
|
+
attr_reader :labels
|
9
|
+
|
10
|
+
def initialize uri, username, password, last_uid=nil, usual=true, archived=false, id=nil
|
11
|
+
raise ArgumentError, "username and password must be specified" unless username && password
|
12
|
+
|
13
|
+
super uri, last_uid, usual, archived, id
|
14
|
+
|
15
|
+
@parsed_uri = URI(uri)
|
16
|
+
@username = username
|
17
|
+
@password = password
|
18
|
+
@imap = nil
|
19
|
+
@labels = [:unread]
|
20
|
+
@labels << mailbox.intern unless mailbox =~ /inbox/i || mailbox.empty?
|
21
|
+
@labels << :inbox unless archived?
|
22
|
+
|
23
|
+
connect
|
24
|
+
end
|
25
|
+
|
26
|
+
def connect
|
27
|
+
return false if broken?
|
28
|
+
return true if @imap
|
29
|
+
Redwood::log "connecting to #{@parsed_uri.host} port #{ssl? ? 993 : 143}, ssl=#{ssl?} ..."
|
30
|
+
|
31
|
+
## ok, this is FUCKING ANNOYING.
|
32
|
+
##
|
33
|
+
## what imap.rb likes to do is, if an exception occurs, catch it
|
34
|
+
## and re-raise it on the calling thread. seems reasonable. but
|
35
|
+
## what that REALLY means is that the only way to reasonably
|
36
|
+
## initialize imap is in its own thread, because otherwise, you
|
37
|
+
## will never be able to catch the exception it raises on the
|
38
|
+
## calling thread, and the backtrace will not make any sense at
|
39
|
+
## all, and you will waste HOURS of your life on this fucking
|
40
|
+
## problem.
|
41
|
+
##
|
42
|
+
## FUCK!!!!!!!!!
|
43
|
+
::Thread.new do
|
44
|
+
begin
|
45
|
+
#raise Net::IMAP::ByeResponseError, "simulated imap failure"
|
46
|
+
@imap = Net::IMAP.new @parsed_uri.host, ssl? ? 993 : 143, ssl?
|
47
|
+
@imap.authenticate 'LOGIN', @username, @password
|
48
|
+
@imap.examine mailbox
|
49
|
+
Redwood::log "successfully connected to #{@parsed_uri}, mailbox #{mailbox}"
|
50
|
+
rescue Exception => e
|
51
|
+
self.broken_msg = e.message.chomp # fucking chomp! fuck!!!
|
52
|
+
@imap = nil
|
53
|
+
Redwood::log "error connecting to IMAP server: #{self.broken_msg}"
|
54
|
+
end
|
55
|
+
end.join
|
56
|
+
|
57
|
+
!!@imap
|
58
|
+
end
|
59
|
+
private :connect
|
60
|
+
|
61
|
+
def mailbox; @parsed_uri.path[1..-1] end ##XXXX TODO handle nil
|
62
|
+
def ssl?; @parsed_uri.scheme == 'imaps' end
|
63
|
+
|
64
|
+
def load_header uid=nil
|
65
|
+
MBox::read_header StringIO.new(raw_header(uid))
|
66
|
+
end
|
67
|
+
|
68
|
+
def load_message uid
|
69
|
+
RMail::Parser.read raw_full_message(uid)
|
70
|
+
end
|
71
|
+
|
72
|
+
## load the full header text
|
73
|
+
def raw_header uid
|
74
|
+
connect or return broken_msg
|
75
|
+
begin
|
76
|
+
connect or return broken_msg
|
77
|
+
rescue Exception => e
|
78
|
+
raise "wtf: #{e.inspect}"
|
79
|
+
end
|
80
|
+
@imap.uid_fetch(uid, 'RFC822.HEADER')[0].attr['RFC822.HEADER'].gsub(/\r\n/, "\n")
|
81
|
+
end
|
82
|
+
|
83
|
+
def raw_full_message uid
|
84
|
+
connect or return broken_msg
|
85
|
+
@imap.uid_fetch(uid, 'RFC822')[0].attr['RFC822'].gsub(/\r\n/, "\n")
|
86
|
+
end
|
87
|
+
|
88
|
+
def each
|
89
|
+
connect or return broken_msg
|
90
|
+
uids = @imap.uid_search ['UID', "#{cur_offset}:#{end_offset}"]
|
91
|
+
uids.each do |uid|
|
92
|
+
@last_uid = uid
|
93
|
+
@dirty = true
|
94
|
+
yield uid, labels
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def start_offset; 1; end
|
99
|
+
def end_offset
|
100
|
+
connect or return start_offset
|
101
|
+
@imap.uid_search(['ALL']).last
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
Redwood::register_yaml(IMAP, %w(uri username password offset usual archived id))
|
106
|
+
|
107
|
+
end
|
data/lib/sup/index.rb
CHANGED
@@ -18,17 +18,11 @@ end
|
|
18
18
|
class Index
|
19
19
|
include Singleton
|
20
20
|
|
21
|
-
LOAD_THREAD_PETIT_DELAY = 0.1
|
22
|
-
LOAD_THREAD_GRAND_DELAY = 5
|
23
|
-
|
24
|
-
MESSAGES_AT_A_TIME = 10
|
25
|
-
|
26
21
|
attr_reader :index # debugging only
|
27
22
|
|
28
23
|
def initialize dir=BASE_DIR
|
29
24
|
@dir = dir
|
30
25
|
@mutex = Mutex.new
|
31
|
-
@load_thread = nil # loads new messages
|
32
26
|
@sources = {}
|
33
27
|
@sources_dirty = false
|
34
28
|
|
@@ -50,7 +44,8 @@ class Index
|
|
50
44
|
raise "duplicate source!" if @sources.include? source
|
51
45
|
@sources_dirty = true
|
52
46
|
source.id ||= @sources.size
|
53
|
-
|
47
|
+
##TODO: why was this necessary?
|
48
|
+
##source.id += 1 while @sources.member? source.id
|
54
49
|
@sources[source.id] = source
|
55
50
|
end
|
56
51
|
|
@@ -71,7 +66,7 @@ class Index
|
|
71
66
|
field_infos = Ferret::Index::FieldInfos.new :store => :yes
|
72
67
|
field_infos.add_field :message_id
|
73
68
|
field_infos.add_field :source_id
|
74
|
-
field_infos.add_field :source_info
|
69
|
+
field_infos.add_field :source_info
|
75
70
|
field_infos.add_field :date, :index => :untokenized
|
76
71
|
field_infos.add_field :body, :store => :no
|
77
72
|
field_infos.add_field :label
|
@@ -190,31 +185,50 @@ class Index
|
|
190
185
|
source = @sources[doc[:source_id].to_i]
|
191
186
|
#puts "building message #{doc[:message_id]} (#{source}##{doc[:source_info]})"
|
192
187
|
raise "invalid source #{doc[:source_id]}" unless source
|
193
|
-
begin
|
194
|
-
raise "no snippet" unless doc[:snippet]
|
195
|
-
Message.new source, doc[:source_info].to_i,
|
196
|
-
doc[:label].split(" ").map { |s| s.intern },
|
197
|
-
doc[:snippet]
|
198
|
-
rescue MessageFormatError => e
|
199
|
-
raise IndexError.new(source, "error building message #{doc[:message_id]} at #{source}/#{doc[:source_info]}: #{e.message}")
|
200
|
-
nil
|
201
|
-
end
|
202
|
-
end
|
203
188
|
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
189
|
+
m =
|
190
|
+
if source.broken?
|
191
|
+
nil
|
192
|
+
else
|
193
|
+
begin
|
194
|
+
Message.new :source => source, :source_info => doc[:source_info].to_i,
|
195
|
+
:labels => doc[:label].split(" ").map { |s| s.intern },
|
196
|
+
:snippet => doc[:snippet]
|
197
|
+
rescue MessageFormatError => e
|
198
|
+
raise IndexError.new(source, "error building message #{doc[:message_id]} at #{source}/#{doc[:source_info]}: #{e.message}")
|
199
|
+
rescue SourceError => e
|
200
|
+
nil
|
201
|
+
end
|
210
202
|
end
|
203
|
+
|
204
|
+
unless m
|
205
|
+
fake_header = {
|
206
|
+
"date" => Time.at(doc[:date].to_i),
|
207
|
+
"subject" => unwrap_subj(doc[:subject]),
|
208
|
+
"from" => doc[:from],
|
209
|
+
"to" => doc[:to],
|
210
|
+
"message-id" => doc[:message_id],
|
211
|
+
"references" => doc[:refs],
|
212
|
+
}
|
213
|
+
|
214
|
+
m = Message.new :labels => doc[:label].split(" ").map { |s| s.intern },
|
215
|
+
:snippet => doc[:snippet], :header => fake_header,
|
216
|
+
:body => <<EOS
|
217
|
+
#{doc[:snippet]}...
|
218
|
+
|
219
|
+
An error occurred while loading this message. It is possible that the source
|
220
|
+
has changed, or (in the case of remote sources) is down.
|
221
|
+
|
222
|
+
The error message was:
|
223
|
+
#{source.broken_msg}
|
224
|
+
EOS
|
211
225
|
end
|
226
|
+
m
|
212
227
|
end
|
213
228
|
|
214
|
-
def end_load_thread; @load_thread = nil; end
|
215
229
|
def fresh_thread_id; @next_thread_id += 1; end
|
216
|
-
|
217
230
|
def wrap_subj subj; "__START_SUBJECT__ #{subj} __END_SUBJECT__"; end
|
231
|
+
def unwrap_subj subj; subj =~ /__START_SUBJECT__ (.*?) __END_SUBJECT__/ && $1; end
|
218
232
|
|
219
233
|
def add_message m
|
220
234
|
return false if contains? m
|
@@ -238,7 +252,7 @@ class Index
|
|
238
252
|
:from => m.from ? m.from.email : "",
|
239
253
|
:to => (m.to + m.cc + m.bcc).map { |x| x.email }.join(" "),
|
240
254
|
:subject => wrap_subj(Message.normalize_subj(m.subj)),
|
241
|
-
:refs => (m.refs + m.replytos).join(" "),
|
255
|
+
:refs => (m.refs + m.replytos).uniq.join(" "),
|
242
256
|
}
|
243
257
|
|
244
258
|
@index.add_document d
|
@@ -316,38 +330,16 @@ protected
|
|
316
330
|
|
317
331
|
def save_sources fn=Redwood::SOURCE_FN
|
318
332
|
if @sources_dirty || @sources.any? { |id, s| s.dirty? }
|
319
|
-
|
333
|
+
bakfn = fn + ".bak"
|
334
|
+
if File.exists? fn
|
335
|
+
File.chmod 0600, fn
|
336
|
+
FileUtils.mv fn, bakfn, :force => true unless File.exists?(bakfn) && File.size(bakfn) > File.size(fn)
|
337
|
+
end
|
320
338
|
Redwood::save_yaml_obj @sources.values, fn
|
339
|
+
File.chmod 0600, fn
|
321
340
|
end
|
322
341
|
@sources_dirty = false
|
323
342
|
end
|
324
|
-
|
325
|
-
def load_some_entries max=ENTRIES_AT_A_TIME, delay1=nil, delay2=nil
|
326
|
-
num = 0
|
327
|
-
begin
|
328
|
-
@sources.each_with_index do |source, source_id|
|
329
|
-
next if source.done? || num >= max
|
330
|
-
source.each do |source_info, label|
|
331
|
-
begin
|
332
|
-
m = Message.new(source, source_info, label + [:inbox])
|
333
|
-
add_message m unless contains_id? m.id
|
334
|
-
puts m.content.inspect
|
335
|
-
num += 1
|
336
|
-
rescue MessageFormatError => e
|
337
|
-
$stderr.puts "ignoring erroneous message at #{source}##{source_info}: #{e.message}"
|
338
|
-
end
|
339
|
-
break if num >= max
|
340
|
-
sleep delay1 if delay1
|
341
|
-
end
|
342
|
-
Redwood::log "loaded #{num} entries from #{source}"
|
343
|
-
sleep delay2 if delay2
|
344
|
-
end
|
345
|
-
ensure
|
346
|
-
save_sources
|
347
|
-
save_index
|
348
|
-
end
|
349
|
-
num
|
350
|
-
end
|
351
343
|
end
|
352
344
|
|
353
345
|
end
|