sup 0.0.3 → 0.0.4

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 CHANGED
@@ -1,3 +1,7 @@
1
+ == 0.0.4 / 2007-01-03
2
+
3
+ * Bugfixes, primarily for threaded networking.
4
+
1
5
  == 0.0.3 / 2007-01-02
2
6
 
3
7
  * Major speed increase for index views (inbox, search results), which
data/Manifest.txt CHANGED
@@ -9,6 +9,7 @@ bin/sup-recover-sources
9
9
  doc/FAQ.txt
10
10
  doc/Philosophy.txt
11
11
  doc/TODO
12
+ doc/UserGuide.txt
12
13
  lib/sup.rb
13
14
  lib/sup/account.rb
14
15
  lib/sup/buffer.rb
data/Rakefile CHANGED
@@ -12,7 +12,7 @@ Hoe.new('sup', Redwood::VERSION) do |p|
12
12
  p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[2].gsub(/^\s+/, "")
13
13
  p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
14
14
  p.email = "wmorgan-sup@masanjin.net"
15
- p.extra_deps = [['ferret', '>= 0.10.13'], ['ncurses', '>= 0.9.1'], ['rmail', '>= 0.17'], 'highline']
15
+ p.extra_deps = [['ferret', '>= 0.10.13'], ['ncurses', '>= 0.9.1'], ['rmail', '>= 0.17'], 'highline', 'net-ssh']
16
16
  end
17
17
 
18
18
  rule 'ss?.png' => 'ss?-small.png' do |t|
data/bin/sup CHANGED
@@ -100,9 +100,8 @@ begin
100
100
  Logger.make_buf
101
101
 
102
102
  bm.draw_screen
103
- imode.load_more_threads ibuf.content_height
103
+ imode.load_more_threads :num => ibuf.content_height, :when_done => lambda { reporting_thread { sleep 1; PollManager.poll } }
104
104
 
105
- reporting_thread { sleep 5; PollManager.poll }
106
105
  PollManager.start_thread
107
106
 
108
107
  until $exception
@@ -141,7 +140,7 @@ begin
141
140
  log "built query from #{text.inspect}: #{qobj}"
142
141
  mode = SearchResultsMode.new qobj
143
142
  bm.spawn "search: \"#{short_text}\"", mode
144
- mode.load_more_threads mode.buffer.content_height
143
+ mode.load_more_threads :num => mode.buffer.content_height
145
144
  rescue Ferret::QueryParser::QueryParseException => e
146
145
  bm.flash "Couldn't parse query."
147
146
  end
@@ -168,7 +167,7 @@ begin
168
167
  b = BufferManager.spawn_unless_exists(:draft) do
169
168
  mode = LabelSearchResultsMode.new [:draft]
170
169
  end
171
- b.mode.load_more_threads b.content_height
170
+ b.mode.load_more_threads :num => b.content_height
172
171
  end
173
172
  when :nothing
174
173
  when :redraw
data/bin/sup-import CHANGED
@@ -76,7 +76,7 @@ def get_login_info uri, sources
76
76
 
77
77
  username, password = nil, nil
78
78
  unless accounts.empty?
79
- say "Would you like to use the same account as for a previous source?"
79
+ say "Would you like to use the same account as for a previous source for #{uri}?"
80
80
  choose do |menu|
81
81
  accounts.each do |host, olduser, oldpw|
82
82
  menu.choice("Use the account info for #{olduser}@#{host}") { username, password = olduser, oldpw }
@@ -95,7 +95,6 @@ def get_login_info uri, sources
95
95
  [username, password]
96
96
  end
97
97
 
98
-
99
98
  educate_user if ARGV.member? '--help'
100
99
 
101
100
  archive = ARGV.delete "--archive"
@@ -180,7 +179,7 @@ begin
180
179
  end
181
180
 
182
181
  m.remove_label :unread if m.status == "RO" unless force_read
