sup 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|