sup 0.19.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +12 -0
- data/CONTRIBUTORS +84 -0
- data/Gemfile +3 -0
- data/HACKING +42 -0
- data/History.txt +361 -0
- data/LICENSE +280 -0
- data/README.md +70 -0
- data/Rakefile +12 -0
- data/ReleaseNotes +231 -0
- data/bin/sup +434 -0
- data/bin/sup-add +118 -0
- data/bin/sup-config +243 -0
- data/bin/sup-dump +43 -0
- data/bin/sup-import-dump +101 -0
- data/bin/sup-psych-ify-config-files +21 -0
- data/bin/sup-recover-sources +87 -0
- data/bin/sup-sync +210 -0
- data/bin/sup-sync-back-maildir +127 -0
- data/bin/sup-tweak-labels +140 -0
- data/contrib/colorpicker.rb +100 -0
- data/contrib/completion/_sup.zsh +114 -0
- data/devel/console.sh +3 -0
- data/devel/count-loc.sh +3 -0
- data/devel/load-index.rb +9 -0
- data/devel/profile.rb +12 -0
- data/devel/start-console.rb +5 -0
- data/doc/FAQ.txt +119 -0
- data/doc/Hooks.txt +79 -0
- data/doc/Philosophy.txt +69 -0
- data/lib/sup.rb +467 -0
- data/lib/sup/account.rb +90 -0
- data/lib/sup/buffer.rb +768 -0
- data/lib/sup/colormap.rb +239 -0
- data/lib/sup/contact.rb +67 -0
- data/lib/sup/crypto.rb +461 -0
- data/lib/sup/draft.rb +119 -0
- data/lib/sup/hook.rb +159 -0
- data/lib/sup/horizontal_selector.rb +59 -0
- data/lib/sup/idle.rb +42 -0
- data/lib/sup/index.rb +882 -0
- data/lib/sup/interactive_lock.rb +89 -0
- data/lib/sup/keymap.rb +140 -0
- data/lib/sup/label.rb +87 -0
- data/lib/sup/logger.rb +77 -0
- data/lib/sup/logger/singleton.rb +10 -0
- data/lib/sup/maildir.rb +257 -0
- data/lib/sup/mbox.rb +187 -0
- data/lib/sup/message.rb +803 -0
- data/lib/sup/message_chunks.rb +328 -0
- data/lib/sup/mode.rb +140 -0
- data/lib/sup/modes/buffer_list_mode.rb +50 -0
- data/lib/sup/modes/completion_mode.rb +55 -0
- data/lib/sup/modes/compose_mode.rb +38 -0
- data/lib/sup/modes/console_mode.rb +125 -0
- data/lib/sup/modes/contact_list_mode.rb +148 -0
- data/lib/sup/modes/edit_message_async_mode.rb +110 -0
- data/lib/sup/modes/edit_message_mode.rb +728 -0
- data/lib/sup/modes/file_browser_mode.rb +109 -0
- data/lib/sup/modes/forward_mode.rb +82 -0
- data/lib/sup/modes/help_mode.rb +19 -0
- data/lib/sup/modes/inbox_mode.rb +85 -0
- data/lib/sup/modes/label_list_mode.rb +138 -0
- data/lib/sup/modes/label_search_results_mode.rb +38 -0
- data/lib/sup/modes/line_cursor_mode.rb +203 -0
- data/lib/sup/modes/log_mode.rb +57 -0
- data/lib/sup/modes/person_search_results_mode.rb +12 -0
- data/lib/sup/modes/poll_mode.rb +19 -0
- data/lib/sup/modes/reply_mode.rb +228 -0
- data/lib/sup/modes/resume_mode.rb +52 -0
- data/lib/sup/modes/scroll_mode.rb +252 -0
- data/lib/sup/modes/search_list_mode.rb +204 -0
- data/lib/sup/modes/search_results_mode.rb +59 -0
- data/lib/sup/modes/text_mode.rb +76 -0
- data/lib/sup/modes/thread_index_mode.rb +1033 -0
- data/lib/sup/modes/thread_view_mode.rb +941 -0
- data/lib/sup/person.rb +134 -0
- data/lib/sup/poll.rb +272 -0
- data/lib/sup/rfc2047.rb +56 -0
- data/lib/sup/search.rb +110 -0
- data/lib/sup/sent.rb +58 -0
- data/lib/sup/service/label_service.rb +45 -0
- data/lib/sup/source.rb +244 -0
- data/lib/sup/tagger.rb +50 -0
- data/lib/sup/textfield.rb +253 -0
- data/lib/sup/thread.rb +452 -0
- data/lib/sup/time.rb +93 -0
- data/lib/sup/undo.rb +38 -0
- data/lib/sup/update.rb +30 -0
- data/lib/sup/util.rb +747 -0
- data/lib/sup/util/ncurses.rb +274 -0
- data/lib/sup/util/path.rb +9 -0
- data/lib/sup/util/query.rb +17 -0
- data/lib/sup/util/uri.rb +15 -0
- data/lib/sup/version.rb +3 -0
- data/sup.gemspec +53 -0
- data/test/dummy_source.rb +61 -0
- data/test/gnupg_test_home/gpg.conf +1 -0
- data/test/gnupg_test_home/pubring.gpg +0 -0
- data/test/gnupg_test_home/receiver_pubring.gpg +0 -0
- data/test/gnupg_test_home/receiver_secring.gpg +0 -0
- data/test/gnupg_test_home/receiver_trustdb.gpg +0 -0
- data/test/gnupg_test_home/secring.gpg +0 -0
- data/test/gnupg_test_home/sup-test-2@foo.bar.asc +20 -0
- data/test/gnupg_test_home/trustdb.gpg +0 -0
- data/test/integration/test_label_service.rb +18 -0
- data/test/messages/bad-content-transfer-encoding-1.eml +8 -0
- data/test/messages/binary-content-transfer-encoding-2.eml +21 -0
- data/test/messages/missing-line.eml +9 -0
- data/test/test_crypto.rb +109 -0
- data/test/test_header_parsing.rb +168 -0
- data/test/test_helper.rb +7 -0
- data/test/test_message.rb +532 -0
- data/test/test_messages_dir.rb +147 -0
- data/test/test_yaml_migration.rb +85 -0
- data/test/test_yaml_regressions.rb +17 -0
- data/test/unit/service/test_label_service.rb +19 -0
- data/test/unit/test_horizontal_selector.rb +40 -0
- data/test/unit/util/test_query.rb +46 -0
- data/test/unit/util/test_string.rb +57 -0
- data/test/unit/util/test_uri.rb +19 -0
- metadata +423 -0
data/doc/Philosophy.txt
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
Should an email client have a philosophy? For many people, email is
|
2
|
+
one of our primary means of communication, and email archives are an
|
3
|
+
integral part of our long-term memory. Something so important ought to
|
4
|
+
warrant a little thought.
|
5
|
+
|
6
|
+
Here's Sup's philosophy.
|
7
|
+
|
8
|
+
Using "traditional" email clients today is increasingly problematic.
|
9
|
+
Anyone who's on a high-traffic mailing list knows this. My ruby-talk
|
10
|
+
folder is 430 megs and Mutt sits there for 60 seconds while it opens
|
11
|
+
it. Keeping up with the all the new traffic is impossible, even with
|
12
|
+
Mutt's excellent threading features, simply because there's so much of
|
13
|
+
it. A single thread can span several pages in the folder index view
|
14
|
+
alone! And Mutt is probably the fastest, most mailing-list aware email
|
15
|
+
client out there. God help me if I try and use Thunderbird.
|
16
|
+
|
17
|
+
The problem with traditional clients like Mutt is that they deal with
|
18
|
+
individual pieces of email. This places a high mental cost on the user
|
19
|
+
for each incoming email, by forcing them to ask: Should I keep this
|
20
|
+
email, or delete it? If I keep it, where should I file it? I've spent
|
21
|
+
the last 10 years of my life laboriously hand-filing every email
|
22
|
+
message I received and feeling a mild sense of panic every time an
|
23
|
+
email was both "from Mom" and "about school". The massive amounts of
|
24
|
+
email that many people receive, and the cheap cost of storage, have
|
25
|
+
made these questions both more costly and less useful to answer.
|
26
|
+
|
27
|
+
Contrast that with using Gmail. As a long-time Mutt user, I was blown
|
28
|
+
away when I first saw someone use Gmail. They treated their email
|
29
|
+
differently from how I ever had. They never filed email and they never
|
30
|
+
deleted it. They relied on an immediate, global, full-text search, and
|
31
|
+
thread-level tagging, to do everything I'd ever done with Mutt, but
|
32
|
+
with a trivial cost to the user at message receipt time.
|
33
|
+
|
34
|
+
From Gmail I learned that making certain operations quantitatively
|
35
|
+
easier (namely, search) resulted in a qualitative improvement in
|
36
|
+
usage. I also learned how thread-centrism was advantageous over
|
37
|
+
message-centrism when message volume was high: most of the time, a
|
38
|
+
message and its context deserve the same treatment. I think it's to
|
39
|
+
the Gmail designers' credit that they started with a somewhat ad-hoc
|
40
|
+
idea (hey, we're really good at search engines, so maybe we can build
|
41
|
+
an email client on top of one) and managed to build something that was
|
42
|
+
actually better than everything else out there. At least, that's how I
|
43
|
+
imagine in happened. Maybe they knew what they were doing from the
|
44
|
+
start.
|
45
|
+
|
46
|
+
Unfortunately, there's a lot to Gmail I can't tolerate (top posting,
|
47
|
+
HTML mail, one-level threads, and ads come to mind, never mind the
|
48
|
+
fact that it's not FOSS). Thus Sup was born.
|
49
|
+
|
50
|
+
Sup is based on the following principles, which I stole directly from
|
51
|
+
Gmail:
|
52
|
+
|
53
|
+
- An immediately accessible and fast search capability over the entire
|
54
|
+
email archive eliminates most of the need for folders, and most of
|
55
|
+
the necessity of deleting email.
|
56
|
+
|
57
|
+
- Labels eliminate what little need for folders search doesn't cover.
|
58
|
+
|
59
|
+
- A thread-centric approach to the UI is much more in line with how
|
60
|
+
people operate than dealing with individual messages is. In the vast
|
61
|
+
majority of cases, a message and its context should be subject to
|
62
|
+
the same treatment.
|
63
|
+
|
64
|
+
Sup is also based on many ideas from mutt and Emacs and vi, having to
|
65
|
+
do with the fantastic productivity of a console- and keyboard-based
|
66
|
+
application, the usefulness of multiple buffers, the necessity of
|
67
|
+
handling multiple email accounts, etc. But those are just details!
|
68
|
+
|
69
|
+
Try it and let me know what you think.
|
data/lib/sup.rb
ADDED
@@ -0,0 +1,467 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'yaml'
|
5
|
+
require 'zlib'
|
6
|
+
require 'thread'
|
7
|
+
require 'fileutils'
|
8
|
+
require 'locale'
|
9
|
+
require 'ncursesw'
|
10
|
+
require 'rmail'
|
11
|
+
begin
|
12
|
+
require 'fastthread'
|
13
|
+
rescue LoadError
|
14
|
+
end
|
15
|
+
|
16
|
+
class Object
|
17
|
+
## this is for debugging purposes because i keep calling #id on the
|
18
|
+
## wrong object and i want it to throw an exception
|
19
|
+
def id
|
20
|
+
raise "wrong id called on #{self.inspect}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Module
|
25
|
+
def yaml_properties *props
|
26
|
+
props = props.map { |p| p.to_s }
|
27
|
+
|
28
|
+
path = name.gsub(/::/, "/")
|
29
|
+
yaml_tag "!#{Redwood::YAML_DOMAIN},#{Redwood::YAML_DATE}/#{path}"
|
30
|
+
|
31
|
+
define_method :init_with do |coder|
|
32
|
+
initialize(*coder.map.values_at(*props))
|
33
|
+
end
|
34
|
+
|
35
|
+
define_method :encode_with do |coder|
|
36
|
+
coder.map = props.inject({}) do |hash, key|
|
37
|
+
hash[key] = instance_variable_get("@#{key}")
|
38
|
+
hash
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Legacy
|
43
|
+
Psych.load_tags["!#{Redwood::LEGACY_YAML_DOMAIN},#{Redwood::YAML_DATE}/#{path}"] = self
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
module Redwood
|
48
|
+
BASE_DIR = ENV["SUP_BASE"] || File.join(ENV["HOME"], ".sup")
|
49
|
+
CONFIG_FN = File.join(BASE_DIR, "config.yaml")
|
50
|
+
COLOR_FN = File.join(BASE_DIR, "colors.yaml")
|
51
|
+
SOURCE_FN = File.join(BASE_DIR, "sources.yaml")
|
52
|
+
LABEL_FN = File.join(BASE_DIR, "labels.txt")
|
53
|
+
CONTACT_FN = File.join(BASE_DIR, "contacts.txt")
|
54
|
+
DRAFT_DIR = File.join(BASE_DIR, "drafts")
|
55
|
+
SENT_FN = File.join(BASE_DIR, "sent.mbox")
|
56
|
+
LOCK_FN = File.join(BASE_DIR, "lock")
|
57
|
+
SUICIDE_FN = File.join(BASE_DIR, "please-kill-yourself")
|
58
|
+
HOOK_DIR = File.join(BASE_DIR, "hooks")
|
59
|
+
SEARCH_FN = File.join(BASE_DIR, "searches.txt")
|
60
|
+
LOG_FN = File.join(BASE_DIR, "log")
|
61
|
+
SYNC_OK_FN = File.join(BASE_DIR, "sync-back-ok")
|
62
|
+
|
63
|
+
YAML_DOMAIN = "supmua.org"
|
64
|
+
LEGACY_YAML_DOMAIN = "masanjin.net"
|
65
|
+
YAML_DATE = "2006-10-01"
|
66
|
+
MAILDIR_SYNC_CHECK_SKIPPED = 'SKIPPED'
|
67
|
+
|
68
|
+
## record exceptions thrown in threads nicely
|
69
|
+
@exceptions = []
|
70
|
+
@exception_mutex = Mutex.new
|
71
|
+
|
72
|
+
attr_reader :exceptions
|
73
|
+
def record_exception e, name
|
74
|
+
@exception_mutex.synchronize do
|
75
|
+
@exceptions ||= []
|
76
|
+
@exceptions << [e, name]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def reporting_thread name
|
81
|
+
if $opts[:no_threads]
|
82
|
+
yield
|
83
|
+
else
|
84
|
+
::Thread.new do
|
85
|
+
begin
|
86
|
+
yield
|
87
|
+
rescue Exception => e
|
88
|
+
record_exception e, name
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
module_function :reporting_thread, :record_exception, :exceptions
|
95
|
+
|
96
|
+
## one-stop shop for yamliciousness
|
97
|
+
def save_yaml_obj o, fn, safe=false, backup=false
|
98
|
+
o = if o.is_a?(Array)
|
99
|
+
o.map { |x| (x.respond_to?(:before_marshal) && x.before_marshal) || x }
|
100
|
+
elsif o.respond_to? :before_marshal
|
101
|
+
o.before_marshal
|
102
|
+
else
|
103
|
+
o
|
104
|
+
end
|
105
|
+
|
106
|
+
mode = if File.exists? fn
|
107
|
+
File.stat(fn).mode
|
108
|
+
else
|
109
|
+
0600
|
110
|
+
end
|
111
|
+
|
112
|
+
if backup
|
113
|
+
backup_fn = fn + '.bak'
|
114
|
+
if File.exists?(fn) && File.size(fn) > 0
|
115
|
+
File.open(backup_fn, "w", mode) do |f|
|
116
|
+
File.open(fn, "r") { |old_f| FileUtils.copy_stream old_f, f }
|
117
|
+
f.fsync
|
118
|
+
end
|
119
|
+
end
|
120
|
+
File.open(fn, "w") do |f|
|
121
|
+
f.puts o.to_yaml
|
122
|
+
f.fsync
|
123
|
+
end
|
124
|
+
elsif safe
|
125
|
+
safe_fn = "#{File.dirname fn}/safe_#{File.basename fn}"
|
126
|
+
File.open(safe_fn, "w", mode) do |f|
|
127
|
+
f.puts o.to_yaml
|
128
|
+
f.fsync
|
129
|
+
end
|
130
|
+
FileUtils.mv safe_fn, fn
|
131
|
+
else
|
132
|
+
File.open(fn, "w", mode) do |f|
|
133
|
+
f.puts o.to_yaml
|
134
|
+
f.fsync
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def load_yaml_obj fn, compress=false
|
140
|
+
o = if File.exists? fn
|
141
|
+
if compress
|
142
|
+
Zlib::GzipReader.open(fn) { |f| YAML::load f }
|
143
|
+
else
|
144
|
+
YAML::load_file fn
|
145
|
+
end
|
146
|
+
end
|
147
|
+
if o.is_a?(Array)
|
148
|
+
o.each { |x| x.after_unmarshal! if x.respond_to?(:after_unmarshal!) }
|
149
|
+
else
|
150
|
+
o.after_unmarshal! if o.respond_to?(:after_unmarshal!)
|
151
|
+
end
|
152
|
+
o
|
153
|
+
end
|
154
|
+
|
155
|
+
def managers
|
156
|
+
%w(HookManager SentManager ContactManager LabelManager AccountManager
|
157
|
+
DraftManager UpdateManager PollManager CryptoManager UndoManager
|
158
|
+
SourceManager SearchManager IdleManager).map { |x| Redwood.const_get x.to_sym }
|
159
|
+
end
|
160
|
+
|
161
|
+
def start bypass_sync_check = false
|
162
|
+
managers.each { |x| fail "#{x} already instantiated" if x.instantiated? }
|
163
|
+
|
164
|
+
FileUtils.mkdir_p Redwood::BASE_DIR
|
165
|
+
$config = load_config Redwood::CONFIG_FN
|
166
|
+
@log_io = File.open(Redwood::LOG_FN, 'a')
|
167
|
+
Redwood::Logger.add_sink @log_io
|
168
|
+
Redwood::HookManager.init Redwood::HOOK_DIR
|
169
|
+
Redwood::SentManager.init $config[:sent_source] || 'sup://sent'
|
170
|
+
Redwood::ContactManager.init Redwood::CONTACT_FN
|
171
|
+
Redwood::LabelManager.init Redwood::LABEL_FN
|
172
|
+
Redwood::AccountManager.init $config[:accounts]
|
173
|
+
Redwood::DraftManager.init Redwood::DRAFT_DIR
|
174
|
+
Redwood::SearchManager.init Redwood::SEARCH_FN
|
175
|
+
|
176
|
+
managers.each { |x| x.init unless x.instantiated? }
|
177
|
+
|
178
|
+
return if bypass_sync_check
|
179
|
+
|
180
|
+
if $config[:sync_back_to_maildir]
|
181
|
+
if not File.exists? Redwood::SYNC_OK_FN
|
182
|
+
Redwood.warn_syncback <<EOS
|
183
|
+
It appears that the "sync_back_to_maildir" option has been changed
|
184
|
+
from false to true since the last execution of sup.
|
185
|
+
EOS
|
186
|
+
$stderr.puts <<EOS
|
187
|
+
|
188
|
+
Should I complain about this again? (Y/n)
|
189
|
+
EOS
|
190
|
+
File.open(Redwood::SYNC_OK_FN, 'w') {|f| f.write(Redwood::MAILDIR_SYNC_CHECK_SKIPPED) } if STDIN.gets.chomp.downcase == 'n'
|
191
|
+
end
|
192
|
+
elsif not $config[:sync_back_to_maildir] and File.exists? Redwood::SYNC_OK_FN
|
193
|
+
File.delete(Redwood::SYNC_OK_FN)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def check_syncback_settings
|
198
|
+
# don't check if syncback was never performed
|
199
|
+
return unless File.exists? Redwood::SYNC_OK_FN
|
200
|
+
active_sync_sources = File.readlines(Redwood::SYNC_OK_FN).collect { |e| e.strip }.find_all { |e| not e.empty? }
|
201
|
+
return if active_sync_sources.length == 1 and active_sync_sources[0] == Redwood::MAILDIR_SYNC_CHECK_SKIPPED
|
202
|
+
sources = SourceManager.sources
|
203
|
+
newly_synced = sources.select { |s| s.is_a? Maildir and s.sync_back_enabled? and not active_sync_sources.include? s.uri }
|
204
|
+
unless newly_synced.empty?
|
205
|
+
|
206
|
+
details =<<EOS
|
207
|
+
It appears that the option "sync_back" of the following source(s)
|
208
|
+
has been changed from false to true since the last execution of
|
209
|
+
sup:
|
210
|
+
|
211
|
+
EOS
|
212
|
+
newly_synced.each do |s|
|
213
|
+
details += "#{s} (usual: #{s.usual})\n"
|
214
|
+
end
|
215
|
+
|
216
|
+
Redwood.warn_syncback details
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def self.warn_syncback details
|
221
|
+
$stderr.puts <<EOS
|
222
|
+
WARNING
|
223
|
+
-------
|
224
|
+
|
225
|
+
#{details}
|
226
|
+
|
227
|
+
It is *strongly* recommended that you run "sup-sync-back-maildir"
|
228
|
+
before continuing, otherwise you might lose changes you have made in sup
|
229
|
+
to your Xapian index.
|
230
|
+
|
231
|
+
This script should be run each time you change the
|
232
|
+
"sync_back_to_maildir" flag in config.yaml from false to true or
|
233
|
+
the "sync_back" flag is changed to true for a source in sources.yaml.
|
234
|
+
|
235
|
+
Please run "sup-sync-back-maildir -h" for more information and why this
|
236
|
+
is needed.
|
237
|
+
|
238
|
+
Note that if you have any sources that are not marked as 'ususal' in
|
239
|
+
sources.yaml you need to manually specify them when running the
|
240
|
+
sup-sync-back-maildir script.
|
241
|
+
|
242
|
+
Are you really sure you want to continue? (y/N)
|
243
|
+
EOS
|
244
|
+
abort "Aborted" unless STDIN.gets.chomp.downcase == 'y'
|
245
|
+
end
|
246
|
+
|
247
|
+
def finish
|
248
|
+
Redwood::LabelManager.save if Redwood::LabelManager.instantiated?
|
249
|
+
Redwood::ContactManager.save if Redwood::ContactManager.instantiated?
|
250
|
+
Redwood::SearchManager.save if Redwood::SearchManager.instantiated?
|
251
|
+
Redwood::Logger.remove_sink @log_io
|
252
|
+
|
253
|
+
managers.each { |x| x.deinstantiate! if x.instantiated? }
|
254
|
+
|
255
|
+
@log_io.close if @log_io
|
256
|
+
@log_io = nil
|
257
|
+
$config = nil
|
258
|
+
end
|
259
|
+
|
260
|
+
## not really a good place for this, so I'll just dump it here.
|
261
|
+
##
|
262
|
+
## a source error is either a FatalSourceError or an OutOfSyncSourceError.
|
263
|
+
## the superclass SourceError is just a generic.
|
264
|
+
def report_broken_sources opts={}
|
265
|
+
return unless BufferManager.instantiated?
|
266
|
+
|
267
|
+
broken_sources = SourceManager.sources.select { |s| s.error.is_a? FatalSourceError }
|
268
|
+
unless broken_sources.empty?
|
269
|
+
BufferManager.spawn_unless_exists("Broken source notification for #{broken_sources.join(',')}", opts) do
|
270
|
+
TextMode.new(<<EOM)
|
271
|
+
Source error notification
|
272
|
+
-------------------------
|
273
|
+
|
274
|
+
Hi there. It looks like one or more message sources is reporting
|
275
|
+
errors. Until this is corrected, messages from these sources cannot
|
276
|
+
be viewed, and new messages will not be detected.
|
277
|
+
|
278
|
+
#{broken_sources.map { |s| "Source: " + s.to_s + "\n Error: " + s.error.message.wrap(70).join("\n ")}.join("\n\n")}
|
279
|
+
EOM
|
280
|
+
#' stupid ruby-mode
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
desynced_sources = SourceManager.sources.select { |s| s.error.is_a? OutOfSyncSourceError }
|
285
|
+
unless desynced_sources.empty?
|
286
|
+
BufferManager.spawn_unless_exists("Out-of-sync source notification for #{broken_sources.join(',')}", opts) do
|
287
|
+
TextMode.new(<<EOM)
|
288
|
+
Out-of-sync source notification
|
289
|
+
-------------------------------
|
290
|
+
|
291
|
+
Hi there. It looks like one or more sources has fallen out of sync
|
292
|
+
with my index. This can happen when you modify these sources with
|
293
|
+
other email clients. (Sorry, I don't play well with others.)
|
294
|
+
|
295
|
+
Until this is corrected, messages from these sources cannot be viewed,
|
296
|
+
and new messages will not be detected. Luckily, this is easy to correct!
|
297
|
+
|
298
|
+
#{desynced_sources.map do |s|
|
299
|
+
"Source: " + s.to_s +
|
300
|
+
"\n Error: " + s.error.message.wrap(70).join("\n ") +
|
301
|
+
"\n Fix: sup-sync --changed #{s.to_s}"
|
302
|
+
end}
|
303
|
+
EOM
|
304
|
+
#' stupid ruby-mode
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
|
310
|
+
## set up default configuration file
|
311
|
+
def load_config filename
|
312
|
+
default_config = {
|
313
|
+
:editor => ENV["EDITOR"] || "/usr/bin/vim -f -c 'setlocal spell spelllang=en_us' -c 'set filetype=mail'",
|
314
|
+
:thread_by_subject => false,
|
315
|
+
:edit_signature => false,
|
316
|
+
:ask_for_from => false,
|
317
|
+
:ask_for_to => true,
|
318
|
+
:ask_for_cc => true,
|
319
|
+
:ask_for_bcc => false,
|
320
|
+
:ask_for_subject => true,
|
321
|
+
:account_selector => true,
|
322
|
+
:confirm_no_attachments => true,
|
323
|
+
:confirm_top_posting => true,
|
324
|
+
:jump_to_open_message => true,
|
325
|
+
:discard_snippets_from_encrypted_messages => false,
|
326
|
+
:load_more_threads_when_scrolling => true,
|
327
|
+
:default_attachment_save_dir => "",
|
328
|
+
:sent_source => "sup://sent",
|
329
|
+
:archive_sent => true,
|
330
|
+
:poll_interval => 300,
|
331
|
+
:wrap_width => 0,
|
332
|
+
:slip_rows => 0,
|
333
|
+
:col_jump => 2,
|
334
|
+
:stem_language => "english",
|
335
|
+
:sync_back_to_maildir => false,
|
336
|
+
:continuous_scroll => false,
|
337
|
+
:always_edit_async => false,
|
338
|
+
}
|
339
|
+
if File.exists? filename
|
340
|
+
config = Redwood::load_yaml_obj filename
|
341
|
+
abort "#{filename} is not a valid configuration file (it's a #{config.class}, not a hash)" unless config.is_a?(Hash)
|
342
|
+
default_config.merge config
|
343
|
+
else
|
344
|
+
require 'etc'
|
345
|
+
require 'socket'
|
346
|
+
name = Etc.getpwnam(ENV["USER"]).gecos.split(/,/).first.force_encoding($encoding).fix_encoding! rescue nil
|
347
|
+
name ||= ENV["USER"]
|
348
|
+
email = ENV["USER"] + "@" +
|
349
|
+
begin
|
350
|
+
Socket.gethostbyname(Socket.gethostname).first
|
351
|
+
rescue SocketError
|
352
|
+
Socket.gethostname
|
353
|
+
end
|
354
|
+
|
355
|
+
config = {
|
356
|
+
:accounts => {
|
357
|
+
:default => {
|
358
|
+
:name => name.dup.fix_encoding!,
|
359
|
+
:email => email.dup.fix_encoding!,
|
360
|
+
:alternates => [],
|
361
|
+
:sendmail => "/usr/sbin/sendmail -oem -ti",
|
362
|
+
:signature => File.join(ENV["HOME"], ".signature"),
|
363
|
+
:gpgkey => ""
|
364
|
+
}
|
365
|
+
},
|
366
|
+
}
|
367
|
+
config.merge! default_config
|
368
|
+
begin
|
369
|
+
Redwood::save_yaml_obj config, filename, false, true
|
370
|
+
rescue StandardError => e
|
371
|
+
$stderr.puts "warning: #{e.message}"
|
372
|
+
end
|
373
|
+
config
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
module_function :save_yaml_obj, :load_yaml_obj, :start, :finish,
|
378
|
+
:report_broken_sources, :load_config, :managers,
|
379
|
+
:check_syncback_settings
|
380
|
+
end
|
381
|
+
|
382
|
+
require 'sup/version'
|
383
|
+
require "sup/util"
|
384
|
+
require "sup/hook"
|
385
|
+
require "sup/time"
|
386
|
+
|
387
|
+
## everything we need to get logging working
|
388
|
+
require "sup/logger/singleton"
|
389
|
+
|
390
|
+
## determine encoding and character set
|
391
|
+
$encoding = Locale.current.charset
|
392
|
+
$encoding = "UTF-8" if $encoding == "utf8"
|
393
|
+
$encoding = "UTF-8" if $encoding == "UTF8"
|
394
|
+
if $encoding
|
395
|
+
debug "using character set encoding #{$encoding.inspect}"
|
396
|
+
else
|
397
|
+
warn "can't find character set by using locale, defaulting to utf-8"
|
398
|
+
$encoding = "UTF-8"
|
399
|
+
end
|
400
|
+
|
401
|
+
# test encoding
|
402
|
+
teststr = "test"
|
403
|
+
teststr.encode('UTF-8')
|
404
|
+
begin
|
405
|
+
teststr.encode($encoding)
|
406
|
+
rescue Encoding::ConverterNotFoundError
|
407
|
+
warn "locale encoding is invalid, defaulting to utf-8"
|
408
|
+
$encoding = "UTF-8"
|
409
|
+
end
|
410
|
+
|
411
|
+
require "sup/buffer"
|
412
|
+
require "sup/keymap"
|
413
|
+
require "sup/mode"
|
414
|
+
require "sup/modes/scroll_mode"
|
415
|
+
require "sup/modes/text_mode"
|
416
|
+
require "sup/modes/log_mode"
|
417
|
+
require "sup/update"
|
418
|
+
require "sup/message_chunks"
|
419
|
+
require "sup/message"
|
420
|
+
require "sup/source"
|
421
|
+
require "sup/mbox"
|
422
|
+
require "sup/maildir"
|
423
|
+
require "sup/person"
|
424
|
+
require "sup/account"
|
425
|
+
require "sup/thread"
|
426
|
+
require "sup/interactive_lock"
|
427
|
+
require "sup/index"
|
428
|
+
require "sup/textfield"
|
429
|
+
require "sup/colormap"
|
430
|
+
require "sup/label"
|
431
|
+
require "sup/contact"
|
432
|
+
require "sup/tagger"
|
433
|
+
require "sup/draft"
|
434
|
+
require "sup/poll"
|
435
|
+
require "sup/crypto"
|
436
|
+
require "sup/undo"
|
437
|
+
require "sup/horizontal_selector"
|
438
|
+
require "sup/modes/line_cursor_mode"
|
439
|
+
require "sup/modes/help_mode"
|
440
|
+
require "sup/modes/edit_message_mode"
|
441
|
+
require "sup/modes/edit_message_async_mode"
|
442
|
+
require "sup/modes/compose_mode"
|
443
|
+
require "sup/modes/resume_mode"
|
444
|
+
require "sup/modes/forward_mode"
|
445
|
+
require "sup/modes/reply_mode"
|
446
|
+
require "sup/modes/label_list_mode"
|
447
|
+
require "sup/modes/contact_list_mode"
|
448
|
+
require "sup/modes/thread_view_mode"
|
449
|
+
require "sup/modes/thread_index_mode"
|
450
|
+
require "sup/modes/label_search_results_mode"
|
451
|
+
require "sup/modes/search_results_mode"
|
452
|
+
require "sup/modes/person_search_results_mode"
|
453
|
+
require "sup/modes/inbox_mode"
|
454
|
+
require "sup/modes/buffer_list_mode"
|
455
|
+
require "sup/modes/poll_mode"
|
456
|
+
require "sup/modes/file_browser_mode"
|
457
|
+
require "sup/modes/completion_mode"
|
458
|
+
require "sup/modes/console_mode"
|
459
|
+
require "sup/sent"
|
460
|
+
require "sup/search"
|
461
|
+
require "sup/modes/search_list_mode"
|
462
|
+
require "sup/idle"
|
463
|
+
|
464
|
+
$:.each do |base|
|
465
|
+
d = File.join base, "sup/share/modes/"
|
466
|
+
Redwood::Mode.load_all_modes d if File.directory? d
|
467
|
+
end
|