183
- puts "# message at #{offset}, labels: #{labels * ', '}"
182
+ puts "# message at #{offset}, labels: #{labels * ', '}" unless rebuild
184
183
  if (rebuild || force_rebuild) &&
185
184
  (docid, entry = index.load_entry_for_id(m.id)) && entry
186
185
  if force_rebuild || entry[:source_info].to_i != offset
data/doc/UserGuide.txt ADDED
@@ -0,0 +1,54 @@
1
+ Welcome to Sup! Here's how to actually use it.
2
+
3
+ To get started, just run 'sup'. Assuming this is your first time,
4
+ you'll be confronted with a mostly blank screen, and a little message
5
+ at the bottom telling you that you have no messages. That's because
6
+ Sup doesn't have any messages in its index. In order to add messages,
7
+ Sup needs to have some "sources" from which to load messages.
8
+
9
+ If you want to play around a little at this point, you can press 'b'
10
+ to cycle between buffers and 'x' to kill a buffer. There's probably
11
+ not too much interesting there, but there's a log buffer with some
12
+ cryptic messages. You can also press '?' at any point to get a list of
13
+ keyboard commands, but in the absense of any email, these will be
14
+ pretty useless. When you're done, press 'q' to quit.
15
+
16
+ Now let's add a source to Sup. Run sup-import with a URI pointing to
17
+ an email source. The URI should be of the form:
18
+ - mbox://path/to/a/filename, for an mbox file on disk. (You can also
19
+ just provide the filename).
20
+ - imap://imap.server/folder or imaps://secure.imap.server/folder for
21
+ an IMAP server.
22
+ - mbox+ssh://remote.machine/path/to/a/filename for a remote mbox file.
23
+
24
+ Note: sup-import tries to be smart about setting the labels on
25
+ messages that it adds (including the special labels "unread" and
26
+ "inbox"). There are options that control this behavior, and it's
27
+ worth taking a look at the output of sup-import --help to make
28
+ sure that you don't end up with 1000 new messages that you've
29
+ actually already read.
30
+
31
+ If sup-import requires a username and password for the source, it will
32
+ prompt you for one. Either way, it will start loading messages from
33
+ that source into the index. Depending on the size of the source, this
34
+ may take some time. Don't worry! This is a one-time step, and all the
35
+ computation done now makes operating on the index faster.
36
+
37
+ Now, before we run 'sup' again, take a moment to edit your
38
+ ~/.sup/config.yaml file. Replace "Your Name Here" with your name,
39
+ "your.email.here@domain.tld" with your email address, and fill in your
40
+ .signature file if you choose. You can also set the default editor.
41
+
42
+ Now run 'sup'. You should messages being loaded into your "inbox!"
43
+ There are two things that are worth understanding at this point.
44
+ First, Sup does not actually use folders. Instead, messages can have
45
+ any number of labels applied to them. Rather than viewing a folder,
46
+ you view the results of a search. So your inbox is simply the set of
47
+ messages that have the 'inbox' label applied to them.
48
+
49
+ Second, Sup groups together messages into threads. You rarely operate
50
+ on an individual message in Sup.
51
+
52
+ Press enter to view a thread.
53
+
54
+ To be continued...
data/lib/sup.rb CHANGED
@@ -13,7 +13,7 @@ class Object
13
13
  end
14
14
 
15
15
  module Redwood
16
- VERSION = "0.0.3"
16
+ VERSION = "0.0.4"
17
17
 
18
18
  BASE_DIR = ENV["SUP_BASE"] || File.join(ENV["HOME"], ".sup")
19
19
  CONFIG_FN = File.join(BASE_DIR, "config.yaml")
data/lib/sup/buffer.rb CHANGED
@@ -136,6 +136,7 @@ class BufferManager
136
136
  @focus_buf = nil
137
137
  @dirty = true
138
138
  @minibuf_stack = []
139
+ @minibuf_mutex = Mutex.new
139
140
  @textfields = {}
140
141
  @flash = nil
