sup 0.0.4 → 0.0.5

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,8 @@
1
+ == 0.0.5 / 2007-01-05
2
+
3
+ * More bugfixes, primarily for IMAP.
4
+ * doc/UserGuide.txt
5
+
1
6
  == 0.0.4 / 2007-01-03
2
7
 
3
8
  * Bugfixes, primarily for threaded networking.
data/Rakefile CHANGED
@@ -16,12 +16,10 @@ Hoe.new('sup', Redwood::VERSION) do |p|
16
16
  end
17
17
 
18
18
  rule 'ss?.png' => 'ss?-small.png' do |t|
19
-
20
19
  end
21
20
 
22
-
23
21
  ## is there really no way to make a rule for this?
24
- WWW_FILES = %w(www/index.html README.txt doc/Philosophy.txt doc/FAQ.txt)
22
+ WWW_FILES = %w(www/index.html README.txt doc/Philosophy.txt doc/FAQ.txt doc/UserGuide.txt)
25
23
 
26
24
  SCREENSHOTS = FileList["www/ss?.png"]
27
25
  SCREENSHOTS_SMALL = []
data/bin/sup CHANGED
@@ -100,7 +100,7 @@ begin
100
100
  Logger.make_buf
101
101
 
102
102
  bm.draw_screen
103
- imode.load_more_threads :num => ibuf.content_height, :when_done => lambda { reporting_thread { sleep 1; PollManager.poll } }
103
+ imode.load_threads :num => ibuf.content_height, :when_done => lambda { reporting_thread { sleep 1; PollManager.poll } }
104
104
 
105
105
  PollManager.start_thread
106
106
 
@@ -129,7 +129,8 @@ begin
129
129
  when :list_buffers
130
130
  bm.spawn_unless_exists("Buffer List") { BufferListMode.new }
131
131
  when :list_contacts
132
- bm.spawn_unless_exists("Contact List") { ContactListMode.new }
132
+ b = bm.spawn_unless_exists("Contact List") { ContactListMode.new }
133
+ b.mode.load_more b.content_height
133
134
  when :search
134
135
  text = bm.ask :search, "query: "
135
136
  next unless text && text !~ /^\s*$/
@@ -140,7 +141,7 @@ begin
140
141
  log "built query from #{text.inspect}: #{qobj}"
141
142
  mode = SearchResultsMode.new qobj
142
143
  bm.spawn "search: \"#{short_text}\"", mode
143
- mode.load_more_threads :num => mode.buffer.content_height
144
+ mode.load_threads :num => mode.buffer.content_height
144
145
  rescue Ferret::QueryParser::QueryParseException => e
145
146
  bm.flash "Couldn't parse query."
146
147
  end
@@ -162,12 +163,14 @@ begin
162
163
  when 1
163
164
  m = nil
164
165
  Index.each_id_by_date(:label => :draft) { |mid, builder| m = builder.call }
165
- BufferManager.spawn "Edit message", ResumeMode.new(m)
166
+ r = ResumeMode.new(m)
167
+ BufferManager.spawn "Edit message", r
168
+ r.edit
166
169
  else
167
170
  b = BufferManager.spawn_unless_exists(:draft) do
168
171
  mode = LabelSearchResultsMode.new [:draft]
169
172
  end
170
- b.mode.load_more_threads :num => b.content_height
173
+ b.mode.load_threads :num => b.content_height
171
174
  end
172
175
  when :nothing
173
176
  when :redraw
data/bin/sup-import CHANGED
@@ -49,6 +49,7 @@ the following options:
49
49
  --force-read: any messages found will not be marked as new.
50
50
 
51
51
  The following options can also be specified:
52
+ --verbose: print message ids as they're processed
52
53
  --the-usual: import new messages from all usual sources
53
54
  --rebuild: rebuild the index for the specified sources rather than
54
55
  just adding new messages. Useful if the sources
@@ -62,6 +63,7 @@ The following options can also be specified:
62
63
  EOS
