rgrove-larch 0.0.1.3 → 0.0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/larch +31 -12
- data/lib/larch/imap.rb +8 -3
- data/lib/larch.rb +33 -14
- metadata +1 -1
data/bin/larch
CHANGED
@@ -27,21 +27,25 @@ EOS
|
|
27
27
|
# opt :dry_run, "Don't actually do anything.", :short => '-n'
|
28
28
|
opt :fast_scan, "Use a faster (but less accurate) method to scan mailboxes. This may result in messages being re-copied.", :short => :none
|
29
29
|
opt :from_folder, "Source folder to copy from.", :short => :none, :default => 'INBOX'
|
30
|
-
opt :from_pass, "Source server password (
|
31
|
-
opt :from_user, "Source server username (
|
30
|
+
opt :from_pass, "Source server password (Default: prompt).", :short => :none, :type => :string
|
31
|
+
opt :from_user, "Source server username (Default: prompt).", :short => :none, :type => :string
|
32
32
|
opt :no_create_folder, "Don't create destination folders that don't already exist.", :short => :none
|
33
33
|
opt :to_folder, "Destination folder to copy to.", :short => :none, :default => 'INBOX'
|
34
|
-
opt :to_pass, "Destination server password (
|
35
|
-
opt :to_user, "Destination server username (
|
34
|
+
opt :to_pass, "Destination server password (Default: prompt).", :short => :none, :type => :string
|
35
|
+
opt :to_user, "Destination server username (Default: prompt).", :short => :none, :type => :string
|
36
36
|
opt :verbosity, "Output verbosity: debug, info, warn, error, or fatal.", :short => '-V', :default => 'info'
|
37
37
|
end
|
38
38
|
|
39
39
|
# Validate command-line options.
|
40
40
|
[:from, :to].each do |sym|
|
41
|
-
|
41
|
+
unless options[sym] =~ IMAP::REGEX_URI
|
42
|
+
Trollop.die sym, "must be a valid IMAP URI (e.g. imap://example.com)"
|
43
|
+
end
|
42
44
|
end
|
43
45
|
|
44
|
-
|
46
|
+
unless Logger::LEVELS.has_key?(options[:verbosity].to_sym)
|
47
|
+
Trollop.die :verbosity, "must be one of: #{Logger::LEVELS.keys.join(', ')}"
|
48
|
+
end
|
45
49
|
|
46
50
|
# Create URIs.
|
47
51
|
options[:from] = URI(options[:from])
|
@@ -50,16 +54,31 @@ EOS
|
|
50
54
|
options[:to].path = '/' + CGI.escape(options[:to_folder].gsub(/^\//, ''))
|
51
55
|
|
52
56
|
# Prompt for usernames and passwords if necessary.
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
+
unless options[:from_user]
|
58
|
+
options[:from_user] = ask("Source username (#{options[:from].host}): ")
|
59
|
+
end
|
60
|
+
|
61
|
+
unless options[:from_pass]
|
62
|
+
options[:from_pass] = ask("Source password (#{options[:from].host}): ") {|q| q.echo = false }
|
63
|
+
end
|
64
|
+
|
65
|
+
unless options[:to_user]
|
66
|
+
options[:to_user] = ask("Destination username (#{options[:to].host}): ")
|
67
|
+
end
|
68
|
+
|
69
|
+
unless options[:to_pass]
|
70
|
+
options[:to_pass] = ask("Destination password (#{options[:to].host}): ") {|q| q.echo = false }
|
71
|
+
end
|
57
72
|
|
58
73
|
# Go go go!
|
59
74
|
init(options[:verbosity])
|
60
75
|
|
61
|
-
source = IMAP.new(options[:from], options[:from_user], options[:from_pass],
|
62
|
-
|
76
|
+
source = IMAP.new(options[:from], options[:from_user], options[:from_pass],
|
77
|
+
:fast_scan => options[:fast_scan])
|
78
|
+
|
79
|
+
dest = IMAP.new(options[:to], options[:to_user], options[:to_pass],
|
80
|
+
:create_mailbox => !options[:no_create_folder],
|
81
|
+
:fast_scan => options[:fast_scan])
|
63
82
|
|
64
83
|
for sig in [:SIGINT, :SIGQUIT, :SIGTERM]
|
65
84
|
trap(sig) { @log.fatal "Interrupted (#{sig})"; summary; exit }
|
data/lib/larch/imap.rb
CHANGED
@@ -145,7 +145,7 @@ class IMAP
|
|
145
145
|
raise NotFoundError, "message not found: #{message_id}" if uid.nil?
|
146
146
|
|
147
147
|
debug "fetching envelope: #{message_id}"
|
148
|
-
|
148
|
+
imap_uid_fetch([uid], 'ENVELOPE').first.attr['ENVELOPE']
|
149
149
|
end
|
150
150
|
|
151
151
|
# Fetches a Larch::Message instance representing the message with the
|
@@ -157,7 +157,7 @@ class IMAP
|
|
157
157
|
raise NotFoundError, "message not found: #{message_id}" if uid.nil?
|
158
158
|
|
159
159
|
debug "#{peek ? 'peeking at' : 'fetching'} message: #{message_id}"
|
160
|
-
data =
|
160
|
+
data = imap_uid_fetch([uid], [(peek ? 'BODY.PEEK[]' : 'BODY[]'), 'FLAGS', 'INTERNALDATE', 'ENVELOPE']).first
|
161
161
|
|
162
162
|
Message.new(message_id, data.attr['ENVELOPE'], data.attr['BODY[]'],
|
163
163
|
data.attr['FLAGS'], Time.parse(data.attr['INTERNALDATE']))
|
@@ -332,6 +332,7 @@ class IMAP
|
|
332
332
|
begin
|
333
333
|
unsafe_connect unless @imap
|
334
334
|
rescue *RECOVERABLE_ERRORS => e
|
335
|
+
info "#{e.class.name}: #{e.message} (will retry)"
|
335
336
|
raise unless (retries += 1) <= 3
|
336
337
|
|
337
338
|
@imap = nil
|
@@ -344,14 +345,18 @@ class IMAP
|
|
344
345
|
begin
|
345
346
|
yield
|
346
347
|
rescue *RECOVERABLE_ERRORS => e
|
348
|
+
info "#{e.class.name}: #{e.message} (will retry)"
|
347
349
|
raise unless (retries += 1) <= 3
|
348
350
|
|
349
351
|
sleep 2 * retries
|
350
352
|
retry
|
351
353
|
end
|
352
354
|
|
355
|
+
rescue Net::IMAP::NoResponseError => e
|
356
|
+
raise Error, "#{e.class.name}: #{e.message} (giving up)"
|
357
|
+
|
353
358
|
rescue IOError, Net::IMAP::Error, OpenSSL::SSL::SSLError, SocketError, SystemCallError => e
|
354
|
-
raise FatalError, "
|
359
|
+
raise FatalError, "#{e.class.name}: #{e.message} (giving up)"
|
355
360
|
end
|
356
361
|
|
357
362
|
def unsafe_connect
|
data/lib/larch.rb
CHANGED
@@ -34,7 +34,8 @@ module Larch
|
|
34
34
|
raise ArgumentError, "source must be a Larch::IMAP instance" unless source.is_a?(IMAP)
|
35
35
|
raise ArgumentError, "dest must be a Larch::IMAP instance" unless dest.is_a?(IMAP)
|
36
36
|
|
37
|
-
msgq
|
37
|
+
msgq = SizedQueue.new(32)
|
38
|
+
mutex = Mutex.new
|
38
39
|
|
39
40
|
@copied = 0
|
40
41
|
@failed = 0
|
@@ -42,18 +43,30 @@ module Larch
|
|
42
43
|
|
43
44
|
@log.info "copying messages from #{source.uri} to #{dest.uri}"
|
44
45
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
46
|
+
source_scan = Thread.new do
|
47
|
+
source.scan_mailbox
|
48
|
+
end
|
49
|
+
|
50
|
+
dest_scan = Thread.new do
|
51
|
+
dest.scan_mailbox
|
52
|
+
end
|
49
53
|
|
50
|
-
|
54
|
+
source_scan.join
|
55
|
+
dest_scan.join
|
56
|
+
|
57
|
+
source_copy = Thread.new do
|
51
58
|
begin
|
52
|
-
@total = source.length
|
59
|
+
mutex.synchronize { @total = source.length }
|
53
60
|
|
54
61
|
source.each do |id|
|
55
62
|
next if dest.has_message?(id)
|
56
|
-
|
63
|
+
|
64
|
+
begin
|
65
|
+
msgq << source.peek(id)
|
66
|
+
rescue Larch::IMAP::Error => e
|
67
|
+
mutex.synchronize { @failed += 1 }
|
68
|
+
next
|
69
|
+
end
|
57
70
|
end
|
58
71
|
|
59
72
|
rescue => e
|
@@ -64,29 +77,35 @@ module Larch
|
|
64
77
|
end
|
65
78
|
end
|
66
79
|
|
67
|
-
|
80
|
+
dest_copy = Thread.new do
|
68
81
|
begin
|
69
82
|
while msg = msgq.pop do
|
70
83
|
break if msg == :finished
|
71
84
|
|
72
|
-
|
73
|
-
|
85
|
+
if msg.envelope.from
|
86
|
+
env_from = msg.envelope.from.first
|
87
|
+
from = "#{env_from.mailbox}@#{env_from.host}"
|
88
|
+
else
|
89
|
+
from = '?'
|
90
|
+
end
|
91
|
+
|
92
|
+
@log.info "copying message: #{from} - #{msg.envelope.subject}"
|
74
93
|
dest << msg
|
75
94
|
|
76
|
-
@copied += 1
|
95
|
+
mutex.synchronize { @copied += 1 }
|
77
96
|
end
|
78
97
|
|
79
98
|
rescue IMAP::FatalError => e
|
80
99
|
@log.fatal e.message
|
81
100
|
|
82
101
|
rescue => e
|
83
|
-
@failed += 1
|
102
|
+
mutex.synchronize { @failed += 1 }
|
84
103
|
@log.error e.message
|
85
104
|
retry
|
86
105
|
end
|
87
106
|
end
|
88
107
|
|
89
|
-
|
108
|
+
dest_copy.join
|
90
109
|
|
91
110
|
source.disconnect
|
92
111
|
dest.disconnect
|