141
142
  @shelled = false
@@ -366,12 +367,19 @@ class BufferManager
366
367
  end
367
368
  end
368
369
 
369
- def minibuf_lines; [(@flash ? 1 : 0) + @minibuf_stack.compact.size, 1].max; end
370
+ def minibuf_lines
371
+ @minibuf_mutex.synchronize do
372
+ [(@flash ? 1 : 0) + @minibuf_stack.compact.size, 1].max
373
+ end
374
+ end
370
375
 
371
376
  def draw_minibuf opts={}
372
- m = @minibuf_stack.compact
373
- m << @flash if @flash
374
- m << "" if m.empty?
377
+ m = nil
378
+ @minibuf_mutex.synchronize do
379
+ m = @minibuf_stack.compact
380
+ m << @flash if @flash
381
+ m << "" if m.empty?
382
+ end
375
383
 
376
384
  Ncurses.mutex.lock unless opts[:sync] == false
377
385
  Ncurses.attrset Colormap.color_for(:none)
@@ -383,9 +391,13 @@ class BufferManager
383
391
  end
384
392
 
385
393
  def say s, id=nil
386
- new_id = id.nil?
387
- id ||= @minibuf_stack.length
388
- @minibuf_stack[id] = s
394
+ new_id = nil
395
+ @minibuf_mutex.synchronize do
396
+ new_id = id.nil?
397
+ id ||= @minibuf_stack.length
398
+ @minibuf_stack[id] = s
399
+ end
400
+
389
401
  if new_id
390
402
  draw_screen :refresh => true
391
403
  else
@@ -412,11 +424,13 @@ class BufferManager
412
424
  ## a little tricky because we can't just delete_at id because ids
413
425
  ## are relative (they're positions into the array).
414
426
  def clear id
415
- @minibuf_stack[id] = nil
416
- if id == @minibuf_stack.length - 1
417
- id.downto(0) do |i|
418
- break if @minibuf_stack[i]
419
- @minibuf_stack.delete_at i
427
+ @minibuf_mutex.synchronize do
428
+ @minibuf_stack[id] = nil
429
+ if id == @minibuf_stack.length - 1
430
+ id.downto(0) do |i|
431
+ break if @minibuf_stack[i]
432
+ @minibuf_stack.delete_at i
433
+ end
420
434
  end
421
435
  end
422
436
 
data/lib/sup/imap.rb CHANGED
@@ -48,7 +48,7 @@ class IMAP < Source
48
48
  @ids = []
49
49
  @labels = [:unread]
50
50
  @labels << :inbox unless archived?
51
- @labels << mailbox.intern unless mailbox =~ /inbox/i || mailbox.nil?
51
+ @labels << mailbox.intern unless mailbox =~ /inbox/i
52
52
  @mutex = Mutex.new
53
53
  end
54
54
 
@@ -122,7 +122,10 @@ class IMAP < Source
122
122
 
123
123
  def host; @parsed_uri.host; end
124
124
  def port; @parsed_uri.port || (ssl? ? 993 : 143); end
125
- def mailbox; @parsed_uri.path[1..-1] || 'INBOX'; end
125
+ def mailbox
126
+ x = @parsed_uri.path[1..-1]
127
+ x.nil? || x.empty? ? 'INBOX' : x
128
+ end
126
129
  def ssl?; @parsed_uri.scheme == 'imaps' end
127
130
 
128
131
  def load_header id
@@ -149,6 +152,7 @@ class IMAP < Source
149
152
  end
150
153
 
151
154
  def get_imap_field id, field
155
+ retries = 0
152
156
  f = nil
153
157
  imap_id = @imap_ids[id] or raise SourceError, "Unknown message id #{id}. It is likely that messages have been deleted from this IMAP mailbox."
154
158
  begin
@@ -157,6 +161,12 @@ class IMAP < Source
157
161
  raise SourceError, "IMAP message mismatch: requested #{id}, got #{got_id}. It is likely the IMAP mailbox has been modified." unless got_id == id