63
64
  exit
64
65
  end
66
+ #' stupid ruby-mode
65
67
 
66
68
  ## for sources that require login information, prompt the user for
67
69
  ## that. also provide a list of previously-defined login info to
@@ -105,6 +107,7 @@ the_usual = ARGV.delete "--the-usual"
105
107
  rebuild = ARGV.delete "--rebuild"
106
108
  force_rebuild = ARGV.delete "--force-rebuild"
107
109
  optimize = ARGV.delete "--optimize"
110
+ verbose = ARGV.delete "--verbose"
108
111
  start_at = # ok really need to use optparse or something now
109
112
  if(i = ARGV.index("--start-at"))
110
113
  raise "start-at requires a numeric argument: #{ARGV[i + 1].inspect}" unless ARGV.length > (i + 1) && ARGV[i + 1] =~ /\d/
@@ -167,7 +170,7 @@ begin
167
170
  start_offset = nil
168
171
  source.each do |offset, labels|
169
172
  start_offset ||= offset
170
- labels -= [:inbox] if force_archive
173
+ labels -= [:inbox] if force_archive || archive
171
174
  labels -= [:unread] if force_read
172
175
  begin
173
176
  m = Redwood::Message.new :source => source, :source_info => offset, :labels => labels
@@ -178,8 +181,9 @@ begin
178
181
  found[m.id] = true
179
182
  end
180
183
 
181
- m.remove_label :unread if m.status == "RO" unless force_read
182
- puts "# message at #{offset}, labels: #{labels * ', '}" unless rebuild
184
+ m.remove_label :unread if m.source_marked_read? unless force_read
185
+ puts "# message at #{offset}, labels: #{labels * ', '}" if verbose unless rebuild
186
+ labels.each { |l| Redwood::LabelManager << l }
183
187
  if (rebuild || force_rebuild) &&
184
188
  (docid, entry = index.load_entry_for_id(m.id)) && entry
185
189
  if force_rebuild || entry[:source_info].to_i != offset
@@ -195,9 +199,9 @@ begin
195
199
  end
196
200
  if num % 1000 == 0 && num > 0
197
201
  elapsed = Time.now - start
198
- pctdone = (offset.to_f - start_offset) / (source.total.to_f - start_offset)
199
- remaining = (source.total.to_f - offset.to_f) * (elapsed.to_f / (offset.to_f - start_offset))
200
- puts "## #{num} (#{(pctdone * 100.0)}% done) read; #{elapsed.to_time_s} elapsed; est. #{remaining.to_time_s} remaining"
202
+ pctdone = source.pct_done
203
+ remaining = (100.0 - pctdone) * (elapsed.to_f / pctdone)
204
+ puts "## #{num} (#{pctdone}% done) read; #{elapsed.to_time_s} elapsed; est. #{remaining.to_time_s} remaining"
201
205
  end
202
206
  end
203
207
  puts "loaded #{num} messages" unless num == 0
data/doc/TODO CHANGED
@@ -1,3 +1,5 @@
1
+ batch deletion
2
+ on startup, multi-threadedly call #connect on all sources
1
3
  support for message-content modules such as ruby-talk:XXXXX detection
2
4
  use Net::SMTP
3
5
  search for other messages from author in thread-view-mode
@@ -13,9 +15,11 @@ editing of arbitrary messages
13
15
  annotations on messages
14
16
  gmail
15
17
  pop
16
- move sup-import argument handling to getopt or something
17
- mark individual messages as spam in thread-view-mode
18
+ move sup-import argument handling to getopt
19
+ be able to mark individual messages as spam in thread-view-mode
18
20
 
21
+ x fix snippet repetitions with small snippets
22
+ x fix next and previous in thread-view-mode with <unreceived messages>
19
23
  x move sup-import username/password prompts to highline
20
24
  x support different remote servers per user account
21
25
  x 'R' to quick-resume most recent draft
