sup 0.12 → 0.12.1
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 +4 -0
- data/ReleaseNotes +8 -0
- data/bin/sup +1 -1
- data/bin/sup-add +1 -1
- data/bin/sup-cmd +138 -0
- data/bin/sup-config +1 -1
- data/bin/sup-import-dump +99 -0
- data/bin/sup-server +44 -0
- data/bin/sup-sync +14 -13
- data/bin/sup-sync-back +1 -1
- data/bin/sup-tweak-labels +1 -1
- data/lib/sup.rb +30 -7
- data/lib/sup/buffer.rb +1 -0
- data/lib/sup/index.rb +15 -0
- data/lib/sup/maildir.rb +3 -2
- data/lib/sup/mbox.rb +24 -4
- data/lib/sup/poll.rb +6 -3
- data/lib/sup/source.rb +13 -9
- metadata +18 -24
data/History.txt
CHANGED
data/ReleaseNotes
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
Release 0.12.1:
|
2
|
+
|
3
|
+
This release changes the gem dependency on ncurses to ncursesw, which
|
4
|
+
allows the gem to install cleanly on Ruby 1.9.
|
5
|
+
|
6
|
+
The new sup-import-dump program applies labels to an existing index,
|
7
|
+
which could be done with sup-sync before 0.12.
|
8
|
+
|
1
9
|
Release 0.12:
|
2
10
|
|
3
11
|
Deprecated remote sources have been removed.
|
data/bin/sup
CHANGED
data/bin/sup-add
CHANGED
@@ -4,7 +4,7 @@ require 'uri'
|
|
4
4
|
require 'rubygems'
|
5
5
|
require 'highline/import'
|
6
6
|
require 'trollop'
|
7
|
-
require "sup"; Redwood::check_library_version_against "0.12"
|
7
|
+
require "sup"; Redwood::check_library_version_against "0.12.1"
|
8
8
|
|
9
9
|
$opts = Trollop::options do
|
10
10
|
version "sup-add (sup #{Redwood::VERSION})"
|
data/bin/sup-cmd
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'rubygems'
|
3
|
+
require 'trollop'
|
4
|
+
require 'sup'
|
5
|
+
require 'sup/client'
|
6
|
+
require 'pp'
|
7
|
+
require 'yaml'
|
8
|
+
include Redwood
|
9
|
+
|
10
|
+
SUB_COMMANDS = %w(query count label add)
|
11
|
+
global_opts = Trollop::options do
|
12
|
+
#version = "sup-cmd (sup #{Redwood::VERSION})"
|
13
|
+
banner <<EOS
|
14
|
+
Connect to a running sup-server.
|
15
|
+
|
16
|
+
Usage:
|
17
|
+
sup-cmd [global options] command [options]
|
18
|
+
|
19
|
+
Valid commands: #{SUB_COMMANDS * ', '}
|
20
|
+
|
21
|
+
Global options:
|
22
|
+
EOS
|
23
|
+
|
24
|
+
opt :host, "server address", :type => :string, :default => 'localhost', :short => 'o'
|
25
|
+
opt :port, "server port", :type => :int, :default => 4300
|
26
|
+
opt :socket, "unix domain socket path", :type => :string, :default => nil
|
27
|
+
opt :verbose
|
28
|
+
|
29
|
+
conflicts :host, :socket
|
30
|
+
conflicts :port, :socket
|
31
|
+
|
32
|
+
stop_on SUB_COMMANDS
|
33
|
+
end
|
34
|
+
|
35
|
+
cmd = ARGV.shift
|
36
|
+
cmd_opts = case cmd
|
37
|
+
when "query"
|
38
|
+
Trollop.options do
|
39
|
+
opt :offset, "Offset", :default => 0, :type => :int
|
40
|
+
opt :limit, "Limit", :type => :int
|
41
|
+
opt :raw, "Retrieve raw message text", :default => false
|
42
|
+
end
|
43
|
+
when "count"
|
44
|
+
Trollop.options do
|
45
|
+
end
|
46
|
+
when "label"
|
47
|
+
Trollop.options do
|
48
|
+
opt :add_labels, "Labels to add", :default => ""
|
49
|
+
opt :remove_labels, "Labels to remove", :default => ""
|
50
|
+
end
|
51
|
+
when "add"
|
52
|
+
Trollop.options do
|
53
|
+
opt :labels, "Labels separated by commas", :default => ""
|
54
|
+
opt :mbox, "Treat input files as mboxes", :default => false
|
55
|
+
end
|
56
|
+
else
|
57
|
+
Trollop::die "unrecognized command #{cmd.inspect}"
|
58
|
+
end
|
59
|
+
|
60
|
+
class SupCmd < Redwood::Client
|
61
|
+
def initialize cmd, args, opts
|
62
|
+
@cmd = cmd
|
63
|
+
@opts = opts
|
64
|
+
@args = args
|
65
|
+
super()
|
66
|
+
end
|
67
|
+
|
68
|
+
def get_query
|
69
|
+
@args.first or fail "query argument required"
|
70
|
+
end
|
71
|
+
|
72
|
+
def connection_established
|
73
|
+
case @cmd
|
74
|
+
when "query"
|
75
|
+
query get_query, @opts[:offset], @opts[:limit], @opts[:raw] do |result|
|
76
|
+
if result
|
77
|
+
puts YAML.dump(result['summary'])
|
78
|
+
puts YAML.dump(result['raw']) if @opts[:raw]
|
79
|
+
else
|
80
|
+
close_connection
|
81
|
+
end
|
82
|
+
end
|
83
|
+
when "count"
|
84
|
+
count(get_query) do |x|
|
85
|
+
puts x
|
86
|
+
close_connection
|
87
|
+
end
|
88
|
+
when "label"
|
89
|
+
label get_query, @opts[:remove_labels].split(','), @opts[:add_labels].split(',') do
|
90
|
+
close_connection
|
91
|
+
end
|
92
|
+
when "add"
|
93
|
+
ARGF.binmode
|
94
|
+
labels = @opts[:labels].split(',')
|
95
|
+
get_message = lambda do
|
96
|
+
return ARGF.gets(nil) unless @opts[:mbox]
|
97
|
+
str = ""
|
98
|
+
l = ARGF.gets
|
99
|
+
str << l until ARGF.closed? || ARGF.eof? || MBox::is_break_line?(l = ARGF.gets)
|
100
|
+
str.empty? ? nil : str
|
101
|
+
end
|
102
|
+
i_s = i = 0
|
103
|
+
t = Time.now
|
104
|
+
while raw = get_message[]
|
105
|
+
i += 1
|
106
|
+
t_d = Time.now - t
|
107
|
+
if t_d >= 5
|
108
|
+
i_d = i - i_s
|
109
|
+
puts "indexed #{i} messages (#{i_d/t_d} m/s)" if global_opts[:verbose]
|
110
|
+
t = Time.now
|
111
|
+
i_s = i
|
112
|
+
end
|
113
|
+
add raw, labels do
|
114
|
+
close_connection
|
115
|
+
end
|
116
|
+
end
|
117
|
+
else
|
118
|
+
fail "#{@cmd} command unimplemented"
|
119
|
+
close_connection
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def unbind
|
124
|
+
EM.stop
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
|
129
|
+
EM.run do
|
130
|
+
if global_opts[:socket]
|
131
|
+
EM.connect global_opts[:socket], SupCmd, cmd, ARGV, cmd_opts.merge(global_opts)
|
132
|
+
else
|
133
|
+
EM.connect global_opts[:host], global_opts[:port], SupCmd, cmd, ARGV, cmd_opts.merge(global_opts)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
exit 0
|
138
|
+
|
data/bin/sup-config
CHANGED
data/bin/sup-import-dump
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'uri'
|
4
|
+
require 'rubygems'
|
5
|
+
require 'trollop'
|
6
|
+
require "sup"; Redwood::check_library_version_against "git"
|
7
|
+
|
8
|
+
PROGRESS_UPDATE_INTERVAL = 15 # seconds
|
9
|
+
|
10
|
+
class AbortExecution < SystemExit
|
11
|
+
end
|
12
|
+
|
13
|
+
opts = Trollop::options do
|
14
|
+
version "sup-import-dump (sup #{Redwood::VERSION})"
|
15
|
+
banner <<EOS
|
16
|
+
Imports message state previously exported by sup-dump into the index.
|
17
|
+
sup-import-dump operates on the index only, so the messages must have already
|
18
|
+
been added using sup-sync. If you need to recreate the index, see sup-sync
|
19
|
+
--restore <filename> instead.
|
20
|
+
|
21
|
+
Messages not mentioned in the dump file will not be modified.
|
22
|
+
|
23
|
+
Usage:
|
24
|
+
sup-import-dump [options] <dump file>
|
25
|
+
|
26
|
+
Options:
|
27
|
+
EOS
|
28
|
+
opt :verbose, "Print message ids as they're processed."
|
29
|
+
opt :ignore_missing, "Silently skip over messages that are not in the index."
|
30
|
+
opt :warn_missing, "Warn about messages that are not in the index, but continue."
|
31
|
+
opt :abort_missing, "Abort on encountering messages that are not in the index. (default)"
|
32
|
+
opt :atomic, "Use transaction to apply all changes atomically."
|
33
|
+
opt :dry_run, "Don't actually modify the index. Probably only useful with --verbose.", :short => "-n"
|
34
|
+
opt :version, "Show version information", :short => :none
|
35
|
+
|
36
|
+
conflicts :ignore_missing, :warn_missing, :abort_missing
|
37
|
+
end
|
38
|
+
Trollop::die "No dump file given" if ARGV.empty?
|
39
|
+
Trollop::die "Extra arguments given" if ARGV.length > 1
|
40
|
+
dump_name = ARGV.shift
|
41
|
+
missing_action = [:ignore_missing, :warn_missing, :abort_missing].find { |x| opts[x] } || :abort_missing
|
42
|
+
|
43
|
+
Redwood::start
|
44
|
+
index = Redwood::Index.init
|
45
|
+
|
46
|
+
index.lock_interactively or exit
|
47
|
+
begin
|
48
|
+
num_read = 0
|
49
|
+
num_changed = 0
|
50
|
+
index.load
|
51
|
+
index.begin_transaction if opts[:atomic]
|
52
|
+
|
53
|
+
IO.foreach dump_name do |l|
|
54
|
+
l =~ /^(\S+) \((.*?)\)$/ or raise "Can't read dump line: #{l.inspect}"
|
55
|
+
mid, labels = $1, $2
|
56
|
+
num_read += 1
|
57
|
+
|
58
|
+
unless index.contains_id? mid
|
59
|
+
if missing_action == :abort_missing
|
60
|
+
$stderr.puts "Message #{mid} not found in index, aborting."
|
61
|
+
raise AbortExecution, 10
|
62
|
+
elsif missing_action == :warn_missing
|
63
|
+
$stderr.puts "Message #{mid} not found in index, skipping."
|
64
|
+
end
|
65
|
+
|
66
|
+
next
|
67
|
+
end
|
68
|
+
|
69
|
+
m = index.build_message mid
|
70
|
+
new_labels = labels.to_set_of_symbols
|
71
|
+
|
72
|
+
if m.labels == new_labels
|
73
|
+
puts "#{mid} unchanged" if opts[:verbose]
|
74
|
+
next
|
75
|
+
end
|
76
|
+
|
77
|
+
puts "Changing flags for #{mid} from '#{m.labels.to_a * ' '}' to '#{new_labels.to_a * ' '}'" if opts[:verbose]
|
78
|
+
num_changed += 1
|
79
|
+
|
80
|
+
next if opts[:dry_run]
|
81
|
+
|
82
|
+
m.labels = new_labels
|
83
|
+
index.update_message_state m
|
84
|
+
end
|
85
|
+
|
86
|
+
index.commit_transaction if opts[:atomic]
|
87
|
+
puts "Updated #{num_changed} of #{num_read} messages."
|
88
|
+
rescue AbortExecution
|
89
|
+
index.cancel_transaction if opts[:atomic]
|
90
|
+
raise
|
91
|
+
rescue Exception => e
|
92
|
+
index.cancel_transaction if opts[:atomic]
|
93
|
+
File.open("sup-exception-log.txt", "w") { |f| f.puts e.backtrace }
|
94
|
+
raise
|
95
|
+
ensure
|
96
|
+
index.save_index unless opts[:atomic]
|
97
|
+
Redwood::finish
|
98
|
+
index.unlock
|
99
|
+
end
|
data/bin/sup-server
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'rubygems'
|
3
|
+
require 'trollop'
|
4
|
+
require 'sup'
|
5
|
+
require 'sup/server'
|
6
|
+
require 'pp'
|
7
|
+
require 'yaml'
|
8
|
+
include Redwood
|
9
|
+
|
10
|
+
global_opts = Trollop::options do
|
11
|
+
#version = "sup-cmd (sup #{Redwood::VERSION})"
|
12
|
+
banner <<EOS
|
13
|
+
Interact with a Sup index.
|
14
|
+
|
15
|
+
Usage:
|
16
|
+
sup-server [options]
|
17
|
+
EOS
|
18
|
+
|
19
|
+
opt :host, "address to listen on", :type => :string, :default => 'localhost', :short => 'o'
|
20
|
+
opt :port, "port to listen on", :type => :int, :default => 4300
|
21
|
+
opt :verbose
|
22
|
+
end
|
23
|
+
|
24
|
+
Redwood.start
|
25
|
+
Index.init
|
26
|
+
Index.lock_interactively or exit
|
27
|
+
begin
|
28
|
+
if(s = Redwood::SourceManager.source_for SentManager.source_uri)
|
29
|
+
SentManager.source = s
|
30
|
+
else
|
31
|
+
Redwood::SourceManager.add_source SentManager.default_source
|
32
|
+
end
|
33
|
+
|
34
|
+
Index.load
|
35
|
+
|
36
|
+
EM.run do
|
37
|
+
EM.start_server global_opts[:host], global_opts[:port],
|
38
|
+
Redwood::Server, Index.instance
|
39
|
+
EM.next_tick { puts "ready" }
|
40
|
+
end
|
41
|
+
|
42
|
+
ensure
|
43
|
+
Index.unlock
|
44
|
+
end
|
data/bin/sup-sync
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
require 'uri'
|
4
4
|
require 'rubygems'
|
5
5
|
require 'trollop'
|
6
|
-
require "sup"; Redwood::check_library_version_against "0.12"
|
6
|
+
require "sup"; Redwood::check_library_version_against "0.12.1"
|
7
7
|
|
8
8
|
PROGRESS_UPDATE_INTERVAL = 15 # seconds
|
9
9
|
|
@@ -117,14 +117,15 @@ begin
|
|
117
117
|
|
118
118
|
sources.each do |source|
|
119
119
|
puts "Scanning #{source}..."
|
120
|
-
num_added = num_updated = num_scanned = num_restored = 0
|
120
|
+
num_added = num_updated = num_deleted = num_scanned = num_restored = 0
|
121
121
|
last_info_time = start_time = Time.now
|
122
122
|
|
123
123
|
Redwood::PollManager.poll_from source do |action,m,old_m,progress|
|
124
|
+
num_scanned += 1
|
124
125
|
if action == :delete
|
125
|
-
|
126
|
+
num_deleted += 1
|
127
|
+
puts "Deleting #{m.id}" if opts[:verbose]
|
126
128
|
elsif action == :add
|
127
|
-
num_scanned += 1
|
128
129
|
seen[m.id] = true
|
129
130
|
|
130
131
|
## tweak source labels according to commandline arguments if necessary
|
@@ -172,20 +173,20 @@ begin
|
|
172
173
|
puts "Changing flags for #{source}##{m.source_info} from #{old_m.labels} to #{m.labels}" if opts[:verbose]
|
173
174
|
num_updated += 1
|
174
175
|
end
|
175
|
-
|
176
|
-
if Time.now - last_info_time > PROGRESS_UPDATE_INTERVAL
|
177
|
-
last_info_time = Time.now
|
178
|
-
elapsed = last_info_time - start_time
|
179
|
-
pctdone = progress * 100.0
|
180
|
-
remaining = (100.0 - pctdone) * (elapsed.to_f / pctdone)
|
181
|
-
printf "## read %dm (~%.0f%%) @ %.1fm/s. %s elapsed, ~%s remaining\n", num_scanned, pctdone, num_scanned / elapsed, elapsed.to_time_s, remaining.to_time_s
|
182
|
-
end
|
183
176
|
else fail
|
184
177
|
end
|
178
|
+
|
179
|
+
if Time.now - last_info_time > PROGRESS_UPDATE_INTERVAL
|
180
|
+
last_info_time = Time.now
|
181
|
+
elapsed = last_info_time - start_time
|
182
|
+
pctdone = progress * 100.0
|
183
|
+
remaining = (100.0 - pctdone) * (elapsed.to_f / pctdone)
|
184
|
+
printf "## scanned %dm (~%.0f%%) @ %.1fm/s. %s elapsed, ~%s remaining\n", num_scanned, pctdone, num_scanned / elapsed, elapsed.to_time_s, remaining.to_time_s
|
185
|
+
end
|
185
186
|
next if opts[:dry_run]
|
186
187
|
end
|
187
188
|
|
188
|
-
puts "Scanned #{num_scanned}, added #{num_added}, updated #{num_updated} messages from #{source}."
|
189
|
+
puts "Scanned #{num_scanned}, added #{num_added}, updated #{num_updated}, deleted #{num_deleted} messages from #{source}."
|
189
190
|
puts "Restored state on #{num_restored} (#{100.0 * num_restored / num_scanned}%) messages." if num_restored > 0
|
190
191
|
end
|
191
192
|
|
data/bin/sup-sync-back
CHANGED
data/bin/sup-tweak-labels
CHANGED
data/lib/sup.rb
CHANGED
@@ -38,7 +38,7 @@ class Module
|
|
38
38
|
end
|
39
39
|
|
40
40
|
module Redwood
|
41
|
-
VERSION = "0.12"
|
41
|
+
VERSION = "0.12.1"
|
42
42
|
|
43
43
|
BASE_DIR = ENV["SUP_BASE"] || File.join(ENV["HOME"], ".sup")
|
44
44
|
CONFIG_FN = File.join(BASE_DIR, "config.yaml")
|
@@ -86,7 +86,7 @@ module Redwood
|
|
86
86
|
module_function :reporting_thread, :record_exception, :exceptions
|
87
87
|
|
88
88
|
## one-stop shop for yamliciousness
|
89
|
-
def save_yaml_obj o, fn, safe=false
|
89
|
+
def save_yaml_obj o, fn, safe=false, backup=false
|
90
90
|
o = if o.is_a?(Array)
|
91
91
|
o.map { |x| (x.respond_to?(:before_marshal) && x.before_marshal) || x }
|
92
92
|
elsif o.respond_to? :before_marshal
|
@@ -95,13 +95,36 @@ module Redwood
|
|
95
95
|
o
|
96
96
|
end
|
97
97
|
|
98
|
-
if
|
98
|
+
mode = if File.exists? fn
|
99
|
+
File.stat(fn).mode
|
100
|
+
else
|
101
|
+
0600
|
102
|
+
end
|
103
|
+
|
104
|
+
if backup
|
105
|
+
backup_fn = fn + '.bak'
|
106
|
+
if File.exists?(fn) && File.size(fn) > 0
|
107
|
+
File.open(backup_fn, "w", mode) do |f|
|
108
|
+
File.open(fn, "r") { |old_f| FileUtils.copy_stream old_f, f }
|
109
|
+
f.fsync
|
110
|
+
end
|
111
|
+
end
|
112
|
+
File.open(fn, "w") do |f|
|
113
|
+
f.puts o.to_yaml
|
114
|
+
f.fsync
|
115
|
+
end
|
116
|
+
elsif safe
|
99
117
|
safe_fn = "#{File.dirname fn}/safe_#{File.basename fn}"
|
100
|
-
|
101
|
-
|
118
|
+
File.open(safe_fn, "w", mode) do |f|
|
119
|
+
f.puts o.to_yaml
|
120
|
+
f.fsync
|
121
|
+
end
|
102
122
|
FileUtils.mv safe_fn, fn
|
103
123
|
else
|
104
|
-
File.open(fn, "w")
|
124
|
+
File.open(fn, "w", mode) do |f|
|
125
|
+
f.puts o.to_yaml
|
126
|
+
f.fsync
|
127
|
+
end
|
105
128
|
end
|
106
129
|
end
|
107
130
|
|
@@ -285,7 +308,7 @@ EOS
|
|
285
308
|
:slip_rows => 0
|
286
309
|
}
|
287
310
|
begin
|
288
|
-
Redwood::save_yaml_obj config, filename
|
311
|
+
Redwood::save_yaml_obj config, filename, false, true
|
289
312
|
rescue StandardError => e
|
290
313
|
$stderr.puts "warning: #{e.message}"
|
291
314
|
end
|
data/lib/sup/buffer.rb
CHANGED
data/lib/sup/index.rb
CHANGED
@@ -256,6 +256,21 @@ EOS
|
|
256
256
|
end
|
257
257
|
end
|
258
258
|
|
259
|
+
# wrap all future changes inside a transaction so they're done atomically
|
260
|
+
def begin_transaction
|
261
|
+
synchronize { @xapian.begin_transaction }
|
262
|
+
end
|
263
|
+
|
264
|
+
# complete the transaction and write all previous changes to disk
|
265
|
+
def commit_transaction
|
266
|
+
synchronize { @xapian.commit_transaction }
|
267
|
+
end
|
268
|
+
|
269
|
+
# abort the transaction and revert all changes made since begin_transaction
|
270
|
+
def cancel_transaction
|
271
|
+
synchronize { @xapian.cancel_transaction }
|
272
|
+
end
|
273
|
+
|
259
274
|
## xapian-compact takes too long, so this is a no-op
|
260
275
|
## until we think of something better
|
261
276
|
def optimize
|
data/lib/sup/maildir.rb
CHANGED
@@ -10,7 +10,8 @@ class Maildir < Source
|
|
10
10
|
yaml_properties :uri, :usual, :archived, :id, :labels
|
11
11
|
def initialize uri, usual=true, archived=false, id=nil, labels=[]
|
12
12
|
super uri, usual, archived, id
|
13
|
-
|
13
|
+
@expanded_uri = Source.expand_filesystem_uri(uri)
|
14
|
+
uri = URI(@expanded_uri)
|
14
15
|
|
15
16
|
raise ArgumentError, "not a maildir URI" unless uri.scheme == "maildir"
|
16
17
|
raise ArgumentError, "maildir URI cannot have a host: #{uri.host}" if uri.host
|
@@ -24,7 +25,7 @@ class Maildir < Source
|
|
24
25
|
|
25
26
|
def file_path; @dir end
|
26
27
|
def self.suggest_labels_for path; [] end
|
27
|
-
def is_source_for? uri; super || (
|
28
|
+
def is_source_for? uri; super || (uri == @expanded_uri); end
|
28
29
|
|
29
30
|
def store_message date, from_email, &block
|
30
31
|
stored = false
|
data/lib/sup/mbox.rb
CHANGED
@@ -18,22 +18,24 @@ class MBox < Source
|
|
18
18
|
|
19
19
|
case uri_or_fp
|
20
20
|
when String
|
21
|
-
|
21
|
+
@expanded_uri = Source.expand_filesystem_uri(uri_or_fp)
|
22
|
+
uri = URI(@expanded_uri)
|
22
23
|
raise ArgumentError, "not an mbox uri" unless uri.scheme == "mbox"
|
23
24
|
raise ArgumentError, "mbox URI ('#{uri}') cannot have a host: #{uri.host}" if uri.host
|
24
25
|
raise ArgumentError, "mbox URI must have a path component" unless uri.path
|
25
|
-
@f =
|
26
|
+
@f = nil
|
26
27
|
@path = uri.path
|
27
28
|
else
|
28
29
|
@f = uri_or_fp
|
29
30
|
@path = uri_or_fp.path
|
31
|
+
@expanded_uri = "mbox://#{@path}"
|
30
32
|
end
|
31
33
|
|
32
34
|
super uri_or_fp, usual, archived, id
|
33
35
|
end
|
34
36
|
|
35
37
|
def file_path; @path end
|
36
|
-
def is_source_for? uri; super || (
|
38
|
+
def is_source_for? uri; super || (uri == @expanded_uri) end
|
37
39
|
|
38
40
|
def self.suggest_labels_for path
|
39
41
|
## heuristic: use the filename as a label, unless the file
|
@@ -45,9 +47,23 @@ class MBox < Source
|
|
45
47
|
end
|
46
48
|
end
|
47
49
|
|
50
|
+
def ensure_open
|
51
|
+
@f = File.open @path, 'rb' if @f.nil?
|
52
|
+
end
|
53
|
+
private :ensure_open
|
54
|
+
|
55
|
+
def go_idle
|
56
|
+
@mutex.synchronize do
|
57
|
+
return if @f.nil? or @path.nil?
|
58
|
+
@f.close
|
59
|
+
@f = nil
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
48
63
|
def load_header offset
|
49
64
|
header = nil
|
50
65
|
@mutex.synchronize do
|
66
|
+
ensure_open
|
51
67
|
@f.seek offset
|
52
68
|
header = parse_raw_email_header @f
|
53
69
|
end
|
@@ -56,6 +72,7 @@ class MBox < Source
|
|
56
72
|
|
57
73
|
def load_message offset
|
58
74
|
@mutex.synchronize do
|
75
|
+
ensure_open
|
59
76
|
@f.seek offset
|
60
77
|
begin
|
61
78
|
## don't use RMail::Mailbox::MBoxReader because it doesn't properly ignore
|
@@ -74,6 +91,7 @@ class MBox < Source
|
|
74
91
|
def raw_header offset
|
75
92
|
ret = ""
|
76
93
|
@mutex.synchronize do
|
94
|
+
ensure_open
|
77
95
|
@f.seek offset
|
78
96
|
until @f.eof? || (l = @f.gets) =~ /^\r*$/
|
79
97
|
ret << l
|
@@ -105,6 +123,7 @@ class MBox < Source
|
|
105
123
|
## sup-sync-back has to do it.
|
106
124
|
def each_raw_message_line offset
|
107
125
|
@mutex.synchronize do
|
126
|
+
ensure_open
|
108
127
|
@f.seek offset
|
109
128
|
until @f.eof? || MBox::is_break_line?(l = @f.gets)
|
110
129
|
yield l
|
@@ -118,7 +137,7 @@ class MBox < Source
|
|
118
137
|
|
119
138
|
def poll
|
120
139
|
first_offset = first_new_message
|
121
|
-
|
140
|
+
offset = first_offset
|
122
141
|
end_offset = File.size @f
|
123
142
|
while offset and offset < end_offset
|
124
143
|
yield :add,
|
@@ -131,6 +150,7 @@ class MBox < Source
|
|
131
150
|
|
132
151
|
def next_offset offset
|
133
152
|
@mutex.synchronize do
|
153
|
+
ensure_open
|
134
154
|
@f.seek offset
|
135
155
|
nil while line = @f.gets and not MBox::is_break_line? line
|
136
156
|
offset = @f.tell
|
data/lib/sup/poll.rb
CHANGED
@@ -115,10 +115,11 @@ EOS
|
|
115
115
|
yield "Deleting #{m.id}"
|
116
116
|
elsif action == :add
|
117
117
|
if old_m
|
118
|
-
|
119
|
-
|
118
|
+
new_locations = (m.locations - old_m.locations)
|
119
|
+
if not new_locations.empty?
|
120
|
+
yield "Message at #{new_locations[0].info} is an update of an old message. Updating labels from #{old_m.labels.to_a * ','} => #{m.labels.to_a * ','}"
|
120
121
|
else
|
121
|
-
yield "Skipping already-imported message at #{m.
|
122
|
+
yield "Skipping already-imported message at #{m.locations[-1].info}"
|
122
123
|
end
|
123
124
|
else
|
124
125
|
yield "Found new message at #{m.source_info} with labels #{m.labels.to_a * ','}"
|
@@ -182,6 +183,8 @@ EOS
|
|
182
183
|
end
|
183
184
|
end
|
184
185
|
end
|
186
|
+
|
187
|
+
source.go_idle
|
185
188
|
rescue SourceError => e
|
186
189
|
warn "problem getting messages from #{source}: #{e.message}"
|
187
190
|
end
|
data/lib/sup/source.rb
CHANGED
@@ -40,6 +40,7 @@ class Source
|
|
40
40
|
## - raw_header offset
|
41
41
|
## - raw_message offset
|
42
42
|
## - check (optional)
|
43
|
+
## - go_idle (optional)
|
43
44
|
## - next (or each, if you prefer): should return a message and an
|
44
45
|
## array of labels.
|
45
46
|
##
|
@@ -81,6 +82,11 @@ class Source
|
|
81
82
|
|
82
83
|
def read?; false; end
|
83
84
|
|
85
|
+
## release resources that are easy to reacquire. it is called
|
86
|
+
## after processing a source (e.g. polling) to prevent resource
|
87
|
+
## leaks (esp. file descriptors).
|
88
|
+
def go_idle; end
|
89
|
+
|
84
90
|
## Yields values of the form [Symbol, Hash]
|
85
91
|
## add: info, labels, progress
|
86
92
|
## delete: info, progress
|
@@ -149,7 +155,7 @@ end
|
|
149
155
|
module SerializeLabelsNicely
|
150
156
|
def before_marshal # can return an object
|
151
157
|
c = clone
|
152
|
-
c.instance_eval { @labels = @labels.to_a.map { |l| l.to_s } }
|
158
|
+
c.instance_eval { @labels = (@labels.to_a.map { |l| l.to_s }).sort }
|
153
159
|
c
|
154
160
|
end
|
155
161
|
|
@@ -187,7 +193,11 @@ class SourceManager
|
|
187
193
|
@source_mutex.synchronize { @sources.values }.sort_by { |s| s.id }.partition { |s| !s.archived? }.flatten
|
188
194
|
end
|
189
195
|
|
190
|
-
def source_for uri
|
196
|
+
def source_for uri
|
197
|
+
expanded_uri = Source.expand_filesystem_uri(uri)
|
198
|
+
sources.find { |s| s.is_source_for? expanded_uri }
|
199
|
+
end
|
200
|
+
|
191
201
|
def usual_sources; sources.find_all { |s| s.usual? }; end
|
192
202
|
def unusual_sources; sources.find_all { |s| !s.usual? }; end
|
193
203
|
|
@@ -202,13 +212,7 @@ class SourceManager
|
|
202
212
|
def save_sources fn=Redwood::SOURCE_FN
|
203
213
|
@source_mutex.synchronize do
|
204
214
|
if @sources_dirty
|
205
|
-
|
206
|
-
if File.exists? fn
|
207
|
-
File.chmod 0600, fn
|
208
|
-
FileUtils.mv fn, bakfn, :force => true unless File.exists?(bakfn) && File.size(fn) == 0
|
209
|
-
end
|
210
|
-
Redwood::save_yaml_obj sources, fn, true
|
211
|
-
File.chmod 0600, fn
|
215
|
+
Redwood::save_yaml_obj sources, fn, false, true
|
212
216
|
end
|
213
217
|
@sources_dirty = false
|
214
218
|
end
|
metadata
CHANGED
@@ -5,7 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 12
|
8
|
-
|
8
|
+
- 1
|
9
|
+
version: 0.12.1
|
9
10
|
platform: ruby
|
10
11
|
authors:
|
11
12
|
- William Morgan
|
@@ -13,7 +14,7 @@ autorequire:
|
|
13
14
|
bindir: bin
|
14
15
|
cert_chain: []
|
15
16
|
|
16
|
-
date: 2011-01-
|
17
|
+
date: 2011-01-23 21:12:40 -08:00
|
17
18
|
default_executable:
|
18
19
|
dependencies:
|
19
20
|
- !ruby/object:Gem::Dependency
|
@@ -33,7 +34,7 @@ dependencies:
|
|
33
34
|
type: :runtime
|
34
35
|
version_requirements: *id001
|
35
36
|
- !ruby/object:Gem::Dependency
|
36
|
-
name:
|
37
|
+
name: ncursesw
|
37
38
|
prerelease: false
|
38
39
|
requirement: &id002 !ruby/object:Gem::Requirement
|
39
40
|
none: false
|
@@ -72,23 +73,10 @@ dependencies:
|
|
72
73
|
version: "0"
|
73
74
|
type: :runtime
|
74
75
|
version_requirements: *id004
|
75
|
-
- !ruby/object:Gem::Dependency
|
76
|
-
name: net-ssh
|
77
|
-
prerelease: false
|
78
|
-
requirement: &id005 !ruby/object:Gem::Requirement
|
79
|
-
none: false
|
80
|
-
requirements:
|
81
|
-
- - ">="
|
82
|
-
- !ruby/object:Gem::Version
|
83
|
-
segments:
|
84
|
-
- 0
|
85
|
-
version: "0"
|
86
|
-
type: :runtime
|
87
|
-
version_requirements: *id005
|
88
76
|
- !ruby/object:Gem::Dependency
|
89
77
|
name: trollop
|
90
78
|
prerelease: false
|
91
|
-
requirement: &
|
79
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
92
80
|
none: false
|
93
81
|
requirements:
|
94
82
|
- - ">="
|
@@ -98,11 +86,11 @@ dependencies:
|
|
98
86
|
- 12
|
99
87
|
version: "1.12"
|
100
88
|
type: :runtime
|
101
|
-
version_requirements: *
|
89
|
+
version_requirements: *id005
|
102
90
|
- !ruby/object:Gem::Dependency
|
103
91
|
name: lockfile
|
104
92
|
prerelease: false
|
105
|
-
requirement: &
|
93
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
106
94
|
none: false
|
107
95
|
requirements:
|
108
96
|
- - ">="
|
@@ -111,11 +99,11 @@ dependencies:
|
|
111
99
|
- 0
|
112
100
|
version: "0"
|
113
101
|
type: :runtime
|
114
|
-
version_requirements: *
|
102
|
+
version_requirements: *id006
|
115
103
|
- !ruby/object:Gem::Dependency
|
116
104
|
name: mime-types
|
117
105
|
prerelease: false
|
118
|
-
requirement: &
|
106
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
119
107
|
none: false
|
120
108
|
requirements:
|
121
109
|
- - ~>
|
@@ -124,11 +112,11 @@ dependencies:
|
|
124
112
|
- 1
|
125
113
|
version: "1"
|
126
114
|
type: :runtime
|
127
|
-
version_requirements: *
|
115
|
+
version_requirements: *id007
|
128
116
|
- !ruby/object:Gem::Dependency
|
129
117
|
name: gettext
|
130
118
|
prerelease: false
|
131
|
-
requirement: &
|
119
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
132
120
|
none: false
|
133
121
|
requirements:
|
134
122
|
- - ">="
|
@@ -137,15 +125,18 @@ dependencies:
|
|
137
125
|
- 0
|
138
126
|
version: "0"
|
139
127
|
type: :runtime
|
140
|
-
version_requirements: *
|
128
|
+
version_requirements: *id008
|
141
129
|
description: "Sup is a console-based email client for people with a lot of email. It supports tagging, very fast full-text search, automatic contact-list management, and more. If you're the type of person who treats email as an extension of your long-term memory, Sup is for you. Sup makes it easy to: - Handle massive amounts of email. - Mix email from different sources: mbox files (even across different machines), Maildir directories, POP accounts, and GMail accounts. - Instantaneously search over your entire email collection. Search over body text, or use a query language to combine search predicates in any way. - Handle multiple accounts. Replying to email sent to a particular account will use the correct SMTP server, signature, and from address. - Add custom code to handle certain types of messages or to handle certain types of text within messages. - Organize email with user-defined labels, automatically track recent contacts, and much more! The goal of Sup is to become the email client of choice for nerds everywhere."
|
142
130
|
email: wmorgan-sup@masanjin.net
|
143
131
|
executables:
|
144
132
|
- sup
|
145
133
|
- sup-add
|
134
|
+
- sup-cmd
|
146
135
|
- sup-config
|
147
136
|
- sup-dump
|
137
|
+
- sup-import-dump
|
148
138
|
- sup-recover-sources
|
139
|
+
- sup-server
|
149
140
|
- sup-sync
|
150
141
|
- sup-sync-back
|
151
142
|
- sup-tweak-labels
|
@@ -161,9 +152,12 @@ files:
|
|
161
152
|
- ReleaseNotes
|
162
153
|
- bin/sup
|
163
154
|
- bin/sup-add
|
155
|
+
- bin/sup-cmd
|
164
156
|
- bin/sup-config
|
165
157
|
- bin/sup-dump
|
158
|
+
- bin/sup-import-dump
|
166
159
|
- bin/sup-recover-sources
|
160
|
+
- bin/sup-server
|
167
161
|
- bin/sup-sync
|
168
162
|
- bin/sup-sync-back
|
169
163
|
- bin/sup-tweak-labels
|