158
162
  rescue Net::IMAP::Error => e
159
163
  raise SourceError, e.message
164
+ rescue Errno::EPIPE
165
+ if (retries += 1) <= 3
166
+ @imap = nil
167
+ connect
168
+ retry
169
+ end
160
170
  end
161
171
  raise SourceError, "null IMAP field '#{field}' for message with id #{id} imap id #{imap_id}" if f.nil?
162
172
 
@@ -37,6 +37,7 @@ class Loader < Source
37
37
  @f.seek offset
38
38
  l = @f.gets
39
39
  unless l =~ BREAK_RE
40
+ Redwood::log "#{to_s}: offset mismatch in mbox file offset #{offset.inspect}: #{l.inspect}"
40
41
  self.broken_msg = "offset mismatch in mbox file offset #{offset.inspect}: #{l.inspect}. Run 'sup-import --rebuild #{to_s}' to correct this."
41
42
  raise SourceError, self.broken_msg
42
43
  end
@@ -75,6 +75,15 @@ class Buffer
75
75
  def to_s; empty? ? "<empty>" : "[#{start}, #{endd})"; end # for debugging
76
76
  end
77
77
 
78
+ ## sharing a ssh connection to one machines between sources seems to
79
+ ## create lots of broken situations: commands returning bizarre (large
80
+ ## positive integer) return codes despite working; commands
81
+ ## occasionally not working, etc. i suspect this is because of the
82
+ ## fragile nature of the ssh syncshell.
83
+ ##
84
+ ## at any rate, we now open up one ssh connection per file, which is
85
+ ## probably silly in the extreme case.
86
+
78
87
  ## the file-like interface to a remote file
79
88
  class SSHFile
80
89
  MAX_BUF_SIZE = 1024 * 1024 # bytes
@@ -82,9 +91,6 @@ class SSHFile
82
91
  REASONABLE_TRANSFER_SIZE = 1024 * 32
83
92
  SIZE_CHECK_INTERVAL = 60 * 1 # seconds
84
93
 
85
- @@shells = {}
86
- @@shells_mutex = Mutex.new
87
-
88
94
  def initialize host, fn, ssh_opts={}
89
95
  @buf = Buffer.new
90
96
  @host = host
@@ -94,8 +100,11 @@ class SSHFile
94
100
  @offset = 0
95
101
  @say_id = nil
96
102
  @broken_msg = nil
103
+ @shell = nil
104
+ @shell_mutex = Mutex.new
97
105
  end
98
106
 
107
+ def to_s; "mbox+ssh://#@host/#@fn"; end ## TODO: remove thisis EVILness
99
108
  def broken?; !@broken_msg.nil?; end
100
109
 
101
110
  ## TODO: share this code with imap
@@ -111,33 +120,20 @@ class SSHFile
111
120
 
112
121
  def connect
113
122
  raise SSHFileError, @broken_msg if broken?
114
- return if @shell
115
-
116
- key = [@host, @ssh_opts[:username]]
117
- @shell =
118
- @@shells_mutex.synchronize do
119
- if @@shells.member? key
120
- returning(@@shells[key]) do |shell|
121
- say "Checking for #@fn..."
122
- begin
123
- raise Errno::ENOENT, @fn unless shell.test("-e #@fn").status == 0
124
- ensure
125
- shutup
126
- end
127
- end
128
- else
123
+
124
+ @shell_mutex.synchronize do
125
+ return if @shell
126
+
127
+ begin
129
128
  say "Opening SSH connection to #{@host}..."