data/doc/UserGuide.txt CHANGED
@@ -1,54 +1,212 @@
1
1
  Welcome to Sup! Here's how to actually use it.
2
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.
3
+ First, try running 'sup'. Assuming this is your first time, you'll be
4
+ confronted with a mostly blank screen, and a notice at the bottom that
5
+ you have no new messages. That's because Sup doesn't have any messages
6
+ in its index yet, and has no idea where to look for them anyways.
8
7
 
9
8
  If you want to play around a little at this point, you can press 'b'
10
9
  to cycle between buffers and 'x' to kill a buffer. There's probably
11
10
  not too much interesting there, but there's a log buffer with some
12
11
  cryptic messages. You can also press '?' at any point to get a list of
13
12
  keyboard commands, but in the absense of any email, these will be
14
- pretty useless. When you're done, press 'q' to quit.
13
+ mostly useless. When you're bored, press 'q' to quit.
15
14
 
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:
15
+ We need to add some messages to Sup. In order for Sup to know about a
16
+ message, it must be in the index. In order for Sup to load a message
17
+ into the index, it must know about the "source" in which it
18
+ resides. Sup stores all information necessary for search, and all
19
+ state we have about a message, in the index, but doesn't duplicate the
20
+ actual contents of the message itself. So when you search for messages
21
+ or view your inbox, Sup just talks to the index (stored locally on
22
+ disk), but when you view a message, Sup must request it from the
23
+ source.
24
+
25
+ So let's tell Sup about some sources. Run 'sup-import' with a URI
26
+ pointing to an email source. The URI should be of the form:
18
27
  - mbox://path/to/a/filename, for an mbox file on disk. (You can also
19
28
  just provide the filename).
20
29
  - imap://imap.server/folder or imaps://secure.imap.server/folder for
21
- an IMAP server.
30
+ an IMAP folder. (Leave the folder blank for INBOX.)
22
31
  - mbox+ssh://remote.machine/path/to/a/filename for a remote mbox file.
23
32
 
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.
33
+ Before you actually import messages, you need to decide whether you
34
+ want them to be archived or not. An archived message will not show up
35
+ in your inbox. (Your inbox in Sup is, by definition, the set of all
36
+ all non-archived messages). If you specify --force-archive, messages
37
+ imported at *this* time will be archived, but new messages will go to
38
+ your inbox. If you specify --archive, messages imported at *any* time
39
+ will be automatically archived. (This is useful for sources that
40
+ contain high-traffic mailing lists that you don't want "polluting"
41
+ your inbox.)
42
+
43
+ Typically you'll want to specify --archive for every source you import
44
+ except your actual inbox. You can also force all messages to be marked
45
+ as read; run sup-import --help for details. The default is to preserve
46
+ the read/unread status from the source.
30
47
 
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.
48
+ Now run sup-import to add the source. If it requires a username and
49
+ password for the source, it will prompt you. Either way, it will add
50
+ the sources to Sup, and immediately start loading messages from them
51
+ into the index. Depending on the size of the source, this may take a
52
+ while. Don't panic! It's a one-time process.
36
53
 
37
54
  Now, before we run 'sup' again, take a moment to edit your
38
55
  ~/.sup/config.yaml file. Replace "Your Name Here" with your name,
39
56
  "your.email.here@domain.tld" with your email address, and fill in your
40
57
  .signature file if you choose. You can also set the default editor.
