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.

@@ -1,3 +1,7 @@
1
+ == 0.12.1 / 2011-01-23
2
+ * Depend on ncursesw rather than ncurses (Ruby 1.9 compatibility)
3
+ * Add sup-import-dump
4
+
1
5
  == 0.12 / 2011-01-13
2
6
  * Remove deprecated IMAP, IMAPS, and mbox+ssh sources
3
7
  * Inline GPG support
@@ -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
@@ -12,7 +12,7 @@ end
12
12
 
13
13
  require 'fileutils'
14
14
  require 'trollop'
15
- require "sup"; Redwood::check_library_version_against "0.12"
15
+ require "sup"; Redwood::check_library_version_against "0.12.1"
16
16
 
17
17
  if ENV['SUP_PROFILE']
18
18
  require 'ruby-prof'
@@ -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})"
@@ -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
+
@@ -191,7 +191,7 @@ else
191
191
  end
192
192
  end
193
193
 
194
- Redwood::save_yaml_obj $config, Redwood::CONFIG_FN
194
+ Redwood::save_yaml_obj $config, Redwood::CONFIG_FN, false, true
195
195
 
196
196
  say "Ok, I've saved you up a nice lil' #{Redwood::CONFIG_FN}."
197
197
 
@@ -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
@@ -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
@@ -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
- puts "Deleting #{m.id}"
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
 
@@ -4,7 +4,7 @@ require 'rubygems'
4
4
  require 'uri'
5
5
  require 'tempfile'
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
  fail "not working yet"
10
10
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'rubygems'
4
4
  require 'trollop'
5
- require "sup"; Redwood::check_library_version_against "0.12"
5
+ require "sup"; Redwood::check_library_version_against "0.12.1"
6
6
 
7
7
  class Float
8
8
  def to_s; sprintf '%.2f', self; end
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 safe
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
- mode = File.stat(fn).mode if File.exists? fn
101
- File.open(safe_fn, "w", mode) { |f| f.puts o.to_yaml }
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") { |f| f.puts o.to_yaml }
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
@@ -575,6 +575,7 @@ EOS
575
575
  ## screen
576
576
  def ask domain, question, default=nil, &block
577
577
  raise "impossible!" if @asking
578
+ raise "Question too long" if Ncurses.cols <= question.length
578
579
  @asking = true
579
580
 
580
581
  @textfields[domain] ||= TextField.new
@@ -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
@@ -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
- uri = URI(Source.expand_filesystem_uri(uri))
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 || (URI(Source.expand_filesystem_uri(uri)) == URI(self.uri)); end
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
@@ -18,22 +18,24 @@ class MBox < Source
18
18
 
19
19
  case uri_or_fp
20
20
  when String
21
- uri = URI(Source.expand_filesystem_uri(uri_or_fp))
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 = File.open uri.path, 'rb'
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 || (self.uri.is_a?(String) && (URI(Source.expand_filesystem_uri(uri)) == URI(Source.expand_filesystem_uri(self.uri)))) end
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
- offset = first_offset
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
@@ -115,10 +115,11 @@ EOS
115
115
  yield "Deleting #{m.id}"
116
116
  elsif action == :add
117
117
  if old_m
118
- if not old_m.locations.member? m.location
119
- yield "Message at #{m.source_info} is an updated of an old message. Updating labels from #{old_m.labels.to_a * ','} => #{m.labels.to_a * ','}"
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.source_info}"
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
@@ -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; sources.find { |s| s.is_source_for? uri }; end
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
- bakfn = fn + ".bak"
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
- version: "0.12"
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-14 21:13:42 -08:00
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: ncurses
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: &id006 !ruby/object:Gem::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: *id006
89
+ version_requirements: *id005
102
90
  - !ruby/object:Gem::Dependency
103
91
  name: lockfile
104
92
  prerelease: false
105
- requirement: &id007 !ruby/object:Gem::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: *id007
102
+ version_requirements: *id006
115
103
  - !ruby/object:Gem::Dependency
116
104
  name: mime-types
117
105
  prerelease: false
118
- requirement: &id008 !ruby/object:Gem::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: *id008
115
+ version_requirements: *id007
128
116
  - !ruby/object:Gem::Dependency
129
117
  name: gettext
130
118
  prerelease: false
131
- requirement: &id009 !ruby/object:Gem::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: *id009
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