130
- begin
131
- #raise SSHFileError, "simulated SSH file error"
132
- session = Net::SSH.start @host, @ssh_opts
133
- say "Starting SSH shell..."
134
- shell = session.shell.sync
135
- say "Checking for #@fn..."
136
- raise Errno::ENOENT, @fn unless shell.test("-e #@fn").status == 0
137
- @@shells[key] = shell
138
- ensure
139
- shutup
140
- end
129
+ #raise SSHFileError, "simulated SSH file error"
130
+ session = Net::SSH.start @host, @ssh_opts
131
+ say "Starting SSH shell..."
132
+ @shell = session.shell.sync
133
+ say "Checking for #@fn..."
134
+ raise Errno::ENOENT, @fn unless @shell.test("-e #@fn").status == 0
135
+ ensure
136
+ shutup
141
137
  end
142
138
  end
143
139
  end
@@ -178,10 +174,16 @@ private
178
174
  # MBox::debug "sending command: #{cmd.inspect}"
179
175
  begin
180
176
  result = @shell.send_command cmd
181
- raise SSHFileError, "Failure during remote command #{cmd.inspect}: #{result.stderr[0 .. 100]}" unless result.status == 0
177
+ raise SSHFileError, "Failure during remote command #{cmd.inspect}: #{(result.stderr || result.stdout || "")[0 .. 100]}" unless result.status == 0
182
178
  rescue Net::SSH::Exception # these happen occasionally for no apparent reason. gotta love that nondeterminism!
183
- retry if (retries += 1) < 3
179
+ retry if (retries += 1) <= 3
184
180
  raise
181
+ rescue Errno::EPIPE
182
+ if (retries += 1) <= e
183
+ @shell = nil
184
+ connect
185
+ retry
186
+ end
185
187
  end
186
188
  rescue Net::SSH::Exception, SSHFileError, Errno::ENOENT => e
187
189
  @broken_msg = e.message
@@ -60,7 +60,7 @@ class ContactListMode < LineCursorMode
60
60
  def multi_search people
61
61
  mode = PersonSearchResultsMode.new people
62
62
  BufferManager.spawn "personal search results", mode
63
- mode.load_more_threads mode.buffer.content_height
63
+ mode.load_more_threads :num => mode.buffer.content_height
64
64
  end
65
65
 
66
66
  def search
@@ -28,19 +28,21 @@ class InboxMode < ThreadIndexMode
28
28
 
29
29
  def is_relevant? m; m.has_label? :inbox; end
30
30
 
31
- def load_more_threads n=ThreadIndexMode::LOAD_MORE_THREAD_NUM
31
+ def load_more_threads opts={}
32
+ n = opts[:num] || ThreadIndexMode::LOAD_MORE_THREAD_NUM
32
33
  load_n_threads_background n, :label => :inbox,
33
34
  :load_killed => false,
34
35
  :load_spam => false,
35
- :when_done => lambda { |num|
36
+ :when_done => (lambda do |num|
37
+ opts[:when_done].call if opts[:when_done]
36
38
  BufferManager.flash "Added #{num} threads."
37
- }
39
+ end)
38
40
  end
39
41
 
40
42
  def reload
41
43
  drop_all_threads
42
44
  BufferManager.draw_screen
43
- load_more_threads buffer.content_height
45
+ load_more_threads :num => buffer.content_height
44
46
  end
45
47
  end
46
48
 
@@ -81,7 +81,7 @@ protected
81
81
  b = BufferManager.spawn_unless_exists(label) do
82
82
  mode = LabelSearchResultsMode.new [label]
83
83
  end
84
- b.mode.load_more_threads b.content_height
84
+ b.mode.load_more_threads :num => b.content_height
85
85
  end
86
86
  end
87
87
  end
@@ -12,11 +12,13 @@ class LabelSearchResultsMode < ThreadIndexMode
12
12
 
13
13
  def is_relevant? m; @labels.all? { |l| m.has_label? l }; end
14
14
 
15
- def load_more_threads n=ThreadIndexMode::LOAD_MORE_THREAD_NUM
15
+ def load_more_threads opts={}
16
+ n = opts[:num] || ThreadIndexMode::LOAD_MORE_THREAD_NUM
16
17
  load_n_threads_background n, :labels => @labels,