41
58
 
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.
59
+ We're finally ready to run 'sup'. You should see the most recent
60
+ loaded messages appear in your inbox. Congratulations, you've got Sup
61
+ working!
62
+
63
+ If you're coming from the world of traditional email, there are a
64
+ couple differences you should be aware of at this point. First, Sup
65
+ does not use folders. Instead, you organize and find messages by a
66
+ combination of search and labels (knowns as 'tags' everywhere else in
67
+ the world). I like to say that 95% of the functionality of folders is
68
+ superceded by a quick, easy-to-access, and powerful search mechanism,
69
+ and the other 5% by labels. (The Sup statement of philosophy has a
70
+ little more on this.)
71
+
72
+ Search and labels are an integral part of Sup because in Sup, rather
73
+ than viewing the contents of a folder, you view the results of a
74
+ search. I mentioned above that your inbox is, by definition, the set
75
+ of all messages that aren't archived. This means that your inbox is,
76
+ in fact, the result of the search for "all messages without the label
77
+ 'archive'". It's actually slightly more complicated---we omit killed,
78
+ deleted and spam messages as well.
79
+
80
+ So you could replicate the folder paradigm easily under this scheme,
81
+ by giving each message exactly one label and only viewing the results
82
+ of simple searches for those labels. But you'd quickly find out that
83
+ life can be easier than that if you just trust the search engine, and
84
+ use labels judiciously for things that are too hard to find with
85
+ search.
86
+
87
+ But enough chit-chat! Let's take a look at your inbox. You'll see that
88
+ Sup groups messages together into threads: each line in the inbox is a
89
+ thread, and the number in parentheses is the number of messages in
90
+ that thread. (If there's no number in parens, it means there's just
91
+ one message.) In Sup, you rarely operate on an individual message. The
92
+ idea is that you rarely want to operate on a message independent of
93
+ its context; you typically want to view, archive, kill, or label all
94
+ the messages in a thread at one time.
95
+
96
+ Use the up and down arrows to highlight a thread. ('j' and 'k' do the
97
+ same thing, and 'J' and 'K' will scroll the whole window. Even the
98
+ left and right arrow keys work!) By default, Sup only loads as many
99
+ threads as it takes to fill the window; if you'd like to load more,
100
+ press 'M'. You can hit tab to cycle between only threads with new
101
+ messages.
102
+
103
+ Highlight a thread and press enter to view it. You'll notice that all
104
+ messages in the thread are displayed together, layed out graphically
105
+ by their relationship to each other (replies are nested under
106
+ parents). By default, only the new messages in a thread are expanded,
107
+ and the others are hidden. You can toggle an individual message's
108
+ state by highlighting a green line and pressing enter. You can use 'E'
109
+ to expand or collapse all messages or 'N' to expand only the new
110
+ messages. You'll also notice that Sup hides quoted text and
111
+ signatures. If you highlight a particular hidden chunk, you can press
112
+ enter to expand it, or you can press 'o' to toggle every hidden chunk
113
+ in a particular message. (Don't worry about remembering all these
114
+ things---you can hit '?' to see the full list of keyboard commands at
115
+ any point.)
116
+
117
+ A few other useful commands while viewing a thread. Press 'd' to
118
+ toggle a detailed header fpr a message. If you've scrolled too far to
119
+ the right, press '[' to jump all the way to the left. Finally, you can
120
+ press 'n' and 'p' to jump forward and backward between open
121
+ messages. (I find that very useful!)
122
+
123
+ Now press 'x' to kill the thread view buffer. You should see the inbox
124
+ again. If you don't, you can cycle through the buffers by pressing
125
+ 'b', or you can press 'A' to see a list of all buffers and simply
126
+ select the inbox.
127
+
128
+ There are many operations you can perform on threads beyond viewing
129
+ them. To archive a thread, press 'a'. The thread will disappear from
130
+ your inbox, but will still appear in search results. If someone
131
+ replies an archived thread, it will reappear in your inbox. To kill a
132
+ thread, press '&'. Killed threads will never come back to your inbox,
133
+ even if people reply, but will still be searchable. (This is useful
134
+ for those interminable threads that you really have no interest in,
135
+ but which seem to pop up on every mailing list.)
136
+
137
+ If a thread is spam, press 'S'. It will disappear and won't come
138
+ back. It won't even appear in search results (unless you explicitly
139
+ search for spam.)
140
+
141
+ You can also star a thread by pressing '*'. Starred threads are
142
+ displayed with a little yellow asterix next to them, but otherwise
143
+ have no special semantics. (But you can also search for them
144
+ easily---we'll see how in a moment).
145
+
146
+ To edit the labels for (all the messages in) a thread, press 'l'. Type
147
+ in the labels as a sequence of space-separated words. To cancel the
148
+ input, press Ctrl-G.
149
+
150
+ Many of these operations can be applied to a group of threads. Press
151
+ 't' to tag a thread. Tag a couple, then press ';' to apply the next
152
+ command to the set of threads. ';t', of course, will untag all tagged
153
+ messages.
154
+
155
+ Ok, let's try using labels and search. Press 'L' to bring up a list of
156
+ all the labels you've ever used, along with some special labels
157
+ (Draft, Starred, Sent, Spam, etc.). Highlight a label and press enter
158
+ to view all the messages with that label.
159
+
160
+ What you just did was actually a specific search. For a general
161
+ search, press "/". Now type in your query (again, Ctrl-G to cancel at
162
+ any point.) You can just type in arbitrary text, which will be matched
163
+ on a per-word basis against the bodies of all email in the index, or
164
+ you can make use of the full Ferret query syntax:
165
+
166
+ - Phrasal queries using double-quotes, e.g.: "three contiguous words"
167
+ - Queries against a particular field using <field name>:<query>,
168
+ e.g.: label:ruby-talk, or from:matz@ruby-lang.org. (Fields include:
169
+ body, from, to, and subject.)
170
+ - Force occurrence and non-occurrence using + and -, e.g. +label:spam,
171
+ or -body:"hot soup"
172
+
173
+ You can combine those all together. For example:
174
+ +label:ruby-talk +subject:\[ANN\] -rails
175
+
176
+ Play around with the search, and see the Ferret documentation for
177
+ details on more sophisticated queries (date ranges, "within n words",
178
+ etc.)
179
+
180
+ At this point, you're well on your way to figuring out all the cool
181
+ things Sup can do. By repeated applications of the '?' key, see if you
182
+ can figure out how to:
183
+ - List some recent contacts
184
+ - Easily search for all mail from a recent contact
185
+ - Easily search for all mail from several recent contacts!
186
+ - Add someone to your address book
187
+ - Postpone a message (i.e., save a draft)
188
+ - Quickly re-edit a just-saved draft message
189
+ - View the raw header of a message
190
+ - Star an individual message, not just a thread
191
+
192
+ There's one last thing to be aware of when using Sup: how it interacts
193
+ with other email programs. Sup stores data about messages in the
194
+ index---information necessary for searching, and message state---but
195
+ doesn't duplicate the message contents themselves. The messages remain
196
+ on the source. If the index and the source every fall out of sync,
197
+ e.g. due to another email client modifying the source, then Sup will
198
+ be unable to operate on that source.
48
199
 
49
- Second, Sup groups together messages into threads. You rarely operate
50
- on an individual message in Sup.
200
+ For example, for mbox files, Sup stores a byte offset into the file
201
+ for each message. If a message deleted from that file by another
202
+ client, or even marked as read (yeah, mbox sucks), all succeeding
203
+ offsets will be wrong.
51
204
 
52
- Press enter to view a thread.
205
+ That's the bad news. The good news is that Sup is fairly good at being
206
+ able to detect this type of situation, and fixing it is just a matter
207
+ of running sup-import --rebuild on the source. Sup will even tell you
208
+ how to invoke sup-import when it detects a problem. This is a
209
+ complication you will almost certainly run in to if you use both Sup
210
+ and another MUA on the same source, so it's good to be aware of it.
53
211
 
54
- To be continued...
212
+ Have fun, and let me know if you have any problems. --William
data/lib/sup.rb CHANGED
@@ -13,7 +13,7 @@ class Object
13
13
  end
14
14
 
15
15
  module Redwood
16
- VERSION = "0.0.4"
16
+ VERSION = "0.0.5"
17
17
 
18
18
  BASE_DIR = ENV["SUP_BASE"] || File.join(ENV["HOME"], ".sup")
19
19
  CONFIG_FN = File.join(BASE_DIR, "config.yaml")
@@ -105,7 +105,7 @@ else
105
105
  :email => "your.email.here@domain.tld",
106
106
  :alternates => [],
107
107
  :sendmail => "/usr/sbin/sendmail -oem -ti",
108
- :sig_file => File.join(ENV["HOME"], ".signature")
108
+ :signature => File.join(ENV["HOME"], ".signature")
109
109
  }
110
110
  },
111
111
  :editor => ENV["EDITOR"] || "/usr/bin/vi",
data/lib/sup/buffer.rb CHANGED
@@ -217,7 +217,6 @@ class BufferManager
217
217
  if true
218
218
  buf = @buffers.last
219
219
  buf.resize Ncurses.rows - minibuf_lines, Ncurses.cols
220
- File.open("asdf.txt", "a") { |f| f.puts "dirty #@dirty, (re)drawing #{buf.mode.name}" }
221
220
  @dirty ? buf.draw : buf.redraw
222
221
  end
223
222
 
@@ -233,7 +232,6 @@ class BufferManager
233
232
  ## the mode is expensive, as it often is.
234
233
  def spawn_unless_exists title, opts={}
235
234
  if @name_map.member? title
236
- Redwood::log "buffer '#{title}' already exists, raising to front"
237
235
  raise_to_front @name_map[title] unless opts[:hidden]
238
236
  else
239
237
  mode = yield
@@ -292,7 +290,10 @@ class BufferManager
292
290
  end
293
291
  end
294
292
 
293
+ ## not really thread safe.
295
294
  def ask domain, question, default=nil
295
+ raise "impossible!" if @asking
296
+
296
297
  @textfields[domain] ||= TextField.new Ncurses.stdscr, Ncurses.rows - 1, 0, Ncurses.cols
297
298
  tf = @textfields[domain]
298
299
 
@@ -310,7 +311,10 @@ class BufferManager
310
311
  ret = nil
311
312
  tf.position_cursor
312
313
  Ncurses.sync { Ncurses.refresh }
314
+
315
+ @asking = true
313
316
  while tf.handle_input(Ncurses.nonblocking_getch); end
317
+ @asking = false
314
318
 
315
319
  ret = tf.value
316
320
  Ncurses.sync { tf.deactivate }
@@ -369,7 +373,9 @@ class BufferManager
369
373
 
370
374
  def minibuf_lines
371
375
  @minibuf_mutex.synchronize do
372
- [(@flash ? 1 : 0) + @minibuf_stack.compact.size, 1].max
376
+ [(@flash ? 1 : 0) +
377
+ (@asking ? 1 : 0) +
378
+ @minibuf_stack.compact.size, 1].max
373
379
  end
374
380
  end
375
381
 
@@ -383,8 +389,9 @@ class BufferManager
383
389
 
384
390
  Ncurses.mutex.lock unless opts[:sync] == false
385
391
  Ncurses.attrset Colormap.color_for(:none)
392
+ adj = @asking ? 2 : 1
386
393
  m.each_with_index do |s, i|
387
- Ncurses.mvaddstr Ncurses.rows - i - 1, 0, s + (" " * [Ncurses.cols - s.length, 0].max)
394
+ Ncurses.mvaddstr Ncurses.rows - i - adj, 0, s + (" " * [Ncurses.cols - s.length, 0].max)
388
395
  end
389
396
  Ncurses.refresh if opts[:refresh]
390
397
  Ncurses.mutex.unlock unless opts[:sync] == false
@@ -406,7 +413,7 @@ class BufferManager
406
413
 
407
414
  if block_given?
408
415
  begin
409
- yield
416
+ yield id
410
417
  ensure
411
418
  clear id
412
419
  end