17
18
  :load_killed => true,
18
19
  :load_spam => false,
19
20
  :when_done =>(lambda do |num|
21
+ opts[:when_done].call if opts[:when_done]
20
22
  if num > 0
21
23
  BufferManager.flash "Found #{num} threads"
22
24
  else
@@ -12,11 +12,13 @@ class PersonSearchResultsMode < ThreadIndexMode
12
12
 
13
13
  def is_relevant? m; @people.any? { |p| m.from == p }; end
14
14
 
15
- def load_more_threads n=ThreadIndexMode::LOAD_MORE_THREAD_NUM
15
+ def load_more_threads opts={}
16
+ n = opts[:num] || ThreadIndexMode::LOAD_MORE_THREAD_NUM
16
17
  load_n_threads_background n, :participants => @people,
17
18
  :load_killed => true,
18
19
  :load_spam => false,
19
20
  :when_done =>(lambda do |num|
21
+ opts[:when_done].call if opts[:when_done]
20
22
  if num > 0
21
23
  BufferManager.flash "Found #{num} threads"
22
24
  else
@@ -13,11 +13,13 @@ class SearchResultsMode < ThreadIndexMode
13
13
  ## TODO: think about this
14
14
  def is_relevant? m; super; end
15
15
 
16
- def load_more_threads n=ThreadIndexMode::LOAD_MORE_THREAD_NUM
16
+ def load_more_threads opts={}
17
+ n = opts[:num] || ThreadIndexMode::LOAD_MORE_THREAD_NUM
17
18
  load_n_threads_background n, :qobj => @qobj,
18
19
  :load_killed => true,
19
20
  :load_spam => false,
20
21
  :when_done =>(lambda do |num|
22
+ opts[:when_done].call if opts[:when_done]
21
23
  if num > 0
22
24
  BufferManager.flash "Found #{num} threads"
23
25
  else
@@ -72,6 +72,7 @@ class ThreadIndexMode < LineCursorMode
72
72
  def handle_add_update m
73
73
  if is_relevant?(m) || @ts.is_relevant?(m)
74
74
  @ts.load_thread_for_message m
75
+ @new_cache.delete @ts.thread_for(m) # force recalculation of newness
75
76
  update
76
77
  end
77
78
  end
data/lib/sup/source.rb CHANGED
@@ -38,7 +38,7 @@ class Source
38
38
  end
39
39
 
40
40
  def broken?; !@broken_msg.nil?; end
41
- def to_s; @uri; end
41
+ def to_s; @uri.to_s; end
42
42
  def seek_to! o; self.cur_offset = o; end
43
43
  def reset!
44
44
  return if broken?
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.11
3
3
  specification_version: 1
4
4
  name: sup
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.0.3
7
- date: 2007-01-02 00:00:00 -08:00
6
+ version: 0.0.4
7
+ date: 2007-01-03 00:00:00 -08:00
8
8
  summary: A console-based email client with the best features of GMail, mutt, and emacs. Features full text search, labels, tagged operations, multiple buffers, recent contacts, and more.
9
9
  require_paths:
10
10
  - lib
@@ -39,6 +39,7 @@ files:
39
39
  - doc/FAQ.txt
40
40
  - doc/Philosophy.txt
41
41
  - doc/TODO
42
+ - doc/UserGuide.txt
42
43
  - lib/sup.rb
43
44
  - lib/sup/account.rb
44
45
  - lib/sup/buffer.rb
@@ -136,3 +137,12 @@ dependencies:
136
137
  - !ruby/object:Gem::Version
137
138
  version: 0.0.0
138
139
  version:
140
+ - !ruby/object:Gem::Dependency
141
+ name: net-ssh
142
+ version_requirement:
143
+ version_requirements: !ruby/object:Gem::Version::Requirement
144
+ requirements:
145
+ - - ">"
146
+ - !ruby/object:Gem::Version
147
+ version: 0.0.0
148
+ version: