[DRE-commits] [sup-mail] 01/03: Imported Upstream version 0.19.0
Caitlin Matos
cm-guest at moszumanska.debian.org
Sat Aug 9 17:23:35 UTC 2014
This is an automated email from the git hooks/post-receive script.
cm-guest pushed a commit to branch master
in repository sup-mail.
commit 9beae7b0ee9d1ae8e69a980097c8c076f403b994
Author: Caitlin Matos <caitlin.matos at zoho.com>
Date: Sat Jul 26 21:48:53 2014 -0400
Imported Upstream version 0.19.0
---
.ditz-plugins | 1 -
.gitignore | 14 +-
.travis.yml | 12 +
CONTRIBUTORS | 64 +++--
Gemfile | 3 +
History.txt | 131 ++++++++++
README.md | 70 ++++++
README.txt | 128 ----------
Rakefile | 68 +----
ReleaseNotes | 112 +++++++++
bin/sup | 66 ++---
bin/sup-add | 7 +-
bin/sup-cmd | 138 -----------
bin/sup-config | 26 +-
bin/sup-dump | 2 +
bin/sup-import-dump | 4 +-
bin/sup-psych-ify-config-files | 21 ++
bin/sup-recover-sources | 2 +
bin/sup-server | 44 ----
bin/sup-sync | 6 +-
bin/sup-sync-back | 179 --------------
bin/sup-sync-back-maildir | 127 ++++++++++
bin/sup-tweak-labels | 13 +-
...e-0240b36671ecb019e57ef27e0901bff055385371.yaml | 22 --
...e-08d6bae05fa885bf6fcae39f864eb923c1e9a79e.yaml | 27 --
...e-09479a2ada22c2a0d76427e12ef2514d4753d070.yaml | 18 --
...e-15738247f939d20f8f202f80ccb85d9ad92101e0.yaml | 18 --
...e-182841e15d6909892adf43678bae03597ce10519.yaml | 25 --
...e-1a1527438c2d198eae9a264ce9e6b847854d9837.yaml | 30 ---
...e-2312263b6a2b7de6ae1ec4ab315c7829763e61be.yaml | 22 --
...e-23658477a445c2e61405fecb4cb641a2298caba6.yaml | 27 --
...e-2673f091c15dd90222a59621a1842d4ef0a743f7.yaml | 19 --
...e-2a0363cdf9d25edfa2a04b21299a538365e8b319.yaml | 30 ---
...e-2e74aa6843feee4daefe740b6e3f1fc54ff4bfcb.yaml | 22 --
...e-314f0cdac8d1998c46759a4ebef9077999bcef09.yaml | 18 --
...e-3408c200a5f47f92d12b5c063a00ce891c2ba4ce.yaml | 22 --
...e-3441fb8b7f955d625633d06fa0bf67a9afab046e.yaml | 18 --
...e-38d6f805b0c8bad013ec73f56e6245c890528591.yaml | 29 ---
...e-3b25f1d56b9be533edaf232b9e60dc24e00cba0b.yaml | 26 --
...e-42ab0840f9a1924f1c0561e8ddcf7e6988543ba0.yaml | 26 --
...e-46df983ccdb75408a37b3911472d4015664a3cf6.yaml | 20 --
...e-47aab6443b6c107c3067cdb614186099db570acf.yaml | 37 ---
...e-4af242013994ae557e431ba350a92c4f9e1739ef.yaml | 28 ---
...e-4daa2721dac8dfeb8730ee081f73b6c62702bd3e.yaml | 23 --
...e-4e501973cea5bd1f28739ae4cea98edce8249895.yaml | 32 ---
...e-5348fec2b1112250e241afc7467de29e5691d1be.yaml | 21 --
...e-57668c69d0190d6e849309834d4ad1d215efa779.yaml | 21 --
...e-5795c3c1b47e88f7261f57f31d33fe15ad08465d.yaml | 20 --
...e-5fab957dcd16f1da8962fe5b1f3a58d970315deb.yaml | 24 --
...e-60d86dd32054533a6206f698033ec668af6a7574.yaml | 29 ---
...e-61949ec83770b5d46f89eff21799968187012cce.yaml | 22 --
...e-65506670167642cc581956bc1b25c26b5bff215b.yaml | 30 ---
...e-658389418b5f0038cc3e6bc20fd3fd1566eb7111.yaml | 26 --
...e-69f785cddcc6e09ef0a357151373b3aa923d5e3f.yaml | 22 --
...e-6c053cca2eb05af486a2d09c6772fd5bd0cca444.yaml | 20 --
...e-6e0d634de74b2eb8297174ecd408b3810ba9351b.yaml | 24 --
...e-6e7960514f66ee67da083bc7bb5632d5808fc607.yaml | 30 ---
...e-7456c2d8fbd5de4dac651f6f4e9756f577497e01.yaml | 26 --
...e-76802330c4fdd091e8b1dd08dcc29ed432f003d4.yaml | 26 --
...e-799771a6a435dcad66dc80e7e051d91d24d005b1.yaml | 31 ---
...e-7a68c1e7120a8540c7c51c6095f4815918d16641.yaml | 28 ---
...e-7c77e757321c2639daea013824ad1a14099815b1.yaml | 26 --
...e-7d8474dfeeefaa50151c3ce48bee6b686d36a216.yaml | 29 ---
...e-829b449c51fca9a39047d00fabc552cc110c69b2.yaml | 24 --
...e-82c80f6dc2ce7b10b9e8f503d68253ced0ee8a1b.yaml | 24 --
...e-8a5cf9242ca60fa6c81091e425f734b4fb03e41a.yaml | 53 ----
...e-8aa7ea95f066fd0668452093b85903bd142905c9.yaml | 26 --
...e-8c0e627c500f679badca28f60ba76998fd65d46a.yaml | 26 --
...e-8e825caee33a6ac144580bf44d0d3060ad162394.yaml | 21 --
...e-91e1549102c0bfa2c201476d9618f7d234d1a626.yaml | 22 --
...e-9f7e28de46d74f7f1e445ae75ea4e230c7473374.yaml | 22 --
...e-a1a3427de5e8d4f74c0620f99e97ed92d21e924c.yaml | 30 ---
...e-a1e622dbae0e1841b4d9a376d419aed1d91460e0.yaml | 26 --
...e-a533480a30a18c3e823dbe20b759e1dcb32ca2b9.yaml | 26 --
...e-a68148169baa3838051f4bdb4c175e11cbf7f143.yaml | 22 --
...e-aae5ae6378afa9bd2a8e1b15d28ba7ccef867791.yaml | 27 --
...e-ad82aa00f4064fc7e1332cee0dae2c2ae95bb217.yaml | 26 --
...e-b1f1579fd8350d8add15c5cb588169acfdc5ea24.yaml | 29 ---
...e-b80aa39ef3b8d33bd57e4988c55d89c7c0df5c96.yaml | 24 --
...e-bc03bc702f41e6a9687b52d3e32db29132c0f65a.yaml | 25 --
...e-bdd4415a9d4c8fd3602500111bf9268aa7c7c6a4.yaml | 27 --
...e-bff2527210b3aacae2f74029e5856fed82f1689c.yaml | 33 ---
...e-c48f7fc58bba0b38ff6ae14cca01b08a5a7a6c33.yaml | 21 --
...e-c52f9762bc24a8f45863eb2e7beefa4201db34e8.yaml | 22 --
...e-c660ddfa9d633501140dd199bdfd7cd9fed5df0b.yaml | 22 --
...e-cef3096582de268c050f78223eb6a22ac2599606.yaml | 31 ---
...e-cf09ec6ec7c35d7d8c002b0521f97b6e94dc9b3e.yaml | 26 --
...e-cfbfc65dc90280fa5ecc63094af01d2a47ff0c6e.yaml | 22 --
...e-d131464e921aefc35571c119aac4d9f1decdebae.yaml | 30 ---
...e-d994a360c9cb2a6e12a734962a39ffbc6486a725.yaml | 34 ---
...e-d9e6be1b524c6c0a5c31c9c468bda170c2a8cb58.yaml | 33 ---
...e-e24df153080c6e7a16335018b04d70d9381258b8.yaml | 20 --
...e-e43b18777ea3aef3566bd80acd126e9ef8a5883a.yaml | 34 ---
...e-e7739718b4dbf49bbd3dd47133affbf7cb1e2361.yaml | 24 --
...e-e9c2f66a7ff4fb4525c2719e77ac8eedf3835dfd.yaml | 20 --
...e-f767a9d2071da7b0f66698ce74e642bf347be96b.yaml | 21 --
...e-fd7c7a7d7caf41ff20e7d10ca3f074fc02c14a5b.yaml | 30 ---
...e-fdfc906e8f4f6eb10f1ebdf39c416415d9ab6af9.yaml | 26 --
bugs/project.yaml | 53 ----
contrib/colorpicker.rb | 6 +-
contrib/completion/_sup.zsh | 4 +-
devel/console.sh | 2 +-
doc/FAQ.txt | 10 +-
doc/Hooks.txt | 7 +-
doc/NewUserGuide.txt | 258 -------------------
lib/sup.rb | 225 ++++++++++-------
lib/sup/account.rb | 5 +-
lib/sup/buffer.rb | 128 +++-------
lib/sup/client.rb | 92 -------
lib/sup/colormap.rb | 67 ++---
lib/sup/contact.rb | 11 +-
lib/sup/crypto.rb | 91 +++++--
lib/sup/draft.rb | 9 +-
lib/sup/hook.rb | 12 +-
...rizontal-selector.rb => horizontal_selector.rb} | 11 +-
lib/sup/idle.rb | 2 +-
lib/sup/index.rb | 234 +++++++++++-------
lib/sup/interactive-lock.rb | 74 ------
lib/sup/interactive_lock.rb | 89 +++++++
lib/sup/keymap.rb | 28 ++-
lib/sup/label.rb | 10 +-
lib/sup/logger.rb | 9 +-
lib/sup/logger/singleton.rb | 10 +
lib/sup/maildir.rb | 171 +++++++++----
lib/sup/mbox.rb | 4 +-
lib/sup/message.rb | 100 +++++++-
lib/sup/{message-chunks.rb => message_chunks.rb} | 74 ++++--
.../{buffer-list-mode.rb => buffer_list_mode.rb} | 0
.../{completion-mode.rb => completion_mode.rb} | 6 +-
lib/sup/modes/{compose-mode.rb => compose_mode.rb} | 6 +-
lib/sup/modes/{console-mode.rb => console_mode.rb} | 17 +-
.../{contact-list-mode.rb => contact_list_mode.rb} | 4 +-
...ge-async-mode.rb => edit_message_async_mode.rb} | 3 +-
.../{edit-message-mode.rb => edit_message_mode.rb} | 147 ++++++++---
.../{file-browser-mode.rb => file_browser_mode.rb} | 0
lib/sup/modes/{forward-mode.rb => forward_mode.rb} | 19 +-
lib/sup/modes/{help-mode.rb => help_mode.rb} | 0
lib/sup/modes/{inbox-mode.rb => inbox_mode.rb} | 44 +---
.../{label-list-mode.rb => label_list_mode.rb} | 0
...esults-mode.rb => label_search_results_mode.rb} | 0
.../{line-cursor-mode.rb => line_cursor_mode.rb} | 29 ++-
lib/sup/modes/{log-mode.rb => log_mode.rb} | 0
...sults-mode.rb => person_search_results_mode.rb} | 0
lib/sup/modes/{poll-mode.rb => poll_mode.rb} | 0
lib/sup/modes/{reply-mode.rb => reply_mode.rb} | 66 ++---
lib/sup/modes/{resume-mode.rb => resume_mode.rb} | 0
lib/sup/modes/{scroll-mode.rb => scroll_mode.rb} | 8 +-
.../{search-list-mode.rb => search_list_mode.rb} | 18 +-
...arch-results-mode.rb => search_results_mode.rb} | 6 +-
lib/sup/modes/{text-mode.rb => text_mode.rb} | 2 +-
.../{thread-index-mode.rb => thread_index_mode.rb} | 105 +++++++-
.../{thread-view-mode.rb => thread_view_mode.rb} | 68 ++++-
lib/sup/person.rb | 7 +-
lib/sup/poll.rb | 200 ++++++++++-----
lib/sup/protocol.rb | 161 ------------
lib/sup/rfc2047.rb | 4 +-
lib/sup/search.rb | 43 +++-
lib/sup/sent.rb | 11 +-
lib/sup/server.rb | 116 ---------
lib/sup/service/label_service.rb | 45 ++++
lib/sup/source.rb | 70 ++++--
lib/sup/tagger.rb | 4 +-
lib/sup/textfield.rb | 86 ++++---
lib/sup/thread.rb | 14 +-
lib/sup/time.rb | 14 +-
lib/sup/undo.rb | 2 +-
lib/sup/update.rb | 2 +-
lib/sup/util.rb | 207 ++++++++++------
lib/sup/util/ncurses.rb | 274 +++++++++++++++++++++
lib/sup/util/path.rb | 9 +
lib/sup/util/query.rb | 17 ++
lib/sup/util/uri.rb | 15 ++
lib/sup/version.rb | 3 +
protocol.md | 168 -------------
release-script.txt | 17 --
sup-files.rb | 11 -
sup-version.rb | 15 --
sup.gemspec | 53 ++++
test/dummy_source.rb | 2 +-
test/gnupg_test_home/gpg.conf | 1 +
test/gnupg_test_home/pubring.gpg | Bin 0 -> 1945 bytes
test/gnupg_test_home/receiver_pubring.gpg | Bin 0 -> 718 bytes
test/gnupg_test_home/receiver_secring.gpg | Bin 0 -> 1382 bytes
test/gnupg_test_home/receiver_trustdb.gpg | Bin 0 -> 1280 bytes
test/gnupg_test_home/secring.gpg | Bin 0 -> 2529 bytes
test/gnupg_test_home/sup-test-2 at foo.bar.asc | 20 ++
test/gnupg_test_home/trustdb.gpg | Bin 0 -> 1360 bytes
test/integration/test_label_service.rb | 18 ++
test/messages/bad-content-transfer-encoding-1.eml | 8 +
.../binary-content-transfer-encoding-2.eml | 21 ++
test/messages/missing-line.eml | 9 +
test/test_crypto.rb | 109 ++++++++
test/test_header_parsing.rb | 35 ++-
test/test_helper.rb | 7 +
test/test_message.rb | 36 ++-
test/test_messages_dir.rb | 147 +++++++++++
test/test_server.rb | 106 --------
test/test_yaml_migration.rb | 85 +++++++
test/test_yaml_regressions.rb | 17 ++
test/unit/service/test_label_service.rb | 19 ++
test/unit/test_horizontal_selector.rb | 40 +++
test/unit/util/test_query.rb | 46 ++++
test/unit/util/test_string.rb | 57 +++++
test/unit/util/test_uri.rb | 19 ++
www/index.html | 224 -----------------
www/main.css | 36 ---
www/ss1.png | Bin 93669 -> 0 bytes
www/ss2.png | Bin 38059 -> 0 bytes
www/ss3.png | Bin 43946 -> 0 bytes
www/ss4.png | Bin 43776 -> 0 bytes
www/ss5.png | Bin 63085 -> 0 bytes
www/ss6.png | Bin 30276 -> 0 bytes
212 files changed, 3436 insertions(+), 4699 deletions(-)
diff --git a/.ditz-plugins b/.ditz-plugins
deleted file mode 100644
index 2756e1e..0000000
--- a/.ditz-plugins
+++ /dev/null
@@ -1 +0,0 @@
-- git
diff --git a/.gitignore b/.gitignore
index 0820160..8c0e90c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,17 @@
# i use vi
*.swp
-.ditz-config
# i use emacs
*~
-# i use rake package task
+# artifact
pkg/
+*.gem
+# i have accidently added this one one too many times
+sup-exception-log.txt
+
+# bundler stuff
+Gemfile.lock
+.bundle
+
+# generated file for gnupg test
+test/gnupg_test_home/random_seed
+
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..c9b3cda
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,12 @@
+language: ruby
+
+rvm:
+ - 2.1.1
+ - 2.0.0
+ - 1.9.3
+
+before_install:
+ - sudo apt-get update -qq
+ - sudo apt-get install -qq uuid-dev uuid libncursesw5-dev libncursesw5 gnupg2
+
+script: bundle exec rake travis
diff --git a/CONTRIBUTORS b/CONTRIBUTORS
index 04d346b..5a89034 100644
--- a/CONTRIBUTORS
+++ b/CONTRIBUTORS
@@ -1,54 +1,84 @@
-William Morgan <wmorgan-sup at the masanjin dot nets>
+William Morgan <william at the twitter dot coms>
Rich Lane <rlane at the club.cc.cmu dot edus>
+Gaute Hope <eg at the gaute.vetsj dot coms>
+Whyme Lyu <callme5long at the gmail dot coms>
+Hamish Downer <dmishd at the gmail dot coms>
+Damien Leone <damien.leone at the fensalir dot frs>
+Sascha Silbe <sascha-pgp at the silbe dot orgs>
+Eric Weikl <eric.weikl at the gmx dot nets>
+Paweł Wilk <siefca at the gnu dot orgs>
Ismo Puustinen <ismo at the iki dot fis>
Nicolas Pouillard <nicolas.pouillard at the gmail dot coms>
-Eric Sherman <hyperbolist at the gmail dot coms>
Michael Stapelberg <michael at the stapelberg dot des>
+Eric Sherman <hyperbolist at the gmail dot coms>
+Tero Tilus <tero at the tilus dot nets>
Ben Walton <bwalton at the artsci.utoronto dot cas>
Mike Stipicevic <stipim at the rpi dot edus>
+Martin Bähr <mbaehr at the societyserver dot orgs>
+Clint Byrum <clint at the ubuntu dot coms>
+Wael M. Nasreddine <wael.nasreddine at the gmail dot coms>
Marcus Williams <marcus-sup at the bar-coded dot nets>
+Matthieu Rakotojaona <matthieu.rakotojaona at the gmail dot coms>
Lionel Ott <white.magic at the gmx dot des>
-Tero Tilus <tero at the tilus dot nets>
+Gaudenz Steinlin <gaudenz at the soziologie dot chs>
Ingmar Vanhassel <ingmar at the exherbo dot orgs>
Mark Alexander <marka at the pobox dot coms>
-Gaute Hope <eg at the gaute.vetsj dot coms>
+Edward Z. Yang <ezyang at the mit dot edus>
+Timon Vonk <timonv at the gmail dot coms>
+julien at macbook <julien.stechele at the gmail dot coms>
Christopher Warrington <chrisw at the rice dot edus>
W. Trevor King <wking at the drexel dot edus>
-Gaudenz Steinlin <gaudenz at the soziologie dot chs>
Richard Brown <rbrown at the exherbo dot orgs>
+Anthony Martinez <pi+sup at the pihost dot uss>
Marc Hartstein <marc.hartstein at the alum.vassar dot edus>
-Sascha Silbe <sascha-pgp at the silbe dot orgs>
Israel Herraiz <israel.herraiz at the gmail dot coms>
-Anthony Martinez <pi+sup at the pihost dot uss>
-Hamish Downer <dmishd at the gmail dot coms>
+Christopher Corley <cscorley at the ua dot edus>
+Markus Klinik <mkl at the lambdanaut dot nets>
Bo Borgerson <gigabo at the gmail dot coms>
-William Erik Baxter <web at the superscript dot coms>
+Atte Kojo <atte.kojo at the reaktor dot fis>
Michael Hamann <michael at the content-space dot des>
+William Erik Baxter <web at the superscript dot coms>
+Jonathan Lassoff <jof at the thejof dot coms>
Grant Hollingworth <grant at the antiflux dot orgs>
+Ico Doornekamp <ico at the pruts dot nls>
Adeodato Simó <dato at the net.com.org dot ess>
Daniel Schoepe <daniel.schoepe at the googlemail dot coms>
+James Taylor <james at the jamestaylor dot orgs>
Jason Petsod <jason at the petsod dot orgs>
+Robin Burchell <viroteck at the viroteck dot nets>
Steve Goldman <sgoldman at the tower-research dot coms>
-Edward Z. Yang <ezyang at the MIT dot EDUs>
+Peter Harkins <ph at the malaprop dot orgs>
Decklin Foster <decklin at the red-bean dot coms>
Cameron Matheson <cam+sup at the cammunism dot orgs>
Carl Worth <cworth at the cworth dot orgs>
-Jeff Balogh <its.jeff.balogh at the gmail dot coms>
+Alex Vandiver <alex at the chmrr dot nets>
Andrew Pimlott <andrew at the pimlott dot nets>
-Alex Vandiver <alexmv at the mit dot edus>
-Peter Harkins <ph at the malaprop dot orgs>
+Jeff Balogh <its.jeff.balogh at the gmail dot coms>
+Matías Aguirre <matiasaguirre at the gmail dot coms>
+PaulSmecker <paul.smecker at the gmail dot coms>
Kornilios Kourtis <kkourt at the cslab.ece.ntua dot grs>
+Lars Fischer <fischer at the wiwi.uni-siegen dot des>
+madhat2r <MaDhAt2r at the dukefoo dot coms>
Giorgio Lando <patroclo7 at the gmail dot coms>
-Damien Leone <damien.leone at the fensalir dot frs>
+Kevin Riggle <kevinr at the free-dissociation dot coms>
Benoît PIERRE <benoit.pierre at the gmail dot coms>
Alvaro Herrera <alvherre at the alvh.no-ip dot orgs>
+Steven Lawrance <stl at the koffein dot nets>
Jonah <Jonah at the GoodCoffee dot cas>
+ian <itaylor at the uark dot edus>
Adam Lloyd <adam at the alloy-d dot nets>
+MichaelRevell <mikearevell at the gmail dot coms>
+Gregor Hoffleit <gregor at the sam.mediasupervision dot des>
Todd Eisenberger <teisenbe at the andrew.cmu dot edus>
-ian <ian at the lorf dot orgs>
+Per Andersson <avtobiff at the gmail dot coms>
+0xACE <0xACE at the users.noreply.github dot coms>
+Steven Schmeiser <steven at the schmeiser dot orgs>
Steven Walter <swalter at the monarch.(none)>
-ian <itaylor at the uark dot edus>
Jon M. Dugan <jdugan at the es dot nets>
-Gregor Hoffleit <gregor at the sam.mediasupervision dot des>
+akojo <atte.kojo at the gmail dot coms>
+Matthias Vallentin <vallentin at the icir dot orgs>
+William A. Kennington III <william at the wkennington dot coms>
+Horacio Sanson <horacio at the skillupjapan.co dot jps>
Stefan Lundström <lundst at the snabb.(none)>
+Johannes Larsen <johs.a.larsen at the gmail dot coms>
Kirill Smelkov <kirr at the landau.phys.spbu dot rus>
diff --git a/Gemfile b/Gemfile
new file mode 100644
index 0000000..e088013
--- /dev/null
+++ b/Gemfile
@@ -0,0 +1,3 @@
+source 'https://rubygems.org/'
+
+gemspec
diff --git a/History.txt b/History.txt
index 8bfd5eb..3c230c5 100644
--- a/History.txt
+++ b/History.txt
@@ -1,3 +1,134 @@
+== 0.19.0 / 2014-07-05
+
+* new check-attachment hook
+* configure times to be seen in 24h format
+* new mailinglist: supmua at googlegroups.com
+
+== 0.18.0 / 2014-05-19
+
+* new color option, :with_attachment for defining colors for the
+ attachment character.
+* sup-tweak-labels works again (out of service since sync_back).
+* gem building is done through bundler
+* you can now kill a thread using & from thread_view
+
+== 0.17.0 / 2014-04-11
+
+* add continuous scrolling to thread view
+* add option for always editing in async mode
+* bugfix: fix completion char
+* bugfix: thread-view: dont close message when it is the first or last
+
+== 0.16.0 / 2014-03-21
+
+* sup-sync-back-mbox removed.
+* safer mime-view attachment file name handling
+* show thread labels in thread-view-mode
+* remove lock file if there is no sup alive
+* deprecate migration script on ruby > 2.1
+
+== 0.15.4 / 2014-02-06
+
+* Various bugfixes
+
+== 0.15.3 / 2014-01-27
+
+* Revert non-functioning hidden_alternates and fix some bugs.
+
+== 0.15.2 / 2013-12-20
+
+* Use the form_driver_w routine for inputing multibyte chars when
+ available.
+* Add hidden_alternates configuration option: hidden aliases for the
+ account.
+
+== 0.15.1 / 2013-12-04
+
+* Thread children are sorted last-activity latest (bottom).
+
+== 0.15.0 / 2013-11-07
+
+* Maildir Syncback has now been merged into main sup! This is a
+ long-time waiting feature initially developed by Damien Leone,
+ then picked up by Edward Z. Yang who continued development. Additionally
+ several others have been contributing.
+
+ Eventually, recently, Eric Weikl has picked up this branch, modernized
+ it to current sup, maintained it and gotten it ready for release.
+
+ Main authors:
+
+ Damien Leone
+ Edward Z. Yang
+ Eric Weikl
+
+ Not all of the features initially proposed have been included. This is
+ to maintain compatibility with more operating systems and wait with
+ the more daring features to make sure sup is stable-ish.
+
+ This is a big change since sup now can modify your mail (!), please
+ back up your mail and your configuration before using the maildir
+ syncback feature. For instructions on how to migrate an existing
+ maildir source or how to set up a new one, refer to the wiki:
+
+ https://github.com/sup-heliotrope/sup/wiki/Using-sup-with-other-clients
+
+ It is possible to both disable maildir syncback globally (default:
+ disabled) and per-source (default: enabled).
+
+* Sup on Ruby 2.0.0 now works - but beware, this has not been very throughly
+ tested. Patches are welcome.
+
+* We are now using our own rmail-sup gem with fixes for Ruby 2.0.0 and
+ various warnings fixed.
+
+* sup-sync-back has been renamed to sup-sync-back-mbox to conform with
+ the other sync-back scripts.
+
+* You can now save attachments to directories without specifying the full
+ filename (default filename is used).
+
+* Various encoding fixes and minor bug fixes
+
+== 0.14.1.1 / 2013-10-29
+
+* SBU1: security release
+* Tempfiles for attachments are persistent through the sup process to
+ ensure that spawned processes have access to them.
+
+== 0.13.2.1 / 2013-10-29
+
+* SBU1: security release
+
+== 0.14.1 / 2013-08-31
+
+* Various bugfixes.
+* Predefined 'All mail' search.
+
+== 0.14.0 / 2013-08-15
+
+* CJK compatability
+* Psych over Syck
+* Ruby 1.8 deprecated
+* Thread safety
+* No more Iconv, but using built in Ruby encodings. Better UTF-8
+ handling.
+* GPGME 2.0 support
+
+== 0.13.2 / 2013-06-26
+
+* FreeBSD 10 comptability
+* More threadsafe polling
+
+== 0.13.1 / 2013-06-21
+
+* Bugfixes
+
+== 0.13.0 / 2013-05-15
+
+* Bugfixes
+* Depend on ncursesw-sup
+
== 0.12.1 / 2011-01-23
* Depend on ncursesw rather than ncurses (Ruby 1.9 compatibility)
* Add sup-import-dump
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..11f013b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,70 @@
+# Sup
+
+A console-based email client with the best features of GMail, mutt and
+Emacs.
+
+## Installation
+
+[See the wiki][Installation]
+
+## Features / Problems
+
+Features:
+
+* GMail-like thread-centered archiving, tagging and muting
+* [Handling mail from multiple mbox and Maildir sources][sources]
+* Blazing fast full-text search with a [rich query language][search]
+* Multiple accounts - pick the right one when sending mail
+* [Ruby-programmable hooks][hooks]
+* Automatically tracking recent contacts
+
+Current limitations:
+
+* Sup does in general not play nicely with other mail clients, not all
+ changes can be synced back to the mail source. Refer to [Maildir Syncback][maildir-syncback]
+ in the wiki for this recently included feature. Maildir Syncback
+ allows you to sync back flag changes in messages and to write messages
+ to maildir sources.
+
+* Unix-centrism in MIME attachment handling and in sendmail invocation.
+
+## Problems
+
+Please report bugs to the [Github issue tracker](https://github.com/sup-heliotrope/sup/issues).
+
+## Links
+
+* [Homepage](http://supmua.org/)
+* [Code repository](https://github.com/sup-heliotrope/sup)
+* [Wiki](https://github.com/sup-heliotrope/sup/wiki)
+* IRC: [#sup @ freenode.net](http://webchat.freenode.net/?channels=#sup)
+* Mailing list: supmua at googlegroups.com (subscribe: supmua+subscribe at googlegroups.com, archive: https://groups.google.com/d/forum/supmua )
+
+## License
+
+```
+Copyright (c) 2013 Sup developers.
+Copyright (c) 2006--2009 William Morgan.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301, USA.
+```
+
+[sources]: https://github.com/sup-heliotrope/sup/wiki/Adding-sources
+[hooks]: https://github.com/sup-heliotrope/sup/wiki/Hooks
+[search]: https://github.com/sup-heliotrope/sup/wiki/Searching-your-mail
+[Installation]: https://github.com/sup-heliotrope/sup/wiki#installation
+[ruby20]: https://github.com/sup-heliotrope/sup/wiki/Development#sup-014
+[maildir-syncback]: https://github.com/sup-heliotrope/sup/wiki/Using-sup-with-other-clients
diff --git a/README.txt b/README.txt
deleted file mode 100644
index c2f0768..0000000
--- a/README.txt
+++ /dev/null
@@ -1,128 +0,0 @@
-sup
- by William Morgan <wmorgan-sup at masanjin.net>
- http://sup.rubyforge.org
-
-== DESCRIPTION:
-
-Sup is a console-based email client for people with a lot of email.
-It supports tagging, very fast full-text search, automatic contact-
-list management, and more. If you're the type of person who treats
-email as an extension of your long-term memory, Sup is for you.
-
-Sup makes it easy to:
-- Handle massive amounts of email.
-
-- Mix email from different sources: mbox files and Maildirs.
-
-- Instantaneously search over your entire email collection. Search over
- body text, or use a query language to combine search predicates in any
- way.
-
-- Handle multiple accounts. Replying to email sent to a particular
- account will use the correct SMTP server, signature, and from address.
-
-- Add custom code to customize Sup to whatever particular and bizarre
- needs you may have.
-
-- Organize email with user-defined labels, automatically track recent
- contacts, and much more!
-
-The goal of Sup is to become the email client of choice for nerds
-everywhere.
-
-== FEATURES/PROBLEMS:
-
-Features:
-
-- Scalability to massive amounts of email. Immediate startup and
- operability, regardless of how much amount of email you have.
-
-- Immediate full-text search of your entire email archive, using the
- Xapian query language. Search over message bodies, labels, from: and
- to: fields, or any combination thereof.
-
-- Thread-centrism. Operations are performed at the thread, not the
- message level. Entire threads are manipulated and viewed (with
- redundancies removed) at a time.
-
-- Labels instead of folders. Drop that tired old metaphor and you'll see
- how much easier it is to organize email.
-
-- GMail-style thread management. Archive a thread, and it will disappear
- from your inbox until someone replies. Kill a thread, and it will
- never come back to your inbox (but will still show up in searches.)
- Mark a thread as spam and you'll never again see it unless explicitly
- searching for spam.
-
-- Console based interface. No mouse clicking required!
-
-- Programmability. It's in Ruby. The code is good. It has an extensive
- hook system that makes it easy to extend and customize.
-
-- Multiple buffer support. Why be limited to viewing one thing at a
- time?
-
-- Tons of other little features, like automatic context-sensitive help,
- multi-message operations, MIME attachment viewing, recent contact list
- generation, etc.
-
-Current limitations which will be fixed:
-
-- Sup doesn't play nicely with other mail clients. If you alter a mail
- source (read, move, delete, etc) with another client Sup will punish
- you with a lengthy reindexing process.
-
-- Unix-centrism in MIME attachment handling and in sendmail invocation.
-
-== SYNOPSYS:
-
- 0. sup-config
- 1. sup
-
- Note that Sup never changes the contents of any mailboxes; it only
- indexes in to them. So it shouldn't ever corrupt your mail. The flip
- side is that if you change a mailbox (e.g. delete messages, or, in the
- case of mbox files, read an unread message) then Sup will be unable to
- load messages from that source and will ask you to run sup-sync
- --changed.
-
-== REQUIREMENTS:
-
- - xapian-full >= 1.1.3.2
- - ncurses >= 0.9.1
- - rmail >= 0.17
- - highline
- - net-ssh
- - trollop >= 1.12
- - lockfile
- - mime-types
- - gettext
- - fastthread
-
-== INSTALL:
-
-* gem install sup
-
-== PROBLEMS:
-
-See FAQ.txt for some common problems and their solutions.
-
-== LICENSE:
-
-Copyright (c) 2006--2009 William Morgan.
-
-This program is free software; you can redistribute it and/or
-modify it under the terms of the GNU General Public License
-as published by the Free Software Foundation; either version 2
-of the License, or (at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
-
diff --git a/Rakefile b/Rakefile
index d88fd8d..f15e7ff 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,64 +1,12 @@
-## is there really no way to make a rule for this?
-WWW_FILES = %w(www/index.html README.txt doc/Philosophy.txt doc/FAQ.txt doc/NewUserGuide.txt www/main.css)
-
-rule 'ss?.png' => 'ss?-small.png' do |t|
-end
-SCREENSHOTS = FileList["www/ss?.png"]
-SCREENSHOTS_SMALL = []
-SCREENSHOTS.each do |fn|
- fn =~ /ss(\d+)\.png/
- sfn = "www/ss#{$1}-small.png"
- file sfn => [fn] do |t|
- sh "cat #{fn} | pngtopnm | pnmscale -xysize 320 240 | pnmtopng > #{sfn}"
- end
- SCREENSHOTS_SMALL << sfn
-end
-
-task :upload_webpage => WWW_FILES do |t|
- sh "rsync -essh -cavz #{t.prerequisites * ' '} wmorgan at rubyforge.org:/var/www/gforge-projects/sup/"
-end
-
-task :upload_webpage_images => (SCREENSHOTS + SCREENSHOTS_SMALL) do |t|
- sh "rsync -essh -cavz #{t.prerequisites * ' '} wmorgan at rubyforge.org:/var/www/gforge-projects/sup/"
-end
-
-# vim: syntax=ruby
-# -*- ruby -*-
-task :upload_report do |t|
- sh "ditz html ditz"
- sh "rsync -essh -cavz ditz wmorgan at rubyforge.org:/var/www/gforge-projects/sup/"
-end
-
-$:.push "lib"
require 'rubygems'
-require "sup-files"
-require "sup-version"
-require 'rake/gempackagetask.rb'
-
-spec = Gem::Specification.new do |s|
- s.name = %q{sup}
- s.version = SUP_VERSION
- s.date = Time.now.to_s
- s.authors = ["William Morgan"]
- s.email = %q{wmorgan-sup at masanjin.net}
- s.summary = %q{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.}
- s.homepage = %q{http://sup.rubyforge.org/}
- s.description = %q{Sup is a console-based email client for people with a lot of email. It supports tagging, very fast full-text search, automatic contact-list management, and more. If you're the type of person who treats email as an extension of your long-term memory, Sup is for you. Sup makes it easy to: - Handle massive amounts of email. - Mix email from different sources: mbox files (even across different machines), Maildir directories, POP accounts, and GMail accounts. - Instant [...]
- s.files = SUP_FILES
- s.executables = SUP_EXECUTABLES
-
- s.add_dependency "xapian-full", ">= 1.2.1"
- s.add_dependency "ncursesw"
- s.add_dependency "rmail", ">= 0.17"
- s.add_dependency "highline"
- s.add_dependency "trollop", ">= 1.12"
- s.add_dependency "lockfile"
- s.add_dependency "mime-types", "~> 1"
- s.add_dependency "gettext"
-end
+require 'rake/testtask'
+require "bundler/gem_tasks"
-Rake::GemPackageTask.new(spec) do |pkg|
- pkg.need_tar = true
+Rake::TestTask.new(:test) do |test|
+ test.libs << 'test'
+ test.test_files = FileList.new('test/**/test_*.rb')
+ test.verbose = true
end
+task :default => :test
-task :tarball => ["pkg/sup-#{SUP_VERSION}.tgz"]
+task :travis => [:test, :build]
diff --git a/ReleaseNotes b/ReleaseNotes
index 69b9286..cb7eeb0 100644
--- a/ReleaseNotes
+++ b/ReleaseNotes
@@ -1,3 +1,115 @@
+Release 0.19.0:
+
+New hook: check-attachment and a new option to shows dates in 24h format.
+
+Our old mailinglists have been closed with the shut down of Rubyforge. Please subscribe to our new list: supmua at googlegroups.com.
+
+Release 0.18.0:
+
+sup-tweak-labels works again. new color options and some bug fixes.
+
+Release 0.17.0:
+
+Bugfixes and new option for continous scrolling as well as an option for
+always editing messages in async mode.
+
+Release 0.16.0:
+
+Removed unfinished and abandoned sup-sync-back-mbox.
+
+Safer mime-view attachment file name handling, a temp file name is used
+while the extension is only used if it is alphanumeric.
+
+The migration script for YAML documents is now deprecated for ruby > 2.1
+and will be removed in the future.
+
+Release 0.15.4:
+
+Bugfixes.
+
+Release 0.15.3:
+
+Revert non-functioning hidden_alternates option and fix bugs.
+
+Release 0.15.2:
+
+Use form_driver_w when available. New hidden_alternates option.
+
+Release 0.15.1:
+
+Sort threads last-activity-first and bug fix.
+
+Release 0.15.0:
+
+Maildir Syncback has been included. Refer to the wiki for more information on
+how to set it up.
+
+sup-sync-back has been moved to sup-sync-back-mbox, please make sure
+you make any needed changes.
+
+Release 0.14.1.1:
+
+See 0.13.2.1.
+
+Release 0.13.2.1:
+
+Security advisory (#SBU1) for Sup
+
+We have been notified of an potential exploit in the somewhat careless
+way Sup treats attachment metadata in received e-mails. The issues
+should now be fixed and I have released Sup 0.13.2.1 and 0.14.1.1 which
+incorporates these fixes. Please upgrade immediately and also ensure
+that your mime-decode or mime-view hooks are secure [0], [1].
+
+This is specifically related to using quotes (',") around filename or
+content_type which is already escaped using Ruby Shellwords.escape -
+this means that the string (content_type, filename) is intended to be
+used _without_ any further quotes. Please make sure that if you use
+.mailcap (non OSX systems), you do not quote the string.
+
+Credit goes to: joernchen of Phenoelit (http://phenoelit.de) who
+discovered and suggested fixes for these issues.
+
+[0] https://github.com/sup-heliotrope/sup/wiki/Viewing-Attachments
+[1] https://github.com/sup-heliotrope/sup/wiki/Secure-usage-of-Sup
+
+Release 0.14.1:
+
+Service release to 0.14.0 plus a predefined 'All mail' search.
+
+Release 0.14.0:
+
+CJK-compatability, Psych usage, thread safety, GPGME 2.0 support. Sup is now
+Ruby 1.9 based, and apart from RMail - ready for Ruby 2.0.0.
+
+Sup now uses Psych as a YAML parser (default by Ruby) and your previous
+configuration files (~/.sup/*.yaml) may need to be migrated or re-created for
+them to work with the new sup. A migration script is included for this.
+
+Check https://github.com/sup-heliotrope/sup/wiki/Migration-0.13-to-0.14 for
+the latest instructions.
+
+First back up your ~/.sup directory and index, after installing the new sup
+run:
+
+$ sup-psych-ify-config-files
+
+to migrate your files. You should now be all set for buisness.
+
+
+Release 0.13.2:
+
+FreeBSD compatability and more thread safe polling.
+
+Release 0.13.1:
+
+Another ruby 1.8 compatible release, various fixes.
+
+Release 0.13.0:
+
+Collection of bugfixes and stability fixes since 0.12.1. We now depend on our
+own ncursesw-sup fork.
+
Release 0.12.1:
This release changes the gem dependency on ncurses to ncursesw, which
diff --git a/bin/sup b/bin/sup
index ad7a0d1..2dda6a3 100755
--- a/bin/sup
+++ b/bin/sup
@@ -1,14 +1,12 @@
#!/usr/bin/env ruby
+# encoding: utf-8
+
+$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
require 'rubygems'
+require 'ncursesw'
-no_ncursesw = false
-begin
- require 'ncursesw'
-rescue LoadError
- require 'ncurses'
- no_ncursesw = true
-end
+require 'sup/util/ncurses'
no_gpgme = false
begin
@@ -19,17 +17,13 @@ end
require 'fileutils'
require 'trollop'
-require "sup"; Redwood::check_library_version_against "git"
+require "sup"
if ENV['SUP_PROFILE']
require 'ruby-prof'
RubyProf.start
end
-if no_ncursesw
- info "No 'ncursesw' gem detected. Install it for wide character support."
-end
-
if no_gpgme
info "No 'gpgme' gem detected. Install it for email encryption, decryption and signatures."
end
@@ -112,14 +106,14 @@ end
## ncurses.so that's been compiled against libncursesw. (note the w.) why
## this works, i have no idea. much like pretty much every aspect of
## dealing with curses. cargo cult programming at its best.
-##
-## BSD users: if libc.so.6 is not found, try installing compat6x.
require 'dl/import'
+require 'rbconfig'
module LibC
extend DL.const_defined?(:Importer) ? DL::Importer : DL::Importable
- setlocale_lib = case Config::CONFIG['arch']
+ setlocale_lib = case RbConfig::CONFIG['arch']
when /darwin/; "libc.dylib"
when /cygwin/; "cygwin1.dll"
+ when /freebsd/; "libc.so.7"
else; "libc.so.6"
end
@@ -132,9 +126,6 @@ module LibC
rescue RuntimeError => e
warn "cannot dlload setlocale(); ncurses wide character support probably broken."
warn "dlload error was #{e.class}: #{e.message}"
- if Config::CONFIG['arch'] =~ /bsd/
- warn "BSD variant detected. You may have to install a compat6x package to acquire libc."
- end
end
end
@@ -146,6 +137,7 @@ def start_cursing
Ncurses.use_default_colors
Ncurses.curs_set 0
Ncurses.start_color
+ Ncurses.prepare_form_driver
$cursing = true
end
@@ -163,11 +155,16 @@ Index.lock_interactively or exit
begin
Redwood::start
Index.load
+ Redwood::check_syncback_settings
Index.start_sync_worker unless $opts[:no_threads]
$die = false
trap("TERM") { |x| $die = true }
- trap("WINCH") { |x| BufferManager.sigwinch_happened! }
+ trap("WINCH") do |x|
+ ::Thread.new do
+ BufferManager.sigwinch_happened!
+ end
+ end
if(s = Redwood::SourceManager.source_for DraftManager.source_name)
DraftManager.source = s
@@ -226,7 +223,7 @@ begin
to = Person.from_address_list $opts[:compose]
mode = ComposeMode.new :to => to, :subj => $opts[:subject]
BufferManager.spawn "New Message", mode
- mode.edit_message
+ mode.default_edit_message
end
unless $opts[:no_threads]
@@ -241,14 +238,14 @@ begin
until Redwood::exceptions.nonempty? || $die
c = begin
- Ncurses.nonblocking_getch
+ Ncurses::CharCode.get false
rescue Interrupt
raise if BufferManager.ask_yes_or_no "Die ungracefully now?"
BufferManager.draw_screen
- nil
+ Ncurses::CharCode.empty
end
- if c.nil?
+ if c.empty?
if BufferManager.sigwinch_happened?
debug "redrawing screen on sigwinch"
BufferManager.completely_redraw_screen
@@ -258,7 +255,7 @@ begin
IdleManager.ping
- if c == 410
+ if c.is_keycode? 410
## this is ncurses's way of telling us it's detected a refresh.
## since we have our own sigwinch handler, we don't do anything.
next
@@ -298,7 +295,10 @@ begin
b, new = bm.spawn_unless_exists("Contact List") { ContactListMode.new }
b.mode.load_in_background if new
when :search
- query = BufferManager.ask :search, "Search all messages (enter for saved searches): "
+ completions = LabelManager.all_labels.map { |l| "label:#{LabelManager.string_for l}" }
+ completions = completions.each { |l| l.fix_encoding! }
+ completions += Index::COMPL_PREFIXES
+ query = BufferManager.ask_many_with_completions :search, "Search all messages (enter for saved searches): ", completions
unless query.nil?
if query.empty?
bm.spawn_unless_exists("Saved searches") { SearchListMode.new }
@@ -310,7 +310,7 @@ begin
SearchResultsMode.spawn_from_query "is:unread"
when :list_labels
labels = LabelManager.all_labels.map { |l| LabelManager.string_for l }
- labels = labels.each { |l| l.force_encoding 'UTF-8' if l.methods.include?(:encoding) }
+ labels = labels.each { |l| l.fix_encoding! }
user_label = bm.ask_with_completions :label, "Show threads with label (enter for listing): ", labels
unless user_label.nil?
@@ -337,7 +337,7 @@ begin
Index.each_id_by_date(:label => :draft) { |mid, builder| m = builder.call }
r = ResumeMode.new(m)
BufferManager.spawn "Edit message", r
- r.edit_message
+ r.default_edit_message
else
b, new = BufferManager.spawn_unless_exists("All drafts") { LabelSearchResultsMode.new [:draft] }
b.mode.load_threads :num => b.content_height if new
@@ -378,7 +378,7 @@ ensure
Index.stop_lock_update_thread
end
- HookManager.run "shutdown"
+ HookManager.run "shutdown" if HookManager.instantiated?
Index.stop_sync_worker
Redwood::finish
@@ -415,14 +415,14 @@ unless Redwood::exceptions.empty?
end
$stderr.puts <<EOS
----------------------------------------------------------------
-I'm very sorry. It seems that an error occurred in Sup. Please
-accept my sincere apologies. Please submit the contents of
+We are very sorry. It seems that an error occurred in Sup. Please
+accept our sincere apologies. Please submit the contents of
#{BASE_DIR}/exception-log.txt and a brief report of the
-circumstances to http://masanjin.net/sup-bugs/ so that I might
-address this problem. Thank you!
+circumstances to https://github.com/sup-heliotrope/sup/issues so that
+we might address this problem. Thank you!
Sincerely,
-William
+The Sup Developers
----------------------------------------------------------------
EOS
Redwood::exceptions.each do |e, name|
diff --git a/bin/sup-add b/bin/sup-add
index c77720f..49d1770 100755
--- a/bin/sup-add
+++ b/bin/sup-add
@@ -1,10 +1,12 @@
#!/usr/bin/env ruby
+$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
+
require 'uri'
require 'rubygems'
require 'highline/import'
require 'trollop'
-require "sup"; Redwood::check_library_version_against "git"
+require "sup"
$opts = Trollop::options do
version "sup-add (sup #{Redwood::VERSION})"
@@ -28,6 +30,7 @@ Options are:
EOS
opt :archive, "Automatically archive all new messages from these sources."
opt :unusual, "Do not automatically poll these sources for new messages."
+ opt :sync_back, "Synchronize status flags back into messages, defaults to true (Maildir sources only).", :default => true
opt :labels, "A comma-separated set of labels to apply to all messages from this source", :type => String
opt :force_new, "Create a new account for this source, even if one already exists."
opt :force_account, "Reuse previously defined account user at hostname.", :type => String
@@ -97,7 +100,7 @@ begin
source =
case parsed_uri.scheme
when "maildir"
- Redwood::Maildir.new uri, !$opts[:unusual], $opts[:archive], nil, labels
+ Redwood::Maildir.new uri, !$opts[:unusual], $opts[:archive], $opts[:sync_back], nil, labels
when "mbox"
Redwood::MBox.new uri, !$opts[:unusual], $opts[:archive], nil, labels
when nil
diff --git a/bin/sup-cmd b/bin/sup-cmd
deleted file mode 100755
index 8ec5c69..0000000
--- a/bin/sup-cmd
+++ /dev/null
@@ -1,138 +0,0 @@
-#!/usr/bin/env ruby
-require 'rubygems'
-require 'trollop'
-require 'sup'
-require 'sup/client'
-require 'pp'
-require 'yaml'
-include Redwood
-
-SUB_COMMANDS = %w(query count label add)
-global_opts = Trollop::options do
- #version = "sup-cmd (sup #{Redwood::VERSION})"
- banner <<EOS
-Connect to a running sup-server.
-
-Usage:
- sup-cmd [global options] command [options]
-
- Valid commands: #{SUB_COMMANDS * ', '}
-
- Global options:
-EOS
-
- opt :host, "server address", :type => :string, :default => 'localhost', :short => 'o'
- opt :port, "server port", :type => :int, :default => 4300
- opt :socket, "unix domain socket path", :type => :string, :default => nil
- opt :verbose
-
- conflicts :host, :socket
- conflicts :port, :socket
-
- stop_on SUB_COMMANDS
-end
-
-cmd = ARGV.shift
-cmd_opts = case cmd
-when "query"
- Trollop.options do
- opt :offset, "Offset", :default => 0, :type => :int
- opt :limit, "Limit", :type => :int
- opt :raw, "Retrieve raw message text", :default => false
- end
-when "count"
- Trollop.options do
- end
-when "label"
- Trollop.options do
- opt :add_labels, "Labels to add", :default => ""
- opt :remove_labels, "Labels to remove", :default => ""
- end
-when "add"
- Trollop.options do
- opt :labels, "Labels separated by commas", :default => ""
- opt :mbox, "Treat input files as mboxes", :default => false
- end
-else
- Trollop::die "unrecognized command #{cmd.inspect}"
-end
-
-class SupCmd < Redwood::Client
- def initialize cmd, args, opts
- @cmd = cmd
- @opts = opts
- @args = args
- super()
- end
-
- def get_query
- @args.first or fail "query argument required"
- end
-
- def connection_established
- case @cmd
- when "query"
- query get_query, @opts[:offset], @opts[:limit], @opts[:raw] do |result|
- if result
- puts YAML.dump(result['summary'])
- puts YAML.dump(result['raw']) if @opts[:raw]
- else
- close_connection
- end
- end
- when "count"
- count(get_query) do |x|
- puts x
- close_connection
- end
- when "label"
- label get_query, @opts[:remove_labels].split(','), @opts[:add_labels].split(',') do
- close_connection
- end
- when "add"
- ARGF.binmode
- labels = @opts[:labels].split(',')
- get_message = lambda do
- return ARGF.gets(nil) unless @opts[:mbox]
- str = ""
- l = ARGF.gets
- str << l until ARGF.closed? || ARGF.eof? || MBox::is_break_line?(l = ARGF.gets)
- str.empty? ? nil : str
- end
- i_s = i = 0
- t = Time.now
- while raw = get_message[]
- i += 1
- t_d = Time.now - t
- if t_d >= 5
- i_d = i - i_s
- puts "indexed #{i} messages (#{i_d/t_d} m/s)" if global_opts[:verbose]
- t = Time.now
- i_s = i
- end
- add raw, labels do
- close_connection
- end
- end
- else
- fail "#{@cmd} command unimplemented"
- close_connection
- end
- end
-
- def unbind
- EM.stop
- end
-end
-
-
-EM.run do
- if global_opts[:socket]
- EM.connect global_opts[:socket], SupCmd, cmd, ARGV, cmd_opts.merge(global_opts)
- else
- EM.connect global_opts[:host], global_opts[:port], SupCmd, cmd, ARGV, cmd_opts.merge(global_opts)
- end
-end
-
-exit 0
-
diff --git a/bin/sup-config b/bin/sup-config
index c4a64a3..0bee67f 100755
--- a/bin/sup-config
+++ b/bin/sup-config
@@ -1,8 +1,9 @@
#!/usr/bin/env ruby
+$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
+
require 'rubygems'
require 'highline/import'
-require 'yaml'
require 'trollop'
require "sup"
@@ -20,12 +21,13 @@ EOS
end
def axe q, default=nil
- ans = if default && !default.empty?
- ask "#{q} (enter for \"#{default}\"): "
- else
- ask "#{q}: "
- end
- ans.empty? ? default : ans
+ question = if default && !default.empty?
+ "#{q} (enter for \"#{default}\"): "
+ else
+ "#{q}: "
+ end
+ ans = ask question
+ ans.empty? ? default : ans.to_s
end
def axe_yes q, default="n"
@@ -37,6 +39,8 @@ def build_cmd cmd
end
def add_source
+ require "sup/util/uri"
+
type = nil
say "Ok, adding a new source."
@@ -70,7 +74,7 @@ def add_source
end
uri = begin
- URI::Generic.build components
+ Redwood::Util::Uri.build components
rescue URI::Error => e
say "Whoopsie! I couldn't build a URI from that: #{e.message}"
if axe_yes("Try again?") then next else return end
@@ -84,6 +88,8 @@ def add_source
usual = axe_yes "Does this source ever receive new messages?", "y"
archive = usual ? axe_yes("Should new messages be automatically archived? (I.e. not appear in your inbox, though still be accessible via search.)") : false
+ sync_back = (type == :maildir) ? axe_yes("Should the original Maildir messages be modified to reflect changes like read status, starred messages, etc.?", "y") : false
+
labels_str = axe("Enter any labels to be automatically added to all messages from this source, separated by spaces (or 'none')", default_labels.join(","))
labels = if labels_str =~ /^\s*none\s*$/i
@@ -95,6 +101,7 @@ def add_source
cmd = build_cmd "sup-add"
cmd += " --unusual" unless usual
cmd += " --archive" if archive
+ cmd += " --no-sync-back" unless sync_back
cmd += " --labels=#{labels.join(',')}" if labels && !labels.empty?
cmd += " #{uri}"
@@ -142,11 +149,14 @@ alts = axe("Alternate email addresses", account[:alternates].join(" ")).split(/\
sigfn = axe "What file contains your signature?", account[:signature]
editor = axe "What editor would you like to use?", $config[:editor]
+time_mode = axe "Would like to display time in 12h (type 12h) or in 24h (type 24h)?", $config[:time_mode]
+
$config[:accounts][:default][:name] = name
$config[:accounts][:default][:email] = email
$config[:accounts][:default][:alternates] = alts
$config[:accounts][:default][:signature] = sigfn
$config[:editor] = editor
+$config[:time_mode] = time_mode
done = false
until done
diff --git a/bin/sup-dump b/bin/sup-dump
index 4908b18..271a982 100755
--- a/bin/sup-dump
+++ b/bin/sup-dump
@@ -1,5 +1,7 @@
#!/usr/bin/env ruby
+$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
+
require 'rubygems'
require 'xapian'
require 'trollop'
diff --git a/bin/sup-import-dump b/bin/sup-import-dump
index 91a1721..bde1723 100755
--- a/bin/sup-import-dump
+++ b/bin/sup-import-dump
@@ -1,9 +1,11 @@
#!/usr/bin/env ruby
+$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
+
require 'uri'
require 'rubygems'
require 'trollop'
-require "sup"; Redwood::check_library_version_against "git"
+require "sup"
PROGRESS_UPDATE_INTERVAL = 15 # seconds
diff --git a/bin/sup-psych-ify-config-files b/bin/sup-psych-ify-config-files
new file mode 100755
index 0000000..cec6873
--- /dev/null
+++ b/bin/sup-psych-ify-config-files
@@ -0,0 +1,21 @@
+#!/usr/bin/env ruby
+
+$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
+
+require "sup"
+require "fileutils"
+
+if RUBY_VERSION >= "2.1"
+ puts "YAML migration is deprecated by Ruby 2.1 and newer."
+ exit
+end
+
+Redwood.start
+
+fn = Redwood::SOURCE_FN
+FileUtils.cp fn, "#{fn}.syck_bak"
+
+Redwood::SourceManager.load_sources fn
+Redwood::SourceManager.save_sources fn, true
+
+Redwood.finish
diff --git a/bin/sup-recover-sources b/bin/sup-recover-sources
index 1f69f62..2d62c32 100755
--- a/bin/sup-recover-sources
+++ b/bin/sup-recover-sources
@@ -1,5 +1,7 @@
#!/usr/bin/env ruby
+$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
+
require 'optparse'
$opts = {
diff --git a/bin/sup-server b/bin/sup-server
deleted file mode 100755
index aeec37c..0000000
--- a/bin/sup-server
+++ /dev/null
@@ -1,44 +0,0 @@
-#!/usr/bin/env ruby
-require 'rubygems'
-require 'trollop'
-require 'sup'
-require 'sup/server'
-require 'pp'
-require 'yaml'
-include Redwood
-
-global_opts = Trollop::options do
- #version = "sup-cmd (sup #{Redwood::VERSION})"
- banner <<EOS
-Interact with a Sup index.
-
-Usage:
- sup-server [options]
-EOS
-
- opt :host, "address to listen on", :type => :string, :default => 'localhost', :short => 'o'
- opt :port, "port to listen on", :type => :int, :default => 4300
- opt :verbose
-end
-
-Redwood.start
-Index.init
-Index.lock_interactively or exit
-begin
- if(s = Redwood::SourceManager.source_for SentManager.source_uri)
- SentManager.source = s
- else
- Redwood::SourceManager.add_source SentManager.default_source
- end
-
- Index.load
-
- EM.run do
- EM.start_server global_opts[:host], global_opts[:port],
- Redwood::Server, Index.instance
- EM.next_tick { puts "ready" }
- end
-
-ensure
- Index.unlock
-end
diff --git a/bin/sup-sync b/bin/sup-sync
index b4d5cba..87bf5ec 100755
--- a/bin/sup-sync
+++ b/bin/sup-sync
@@ -1,9 +1,11 @@
#!/usr/bin/env ruby
+$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
+
require 'uri'
require 'rubygems'
require 'trollop'
-require "sup"; Redwood::check_library_version_against "git"
+require "sup"
PROGRESS_UPDATE_INTERVAL = 15 # seconds
@@ -173,7 +175,7 @@ begin
puts "Changing flags for #{source}##{m.source_info} from #{old_m.labels} to #{m.labels}" if opts[:verbose]
num_updated += 1
end
- else fail
+ else fail "sup-sync cannot handle :update's"
end
if Time.now - last_info_time > PROGRESS_UPDATE_INTERVAL
diff --git a/bin/sup-sync-back b/bin/sup-sync-back
deleted file mode 100755
index 5d84cba..0000000
--- a/bin/sup-sync-back
+++ /dev/null
@@ -1,179 +0,0 @@
-#!/usr/bin/env ruby
-
-require 'rubygems'
-require 'uri'
-require 'tempfile'
-require 'trollop'
-require "sup"; Redwood::check_library_version_against "git"
-
-fail "not working yet"
-
-## save a message 'm' to an open file pointer 'fp'
-def save m, fp
- m.source.each_raw_message_line(m.source_info) { |l| fp.print l }
-end
-def die msg
- $stderr.puts "Error: #{msg}"
- exit(-1)
-end
-def has_any_from_source_with_label? index, source, label
- query = { :source_id => source.id, :label => label, :limit => 1, :load_spam => true, :load_deleted => true, :load_killed => true }
- index.num_results_for(query) != 0
-end
-
-opts = Trollop::options do
- version "sup-sync-back (sup #{Redwood::VERSION})"
- banner <<EOS
-Drop or move messages from Sup sources that are marked as deleted or
-spam in the Sup index.
-
-Currently only works with mbox sources.
-
-Usage:
- sup-sync-back [options] <source>*
-
-where <source>* is zero or more source URIs. If no sources are given,
-sync back all usual sources.
-
-You almost certainly want to run sup-sync --changed after this command.
-Running this does not change the index.
-
-Options include:
-EOS
- opt :drop_deleted, "Drop deleted messages.", :default => false, :short => "d"
- opt :move_deleted, "Move deleted messages to a local mbox file.", :type => String, :short => :none
- opt :drop_spam, "Drop spam messages.", :default => false, :short => "s"
- opt :move_spam, "Move spam messages to a local mbox file.", :type => String, :short => :none
-
- opt :with_dotlockfile, "Specific dotlockfile location (mbox files only).", :default => "/usr/bin/dotlockfile", :short => :none
- opt :dont_use_dotlockfile, "Don't use dotlockfile to lock mbox files. Dangerous if other processes modify them concurrently.", :default => false, :short => :none
-
- opt :verbose, "Print message ids as they're processed."
- opt :dry_run, "Don't actually modify the index. Probably only useful with --verbose.", :short => "-n"
- opt :version, "Show version information", :short => :none
-
- conflicts :drop_deleted, :move_deleted
- conflicts :drop_spam, :move_spam
-end
-
-unless opts[:drop_deleted] || opts[:move_deleted] || opts[:drop_spam] || opts[:move_spam]
- puts <<EOS
-Nothing to do. Please specify at least one of --drop-deleted, --move-deleted,
---drop-spam, or --move-spam.
-EOS
-
- exit
-end
-
-Redwood::start
-index = Redwood::Index.init
-index.lock_interactively or exit
-
-deleted_fp, spam_fp = nil
-unless opts[:dry_run]
- deleted_fp = File.open(opts[:move_deleted], "a") if opts[:move_deleted]
- spam_fp = File.open(opts[:move_spam], "a") if opts[:move_spam]
-end
-
-dotlockfile = opts[:with_dotlockfile] || "/usr/bin/dotlockfile"
-
-begin
- index.load
-
- sources = ARGV.map do |uri|
- s = Redwood::SourceManager.source_for(uri) or die "unknown source: #{uri}. Did you add it with sup-add first?"
- s.is_a?(Redwood::MBox) or die "#{uri} is not an mbox source."
- s
- end
-
- if sources.empty?
- sources = Redwood::SourceManager.usual_sources.select { |s| s.is_a? Redwood::MBox }
- end
-
- unless sources.all? { |s| s.file_path.nil? } || File.executable?(dotlockfile) || opts[:dont_use_dotlockfile]
- die <<EOS
-can't execute dotlockfile binary: #{dotlockfile}. Specify --with-dotlockfile
-if it's in a nonstandard location, or, if you want to live dangerously, try
---dont-use-dotlockfile
-EOS
- end
-
- modified_sources = []
- sources.each do |source|
- $stderr.puts "Scanning #{source}..."
-
- unless ((opts[:drop_deleted] || opts[:move_deleted]) && has_any_from_source_with_label?(index, source, :deleted)) || ((opts[:drop_spam] || opts[:move_spam]) && has_any_from_source_with_label?(index, source, :spam))
- $stderr.puts "Nothing to do from this source; skipping"
- next
- end
-
- source.reset!
- num_dropped = num_moved = num_scanned = 0
-
- out_fp = Tempfile.new "sup-sync-back-#{source.id}"
- Redwood::PollManager.each_message_from source do |m|
- num_scanned += 1
-
- if(m_old = index.build_message(m.id))
- labels = m_old.labels
-
- if labels.member? :deleted
- if opts[:drop_deleted]
- puts "Dropping deleted message #{source}##{m.source_info}" if opts[:verbose]
- num_dropped += 1
- elsif opts[:move_deleted] && labels.member?(:deleted)
- puts "Moving deleted message #{source}##{m.source_info}" if opts[:verbose]
- save m, deleted_fp unless opts[:dry_run]
- num_moved += 1
- end
-
- elsif labels.member? :spam
- if opts[:drop_spam]
- puts "Dropping spam message #{source}##{m.source_info}" if opts[:verbose]
- num_dropped += 1
- elsif opts[:move_spam] && labels.member?(:spam)
- puts "Moving spam message #{source}##{m.source_info}" if opts[:verbose]
- save m, spam_fp unless opts[:dry_run]
- num_moved += 1
- end
- else
- save m, out_fp unless opts[:dry_run]
- end
- else
- save m, out_fp unless opts[:dry_run]
- end
- end
- $stderr.puts "Scanned #{num_scanned}, dropped #{num_dropped}, moved #{num_moved} messages from #{source}."
- modified_sources << source if num_dropped > 0 || num_moved > 0
- out_fp.close unless opts[:dry_run]
-
- unless opts[:dry_run] || (num_dropped == 0 && num_moved == 0)
- deleted_fp.flush if deleted_fp
- spam_fp.flush if spam_fp
- unless opts[:dont_use_dotlockfile]
- puts "Locking #{source.file_path}..."
- system "#{opts[:dotlockfile]} -l #{source.file_path}"
- puts "Writing #{source.file_path}..."
- FileUtils.cp out_fp.path, source.file_path
- puts "Unlocking #{source.file_path}..."
- system "#{opts[:dotlockfile]} -u #{source.file_path}"
- end
- end
- end
-
- unless opts[:dry_run]
- deleted_fp.close if deleted_fp
- spam_fp.close if spam_fp
- end
-
- $stderr.puts "Done."
- unless modified_sources.empty?
- $stderr.puts "You should now run: sup-sync --changed #{modified_sources.join(' ')}"
- end
-rescue Exception => e
- File.open("sup-exception-log.txt", "w") { |f| f.puts e.backtrace }
- raise
-ensure
- Redwood::finish
- index.unlock
-end
diff --git a/bin/sup-sync-back-maildir b/bin/sup-sync-back-maildir
new file mode 100755
index 0000000..e3fa7b0
--- /dev/null
+++ b/bin/sup-sync-back-maildir
@@ -0,0 +1,127 @@
+#!/usr/bin/env ruby
+# encoding: utf-8
+
+$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
+
+require 'rubygems'
+require 'trollop'
+require "sup"
+
+opts = Trollop::options do
+ version "sup-sync-back-maildir (sup #{Redwood::VERSION})"
+ banner <<EOS
+Export Xapian entries to Maildir sources on disk.
+
+This script parses the Xapian entries for a given Maildir source and renames
+(changes maildir flags) e-mail files on disk according to the labels stored in
+the index. It will export all the changes you made in Sup to your
+Maildirs so that they can be propagated to your IMAP server with e.g. offlineimap.
+
+The script also merges some Maildir flags into Sup such
+as R (replied) and P (passed, forwarded), for instance suppose you
+have an e-mail file like this: foo_bar:2,FRS (flags are favorite,
+replied, seen) and its Xapian entry has labels 'starred', the merge
+operation will add the 'replied' label to the Xapian entry.
+
+If you choose not to merge (-m) you will lose information ('replied'), and in
+the previous example the file will be renamed to foo_bar:2,FS.
+
+Running this script is *strongly* recommended when setting the
+"sync_back_to_maildir" option from false to true in config.yaml or changing the
+"sync_back" flag to true for a source in sources.yaml.
+
+Usage:
+ sup-sync-back-maildir [options] <source>*
+
+where <source>* is source URIs. If no source is given, the default behavior is
+to sync back all Maildir sources marked as usual and that have not disabled
+sync back using the configuration parameter sync_back = false in sources.yaml.
+
+Options include:
+EOS
+ opt :no_confirm, "Don't ask for confirmation before synchronizing", :default => false, :short => "n"
+ opt :no_merge, "Don't merge new supported Maildir flags (R and P)", :default => false, :short => "m"
+ opt :list_sources, "List your Maildir sources and exit", :default => false, :short => "l"
+ opt :unusual_sources_too, "Sync unusual sources too if no specific source information is given", :default => false, :short => "u"
+end
+
+def die msg
+ $stderr.puts "Error: #{msg}"
+ exit(-1)
+end
+
+Redwood::start true
+index = Redwood::Index.init
+index.lock_interactively or exit
+index.load
+
+## Force sync_back_to_maildir option otherwise nothing will happen
+$config[:sync_back_to_maildir] = true
+
+begin
+ sync_performed = []
+ sync_performed = File.readlines(Redwood::SYNC_OK_FN).collect { |e| e.strip }.find_all { |e| not e.empty? } if File.exists? Redwood::SYNC_OK_FN
+ sources = []
+
+ ## Try to find out sources given in parameters
+ sources = ARGV.map do |uri|
+ s = Redwood::SourceManager.source_for(uri) or die "unknown source: #{uri}. Did you add it with sup-add first?"
+ s.is_a?(Redwood::Maildir) or die "#{uri} is not a Maildir source."
+ s.sync_back_enabled? or die "#{uri} has disabled sync back - check your configuration."
+ s
+ end unless opts[:list_sources]
+
+ ## Otherwise, check all sources in sources.yaml
+ if sources.empty? or opts[:list_sources] == true
+ if opts[:unusual_sources_too]
+ sources = Redwood::SourceManager.sources.select do |s|
+ s.is_a? Redwood::Maildir and s.sync_back_enabled?
+ end
+ else
+ sources = Redwood::SourceManager.usual_sources.select do |s|
+ s.is_a? Redwood::Maildir and s.sync_back_enabled?
+ end
+ end
+ end
+
+ if opts[:list_sources] == true
+ sources.each do |s|
+ puts "id: #{s.id}, uri: #{s.uri}"
+ end
+ else
+ sources.each do |s|
+ if opts[:no_confirm] == false
+ print "Are you sure you want to synchronize '#{s.uri}'? (Y/n) "
+ next if STDIN.gets.chomp.downcase == 'n'
+ end
+
+ infos = Enumerator.new(index, :each_source_info, s.id).to_a
+ counter = 0
+ infos.each do |info|
+ print "\rSynchronizing '#{s.uri}'... #{((counter += 1)/infos.size.to_f*100).to_i}%"
+ index.each_message({:location => [s.id, info]}, false) do |m|
+ if opts[:no_merge] == false
+ m.merge_labels_from_locations [:replied, :forwarded]
+ end
+
+ if Redwood::Index.message_joining_killed? m
+ m.labels += [:killed]
+ end
+
+ index.save_message m
+ end
+ end
+ print "\n"
+ sync_performed << s.uri
+ end
+ ## Write a flag file to tell sup that the synchronization has been performed
+ File.open(Redwood::SYNC_OK_FN, 'w') {|f| f.write(sync_performed.join("\n")) }
+ end
+rescue Exception => e
+ File.open("sup-exception-log.txt", "w") { |f| f.puts e.backtrace }
+ raise
+ensure
+ index.save_index
+ Redwood::finish
+ index.unlock
+end
diff --git a/bin/sup-tweak-labels b/bin/sup-tweak-labels
index 076c802..78e7991 100755
--- a/bin/sup-tweak-labels
+++ b/bin/sup-tweak-labels
@@ -1,8 +1,10 @@
#!/usr/bin/env ruby
+$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
+
require 'rubygems'
require 'trollop'
-require "sup"; Redwood::check_library_version_against "git"
+require "sup"
class Float
def to_s; sprintf '%.2f', self; end
@@ -49,6 +51,7 @@ EOS
opt :very_verbose, "Print message names and subjects as they're processed."
opt :all_sources, "Scan over all sources.", :short => :none
opt :dry_run, "Don't actually modify the index. Probably only useful with --verbose.", :short => "-n"
+ opt :no_sync_back, "Do not sync back to the original Maildir."
opt :version, "Show version information", :short => :none
end
opts[:verbose] = true if opts[:very_verbose]
@@ -61,6 +64,7 @@ Trollop::die "nothing to do: no labels to add or remove" if add_labels.empty? &&
Redwood::start
index = Redwood::Index.init
index.lock_interactively or exit
+
begin
index.load
@@ -83,7 +87,7 @@ begin
parsed_query = index.parse_query query
parsed_query.merge! :load_spam => true, :load_deleted => true, :load_killed => true
- ids = Enumerator.new(index, :each_id, parsed_query)
+ ids = index.to_enum(:each_id, parsed_query)
num_total = index.num_results_for parsed_query
$stderr.puts "Found #{num_total} documents across #{source_ids.length} sources. Scanning..."
@@ -104,7 +108,10 @@ begin
puts "From #{m.from}, subject: #{m.subj}" if opts[:very_verbose]
puts "#{m.id}: {#{old_labels.to_a.join ','}} => {#{m.labels.to_a.join ','}}" if opts[:verbose]
puts if opts[:very_verbose]
- index.update_message_state m unless opts[:dry_run]
+ unless opts[:dry_run]
+ index.update_message_state [m, false]
+ m.sync_back unless opts[:no_sync_back]
+ end
end
if Time.now - last_info_time > 60
diff --git a/bugs/issue-0240b36671ecb019e57ef27e0901bff055385371.yaml b/bugs/issue-0240b36671ecb019e57ef27e0901bff055385371.yaml
deleted file mode 100644
index e5565ce..0000000
--- a/bugs/issue-0240b36671ecb019e57ef27e0901bff055385371.yaml
+++ /dev/null
@@ -1,22 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: messages with unparseable date headers are being discarded entirely
-desc: it's better to forge the date headers and keep the messages
-type: :bugfix
-component: sup
-release: "0.5"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: fixed
-creation_time: 2008-03-07 03:57:25.399978 Z
-references: []
-
-id: 0240b36671ecb019e57ef27e0901bff055385371
-log_events:
-- - 2008-03-07 03:57:25.400014 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-03-07 03:57:29.249827 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - changed status from unstarted to fixed
- - ""
diff --git a/bugs/issue-08d6bae05fa885bf6fcae39f864eb923c1e9a79e.yaml b/bugs/issue-08d6bae05fa885bf6fcae39f864eb923c1e9a79e.yaml
deleted file mode 100644
index 3c4be72..0000000
--- a/bugs/issue-08d6bae05fa885bf6fcae39f864eb923c1e9a79e.yaml
+++ /dev/null
@@ -1,27 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: reply-from hook
-desc: "hook for setting the from: address of a reply programmatically"
-type: :feature
-component: hooks
-release: "0.6"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-06-19 17:58:26.142289 Z
-references: []
-
-id: 08d6bae05fa885bf6fcae39f864eb923c1e9a79e
-log_events:
-- - 2008-06-19 17:58:27.334371 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-06-19 17:58:40.526270 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - changed status from unstarted to in_progress
- - branch reply-from-hook, merged into next
-- - 2008-07-30 23:41:56.898257 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - closed with disposition fixed
- - merged into master
-git_branch: reply-from-hook
diff --git a/bugs/issue-09479a2ada22c2a0d76427e12ef2514d4753d070.yaml b/bugs/issue-09479a2ada22c2a0d76427e12ef2514d4753d070.yaml
deleted file mode 100644
index 94aa231..0000000
--- a/bugs/issue-09479a2ada22c2a0d76427e12ef2514d4753d070.yaml
+++ /dev/null
@@ -1,18 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: forwarded messages should be threaded under original
-desc: ""
-type: :feature
-component: threading
-release:
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :unstarted
-disposition:
-creation_time: 2008-06-07 23:45:11.606455 Z
-references: []
-
-id: 09479a2ada22c2a0d76427e12ef2514d4753d070
-log_events:
-- - 2008-06-07 23:45:12.746568 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
diff --git a/bugs/issue-15738247f939d20f8f202f80ccb85d9ad92101e0.yaml b/bugs/issue-15738247f939d20f8f202f80ccb85d9ad92101e0.yaml
deleted file mode 100644
index 3c780b9..0000000
--- a/bugs/issue-15738247f939d20f8f202f80ccb85d9ad92101e0.yaml
+++ /dev/null
@@ -1,18 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: command to reload hooks
-desc: useful for debugging hooks without having to restart sup each time
-type: :feature
-component: hooks
-release:
-reporter: William Morgan <wmorgan-ditz at masanjin.net>
-status: :unstarted
-disposition:
-creation_time: 2008-05-09 06:09:46.127686 Z
-references: []
-
-id: 15738247f939d20f8f202f80ccb85d9ad92101e0
-log_events:
-- - 2008-05-09 06:09:46.803222 Z
- - William Morgan <wmorgan-ditz at masanjin.net>
- - created
- - ""
diff --git a/bugs/issue-182841e15d6909892adf43678bae03597ce10519.yaml b/bugs/issue-182841e15d6909892adf43678bae03597ce10519.yaml
deleted file mode 100644
index b680b9d..0000000
--- a/bugs/issue-182841e15d6909892adf43678bae03597ce10519.yaml
+++ /dev/null
@@ -1,25 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: external mime viewing logic not quite right
-desc: |-
- weird things happen depending on the specifics on whether you have
- certain binaries in your path, or you implement the mime-view hook,
- you can get very weird behavior
-type: :bugfix
-component: hooks
-release: "0.5"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: fixed
-creation_time: 2008-03-07 04:05:53.956188 Z
-references: []
-
-id: 182841e15d6909892adf43678bae03597ce10519
-log_events:
-- - 2008-03-07 04:05:53.956222 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-03-07 04:06:02.239869 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - changed status from unstarted to fixed
- - ""
diff --git a/bugs/issue-1a1527438c2d198eae9a264ce9e6b847854d9837.yaml b/bugs/issue-1a1527438c2d198eae9a264ce9e6b847854d9837.yaml
deleted file mode 100644
index 87d0472..0000000
--- a/bugs/issue-1a1527438c2d198eae9a264ce9e6b847854d9837.yaml
+++ /dev/null
@@ -1,30 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: attachment name searchability
-desc: ""
-type: :feature
-component: indexing
-release: "0.6"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-05-25 03:53:44.524558 Z
-references: []
-
-id: 1a1527438c2d198eae9a264ce9e6b847854d9837
-log_events:
-- - 2008-05-25 03:53:45.177580 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-05-25 03:54:05.978412 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - changed status from unstarted to in_progress
- - branch attachments, merged into next
-- - 2008-05-31 17:09:04.254381 Z
- - William Morgan <wmorgan-ditz at masanjin.net>
- - commented
- - see {issue 65506670167642cc581956bc1b25c26b5bff215b} and {issue 7a68c1e7120a8540c7c51c6095f4815918d16641}
-- - 2008-06-19 18:20:41.656527 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - closed issue with disposition fixed
- - merged into master
diff --git a/bugs/issue-2312263b6a2b7de6ae1ec4ab315c7829763e61be.yaml b/bugs/issue-2312263b6a2b7de6ae1ec4ab315c7829763e61be.yaml
deleted file mode 100644
index 38364d9..0000000
--- a/bugs/issue-2312263b6a2b7de6ae1ec4ab315c7829763e61be.yaml
+++ /dev/null
@@ -1,22 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: sup-sync-back "nothing to do" error message not informative
-desc: should tell the user that one of the four magic options are required
-type: :bugfix
-component: sup-sync-back
-release: "0.5"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: fixed
-creation_time: 2008-03-07 03:37:41.693484 Z
-references: []
-
-id: 2312263b6a2b7de6ae1ec4ab315c7829763e61be
-log_events:
-- - 2008-03-07 03:37:41.693520 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-03-07 03:37:46.110644 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - changed status from unstarted to fixed
- - ""
diff --git a/bugs/issue-23658477a445c2e61405fecb4cb641a2298caba6.yaml b/bugs/issue-23658477a445c2e61405fecb4cb641a2298caba6.yaml
deleted file mode 100644
index 91e661a..0000000
--- a/bugs/issue-23658477a445c2e61405fecb4cb641a2298caba6.yaml
+++ /dev/null
@@ -1,27 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: wide character ncurses support
-desc: ""
-type: :feature
-component: curses
-release:
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :in_progress
-disposition:
-creation_time: 2008-04-22 22:43:23.153185 Z
-references: []
-
-id: 23658477a445c2e61405fecb4cb641a2298caba6
-log_events:
-- - 2008-04-22 22:43:24.808717 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-04-22 22:45:52.511820 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - changed status from unstarted to in_progress
- - |-
- Branch 'ncurses-widechar' has been merged into next.
-
- Branch "ncursesw" now has a copy of the ncurses 0.9.2 gem with wide character
- modifications, and a script "run-this-for-sup.sh" to build and install it
- (assuming you're running from git, of course.)
diff --git a/bugs/issue-2673f091c15dd90222a59621a1842d4ef0a743f7.yaml b/bugs/issue-2673f091c15dd90222a59621a1842d4ef0a743f7.yaml
deleted file mode 100644
index df14160..0000000
--- a/bugs/issue-2673f091c15dd90222a59621a1842d4ef0a743f7.yaml
+++ /dev/null
@@ -1,19 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: make sup-sync-back work on IMAP folders
-desc: ""
-type: :feature
-component: sup-sync-back
-release:
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :unstarted
-disposition:
-creation_time: 2008-10-14 01:14:08.690909 Z
-references: []
-
-id: 2673f091c15dd90222a59621a1842d4ef0a743f7
-log_events:
-- - 2008-10-14 01:14:09.898338 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-git_branch:
diff --git a/bugs/issue-2a0363cdf9d25edfa2a04b21299a538365e8b319.yaml b/bugs/issue-2a0363cdf9d25edfa2a04b21299a538365e8b319.yaml
deleted file mode 100644
index 8aab1df..0000000
--- a/bugs/issue-2a0363cdf9d25edfa2a04b21299a538365e8b319.yaml
+++ /dev/null
@@ -1,30 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: gpg mode hook
-desc: |-
- need a hook for controlling the default setting of the gpg mode (none,
- sign, sign & encrypt) in reply-mode, based on the gpg mode of the original
- message.
-type: :feature
-component: hooks
-release:
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :unstarted
-disposition:
-creation_time: 2008-03-07 02:42:29.391022 Z
-references: []
-
-id: 2a0363cdf9d25edfa2a04b21299a538365e8b319
-log_events:
-- - 2008-03-07 02:42:29.391058 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-04-20 21:18:14.263736 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - assigned to release 0.6 from 0.5
- - ""
-- - 2008-07-31 00:54:02.960978 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - unassigned from release 0.6
- - ""
-git_branch:
diff --git a/bugs/issue-2e74aa6843feee4daefe740b6e3f1fc54ff4bfcb.yaml b/bugs/issue-2e74aa6843feee4daefe740b6e3f1fc54ff4bfcb.yaml
deleted file mode 100644
index 87252e4..0000000
--- a/bugs/issue-2e74aa6843feee4daefe740b6e3f1fc54ff4bfcb.yaml
+++ /dev/null
@@ -1,22 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: oldest-first thread ordering
-desc: ""
-type: :feature
-component: indexing
-release:
-reporter: Matt Liggett <mml at pobox.com>
-status: :unstarted
-disposition:
-creation_time: 2008-03-14 18:33:01.603318 Z
-references:
-- http://rubyforge.org/pipermail/sup-talk/2008-March/001271.html
-id: 2e74aa6843feee4daefe740b6e3f1fc54ff4bfcb
-log_events:
-- - 2008-03-14 18:33:01.603569 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-03-14 18:33:31.116057 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - added reference 1
- - ""
diff --git a/bugs/issue-314f0cdac8d1998c46759a4ebef9077999bcef09.yaml b/bugs/issue-314f0cdac8d1998c46759a4ebef9077999bcef09.yaml
deleted file mode 100644
index 431a522..0000000
--- a/bugs/issue-314f0cdac8d1998c46759a4ebef9077999bcef09.yaml
+++ /dev/null
@@ -1,18 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: new user welcome screen the first time you start up
-desc: ""
-type: :feature
-component: curses
-release:
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :unstarted
-disposition:
-creation_time: 2008-03-07 02:44:24.399133 Z
-references: []
-
-id: 314f0cdac8d1998c46759a4ebef9077999bcef09
-log_events:
-- - 2008-03-07 02:44:24.399167 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
diff --git a/bugs/issue-3408c200a5f47f92d12b5c063a00ce891c2ba4ce.yaml b/bugs/issue-3408c200a5f47f92d12b5c063a00ce891c2ba4ce.yaml
deleted file mode 100644
index cde47bd..0000000
--- a/bugs/issue-3408c200a5f47f92d12b5c063a00ce891c2ba4ce.yaml
+++ /dev/null
@@ -1,22 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: "email header parsing: space doesn't need to follow colon"
-desc: ""
-type: :bugfix
-component: sup
-release: "0.5"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: fixed
-creation_time: 2008-03-07 04:31:55.733379 Z
-references: []
-
-id: 3408c200a5f47f92d12b5c063a00ce891c2ba4ce
-log_events:
-- - 2008-03-07 04:31:55.733416 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-03-07 04:31:59.580856 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - changed status from unstarted to fixed
- - ""
diff --git a/bugs/issue-3441fb8b7f955d625633d06fa0bf67a9afab046e.yaml b/bugs/issue-3441fb8b7f955d625633d06fa0bf67a9afab046e.yaml
deleted file mode 100644
index bd3c0fe..0000000
--- a/bugs/issue-3441fb8b7f955d625633d06fa0bf67a9afab046e.yaml
+++ /dev/null
@@ -1,18 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: saving a message as a draft drops attachments
-desc: ""
-type: :bugfix
-component: sup
-release:
-reporter: William Morgan <wmorgan-ditz at masanjin.net>
-status: :unstarted
-disposition:
-creation_time: 2008-05-25 02:14:13.362087 Z
-references: []
-
-id: 3441fb8b7f955d625633d06fa0bf67a9afab046e
-log_events:
-- - 2008-05-25 02:14:14.224040 Z
- - William Morgan <wmorgan-ditz at masanjin.net>
- - created
- - ""
diff --git a/bugs/issue-38d6f805b0c8bad013ec73f56e6245c890528591.yaml b/bugs/issue-38d6f805b0c8bad013ec73f56e6245c890528591.yaml
deleted file mode 100644
index 635bd65..0000000
--- a/bugs/issue-38d6f805b0c8bad013ec73f56e6245c890528591.yaml
+++ /dev/null
@@ -1,29 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: "'m' in edit-message-mode should prompt for a to: with a default"
-desc: |-
- the current behavior is to just go ahead and compose the message, which
- is irritating if you're just trying to compose a message incidental to
- having highlighted someone's email address.
-type: :bugfix
-component: curses
-release: "0.6"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-03-07 02:37:38.905689 Z
-references: []
-
-id: 38d6f805b0c8bad013ec73f56e6245c890528591
-log_events:
-- - 2008-03-07 02:37:38.905723 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-04-20 21:44:06.473431 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - assigned to release 0.6 from 0.5
- - ""
-- - 2008-05-25 03:47:42.600153 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - closed issue with disposition fixed
- - fixed in master
diff --git a/bugs/issue-3b25f1d56b9be533edaf232b9e60dc24e00cba0b.yaml b/bugs/issue-3b25f1d56b9be533edaf232b9e60dc24e00cba0b.yaml
deleted file mode 100644
index 07ca173..0000000
--- a/bugs/issue-3b25f1d56b9be533edaf232b9e60dc24e00cba0b.yaml
+++ /dev/null
@@ -1,26 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: maildir speedups
-desc: caching mtimes, using dir mtimes as an upper bound on file mtimes
-type: :feature
-component: maildir
-release: "0.6"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-05-25 02:30:42.010965 Z
-references: []
-
-id: 3b25f1d56b9be533edaf232b9e60dc24e00cba0b
-log_events:
-- - 2008-05-25 02:30:42.815974 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-05-25 02:30:59.062438 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - changed status from unstarted to in_progress
- - branch maildir-speedups. merged into next.
-- - 2008-06-19 18:09:27.239553 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - closed issue with disposition fixed
- - merged into master
diff --git a/bugs/issue-42ab0840f9a1924f1c0561e8ddcf7e6988543ba0.yaml b/bugs/issue-42ab0840f9a1924f1c0561e8ddcf7e6988543ba0.yaml
deleted file mode 100644
index ba95c00..0000000
--- a/bugs/issue-42ab0840f9a1924f1c0561e8ddcf7e6988543ba0.yaml
+++ /dev/null
@@ -1,26 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: query normalization breaking disjunctive queries, date modifers, etc
-desc: ""
-type: :bugfix
-component: indexing
-release: "0.5"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-03-07 03:39:26.683059 Z
-references: []
-
-id: 42ab0840f9a1924f1c0561e8ddcf7e6988543ba0
-log_events:
-- - 2008-03-07 03:39:26.683093 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-03-07 03:50:16.796313 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - changed status from unstarted to in_progress
- - ""
-- - 2008-04-20 22:10:39.075075 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - closed issue with disposition fixed
- - ""
diff --git a/bugs/issue-46df983ccdb75408a37b3911472d4015664a3cf6.yaml b/bugs/issue-46df983ccdb75408a37b3911472d4015664a3cf6.yaml
deleted file mode 100644
index 5358756..0000000
--- a/bugs/issue-46df983ccdb75408a37b3911472d4015664a3cf6.yaml
+++ /dev/null
@@ -1,20 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: curses interface generally sluggish
-desc: |-
- moving cursors around, etc is sluggish. should be faster. will require
- profiling. might require dipping into the C level.
-type: :bugfix
-component: curses
-release:
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :unstarted
-disposition:
-creation_time: 2008-05-19 23:38:35.608104 Z
-references: []
-
-id: 46df983ccdb75408a37b3911472d4015664a3cf6
-log_events:
-- - 2008-05-19 23:38:36.229747 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
diff --git a/bugs/issue-47aab6443b6c107c3067cdb614186099db570acf.yaml b/bugs/issue-47aab6443b6c107c3067cdb614186099db570acf.yaml
deleted file mode 100644
index ced024b..0000000
--- a/bugs/issue-47aab6443b6c107c3067cdb614186099db570acf.yaml
+++ /dev/null
@@ -1,37 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: before-add-hook not applied to sent messages
-desc: |-
- they're not being polled in the regular way but in a vestigal irregular
- way
-type: :bugfix
-component: sup
-release: "0.5"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: fixed
-creation_time: 2008-03-07 03:54:10.773413 Z
-references:
-- http://rubyforge.org/pipermail/sup-talk/2008-March/001259.html
-- http://rubyforge.org/pipermail/sup-talk/2008-February/001203.html
-id: 47aab6443b6c107c3067cdb614186099db570acf
-log_events:
-- - 2008-03-07 03:54:10.773449 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-03-07 03:54:15.699906 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - changed status from unstarted to in_progress
- - ""
-- - 2008-03-08 22:02:45.840451 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - changed status from in_progress to fixed
- - merged down to master.
-- - 2008-03-08 22:16:00.346710 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - added reference 1
- - ""
-- - 2008-03-08 22:22:02.528552 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - added reference 2
- - ""
diff --git a/bugs/issue-4af242013994ae557e431ba350a92c4f9e1739ef.yaml b/bugs/issue-4af242013994ae557e431ba350a92c4f9e1739ef.yaml
deleted file mode 100644
index db4b825..0000000
--- a/bugs/issue-4af242013994ae557e431ba350a92c4f9e1739ef.yaml
+++ /dev/null
@@ -1,28 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: sup-sync shouldn't save the index and sources if an error occurred
-desc: |-
- If the error was caused by a particular message, saving the source file
- will move the pointer past the message, so it will never get added.
-type: :bugfix
-component: sup-sync
-release: "0.5"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: fixed
-creation_time: 2008-03-07 04:14:07.913103 Z
-references: []
-
-id: 4af242013994ae557e431ba350a92c4f9e1739ef
-log_events:
-- - 2008-03-07 04:14:07.913140 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-03-07 04:14:12.599051 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - changed status from unstarted to fixed
- - ""
-- - 2008-03-09 18:29:51.789364 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - changed description
- - ""
diff --git a/bugs/issue-4daa2721dac8dfeb8730ee081f73b6c62702bd3e.yaml b/bugs/issue-4daa2721dac8dfeb8730ee081f73b6c62702bd3e.yaml
deleted file mode 100644
index 2761c60..0000000
--- a/bugs/issue-4daa2721dac8dfeb8730ee081f73b6c62702bd3e.yaml
+++ /dev/null
@@ -1,23 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: last message of every source is returned twice during polling
-desc: http://rubyforge.org/pipermail/sup-talk/2008-April/001358.html
-type: :bugfix
-component: sup
-release:
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :unstarted
-disposition:
-creation_time: 2008-05-01 01:11:50.650800 Z
-references: []
-
-id: 4daa2721dac8dfeb8730ee081f73b6c62702bd3e
-log_events:
-- - 2008-05-01 01:11:51.434589 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-07-31 00:54:39.589377 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - unassigned from release 0.6
- - ""
-git_branch:
diff --git a/bugs/issue-4e501973cea5bd1f28739ae4cea98edce8249895.yaml b/bugs/issue-4e501973cea5bd1f28739ae4cea98edce8249895.yaml
deleted file mode 100644
index 1c3d0a4..0000000
--- a/bugs/issue-4e501973cea5bd1f28739ae4cea98edce8249895.yaml
+++ /dev/null
@@ -1,32 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: thread joining is not preserved when reindexing
-desc: |
- the current thread joining just adds references to the index entries. that's
- fine but if the messages are reindexed, the references obviously won't be
- there.
- i think we need to add some separate blob of information somewhere that
- maintains these references, which sup-sync is aware of.
-
- if we're going down the bdb route for state preservation, that might be an
- obvious place to put this too, because it's essentially a hashtable keyed on
- message ids.
-
-type: :bugfix
-component: indexing
-release:
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :unstarted
-disposition:
-creation_time: 2008-03-14 18:39:59.697902 Z
-references:
-- http://rubyforge.org/pipermail/sup-talk/2008-March/001270.html
-id: 4e501973cea5bd1f28739ae4cea98edce8249895
-log_events:
-- - 2008-03-14 18:39:59.698163 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-03-14 18:40:26.559418 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - added reference 1
- - ""
diff --git a/bugs/issue-5348fec2b1112250e241afc7467de29e5691d1be.yaml b/bugs/issue-5348fec2b1112250e241afc7467de29e5691d1be.yaml
deleted file mode 100644
index 64d84a2..0000000
--- a/bugs/issue-5348fec2b1112250e241afc7467de29e5691d1be.yaml
+++ /dev/null
@@ -1,21 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: imap header caching
-desc: |-
- imap headers aren't cached at all. that would speed up the initial
- connection, at least for servers that didn't set uid_validity to
- the current time (blearf).
-type: :feature
-component: imap
-release:
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :unstarted
-disposition:
-creation_time: 2008-03-07 04:26:31.474463 Z
-references: []
-
-id: 5348fec2b1112250e241afc7467de29e5691d1be
-log_events:
-- - 2008-03-07 04:26:31.474497 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
diff --git a/bugs/issue-57668c69d0190d6e849309834d4ad1d215efa779.yaml b/bugs/issue-57668c69d0190d6e849309834d4ad1d215efa779.yaml
deleted file mode 100644
index fcb39c1..0000000
--- a/bugs/issue-57668c69d0190d6e849309834d4ad1d215efa779.yaml
+++ /dev/null
@@ -1,21 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: make sup-sync-back work on Maildir folders
-desc: |-
- possibly we could abstract things entirely between mbox and maildir, but
- it might just be easiest to have a sup-sync-back-maildir or a big if
- statement.
-type: :feature
-component: sup-sync-back
-release:
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :unstarted
-disposition:
-creation_time: 2008-06-12 19:24:51.772444 Z
-references: []
-
-id: 57668c69d0190d6e849309834d4ad1d215efa779
-log_events:
-- - 2008-06-12 19:24:53.668373 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
diff --git a/bugs/issue-5795c3c1b47e88f7261f57f31d33fe15ad08465d.yaml b/bugs/issue-5795c3c1b47e88f7261f57f31d33fe15ad08465d.yaml
deleted file mode 100644
index 8f774aa..0000000
--- a/bugs/issue-5795c3c1b47e88f7261f57f31d33fe15ad08465d.yaml
+++ /dev/null
@@ -1,20 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: flat (gmail-style) version of thread-view-mode
-desc: |-
- make thread-view-mode show a flat, chronological list of messages
- instead of a tree, based on some configuration variable.
-type: :feature
-component: curses
-release:
-reporter: William Morgan <wmorgan-ditz at masanjin.net>
-status: :unstarted
-disposition:
-creation_time: 2008-05-25 02:38:06.098950 Z
-references: []
-
-id: 5795c3c1b47e88f7261f57f31d33fe15ad08465d
-log_events:
-- - 2008-05-25 02:38:06.823848 Z
- - William Morgan <wmorgan-ditz at masanjin.net>
- - created
- - ""
diff --git a/bugs/issue-5fab957dcd16f1da8962fe5b1f3a58d970315deb.yaml b/bugs/issue-5fab957dcd16f1da8962fe5b1f3a58d970315deb.yaml
deleted file mode 100644
index 7c3c725..0000000
--- a/bugs/issue-5fab957dcd16f1da8962fe5b1f3a58d970315deb.yaml
+++ /dev/null
@@ -1,24 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: extra-contact-addresses hook for lbdb (etc.) integration
-desc: |-
- add an extra-contact-addresses hook for inserting addresses into the
- tab-completion list for To:, Cc:, etc. entries.
-type: :feature
-component: hooks
-release: "0.5"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-04-20 20:43:06.972853 Z
-references: []
-
-id: 5fab957dcd16f1da8962fe5b1f3a58d970315deb
-log_events:
-- - 2008-04-20 20:43:08.667355 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-04-20 20:43:20.678566 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - closed issue with disposition fixed
- - ""
diff --git a/bugs/issue-60d86dd32054533a6206f698033ec668af6a7574.yaml b/bugs/issue-60d86dd32054533a6206f698033ec668af6a7574.yaml
deleted file mode 100644
index 2830fff..0000000
--- a/bugs/issue-60d86dd32054533a6206f698033ec668af6a7574.yaml
+++ /dev/null
@@ -1,29 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: killed threads showing up in inbox-mode
-desc: this goddamn problem is recurring
-type: :bugfix
-component: indexing
-release:
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-04-25 19:28:51.369257 Z
-references: []
-
-id: 60d86dd32054533a6206f698033ec668af6a7574
-log_events:
-- - 2008-04-25 19:28:52.476687 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-07-31 00:54:38.916308 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - unassigned from release 0.6
- - ""
-- - 2008-11-21 14:23:17.566852 Z
- - Nicolas Pouillard <nicolas.pouillard at gmail.com>
- - closed with disposition fixed
- - |-
- Loading options was not given to load_thread_for_message in
- ThreadIndexMode.add_or_unhide.
-git_branch:
diff --git a/bugs/issue-61949ec83770b5d46f89eff21799968187012cce.yaml b/bugs/issue-61949ec83770b5d46f89eff21799968187012cce.yaml
deleted file mode 100644
index fb3df1a..0000000
--- a/bugs/issue-61949ec83770b5d46f89eff21799968187012cce.yaml
+++ /dev/null
@@ -1,22 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: \127 should be handled like backspace (it's the 70's all over again)
-desc: ""
-type: :bugfix
-component: curses
-release: "0.5"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: fixed
-creation_time: 2008-03-07 04:29:46.043812 Z
-references: []
-
-id: 61949ec83770b5d46f89eff21799968187012cce
-log_events:
-- - 2008-03-07 04:29:46.043850 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-03-07 04:29:52.233706 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - changed status from unstarted to fixed
- - ""
diff --git a/bugs/issue-65506670167642cc581956bc1b25c26b5bff215b.yaml b/bugs/issue-65506670167642cc581956bc1b25c26b5bff215b.yaml
deleted file mode 100644
index e43d6af..0000000
--- a/bugs/issue-65506670167642cc581956bc1b25c26b5bff215b.yaml
+++ /dev/null
@@ -1,30 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: attachment markers in thread-index-mode
-desc: show a little @ if the message has an attachment
-type: :feature
-component: curses
-release: "0.6"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-05-25 03:53:10.005404 Z
-references: []
-
-id: 65506670167642cc581956bc1b25c26b5bff215b
-log_events:
-- - 2008-05-25 03:53:10.994290 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-05-25 03:53:26.388023 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - changed status from unstarted to in_progress
- - branch attachments, merged into next
-- - 2008-05-31 17:09:17.710631 Z
- - William Morgan <wmorgan-ditz at masanjin.net>
- - commented
- - see {issue 1a1527438c2d198eae9a264ce9e6b847854d9837} and {issue 7a68c1e7120a8540c7c51c6095f4815918d16641}
-- - 2008-06-19 18:20:33.912937 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - closed issue with disposition fixed
- - ""
diff --git a/bugs/issue-658389418b5f0038cc3e6bc20fd3fd1566eb7111.yaml b/bugs/issue-658389418b5f0038cc3e6bc20fd3fd1566eb7111.yaml
deleted file mode 100644
index 26ce1e8..0000000
--- a/bugs/issue-658389418b5f0038cc3e6bc20fd3fd1566eb7111.yaml
+++ /dev/null
@@ -1,26 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: add a startup hook
-desc: ""
-type: :feature
-component: hooks
-release: "0.5"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-03-07 04:03:52.999956 Z
-references: []
-
-id: 658389418b5f0038cc3e6bc20fd3fd1566eb7111
-log_events:
-- - 2008-03-07 04:03:52.999992 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-03-07 04:03:56.880347 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - changed status from unstarted to in_progress
- - ""
-- - 2008-04-20 20:45:20.047253 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - closed issue with disposition fixed
- - ""
diff --git a/bugs/issue-69f785cddcc6e09ef0a357151373b3aa923d5e3f.yaml b/bugs/issue-69f785cddcc6e09ef0a357151373b3aa923d5e3f.yaml
deleted file mode 100644
index 6a6f488..0000000
--- a/bugs/issue-69f785cddcc6e09ef0a357151373b3aa923d5e3f.yaml
+++ /dev/null
@@ -1,22 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: escape filenames in call to run-mailcap
-desc: otherwise, filenames with spaces don't work
-type: :bugfix
-component: sup
-release: "0.5"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: fixed
-creation_time: 2008-03-07 04:10:43.771843 Z
-references: []
-
-id: 69f785cddcc6e09ef0a357151373b3aa923d5e3f
-log_events:
-- - 2008-03-07 04:10:43.771878 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-03-07 04:10:48.789189 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - changed status from unstarted to fixed
- - ""
diff --git a/bugs/issue-6c053cca2eb05af486a2d09c6772fd5bd0cca444.yaml b/bugs/issue-6c053cca2eb05af486a2d09c6772fd5bd0cca444.yaml
deleted file mode 100644
index bbdf1c6..0000000
--- a/bugs/issue-6c053cca2eb05af486a2d09c6772fd5bd0cca444.yaml
+++ /dev/null
@@ -1,20 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: cache threading
-desc: |-
- thread information should be cached so that it doesn't have to be
- recomputed each time.
-type: :feature
-component: threading
-release:
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :unstarted
-disposition:
-creation_time: 2008-05-19 23:39:19.716625 Z
-references: []
-
-id: 6c053cca2eb05af486a2d09c6772fd5bd0cca444
-log_events:
-- - 2008-05-19 23:39:20.190260 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
diff --git a/bugs/issue-6e0d634de74b2eb8297174ecd408b3810ba9351b.yaml b/bugs/issue-6e0d634de74b2eb8297174ecd408b3810ba9351b.yaml
deleted file mode 100644
index 564b3b1..0000000
--- a/bugs/issue-6e0d634de74b2eb8297174ecd408b3810ba9351b.yaml
+++ /dev/null
@@ -1,24 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: imap connection sharing
-desc: |
- current behavior is a separate connection for each folder, which is kinda
- silly.
- potentially want to still keep a separate connection for polling, but that
- might be micro-optimization, especially given that the whole ruby imap
- library seems quite slow.
-
-type: :feature
-component: imap
-release:
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :unstarted
-disposition:
-creation_time: 2008-03-07 04:25:12.351934 Z
-references: []
-
-id: 6e0d634de74b2eb8297174ecd408b3810ba9351b
-log_events:
-- - 2008-03-07 04:25:12.351966 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
diff --git a/bugs/issue-6e7960514f66ee67da083bc7bb5632d5808fc607.yaml b/bugs/issue-6e7960514f66ee67da083bc7bb5632d5808fc607.yaml
deleted file mode 100644
index 9656537..0000000
--- a/bugs/issue-6e7960514f66ee67da083bc7bb5632d5808fc607.yaml
+++ /dev/null
@@ -1,30 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: "'!!' will load all threads in current search"
-desc: |-
- can be dangerous, but sometimes you know there aren't a million and you
- just want them all loaded, e.g. to apply some mass tagging operation.
- with the cancel-search feature, can always be canceled if
- onerous.
-type: :feature
-component: sup
-release: "0.5"
-reporter: Marcus Williams <marcus-sup at bar-coded.net>
-status: :closed
-disposition: fixed
-creation_time: 2008-03-07 04:17:44.706909 Z
-references: []
-
-id: 6e7960514f66ee67da083bc7bb5632d5808fc607
-log_events:
-- - 2008-03-07 04:17:44.706948 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-03-07 04:17:48.834972 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - changed status from unstarted to fixed
- - ""
-- - 2008-03-11 06:52:15.604233 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - changed description
- - ""
diff --git a/bugs/issue-7456c2d8fbd5de4dac651f6f4e9756f577497e01.yaml b/bugs/issue-7456c2d8fbd5de4dac651f6f4e9756f577497e01.yaml
deleted file mode 100644
index 915eb68..0000000
--- a/bugs/issue-7456c2d8fbd5de4dac651f6f4e9756f577497e01.yaml
+++ /dev/null
@@ -1,26 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: edit-as-new doesn't preserve replyto and references headers
-desc: ""
-type: :bugfix
-component: sup
-release: "0.6"
-reporter: William Morgan <wmorgan-ditz at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-05-31 17:07:05.126884 Z
-references: []
-
-id: 7456c2d8fbd5de4dac651f6f4e9756f577497e01
-log_events:
-- - 2008-05-31 17:07:07.008637 Z
- - William Morgan <wmorgan-ditz at masanjin.net>
- - created
- - ""
-- - 2008-05-31 17:07:22.611383 Z
- - William Morgan <wmorgan-ditz at masanjin.net>
- - changed status from unstarted to in_progress
- - in branch edit-as-new-fix, merged into next
-- - 2008-06-19 18:22:24.036557 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - closed issue with disposition fixed
- - merged into master
diff --git a/bugs/issue-76802330c4fdd091e8b1dd08dcc29ed432f003d4.yaml b/bugs/issue-76802330c4fdd091e8b1dd08dcc29ed432f003d4.yaml
deleted file mode 100644
index e219937..0000000
--- a/bugs/issue-76802330c4fdd091e8b1dd08dcc29ed432f003d4.yaml
+++ /dev/null
@@ -1,26 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: in-buffer search always shifts the screen, even when unnecessary
-desc: ""
-type: :bugfix
-component: curses
-release: "0.6"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-05-01 22:34:01.983057 Z
-references: []
-
-id: 76802330c4fdd091e8b1dd08dcc29ed432f003d4
-log_events:
-- - 2008-05-01 22:34:02.958758 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-05-01 22:34:24.254778 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - changed status from unstarted to in_progress
- - branch find-in-buffer-fix, merged into next
-- - 2008-05-25 04:19:45.168367 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - closed issue with disposition fixed
- - merged into master
diff --git a/bugs/issue-799771a6a435dcad66dc80e7e051d91d24d005b1.yaml b/bugs/issue-799771a6a435dcad66dc80e7e051d91d24d005b1.yaml
deleted file mode 100644
index f1f2397..0000000
--- a/bugs/issue-799771a6a435dcad66dc80e7e051d91d24d005b1.yaml
+++ /dev/null
@@ -1,31 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: IMAP server restart crashes sup
-desc: |-
- this very bizarre backtrace:
-
- --- SystemExit from thread: main
- closed stream
- /usr/lib/ruby/1.8/openssl/buffering.rb:237:in `select'
- ./lib/sup/buffer.rb:31:in `nonblocking_getch'
- bin/sup:227
-
- wtf?
-
- There's no reason that
- nonblocking_getch would be calling the openssl stuff, and openssl's
- buffering.rb doesn't mention select at all. Weird.
-type: :bugfix
-component: imap
-release:
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :unstarted
-disposition:
-creation_time: 2008-05-19 23:17:32.271870 Z
-references: []
-
-id: 799771a6a435dcad66dc80e7e051d91d24d005b1
-log_events:
-- - 2008-05-19 23:17:33.615525 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
diff --git a/bugs/issue-7a68c1e7120a8540c7c51c6095f4815918d16641.yaml b/bugs/issue-7a68c1e7120a8540c7c51c6095f4815918d16641.yaml
deleted file mode 100644
index 3892dc7..0000000
--- a/bugs/issue-7a68c1e7120a8540c7c51c6095f4815918d16641.yaml
+++ /dev/null
@@ -1,28 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: attachment markers in thread-view-mode
-desc: |-
- i'd like to see them in thread-view-mode as well
- (c.f. {issue 65506670167642cc581956bc1b25c26b5bff215b} and {issue 1a1527438c2d198eae9a264ce9e6b847854d9837})
-type: :feature
-component: curses
-release: "0.6"
-reporter: William Morgan <wmorgan-ditz at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-05-31 17:08:28.081944 Z
-references: []
-
-id: 7a68c1e7120a8540c7c51c6095f4815918d16641
-log_events:
-- - 2008-05-31 17:08:28.710190 Z
- - William Morgan <wmorgan-ditz at masanjin.net>
- - created
- - ""
-- - 2008-05-31 17:08:50.679595 Z
- - William Morgan <wmorgan-ditz at masanjin.net>
- - changed status from unstarted to in_progress
- - on branch attachments as well, remerged into next
-- - 2008-06-19 18:20:56.657318 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - closed issue with disposition fixed
- - merged into master
diff --git a/bugs/issue-7c77e757321c2639daea013824ad1a14099815b1.yaml b/bugs/issue-7c77e757321c2639daea013824ad1a14099815b1.yaml
deleted file mode 100644
index ecc1ae8..0000000
--- a/bugs/issue-7c77e757321c2639daea013824ad1a14099815b1.yaml
+++ /dev/null
@@ -1,26 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: in-buffer searches should move buffer horizontally when necessary
-desc: ""
-type: :feature
-component: curses
-release: "0.5"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-03-07 03:56:17.653639 Z
-references: []
-
-id: 7c77e757321c2639daea013824ad1a14099815b1
-log_events:
-- - 2008-03-07 03:56:17.653674 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-03-07 03:56:21.870750 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - changed status from unstarted to in_progress
- - ""
-- - 2008-04-20 20:44:07.668281 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - closed issue with disposition fixed
- - ""
diff --git a/bugs/issue-7d8474dfeeefaa50151c3ce48bee6b686d36a216.yaml b/bugs/issue-7d8474dfeeefaa50151c3ce48bee6b686d36a216.yaml
deleted file mode 100644
index a2906d8..0000000
--- a/bugs/issue-7d8474dfeeefaa50151c3ce48bee6b686d36a216.yaml
+++ /dev/null
@@ -1,29 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: reply-to mode hook
-desc: |-
- need a hook for selecting the default setting of the reply-to
- horizontal selector
-type: :feature
-component: hooks
-release: "0.6"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-06-04 03:04:10.253690 Z
-references: []
-
-id: 7d8474dfeeefaa50151c3ce48bee6b686d36a216
-log_events:
-- - 2008-06-04 03:04:10.945071 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-06-04 03:04:21.284329 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - changed status from unstarted to in_progress
- - branch 'reply-to-hook', merged into next
-- - 2008-07-30 23:41:50.393799 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - closed with disposition fixed
- - merged into master
-git_branch: reply-to-hook
diff --git a/bugs/issue-829b449c51fca9a39047d00fabc552cc110c69b2.yaml b/bugs/issue-829b449c51fca9a39047d00fabc552cc110c69b2.yaml
deleted file mode 100644
index e17e213..0000000
--- a/bugs/issue-829b449c51fca9a39047d00fabc552cc110c69b2.yaml
+++ /dev/null
@@ -1,24 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: don't crash when people.txt is corrupted
-desc: |-
- This was a debug check, but if Sup crashes when writing out people.txt,
- this will prevent it from ever starting again!
-type: :bugfix
-component: sup
-release: "0.5"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: fixed
-creation_time: 2008-03-07 04:07:56.208521 Z
-references: []
-
-id: 829b449c51fca9a39047d00fabc552cc110c69b2
-log_events:
-- - 2008-03-07 04:07:56.208558 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-03-07 04:21:04.553157 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - changed status from unstarted to fixed
- - ""
diff --git a/bugs/issue-82c80f6dc2ce7b10b9e8f503d68253ced0ee8a1b.yaml b/bugs/issue-82c80f6dc2ce7b10b9e8f503d68253ced0ee8a1b.yaml
deleted file mode 100644
index 86be9e0..0000000
--- a/bugs/issue-82c80f6dc2ce7b10b9e8f503d68253ced0ee8a1b.yaml
+++ /dev/null
@@ -1,24 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: gpg generates invalid signature when :edit_signature is on
-desc: |-
- when :edit_signature is on and there's a signature, gpg signatures are invalid,
- ccording to mutt and other clients. sup itself thinks they're fine.
-type: :bugfix
-component: crypto
-release: "0.6"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-05-19 21:19:23.267668 Z
-references: []
-
-id: 82c80f6dc2ce7b10b9e8f503d68253ced0ee8a1b
-log_events:
-- - 2008-05-19 21:19:24.611826 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-05-19 21:19:49.649415 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - closed issue with disposition fixed
- - turns out it was a newline issue. patch directly applied to master.
diff --git a/bugs/issue-8a5cf9242ca60fa6c81091e425f734b4fb03e41a.yaml b/bugs/issue-8a5cf9242ca60fa6c81091e425f734b4fb03e41a.yaml
deleted file mode 100644
index b91de0d..0000000
--- a/bugs/issue-8a5cf9242ca60fa6c81091e425f734b4fb03e41a.yaml
+++ /dev/null
@@ -1,53 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: index speedup
-desc: |
- I've just merged in a changeset that makes Sup store message body
- content in the Ferret index. (They've always been indexed, but now
- they're stored as well.) This means that changing the labels on a
- message can be a copy operation of the previous Ferret document, rather
- than requiring downloading and parsing the original message to create a
- new Ferret document.
- So, this should have two effects:
-
- 1. The Ferret index size will expand by about 50%. Sorry.
- 2. Tweaking message labels should be much, much faster, since the
- message no longer has to be downloaded from the source in order to
- change the labels. If you've ever tried to label a large IMAP thread,
- you no longer have to wait 5 minutes just to save. :)
-
- The index size increase is unfortunate, but it's something that has to
- happen anyways if we want search-results-mode to have matching text in
- the snippets, which is in the future TODO.
-
- The change was made in such a way that it's incrementally applied
- whenever a message is saved or changed in the Ferret index. So, if you
- want the above behavior on all messages immediately, you must do
- sup-sync --all on a source (which will require downloading each
- message). Otherwise, you will get the slow behavior (message body needs
- to be downloaded from the source) the first time you save a message
- after merging this change, and the fast behavior (no downloading
- required) on all subsequent times.
-
-type: :feature
-component: indexing
-release: "0.5"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-03-07 02:48:23.885656 Z
-references: []
-
-id: 8a5cf9242ca60fa6c81091e425f734b4fb03e41a
-log_events:
-- - 2008-03-07 02:48:23.885693 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-03-07 02:48:50.979828 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - changed status from unstarted to in_progress
- - ""
-- - 2008-04-20 22:10:33.970635 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - closed issue with disposition fixed
- - ""
diff --git a/bugs/issue-8aa7ea95f066fd0668452093b85903bd142905c9.yaml b/bugs/issue-8aa7ea95f066fd0668452093b85903bd142905c9.yaml
deleted file mode 100644
index 59c41c0..0000000
--- a/bugs/issue-8aa7ea95f066fd0668452093b85903bd142905c9.yaml
+++ /dev/null
@@ -1,26 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: "'q' asks and 'Q' quits without asking"
-desc: ""
-type: :feature
-component: curses
-release: "0.6"
-reporter: William Morgan <wmorgan-ditz at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-05-20 03:25:17.109472 Z
-references: []
-
-id: 8aa7ea95f066fd0668452093b85903bd142905c9
-log_events:
-- - 2008-05-20 03:25:19.265580 Z
- - William Morgan <wmorgan-ditz at masanjin.net>
- - created
- - ""
-- - 2008-05-25 02:13:32.219668 Z
- - William Morgan <wmorgan-ditz at masanjin.net>
- - closed issue with disposition fixed
- - fixed in master
-- - 2008-05-25 02:13:40.344453 Z
- - William Morgan <wmorgan-ditz at masanjin.net>
- - assigned to release 0.6 from unassigned
- - ""
diff --git a/bugs/issue-8c0e627c500f679badca28f60ba76998fd65d46a.yaml b/bugs/issue-8c0e627c500f679badca28f60ba76998fd65d46a.yaml
deleted file mode 100644
index f31067e..0000000
--- a/bugs/issue-8c0e627c500f679badca28f60ba76998fd65d46a.yaml
+++ /dev/null
@@ -1,26 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: workaround for rubymail quoting bug in pgp MIME header
-desc: gpg MIME headers are being double-quoted due to a rubymail bug
-type: :bugfix
-component: sup
-release: "0.5"
-reporter: Jan Spakula <teatime at gmx.com>
-status: :closed
-disposition: fixed
-creation_time: 2008-03-07 03:35:36.731751 Z
-references:
-- http://rubyforge.org/pipermail/sup-talk/2008-February/001222.html
-id: 8c0e627c500f679badca28f60ba76998fd65d46a
-log_events:
-- - 2008-03-07 03:35:36.731787 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-03-07 03:36:24.938159 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - changed status from unstarted to fixed
- - ""
-- - 2008-03-08 22:22:59.515414 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - added reference 1
- - ""
diff --git a/bugs/issue-8e825caee33a6ac144580bf44d0d3060ad162394.yaml b/bugs/issue-8e825caee33a6ac144580bf44d0d3060ad162394.yaml
deleted file mode 100644
index 77a3ce8..0000000
--- a/bugs/issue-8e825caee33a6ac144580bf44d0d3060ad162394.yaml
+++ /dev/null
@@ -1,21 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: From lines detected over-aggressively
-desc: |-
- mbox lines starting with "From " should only be considered new-message
- delimiters if they have a valid email address, date, etc.
-type: :bugfix
-component: mbox
-release:
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :unstarted
-disposition:
-creation_time: 2008-11-10 03:56:07.002467 Z
-references: []
-
-id: 8e825caee33a6ac144580bf44d0d3060ad162394
-log_events:
-- - 2008-11-10 03:56:07.002940 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-git_branch:
diff --git a/bugs/issue-91e1549102c0bfa2c201476d9618f7d234d1a626.yaml b/bugs/issue-91e1549102c0bfa2c201476d9618f7d234d1a626.yaml
deleted file mode 100644
index ae6d02a..0000000
--- a/bugs/issue-91e1549102c0bfa2c201476d9618f7d234d1a626.yaml
+++ /dev/null
@@ -1,22 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: gpg should use exact match for email address
-desc: otherwise substring matches can select the wrong key
-type: :bugfix
-component: crypto
-release: "0.6"
-reporter: William Morgan <wmorgan-ditz at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-05-25 02:15:58.006265 Z
-references: []
-
-id: 91e1549102c0bfa2c201476d9618f7d234d1a626
-log_events:
-- - 2008-05-25 02:15:59.100203 Z
- - William Morgan <wmorgan-ditz at masanjin.net>
- - created
- - ""
-- - 2008-05-25 02:16:07.730483 Z
- - William Morgan <wmorgan-ditz at masanjin.net>
- - closed issue with disposition fixed
- - fixed in master
diff --git a/bugs/issue-9f7e28de46d74f7f1e445ae75ea4e230c7473374.yaml b/bugs/issue-9f7e28de46d74f7f1e445ae75ea4e230c7473374.yaml
deleted file mode 100644
index e711eea..0000000
--- a/bugs/issue-9f7e28de46d74f7f1e445ae75ea4e230c7473374.yaml
+++ /dev/null
@@ -1,22 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: threads with unsent draft messages are now shown in red
-desc: ""
-type: :feature
-component: curses
-release: "0.5"
-reporter: Nicolas Pouillard <nicolas.pouillard at gmail.com>
-status: :closed
-disposition: fixed
-creation_time: 2008-03-07 04:30:53.909487 Z
-references: []
-
-id: 9f7e28de46d74f7f1e445ae75ea4e230c7473374
-log_events:
-- - 2008-03-07 04:30:53.909522 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-03-07 04:31:03.704713 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - changed status from unstarted to fixed
- - ""
diff --git a/bugs/issue-a1a3427de5e8d4f74c0620f99e97ed92d21e924c.yaml b/bugs/issue-a1a3427de5e8d4f74c0620f99e97ed92d21e924c.yaml
deleted file mode 100644
index 7c30f15..0000000
--- a/bugs/issue-a1a3427de5e8d4f74c0620f99e97ed92d21e924c.yaml
+++ /dev/null
@@ -1,30 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: sup-config name guessing logic can generate nil and crash
-desc: ""
-type: :bugfix
-component: sup
-release: "0.5"
-reporter: Jean-Hadrien CHABRAN <jh at chabran.fr>
-status: :closed
-disposition: fixed
-creation_time: 2008-03-09 17:45:46.095924 Z
-references:
-- http://rubyforge.org/pipermail/sup-talk/2008-March/001260.html
-id: a1a3427de5e8d4f74c0620f99e97ed92d21e924c
-log_events:
-- - 2008-03-09 17:45:46.095961 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-03-09 17:46:21.702965 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - added reference 1
- - ""
-- - 2008-03-09 17:46:45.446763 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - changed reporter
- - ""
-- - 2008-03-09 17:47:06.797832 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - changed status from unstarted to fixed
- - fixed in master
diff --git a/bugs/issue-a1e622dbae0e1841b4d9a376d419aed1d91460e0.yaml b/bugs/issue-a1e622dbae0e1841b4d9a376d419aed1d91460e0.yaml
deleted file mode 100644
index aa6e31f..0000000
--- a/bugs/issue-a1e622dbae0e1841b4d9a376d419aed1d91460e0.yaml
+++ /dev/null
@@ -1,26 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: non-ascii characters in message id need to be normalized
-desc: apparently this happens. in spam email, of course.
-type: :bugfix
-component: sup
-release: "0.6"
-reporter: William Morgan <wmorgan-ditz at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-05-11 23:04:01.078305 Z
-references: []
-
-id: a1e622dbae0e1841b4d9a376d419aed1d91460e0
-log_events:
-- - 2008-05-11 23:04:01.677838 Z
- - William Morgan <wmorgan-ditz at masanjin.net>
- - created
- - ""
-- - 2008-05-11 23:21:40.281018 Z
- - William Morgan <wmorgan-ditz at masanjin.net>
- - changed status from unstarted to in_progress
- - branch non-ascii-message-id, merged into next
-- - 2008-06-19 18:09:04.143173 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - closed issue with disposition fixed
- - merged into master
diff --git a/bugs/issue-a533480a30a18c3e823dbe20b759e1dcb32ca2b9.yaml b/bugs/issue-a533480a30a18c3e823dbe20b759e1dcb32ca2b9.yaml
deleted file mode 100644
index 2021c6f..0000000
--- a/bugs/issue-a533480a30a18c3e823dbe20b759e1dcb32ca2b9.yaml
+++ /dev/null
@@ -1,26 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: forward/reply without saving in the editor drops all newlines
-desc: ""
-type: :bugfix
-component: sup
-release: "0.6"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-04-23 01:31:16.072859 Z
-references: []
-
-id: a533480a30a18c3e823dbe20b759e1dcb32ca2b9
-log_events:
-- - 2008-04-23 01:31:16.640737 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-04-23 01:39:10.304801 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - changed status from unstarted to in_progress
- - branch unedited-newlines, merged into next
-- - 2008-05-25 04:22:44.178693 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - closed issue with disposition fixed
- - merged into master
diff --git a/bugs/issue-a68148169baa3838051f4bdb4c175e11cbf7f143.yaml b/bugs/issue-a68148169baa3838051f4bdb4c175e11cbf7f143.yaml
deleted file mode 100644
index e4ce8bf..0000000
--- a/bugs/issue-a68148169baa3838051f4bdb4c175e11cbf7f143.yaml
+++ /dev/null
@@ -1,22 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: all ferret access needs to be wrapped in a mutex
-desc: |-
- concurrent access breaks things. at least, that's what I *think* is
- going on here.
-
- http://rubyforge.org/pipermail/sup-talk/2008-April/001333.html
-type: :bugfix
-component: indexing
-release:
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :unstarted
-disposition:
-creation_time: 2008-05-01 01:09:39.706808 Z
-references: []
-
-id: a68148169baa3838051f4bdb4c175e11cbf7f143
-log_events:
-- - 2008-05-01 01:09:40.747646 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
diff --git a/bugs/issue-aae5ae6378afa9bd2a8e1b15d28ba7ccef867791.yaml b/bugs/issue-aae5ae6378afa9bd2a8e1b15d28ba7ccef867791.yaml
deleted file mode 100644
index 83cc00a..0000000
--- a/bugs/issue-aae5ae6378afa9bd2a8e1b15d28ba7ccef867791.yaml
+++ /dev/null
@@ -1,27 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: remove email->name mapping
-desc: it doesn't work and wouldn't buy that much even if it did
-type: :bugfix
-component: sup
-release:
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-05-19 23:42:25.910550 Z
-references: []
-
-id: aae5ae6378afa9bd2a8e1b15d28ba7ccef867791
-log_events:
-- - 2008-05-19 23:42:26.490587 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-07-31 00:54:39.921596 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - unassigned from release 0.6
- - ""
-- - 2008-11-22 16:31:27.450146 Z
- - Nicolas Pouillard <nicolas.pouillard at gmail.com>
- - closed with disposition fixed
- - This mapping and the PersonManager are now removed.
-git_branch:
diff --git a/bugs/issue-ad82aa00f4064fc7e1332cee0dae2c2ae95bb217.yaml b/bugs/issue-ad82aa00f4064fc7e1332cee0dae2c2ae95bb217.yaml
deleted file mode 100644
index 1802a8a..0000000
--- a/bugs/issue-ad82aa00f4064fc7e1332cee0dae2c2ae95bb217.yaml
+++ /dev/null
@@ -1,26 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: add more vi keys
-desc: ""
-type: :feature
-component: curses
-release: "0.6"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-04-26 22:23:43.675951 Z
-references: []
-
-id: ad82aa00f4064fc7e1332cee0dae2c2ae95bb217
-log_events:
-- - 2008-04-26 22:23:44.484689 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-04-26 22:24:00.893661 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - changed status from unstarted to in_progress
- - branch more-vi-keys. in next.
-- - 2008-05-25 04:12:57.577438 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - closed issue with disposition fixed
- - merged into master
diff --git a/bugs/issue-b1f1579fd8350d8add15c5cb588169acfdc5ea24.yaml b/bugs/issue-b1f1579fd8350d8add15c5cb588169acfdc5ea24.yaml
deleted file mode 100644
index 138772c..0000000
--- a/bugs/issue-b1f1579fd8350d8add15c5cb588169acfdc5ea24.yaml
+++ /dev/null
@@ -1,29 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: offer to delete lockfile after trying to kill owner process
-desc: |-
- often the lockfile points to a dead process, so repeatedly offering to kill
- it isn't all that useful.
-type: :feature
-component: sup
-release:
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :unstarted
-disposition:
-creation_time: 2008-04-14 01:25:59.111165 Z
-references: []
-
-id: b1f1579fd8350d8add15c5cb588169acfdc5ea24
-log_events:
-- - 2008-04-14 01:26:00.135062 Z
- - William Morgan <w at adap.tv>
- - created
- - ""
-- - 2008-05-19 23:40:28.102694 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - assigned to release 0.6 from unassigned
- - ""
-- - 2008-07-31 00:54:38.573917 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - unassigned from release 0.6
- - ""
-git_branch:
diff --git a/bugs/issue-b80aa39ef3b8d33bd57e4988c55d89c7c0df5c96.yaml b/bugs/issue-b80aa39ef3b8d33bd57e4988c55d89c7c0df5c96.yaml
deleted file mode 100644
index f293a90..0000000
--- a/bugs/issue-b80aa39ef3b8d33bd57e4988c55d89c7c0df5c96.yaml
+++ /dev/null
@@ -1,24 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: spurious messages appearing in inbox
-desc: |-
- this is because ThreadSet is claiming that non-relevant videos are actually
- relevant
-type: :bugfix
-component: indexing
-release: "0.5"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: fixed
-creation_time: 2008-03-07 03:55:29.353904 Z
-references: []
-
-id: b80aa39ef3b8d33bd57e4988c55d89c7c0df5c96
-log_events:
-- - 2008-03-07 03:55:29.353940 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-03-07 03:55:34.495965 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - changed status from unstarted to fixed
- - ""
diff --git a/bugs/issue-bc03bc702f41e6a9687b52d3e32db29132c0f65a.yaml b/bugs/issue-bc03bc702f41e6a9687b52d3e32db29132c0f65a.yaml
deleted file mode 100644
index 2572ce4..0000000
--- a/bugs/issue-bc03bc702f41e6a9687b52d3e32db29132c0f65a.yaml
+++ /dev/null
@@ -1,25 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: add a mark-as-spam hook
-desc: |-
- a simple hook that triggers when a message is marked as spam, so that
- users can trigger additional stuff.
-type: :feature
-component: hooks
-release: "0.6"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-07-31 00:50:51.282526 Z
-references: []
-
-id: bc03bc702f41e6a9687b52d3e32db29132c0f65a
-log_events:
-- - 2008-07-31 00:50:52.114135 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-07-31 00:51:10.671706 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - closed with disposition fixed
- - branch mark-as-spam-hook, merged into master
-git_branch:
diff --git a/bugs/issue-bdd4415a9d4c8fd3602500111bf9268aa7c7c6a4.yaml b/bugs/issue-bdd4415a9d4c8fd3602500111bf9268aa7c7c6a4.yaml
deleted file mode 100644
index b8fc2ad..0000000
--- a/bugs/issue-bdd4415a9d4c8fd3602500111bf9268aa7c7c6a4.yaml
+++ /dev/null
@@ -1,27 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: configurable colors
-desc: All colors should be user-configurable.
-type: :feature
-component: curses
-release: "0.6"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-06-04 02:27:15.458560 Z
-references: []
-
-id: bdd4415a9d4c8fd3602500111bf9268aa7c7c6a4
-log_events:
-- - 2008-06-04 02:27:16.721829 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-06-04 02:27:27.256556 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - changed status from unstarted to in_progress
- - branch 'colors', merged into next.
-- - 2008-07-30 23:41:33.553377 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - closed with disposition fixed
- - merged into master
-git_branch: color
diff --git a/bugs/issue-bff2527210b3aacae2f74029e5856fed82f1689c.yaml b/bugs/issue-bff2527210b3aacae2f74029e5856fed82f1689c.yaml
deleted file mode 100644
index 802f1b0..0000000
--- a/bugs/issue-bff2527210b3aacae2f74029e5856fed82f1689c.yaml
+++ /dev/null
@@ -1,33 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: wide characters break screen clearing
-desc: |-
- if you look at a message with wide characters using the new wide-char-aware
- ncurses library, there will often be snippets of the previous screen
- immediately to the right of the end of the lines that have wide characters
- in them.
-
- some kind of line length issue maybe? (because everything is done in terms
- of bytes still. thanks ruby!)
-type: :bugfix
-component: curses
-release:
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :wontfix
-creation_time: 2008-04-26 21:35:31.519359 Z
-references: []
-
-id: bff2527210b3aacae2f74029e5856fed82f1689c
-log_events:
-- - 2008-04-26 21:35:32.384516 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-04-26 21:44:04.152193 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - unassigned from release 0.6
- - depends on {issue 23658477a445c2e61405fecb4cb641a2298caba6} and that's not necessarily destined for 0.6 yet.
-- - 2008-04-28 02:36:51.698817 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - closed issue with disposition wontfix
- - dup of {issue c48f7fc58bba0b38ff6ae14cca01b08a5a7a6c33}. you'd think i'd'a remembered.
diff --git a/bugs/issue-c48f7fc58bba0b38ff6ae14cca01b08a5a7a6c33.yaml b/bugs/issue-c48f7fc58bba0b38ff6ae14cca01b08a5a7a6c33.yaml
deleted file mode 100644
index b15f04a..0000000
--- a/bugs/issue-c48f7fc58bba0b38ff6ae14cca01b08a5a7a6c33.yaml
+++ /dev/null
@@ -1,21 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: wide characters screw up line cursor display
-desc: |-
- in a message with wide characters, the screen isn't cleared properly, or
- something. probably due to the # of characters for something being calculated
- wrong (bytes instead of chars).
-type: :bugfix
-component: curses
-release:
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :unstarted
-disposition:
-creation_time: 2008-04-23 02:05:47.272610 Z
-references: []
-
-id: c48f7fc58bba0b38ff6ae14cca01b08a5a7a6c33
-log_events:
-- - 2008-04-23 02:05:49.360399 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
diff --git a/bugs/issue-c52f9762bc24a8f45863eb2e7beefa4201db34e8.yaml b/bugs/issue-c52f9762bc24a8f45863eb2e7beefa4201db34e8.yaml
deleted file mode 100644
index b5b573f..0000000
--- a/bugs/issue-c52f9762bc24a8f45863eb2e7beefa4201db34e8.yaml
+++ /dev/null
@@ -1,22 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: add a --compose option to spawn a compose-message buffer on startup
-desc: ""
-type: :feature
-component: sup
-release: "0.5"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: fixed
-creation_time: 2008-03-07 04:28:32.198492 Z
-references: []
-
-id: c52f9762bc24a8f45863eb2e7beefa4201db34e8
-log_events:
-- - 2008-03-07 04:28:32.198527 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-03-07 04:28:37.471873 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - changed status from unstarted to fixed
- - ""
diff --git a/bugs/issue-c660ddfa9d633501140dd199bdfd7cd9fed5df0b.yaml b/bugs/issue-c660ddfa9d633501140dd199bdfd7cd9fed5df0b.yaml
deleted file mode 100644
index 56c68c5..0000000
--- a/bugs/issue-c660ddfa9d633501140dd199bdfd7cd9fed5df0b.yaml
+++ /dev/null
@@ -1,22 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: ctrl-g should interrupt thread search operation
-desc: ""
-type: :feature
-component: sup
-release: "0.5"
-reporter: Marcus Williams <marcus-sup at bar-coded.net>
-status: :closed
-disposition: fixed
-creation_time: 2008-03-07 04:15:47.155992 Z
-references: []
-
-id: c660ddfa9d633501140dd199bdfd7cd9fed5df0b
-log_events:
-- - 2008-03-07 04:15:47.156031 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-03-07 04:15:52.274258 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - changed status from unstarted to fixed
- - ""
diff --git a/bugs/issue-cef3096582de268c050f78223eb6a22ac2599606.yaml b/bugs/issue-cef3096582de268c050f78223eb6a22ac2599606.yaml
deleted file mode 100644
index 1881fc4..0000000
--- a/bugs/issue-cef3096582de268c050f78223eb6a22ac2599606.yaml
+++ /dev/null
@@ -1,31 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: ruby 1.8.7 breaks sup in weird ways
-desc: |-
- --- ArgumentError from thread: main
- wrong number of arguments (2 for 1)
- /home/benjamin/projects/sup/lib/sup/index.rb:422:in `respond_to?'
- /home/benjamin/projects/sup/lib/sup/index.rb:422:in `flatten'
- /home/benjamin/projects/sup/lib/sup/index.rb:422:in `load_sources'
- /home/benjamin/projects/sup/lib/sup/index.rb:108:in `load'
- /home/benjamin/projects/sup/lib/sup/util.rb:497:in `send'
- /home/benjamin/projects/sup/lib/sup/util.rb:497:in `method_missing'
- /home/benjamin/projects/sup/bin/sup:122
-type: :bugfix
-component: sup
-release: "0.6"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-06-04 17:39:39.013305 Z
-references: []
-
-id: cef3096582de268c050f78223eb6a22ac2599606
-log_events:
-- - 2008-06-04 17:39:39.670176 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-06-19 17:57:46.648682 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - closed issue with disposition fixed
- - fixed directly in master
diff --git a/bugs/issue-cf09ec6ec7c35d7d8c002b0521f97b6e94dc9b3e.yaml b/bugs/issue-cf09ec6ec7c35d7d8c002b0521f97b6e94dc9b3e.yaml
deleted file mode 100644
index bbba370..0000000
--- a/bugs/issue-cf09ec6ec7c35d7d8c002b0521f97b6e94dc9b3e.yaml
+++ /dev/null
@@ -1,26 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: invalid gecos can cause sup-config to crash
-desc: ""
-type: :bugfix
-component: sup
-release: "0.5"
-reporter: Jean-Hadrien CHABRAN <jh at chabran.fr>
-status: :closed
-disposition: fixed
-creation_time: 2008-03-14 18:54:32.560987 Z
-references:
-- http://rubyforge.org/pipermail/sup-talk/2008-March/001260.html
-id: cf09ec6ec7c35d7d8c002b0521f97b6e94dc9b3e
-log_events:
-- - 2008-03-14 18:54:32.561241 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-03-14 18:54:52.486259 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - added reference 1
- - ""
-- - 2008-03-14 18:55:10.323790 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - changed status from unstarted to fixed
- - ""
diff --git a/bugs/issue-cfbfc65dc90280fa5ecc63094af01d2a47ff0c6e.yaml b/bugs/issue-cfbfc65dc90280fa5ecc63094af01d2a47ff0c6e.yaml
deleted file mode 100644
index 5db69fa..0000000
--- a/bugs/issue-cfbfc65dc90280fa5ecc63094af01d2a47ff0c6e.yaml
+++ /dev/null
@@ -1,22 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: exception thrown when a forwarded attachment is not a known mime type
-desc: a minor typo
-type: :bugfix
-component: sup
-release: "0.6"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-04-23 01:23:06.368926 Z
-references: []
-
-id: cfbfc65dc90280fa5ecc63094af01d2a47ff0c6e
-log_events:
-- - 2008-04-23 01:23:07.968757 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-04-23 01:23:14.995087 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - closed issue with disposition fixed
- - fixed in master
diff --git a/bugs/issue-d131464e921aefc35571c119aac4d9f1decdebae.yaml b/bugs/issue-d131464e921aefc35571c119aac4d9f1decdebae.yaml
deleted file mode 100644
index d49dcaf..0000000
--- a/bugs/issue-d131464e921aefc35571c119aac4d9f1decdebae.yaml
+++ /dev/null
@@ -1,30 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: blank message-id headers are treated as valid and crash indexing
-desc: |-
- specifically, they trigger the "just added message to index" debug exception
- because the id consists of nothing but spaces.
-
- header parsing needs to be fixed to not grab headers that are empty.
-type: :bugfix
-component: mbox
-release: "0.6"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-04-26 21:07:33.463910 Z
-references: []
-
-id: d131464e921aefc35571c119aac4d9f1decdebae
-log_events:
-- - 2008-04-26 21:07:34.221325 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-04-26 21:41:55.731750 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - changed status from unstarted to in_progress
- - branch header-parsing-fix. merged into next.
-- - 2008-05-25 04:19:15.986573 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - closed issue with disposition fixed
- - merged into master
diff --git a/bugs/issue-d994a360c9cb2a6e12a734962a39ffbc6486a725.yaml b/bugs/issue-d994a360c9cb2a6e12a734962a39ffbc6486a725.yaml
deleted file mode 100644
index 1fbb23a..0000000
--- a/bugs/issue-d994a360c9cb2a6e12a734962a39ffbc6486a725.yaml
+++ /dev/null
@@ -1,34 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: flags need a separate backup outside of the ferret index
-desc: |
- Ferret still occasionally barfs and corrupts its own index. Currently all
- user state is stored in the index and only in the index, so that means
- you lose big-time if that happens. You can sup-dump your labels, but really,
- how often are you going to do that.
- Sup should maintain a separate backup of all labels in some fast on-disk
- hashtable (message ids to label sets). Bdb comes to mind.
-
-type: :feature
-component: sup
-release:
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :unstarted
-disposition:
-creation_time: 2008-03-07 04:22:50.236621 Z
-references: []
-
-id: d994a360c9cb2a6e12a734962a39ffbc6486a725
-log_events:
-- - 2008-03-07 04:22:50.236657 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-04-20 21:44:51.655741 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - assigned to release 0.6 from 0.5
- - ""
-- - 2008-07-31 00:54:37.881077 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - unassigned from release 0.6
- - ""
-git_branch:
diff --git a/bugs/issue-d9e6be1b524c6c0a5c31c9c468bda170c2a8cb58.yaml b/bugs/issue-d9e6be1b524c6c0a5c31c9c468bda170c2a8cb58.yaml
deleted file mode 100644
index 81bb3a8..0000000
--- a/bugs/issue-d9e6be1b524c6c0a5c31c9c468bda170c2a8cb58.yaml
+++ /dev/null
@@ -1,33 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: temp files disappear on sup crash
-desc: |-
- because we're using the Tempfile library, Sup crashes mean that tempfiles
- containing message bodies are lost. Sup needs to manage its own tempfiles.
-type: :bugfix
-component: sup
-release:
-reporter: "Marko Myllym\xC3\xA4ki <marko.myllymaki at iki.fi>"
-status: :unstarted
-disposition:
-creation_time: 2008-03-07 04:59:51.404664 Z
-references:
-- http://rubyforge.org/pipermail/sup-talk/2008-February/001174.html
-id: d9e6be1b524c6c0a5c31c9c468bda170c2a8cb58
-log_events:
-- - 2008-03-07 04:59:51.404701 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-03-11 06:20:47.540967 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - added reference 1
- - ""
-- - 2008-04-20 21:45:00.615452 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - assigned to release 0.6 from 0.5
- - ""
-- - 2008-07-31 00:54:38.222035 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - unassigned from release 0.6
- - ""
-git_branch:
diff --git a/bugs/issue-e24df153080c6e7a16335018b04d70d9381258b8.yaml b/bugs/issue-e24df153080c6e7a16335018b04d70d9381258b8.yaml
deleted file mode 100644
index 006e2b1..0000000
--- a/bugs/issue-e24df153080c6e7a16335018b04d70d9381258b8.yaml
+++ /dev/null
@@ -1,20 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: Pulling new threads should not shift the cursor.
-desc: |-
- In thread-view-index, the selected thread should not change due to additions
- of new threads.
-type: :bugfix
-component: curses
-release:
-reporter: Nicolas Pouillard <nicolas.pouillard at gmail.com>
-status: :unstarted
-disposition:
-creation_time: 2008-04-21 08:26:00.191881 Z
-references: []
-
-id: e24df153080c6e7a16335018b04d70d9381258b8
-log_events:
-- - 2008-04-21 08:26:03.807376 Z
- - Nicolas Pouillard <nicolas.pouillard at gmail.com>
- - created
- - ""
diff --git a/bugs/issue-e43b18777ea3aef3566bd80acd126e9ef8a5883a.yaml b/bugs/issue-e43b18777ea3aef3566bd80acd126e9ef8a5883a.yaml
deleted file mode 100644
index 0216c8b..0000000
--- a/bugs/issue-e43b18777ea3aef3566bd80acd126e9ef8a5883a.yaml
+++ /dev/null
@@ -1,34 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: long message ids (>255 characters) never get matched by ferret
-desc: |
- this is a ferret bug.
- Apparently, constructing a TermQuery object with a field value of more than
- 255 characters never successfully matches.
-
- This is not a good long-term solution. A good one would be to take the SHA1
- of every message id instead. That will require an index rebuild, so I will
- save that patch until later.
-
-type: :bugfix
-component: indexing
-release: "0.5"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-03-07 03:40:55.475449 Z
-references: []
-
-id: e43b18777ea3aef3566bd80acd126e9ef8a5883a
-log_events:
-- - 2008-03-07 03:40:55.475485 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-03-07 03:50:18.590242 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - changed status from unstarted to in_progress
- - ""
-- - 2008-04-20 22:10:44.010446 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - closed issue with disposition fixed
- - ""
diff --git a/bugs/issue-e7739718b4dbf49bbd3dd47133affbf7cb1e2361.yaml b/bugs/issue-e7739718b4dbf49bbd3dd47133affbf7cb1e2361.yaml
deleted file mode 100644
index 5e471f9..0000000
--- a/bugs/issue-e7739718b4dbf49bbd3dd47133affbf7cb1e2361.yaml
+++ /dev/null
@@ -1,24 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: "maildir speedup: don't scan directory except when polling"
-desc: |-
- lots of useless scanning. removing it should make things faster for large
- maildirs.
-type: :feature
-component: maildir
-release: "0.5"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: fixed
-creation_time: 2008-03-07 04:20:32.735159 Z
-references: []
-
-id: e7739718b4dbf49bbd3dd47133affbf7cb1e2361
-log_events:
-- - 2008-03-07 04:20:32.735194 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-03-07 04:20:37.257919 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - changed status from unstarted to fixed
- - ""
diff --git a/bugs/issue-e9c2f66a7ff4fb4525c2719e77ac8eedf3835dfd.yaml b/bugs/issue-e9c2f66a7ff4fb4525c2719e77ac8eedf3835dfd.yaml
deleted file mode 100644
index f0ffc9d..0000000
--- a/bugs/issue-e9c2f66a7ff4fb4525c2719e77ac8eedf3835dfd.yaml
+++ /dev/null
@@ -1,20 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: force hook reload feature
-desc: |-
- would be nice for hook debugging. otherwise you have to restart sup
- each time.
-type: :feature
-component: hooks
-release:
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :unstarted
-disposition:
-creation_time: 2008-05-19 23:19:38.202269 Z
-references: []
-
-id: e9c2f66a7ff4fb4525c2719e77ac8eedf3835dfd
-log_events:
-- - 2008-05-19 23:19:38.757600 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
diff --git a/bugs/issue-f767a9d2071da7b0f66698ce74e642bf347be96b.yaml b/bugs/issue-f767a9d2071da7b0f66698ce74e642bf347be96b.yaml
deleted file mode 100644
index ce4ef23..0000000
--- a/bugs/issue-f767a9d2071da7b0f66698ce74e642bf347be96b.yaml
+++ /dev/null
@@ -1,21 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: mbox file handle closing
-desc: |-
- currently an open file handle is maintained for every single mbox folder.
- (well, every one that's accessed by polling or by opening a message therefrom.)
- that is plum crazy.
-type: :feature
-component: mbox
-release:
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :unstarted
-disposition:
-creation_time: 2008-03-07 04:27:35.146273 Z
-references: []
-
-id: f767a9d2071da7b0f66698ce74e642bf347be96b
-log_events:
-- - 2008-03-07 04:27:35.146307 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
diff --git a/bugs/issue-fd7c7a7d7caf41ff20e7d10ca3f074fc02c14a5b.yaml b/bugs/issue-fd7c7a7d7caf41ff20e7d10ca3f074fc02c14a5b.yaml
deleted file mode 100644
index f4d38d9..0000000
--- a/bugs/issue-fd7c7a7d7caf41ff20e7d10ca3f074fc02c14a5b.yaml
+++ /dev/null
@@ -1,30 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: thread joining doesn't always work
-desc: |
- sometimes it works, and sometimes it doesn't, and i haven't found the
- pattern yet.
-
- also, UpdateManager isn't being called properly (maybe even needs a
- custom event). e.g. if you join in search-results-mode, the results
- aren't joined in inbox-mode.
-
-type: :bugfix
-component: indexing
-release:
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :unstarted
-disposition:
-creation_time: 2008-04-29 00:09:38.366801 Z
-references: []
-
-id: fd7c7a7d7caf41ff20e7d10ca3f074fc02c14a5b
-log_events:
-- - 2008-04-29 00:09:38.998592 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-07-31 00:54:39.251862 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - unassigned from release 0.6
- - ""
-git_branch:
diff --git a/bugs/issue-fdfc906e8f4f6eb10f1ebdf39c416415d9ab6af9.yaml b/bugs/issue-fdfc906e8f4f6eb10f1ebdf39c416415d9ab6af9.yaml
deleted file mode 100644
index 99cbd3e..0000000
--- a/bugs/issue-fdfc906e8f4f6eb10f1ebdf39c416415d9ab6af9.yaml
+++ /dev/null
@@ -1,26 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue
-title: archive-and-mark-read command in inbox-mode
-desc: ""
-type: :feature
-component: curses
-release: "0.6"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-04-26 23:32:53.791207 Z
-references: []
-
-id: fdfc906e8f4f6eb10f1ebdf39c416415d9ab6af9
-log_events:
-- - 2008-04-26 23:32:57.083084 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
-- - 2008-04-26 23:33:12.980220 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - changed status from unstarted to in_progress
- - branch read-and-archive. in next.
-- - 2008-05-25 04:14:48.307896 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - closed issue with disposition fixed
- - merged into master
diff --git a/bugs/project.yaml b/bugs/project.yaml
deleted file mode 100644
index f6afd47..0000000
--- a/bugs/project.yaml
+++ /dev/null
@@ -1,53 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/project
-name: sup
-version: 0.0.1
-components:
-- !ditz.rubyforge.org,2008-03-06/component
- name: sup
-- !ditz.rubyforge.org,2008-03-06/component
- name: threading
-- !ditz.rubyforge.org,2008-03-06/component
- name: indexing
-- !ditz.rubyforge.org,2008-03-06/component
- name: curses
-- !ditz.rubyforge.org,2008-03-06/component
- name: hooks
-- !ditz.rubyforge.org,2008-03-06/component
- name: sup-sync
-- !ditz.rubyforge.org,2008-03-06/component
- name: sup-sync-back
-- !ditz.rubyforge.org,2008-03-06/component
- name: maildir
-- !ditz.rubyforge.org,2008-03-06/component
- name: imap
-- !ditz.rubyforge.org,2008-03-06/component
- name: mbox
-- !ditz.rubyforge.org,2008-03-06/component
- name: crypto
-releases:
-- !ditz.rubyforge.org,2008-03-06/release
- name: "0.5"
- status: :released
- release_time: 2008-04-22 15:55:47.323776 Z
- log_events:
- - - 2008-03-07 02:37:54.903172 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
- - - 2008-04-22 15:55:47.323829 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - released
- - ""
-- !ditz.rubyforge.org,2008-03-06/release
- name: "0.6"
- status: :released
- release_time: 2008-08-04 02:48:44.154676 Z
- log_events:
- - - 2008-04-20 21:17:04.443432 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - created
- - ""
- - - 2008-08-04 02:48:44.154704 Z
- - William Morgan <wmorgan-sup at masanjin.net>
- - released
- - ""
diff --git a/contrib/colorpicker.rb b/contrib/colorpicker.rb
index c981a23..947b3d5 100644
--- a/contrib/colorpicker.rb
+++ b/contrib/colorpicker.rb
@@ -1,10 +1,6 @@
require 'rubygems'
-begin
- require 'ncursesw'
-rescue LoadError
- require 'ncurses'
-end
+require 'ncursesw'
Ncurses.initscr
Ncurses.noecho
diff --git a/contrib/completion/_sup.zsh b/contrib/completion/_sup.zsh
index 76870ca..f49d4c5 100644
--- a/contrib/completion/_sup.zsh
+++ b/contrib/completion/_sup.zsh
@@ -1,8 +1,8 @@
-#compdef sup sup-add sup-config sup-dump sup-sync sup-sync-back sup-tweak-labels sup-recover-sources
+#compdef sup sup-add sup-config sup-dump sup-sync sup-tweak-labels sup-recover-sources
# vim: set et sw=2 sts=2 ts=2 ft=zsh :
# TODO: sources completion: maildir://some/dir, mbox://some/file, ...
-# for sup-add, sup-sync, sup-sync-back, sup-tweak-labels
+# for sup-add, sup-sync, sup-tweak-labels
(( ${+functions[_sup_cmd]} )) ||
_sup_cmd()
diff --git a/devel/console.sh b/devel/console.sh
old mode 100644
new mode 100755
index ac11934..dc70775
--- a/devel/console.sh
+++ b/devel/console.sh
@@ -1,3 +1,3 @@
#!/bin/sh
-irb -I lib -r devel/start-console.rb
+irb -I lib -r ./devel/start-console.rb
diff --git a/doc/FAQ.txt b/doc/FAQ.txt
index 9a24d7f..16c9a59 100644
--- a/doc/FAQ.txt
+++ b/doc/FAQ.txt
@@ -21,8 +21,8 @@ A: I hate ads, I hate using a mouse, and I hate non-programmability and
Q: Why the console?
A: Because a keystroke is worth a hundred mouse clicks, as any Unix
- user knows. Because you don't need web browser. Because you get
- instantaneous response and a simple interface.
+ user knows. Because you don't need a web browser. Because you get
+ an instantaneous response and a simple interface.
Q: How does Sup deal with spam?
A: You can manually mark messages as spam, which prevents them from
@@ -112,4 +112,8 @@ P: When I run Sup remotely and view an HTML attachment, an existing
file, which it can't find (since it's on the remote machine). How do
I view HTML attachments in this environment?
S: Put this in your ~/.mailcap on the machine you run Sup on:
- text/html; /usr/bin/firefox -a sup '%s'; description=HTML Text; test=test -n "$DISPLAY"; nametemplate=%s.html
+ text/html; /usr/bin/firefox -a sup %s; description=HTML Text; test=test -n "$DISPLAY"; nametemplate=%s.html
+
+ Please read
+ https://github.com/sup-heliotrope/sup/wiki/Viewing-Attachments for
+ some security concerns on opening attachments.
diff --git a/doc/Hooks.txt b/doc/Hooks.txt
index 21b1e5e..6c33971 100644
--- a/doc/Hooks.txt
+++ b/doc/Hooks.txt
@@ -48,12 +48,17 @@ before-poll:
mime-decode:
+ ## Please read:
+ https://github.com/sup-heliotrope/sup/wiki/Viewing-Attachments for
+ some security concerns on opening attachments.
+
## turn text/html attachments into plain text, unless they are part
## of a multipart/alternative pair
+ require 'shellwords'
unless sibling_types.member? "text/plain"
case content_type
when "text/html"
- `/usr/bin/w3m -dump -T #{content_type} '#{filename}'`
+ `/usr/bin/w3m -dump -T #{content_type} #{Shellwords.escape filename}`
end
end
diff --git a/doc/NewUserGuide.txt b/doc/NewUserGuide.txt
deleted file mode 100644
index c071deb..0000000
--- a/doc/NewUserGuide.txt
+++ /dev/null
@@ -1,258 +0,0 @@
-Welcome to Sup! Here's how to get started.
-
-First, try running `sup`. Since this is your first time, you'll be
-confronted with a mostly blank screen, and a notice at the bottom that
-you have no new messages. That's because Sup doesn't hasn't loaded
-anything into its index yet, and has no idea where to look for them
-anyways.
-
-If you want to play around a little at this point, you can press 'b'
-to cycle between buffers, ';' to get a list of the open buffers, and
-'x' to kill a buffer. There's probably not too much interesting there,
-but there's a log buffer with some cryptic messages. You can also
-press '?' at any point to get a list of keyboard commands, but in the
-absence of any email, these will be mostly useless. When you get
-bored, press 'q' to quit.
-
-To use Sup for email, we need to load messages into the index. The
-index is where Sup stores all message state (e.g. read or unread, any
-message labels), and all information necessary for searching and for
-threading messages. Sup only knows about messages in its index.
-
-We can add messages to the index by telling Sup about the "source"
-where the messages reside. Sources are things like mbox folders, and
-maildir directories. Sup doesn't duplicate the actual message content
-in the index; it only stores whatever information is necessary for
-searching, threading and labelling. So when you search for messages or
-view your inbox, Sup talks only to the index (stored locally on
-disk). When you view a thread, Sup requests the full content of all
-the messages from the source.
-
-The easiest way to set up all your sources is to run `sup-config`.
-This will interactively walk you through some basic configuration,
-prompt you for all the sources you need, and optionally import
-messages from them. Sup-config uses two other tools, sup-add and
-sup-sync, to load messages into the index. In the future you may make
-use of these tools directly (see below).
-
-Once you've run sup-config, you're ready to run `sup`. You should see
-the most recent unarchived messages appear in your inbox.
-Congratulations, you've got Sup working!
-
-If you're coming from the world of traditional MUAs, there are a
-couple differences you should be aware of at this point. First, Sup
-has no folders. Instead, you organize and find messages by a
-combination of search and labels (known as "tags" everywhere else in
-the world). Search and labels are an integral part of Sup because in
-Sup, rather than viewing the contents of a folder, you view the
-results of a search. I mentioned above that your inbox is, by
-definition, the set of all messages that aren't archived. This means
-that your inbox is nothing more than the result of the search for all
-messages with the label "inbox". (It's actually slightly more
-complicated---we also omit messages marked as killed, deleted or
-spam.)
-
-You could replicate the folder paradigm easily under this scheme, by
-giving each message exactly one label and only viewing the results of
-simple searches for those labels. But you'd quickly find out that life
-can be easier than that if you just trust the search engine, and use
-labels judiciously for things that are too hard to find with search.
-The idea is that a labeling system that allows arbitrary, user-defined
-labels, supplemented by a quick and easy-to-access search mechanism
-provides all the functionality that folders does, plus much more, at a
-far lower cost to the user.
-
-Now let's take a look at your inbox. You'll see that Sup groups
-messages together into threads: each line in the inbox is a thread,
-and the number in parentheses is the number of messages in that
-thread. (If there's no number, there's just one message in the
-thread.) In Sup, most operations are on threads, not individual
-messages. The idea is that you rarely want to operate on a message
-independent of its context. You typically want to view, archive, kill,
-or label all the messages in a thread at one time.
-
-Use the up and down arrows to highlight a thread. ('j' and 'k' do the
-same thing, and 'J' and 'K' will scroll the whole window. Even the
-left and right arrow keys work.) By default, Sup only loads as many
-threads as it takes to fill the window; if you'd like to load more,
-press 'M'. You can hit tab to cycle between only threads with new
-messages.
-
-Highlight a thread and press enter to view it. You'll notice that all
-messages in the thread are displayed together, laid out graphically by
-their relationship to each other (replies are nested under parents).
-By default, only the new messages in a thread are expanded, and the
-others are hidden. You can toggle an individual message's state by
-highlighting a green line and pressing enter. You can use 'E' to
-expand or collapse all messages or 'N' to expand only the new
-messages. You'll also notice that Sup hides quoted text and
-signatures. If you highlight a particular hidden chunk, you can press
-enter to expand it, or you can press 'o' to toggle every hidden chunk
-in a particular message.
-
-Other useful keyboard commands when viewing a thread are: 'n' and 'p'
-to jump to the next and previous open messages, 'h' to toggle the
-detailed headers for the current message, and enter to expand or
-collapse the current message (when it's on a text region). Enter and
-'n' in combination are useful for scanning through a thread---press
-enter to close the current message and jump to the next open one, and
-'n' to keep it open and jump. If the buffer is misaligned with a
-message, you can press 'z' to highlight it.
-
-This is a lot to remember, but you can always hit '?' to see the full
-list of keyboard commands at any point. There's a lot of useful stuff
-in there---once you learn some, try out some of the others!
-
-Now press 'x' to kill the thread view buffer. You should see the inbox
-again. If you don't, you can cycle through the buffers by pressing
-'b', or you can press ';' to see a list of all buffers and simply
-select the inbox.
-
-There are many operations you can perform on threads beyond viewing
-them. To archive a thread, press 'a'. The thread will disappear from
-your inbox, but will still appear in search results. If someone
-replies an archived thread, it will reappear in your inbox. To kill a
-thread, press '&'. Killed threads will never come back to your inbox,
-even if people reply, but will still be searchable. (This is useful
-for those interminable threads that you really have no immediate
-interest in, but which seem to pop up on every mailing list.)
-
-If a thread is spam, press 'S'. It will disappear and won't come back.
-It won't even appear in search results, unless you explicitly search
-for spam.
-
-You can star a thread by pressing '*'. Starred threads are displayed
-with a little yellow asterisk next to them, but otherwise have no
-special semantics. But you can also search for them easily---we'll see
-how in a moment.
-
-To edit the labels for (all the messages in) a thread, press 'l'. Type
-in the labels as a sequence of space-separated words. To cancel the
-input, press Ctrl-G.
-
-Many of these operations can be applied to a group of threads. Press
-'t' to tag a thread. Tag a couple, then press '=' to apply the next
-command to the set of threads. '=t', of course, will untag all tagged
-messages.
-
-Ok, let's try using labels and search. Press 'L' to do a quick label
-search. You'll be prompted for a label; simply hit enter to bring up
-scrollable list of all the labels you've ever used, along with some
-special labels (Draft, Starred, Sent, Spam, etc.). Highlight a label
-and press enter to view all the messages with that label.
-
-What you just did was actually a specific search. For a general search,
-press '\' (backslash---forward slash is used for in-buffer search,
-following console conventions). Now type in your query (again, Ctrl-G to
-cancel at any point.) You can just type in arbitrary text, which will be
-matched on a per-word basis against the bodies of all email in the
-index, or you can make use of the full Xapian query syntax
-(http://xapian.org/docs/queryparser.html):
-
-- Phrasal queries using double-quotes, e.g.: "three contiguous words"
-- Queries against a particular field using <field name>:<query>,
- e.g.: label:ruby-talk, or from:matz at ruby-lang.org. (Fields include:
- body, from, to, and subject.)
-- Force non-occurrence by -, e.g. -body:"hot soup".
-- If you have the chronic gem installed, date queries like
- "before:today", "on:today", "after:yesterday", "after:(2 days ago)"
- (parentheses required for multi-word descriptions).
-
-You can combine those all together. For example:
-
- label:ruby-talk subject:\[ANN\] -rails on:today
-
-Play around with the search, and see the Xapian documentation for
-details on more sophisticated queries (date ranges, "within n words",
-etc.)
-
-At this point, you're well on your way to figuring out all the cool
-things Sup can do. By repeated application of the '?' key, see if you
-can figure out how to:
-
- - List some recent contacts
- - Easily search for all mail from a recent contact
- - Easily search for all mail from several recent contacts
- - Add someone to your address book
- - Postpone a message (i.e., save a draft)
- - Quickly re-edit a just-saved draft message
- - View the raw header of a message
- - Star an individual message, not just a thread
-
-There's one last thing to be aware of when using Sup: how it interacts
-with other email programs. As I described above, Sup stores data about
-messages in the index, but doesn't duplicate the message contents
-themselves. The messages remain on the source. If the index and the
-source every fall out of sync, e.g. due to another email client
-modifying the source, then Sup will be unable to operate on that
-source. For example, for mbox files, Sup stores a byte offset into the
-file for each message. If a message deleted from that file by another
-client, or even marked as read (yeah, mbox sucks), all succeeding
-offsets will be wrong.
-
-That's the bad news. The good news is that Sup is pretty good at being
-able to detect this type of situation, and fixing it is just a matter
-of running `sup-sync --changed` on the source. Sup will even tell you
-how to invoke sup-sync when it detects a problem. This is a
-complication you will almost certainly run in to if you use both Sup
-and another MUA on the same source, so it's good to be aware of it.
-
-Have fun, and email sup-talk at rubyforge.org if you have any problems!
-
-Appendix A: sup-add and sup-sync
----------------------------------
-
-Instead of using sup-config to add a new source, you can manually run
-`sup-add` with a URI pointing to it. The URI should be of the form:
-
-- mbox://path/to/a/filename, for an mbox file on disk.
-- maildir://path/to/a/filename, for a maildir directory on disk.
-
-Before you add the source, you need make three decisions. The first is
-whether you want Sup to regularly poll this source for new messages.
-By default it will, but if this is a source that will never have new
-messages, you can specify `--unusual`. Sup polls only "usual" sources
-when checking for new mail (unless you manually invoke sup-sync).
-
-The second is whether you want messages from the source to be
-automatically archived. An archived message will not show up in your
-inbox, but will be found when you search. (Your inbox in Sup is, by
-definition, the set of all all non-archived messages). Specify
-`--archive` to automatically archive all messages from the source. This
-is useful for sources that contain, for example, high-traffic mailing
-lists that you don't want polluting your inbox.
-
-The final decision is whether you want any labels automatically
-applied to messages from this source. You can use `--labels` to do this.
-
-Now that you've added the source, let's import all the current
-messages from it, by running sup-sync with the source URI. You can
-specify `--archive` to automatically archive all messages in this
-import; typically you'll want to specify this for every source you
-import except your actual inbox. You can also specify `--read` to mark
-all imported messages as read; the default is to preserve the
-read/unread status from the source.
-
-Sup-sync will now load all the messages from the source into the
-index. Depending on the size of the source, this may take a while.
-Don't panic! It's a one-time process.
-
-Appendix B: Automatically labeling incoming email
--------------------------------------------------
-
-One option is to filter incoming email into different sources with
-something like procmail, and have each of these sources auto-apply
-labels by using `sup-add --labels`.
-
-But the better option is to learn Ruby and write a before-add hook.
-This will allow you to apply labels based on whatever crazy logic you
-can come up with. See http://sup.rubyforge.org/wiki/wiki.pl?Hooks for
-examples.
-
-Appendix C: Reading blogs with Sup
-----------------------------------
-
-Really, blog posts should be read like emails are read---you should be
-able to mark them as unread, flag them, label them, etc. Use rss2email
-to transform RSS feeds into emails, direct them all into a source, and
-add that source to Sup. Voila!
diff --git a/lib/sup.rb b/lib/sup.rb
index 74d5cde..0ea3720 100644
--- a/lib/sup.rb
+++ b/lib/sup.rb
@@ -1,11 +1,12 @@
+# encoding: utf-8
+
require 'rubygems'
-require 'syck'
require 'yaml'
require 'zlib'
require 'thread'
require 'fileutils'
-require 'gettext'
-require 'curses'
+require 'locale'
+require 'ncursesw'
require 'rmail'
begin
require 'fastthread'
@@ -23,24 +24,27 @@ end
class Module
def yaml_properties *props
props = props.map { |p| p.to_s }
- vars = props.map { |p| "@#{p}" }
- klass = self
- path = klass.name.gsub(/::/, "/")
- klass.instance_eval do
- define_method(:to_yaml_properties) { vars }
- define_method(:to_yaml_type) { "!#{Redwood::YAML_DOMAIN},#{Redwood::YAML_DATE}/#{path}" }
+ path = name.gsub(/::/, "/")
+ yaml_tag "!#{Redwood::YAML_DOMAIN},#{Redwood::YAML_DATE}/#{path}"
+
+ define_method :init_with do |coder|
+ initialize(*coder.map.values_at(*props))
end
- YAML.add_domain_type("#{Redwood::YAML_DOMAIN},#{Redwood::YAML_DATE}", path) do |type, val|
- klass.new(*props.map { |p| val[p] })
+ define_method :encode_with do |coder|
+ coder.map = props.inject({}) do |hash, key|
+ hash[key] = instance_variable_get("@#{key}")
+ hash
+ end
end
+
+ # Legacy
+ Psych.load_tags["!#{Redwood::LEGACY_YAML_DOMAIN},#{Redwood::YAML_DATE}/#{path}"] = self
end
end
module Redwood
- VERSION = "git"
-
BASE_DIR = ENV["SUP_BASE"] || File.join(ENV["HOME"], ".sup")
CONFIG_FN = File.join(BASE_DIR, "config.yaml")
COLOR_FN = File.join(BASE_DIR, "colors.yaml")
@@ -54,9 +58,12 @@ module Redwood
HOOK_DIR = File.join(BASE_DIR, "hooks")
SEARCH_FN = File.join(BASE_DIR, "searches.txt")
LOG_FN = File.join(BASE_DIR, "log")
+ SYNC_OK_FN = File.join(BASE_DIR, "sync-back-ok")
- YAML_DOMAIN = "masanjin.net"
+ YAML_DOMAIN = "supmua.org"
+ LEGACY_YAML_DOMAIN = "masanjin.net"
YAML_DATE = "2006-10-01"
+ MAILDIR_SYNC_CHECK_SKIPPED = 'SKIPPED'
## record exceptions thrown in threads nicely
@exceptions = []
@@ -151,7 +158,7 @@ module Redwood
SourceManager SearchManager IdleManager).map { |x| Redwood.const_get x.to_sym }
end
- def start
+ def start bypass_sync_check = false
managers.each { |x| fail "#{x} already instantiated" if x.instantiated? }
FileUtils.mkdir_p Redwood::BASE_DIR
@@ -167,6 +174,74 @@ module Redwood
Redwood::SearchManager.init Redwood::SEARCH_FN
managers.each { |x| x.init unless x.instantiated? }
+
+ return if bypass_sync_check
+
+ if $config[:sync_back_to_maildir]
+ if not File.exists? Redwood::SYNC_OK_FN
+ Redwood.warn_syncback <<EOS
+It appears that the "sync_back_to_maildir" option has been changed
+from false to true since the last execution of sup.
+EOS
+ $stderr.puts <<EOS
+
+Should I complain about this again? (Y/n)
+EOS
+ File.open(Redwood::SYNC_OK_FN, 'w') {|f| f.write(Redwood::MAILDIR_SYNC_CHECK_SKIPPED) } if STDIN.gets.chomp.downcase == 'n'
+ end
+ elsif not $config[:sync_back_to_maildir] and File.exists? Redwood::SYNC_OK_FN
+ File.delete(Redwood::SYNC_OK_FN)
+ end
+ end
+
+ def check_syncback_settings
+ # don't check if syncback was never performed
+ return unless File.exists? Redwood::SYNC_OK_FN
+ active_sync_sources = File.readlines(Redwood::SYNC_OK_FN).collect { |e| e.strip }.find_all { |e| not e.empty? }
+ return if active_sync_sources.length == 1 and active_sync_sources[0] == Redwood::MAILDIR_SYNC_CHECK_SKIPPED
+ sources = SourceManager.sources
+ newly_synced = sources.select { |s| s.is_a? Maildir and s.sync_back_enabled? and not active_sync_sources.include? s.uri }
+ unless newly_synced.empty?
+
+ details =<<EOS
+It appears that the option "sync_back" of the following source(s)
+has been changed from false to true since the last execution of
+sup:
+
+EOS
+ newly_synced.each do |s|
+ details += "#{s} (usual: #{s.usual})\n"
+ end
+
+ Redwood.warn_syncback details
+ end
+ end
+
+ def self.warn_syncback details
+ $stderr.puts <<EOS
+WARNING
+-------
+
+#{details}
+
+It is *strongly* recommended that you run "sup-sync-back-maildir"
+before continuing, otherwise you might lose changes you have made in sup
+to your Xapian index.
+
+This script should be run each time you change the
+"sync_back_to_maildir" flag in config.yaml from false to true or
+the "sync_back" flag is changed to true for a source in sources.yaml.
+
+Please run "sup-sync-back-maildir -h" for more information and why this
+is needed.
+
+Note that if you have any sources that are not marked as 'ususal' in
+sources.yaml you need to manually specify them when running the
+sup-sync-back-maildir script.
+
+Are you really sure you want to continue? (y/N)
+EOS
+ abort "Aborted" unless STDIN.gets.chomp.downcase == 'y'
end
def finish
@@ -177,7 +252,7 @@ module Redwood
managers.each { |x| x.deinstantiate! if x.instantiated? }
- @log_io.close
+ @log_io.close if @log_io
@log_io = nil
$config = nil
end
@@ -231,36 +306,6 @@ EOM
end
end
- ## to be called by entry points in bin/, to ensure that
- ## their versions match up against the library versions.
- ##
- ## this is a perennial source of bug reports from people
- ## who both use git and have a gem version installed.
- def check_library_version_against v
- unless Redwood::VERSION == v
- $stderr.puts <<EOS
-Error: version mismatch!
-The sup executable is at version #{v.inspect}.
-The sup libraries are at version #{Redwood::VERSION.inspect}.
-
-Your development environment may be picking up code from a
-rubygems installation of sup.
-
-If you're running from git with a commandline like
-
- ruby -Ilib #{$0}
-
-try this instead:
-
- RUBY_INVOCATION="ruby -Ilib" ruby -Ilib #{$0}
-
-You can also try `gem uninstall sup` and removing all Sup rubygems.
-
-EOS
-#' duh!
- abort
- end
- end
## set up default configuration file
def load_config filename
@@ -285,7 +330,11 @@ EOS
:poll_interval => 300,
:wrap_width => 0,
:slip_rows => 0,
- :col_jump => 2
+ :col_jump => 2,
+ :stem_language => "english",
+ :sync_back_to_maildir => false,
+ :continuous_scroll => false,
+ :always_edit_async => false,
}
if File.exists? filename
config = Redwood::load_yaml_obj filename
@@ -294,7 +343,7 @@ EOS
else
require 'etc'
require 'socket'
- name = Etc.getpwnam(ENV["USER"]).gecos.split(/,/).first rescue nil
+ name = Etc.getpwnam(ENV["USER"]).gecos.split(/,/).first.force_encoding($encoding).fix_encoding! rescue nil
name ||= ENV["USER"]
email = ENV["USER"] + "@" +
begin
@@ -306,8 +355,8 @@ EOS
config = {
:accounts => {
:default => {
- :name => name,
- :email => email,
+ :name => name.dup.fix_encoding!,
+ :email => email.dup.fix_encoding!,
:alternates => [],
:sendmail => "/usr/sbin/sendmail -oem -ti",
:signature => File.join(ENV["HOME"], ".signature"),
@@ -326,22 +375,22 @@ EOS
end
module_function :save_yaml_obj, :load_yaml_obj, :start, :finish,
- :report_broken_sources, :check_library_version_against,
- :load_config, :managers
+ :report_broken_sources, :load_config, :managers,
+ :check_syncback_settings
end
+require 'sup/version'
require "sup/util"
require "sup/hook"
require "sup/time"
## everything we need to get logging working
-require "sup/logger"
-Redwood::Logger.init.add_sink $stderr
-include Redwood::LogsStuff
+require "sup/logger/singleton"
## determine encoding and character set
$encoding = Locale.current.charset
$encoding = "UTF-8" if $encoding == "utf8"
+$encoding = "UTF-8" if $encoding == "UTF8"
if $encoding
debug "using character set encoding #{$encoding.inspect}"
else
@@ -349,14 +398,24 @@ else
$encoding = "UTF-8"
end
+# test encoding
+teststr = "test"
+teststr.encode('UTF-8')
+begin
+ teststr.encode($encoding)
+rescue Encoding::ConverterNotFoundError
+ warn "locale encoding is invalid, defaulting to utf-8"
+ $encoding = "UTF-8"
+end
+
require "sup/buffer"
require "sup/keymap"
require "sup/mode"
-require "sup/modes/scroll-mode"
-require "sup/modes/text-mode"
-require "sup/modes/log-mode"
+require "sup/modes/scroll_mode"
+require "sup/modes/text_mode"
+require "sup/modes/log_mode"
require "sup/update"
-require "sup/message-chunks"
+require "sup/message_chunks"
require "sup/message"
require "sup/source"
require "sup/mbox"
@@ -364,7 +423,7 @@ require "sup/maildir"
require "sup/person"
require "sup/account"
require "sup/thread"
-require "sup/interactive-lock"
+require "sup/interactive_lock"
require "sup/index"
require "sup/textfield"
require "sup/colormap"
@@ -375,31 +434,31 @@ require "sup/draft"
require "sup/poll"
require "sup/crypto"
require "sup/undo"
-require "sup/horizontal-selector"
-require "sup/modes/line-cursor-mode"
-require "sup/modes/help-mode"
-require "sup/modes/edit-message-mode"
-require "sup/modes/edit-message-async-mode"
-require "sup/modes/compose-mode"
-require "sup/modes/resume-mode"
-require "sup/modes/forward-mode"
-require "sup/modes/reply-mode"
-require "sup/modes/label-list-mode"
-require "sup/modes/contact-list-mode"
-require "sup/modes/thread-view-mode"
-require "sup/modes/thread-index-mode"
-require "sup/modes/label-search-results-mode"
-require "sup/modes/search-results-mode"
-require "sup/modes/person-search-results-mode"
-require "sup/modes/inbox-mode"
-require "sup/modes/buffer-list-mode"
-require "sup/modes/poll-mode"
-require "sup/modes/file-browser-mode"
-require "sup/modes/completion-mode"
-require "sup/modes/console-mode"
+require "sup/horizontal_selector"
+require "sup/modes/line_cursor_mode"
+require "sup/modes/help_mode"
+require "sup/modes/edit_message_mode"
+require "sup/modes/edit_message_async_mode"
+require "sup/modes/compose_mode"
+require "sup/modes/resume_mode"
+require "sup/modes/forward_mode"
+require "sup/modes/reply_mode"
+require "sup/modes/label_list_mode"
+require "sup/modes/contact_list_mode"
+require "sup/modes/thread_view_mode"
+require "sup/modes/thread_index_mode"
+require "sup/modes/label_search_results_mode"
+require "sup/modes/search_results_mode"
+require "sup/modes/person_search_results_mode"
+require "sup/modes/inbox_mode"
+require "sup/modes/buffer_list_mode"
+require "sup/modes/poll_mode"
+require "sup/modes/file_browser_mode"
+require "sup/modes/completion_mode"
+require "sup/modes/console_mode"
require "sup/sent"
require "sup/search"
-require "sup/modes/search-list-mode"
+require "sup/modes/search_list_mode"
require "sup/idle"
$:.each do |base|
diff --git a/lib/sup/account.rb b/lib/sup/account.rb
index 1cbf7d8..ec8a19b 100644
--- a/lib/sup/account.rb
+++ b/lib/sup/account.rb
@@ -25,7 +25,7 @@ class Account < Person
end
class AccountManager
- include Singleton
+ include Redwood::Singleton
attr_accessor :default_account
@@ -50,8 +50,9 @@ class AccountManager
[:name, :sendmail, :signature, :gpgkey].each { |k| hash[k] ||= @default_account.send(k) }
end
hash[:alternates] ||= []
+ fail "alternative emails are not an array: #{hash[:alternates]}" unless hash[:alternates].kind_of? Array
- [:name, :signature].each { |x| hash[x].force_encoding Encoding::UTF_8 if hash[x].respond_to? :encoding }
+ [:name, :signature].each { |x| hash[x] ? hash[x].fix_encoding! : nil }
a = Account.new hash
@accounts[a] = true
diff --git a/lib/sup/buffer.rb b/lib/sup/buffer.rb
index 444589a..e8bb0c0 100644
--- a/lib/sup/buffer.rb
+++ b/lib/sup/buffer.rb
@@ -1,70 +1,10 @@
+# encoding: utf-8
+
require 'etc'
require 'thread'
+require 'ncursesw'
-begin
- require 'ncursesw'
-rescue LoadError
- require 'ncurses'
-end
-
-if defined? Ncurses
-module Ncurses
- def rows
- lame, lamer = [], []
- stdscr.getmaxyx lame, lamer
- lame.first
- end
-
- def cols
- lame, lamer = [], []
- stdscr.getmaxyx lame, lamer
- lamer.first
- end
-
- def curx
- lame, lamer = [], []
- stdscr.getyx lame, lamer
- lamer.first
- end
-
- def mutex; @mutex ||= Mutex.new; end
- def sync &b; mutex.synchronize(&b); end
-
- ## magically, this stuff seems to work now. i could swear it didn't
- ## before. hm.
- def nonblocking_getch
- ## INSANTIY
- ## it is NECESSARY to wrap Ncurses.getch in a select() otherwise all
- ## background threads will be BLOCKED. (except in very modern versions
- ## of libncurses-ruby. the current one on ubuntu seems to work well.)
- if IO.select([$stdin], nil, nil, 0.5)
- if Redwood::BufferManager.shelled?
- # If we get input while we're shelled, we'll ignore it for the
- # moment and use Ncurses.sync to wait until the shell_out is done.
- Ncurses.sync { nil }
- else
- Ncurses.getch
- end
- end
- end
-
- ## pretends ctrl-c's are ctrl-g's
- def safe_nonblocking_getch
- nonblocking_getch
- rescue Interrupt
- KEY_CANCEL
- end
-
- module_function :rows, :cols, :curx, :nonblocking_getch, :safe_nonblocking_getch, :mutex, :sync
-
- remove_const :KEY_ENTER
- remove_const :KEY_CANCEL
-
- KEY_ENTER = 10
- KEY_CANCEL = 7 # ctrl-g
- KEY_TAB = 9
-end
-end
+require 'sup/util/ncurses'
module Redwood
@@ -130,14 +70,11 @@ class Buffer
@w.attrset Colormap.color_for(opts[:color] || :none, opts[:highlight])
s ||= ""
maxl = @width - x # maximum display width width
- stringl = maxl # string "length"
# fill up the line with blanks to overwrite old screen contents
@w.mvaddstr y, x, " " * maxl unless opts[:no_fill]
- ## the next horribleness is thanks to ruby's lack of widechar support
- stringl += 1 while stringl < s.length && s[0 ... stringl].display_length < maxl
- @w.mvaddstr y, x, s[0 ... stringl]
+ @w.mvaddstr y, x, s.slice_by_display_length(maxl)
end
def clear
@@ -162,7 +99,7 @@ class Buffer
end
class BufferManager
- include Singleton
+ include Redwood::Singleton
attr_reader :focus_buf
@@ -219,7 +156,14 @@ EOS
@sigwinch_mutex = Mutex.new
end
- def sigwinch_happened!; @sigwinch_mutex.synchronize { @sigwinch_happened = true } end
+ def sigwinch_happened!
+ @sigwinch_mutex.synchronize do
+ return if @sigwinch_happened
+ @sigwinch_happened = true
+ Ncurses.ungetch ?\C-l.ord
+ end
+ end
+
def sigwinch_happened?; @sigwinch_mutex.synchronize { @sigwinch_happened } end
def buffers; @name_map.to_a; end
@@ -271,7 +215,7 @@ EOS
def handle_input c
if @focus_buf
- if @focus_buf.mode.in_search? && c != CONTINUE_IN_BUFFER_SEARCH_KEY[0]
+ if @focus_buf.mode.in_search? && c != CONTINUE_IN_BUFFER_SEARCH_KEY
@focus_buf.mode.cancel_search!
@focus_buf.mark_dirty
end
@@ -403,9 +347,9 @@ EOS
draw_screen
until mode.done?
- c = Ncurses.safe_nonblocking_getch
- next unless c # getch timeout
- break if c == Ncurses::KEY_CANCEL
+ c = Ncurses::CharCode.get
+ next unless c.present? # getch timeout
+ break if c.is_keycode? Ncurses::KEY_CANCEL
begin
mode.handle_input c
rescue InputSequenceAborted # do nothing
@@ -454,7 +398,7 @@ EOS
def ask_with_completions domain, question, completions, default=nil
ask domain, question, default do |s|
- s.force_encoding 'UTF-8' if s.methods.include?(:encoding)
+ s.fix_encoding!
completions.select { |x| x =~ /^#{Regexp::escape s}/iu }.map { |x| [x, x] }
end
end
@@ -471,9 +415,9 @@ EOS
raise "william screwed up completion: #{partial.inspect}"
end
- prefix.force_encoding 'UTF-8' if prefix.methods.include?(:encoding)
- target.force_encoding 'UTF-8' if target.methods.include?(:encoding)
- completions.select { |x| x =~ /^#{Regexp::escape target}/i }.map { |x| [prefix + x, x] }
+ prefix.fix_encoding!
+ target.fix_encoding!
+ completions.select { |x| x =~ /^#{Regexp::escape target}/iu }.map { |x| [prefix + x, x] }
end
end
@@ -481,12 +425,12 @@ EOS
ask domain, question, default do |partial|
prefix, target = partial.split_on_commas_with_remainder
target ||= prefix.pop || ""
- target.force_encoding 'UTF-8' if target.methods.include?(:encoding)
+ target.fix_encoding!
prefix = prefix.join(", ") + (prefix.empty? ? "" : ", ")
- prefix.force_encoding 'UTF-8' if prefix.methods.include?(:encoding)
+ prefix.fix_encoding!
- completions.select { |x| x =~ /^#{Regexp::escape target}/i }.sort_by { |c| [ContactManager.contact_for(c) ? 0 : 1, c] }.map { |x| [prefix + x, x] }
+ completions.select { |x| x =~ /^#{Regexp::escape target}/iu }.sort_by { |c| [ContactManager.contact_for(c) ? 0 : 1, c] }.map { |x| [prefix + x, x] }
end
end
@@ -499,7 +443,7 @@ EOS
if dir
[[s.sub(full, dir), "~#{name}"]]
else
- users.select { |u| u =~ /^#{Regexp::escape name}/ }.map do |u|
+ users.select { |u| u =~ /^#{Regexp::escape name}/u }.map do |u|
[s.sub("~#{name}", "~#{u}"), "~#{u}"]
end
end
@@ -558,6 +502,7 @@ EOS
completions = (recent + contacts).flatten.uniq
completions += HookManager.run("extra-contact-addresses") || []
+
answer = BufferManager.ask_many_emails_with_completions domain, question, completions, default
if answer
@@ -594,15 +539,15 @@ EOS
end
while true
- c = Ncurses.safe_nonblocking_getch
- next unless c # getch timeout
+ c = Ncurses::CharCode.get
+ next unless c.present? # getch timeout
break unless tf.handle_input c # process keystroke
if tf.new_completions?
kill_buffer completion_buf if completion_buf
shorts = tf.completions.map { |full, short| short }
- prefix_len = shorts.shared_prefix.length
+ prefix_len = shorts.shared_prefix(caseless=true).length
mode = CompletionMode.new shorts, :header => "Possible completions for \"#{tf.value}\": ", :prefix_len => prefix_len
completion_buf = spawn "<completions>", mode, :height => 10
@@ -626,7 +571,7 @@ EOS
tf.deactivate
draw_screen :sync => false, :status => status, :title => title
end
- tf.value.tap { |x| x.force_encoding Encoding::UTF_8 if x && x.respond_to?(:encoding) }
+ tf.value.tap { |x| x }
end
def ask_getch question, accept=nil
@@ -647,10 +592,11 @@ EOS
ret = nil
done = false
until done
- key = Ncurses.safe_nonblocking_getch or next
- if key == Ncurses::KEY_CANCEL
+ key = Ncurses::CharCode.get
+ next if key.empty?
+ if key.is_keycode? Ncurses::KEY_CANCEL
done = true
- elsif accept.nil? || accept.empty? || accept.member?(key)
+ elsif accept.nil? || accept.empty? || accept.member?(key.code)
ret = key
done = true
end
@@ -668,7 +614,7 @@ EOS
## returns true (y), false (n), or nil (ctrl-g / cancel)
def ask_yes_or_no question
case(r = ask_getch question, "ynYN")
- when ?y.ord, ?Y.ord
+ when ?y, ?Y
true
when nil
nil
@@ -713,7 +659,7 @@ EOS
end
Ncurses.mutex.lock unless opts[:sync] == false
- Ncurses.attrset Colormap.color_for(:none)
+ Ncurses.attrset Colormap.color_for(:text_color)
adj = @asking ? 2 : 1
m.each_with_index do |s, i|
Ncurses.mvaddstr Ncurses.rows - i - adj, 0, s + (" " * [Ncurses.cols - s.length, 0].max)
diff --git a/lib/sup/client.rb b/lib/sup/client.rb
deleted file mode 100644
index 23da5c6..0000000
--- a/lib/sup/client.rb
+++ /dev/null
@@ -1,92 +0,0 @@
-require 'sup/protocol'
-
-module Redwood
-
-class Client < EM::P::RedwoodClient
- def initialize *a
- @next_tag = 1
- @cbs = {}
- super *a
- end
-
- def mktag &b
- @next_tag.tap do |x|
- @cbs[x] = b
- @next_tag += 1
- end
- end
-
- def rmtag tag
- @cbs.delete tag
- end
-
- def query qstr, offset, limit, raw, &b
- tag = mktag do |type,tag,args|
- if type == 'message'
- b.call args
- else
- fail unless type == 'done'
- b.call nil
- rmtag tag
- end
- end
- send_message 'query', tag,
- 'query' => qstr,
- 'offset' => offset,
- 'limit' => limit,
- 'raw' => raw
- end
-
- def count qstr, &b
- tag = mktag do |type,tag,args|
- b.call args['count']
- rmtag tag
- end
- send_message 'count', tag,
- 'query' => qstr
- end
-
- def label qstr, add, remove, &b
- tag = mktag do |type,tag,args|
- b.call
- rmtag tag
- end
- send_message 'label', tag,
- 'query' => qstr,
- 'add' => add,
- 'remove' => remove
- end
-
- def add raw, labels, &b
- tag = mktag do |type,tag,args|
- b.call
- rmtag tag
- end
- send_message 'add', tag,
- 'raw' => raw,
- 'labels' => labels
- end
-
- def thread msg_id, raw, &b
- tag = mktag do |type,tag,args|
- if type == 'message'
- b.call args
- else
- fail unless type == 'done'
- b.call nil
- rmtag tag
- end
- end
-
- send_message 'thread', tag,
- 'message_id' => msg_id,
- 'raw' => raw
- end
-
- def receive_message type, tag, args
- cb = @cbs[tag] or fail "invalid tag #{tag.inspect}"
- cb[type, tag, args]
- end
-end
-
-end
diff --git a/lib/sup/colormap.rb b/lib/sup/colormap.rb
index 2957de7..1474d59 100644
--- a/lib/sup/colormap.rb
+++ b/lib/sup/colormap.rb
@@ -1,4 +1,4 @@
-module Curses
+module Ncurses
COLOR_DEFAULT = -1
NUM_COLORS = `tput colors`.to_i
@@ -9,9 +9,9 @@ module Curses
end
## numeric colors
- Curses::NUM_COLORS.times { |x| color! x, x }
+ Ncurses::NUM_COLORS.times { |x| color! x, x }
- if Curses::NUM_COLORS == 256
+ if Ncurses::NUM_COLORS == 256
## xterm 6x6x6 color cube
6.times { |x| 6.times { |y| 6.times { |z| color! "c#{x}#{y}#{z}", 16 + z + 6*y + 36*x } } }
@@ -26,6 +26,7 @@ class Colormap
@@instance = nil
DEFAULT_COLORS = {
+ :text => { :fg => "white", :bg => "black" },
:status => { :fg => "white", :bg => "blue", :attrs => ["bold"] },
:index_old => { :fg => "white", :bg => "default" },
:index_new => { :fg => "white", :bg => "default", :attrs => ["bold"] },
@@ -49,6 +50,7 @@ class Colormap
:quote => { :fg => "yellow", :bg => "default" },
:sig => { :fg => "yellow", :bg => "default" },
:to_me => { :fg => "green", :bg => "default" },
+ :with_attachment => { :fg => "green", :bg => "default" },
:starred => { :fg => "yellow", :bg => "default", :attrs => ["bold"] },
:starred_patina => { :fg => "yellow", :bg => "green", :attrs => ["bold"] },
:alternate_starred_patina => { :fg => "yellow", :bg => "blue", :attrs => ["bold"] },
@@ -70,7 +72,7 @@ class Colormap
def initialize
raise "only one instance can be created" if @@instance
@@instance = self
- @color_pairs = {[Curses::COLOR_WHITE, Curses::COLOR_BLACK] => 0}
+ @color_pairs = {[Ncurses::COLOR_WHITE, Ncurses::COLOR_BLACK] => 0}
@users = []
@next_id = 0
reset
@@ -80,15 +82,15 @@ class Colormap
def reset
@entries = {}
@highlights = { :none => highlight_sym(:none)}
- @entries[highlight_sym(:none)] = highlight_for(Curses::COLOR_WHITE,
- Curses::COLOR_BLACK,
+ @entries[highlight_sym(:none)] = highlight_for(Ncurses::COLOR_WHITE,
+ Ncurses::COLOR_BLACK,
[]) + [nil]
end
def add sym, fg, bg, attr=nil, highlight=nil
raise ArgumentError, "color for #{sym} already defined" if @entries.member? sym
- raise ArgumentError, "color '#{fg}' unknown" unless (-1...Curses::NUM_COLORS).include? fg
- raise ArgumentError, "color '#{bg}' unknown" unless (-1...Curses::NUM_COLORS).include? bg
+ raise ArgumentError, "color '#{fg}' unknown" unless (-1...Ncurses::NUM_COLORS).include? fg
+ raise ArgumentError, "color '#{bg}' unknown" unless (-1...Ncurses::NUM_COLORS).include? bg
attrs = [attr].flatten.compact
@entries[sym] = [fg, bg, attrs, nil]
@@ -108,33 +110,33 @@ class Colormap
def highlight_for fg, bg, attrs
hfg =
case fg
- when Curses::COLOR_BLUE
- Curses::COLOR_WHITE
- when Curses::COLOR_YELLOW, Curses::COLOR_GREEN
+ when Ncurses::COLOR_BLUE
+ Ncurses::COLOR_WHITE
+ when Ncurses::COLOR_YELLOW, Ncurses::COLOR_GREEN
fg
else
- Curses::COLOR_BLACK
+ Ncurses::COLOR_BLACK
end
hbg =
case bg
- when Curses::COLOR_CYAN
- Curses::COLOR_YELLOW
- when Curses::COLOR_YELLOW
- Curses::COLOR_BLUE
+ when Ncurses::COLOR_CYAN
+ Ncurses::COLOR_YELLOW
+ when Ncurses::COLOR_YELLOW
+ Ncurses::COLOR_BLUE
else
- Curses::COLOR_CYAN
+ Ncurses::COLOR_CYAN
end
attrs =
- if fg == Curses::COLOR_WHITE && attrs.include?(Curses::A_BOLD)
- [Curses::A_BOLD]
+ if fg == Ncurses::COLOR_WHITE && attrs.include?(Ncurses::A_BOLD)
+ [Ncurses::A_BOLD]
else
case hfg
- when Curses::COLOR_BLACK
+ when Ncurses::COLOR_BLACK
[]
else
- [Curses::A_BOLD]
+ [Ncurses::A_BOLD]
end
end
[hfg, hbg, attrs]
@@ -142,7 +144,7 @@ class Colormap
def color_for sym, highlight=false
sym = @highlights[sym] if highlight
- return Curses::COLOR_BLACK if sym == :none
+ return Ncurses::COLOR_BLACK if sym == :none
raise ArgumentError, "undefined color #{sym}" unless @entries.member? sym
## if this color is cached, return it
@@ -152,14 +154,14 @@ class Colormap
if(cp = @color_pairs[[fg, bg]])
## nothing
else ## need to get a new colorpair
- @next_id = (@next_id + 1) % Curses::MAX_PAIRS
+ @next_id = (@next_id + 1) % Ncurses::MAX_PAIRS
@next_id += 1 if @next_id == 0 # 0 is always white on black
id = @next_id
debug "colormap: for color #{sym}, using id #{id} -> #{fg}, #{bg}"
- Curses.init_pair id, fg, bg or raise ArgumentError,
+ Ncurses.init_pair id, fg, bg or raise ArgumentError,
"couldn't initialize curses color pair #{fg}, #{bg} (key #{id})"
- cp = @color_pairs[[fg, bg]] = Curses.color_pair(id)
+ cp = @color_pairs[[fg, bg]] = Ncurses.COLOR_PAIR(id)
## delete the old mapping, if it exists
if @users[cp]
@users[cp].each do |usym|
@@ -189,24 +191,29 @@ class Colormap
Redwood::load_yaml_obj Redwood::COLOR_FN
end
+ ## Set attachment sybmol to sane default for existing colorschemes
+ if user_colors and user_colors.has_key? :to_me
+ user_colors[:with_attachment] = user_colors[:to_me] unless user_colors.has_key? :with_attachment
+ end
+
Colormap::DEFAULT_COLORS.merge(user_colors||{}).each_pair do |k, v|
fg = begin
- Curses.const_get "COLOR_#{v[:fg].to_s.upcase}"
+ Ncurses.const_get "COLOR_#{v[:fg].to_s.upcase}"
rescue NameError
warn "there is no color named \"#{v[:fg]}\""
- Curses::COLOR_GREEN
+ Ncurses::COLOR_GREEN
end
bg = begin
- Curses.const_get "COLOR_#{v[:bg].to_s.upcase}"
+ Ncurses.const_get "COLOR_#{v[:bg].to_s.upcase}"
rescue NameError
warn "there is no color named \"#{v[:bg]}\""
- Curses::COLOR_RED
+ Ncurses::COLOR_RED
end
attrs = (v[:attrs]||[]).map do |a|
begin
- Curses.const_get "A_#{a.upcase}"
+ Ncurses.const_get "A_#{a.upcase}"
rescue NameError
warn "there is no attribute named \"#{a}\", using fallback."
nil
diff --git a/lib/sup/contact.rb b/lib/sup/contact.rb
index 382896d..a2288ff 100644
--- a/lib/sup/contact.rb
+++ b/lib/sup/contact.rb
@@ -1,7 +1,9 @@
+# encoding: utf-8
+
module Redwood
class ContactManager
- include Singleton
+ include Redwood::Singleton
def initialize fn
@fn = fn
@@ -27,9 +29,10 @@ class ContactManager
def contacts_with_aliases; @a2p.values.uniq end
def update_alias person, aalias=nil
- if(old_aalias = @p2a[person]) # remove old alias
+ old_aalias = @p2a[person]
+ if(old_aalias != nil and old_aalias != "") # remove old alias
@a2p.delete old_aalias
- @e2p.delete old_aalias.email
+ @e2p.delete person.email
end
@p2a[person] = aalias
unless aalias.nil? || aalias.empty?
@@ -53,7 +56,7 @@ class ContactManager
def is_aliased_contact? person; !@p2a[person].nil? end
def save
- File.open(@fn, "w") do |f|
+ File.open(@fn, "w:UTF-8") do |f|
@p2a.sort_by { |(p, a)| [p.full_address, a] }.each do |(p, a)|
f.puts "#{a || ''}: #{p.full_address}"
end
diff --git a/lib/sup/crypto.rb b/lib/sup/crypto.rb
index bc96f88..fd8f824 100644
--- a/lib/sup/crypto.rb
+++ b/lib/sup/crypto.rb
@@ -6,7 +6,7 @@ end
module Redwood
class CryptoManager
- include Singleton
+ include Redwood::Singleton
class Error < StandardError; end
@@ -39,6 +39,20 @@ from_key: the key that generated the signature (class is GPGME::Key)
Return value: an array of lines of output
EOS
+ HookManager.register "gpg-expand-keys", <<EOS
+Runs when the list of encryption recipients is created, allowing you to
+replace a recipient with one or more GPGME recipients. For example, you could
+replace the email address of a mailing list with the key IDs that belong to
+the recipients of that list. This is essentially what GPG groups do, which
+are not supported by GPGME.
+
+Variables:
+recipients: an array of recipients of the current email
+
+Return value: an array of recipients (email address or GPG key ID) to encrypt
+the email for
+EOS
+
def initialize
@mutex = Mutex.new
@@ -48,28 +62,39 @@ EOS
@gpgme_present =
begin
begin
- GPGME.check_version({:protocol => GPGME::PROTOCOL_OpenPGP})
+ begin
+ GPGME.check_version({:protocol => GPGME::PROTOCOL_OpenPGP})
+ rescue TypeError
+ GPGME.check_version(nil)
+ end
true
rescue GPGME::Error
false
+ rescue ArgumentError
+ # gpgme 2.0.0 raises this due to the hash->string conversion
+ false
end
rescue NameError
false
end
unless @gpgme_present
- @not_working_reason = ['gpgme gem not present',
+ @not_working_reason = ['gpgme gem not present',
'Install the gpgme gem in order to use signed and encrypted emails']
return
end
# if gpg2 is available, it will start gpg-agent if required
if (bin = `which gpg2`.chomp) =~ /\S/
- GPGME.set_engine_info GPGME::PROTOCOL_OpenPGP, bin, nil
+ if GPGME.respond_to?('set_engine_info')
+ GPGME.set_engine_info GPGME::PROTOCOL_OpenPGP, bin, nil
+ else
+ GPGME.gpgme_set_engine_info GPGME::PROTOCOL_OpenPGP, bin, nil
+ end
else
# check if the gpg-options hook uses the passphrase_callback
# if it doesn't then check if gpg agent is present
- gpg_opts = HookManager.run("gpg-options",
+ gpg_opts = HookManager.run("gpg-options",
{:operation => "sign", :options => {}}) || {}
if gpg_opts[:passphrase_callback].nil?
if ENV['GPG_AGENT_INFO'].nil?
@@ -94,22 +119,28 @@ EOS
end
def have_crypto?; @not_working_reason.nil? end
+ def not_working_reason; @not_working_reason end
def sign from, to, payload
return unknown_status(@not_working_reason) unless @not_working_reason.nil?
gpg_opts = {:protocol => GPGME::PROTOCOL_OpenPGP, :armor => true, :textmode => true}
gpg_opts.merge!(gen_sign_user_opts(from))
- gpg_opts = HookManager.run("gpg-options",
+ gpg_opts = HookManager.run("gpg-options",
{:operation => "sign", :options => gpg_opts}) || gpg_opts
-
begin
- sig = GPGME.detach_sign(format_payload(payload), gpg_opts)
+ if GPGME.respond_to?('detach_sign')
+ sig = GPGME.detach_sign(format_payload(payload), gpg_opts)
+ else
+ crypto = GPGME::Crypto.new
+ gpg_opts[:mode] = GPGME::SIG_MODE_DETACH
+ sig = crypto.sign(format_payload(payload), gpg_opts).read
+ end
rescue GPGME::Error => exc
raise Error, gpgme_exc_msg(exc.message)
end
- # if the key (or gpg-agent) is not available GPGME does not complain
+ # if the key (or gpg-agent) is not available GPGME does not complain
# but just returns a zero length string. Let's catch that
if sig.length == 0
raise Error, gpgme_exc_msg("GPG failed to generate signature: check that gpg-agent is running and your key is available.")
@@ -129,20 +160,26 @@ EOS
gpg_opts = {:protocol => GPGME::PROTOCOL_OpenPGP, :armor => true, :textmode => true}
if sign
- gpg_opts.merge!(gen_sign_user_opts(from))
+ gpg_opts.merge!(gen_sign_user_opts(from))
gpg_opts.merge!({:sign => true})
end
gpg_opts = HookManager.run("gpg-options",
{:operation => "encrypt", :options => gpg_opts}) || gpg_opts
recipients = to + [from]
-
+ recipients = HookManager.run("gpg-expand-keys", { :recipients => recipients }) || recipients
begin
- cipher = GPGME.encrypt(recipients, format_payload(payload), gpg_opts)
+ if GPGME.respond_to?('encrypt')
+ cipher = GPGME.encrypt(recipients, format_payload(payload), gpg_opts)
+ else
+ crypto = GPGME::Crypto.new
+ gpg_opts[:recipients] = recipients
+ cipher = crypto.encrypt(format_payload(payload), gpg_opts).read
+ end
rescue GPGME::Error => exc
raise Error, gpgme_exc_msg(exc.message)
end
- # if the key (or gpg-agent) is not available GPGME does not complain
+ # if the key (or gpg-agent) is not available GPGME does not complain
# but just returns a zero length string. Let's catch that
if cipher.length == 0
raise Error, gpgme_exc_msg("GPG failed to generate cipher text: check that gpg-agent is running and your key is available.")
@@ -223,7 +260,11 @@ EOS
plain_data = nil
else
signed_text_data = nil
- plain_data = GPGME::Data.empty
+ if GPGME::Data.respond_to?('empty')
+ plain_data = GPGME::Data.empty
+ else
+ plain_data = GPGME::Data.empty!
+ end
end
begin
ctx.verify(sig_data, signed_text_data, plain_data)
@@ -246,7 +287,11 @@ EOS
{:operation => "decrypt", :options => gpg_opts}) || gpg_opts
ctx = GPGME::Ctx.new(gpg_opts)
cipher_data = GPGME::Data.from_str(format_payload(payload))
- plain_data = GPGME::Data.empty
+ if GPGME::Data.respond_to?('empty')
+ plain_data = GPGME::Data.empty
+ else
+ plain_data = GPGME::Data.empty!
+ end
begin
ctx.decrypt_verify(cipher_data, plain_data)
rescue GPGME::Error => exc
@@ -259,7 +304,7 @@ EOS
end
plain_data.seek(0, IO::SEEK_SET)
output = plain_data.read
- output.force_encoding Encoding::ASCII_8BIT if output.respond_to? :force_encoding
+ output.transcode(Encoding::ASCII_8BIT, output.encoding)
## TODO: test to see if it is still necessary to do a 2nd run if verify
## fails.
@@ -274,7 +319,7 @@ EOS
# Look for Charset, they are put before the base64 crypted part
charsets = payload.body.split("\n").grep(/^Charset:/)
if !charsets.empty? and charsets[0] =~ /^Charset: (.+)$/
- output = Iconv.easy_decode($encoding, $1, output)
+ output.transcode($encoding, $1)
end
msg.body = output
else
@@ -298,7 +343,7 @@ EOS
msg = RMail::Parser.read output
if msg.header.content_type =~ %r{^multipart/} && !msg.multipart?
output = "MIME-Version: 1.0\n" + output
- output.force_encoding Encoding::ASCII_8BIT if output.respond_to? :force_encoding
+ output.fix_encoding!
msg = RMail::Parser.read output
end
end
@@ -314,7 +359,7 @@ private
def gpgme_exc_msg msg
err_msg = "Exception in GPGME call: #{msg}"
- info err_msg
+ #info err_msg
err_msg
end
@@ -346,7 +391,7 @@ private
else
first_sig = "Unknown error or empty signature"
end
- rescue EOFError
+ rescue EOFError
from_key = nil
first_sig = "No public key available for #{signature.fingerprint}"
end
@@ -397,16 +442,18 @@ private
# if gpgkey set for this account, then use that
# elsif only one account, then leave blank so gpg default will be user
# else set --local-user from_email_address
+ # NOTE: multiple signers doesn't seem to work with gpgme (2.0.2, 1.0.8)
+ #
def gen_sign_user_opts from
account = AccountManager.account_for from
account ||= AccountManager.default_account
if !account.gpgkey.nil?
- opts = {:signers => account.gpgkey}
+ opts = {:signer => account.gpgkey}
elsif AccountManager.user_emails.length == 1
# only one account
opts = {}
else
- opts = {:signers => from}
+ opts = {:signer => from}
end
opts
end
diff --git a/lib/sup/draft.rb b/lib/sup/draft.rb
index 58c45db..7266e0a 100644
--- a/lib/sup/draft.rb
+++ b/lib/sup/draft.rb
@@ -1,7 +1,7 @@
module Redwood
class DraftManager
- include Singleton
+ include Redwood::Singleton
attr_accessor :source
def initialize dir
@@ -32,14 +32,17 @@ class DraftLoader < Source
attr_accessor :dir
yaml_properties
- def initialize
- dir = Redwood::DRAFT_DIR
+ def initialize dir=Redwood::DRAFT_DIR
Dir.mkdir dir unless File.exists? dir
super DraftManager.source_name, true, false
@dir = dir
@cur_offset = 0
end
+ def properly_initialized?
+ !!(@dir && @cur_offset)
+ end
+
def id; DraftManager.source_id; end
def to_s; DraftManager.source_name; end
def uri; DraftManager.source_name; end
diff --git a/lib/sup/hook.rb b/lib/sup/hook.rb
index 9be1295..9dc4ca8 100644
--- a/lib/sup/hook.rb
+++ b/lib/sup/hook.rb
@@ -1,3 +1,5 @@
+require "sup/util"
+
module Redwood
class HookManager
@@ -17,6 +19,14 @@ class HookManager
end
end
+ def flash s
+ if BufferManager.instantiated?
+ BufferManager.flash s
+ else
+ log s
+ end
+ end
+
def log s
info "hook[#@__name]: #{s}"
end
@@ -59,7 +69,7 @@ class HookManager
end
end
- include Singleton
+ include Redwood::Singleton
@descs = {}
diff --git a/lib/sup/horizontal-selector.rb b/lib/sup/horizontal_selector.rb
similarity index 83%
rename from lib/sup/horizontal-selector.rb
rename to lib/sup/horizontal_selector.rb
index e6ec6dc..4b14410 100644
--- a/lib/sup/horizontal-selector.rb
+++ b/lib/sup/horizontal_selector.rb
@@ -1,6 +1,8 @@
module Redwood
class HorizontalSelector
+ class UnknownValue < StandardError; end
+
attr_accessor :label, :changed_by_user
def initialize label, vals, labels, base_color=:horizontal_selector_unselected_color, selected_color=:horizontal_selector_selected_color
@@ -13,7 +15,14 @@ class HorizontalSelector
@changed_by_user = false
end
- def set_to val; @selection = @vals.index(val) end
+ def set_to val
+ raise UnknownValue, val.inspect unless can_set_to? val
+ @selection = @vals.index(val)
+ end
+
+ def can_set_to? val
+ @vals.include? val
+ end
def val; @vals[@selection] end
diff --git a/lib/sup/idle.rb b/lib/sup/idle.rb
index a3a272f..8732340 100644
--- a/lib/sup/idle.rb
+++ b/lib/sup/idle.rb
@@ -3,7 +3,7 @@ require 'thread'
module Redwood
class IdleManager
- include Singleton
+ include Redwood::Singleton
IDLE_THRESHOLD = 60
diff --git a/lib/sup/index.rb b/lib/sup/index.rb
index 95f104a..48f3f1d 100644
--- a/lib/sup/index.rb
+++ b/lib/sup/index.rb
@@ -1,20 +1,27 @@
ENV["XAPIAN_FLUSH_THRESHOLD"] = "1000"
+ENV["XAPIAN_CJK_NGRAM"] = "1"
require 'xapian'
require 'set'
require 'fileutils'
require 'monitor'
+require 'chronic'
-begin
- require 'chronic'
- $have_chronic = true
-rescue LoadError => e
- debug "No 'chronic' gem detected. Install it for date/time query restrictions."
- $have_chronic = false
-end
+require "sup/util/query"
+require "sup/interactive_lock"
+require "sup/hook"
+require "sup/logger/singleton"
+
+
+if ([Xapian.major_version, Xapian.minor_version, Xapian.revision] <=> [1,2,15]) < 0
+ fail <<-EOF
+\n
+Xapian version 1.2.15 or higher required.
+If you have xapian-full-alaveteli installed,
+Please remove it by running `gem uninstall xapian-full-alaveteli`
+since it's been replaced by the xapian-ruby gem.
-if ([Xapian.major_version, Xapian.minor_version, Xapian.revision] <=> [1,2,1]) < 0
- fail "Xapian version 1.2.1 or higher required"
+ EOF
end
module Redwood
@@ -25,7 +32,6 @@ module Redwood
class Index
include InteractiveLock
- STEM_LANGUAGE = "english"
INDEX_VERSION = '4'
## dates are converted to integers for xapian, and are used for document ids,
@@ -49,7 +55,7 @@ EOS
def method_missing m; @h[m.to_s] end
end
- include Singleton
+ include Redwood::Singleton
def initialize dir=BASE_DIR
@dir = dir
@@ -92,9 +98,9 @@ EOS
end
end
- def load
+ def load failsafe=false
SourceManager.load_sources
- load_index
+ load_index failsafe
end
def save
@@ -104,7 +110,11 @@ EOS
save_index
end
- def load_index
+ def get_xapian
+ @xapian
+ end
+
+ def load_index failsafe=false
path = File.join(@dir, 'xapian')
if File.exists? path
@xapian = Xapian::WritableDatabase.new(path, Xapian::DB_OPEN)
@@ -128,7 +138,7 @@ EOS
def add_message m; sync_message m, true end
def update_message m; sync_message m, true end
- def update_message_state m; sync_message m, false end
+ def update_message_state m; sync_message m[0], false, m[1] end
def save_index
info "Flushing Xapian updates to disk. This may take a while..."
@@ -162,6 +172,32 @@ EOS
matchset.matches_estimated
end
+ ## check if a message is part of a killed thread
+ ## (warning: duplicates code below)
+ ## NOTE: We can be more efficient if we assume every
+ ## killed message that hasn't been initially added
+ ## to the indexi s this way
+ def message_joining_killed? m
+ return false unless doc = find_doc(m.id)
+ queue = doc.value(THREAD_VALUENO).split(',')
+ seen_threads = Set.new
+ seen_messages = Set.new [m.id]
+ while not queue.empty?
+ thread_id = queue.pop
+ next if seen_threads.member? thread_id
+ return true if thread_killed?(thread_id)
+ seen_threads << thread_id
+ docs = term_docids(mkterm(:thread, thread_id)).map { |x| @xapian.document x }
+ docs.each do |doc|
+ msgid = doc.value MSGID_VALUENO
+ next if seen_messages.member? msgid
+ seen_messages << msgid
+ queue.concat doc.value(THREAD_VALUENO).split(',')
+ end
+ end
+ false
+ end
+
## yield all messages in the thread containing 'm' by repeatedly
## querying the index. yields pairs of message ids and
## message-building lambdas, so that building an unwanted message
@@ -242,11 +278,11 @@ EOS
## Yield each message-id matching query
EACH_ID_PAGE = 100
- def each_id query={}
+ def each_id query={}, ignore_neg_terms = true
offset = 0
page = EACH_ID_PAGE
- xapian_query = build_xapian_query query
+ xapian_query = build_xapian_query query, ignore_neg_terms
while true
ids = run_query_ids xapian_query, offset, (offset+page)
ids.each { |id| yield id }
@@ -256,12 +292,21 @@ EOS
end
## Yield each message matching query
- def each_message query={}, &b
- each_id query do |id|
+ ## The ignore_neg_terms parameter is used to display result even if
+ ## it contains "forbidden" labels such as :deleted, it is used in
+ ## Poll#poll_from when we need to get the location of a message that
+ ## may contain these labels
+ def each_message query={}, ignore_neg_terms = true, &b
+ each_id query, ignore_neg_terms do |id|
yield build_message(id)
end
end
+ # Search messages. Returns an Enumerator.
+ def find_messages query_expr
+ enum_for :each_message, parse_query(query_expr)
+ end
+
# wrap all future changes inside a transaction so they're done atomically
def begin_transaction
synchronize { @xapian.begin_transaction }
@@ -288,7 +333,7 @@ EOS
synchronize { get_entry(id)[:source_id] }
end
- ## Yields each tearm in the index that starts with prefix
+ ## Yields each term in the index that starts with prefix
def each_prefixed_term prefix
term = @xapian._dangerous_allterms_begin prefix
lastTerm = @xapian._dangerous_allterms_end prefix
@@ -302,14 +347,56 @@ EOS
## Yields (in lexicographical order) the source infos of all locations from
## the given source with the given source_info prefix
def each_source_info source_id, prefix='', &b
- prefix = mkterm :location, source_id, prefix
- each_prefixed_term prefix do |x|
- yield x[prefix.length..-1]
+ p = mkterm :location, source_id, prefix
+ each_prefixed_term p do |x|
+ yield prefix + x[p.length..-1]
end
end
class ParseError < StandardError; end
+ # Stemmed
+ NORMAL_PREFIX = {
+ 'subject' => {:prefix => 'S', :exclusive => false},
+ 'body' => {:prefix => 'B', :exclusive => false},
+ 'from_name' => {:prefix => 'FN', :exclusive => false},
+ 'to_name' => {:prefix => 'TN', :exclusive => false},
+ 'name' => {:prefix => %w(FN TN), :exclusive => false},
+ 'attachment' => {:prefix => 'A', :exclusive => false},
+ 'email_text' => {:prefix => 'E', :exclusive => false},
+ '' => {:prefix => %w(S B FN TN A E), :exclusive => false},
+ }
+
+ # Unstemmed
+ BOOLEAN_PREFIX = {
+ 'type' => {:prefix => 'K', :exclusive => true},
+ 'from_email' => {:prefix => 'FE', :exclusive => false},
+ 'to_email' => {:prefix => 'TE', :exclusive => false},
+ 'email' => {:prefix => %w(FE TE), :exclusive => false},
+ 'date' => {:prefix => 'D', :exclusive => true},
+ 'label' => {:prefix => 'L', :exclusive => false},
+ 'source_id' => {:prefix => 'I', :exclusive => true},
+ 'attachment_extension' => {:prefix => 'O', :exclusive => false},
+ 'msgid' => {:prefix => 'Q', :exclusive => true},
+ 'id' => {:prefix => 'Q', :exclusive => true},
+ 'thread' => {:prefix => 'H', :exclusive => false},
+ 'ref' => {:prefix => 'R', :exclusive => false},
+ 'location' => {:prefix => 'J', :exclusive => false},
+ }
+
+ PREFIX = NORMAL_PREFIX.merge BOOLEAN_PREFIX
+
+ COMPL_OPERATORS = %w[AND OR NOT]
+ COMPL_PREFIXES = (
+ %w[
+ from to
+ is has label
+ filename filetypem
+ before on in during after
+ limit
+ ] + NORMAL_PREFIX.keys + BOOLEAN_PREFIX.keys
+ ).map{|p|"#{p}:"} + COMPL_OPERATORS
+
## parse a query string from the user. returns a query object
## that can be passed to any index method with a 'query'
## argument.
@@ -389,27 +476,25 @@ EOS
end
end
- if $have_chronic
- lastdate = 2<<32 - 1
- firstdate = 0
- subs = subs.gsub(/\b(before|on|in|during|after):(\((.+?)\)\B|(\S+)\b)/) do
- field, datestr = $1, ($3 || $4)
- realdate = Chronic.parse datestr, :guess => false, :context => :past
- if realdate
- case field
- when "after"
- debug "chronic: translated #{field}:#{datestr} to #{realdate.end}"
- "date:#{realdate.end.to_i}..#{lastdate}"
- when "before"
- debug "chronic: translated #{field}:#{datestr} to #{realdate.begin}"
- "date:#{firstdate}..#{realdate.end.to_i}"
- else
- debug "chronic: translated #{field}:#{datestr} to #{realdate}"
- "date:#{realdate.begin.to_i}..#{realdate.end.to_i}"
- end
+ lastdate = 2<<32 - 1
+ firstdate = 0
+ subs = subs.gsub(/\b(before|on|in|during|after):(\((.+?)\)\B|(\S+)\b)/) do
+ field, datestr = $1, ($3 || $4)
+ realdate = Chronic.parse datestr, :guess => false, :context => :past
+ if realdate
+ case field
+ when "after"
+ debug "chronic: translated #{field}:#{datestr} to #{realdate.end}"
+ "date:#{realdate.end.to_i}..#{lastdate}"
+ when "before"
+ debug "chronic: translated #{field}:#{datestr} to #{realdate.begin}"
+ "date:#{firstdate}..#{realdate.end.to_i}"
else
- raise ParseError, "can't understand date #{datestr.inspect}"
+ debug "chronic: translated #{field}:#{datestr} to #{realdate}"
+ "date:#{realdate.begin.to_i}..#{realdate.end.to_i}"
end
+ else
+ raise ParseError, "can't understand date #{datestr.inspect}"
end
end
@@ -428,7 +513,7 @@ EOS
qp = Xapian::QueryParser.new
qp.database = @xapian
- qp.stemmer = Xapian::Stem.new(STEM_LANGUAGE)
+ qp.stemmer = Xapian::Stem.new($config[:stem_language])
qp.stemming_strategy = Xapian::QueryParser::STEM_SOME
qp.default_op = Xapian::Query::OP_AND
qp.add_valuerangeprocessor(Xapian::NumberValueRangeProcessor.new(DATE_VALUENO, 'date:', true))
@@ -441,7 +526,7 @@ EOS
raise ParseError, "xapian query parser error: #{e}"
end
- debug "parsed xapian query: #{xapian_query.description}"
+ debug "parsed xapian query: #{Util::Query.describe(xapian_query, subs)}"
raise ParseError if xapian_query.nil? or xapian_query.empty?
query[:qobj] = xapian_query
@@ -449,14 +534,18 @@ EOS
query
end
- def save_thread t
+ def save_message m, sync_back = true
+ if @sync_worker
+ @sync_queue << [m, sync_back]
+ else
+ update_message_state [m, sync_back]
+ end
+ m.clear_dirty
+ end
+
+ def save_thread t, sync_back = true
t.each_dirty_message do |m|
- if @sync_worker
- @sync_queue << m
- else
- update_message_state m
- end
- m.clear_dirty
+ save_message m, sync_back
end
end
@@ -482,37 +571,6 @@ EOS
private
- # Stemmed
- NORMAL_PREFIX = {
- 'subject' => {:prefix => 'S', :exclusive => false},
- 'body' => {:prefix => 'B', :exclusive => false},
- 'from_name' => {:prefix => 'FN', :exclusive => false},
- 'to_name' => {:prefix => 'TN', :exclusive => false},
- 'name' => {:prefix => %w(FN TN), :exclusive => false},
- 'attachment' => {:prefix => 'A', :exclusive => false},
- 'email_text' => {:prefix => 'E', :exclusive => false},
- '' => {:prefix => %w(S B FN TN A E), :exclusive => false},
- }
-
- # Unstemmed
- BOOLEAN_PREFIX = {
- 'type' => {:prefix => 'K', :exclusive => true},
- 'from_email' => {:prefix => 'FE', :exclusive => false},
- 'to_email' => {:prefix => 'TE', :exclusive => false},
- 'email' => {:prefix => %w(FE TE), :exclusive => false},
- 'date' => {:prefix => 'D', :exclusive => true},
- 'label' => {:prefix => 'L', :exclusive => false},
- 'source_id' => {:prefix => 'I', :exclusive => true},
- 'attachment_extension' => {:prefix => 'O', :exclusive => false},
- 'msgid' => {:prefix => 'Q', :exclusive => true},
- 'id' => {:prefix => 'Q', :exclusive => true},
- 'thread' => {:prefix => 'H', :exclusive => false},
- 'ref' => {:prefix => 'R', :exclusive => false},
- 'location' => {:prefix => 'J', :exclusive => false},
- }
-
- PREFIX = NORMAL_PREFIX.merge BOOLEAN_PREFIX
-
MSGID_VALUENO = 0
THREAD_VALUENO = 1
DATE_VALUENO = 2
@@ -594,7 +652,7 @@ EOS
end
Q = Xapian::Query
- def build_xapian_query opts
+ def build_xapian_query opts, ignore_neg_terms = true
labels = ([opts[:label]] + (opts[:labels] || [])).compact
neglabels = [:spam, :deleted, :killed].reject { |l| (labels.include? l) || opts.member?("load_#{l}".intern) }
pos_terms, neg_terms = [], []
@@ -610,7 +668,7 @@ EOS
pos_terms << Q.new(Q::OP_OR, participant_terms)
end
- neg_terms.concat(neglabels.map { |l| mkterm(:label,l) })
+ neg_terms.concat(neglabels.map { |l| mkterm(:label,l) }) if ignore_neg_terms
pos_query = Q.new(Q::OP_AND, pos_terms)
neg_query = Q.new(Q::OP_OR, neg_terms)
@@ -622,7 +680,11 @@ EOS
end
end
- def sync_message m, overwrite
+ def sync_message m, overwrite, sync_back = true
+ ## TODO: we should not save the message if the sync_back failed
+ ## since it would overwrite the location field
+ m.sync_back if sync_back
+
doc = synchronize { find_doc(m.id) }
existed = doc != nil
doc ||= Xapian::Document.new
@@ -804,7 +866,7 @@ class Xapian::Document
def index_text text, prefix, weight=1
term_generator = Xapian::TermGenerator.new
- term_generator.stemmer = Xapian::Stem.new(Redwood::Index::STEM_LANGUAGE)
+ term_generator.stemmer = Xapian::Stem.new($config[:stem_language])
term_generator.document = self
term_generator.index_text text, weight, prefix
end
diff --git a/lib/sup/interactive-lock.rb b/lib/sup/interactive-lock.rb
deleted file mode 100644
index 92a5ead..0000000
--- a/lib/sup/interactive-lock.rb
+++ /dev/null
@@ -1,74 +0,0 @@
-require 'fileutils'
-
-module Redwood
-
-## wrap a nice interactive layer on top of anything that has a #lock method
-## which throws a LockError which responds to #user, #host, #mtim, #pname, and
-## #pid.
-
-module InteractiveLock
- def pluralize number_of, kind; "#{number_of} #{kind}" + (number_of == 1 ? "" : "s") end
-
- def time_ago_in_words time
- secs = (Time.now - time).to_i
- mins = secs / 60
- time = if mins == 0
- pluralize secs, "second"
- else
- pluralize mins, "minute"
- end
- end
-
- DELAY = 5 # seconds
-
- def lock_interactively stream=$stderr
- begin
- Index.lock
- rescue Index::LockError => e
- stream.puts <<EOS
-Error: the index is locked by another process! User '#{e.user}' on
-host '#{e.host}' is running #{e.pname} with pid #{e.pid}.
-The process was alive as of at least #{time_ago_in_words e.mtime} ago.
-
-EOS
- stream.print "Should I ask that process to kill itself (y/n)? "
- stream.flush
-
- success = if $stdin.gets =~ /^\s*y(es)?\s*$/i
- stream.puts "Ok, trying to kill process..."
-
- begin
- Process.kill "TERM", e.pid.to_i
- sleep DELAY
- rescue Errno::ESRCH # no such process
- stream.puts "Hm, I couldn't kill it."
- end
-
- stream.puts "Let's try that again."
- begin
- Index.lock
- rescue Index::LockError => e
- stream.puts "I couldn't lock the index. The lockfile might just be stale."
- stream.print "Should I just remove it and continue? (y/n) "
- stream.flush
-
- if $stdin.gets =~ /^\s*y(es)?\s*$/i
- FileUtils.rm e.path
-
- stream.puts "Let's try that one more time."
- begin
- Index.lock
- true
- rescue Index::LockError => e
- end
- end
- end
- end
-
- stream.puts "Sorry, couldn't unlock the index." unless success
- success
- end
- end
-end
-
-end
diff --git a/lib/sup/interactive_lock.rb b/lib/sup/interactive_lock.rb
new file mode 100644
index 0000000..f417ef9
--- /dev/null
+++ b/lib/sup/interactive_lock.rb
@@ -0,0 +1,89 @@
+require 'fileutils'
+
+module Redwood
+
+## wrap a nice interactive layer on top of anything that has a #lock method
+## which throws a LockError which responds to #user, #host, #mtim, #pname, and
+## #pid.
+
+module InteractiveLock
+ def pluralize number_of, kind; "#{number_of} #{kind}" + (number_of == 1 ? "" : "s") end
+
+ def time_ago_in_words time
+ secs = (Time.now - time).to_i
+ mins = secs / 60
+ time = if mins == 0
+ pluralize secs, "second"
+ else
+ pluralize mins, "minute"
+ end
+ end
+
+ DELAY = 5 # seconds
+
+ def lock_interactively stream=$stderr
+ begin
+ Index.lock
+ rescue Index::LockError => e
+ begin
+ Process.kill 0, e.pid.to_i # 0 signal test the existence of PID
+ stream.puts <<EOS
+ Error: the index is locked by another process! User '#{e.user}' on
+ host '#{e.host}' is running #{e.pname} with pid #{e.pid}.
+ The process was alive as of at least #{time_ago_in_words e.mtime} ago.
+
+EOS
+ stream.print "Should I ask that process to kill itself (y/n)? "
+ stream.flush
+ if $stdin.gets =~ /^\s*y(es)?\s*$/i
+ Process.kill "TERM", e.pid.to_i
+ sleep DELAY
+ stream.puts "Let's try that again."
+ begin
+ Index.lock
+ rescue Index::LockError => e
+ stream.puts "I couldn't lock the index. The lockfile might just be stale."
+ stream.print "Should I just remove it and continue? (y/n) "
+ stream.flush
+ if $stdin.gets =~ /^\s*y(es)?\s*$/i
+ begin
+ FileUtils.rm e.path
+ rescue Errno::ENOENT
+ stream.puts "The lockfile doesn't exists. We continue."
+ end
+ stream.puts "Let's try that one more time."
+ begin
+ Index.lock
+ rescue Index::LockError => e
+ stream.puts "I couldn't unlock the index."
+ return false
+ end
+ return true
+ end
+ end
+ end
+ rescue Errno::ESRCH # no such process
+ stream.puts "I couldn't lock the index. The lockfile might just be stale."
+ begin
+ FileUtils.rm e.path
+ rescue Errno::ENOENT
+ stream.puts "The lockfile doesn't exists. We continue."
+ end
+ stream.puts "Let's try that one more time."
+ begin
+ sleep DELAY
+ Index.lock
+ rescue Index::LockError => e
+ stream.puts "I couldn't unlock the index."
+ return false
+ end
+ return true
+ end
+ stream.puts "Sorry, couldn't unlock the index."
+ return false
+ end
+ return true
+ end
+end
+
+end
diff --git a/lib/sup/keymap.rb b/lib/sup/keymap.rb
index 93060b8..5955309 100644
--- a/lib/sup/keymap.rb
+++ b/lib/sup/keymap.rb
@@ -1,3 +1,5 @@
+require 'sup/util/ncurses'
+
module Redwood
class Keymap
@@ -17,19 +19,19 @@ EOS
def self.keysym_to_keycode k
case k
- when :down then Curses::KEY_DOWN
- when :up then Curses::KEY_UP
- when :left then Curses::KEY_LEFT
- when :right then Curses::KEY_RIGHT
- when :page_down then Curses::KEY_NPAGE
- when :page_up then Curses::KEY_PPAGE
- when :backspace then Curses::KEY_BACKSPACE
- when :home then Curses::KEY_HOME
- when :end then Curses::KEY_END
+ when :down then Ncurses::KEY_DOWN
+ when :up then Ncurses::KEY_UP
+ when :left then Ncurses::KEY_LEFT
+ when :right then Ncurses::KEY_RIGHT
+ when :page_down then Ncurses::KEY_NPAGE
+ when :page_up then Ncurses::KEY_PPAGE
+ when :backspace then Ncurses::KEY_BACKSPACE
+ when :home then Ncurses::KEY_HOME
+ when :end then Ncurses::KEY_END
when :ctrl_l then "\f".ord
when :ctrl_g then "\a".ord
when :tab then "\t".ord
- when :enter, :return then 10 #Curses::KEY_ENTER
+ when :enter, :return then 10 #Ncurses::KEY_ENTER
else
if k.is_a?(String) && k.length == 1
k.ord
@@ -54,7 +56,7 @@ EOS
when :tab then "tab"
when " " then "<space>"
else
- Curses::keyname(keysym_to_keycode(k))
+ Ncurses::keyname(keysym_to_keycode(k))
end
end
@@ -96,11 +98,11 @@ EOS
end
def action_for kc
- action, help, keys = @map[kc]
+ action, help, keys = @map[kc.code]
[action, help]
end
- def has_key? k; @map[k] end
+ def has_key? k; @map[k.code] end
def keysyms; @map.values.map { |action, help, keys| keys }.flatten; end
diff --git a/lib/sup/label.rb b/lib/sup/label.rb
index 1699896..1053aad 100644
--- a/lib/sup/label.rb
+++ b/lib/sup/label.rb
@@ -1,14 +1,16 @@
+# encoding: utf-8
+
module Redwood
class LabelManager
- include Singleton
+ include Redwood::Singleton
## labels that have special semantics. user will be unable to
## add/remove these via normal label mechanisms.
- RESERVED_LABELS = [ :starred, :spam, :draft, :unread, :killed, :sent, :deleted, :inbox, :attachment ]
+ RESERVED_LABELS = [ :starred, :spam, :draft, :unread, :killed, :sent, :deleted, :inbox, :attachment, :forwarded, :replied ]
## labels that will typically be hidden from the user
- HIDDEN_RESERVED_LABELS = [ :starred, :unread, :attachment ]
+ HIDDEN_RESERVED_LABELS = [ :starred, :unread, :attachment, :forwarded, :replied ]
def initialize fn
@fn = fn
@@ -77,7 +79,7 @@ class LabelManager
def save
return unless @modified
- File.open(@fn, "w") { |f| f.puts @labels.keys.sort_by { |l| l.to_s } }
+ File.open(@fn, "w:UTF-8") { |f| f.puts @labels.keys.sort_by { |l| l.to_s } }
@new_labels = {}
end
end
diff --git a/lib/sup/logger.rb b/lib/sup/logger.rb
index 46e8a08..23da8d5 100644
--- a/lib/sup/logger.rb
+++ b/lib/sup/logger.rb
@@ -1,4 +1,4 @@
-require "sup"
+require "sup/util"
require 'stringio'
require 'thread'
@@ -8,7 +8,7 @@ module Redwood
## also keeps a record of all messages, so that adding a new sink will send all
## previous messages to it by default.
class Logger
- include Singleton
+ include Redwood::Singleton
LEVELS = %w(debug info warn error) # in order!
@@ -60,7 +60,10 @@ private
## actually distribute the message
def send_message m
@mutex.synchronize do
- @sinks.each { |sink| sink << m }
+ @sinks.each do |sink|
+ sink << m
+ sink.flush if sink.respond_to?(:flush) and level == "debug"
+ end
@buf << m
end
end
diff --git a/lib/sup/logger/singleton.rb b/lib/sup/logger/singleton.rb
new file mode 100644
index 0000000..3321785
--- /dev/null
+++ b/lib/sup/logger/singleton.rb
@@ -0,0 +1,10 @@
+# TODO: this is ugly. It's better to have a application singleton passed
+# down to lower level components instead of including logging methods in
+# class `Object'
+#
+# For now this is what we have to do.
+require "sup/logger"
+Redwood::Logger.init.add_sink $stderr
+class Object
+ include Redwood::LogsStuff
+end
diff --git a/lib/sup/maildir.rb b/lib/sup/maildir.rb
index 0c3061c..84236be 100644
--- a/lib/sup/maildir.rb
+++ b/lib/sup/maildir.rb
@@ -1,4 +1,5 @@
require 'uri'
+require 'set'
module Redwood
@@ -7,8 +8,8 @@ class Maildir < Source
MYHOSTNAME = Socket.gethostname
## remind me never to use inheritance again.
- yaml_properties :uri, :usual, :archived, :id, :labels
- def initialize uri, usual=true, archived=false, id=nil, labels=[]
+ yaml_properties :uri, :usual, :archived, :sync_back, :id, :labels
+ def initialize uri, usual=true, archived=false, sync_back=true, id=nil, labels=[]
super uri, usual, archived, id
@expanded_uri = Source.expand_filesystem_uri(uri)
uri = URI(@expanded_uri)
@@ -17,16 +18,28 @@ class Maildir < Source
raise ArgumentError, "maildir URI cannot have a host: #{uri.host}" if uri.host
raise ArgumentError, "maildir URI must have a path component" unless uri.path
+ @sync_back = sync_back
+ # sync by default if not specified
+ @sync_back = true if @sync_back.nil?
+
@dir = uri.path
@labels = Set.new(labels || [])
@mutex = Mutex.new
- @mtimes = { 'cur' => Time.at(0), 'new' => Time.at(0) }
+ @ctimes = { 'cur' => Time.at(0), 'new' => Time.at(0) }
end
def file_path; @dir end
def self.suggest_labels_for path; [] end
def is_source_for? uri; super || (uri == @expanded_uri); end
+ def supported_labels?
+ [:draft, :starred, :forwarded, :replied, :unread, :deleted]
+ end
+
+ def sync_back_enabled?
+ @sync_back
+ end
+
def store_message date, from_email, &block
stored = false
new_fn = new_maildir_basefn + ':2,S'
@@ -44,7 +57,7 @@ class Maildir < Source
f.fsync
end
- File.link tmp_path, new_path
+ File.safe_link tmp_path, new_path
stored = true
ensure
File.unlink tmp_path if File.exists? tmp_path
@@ -71,6 +84,14 @@ class Maildir < Source
with_file_for(id) { |f| RMail::Parser.read f }
end
+ def sync_back id, labels
+ synchronize do
+ debug "syncing back maildir message #{id} with flags #{labels.to_a}"
+ flags = maildir_reconcile_flags id, labels
+ maildir_mark_file id, flags
+ end
+ end
+
def raw_header id
ret = ""
with_file_for(id) do |f|
@@ -87,41 +108,78 @@ class Maildir < Source
## XXX use less memory
def poll
- @mtimes.each do |d,prev_mtime|
+ added = []
+ deleted = []
+ updated = []
+ @ctimes.each do |d,prev_ctime|
subdir = File.join @dir, d
debug "polling maildir #{subdir}"
raise FatalSourceError, "#{subdir} not a directory" unless File.directory? subdir
- mtime = File.mtime subdir
- next if prev_mtime >= mtime
- @mtimes[d] = mtime
-
- old_ids = benchmark(:maildir_read_index) { Enumerator.new(Index.instance, :each_source_info, self.id, "#{d}/").to_a }
- new_ids = benchmark(:maildir_read_dir) { Dir.glob("#{subdir}/*").map { |x| File.basename x }.sort }
- added = new_ids - old_ids
- deleted = old_ids - new_ids
+ ctime = File.ctime subdir
+ next if prev_ctime >= ctime
+ @ctimes[d] = ctime
+
+ old_ids = benchmark(:maildir_read_index) { Index.instance.enum_for(:each_source_info, self.id, "#{d}/").to_a }
+ new_ids = benchmark(:maildir_read_dir) { Dir.glob("#{subdir}/*").map { |x| File.join(d,File.basename(x)) }.sort }
+ added += new_ids - old_ids
+ deleted += old_ids - new_ids
debug "#{old_ids.size} in index, #{new_ids.size} in filesystem"
- debug "#{added.size} added, #{deleted.size} deleted"
+ end
- added.each_with_index do |id,i|
- yield :add,
- :info => File.join(d,id),
- :labels => @labels + maildir_labels(id) + [:inbox],
- :progress => i.to_f/(added.size+deleted.size)
- end
+ ## find updated mails by checking if an id is in both added and
+ ## deleted arrays, meaning that its flags changed or that it has
+ ## been moved, these ids need to be removed from added and deleted
+ add_to_delete = del_to_delete = []
+ map = Hash.new { |hash, key| hash[key] = [] }
+ deleted.each do |id_del|
+ map[maildir_data(id_del)[0]].push id_del
+ end
+ added.each do |id_add|
+ map[maildir_data(id_add)[0]].each do |id_del|
+ updated.push [ id_del, id_add ]
+ add_to_delete.push id_add
+ del_to_delete.push id_del
+ end
+ end
+ added -= add_to_delete
+ deleted -= del_to_delete
+ debug "#{added.size} added, #{deleted.size} deleted, #{updated.size} updated"
+ total_size = added.size+deleted.size+updated.size
- deleted.each_with_index do |id,i|
- yield :delete,
- :info => File.join(d,id),
- :progress => (i.to_f+added.size)/(added.size+deleted.size)
- end
+ added.each_with_index do |id,i|
+ yield :add,
+ :info => id,
+ :labels => @labels + maildir_labels(id) + [:inbox],
+ :progress => i.to_f/total_size
+ end
+
+ deleted.each_with_index do |id,i|
+ yield :delete,
+ :info => id,
+ :progress => (i.to_f+added.size)/total_size
+ end
+
+ updated.each_with_index do |id,i|
+ yield :update,
+ :old_info => id[0],
+ :new_info => id[1],
+ :labels => @labels + maildir_labels(id[1]),
+ :progress => (i.to_f+added.size+deleted.size)/total_size
end
nil
end
+ def labels? id
+ maildir_labels id
+ end
+
def maildir_labels id
(seen?(id) ? [] : [:unread]) +
(trashed?(id) ? [:deleted] : []) +
- (flagged?(id) ? [:starred] : [])
+ (flagged?(id) ? [:starred] : []) +
+ (passed?(id) ? [:forwarded] : []) +
+ (replied?(id) ? [:replied] : []) +
+ (draft?(id) ? [:draft] : [])
end
def draft? id; maildir_data(id)[2].include? "D"; end
@@ -131,13 +189,6 @@ class Maildir < Source
def seen? id; maildir_data(id)[2].include? "S"; end
def trashed? id; maildir_data(id)[2].include? "T"; end
- def mark_draft id; maildir_mark_file id, "D" unless draft? id; end
- def mark_flagged id; maildir_mark_file id, "F" unless flagged? id; end
- def mark_passed id; maildir_mark_file id, "P" unless passed? id; end
- def mark_replied id; maildir_mark_file id, "R" unless replied? id; end
- def mark_seen id; maildir_mark_file id, "S" unless seen? id; end
- def mark_trashed id; maildir_mark_file id, "T" unless trashed? id; end
-
def valid? id
File.exists? File.join(@dir, id)
end
@@ -159,25 +210,47 @@ private
end
def maildir_data id
- id =~ %r{^([^:]+):([12]),([DFPRST]*)$}
+ id = File.basename id
+ # Flags we recognize are DFPRST
+ id =~ %r{^([^:]+):([12]),([A-Za-z]*)$}
[($1 || id), ($2 || "2"), ($3 || "")]
end
- ## not thread-safe on msg
- def maildir_mark_file msg, flag
- orig_path = @ids_to_fns[msg]
- orig_base, orig_fn = File.split(orig_path)
- new_base = orig_base.slice(0..-4) + 'cur'
- tmp_base = orig_base.slice(0..-4) + 'tmp'
- md_base, md_ver, md_flags = maildir_data msg
- md_flags += flag; md_flags = md_flags.split(//).sort.join.squeeze
- new_path = File.join new_base, "#{md_base}:#{md_ver},#{md_flags}"
- tmp_path = File.join tmp_base, "#{md_base}:#{md_ver},#{md_flags}"
- File.link orig_path, tmp_path
- File.unlink orig_path
- File.link tmp_path, new_path
- File.unlink tmp_path
- @ids_to_fns[msg] = new_path
+ def maildir_reconcile_flags id, labels
+ new_flags = Set.new( maildir_data(id)[2].each_char )
+
+ # Set flags based on labels for the six flags we recognize
+ if labels.member? :draft then new_flags.add?( "D" ) else new_flags.delete?( "D" ) end
+ if labels.member? :starred then new_flags.add?( "F" ) else new_flags.delete?( "F" ) end
+ if labels.member? :forwarded then new_flags.add?( "P" ) else new_flags.delete?( "P" ) end
+ if labels.member? :replied then new_flags.add?( "R" ) else new_flags.delete?( "R" ) end
+ if not labels.member? :unread then new_flags.add?( "S" ) else new_flags.delete?( "S" ) end
+ if labels.member? :deleted or labels.member? :killed then new_flags.add?( "T" ) else new_flags.delete?( "T" ) end
+
+ ## Flags must be stored in ASCII order according to Maildir
+ ## documentation
+ new_flags.to_a.sort.join
+ end
+
+ def maildir_mark_file orig_path, flags
+ @mutex.synchronize do
+ new_base = (flags.include?("S")) ? "cur" : "new"
+ md_base, md_ver, md_flags = maildir_data orig_path
+
+ return if md_flags == flags
+
+ new_loc = File.join new_base, "#{md_base}:#{md_ver},#{flags}"
+ orig_path = File.join @dir, orig_path
+ new_path = File.join @dir, new_loc
+ tmp_path = File.join @dir, "tmp", "#{md_base}:#{md_ver},#{flags}"
+
+ File.safe_link orig_path, tmp_path
+ File.unlink orig_path
+ File.safe_link tmp_path, new_path
+ File.unlink tmp_path
+
+ new_loc
+ end
end
end
diff --git a/lib/sup/mbox.rb b/lib/sup/mbox.rb
index 95753c4..ba01fce 100644
--- a/lib/sup/mbox.rb
+++ b/lib/sup/mbox.rb
@@ -119,8 +119,6 @@ class MBox < Source
## we're just moving messages around on disk, than reading things
## into memory with raw_message.
##
- ## i hoped never to have to move shit around on disk but
- ## sup-sync-back has to do it.
def each_raw_message_line offset
@mutex.synchronize do
ensure_open
@@ -161,7 +159,7 @@ class MBox < Source
## TODO optimize this by iterating over allterms list backwards or
## storing source_info negated
def last_indexed_message
- benchmark(:mbox_read_index) { Enumerator.new(Index.instance, :each_source_info, self.id).map(&:to_i).max }
+ benchmark(:mbox_read_index) { Index.instance.enum_for(:each_source_info, self.id).map(&:to_i).max }
end
## offset of first new message or nil
diff --git a/lib/sup/message.rb b/lib/sup/message.rb
index 66745ca..7fcb155 100644
--- a/lib/sup/message.rb
+++ b/lib/sup/message.rb
@@ -1,3 +1,5 @@
+# encoding: UTF-8
+
require 'time'
module Redwood
@@ -69,7 +71,9 @@ class Message
return unless v
return v unless v.is_a? String
return unless v.size < MAX_HEADER_VALUE_SIZE # avoid regex blowup on spam
- Rfc2047.decode_to $encoding, Iconv.easy_decode($encoding, 'ASCII', v)
+ d = v.dup
+ d = d.transcode($encoding, 'ASCII')
+ Rfc2047.decode_to $encoding, d
end
def parse_header encoded_header
@@ -109,7 +113,9 @@ class Message
Time.now
end
- @subj = header["subject"] ? header["subject"].gsub(/\s+/, " ").gsub(/\s+$/, "") : DEFAULT_SUBJECT
+ subj = header["subject"]
+ subj = subj ? subj.fix_encoding! : nil
+ @subj = subj ? subj.gsub(/\s+/, " ").gsub(/\s+$/, "") : DEFAULT_SUBJECT
@to = Person.from_address_list header["to"]
@cc = Person.from_address_list header["cc"]
@bcc = Person.from_address_list header["bcc"]
@@ -259,6 +265,8 @@ class Message
message_to_chunks rmsg
rescue SourceError, SocketError, RMail::EncodingUnsupportedError => e
warn "problem reading message #{id}"
+ debug "could not load message: #{location.inspect}, exception: #{e.inspect}"
+
[Chunk::Text.new(error_message.split("\n"))]
end
end
@@ -285,6 +293,32 @@ EOS
location.each_raw_message_line &b
end
+ def sync_back
+ @locations.map { |l| l.sync_back @labels, self }.any? do
+ UpdateManager.relay self, :updated, self
+ end
+ end
+
+ def merge_labels_from_locations merge_labels
+ ## Get all labels from all locations
+ location_labels = Set.new([])
+
+ @locations.each do |l|
+ if l.valid?
+ location_labels = location_labels.union(l.labels?)
+ end
+ end
+
+ ## Add to the message labels the intersection between all location
+ ## labels and those we want to merge
+ location_labels = location_labels.intersection(merge_labels.to_set)
+
+ if not location_labels.empty?
+ @labels = @labels.union(location_labels)
+ @dirty = true
+ end
+ end
+
## returns all the content from a message that will be indexed
def indexable_content
load_from_source!
@@ -303,7 +337,7 @@ EOS
end
def indexable_chunks
- chunks.select { |c| c.is_a? Chunk::Text }
+ chunks.select { |c| c.is_a? Chunk::Text } || []
end
def indexable_subject
@@ -465,13 +499,21 @@ private
## they have no MIME multipart and just set the body content type to
## application/pgp. this handles that.
##
- ## TODO: unduplicate code between here and multipart_encrypted_to_chunks
+ ## TODO 1: unduplicate code between here and
+ ## multipart_encrypted_to_chunks
+ ## TODO 2: this only tries to decrypt. it cannot handle inline PGP
notice, sig, decryptedm = CryptoManager.decrypt m.body
if decryptedm # managed to decrypt
children = message_to_chunks decryptedm, true
[notice, sig].compact + children
else
- [notice]
+ ## try inline pgp signed
+ chunks = inline_gpg_to_chunks m.body, $encoding, (m.charset || $encoding)
+ if chunks
+ chunks
+ else
+ [notice]
+ end
end
else
filename =
@@ -524,7 +566,7 @@ private
## if there's no charset, use the current encoding as the charset.
## this ensures that the body is normalized to avoid non-displayable
## characters
- body = Iconv.easy_decode($encoding, m.charset || $encoding, m.decode)
+ body = m.decode.transcode($encoding, m.charset)
else
body = ""
end
@@ -539,12 +581,23 @@ private
## (and possible signed) inline GPG messages
def inline_gpg_to_chunks body, encoding_to, encoding_from
lines = body.split("\n")
+
+ # First case: Message is enclosed between
+ #
+ # -----BEGIN PGP SIGNED MESSAGE-----
+ # and
+ # -----END PGP SIGNED MESSAGE-----
+ #
+ # In some cases, END PGP SIGNED MESSAGE doesn't appear
+ # (and may leave strange -----BEGIN PGP SIGNATURE----- ?)
gpg = lines.between(GPG_SIGNED_START, GPG_SIGNED_END)
+ # between does not check if GPG_END actually exists
+ # Reference: http://permalink.gmane.org/gmane.mail.sup.devel/641
if !gpg.empty?
msg = RMail::Message.new
msg.body = gpg.join("\n")
- body = Iconv.easy_decode(encoding_to, encoding_from, body)
+ body = body.transcode(encoding_to, encoding_from)
lines = body.split("\n")
sig = lines.between(GPG_SIGNED_START, GPG_SIG_START)
startidx = lines.index(GPG_SIGNED_START)
@@ -552,14 +605,21 @@ private
before = startidx != 0 ? lines[0 .. startidx-1] : []
after = endidx ? lines[endidx+1 .. lines.size] : []
+ # sig contains BEGIN PGP SIGNED MESSAGE and END PGP SIGNATURE, so
+ # we ditch them. sig may also contain the hash used by PGP (with a
+ # newline), so we also skip them
+ sig_start = sig[1].match(/^Hash:/) ? 3 : 1
+ sig_end = sig.size-2
payload = RMail::Message.new
- payload.body = sig[1, sig.size-2].join("\n")
+ payload.body = sig[sig_start, sig_end].join("\n")
return [text_to_chunks(before, false),
CryptoManager.verify(nil, msg, false),
message_to_chunks(payload),
text_to_chunks(after, false)].flatten.compact
end
+ # Second case: Message is encrypted
+
gpg = lines.between(GPG_START, GPG_END)
# between does not check if GPG_END actually exists
if !gpg.empty? && !lines.index(GPG_END).nil?
@@ -615,7 +675,7 @@ private
## like ":a:a:a:a:a" that occurred in certain emails.
if line =~ QUOTE_PATTERN || (line =~ /:$/ && line =~ /\w/ && nextline =~ QUOTE_PATTERN)
newstate = :quote
- elsif line =~ SIG_PATTERN && (lines.length - i) < MAX_SIG_DISTANCE
+ elsif line =~ SIG_PATTERN && (lines.length - i) < MAX_SIG_DISTANCE && !lines[(i+1)..-1].index { |l| l =~ /^-- $/ }
newstate = :sig
elsif line =~ BLOCK_QUOTE_PATTERN
newstate = :block_quote
@@ -696,6 +756,24 @@ class Location
source.raw_message info
end
+ def sync_back labels, message
+ synced = false
+ return synced unless sync_back_enabled? and valid?
+ source.synchronize do
+ new_info = source.sync_back(@info, labels)
+ if new_info
+ @info = new_info
+ Index.sync_message message, true
+ synced = true
+ end
+ end
+ synced
+ end
+
+ def sync_back_enabled?
+ source.respond_to? :sync_back and $config[:sync_back_to_maildir] and source.sync_back_enabled?
+ end
+
## much faster than raw_message
def each_raw_message_line &b
source.each_raw_message_line info, &b
@@ -709,6 +787,10 @@ class Location
source.valid? info
end
+ def labels?
+ source.labels? info
+ end
+
def == o
o.source.id == source.id and o.info == info
end
diff --git a/lib/sup/message-chunks.rb b/lib/sup/message_chunks.rb
similarity index 78%
rename from lib/sup/message-chunks.rb
rename to lib/sup/message_chunks.rb
index 7a061d9..539a412 100644
--- a/lib/sup/message-chunks.rb
+++ b/lib/sup/message_chunks.rb
@@ -1,4 +1,8 @@
+# encoding: UTF-8
+
require 'tempfile'
+require 'rbconfig'
+require 'shellwords'
## Here we define all the "chunks" that a message is parsed
## into. Chunks are used by ThreadViewMode to render a message. Chunks
@@ -74,6 +78,7 @@ Return value:
The decoded text of the attachment, or nil if not decoded.
EOS
+
HookManager.register "mime-view", <<EOS
Views a non-text MIME attachment. This hook allows you to run
third-party programs for attachments that require such a thing (e.g.
@@ -99,8 +104,18 @@ EOS
attr_reader :content_type, :filename, :lines, :raw_content
bool_reader :quotable
+ ## store tempfile objects as class variables so that they
+ ## are not removed when the viewing process returns. they
+ ## should be garbage collected when the class variable is removed.
+ @@view_tempfiles = []
+
def initialize content_type, filename, encoded_content, sibling_types
@content_type = content_type.downcase
+ if Shellwords.escape(@content_type) != @content_type
+ warn "content_type #{@content_type} is not safe, changed to application/octet-stream"
+ @content_type = 'application/octet-stream'
+ end
+
@filename = filename
@quotable = false # changed to true if we can parse it through the
# mime-decode hook, or if it's plain text
@@ -115,7 +130,7 @@ EOS
when /^text\/plain\b/
@raw_content
else
- HookManager.run "mime-decode", :content_type => content_type,
+ HookManager.run "mime-decode", :content_type => @content_type,
:filename => lambda { write_to_disk },
:charset => encoded_content.charset,
:sibling_types => sibling_types
@@ -123,13 +138,19 @@ EOS
@lines = nil
if text
- text = text.transcode(encoded_content.charset || $encoding)
- @lines = text.gsub("\r\n", "\n").gsub(/\t/, " ").gsub(/\r/, "").split("\n")
+ text = text.transcode(encoded_content.charset || $encoding, text.encoding)
+ begin
+ @lines = text.gsub("\r\n", "\n").gsub(/\t/, " ").gsub(/\r/, "").split("\n")
+ rescue Encoding::CompatibilityError
+ @lines = text.fix_encoding!.gsub("\r\n", "\n").gsub(/\t/, " ").gsub(/\r/, "").split("\n")
+ debug "error while decoding message text, falling back to default encoding, expect errors in encoding: #{text.fix_encoding!}"
+ end
+
@quotable = true
end
end
- def color; :none end
+ def color; :text_color end
def patina_color; :attachment_color end
def patina_text
if expandable?
@@ -146,11 +167,11 @@ EOS
def initial_state; :open end
def viewable?; @lines.nil? end
def view_default! path
- case Config::CONFIG['arch']
+ case RbConfig::CONFIG['arch']
when /darwin/
- cmd = "open '#{path}'"
+ cmd = "open #{path}"
else
- cmd = "/usr/bin/run-mailcap --action=view '#{@content_type}:#{path}'"
+ cmd = "/usr/bin/run-mailcap --action=view #{@content_type}:#{path}"
end
debug "running: #{cmd.inspect}"
BufferManager.shell_out(cmd)
@@ -158,17 +179,36 @@ EOS
end
def view!
- path = write_to_disk
- ret = HookManager.run "mime-view", :content_type => @content_type,
- :filename => path
- ret || view_default!(path)
+ write_to_disk do |path|
+ ret = HookManager.run "mime-view", :content_type => @content_type,
+ :filename => path
+ ret || view_default!(path)
+ end
end
def write_to_disk
- file = Tempfile.new(["sup", @filename.gsub("/", "_") || "sup-attachment"])
- file.print @raw_content
- file.close
- file.path
+ begin
+ # Add the original extension to the generated tempfile name only if the
+ # extension is "safe" (won't be interpreted by the shell). Since
+ # Tempfile.new always generates safe file names this should prevent
+ # attacking the user with funny attachment file names.
+ tempname = if (File.extname @filename) =~ /^\.[[:alnum:]]+$/ then
+ ["sup-attachment", File.extname(@filename)]
+ else
+ "sup-attachment"
+ end
+
+ file = Tempfile.new(tempname)
+ file.print @raw_content
+ file.flush
+
+ @@view_tempfiles.push file # make sure the tempfile is not garbage collected before sup stops
+
+ yield file.path if block_given?
+ return file.path
+ ensure
+ file.close
+ end
end
## used when viewing the attachment as text
@@ -190,7 +230,7 @@ EOS
def quotable?; true end
def expandable?; false end
def viewable?; false end
- def color; :none end
+ def color; :text_color end
end
class Quote
@@ -228,7 +268,7 @@ EOS
class EnclosedMessage
attr_reader :lines
def initialize from, to, cc, date, subj
- @from = from ? "unknown sender" : from.full_adress
+ @from = from ? "unknown sender" : from.full_address
@to = to ? "" : to.map { |p| p.full_address }.join(", ")
@cc = cc ? "" : cc.map { |p| p.full_address }.join(", ")
if date
diff --git a/lib/sup/modes/buffer-list-mode.rb b/lib/sup/modes/buffer_list_mode.rb
similarity index 100%
rename from lib/sup/modes/buffer-list-mode.rb
rename to lib/sup/modes/buffer_list_mode.rb
diff --git a/lib/sup/modes/completion-mode.rb b/lib/sup/modes/completion_mode.rb
similarity index 82%
rename from lib/sup/modes/completion-mode.rb
rename to lib/sup/modes/completion_mode.rb
index 3cb2fad..4bf06e8 100644
--- a/lib/sup/modes/completion-mode.rb
+++ b/lib/sup/modes/completion_mode.rb
@@ -38,11 +38,11 @@ private
suffix = s[(@prefix_len + 1) .. -1]
char = s[@prefix_len].chr
- @lines.last += [[:none, sprintf("%#{max_length - suffix.length - 1}s", prefix)],
+ @lines.last += [[:text_color, sprintf("%#{max_length - suffix.length - 1}s", prefix)],
[:completion_character_color, char],
- [:none, suffix + INTERSTITIAL]]
+ [:text_color, suffix + INTERSTITIAL]]
else
- @lines.last += [[:none, sprintf("%#{max_length}s#{INTERSTITIAL}", s)]]
+ @lines.last += [[:text_color, sprintf("%#{max_length}s#{INTERSTITIAL}", s)]]
end
else
@lines << "" if i % num_per == 0
diff --git a/lib/sup/modes/compose-mode.rb b/lib/sup/modes/compose_mode.rb
similarity index 95%
rename from lib/sup/modes/compose-mode.rb
rename to lib/sup/modes/compose_mode.rb
index cf7a9de..16d44c8 100644
--- a/lib/sup/modes/compose-mode.rb
+++ b/lib/sup/modes/compose_mode.rb
@@ -1,3 +1,5 @@
+# encoding: utf-8
+
module Redwood
class ComposeMode < EditMessageMode
@@ -14,7 +16,7 @@ class ComposeMode < EditMessageMode
super :header => header, :body => (opts[:body] || [])
end
- def edit_message
+ def default_edit_message
edited = super
BufferManager.kill_buffer self.buffer unless edited
edited
@@ -29,7 +31,7 @@ class ComposeMode < EditMessageMode
mode = ComposeMode.new :from => from, :to => to, :cc => cc, :bcc => bcc, :subj => subj
BufferManager.spawn "New Message", mode
- mode.edit_message
+ mode.default_edit_message
end
end
diff --git a/lib/sup/modes/console-mode.rb b/lib/sup/modes/console_mode.rb
similarity index 82%
rename from lib/sup/modes/console-mode.rb
rename to lib/sup/modes/console_mode.rb
index 6442b9d..6c602cd 100644
--- a/lib/sup/modes/console-mode.rb
+++ b/lib/sup/modes/console_mode.rb
@@ -1,10 +1,13 @@
require 'pp'
+require "sup/service/label_service"
+
module Redwood
class Console
def initialize mode
@mode = mode
+ @label_service = LabelService.new
end
def query(query)
@@ -12,19 +15,27 @@ class Console
end
def add_labels(query, *labels)
- query(query).each { |m| m.labels += labels; m.save Index }
+ count = @label_service.add_labels(query, *labels)
+ print_buffer_dirty_msg count
end
def remove_labels(query, *labels)
- query(query).each { |m| m.labels -= labels; m.save Index }
+ count = @label_service.remove_labels(query, *labels)
+ print_buffer_dirty_msg count
+ end
+
+ def print_buffer_dirty_msg msg_count
+ puts "Scanned #{msg_count} messages."
+ puts "You might want to refresh open buffers with `@` key."
end
+ private :print_buffer_dirty_msg
def xapian; Index.instance.instance_variable_get :@xapian; end
def loglevel; Redwood::Logger.level; end
def set_loglevel(level); Redwood::Logger.level = level; end
- def special_methods; methods - Object.methods end
+ def special_methods; public_methods - Object.methods end
def puts x; @mode << "#{x.to_s.rstrip}\n" end
def p x; puts x.inspect end
diff --git a/lib/sup/modes/contact-list-mode.rb b/lib/sup/modes/contact_list_mode.rb
similarity index 96%
rename from lib/sup/modes/contact-list-mode.rb
rename to lib/sup/modes/contact_list_mode.rb
index 76461a9..3c2a8bd 100644
--- a/lib/sup/modes/contact-list-mode.rb
+++ b/lib/sup/modes/contact_list_mode.rb
@@ -71,7 +71,7 @@ class ContactListMode < LineCursorMode
when :regular
mode = ComposeMode.new :to => people
BufferManager.spawn "new message", mode
- mode.edit_message
+ mode.default_edit_message
end
end
@@ -130,7 +130,7 @@ protected
def text_for_contact p
aalias = ContactManager.alias_for(p) || ""
[[:tagged_color, @tags.tagged?(p) ? ">" : " "],
- [:none, sprintf("%-#{@awidth}s %-#{@nwidth}s %s", aalias, p.name, p.email)]]
+ [:text_color, sprintf("%-#{@awidth}s %-#{@nwidth}s %s", aalias, p.name, p.email)]]
end
def regen_text
diff --git a/lib/sup/modes/edit-message-async-mode.rb b/lib/sup/modes/edit_message_async_mode.rb
similarity index 98%
rename from lib/sup/modes/edit-message-async-mode.rb
rename to lib/sup/modes/edit_message_async_mode.rb
index 776d8bd..e06220e 100644
--- a/lib/sup/modes/edit-message-async-mode.rb
+++ b/lib/sup/modes/edit_message_async_mode.rb
@@ -29,11 +29,12 @@ EOS
@orig_mtime = File.mtime @file_path
@text = ["ASYNC MESSAGE EDIT",
- "", "Your message with subject:", msg_subject, "is saved in a file:", "", @file_path, "",
+ "", "Your message with subject:", msg_subject, "is saved in a file:", "", @file_path, "",
"You can edit your message in the editor of your choice and continue to",
"use sup while you edit your message.", "",
"Press <Enter> to have the file path copied to the clipboard.", "",
"When you have finished editing, select this buffer and press 'E'.",]
+ run_async_hook()
super()
end
diff --git a/lib/sup/modes/edit-message-mode.rb b/lib/sup/modes/edit_message_mode.rb
similarity index 82%
rename from lib/sup/modes/edit-message-mode.rb
rename to lib/sup/modes/edit_message_mode.rb
index 5947ffd..2b8b590 100644
--- a/lib/sup/modes/edit-message-mode.rb
+++ b/lib/sup/modes/edit_message_mode.rb
@@ -25,6 +25,16 @@ Return value:
use the default signature, or :none for no signature.
EOS
+ HookManager.register "check-attachment", <<EOS
+Do checks on the attachment filename
+Variables:
+ filename: the name of the attachment
+Return value:
+ A String (single line) containing a message why this attachment is not optimal
+ to be attached.
+ If it is ok just return an empty string or nil
+EOS
+
HookManager.register "before-edit", <<EOS
Modifies message body and headers before editing a new message. Variables
should be modified in place.
@@ -79,8 +89,8 @@ EOS
k.add :edit_to, "Edit To:", 't'
k.add :edit_cc, "Edit Cc:", 'c'
k.add :edit_subject, "Edit Subject", 's'
- k.add :edit_message, "Edit message", :enter
- k.add :edit_message_async, "Edit message asynchronously", 'E'
+ k.add :default_edit_message, "Edit message (default)", :enter
+ k.add :alternate_edit_message, "Edit message (alternate, asynchronously)", 'E'
k.add :save_as_draft, "Save as draft", 'P'
k.add :attach_file, "Attach a file", 'a'
k.add :delete_attachment, "Delete an attachment", 'd'
@@ -93,7 +103,6 @@ EOS
@header_lines = []
@body = opts.delete(:body) || []
- @body += sig_lines if $config[:edit_signature] && !opts.delete(:have_signature)
if opts[:attachments]
@attachments = opts[:attachments].values
@@ -106,16 +115,20 @@ EOS
begin
hostname = File.open("/etc/mailname", "r").gets.chomp
rescue
- nil
+ nil
end
hostname = Socket.gethostname if hostname.nil? or hostname.empty?
@message_id = "<#{Time.now.to_i}-sup-#{rand 10000}@#{hostname}>"
@edited = false
+ @sig_edited = false
@selectors = []
@selector_label_width = 0
@async_mode = nil
+ HookManager.run "before-edit", :header => @header, :body => @body
+
+ @account_selector = nil
# only show account selector if there is more than one email address
if $config[:account_selector] && AccountManager.user_emails.length > 1
## Duplicate e-mail strings to prevent a "can't modify frozen
@@ -128,13 +141,18 @@ EOS
HorizontalSelector.new "Account:", AccountManager.user_emails + [nil], user_emails_copy + ["Customized"]
if @header["From"] =~ /<?(\S+@(\S+?))>?$/
- @account_selector.set_to $1
- @account_user = ""
+ # TODO: this is ugly. might implement an AccountSelector and handle
+ # special cases more transparently.
+ account_from = @account_selector.can_set_to?($1) ? $1 : nil
+ @account_selector.set_to account_from
else
@account_selector.set_to nil
- @account_user = @header["From"]
end
+ # A single source of truth might better than duplicating this in both
+ # @account_user and @account_selector.
+ @account_user = @header["From"]
+
add_selector @account_selector
end
@@ -144,7 +162,6 @@ EOS
end
add_selector @crypto_selector if @crypto_selector
- HookManager.run "before-edit", :header => @header, :body => @body
if @crypto_selector
HookManager.run "crypto-mode", :header => @header, :body => @body, :crypto_selector => @crypto_selector
end
@@ -171,11 +188,11 @@ EOS
def handle_new_text header, body; end
def edit_message_or_field
- lines = DECORATION_LINES + @selectors.size
+ lines = (@selectors.empty? ? 0 : DECORATION_LINES) + @selectors.size
if lines > curpos
return
elsif (curpos - lines) >= @header_lines.length
- edit_message
+ default_edit_message
else
edit_field @header_lines[curpos - lines]
end
@@ -185,14 +202,65 @@ EOS
def edit_cc; edit_field "Cc" end
def edit_subject; edit_field "Subject" end
- def edit_message
- old_from = @header["From"] if @account_selector
-
- @file = Tempfile.new "sup.#{self.class.name.gsub(/.*::/, '').camel_to_hyphy}"
+ def save_message_to_file
+ sig = sig_lines.join("\n")
+ @file = Tempfile.new ["sup.#{self.class.name.gsub(/.*::/, '').camel_to_hyphy}", ".eml"]
@file.puts format_headers(@header - NON_EDITABLE_HEADERS).first
@file.puts
- @file.puts @body.join("\n")
+
+ begin
+ text = @body.join("\n")
+ rescue Encoding::CompatibilityError
+ text = @body.map { |x| x.fix_encoding! }.join("\n")
+ debug "encoding problem while writing message, trying to rescue, but expect errors: #{text}"
+ end
+
+ @file.puts text
+ @file.puts sig if ($config[:edit_signature] and !@sig_edited)
@file.close
+ end
+
+ def set_sig_edit_flag
+ sig = sig_lines.join("\n")
+ if $config[:edit_signature]
+ pbody = @body.map { |x| x.fix_encoding! }.join("\n").fix_encoding!
+ blen = pbody.length
+ slen = sig.length
+
+ if blen > slen and pbody[blen-slen..blen] == sig
+ @sig_edited = false
+ @body = pbody[0..blen-slen].fix_encoding!.split("\n")
+ else
+ @sig_edited = true
+ end
+ end
+ end
+
+ def default_edit_message
+ if $config[:always_edit_async]
+ return edit_message_async
+ else
+ return edit_message
+ end
+ end
+
+ def alternate_edit_message
+ if $config[:always_edit_async]
+ return edit_message
+ else
+ return edit_message_async
+ end
+ end
+
+ def edit_message
+ old_from = @header["From"] if @account_selector
+
+ begin
+ save_message_to_file
+ rescue SystemCallError => e
+ BufferManager.flash "Can't save message to file: #{e.message}"
+ return
+ end
editor = $config[:editor] || ENV['EDITOR'] || "/usr/bin/vi"
@@ -204,6 +272,7 @@ EOS
header, @body = parse_file @file.path
@header = header - NON_EDITABLE_HEADERS
+ set_sig_edit_flag
if @account_selector and @header["From"] != old_from
@account_user = @header["From"]
@@ -218,11 +287,12 @@ EOS
end
def edit_message_async
- @file = Tempfile.new ["sup.#{self.class.name.gsub(/.*::/, '').camel_to_hyphy}", ".eml"]
- @file.puts format_headers(@header - NON_EDITABLE_HEADERS).first
- @file.puts
- @file.puts @body.join("\n")
- @file.close
+ begin
+ save_message_to_file
+ rescue SystemCallError => e
+ BufferManager.flash "Can't save message to file: #{e.message}"
+ return
+ end
@mtime = File.mtime @file.path
@@ -245,6 +315,7 @@ EOS
header, @body = parse_file @file.path
@header = header - NON_EDITABLE_HEADERS
+ set_sig_edit_flag
handle_new_text @header, @body
update
@@ -270,6 +341,12 @@ EOS
def attach_file
fn = BufferManager.ask_for_filename :attachment, "File name (enter for browser): "
return unless fn
+ if HookManager.enabled? "check-attachment"
+ reason = HookManager.run("check-attachment", :filename => fn)
+ if reason
+ return unless BufferManager.ask_yes_or_no("#{reason} Attach anyway?")
+ end
+ end
begin
Dir[fn].each do |f|
@attachments << RMail::Message.make_file_attachment(f)
@@ -282,7 +359,7 @@ EOS
end
def delete_attachment
- i = curpos - @attachment_lines_offset - DECORATION_LINES - 2
+ i = curpos - @attachment_lines_offset - (@selectors.empty? ? 0 : DECORATION_LINES) - @selectors.size
if i >= 0 && i < @attachments.size && BufferManager.ask_yes_or_no("Delete attachment #{@attachment_names[i]}?")
@attachments.delete_at i
@attachment_names.delete_at i
@@ -362,7 +439,7 @@ protected
def regen_text
header, @header_lines = format_headers(@header - NON_EDITABLE_HEADERS) + [""]
@text = header + [""] + @body
- @text += sig_lines unless $config[:edit_signature]
+ @text += sig_lines unless @sig_edited
@attachment_lines_offset = 0
@@ -447,12 +524,12 @@ protected
m = build_message date
if HookManager.enabled? "sendmail"
- if not HookManager.run "sendmail", :message => m, :account => acct
- warn "Sendmail hook was not successful"
- return false
- end
+ if not HookManager.run "sendmail", :message => m, :account => acct
+ warn "Sendmail hook was not successful"
+ return false
+ end
else
- IO.popen(acct.sendmail, "w") { |p| p.puts m }
+ IO.popen(acct.sendmail, "w:UTF-8") { |p| p.puts m }
raise SendmailCommandFailed, "Couldn't execute #{acct.sendmail}" unless $? == 0
end
@@ -477,9 +554,10 @@ protected
m = RMail::Message.new
m.header["Content-Type"] = "text/plain; charset=#{$encoding}"
m.body = @body.join("\n")
- m.body += sig_lines.join("\n") unless $config[:edit_signature]
+ m.body += "\n" + sig_lines.join("\n") unless @sig_edited
## body must end in a newline or GPG signatures will be WRONG!
m.body += "\n" unless m.body =~ /\n\Z/
+ m.body = m.body.fix_encoding!
## there are attachments, so wrap body in an attachment of its own
unless @attachments.empty?
@@ -488,7 +566,10 @@ protected
m = RMail::Message.new
m.add_part body_m
- @attachments.each { |a| m.add_part a }
+ @attachments.each do |a|
+ a.body = a.body.fix_encoding! if a.body.kind_of? String
+ m.add_part a
+ end
end
## do whatever crypto transformation is necessary
@@ -510,9 +591,9 @@ protected
m.header[k] =
case v
when String
- k.match(/subject/i) ? mime_encode_subject(v) : mime_encode_address(v)
+ (k.match(/subject/i) ? mime_encode_subject(v).dup.fix_encoding! : mime_encode_address(v)).dup.fix_encoding!
when Array
- v.map { |v| mime_encode_address v }.join ", "
+ (v.map { |v| mime_encode_address v }.join ", ").dup.fix_encoding!
end
end
@@ -594,12 +675,12 @@ private
if HookManager.enabled? "mentions-attachments"
HookManager.run "mentions-attachments", :header => @header, :body => @body
else
- @body.any? { |l| l =~ /^[^>]/ && l =~ /\battach(ment|ed|ing|)\b/i }
+ @body.any? { |l| l.fix_encoding! =~ /^[^>]/ && l.fix_encoding! =~ /\battach(ment|ed|ing|)\b/i }
end
end
def top_posting?
- @body.join("\n") =~ /(\S+)\s*Excerpts from.*\n(>.*\n)+\s*\Z/
+ @body.map { |x| x.fix_encoding! }.join("\n").fix_encoding! =~ /(\S+)\s*Excerpts from.*\n(>.*\n)+\s*\Z/
end
def sig_lines
diff --git a/lib/sup/modes/file-browser-mode.rb b/lib/sup/modes/file_browser_mode.rb
similarity index 100%
rename from lib/sup/modes/file-browser-mode.rb
rename to lib/sup/modes/file_browser_mode.rb
diff --git a/lib/sup/modes/forward-mode.rb b/lib/sup/modes/forward_mode.rb
similarity index 88%
rename from lib/sup/modes/forward-mode.rb
rename to lib/sup/modes/forward_mode.rb
index 5d1ec58..652a97f 100644
--- a/lib/sup/modes/forward-mode.rb
+++ b/lib/sup/modes/forward_mode.rb
@@ -7,9 +7,10 @@ class ForwardMode < EditMessageMode
"From" => AccountManager.default_account.full_address,
}
+ @m = opts[:message]
header["Subject"] =
- if opts[:message]
- "Fwd: " + opts[:message].subj
+ if @m
+ "Fwd: " + @m.subj
elsif opts[:attachments]
"Fwd: " + opts[:attachments].keys.join(", ")
end
@@ -19,8 +20,8 @@ class ForwardMode < EditMessageMode
header["Bcc"] = opts[:bcc].map { |p| p.full_address }.join(", ") if opts[:bcc]
body =
- if opts[:message]
- forward_body_lines(opts[:message])
+ if @m
+ forward_body_lines @m
elsif opts[:attachments]
["Note: #{opts[:attachments].size.pluralize 'attachment'}."]
end
@@ -58,7 +59,7 @@ class ForwardMode < EditMessageMode
end
BufferManager.spawn title, mode
- mode.edit_message
+ mode.default_edit_message
end
protected
@@ -68,6 +69,14 @@ protected
m.quotable_header_lines + [""] + m.quotable_body_lines +
["--- End forwarded message ---"]
end
+
+ def send_message
+ return unless super # super returns true if the mail has been sent
+ if @m
+ @m.add_label :forwarded
+ Index.save_message @m
+ end
+ end
end
end
diff --git a/lib/sup/modes/help-mode.rb b/lib/sup/modes/help_mode.rb
similarity index 100%
rename from lib/sup/modes/help-mode.rb
rename to lib/sup/modes/help_mode.rb
diff --git a/lib/sup/modes/inbox-mode.rb b/lib/sup/modes/inbox_mode.rb
similarity index 64%
rename from lib/sup/modes/inbox-mode.rb
rename to lib/sup/modes/inbox_mode.rb
index 9e37909..7638ea8 100644
--- a/lib/sup/modes/inbox-mode.rb
+++ b/lib/sup/modes/inbox_mode.rb
@@ -1,4 +1,4 @@
-require 'sup'
+require "sup/modes/thread_index_mode"
module Redwood
@@ -6,7 +6,6 @@ class InboxMode < ThreadIndexMode
register_keymap do |k|
## overwrite toggle_archived with archive
k.add :archive, "Archive thread (remove from inbox)", 'a'
- k.add :read_and_archive, "Archive thread (remove from inbox) and mark read", 'A'
k.add :refine_search, "Refine search", '|'
end
@@ -64,47 +63,6 @@ class InboxMode < ThreadIndexMode
threads.each { |t| Index.save_thread t }
end
- def read_and_archive
- return unless cursor_thread
- thread = cursor_thread # to make sure lambda only knows about 'old' cursor_thread
-
- was_unread = thread.labels.member? :unread
- UndoManager.register "reading and archiving thread" do
- thread.apply_label :inbox
- thread.apply_label :unread if was_unread
- add_or_unhide thread.first
- Index.save_thread thread
- end
-
- cursor_thread.remove_label :unread
- cursor_thread.remove_label :inbox
- hide_thread cursor_thread
- regen_text
- Index.save_thread thread
- end
-
- def multi_read_and_archive threads
- old_labels = threads.map { |t| t.labels.dup }
-
- threads.each do |t|
- t.remove_label :unread
- t.remove_label :inbox
- hide_thread t
- end
- regen_text
-
- UndoManager.register "reading and archiving #{threads.size.pluralize 'thread'}" do
- threads.zip(old_labels).each do |t, l|
- t.labels = l
- add_or_unhide t.first
- Index.save_thread t
- end
- regen_text
- end
-
- threads.each { |t| Index.save_thread t }
- end
-
def handle_unarchived_update sender, m
add_or_unhide m
end
diff --git a/lib/sup/modes/label-list-mode.rb b/lib/sup/modes/label_list_mode.rb
similarity index 100%
rename from lib/sup/modes/label-list-mode.rb
rename to lib/sup/modes/label_list_mode.rb
diff --git a/lib/sup/modes/label-search-results-mode.rb b/lib/sup/modes/label_search_results_mode.rb
similarity index 100%
rename from lib/sup/modes/label-search-results-mode.rb
rename to lib/sup/modes/label_search_results_mode.rb
diff --git a/lib/sup/modes/line-cursor-mode.rb b/lib/sup/modes/line_cursor_mode.rb
similarity index 85%
rename from lib/sup/modes/line-cursor-mode.rb
rename to lib/sup/modes/line_cursor_mode.rb
index aad6fe1..d9cfbf1 100644
--- a/lib/sup/modes/line-cursor-mode.rb
+++ b/lib/sup/modes/line_cursor_mode.rb
@@ -47,9 +47,9 @@ protected
def draw_line ln, opts={}
if ln == @curpos
- super ln, :highlight => true, :debug => opts[:debug]
+ super ln, :highlight => true, :debug => opts[:debug], :color => :text_color
else
- super
+ super ln, :color => :text_color
end
end
@@ -94,7 +94,17 @@ protected
call_load_more_callbacks buffer.content_height if @curpos >= lines - [buffer.content_height/2,1].max
return false unless @curpos < lines - 1
- if @curpos >= botline - 1
+ if $config[:continuous_scroll] and (@curpos == botline - 3 and @curpos < lines - 3)
+ # load more lines, one at a time.
+ jump_to_line topline + 1
+ @curpos += 1
+ unless buffer.dirty?
+ draw_line @curpos - 1
+ draw_line @curpos
+ set_status
+ buffer.commit
+ end
+ elsif @curpos >= botline - 1
page_down
set_cursor_pos topline
else
@@ -111,7 +121,17 @@ protected
def cursor_up
return false unless @curpos > @cursor_top
- if @curpos == topline
+
+ if $config[:continuous_scroll] and (@curpos == topline + 2)
+ jump_to_line topline - 1
+ @curpos -= 1
+ unless buffer.dirty?
+ draw_line @curpos + 1
+ draw_line @curpos
+ set_status
+ buffer.commit
+ end
+ elsif @curpos == topline
old_topline = topline
page_up
set_cursor_pos [old_topline - 1, topline].max
@@ -180,5 +200,4 @@ private
@load_more_q.push size if $config[:load_more_threads_when_scrolling]
end
end
-
end
diff --git a/lib/sup/modes/log-mode.rb b/lib/sup/modes/log_mode.rb
similarity index 100%
rename from lib/sup/modes/log-mode.rb
rename to lib/sup/modes/log_mode.rb
diff --git a/lib/sup/modes/person-search-results-mode.rb b/lib/sup/modes/person_search_results_mode.rb
similarity index 100%
rename from lib/sup/modes/person-search-results-mode.rb
rename to lib/sup/modes/person_search_results_mode.rb
diff --git a/lib/sup/modes/poll-mode.rb b/lib/sup/modes/poll_mode.rb
similarity index 100%
rename from lib/sup/modes/poll-mode.rb
rename to lib/sup/modes/poll_mode.rb
diff --git a/lib/sup/modes/reply-mode.rb b/lib/sup/modes/reply_mode.rb
similarity index 81%
rename from lib/sup/modes/reply-mode.rb
rename to lib/sup/modes/reply_mode.rb
index ccdf371..4225d0f 100644
--- a/lib/sup/modes/reply-mode.rb
+++ b/lib/sup/modes/reply_mode.rb
@@ -48,6 +48,7 @@ EOS
## the full headers (most importantly the list-post header, if
## any)
body = reply_body_lines message
+ @body_orig = body
## first, determine the address at which we received this email. this will
## become our From: address in the reply.
@@ -97,14 +98,21 @@ EOS
@headers = {}
@headers[:recipient] = {
"To" => cc.map { |p| p.full_address },
+ "Cc" => [],
} if useful_recipient
## typically we don't want to have a reply-to-sender option if the sender
## is a user account. however, if the cc is empty, it's a message to
## ourselves, so for the lack of any other options, we'll add it.
- @headers[:sender] = { "To" => [to.full_address], } if !AccountManager.is_account?(to) || !useful_recipient
+ @headers[:sender] = {
+ "To" => [to.full_address],
+ "Cc" => [],
+ } if !AccountManager.is_account?(to) || !useful_recipient
- @headers[:user] = {}
+ @headers[:user] = {
+ "To" => [],
+ "Cc" => [],
+ }
not_me_ccs = cc.select { |p| !AccountManager.is_account?(p) }
@headers[:all] = {
@@ -114,22 +122,11 @@ EOS
@headers[:list] = {
"To" => [@m.list_address.full_address],
+ "Cc" => [],
} if @m.is_list_message?
refs = gen_references
- @headers.each do |k, v|
- @headers[k] = {
- "From" => from.full_address,
- "To" => [],
- "Cc" => [],
- "Bcc" => [],
- "In-reply-to" => "<#{@m.id}>",
- "Subject" => Message.reify_subj(@m.subj),
- "References" => refs,
- }.merge v
- end
-
types = REPLY_TYPES.select { |t| @headers.member?(t) }
@type_selector = HorizontalSelector.new "Reply to:", types, types.map { |x| TYPE_DESCRIPTIONS[x] }
@@ -148,13 +145,17 @@ EOS
:recipient
end)
- @bodies = {}
- @headers.each do |k, v|
- @bodies[k] = body
- HookManager.run "before-edit", :header => v, :body => @bodies[k]
- end
+ headers_full = {
+ "From" => from.full_address,
+ "Bcc" => [],
+ "In-reply-to" => "<#{@m.id}>",
+ "Subject" => Message.reify_subj(@m.subj),
+ "References" => refs,
+ }.merge @headers[@type_selector.val]
- super :header => @headers[@type_selector.val], :body => @bodies[@type_selector.val], :twiddles => false
+ HookManager.run "before-edit", :header => headers_full, :body => body
+
+ super :header => headers_full, :body => body, :twiddles => false
add_selector @type_selector
end
@@ -163,8 +164,7 @@ protected
def move_cursor_right
super
if @headers[@type_selector.val] != self.header
- self.header = @headers[@type_selector.val]
- self.body = @bodies[@type_selector.val] unless @edited
+ self.header = self.header.merge @headers[@type_selector.val]
rerun_crypto_selector_hook
update
end
@@ -173,8 +173,7 @@ protected
def move_cursor_left
super
if @headers[@type_selector.val] != self.header
- self.header = @headers[@type_selector.val]
- self.body = @bodies[@type_selector.val] unless @edited
+ self.header = self.header.merge @headers[@type_selector.val]
rerun_crypto_selector_hook
update
end
@@ -192,14 +191,15 @@ protected
end
def handle_new_text new_header, new_body
- if new_body != @bodies[@type_selector.val]
- @bodies[@type_selector.val] = new_body
+ if new_body != @body_orig
+ @body_orig = new_body
@edited = true
end
old_header = @headers[@type_selector.val]
- if new_header.size != old_header.size || old_header.any? { |k, v| new_header[k] != v }
+ if old_header.any? { |k, v| new_header[k] != v }
@type_selector.set_to :user
- self.header = @headers[:user] = new_header
+ self.header["To"] = @headers[:user]["To"] = new_header["To"]
+ self.header["Cc"] = @headers[:user]["Cc"] = new_header["Cc"]
update
end
end
@@ -210,11 +210,19 @@ protected
def edit_field field
edited_field = super
- if edited_field && edited_field != "Subject"
+ if edited_field and (field == "To" or field == "Cc")
@type_selector.set_to :user
+ @headers[:user]["To"] = self.header["To"]
+ @headers[:user]["Cc"] = self.header["Cc"]
update
end
end
+
+ def send_message
+ return unless super # super returns true if the mail has been sent
+ @m.add_label :replied
+ Index.save_message @m
+ end
end
end
diff --git a/lib/sup/modes/resume-mode.rb b/lib/sup/modes/resume_mode.rb
similarity index 100%
rename from lib/sup/modes/resume-mode.rb
rename to lib/sup/modes/resume_mode.rb
diff --git a/lib/sup/modes/scroll-mode.rb b/lib/sup/modes/scroll_mode.rb
similarity index 96%
rename from lib/sup/modes/scroll-mode.rb
rename to lib/sup/modes/scroll_mode.rb
index aac1c19..9bee795 100644
--- a/lib/sup/modes/scroll-mode.rb
+++ b/lib/sup/modes/scroll_mode.rb
@@ -43,12 +43,12 @@ class ScrollMode < Mode
def draw
ensure_mode_validity
- (@topline ... @botline).each { |ln| draw_line ln }
+ (@topline ... @botline).each { |ln| draw_line ln, :color => :text_color }
((@botline - @topline) ... buffer.content_height).each do |ln|
if @twiddles
buffer.write ln, 0, "~", :color => :twiddle_color
else
- buffer.write ln, 0, ""
+ buffer.write ln, 0, "", :color => :text_color
end
end
@status = "lines #{@topline + 1}:#{@botline}/#{lines}"
@@ -208,7 +208,7 @@ protected
# return
end
- def matching_text_array s, regex, oldcolor=:none
+ def matching_text_array s, regex, oldcolor=:text_color
s.split(regex).map do |text|
next if text.empty?
if text =~ regex
@@ -244,7 +244,7 @@ protected
end
def draw_line_from_string ln, s, opts
- buffer.write ln - @topline, 0, s[@leftcol .. -1], :highlight => opts[:highlight]
+ buffer.write ln - @topline, 0, s[@leftcol .. -1], :highlight => opts[:highlight], :color => opts[:color]
end
end
diff --git a/lib/sup/modes/search-list-mode.rb b/lib/sup/modes/search_list_mode.rb
similarity index 92%
rename from lib/sup/modes/search-list-mode.rb
rename to lib/sup/modes/search_list_mode.rb
index 8f73659..741abf4 100644
--- a/lib/sup/modes/search-list-mode.rb
+++ b/lib/sup/modes/search_list_mode.rb
@@ -86,7 +86,11 @@ protected
counted = searches.map do |name|
search_string = SearchManager.search_string_for name
begin
- query = Index.parse_query search_string
+ if SearchManager.predefined_queries.has_key? search_string
+ query = SearchManager.predefined_queries[search_string]
+ else
+ query = Index.parse_query search_string
+ end
total = Index.num_results_for :qobj => query[:qobj]
unread = Index.num_results_for :qobj => query[:qobj], :label => :unread
rescue Index::ParseError => e
@@ -141,6 +145,12 @@ protected
def rename_selected_search
old_name, num_unread = @searches[curpos]
return unless old_name
+
+ if SearchManager.predefined_searches.has_key? old_name
+ BufferManager.flash "Cannot be edited: predefined search."
+ return
+ end
+
new_name = BufferManager.ask :save_search, "Rename this saved search: ", old_name
return unless new_name && new_name !~ /^\s*$/ && new_name != old_name
new_name.strip!
@@ -159,6 +169,12 @@ protected
def edit_selected_search
name, num_unread = @searches[curpos]
return unless name
+
+ if SearchManager.predefined_searches.has_key? name
+ BufferManager.flash "Cannot be edited: predefined search."
+ return
+ end
+
old_search_string = SearchManager.search_string_for name
new_search_string = BufferManager.ask :search, "Edit this saved search: ", (old_search_string + " ")
return unless new_search_string && new_search_string !~ /^\s*$/ && new_search_string != old_search_string
diff --git a/lib/sup/modes/search-results-mode.rb b/lib/sup/modes/search_results_mode.rb
similarity index 90%
rename from lib/sup/modes/search-results-mode.rb
rename to lib/sup/modes/search_results_mode.rb
index f346e97..7bcb35a 100644
--- a/lib/sup/modes/search-results-mode.rb
+++ b/lib/sup/modes/search_results_mode.rb
@@ -40,7 +40,11 @@ class SearchResultsMode < ThreadIndexMode
def self.spawn_from_query text
begin
- query = Index.parse_query(text)
+ if SearchManager.predefined_queries.has_key? text
+ query = SearchManager.predefined_queries[text]
+ else
+ query = Index.parse_query(text)
+ end
return unless query
short_text = text.length < 20 ? text : text[0 ... 20] + "..."
mode = SearchResultsMode.new query
diff --git a/lib/sup/modes/text-mode.rb b/lib/sup/modes/text_mode.rb
similarity index 98%
rename from lib/sup/modes/text-mode.rb
rename to lib/sup/modes/text_mode.rb
index 39706d3..638393f 100644
--- a/lib/sup/modes/text-mode.rb
+++ b/lib/sup/modes/text_mode.rb
@@ -46,7 +46,7 @@ class TextMode < ScrollMode
def << line
@lines = [0] if @text.empty?
- @text << line
+ @text << line.fix_encoding!
@lines << @text.length
if buffer
ensure_mode_validity
diff --git a/lib/sup/modes/thread-index-mode.rb b/lib/sup/modes/thread_index_mode.rb
similarity index 90%
rename from lib/sup/modes/thread-index-mode.rb
rename to lib/sup/modes/thread_index_mode.rb
index f04295b..0901ad6 100644
--- a/lib/sup/modes/thread-index-mode.rb
+++ b/lib/sup/modes/thread_index_mode.rb
@@ -33,6 +33,7 @@ EOS
k.add_multi "Load all threads (! to confirm) :", '!' do |kk|
kk.add :load_all_threads, "Load all threads (may list a _lot_ of threads)", '!'
end
+ k.add :read_and_archive, "Archive thread (remove from inbox) and mark read", 'A'
k.add :cancel_search, "Cancel current search", :ctrl_g
k.add :reload, "Refresh view", '@'
k.add :toggle_archived, "Toggle archived status", 'a'
@@ -110,7 +111,7 @@ EOS
num = t.size
message = "Loading #{num.pluralize 'message body'}..."
BufferManager.say(message) do |sid|
- t.each_with_index do |(m, *o), i|
+ t.each_with_index do |(m, *_), i|
next unless m
BufferManager.say "#{message} (#{i}/#{num})", sid if t.size > 1
m.load_from_source!
@@ -200,6 +201,26 @@ EOS
BufferManager.draw_screen
end
+ def handle_updated_update sender, m
+ t = thread_containing(m) or return
+ l = @lines[t] or return
+ @ts_mutex.synchronize do
+ @ts.delete_message m
+ @ts.add_message m
+ end
+ Index.save_thread t, sync_back = false
+ update_text_for_line l
+ end
+
+ def handle_location_deleted_update sender, m
+ t = thread_containing(m)
+ delete_thread t if t and t.first.id == m.id
+ @ts_mutex.synchronize do
+ @ts.delete_message m if t
+ end
+ update
+ end
+
def handle_single_message_deleted_update sender, m
@ts_mutex.synchronize do
return unless @ts.contains? m
@@ -215,6 +236,13 @@ EOS
update
end
+ def handle_killed_update sender, m
+ t = @ts_mutex.synchronize { @ts.thread_for m }
+ return unless t
+ hide_thread t
+ update
+ end
+
def handle_spammed_update sender, m
t = @ts_mutex.synchronize { @ts.thread_for m }
return unless t
@@ -226,6 +254,10 @@ EOS
add_or_unhide m
end
+ def handle_unkilled_update sender, m
+ add_or_unhide m
+ end
+
def undo
UndoManager.undo
end
@@ -247,7 +279,7 @@ EOS
def edit_message
return unless(t = cursor_thread)
- message, *crap = t.find { |m, *o| m.has_label? :draft }
+ message, *_ = t.find { |m, *o| m.has_label? :draft }
if message
mode = ResumeMode.new message
BufferManager.spawn "Edit message", mode
@@ -258,7 +290,6 @@ EOS
## returns an undo lambda
def actually_toggle_starred t
- pos = curpos
if t.has_label? :starred # if ANY message has a star
t.remove_label :starred # remove from all
UpdateManager.relay self, :unstarred, t.first
@@ -713,6 +744,47 @@ EOS
end
ignore_concurrent_calls :load_threads
+ def read_and_archive
+ return unless cursor_thread
+ thread = cursor_thread # to make sure lambda only knows about 'old' cursor_thread
+
+ was_unread = thread.labels.member? :unread
+ UndoManager.register "reading and archiving thread" do
+ thread.apply_label :inbox
+ thread.apply_label :unread if was_unread
+ add_or_unhide thread.first
+ Index.save_thread thread
+ end
+
+ cursor_thread.remove_label :unread
+ cursor_thread.remove_label :inbox
+ hide_thread cursor_thread
+ regen_text
+ Index.save_thread thread
+ end
+
+ def multi_read_and_archive threads
+ old_labels = threads.map { |t| t.labels.dup }
+
+ threads.each do |t|
+ t.remove_label :unread
+ t.remove_label :inbox
+ hide_thread t
+ end
+ regen_text
+
+ UndoManager.register "reading and archiving #{threads.size.pluralize 'thread'}" do
+ threads.zip(old_labels).each do |t, l|
+ t.labels = l
+ add_or_unhide t.first
+ Index.save_thread t
+ end
+ regen_text
+ end
+
+ threads.each { |t| Index.save_thread t }
+ end
+
def resize rows, cols
regen_text
super
@@ -756,6 +828,16 @@ protected
update
end
+ def delete_thread t
+ @mutex.synchronize do
+ i = @threads.index(t) or return
+ @threads.delete_at i
+ @size_widgets.delete_at i
+ @date_widgets.delete_at i
+ @tags.drop_tag_for t
+ end
+ end
+
def hide_thread t
@mutex.synchronize do
i = @threads.index(t) or return
@@ -857,14 +939,14 @@ protected
abbrev =
if cur_width + name.display_length > from_width
- name[0 ... (from_width - cur_width - 1)] + "."
+ name.slice_by_display_length(from_width - cur_width - 1) + "."
elsif cur_width + name.display_length == from_width
- name[0 ... (from_width - cur_width)]
+ name.slice_by_display_length(from_width - cur_width)
else
if last
- name[0 ... (from_width - cur_width)]
+ name.slice_by_display_length(from_width - cur_width)
else
- name[0 ... (from_width - cur_width - 1)] + ","
+ name.slice_by_display_length(from_width - cur_width - 1) + ","
end
end
@@ -877,8 +959,9 @@ protected
from << [(newness ? :index_new_color : (starred ? :index_starred_color : :index_old_color)), abbrev]
end
- dp = t.direct_participants.any? { |p| AccountManager.is_account? p }
- p = dp || t.participants.any? { |p| AccountManager.is_account? p }
+ is_me = AccountManager.method(:is_account?)
+ directly_participated = t.direct_participants.any?(&is_me)
+ participated = directly_participated || t.participants.any?(&is_me)
subj_color =
if t.has_label?(:draft)
@@ -907,8 +990,8 @@ protected
from +
[
[:size_widget_color, size_widget_text],
- [:to_me_color, t.labels.member?(:attachment) ? "@" : " "],
- [:to_me_color, dp ? ">" : (p ? '+' : " ")],
+ [:with_attachment_color , t.labels.member?(:attachment) ? "@" : " "],
+ [:to_me_color, directly_participated ? ">" : (participated ? '+' : " ")],
] +
(t.labels - @hidden_labels).sort_by {|x| x.to_s}.map {
|label| [Colormap.sym_is_defined("label_#{label}_color".to_sym) || :label_color, "#{label} "]
diff --git a/lib/sup/modes/thread-view-mode.rb b/lib/sup/modes/thread_view_mode.rb
similarity index 92%
rename from lib/sup/modes/thread-view-mode.rb
rename to lib/sup/modes/thread_view_mode.rb
index 9fcc45d..07b6f0b 100644
--- a/lib/sup/modes/thread-view-mode.rb
+++ b/lib/sup/modes/thread_view_mode.rb
@@ -10,7 +10,6 @@ class ThreadViewMode < LineCursorMode
attr_accessor :state
end
- DATE_FORMAT = "%B %e %Y %l:%M%p"
INDENT_SPACES = 2 # how many spaces to indent child messages
HookManager.register "detailed-headers", <<EOS
@@ -78,11 +77,13 @@ EOS
k.add :archive_and_next, "Archive this thread, kill buffer, and view next", 'a'
k.add :delete_and_next, "Delete this thread, kill buffer, and view next", 'd'
+ k.add :kill_and_next, "Kill this thread, kill buffer, and view next", '&'
k.add :toggle_wrap, "Toggle wrapping of text", 'w'
k.add_multi "(a)rchive/(d)elete/mark as (s)pam/mark as u(N)read:", '.' do |kk|
kk.add :archive_and_kill, "Archive this thread and kill buffer", 'a'
kk.add :delete_and_kill, "Delete this thread and kill buffer", 'd'
+ kk.add :kill_and_kill, "Kill this thread and kill buffer", '&'
kk.add :spam_and_kill, "Mark this thread as spam and kill buffer", 's'
kk.add :unread_and_kill, "Mark this thread as unread and kill buffer", 'N'
kk.add :do_nothing_and_kill, "Just kill this buffer", '.'
@@ -91,6 +92,7 @@ EOS
k.add_multi "(a)rchive/(d)elete/mark as (s)pam/mark as u(N)read/do (n)othing:", ',' do |kk|
kk.add :archive_and_next, "Archive this thread, kill buffer, and view next", 'a'
kk.add :delete_and_next, "Delete this thread, kill buffer, and view next", 'd'
+ kk.add :kill_and_next, "Kill this thread, kill buffer, and view next", '&'
kk.add :spam_and_next, "Mark this thread as spam, kill buffer, and view next", 's'
kk.add :unread_and_next, "Mark this thread as unread, kill buffer, and view next", 'N'
kk.add :do_nothing_and_next, "Kill buffer, and view next", 'n', ','
@@ -99,6 +101,7 @@ EOS
k.add_multi "(a)rchive/(d)elete/mark as (s)pam/mark as u(N)read/do (n)othing:", ']' do |kk|
kk.add :archive_and_prev, "Archive this thread, kill buffer, and view previous", 'a'
kk.add :delete_and_prev, "Delete this thread, kill buffer, and view previous", 'd'
+ kk.add :kill_and_prev, "Kill this thread, kill buffer, and view previous", '&'
kk.add :spam_and_prev, "Mark this thread as spam, kill buffer, and view previous", 's'
kk.add :unread_and_prev, "Mark this thread as unread, kill buffer, and view previous", 'N'
kk.add :do_nothing_and_prev, "Kill buffer, and view previous", 'n', ']'
@@ -237,7 +240,7 @@ EOS
else hookcmd
end + ' ' + to.map { |t| t.email }.join(' ')
- bt = to.size > 1 ? "#{to.size} recipients" : to.to_s
+ bt = to.size > 1 ? "#{to.size} recipients" : to[0].to_s
if BufferManager.ask_yes_or_no "Really bounce to #{bt}?"
debug "bounce command: #{cmd}"
@@ -246,6 +249,8 @@ EOS
sm.puts m.raw_message
end
raise SendmailCommandFailed, "Couldn't execute #{cmd}" unless $? == 0
+ m.add_label :forwarded
+ Index.save_message m
rescue SystemCallError, SendmailCommandFailed => e
warn "problem sending mail: #{e.message}"
BufferManager.flash "Problem sending mail: #{e.message}"
@@ -350,7 +355,7 @@ EOS
m = @message_lines[curpos] or return
mode = ComposeMode.new(:body => m.quotable_body_lines, :to => m.to, :cc => m.cc, :subj => m.subj, :bcc => m.bcc, :refs => m.refs, :replytos => m.replytos)
BufferManager.spawn "edit as new", mode
- mode.edit_message
+ mode.default_edit_message
end
def save_to_disk
@@ -360,7 +365,13 @@ EOS
default_dir = $config[:default_attachment_save_dir]
default_dir = ENV["HOME"] if default_dir.nil? || default_dir.empty?
default_fn = File.expand_path File.join(default_dir, chunk.filename)
- fn = BufferManager.ask_for_filename :filename, "Save attachment to file: ", default_fn
+ fn = BufferManager.ask_for_filename :filename, "Save attachment to file or directory: ", default_fn, true
+
+ # if user selects directory use file name from message
+ if fn and File.directory? fn
+ fn = File.join(fn, chunk.filename)
+ end
+
save_to_file(fn) { |f| f.print chunk.raw_content } if fn
else
m = @message_lines[curpos]
@@ -413,7 +424,7 @@ EOS
mode = ResumeMode.new m
BufferManager.spawn "Edit message", mode
BufferManager.kill_buffer self.buffer
- mode.edit_message
+ mode.default_edit_message
else
BufferManager.flash "Not a draft message!"
end
@@ -446,13 +457,15 @@ EOS
m = (curpos ... @message_lines.length).argfind { |i| @message_lines[i] }
return unless m
+ nextm = @layout[m].next
+ return unless nextm
+
if @layout[m].toggled_state == true
@layout[m].state = :closed
@layout[m].toggled_state = false
update
end
- nextm = @layout[m].next
if @layout[nextm].state == :closed
@layout[nextm].state = :open
@layout[nextm].toggled_state = true
@@ -483,13 +496,15 @@ EOS
m = (0 .. curpos).to_a.reverse.argfind { |i| @message_lines[i] }
return unless m
+ nextm = @layout[m].prev
+ return unless nextm
+
if @layout[m].toggled_state == true
@layout[m].state = :closed
@layout[m].toggled_state = false
update
end
- nextm = @layout[m].prev
if @layout[nextm].state == :closed
@layout[nextm].state = :open
@layout[nextm].toggled_state = true
@@ -569,18 +584,21 @@ EOS
def archive_and_kill; archive_and_then :kill end
def spam_and_kill; spam_and_then :kill end
def delete_and_kill; delete_and_then :kill end
+ def kill_and_kill; kill_and_then :kill end
def unread_and_kill; unread_and_then :kill end
def do_nothing_and_kill; do_nothing_and_then :kill end
def archive_and_next; archive_and_then :next end
def spam_and_next; spam_and_then :next end
def delete_and_next; delete_and_then :next end
+ def kill_and_next; kill_and_then :next end
def unread_and_next; unread_and_then :next end
def do_nothing_and_next; do_nothing_and_then :next end
def archive_and_prev; archive_and_then :prev end
def spam_and_prev; spam_and_then :prev end
def delete_and_prev; delete_and_then :prev end
+ def kill_and_prev; kill_and_then :prev end
def unread_and_prev; unread_and_then :prev end
def do_nothing_and_prev; do_nothing_and_then :prev end
@@ -623,6 +641,19 @@ EOS
end
end
+ def kill_and_then op
+ dispatch op do
+ @thread.apply_label :killed
+ UpdateManager.relay self, :killed, @thread.first
+ Index.save_thread @thread
+ UndoManager.register "killed 1 thread" do
+ @thread.remove_label :killed
+ Index.save_thread @thread
+ UpdateManager.relay self, :unkilled, @thread.first
+ end
+ end
+ end
+
def unread_and_then op
dispatch op do
@thread.apply_label :unread
@@ -682,6 +713,15 @@ EOS
end
end
+
+ def status
+ user_labels = @thread.labels.to_a.map do |l|
+ l.to_s if LabelManager.user_defined_labels.member?(l)
+ end.compact.join(",")
+ user_labels = (user_labels.empty? and "" or "<#{user_labels}>")
+ [user_labels, super].join(" -- ")
+ end
+
private
def initial_state_for m
@@ -780,13 +820,13 @@ private
@person_lines[start] = m.from
[[prefix_widget, open_widget, new_widget, attach_widget, starred_widget,
[color,
- "#{m.from ? m.from.mediumname : '?'} to #{m.recipients.map { |l| l.shortname }.join(', ')} #{m.date.to_nice_s} (#{m.date.to_nice_distance_s})"]]]
+ "#{m.from ? m.from.mediumname.fix_encoding! : '?'} to #{m.recipients.map { |l| l.shortname.fix_encoding! }.join(', ')} #{m.date.to_nice_s.fix_encoding!} (#{m.date.to_nice_distance_s.fix_encoding!})"]]]
when :closed
@person_lines[start] = m.from
[[prefix_widget, open_widget, new_widget, attach_widget, starred_widget,
[color,
- "#{m.from ? m.from.mediumname : '?'}, #{m.date.to_nice_s} (#{m.date.to_nice_distance_s}) #{m.snippet}"]]]
+ "#{m.from ? m.from.mediumname.fix_encoding! : '?'}, #{m.date.to_nice_s.fix_encoding!} (#{m.date.to_nice_distance_s.fix_encoding!}) #{m.snippet ? m.snippet.fix_encoding! : ''}"]]]
when :detailed
@person_lines[start] = m.from
@@ -808,7 +848,7 @@ private
end
headers = OrderedHash.new
- headers["Date"] = "#{m.date.strftime DATE_FORMAT} (#{m.date.to_nice_distance_s})"
+ headers["Date"] = "#{m.date.to_message_nice_s} (#{m.date.to_nice_distance_s})"
headers["Subject"] = m.subj
show_labels = @thread.labels - LabelManager::HIDDEN_RESERVED_LABELS
@@ -816,7 +856,7 @@ private
headers["Labels"] = show_labels.map { |x| x.to_s }.sort.join(', ')
end
if parent
- headers["In reply to"] = "#{parent.from.mediumname}'s message of #{parent.date.strftime DATE_FORMAT}"
+ headers["In reply to"] = "#{parent.from.mediumname}'s message of #{parent.date.to_message_nice_s}"
end
HookManager.run "detailed-headers", :message => m, :headers => headers
@@ -846,7 +886,11 @@ private
else
width = buffer.content_width
end
- lines = lines.map { |l| l.chomp.wrap width }.flatten
+ # lines can apparently be both String and Array, convert to Array for map.
+ if lines.kind_of? String
+ lines = lines.lines.to_a
+ end
+ lines = lines.map { |l| l.chomp.wrap width if l }.flatten
end
return lines
end
diff --git a/lib/sup/person.rb b/lib/sup/person.rb
index c271f38..c8eff7f 100644
--- a/lib/sup/person.rb
+++ b/lib/sup/person.rb
@@ -6,13 +6,16 @@ class Person
def initialize name, email
raise ArgumentError, "email can't be nil" unless email
+ email.fix_encoding!
+
@name = if name
+ name.fix_encoding!
name = name.strip.gsub(/\s+/, " ")
name =~ /^(['"]\s*)(.*?)(\s*["'])$/ ? $2 : name
name.gsub('\\\\', '\\')
end
- @email = email.strip.gsub(/\s+/, " ").downcase
+ @email = email.strip.gsub(/\s+/, " ")
end
def to_s; "#@name <#@email>" end
@@ -116,7 +119,7 @@ class Person
def self.from_address_list ss
return [] if ss.nil?
- ss.split_on_commas.map { |s| self.from_address s }
+ ss.dup.split_on_commas.map { |s| self.from_address s }
end
## see comments in self.from_address
diff --git a/lib/sup/poll.rb b/lib/sup/poll.rb
index 7e05292..a07b201 100644
--- a/lib/sup/poll.rb
+++ b/lib/sup/poll.rb
@@ -3,7 +3,7 @@ require 'thread'
module Redwood
class PollManager
- include Singleton
+ include Redwood::Singleton
HookManager.register "before-add-message", <<EOS
Executes immediately before a message is added to the index.
@@ -22,7 +22,12 @@ Variables:
num: the total number of new messages added in this poll
num_inbox: the number of new messages added in this poll which
appear in the inbox (i.e. were not auto-archived).
+ num_total: the total number of messages
+ num_inbox_total: the total number of new messages in the inbox.
num_inbox_total_unread: the total number of unread messages in the inbox
+ num_updated: the total number of updated messages
+ num_deleted: the total number of deleted messages
+ labels: the labels that were applied
from_and_subj: an array of (from email address, subject) pairs
from_and_subj_inbox: an array of (from email address, subject) pairs for
only those messages appearing in the inbox
@@ -33,7 +38,7 @@ EOS
@mutex = Mutex.new
@thread = nil
@last_poll = nil
- @polling = false
+ @polling = Mutex.new
@poll_sources = nil
@mode = nil
@should_clear_running_totals = false
@@ -43,40 +48,68 @@ EOS
def poll_with_sources
@mode ||= PollMode.new
- HookManager.run "before-poll"
- BufferManager.flash "Polling for new messages..."
- num, numi, from_and_subj, from_and_subj_inbox, loaded_labels = @mode.poll
+ if HookManager.enabled? "before-poll"
+ HookManager.run("before-poll")
+ else
+ BufferManager.flash "Polling for new messages..."
+ end
+
+ num, numi, numu, numd, from_and_subj, from_and_subj_inbox, loaded_labels = @mode.poll
clear_running_totals if @should_clear_running_totals
@running_totals[:num] += num
@running_totals[:numi] += numi
+ @running_totals[:numu] += numu
+ @running_totals[:numd] += numd
@running_totals[:loaded_labels] += loaded_labels || []
- if @running_totals[:num] > 0
- BufferManager.flash "Loaded #{@running_totals[:num].pluralize 'new message'}, #{@running_totals[:numi]} to inbox. Labels: #{@running_totals[:loaded_labels].map{|l| l.to_s}.join(', ')}"
+
+
+ if HookManager.enabled? "after-poll"
+ hook_args = { :num => num, :num_inbox => numi,
+ :num_total => @running_totals[:num], :num_inbox_total => @running_totals[:numi],
+ :num_updated => @running_totals[:numu],
+ :num_deleted => @running_totals[:numd],
+ :labels => @running_totals[:loaded_labels],
+ :from_and_subj => from_and_subj, :from_and_subj_inbox => from_and_subj_inbox,
+ :num_inbox_total_unread => lambda { Index.num_results_for :labels => [:inbox, :unread] } }
+
+ HookManager.run("after-poll", hook_args)
else
- BufferManager.flash "No new messages."
+ if @running_totals[:num] > 0
+ flash_msg = "Loaded #{@running_totals[:num].pluralize 'new message'}, #{@running_totals[:numi]} to inbox. " if @running_totals[:num] > 0
+ flash_msg += "Updated #{@running_totals[:numu].pluralize 'message'}. " if @running_totals[:numu] > 0
+ flash_msg += "Deleted #{@running_totals[:numd].pluralize 'message'}. " if @running_totals[:numd] > 0
+ flash_msg += "Labels: #{@running_totals[:loaded_labels].map{|l| l.to_s}.join(', ')}." if @running_totals[:loaded_labels].size > 0
+ BufferManager.flash flash_msg
+ else
+ BufferManager.flash "No new messages."
+ end
end
- HookManager.run "after-poll", :num => num, :num_inbox => numi, :from_and_subj => from_and_subj, :from_and_subj_inbox => from_and_subj_inbox, :num_inbox_total_unread => lambda { Index.num_results_for :labels => [:inbox, :unread] }
-
end
def poll
- return if @polling
- @polling = true
- @poll_sources = SourceManager.usual_sources
- num, numi = poll_with_sources
- @polling = false
- [num, numi]
+ if @polling.try_lock
+ @poll_sources = SourceManager.usual_sources
+ num, numi = poll_with_sources
+ @polling.unlock
+ [num, numi]
+ else
+ debug "poll already in progress."
+ return
+ end
end
def poll_unusual
- return if @polling
- @polling = true
- @poll_sources = SourceManager.unusual_sources
- num, numi = poll_with_sources
- @polling = false
- [num, numi]
+ if @polling.try_lock
+ @poll_sources = SourceManager.unusual_sources
+ num, numi = poll_with_sources
+ @polling.unlock
+ [num, numi]
+ else
+ debug "poll_unusual already in progress."
+ return
+ end
end
def start
@@ -94,7 +127,7 @@ EOS
end
def do_poll
- total_num = total_numi = 0
+ total_num = total_numi = total_numu = total_numd = 0
from_and_subj = []
from_and_subj_inbox = []
loaded_labels = Set.new
@@ -108,16 +141,23 @@ EOS
next
end
- num = 0
- numi = 0
+ msg = ""
+ num = numi = numu = numd = 0
poll_from source do |action,m,old_m,progress|
if action == :delete
yield "Deleting #{m.id}"
+ loaded_labels.merge m.labels
+ numd += 1
+ elsif action == :update
+ yield "Message at #{m.source_info} is an update of an old message. Updating labels from #{old_m.labels.to_a * ','} => #{m.labels.to_a * ','}"
+ loaded_labels.merge m.labels
+ numu += 1
elsif action == :add
if old_m
new_locations = (m.locations - old_m.locations)
if not new_locations.empty?
- yield "Message at #{new_locations[0].info} is an update of an old message. Updating labels from #{old_m.labels.to_a * ','} => #{m.labels.to_a * ','}"
+ yield "Message at #{new_locations[0].info} has changed its source location. Updating labels from #{old_m.labels.to_a * ','} => #{m.labels.to_a * ','}"
+ numu += 1
else
yield "Skipping already-imported message at #{m.locations[-1].info}"
end
@@ -134,65 +174,99 @@ EOS
else fail
end
end
- yield "Found #{num} messages, #{numi} to inbox." unless num == 0
+ msg += "Found #{num} messages, #{numi} to inbox. " unless num == 0
+ msg += "Updated #{numu} messages. " unless numu == 0
+ msg += "Deleted #{numd} messages." unless numd == 0
+ yield msg unless msg == ""
total_num += num
total_numi += numi
+ total_numu += numu
+ total_numd += numd
end
loaded_labels = loaded_labels - LabelManager::HIDDEN_RESERVED_LABELS - [:inbox, :killed]
yield "Done polling; loaded #{total_num} new messages total"
@last_poll = Time.now
- @polling = false
end
- [total_num, total_numi, from_and_subj, from_and_subj_inbox, loaded_labels]
+ [total_num, total_numi, total_numu, total_numd, from_and_subj, from_and_subj_inbox, loaded_labels]
end
## like Source#poll, but yields successive Message objects, which have their
## labels and locations set correctly. The Messages are saved to or removed
## from the index after being yielded.
def poll_from source, opts={}
- begin
- source.poll do |sym, args|
- case sym
- when :add
- m = Message.build_from_source source, args[:info]
- old_m = Index.build_message m.id
- m.labels += args[:labels]
- m.labels.delete :inbox if source.archived?
- m.labels.delete :unread if source.read?
- m.labels.delete :unread if m.source_marked_read? # preserve read status if possible
- m.labels.each { |l| LabelManager << l }
- m.labels = old_m.labels + (m.labels - [:unread, :inbox]) if old_m
- m.locations = old_m.locations + m.locations if old_m
- HookManager.run "before-add-message", :message => m
- yield :add, m, old_m, args[:progress] if block_given?
- Index.sync_message m, true
-
- ## We need to add or unhide the message when it either did not exist
- ## before at all or when it was updated. We do *not* add/unhide when
- ## the same message was found at a different location
- if !old_m or not old_m.locations.member? m.location
- UpdateManager.relay self, :added, m
- end
- when :delete
- Index.each_message :location => [source.id, args[:info]] do |m|
- m.locations.delete Location.new(source, args[:info])
- yield :delete, m, [source,args[:info]], args[:progress] if block_given?
- Index.sync_message m, false
- #UpdateManager.relay self, :deleted, m
+ debug "trying to acquire poll lock for: #{source}..."
+ if source.try_lock
+ begin
+ source.poll do |sym, args|
+ case sym
+ when :add
+ m = Message.build_from_source source, args[:info]
+ old_m = Index.build_message m.id
+ m.labels += args[:labels]
+ m.labels.delete :inbox if source.archived?
+ m.labels.delete :unread if source.read?
+ m.labels.delete :unread if m.source_marked_read? # preserve read status if possible
+ m.labels.each { |l| LabelManager << l }
+ m.labels = old_m.labels + (m.labels - [:unread, :inbox]) if old_m
+ m.locations = old_m.locations + m.locations if old_m
+ HookManager.run "before-add-message", :message => m
+ yield :add, m, old_m, args[:progress] if block_given?
+ Index.sync_message m, true
+
+ if Index.message_joining_killed? m
+ m.labels += [:killed]
+ Index.sync_message m, true
+ end
+
+ ## We need to add or unhide the message when it either did not exist
+ ## before at all or when it was updated. We do *not* add/unhide when
+ ## the same message was found at a different location
+ if old_m
+ UpdateManager.relay self, :updated, m
+ elsif !old_m or not old_m.locations.member? m.location
+ UpdateManager.relay self, :added, m
+ end
+ when :delete
+ Index.each_message({:location => [source.id, args[:info]]}, false) do |m|
+ m.locations.delete Location.new(source, args[:info])
+ Index.sync_message m, false
+ if m.locations.size == 0
+ yield :delete, m, [source,args[:info]], args[:progress] if block_given?
+ Index.delete m.id
+ UpdateManager.relay self, :location_deleted, m
+ end
+ end
+ when :update
+ Index.each_message({:location => [source.id, args[:old_info]]}, false) do |m|
+ old_m = Index.build_message m.id
+ m.locations.delete Location.new(source, args[:old_info])
+ m.locations.push Location.new(source, args[:new_info])
+ ## Update labels that might have been modified remotely
+ m.labels -= source.supported_labels?
+ m.labels += args[:labels]
+ yield :update, m, old_m if block_given?
+ Index.sync_message m, true
+ UpdateManager.relay self, :updated, m
+ end
end
end
- end
- source.go_idle
- rescue SourceError => e
- warn "problem getting messages from #{source}: #{e.message}"
+ rescue SourceError => e
+ warn "problem getting messages from #{source}: #{e.message}"
+
+ ensure
+ source.go_idle
+ source.unlock
+ end
+ else
+ debug "source #{source} is already being polled."
end
end
def handle_idle_update sender, idle_since; @should_clear_running_totals = false; end
def handle_unidle_update sender, idle_since; @should_clear_running_totals = true; clear_running_totals; end
- def clear_running_totals; @running_totals = {:num => 0, :numi => 0, :loaded_labels => Set.new}; end
+ def clear_running_totals; @running_totals = {:num => 0, :numi => 0, :numu => 0, :numd => 0, :loaded_labels => Set.new}; end
end
end
diff --git a/lib/sup/protocol.rb b/lib/sup/protocol.rb
deleted file mode 100644
index 573138b..0000000
--- a/lib/sup/protocol.rb
+++ /dev/null
@@ -1,161 +0,0 @@
-require 'eventmachine'
-require 'socket'
-require 'stringio'
-require 'yajl'
-
-class EM::P::Redwood < EM::Connection
- VERSION = 1
- ENCODINGS = %w(marshal json)
-
- attr_reader :debug
-
- def initialize *args
- @state = :negotiating
- @version_buf = ""
- @debug = false
- super
- end
-
- def receive_data data
- if @state == :negotiating
- @version_buf << data
- if i = @version_buf.index("\n")
- l = @version_buf.slice!(0..i)
- receive_version *parse_version(l.strip)
- x = @version_buf
- @version_buf = nil
- @state = :established
- connection_established
- receive_data x
- end
- else
- @filter.decode(data).each do |msg|
- puts "#{self.class.name} received: #{msg.inspect}" if @debug
- validate_message *msg
- receive_message *msg
- end
- end
- end
-
- def connection_established
- end
-
- def send_version encodings, extensions
- fail if encodings.empty?
- send_data "Redwood #{VERSION} #{encodings * ','} #{extensions.empty? ? :none : (extensions * ',')}\n"
- end
-
- def send_message type, tag, params={}
- fail "attempted to send message during negotiation" unless @state == :established
- puts "#{self.class.name} sent: #{[type, tag, params].inspect}" if @debug
- validate_message type, tag, params
- send_data @filter.encode([type,tag,params])
- end
-
- def receive_version l
- fail "unimplemented"
- end
-
- def receive_message type, tag, params
- fail "unimplemented"
- end
-
-private
-
- def validate_message type, tag, params
- fail unless type.is_a? String or type.is_a? Symbol
- fail unless tag.is_a? String or tag.is_a? Integer
- fail unless params.is_a? Hash
- end
-
- def parse_version l
- l =~ /^Redwood\s+(\d+)\s+([\w,]+)\s+([\w,]+)$/ or fail "unexpected banner #{l.inspect}"
- version, encodings, extensions = $1.to_i, $2, $3
- encodings = encodings.split ','
- extensions = extensions.split ','
- extensions = [] if extensions == ['none']
- fail unless version == VERSION
- fail if encodings.empty?
- [encodings, extensions]
- end
-
- def create_filter encoding
- case encoding
- when 'json' then JSONFilter.new
- when 'marshal' then MarshalFilter.new
- else fail "unknown encoding #{encoding.inspect}"
- end
- end
-
- class JSONFilter
- def initialize
- @parser = Yajl::Parser.new :check_utf8 => false
- end
-
- def decode chunk
- parsed = []
- @parser.on_parse_complete = lambda { |o| parsed << o }
- @parser << chunk
- parsed
- end
-
- def encode *os
- os.inject('') { |s, o| s << Yajl::Encoder.encode(o) }
- end
- end
-
- class MarshalFilter
- def initialize
- @buf = ''
- @state = :prefix
- @size = 0
- end
-
- def decode chunk
- received = []
- @buf << chunk
-
- begin
- if @state == :prefix
- break unless @buf.size >= 4
- prefix = @buf.slice!(0...4)
- @size = prefix.unpack('N')[0]
- @state = :data
- end
-
- fail unless @state == :data
- break if @buf.size < @size
- received << Marshal.load(@buf.slice!(0... at size))
- @state = :prefix
- end until @buf.empty?
-
- received
- end
-
- def encode o
- data = Marshal.dump o
- [data.size].pack('N') + data
- end
- end
-end
-
-class EM::P::RedwoodServer < EM::P::Redwood
- def post_init
- send_version ENCODINGS, []
- end
-
- def receive_version encodings, extensions
- fail unless encodings.size == 1
- fail unless ENCODINGS.member? encodings.first
- @filter = create_filter encodings.first
- end
-end
-
-class EM::P::RedwoodClient < EM::P::Redwood
- def receive_version encodings, extensions
- encoding = (ENCODINGS & encodings).first
- fail unless encoding
- @filter = create_filter encoding
- send_version [encoding], []
- end
-end
diff --git a/lib/sup/rfc2047.rb b/lib/sup/rfc2047.rb
index 8b987db..1b00f23 100644
--- a/lib/sup/rfc2047.rb
+++ b/lib/sup/rfc2047.rb
@@ -16,8 +16,6 @@
#
# This file is distributed under the same terms as Ruby.
-require 'iconv'
-
module Rfc2047
WORD = %r{=\?([!\#$%&'*+-/0-9A-Z\\^\`a-z{|}~]+)\?([BbQq])\?([!->@-~]+)\?=} # :nodoc: 'stupid ruby-mode
WORDSEQ = %r{(#{WORD.source})\s+(?=#{WORD.source})}
@@ -52,7 +50,7 @@ module Rfc2047
# WORD.
end
- Iconv.easy_decode(target, charset, text)
+ text.transcode(target, charset)
end
end
end
diff --git a/lib/sup/search.rb b/lib/sup/search.rb
index 0c63b06..13c1a97 100644
--- a/lib/sup/search.rb
+++ b/lib/sup/search.rb
@@ -1,10 +1,14 @@
+# encoding: utf-8
+
module Redwood
class SearchManager
- include Singleton
+ include Redwood::Singleton
class ExpansionError < StandardError; end
+ attr_reader :predefined_searches
+
def initialize fn
@fn = fn
@searches = {}
@@ -15,33 +19,66 @@ class SearchManager
end
end
@modified = false
+
+ @predefined_searches = { 'All mail' => 'Search all mail.' }
+ @predefined_queries = { 'All mail'.to_sym => { :qobj => Xapian::Query.new('Kmail'),
+ :load_spam => false,
+ :load_deleted => false,
+ :load_killed => false,
+ :text => 'Search all mail.'}
+ }
+ @predefined_searches.each do |k,v|
+ @searches[k] = v
+ end
end
+ def predefined_queries; return @predefined_queries; end
def all_searches; return @searches.keys.sort; end
- def search_string_for name; return @searches[name]; end
+ def search_string_for name;
+ if @predefined_searches.keys.member? name
+ return name.to_sym
+ end
+ return @searches[name];
+ end
def valid_name? name; name =~ /^[\w-]+$/; end
def name_format_hint; "letters, numbers, underscores and dashes only"; end
def add name, search_string
return unless valid_name? name
+ if @predefined_searches.has_key? name
+ warn "cannot add search: #{name} is already taken by a predefined search"
+ return
+ end
@searches[name] = search_string
@modified = true
end
def rename old, new
return unless @searches.has_key? old
+ if [old, new].any? { |x| @predefined_searches.has_key? x }
+ warn "cannot rename search: #{old} or #{new} is already taken by a predefined search"
+ return
+ end
search_string = @searches[old]
delete old if add new, search_string
end
def edit name, search_string
return unless @searches.has_key? name
+ if @predefined_searches.has_key? name
+ warn "cannot edit predefined search: #{name}."
+ return
+ end
@searches[name] = search_string
@modified = true
end
def delete name
return unless @searches.has_key? name
+ if @predefined_searches.has_key? name
+ warn "cannot delete predefined search: #{name}."
+ return
+ end
@searches.delete name
@modified = true
end
@@ -65,7 +102,7 @@ class SearchManager
def save
return unless @modified
- File.open(@fn, "w") { |f| @searches.sort.each { |(n, s)| f.puts "#{n}: #{s}" } }
+ File.open(@fn, "w:UTF-8") { |f| (@searches - @predefined_searches.keys).sort.each { |(n, s)| f.puts "#{n}: #{s}" } }
@modified = false
end
end
diff --git a/lib/sup/sent.rb b/lib/sup/sent.rb
index 0ca1fb1..9a5e55c 100644
--- a/lib/sup/sent.rb
+++ b/lib/sup/sent.rb
@@ -1,7 +1,7 @@
module Redwood
class SentManager
- include Singleton
+ include Redwood::Singleton
attr_reader :source, :source_uri
@@ -25,8 +25,13 @@ class SentManager
end
def write_sent_message date, from_email, &block
- @source.store_message date, from_email, &block
- PollManager.poll_from @source
+ ::Thread.new do
+ debug "store the sent message (locking sent source..)"
+ @source.synchronize do
+ @source.store_message date, from_email, &block
+ end
+ PollManager.poll_from @source
+ end
end
end
diff --git a/lib/sup/server.rb b/lib/sup/server.rb
deleted file mode 100644
index f2dde6a..0000000
--- a/lib/sup/server.rb
+++ /dev/null
@@ -1,116 +0,0 @@
-require 'sup/protocol'
-
-module Redwood
-
-class Server < EM::P::RedwoodServer
- def initialize index
- super
- @index = index
- end
-
- def receive_message type, tag, params
- if respond_to? :"request_#{type}"
- send :"request_#{type}", tag, params
- else
- send_message 'error', tag, 'description' => "invalid request type #{type.inspect}"
- end
- end
-
- def request_query tag, a
- q = @index.parse_query a['query']
- query q, a['offset'], a['limit'], a['raw'] do |r|
- send_message 'message', tag, r
- end
- send_message 'done', tag
- end
-
- def request_count tag, a
- q = @index.parse_query a['query']
- c = count q
- send_message 'count', tag, 'count' => c
- end
-
- def request_label tag, a
- q = @index.parse_query a['query']
- label q, a['add'], a['remove']
- send_message 'done', tag
- end
-
- def request_add tag, a
- add a['raw'], a['labels']
- send_message 'done', tag
- end
-
- def request_thread tag, a
- thread a['message_id'], a['raw'] do |r|
- send_message 'message', tag, r
- end
- send_message 'done', tag
- end
-
-private
-
- def result_from_message m, raw
- mkperson = lambda { |p| { :email => p.email, :name => p.name } }
- {
- 'summary' => {
- 'message_id' => m.id,
- 'date' => m.date,
- 'from' => mkperson[m.from],
- 'to' => m.to.map(&mkperson),
- 'cc' => m.cc.map(&mkperson),
- 'bcc' => m.bcc.map(&mkperson),
- 'subject' => m.subj,
- 'refs' => m.refs,
- 'replytos' => m.replytos,
- 'labels' => m.labels.map(&:to_s),
- },
- 'raw' => raw ? m.raw_message : nil,
- }
- end
-
- def query query, offset, limit, raw
- c = 0
- @index.each_message query do |m|
- next if c < offset
- break if c >= offset + limit if limit
- yield result_from_message(m, raw)
- c += 1
- end
- nil
- end
-
- def count query
- @index.num_results_for query
- end
-
- def label query, remove_labels, add_labels
- @index.each_message query do |m|
- remove_labels.each { |l| m.remove_label l }
- add_labels.each { |l| m.add_label l }
- @index.update_message_state m
- end
- nil
- end
-
- def add raw, labels
- SentManager.source.store_message Time.now, "test at example.com" do |io|
- io.write raw
- end
- PollManager.poll_from SentManager.source do |sym,m,old_m,progress|
- next unless sym == :add
- m.labels = labels
- end
- nil
- end
-
- def thread msg_id, raw
- msg = @index.build_message msg_id
- @index.each_message_in_thread_for msg do |id, builder|
- m = builder.call
- yield result_from_message(m, raw)
- end
- end
-end
-
-end
diff --git a/lib/sup/service/label_service.rb b/lib/sup/service/label_service.rb
new file mode 100644
index 0000000..1882c67
--- /dev/null
+++ b/lib/sup/service/label_service.rb
@@ -0,0 +1,45 @@
+require "sup/index"
+
+module Redwood
+ # Provides label tweaking service to the user.
+ # Working as the backend of ConsoleMode.
+ #
+ # Should become the backend of bin/sup-tweak-labels in the future.
+ class LabelService
+ # @param index [Redwood::Index]
+ def initialize index=Index.instance
+ @index = index
+ end
+
+ def add_labels query, *labels
+ run_on_each_message(query) do |m|
+ labels.each {|l| m.add_label l }
+ end
+ end
+
+ def remove_labels query, *labels
+ run_on_each_message(query) do |m|
+ labels.each {|l| m.remove_label l }
+ end
+ end
+
+
+ private
+ def run_on_each_message query, &operation
+ count = 0
+
+ find_messages(query).each do |m|
+ operation.call(m)
+ @index.update_message_state m
+ count += 1
+ end
+
+ @index.save_index
+ count
+ end
+
+ def find_messages query
+ @index.find_messages(query)
+ end
+ end
+end
diff --git a/lib/sup/source.rb b/lib/sup/source.rb
index e0aa90e..89047c7 100644
--- a/lib/sup/source.rb
+++ b/lib/sup/source.rb
@@ -1,4 +1,5 @@
require "sup/rfc2047"
+require "monitor"
module Redwood
@@ -22,30 +23,23 @@ class Source
## read, delete them, or anything else. (Well, it's nice to be able
## to delete them, but that is optional.)
##
- ## On the other hand, Sup assumes that you can assign each message a
- ## unique integer id, such that newer messages have higher ids than
- ## earlier ones, and that those ids stay constant across sessions
- ## (in the absence of some other client going in and fucking
- ## everything up). For example, for mboxes I use the file offset of
- ## the start of the message. If a source does NOT have that
- ## capability, e.g. IMAP, then you have to do a little more work to
- ## simulate it.
+ ## Messages are identified internally based on the message id, and stored
+ ## with an unique document id. Along with the message, source information
+ ## that can contain arbitrary fields (set up by the source) is stored. This
+ ## information will be passed back to the source when a message in the
+ ## index (Sup database) needs to be identified to its source, e.g. when
+ ## re-reading or modifying a unique message.
##
## To write a new source, subclass this class, and implement:
##
- ## - start_offset
- ## - end_offset (exclusive!) (or, #done?)
+ ## - initialize
## - load_header offset
## - load_message offset
## - raw_header offset
## - raw_message offset
- ## - check (optional)
+ ## - store_message (optional)
+ ## - poll (loads new messages)
## - go_idle (optional)
- ## - next (or each, if you prefer): should return a message and an
- ## array of labels.
- ##
- ## ... where "offset" really means unique id. (You can tell I
- ## started with mbox.)
##
## All exceptions relating to accessing the source must be caught
## and rethrown as FatalSourceErrors or OutOfSyncSourceErrors.
@@ -57,11 +51,10 @@ class Source
## Finally, be sure the source is thread-safe, since it WILL be
## pummelled from multiple threads at once.
##
- ## Examples for you to look at: mbox/loader.rb, imap.rb, and
- ## maildir.rb.
+ ## Examples for you to look at: mbox.rb and maildir.rb.
bool_accessor :usual, :archived
- attr_reader :uri
+ attr_reader :uri, :usual
attr_accessor :id
def initialize uri, usual=true, archived=false, id=nil
@@ -71,9 +64,11 @@ class Source
@usual = usual
@archived = archived
@id = id
+
+ @poll_lock = Monitor.new
end
- ## overwrite me if you have a disk incarnation (currently used only for sup-sync-back)
+ ## overwrite me if you have a disk incarnation
def file_path; nil end
def to_s; @uri.to_s; end
@@ -87,6 +82,14 @@ class Source
## leaks (esp. file descriptors).
def go_idle; end
+ ## Returns an array containing all the labels that are natively
+ ## supported by this source
+ def supported_labels?; [] end
+
+ ## Returns an array containing all the labels that are currently in
+ ## the location filename
+ def labels? info; [] end
+
## Yields values of the form [Symbol, Hash]
## add: info, labels, progress
## delete: info, progress
@@ -98,6 +101,25 @@ class Source
true
end
+ def synchronize &block
+ @poll_lock.synchronize &block
+ end
+
+ def try_lock
+ acquired = @poll_lock.try_enter
+ if acquired
+ debug "lock acquired for: #{self}"
+ else
+ debug "could not acquire lock for: #{self}"
+ end
+ acquired
+ end
+
+ def unlock
+ @poll_lock.exit
+ debug "lock released for: #{self}"
+ end
+
## utility method to read a raw email header from an IO stream and turn it
## into a hash of key-value pairs. minor special semantics for certain headers.
##
@@ -160,12 +182,12 @@ module SerializeLabelsNicely
end
def after_unmarshal!
- @labels = Set.new(@labels.map { |s| s.to_sym })
+ @labels = Set.new(@labels.to_a.map { |s| s.to_sym })
end
end
class SourceManager
- include Singleton
+ include Redwood::Singleton
def initialize
@sources = {}
@@ -209,9 +231,9 @@ class SourceManager
end
end
- def save_sources fn=Redwood::SOURCE_FN
+ def save_sources fn=Redwood::SOURCE_FN, force=false
@source_mutex.synchronize do
- if @sources_dirty
+ if @sources_dirty || force
Redwood::save_yaml_obj sources, fn, false, true
end
@sources_dirty = false
diff --git a/lib/sup/tagger.rb b/lib/sup/tagger.rb
index d62f340..d02b045 100644
--- a/lib/sup/tagger.rb
+++ b/lib/sup/tagger.rb
@@ -1,3 +1,5 @@
+require 'sup/util/ncurses'
+
module Redwood
class Tagger
@@ -27,7 +29,7 @@ class Tagger
unless action
c = BufferManager.ask_getch "apply to #{num_tagged} tagged #{noun}:"
- return if c.nil? # user cancelled
+ return if c.empty? # user cancelled
action = @mode.resolve_input c
end
diff --git a/lib/sup/textfield.rb b/lib/sup/textfield.rb
index 343e450..22f7b11 100644
--- a/lib/sup/textfield.rb
+++ b/lib/sup/textfield.rb
@@ -1,3 +1,5 @@
+require 'sup/util/ncurses'
+
module Redwood
## a fully-functional text field supporting completions, expansions,
@@ -16,6 +18,8 @@ module Redwood
## in sup, completion support is implemented through BufferManager#ask
## and CompletionMode.
class TextField
+ include Ncurses::Form::DriverHelpers
+
def initialize
@i = nil
@history = []
@@ -48,8 +52,8 @@ class TextField
@w.attrset Colormap.color_for(:none)
@w.mvaddstr @y, 0, @question
Ncurses.curs_set 1
- Ncurses::Form.form_driver @form, Ncurses::Form::REQ_END_FIELD
- Ncurses::Form.form_driver @form, Ncurses::Form::REQ_NEXT_CHAR if @value && @value =~ / $/ # fucking RETARDED
+ form_driver_key Ncurses::Form::REQ_END_FIELD
+ form_driver_key Ncurses::Form::REQ_NEXT_CHAR if @value && @value =~ / $/ # fucking RETARDED
end
def deactivate
@@ -63,7 +67,7 @@ class TextField
def handle_input c
## short-circuit exit paths
- case c
+ case c.code
when Ncurses::KEY_ENTER # submit!
@value = get_cursed_value
@history.push @value unless @value =~ /^\s*$/
@@ -97,39 +101,27 @@ class TextField
reset_completion_state
@value = nil
- d =
- case c
+ # ctrl_c: control char
+ ctrl_c =
+ case c.keycode # only test for keycodes
when Ncurses::KEY_LEFT
Ncurses::Form::REQ_PREV_CHAR
when Ncurses::KEY_RIGHT
Ncurses::Form::REQ_NEXT_CHAR
when Ncurses::KEY_DC
Ncurses::Form::REQ_DEL_CHAR
- when Ncurses::KEY_BACKSPACE, 127 # 127 is also a backspace keysym
+ when Ncurses::KEY_BACKSPACE
Ncurses::Form::REQ_DEL_PREV
- when ?\C-a.ord, Ncurses::KEY_HOME
+ when Ncurses::KEY_HOME
nop
Ncurses::Form::REQ_BEG_FIELD
- when ?\C-e.ord, Ncurses::KEY_END
+ when Ncurses::KEY_END
Ncurses::Form::REQ_END_FIELD
- when ?\C-k.ord
- Ncurses::Form::REQ_CLR_EOF
- when ?\C-u.ord
- set_cursed_value cursed_value_after_point
- Ncurses::Form.form_driver @form, Ncurses::Form::REQ_END_FIELD
- nop
- Ncurses::Form::REQ_BEG_FIELD
- when ?\C-w.ord
- while action = remove_extra_space
- Ncurses::Form.form_driver @form, action
- end
- Ncurses::Form.form_driver @form, Ncurses::Form::REQ_PREV_CHAR
- Ncurses::Form.form_driver @form, Ncurses::Form::REQ_DEL_WORD
when Ncurses::KEY_UP, Ncurses::KEY_DOWN
unless !@i || @history.empty?
value = get_cursed_value
#debug "history before #{@history.inspect}"
- @i = @i + (c == Ncurses::KEY_UP ? -1 : 1)
+ @i = @i + (c.is_keycode?(Ncurses::KEY_UP) ? -1 : 1)
@i = 0 if @i < 0
@i = @history.size if @i > @history.size
@value = @history[@i] || ''
@@ -138,10 +130,37 @@ class TextField
Ncurses::Form::REQ_END_FIELD
end
else
- c
+ # return other keycode or nil if it's not a keycode
+ c.dumb? ? nil : c.keycode
end
- Ncurses::Form.form_driver @form, d if d
+ # handle keysyms
+ # ctrl_c: control char
+ ctrl_c = case c
+ when ?\177 # backspace (octal)
+ Ncurses::Form::REQ_DEL_PREV
+ when ?\C-a # home
+ nop
+ Ncurses::Form::REQ_BEG_FIELD
+ when ?\C-e # end keysym
+ Ncurses::Form::REQ_END_FIELD
+ when ?\C-k
+ Ncurses::Form::REQ_CLR_EOF
+ when ?\C-u
+ set_cursed_value cursed_value_after_point
+ form_driver_key Ncurses::Form::REQ_END_FIELD
+ nop
+ Ncurses::Form::REQ_BEG_FIELD
+ when ?\C-w
+ while action = remove_extra_space
+ form_driver_key action
+ end
+ form_driver_key Ncurses::Form::REQ_PREV_CHAR
+ form_driver_key Ncurses::Form::REQ_DEL_WORD
+ end if ctrl_c.nil?
+
+ c.replace(ctrl_c).keycode! if ctrl_c # no effect for dumb CharCode
+ form_driver c if c.present?
true
end
@@ -159,7 +178,7 @@ private
return nil unless @field
x = Ncurses.curx
- Ncurses::Form.form_driver @form, Ncurses::Form::REQ_VALIDATION
+ form_driver_key Ncurses::Form::REQ_VALIDATION
v = @field.field_buffer(0).gsub(/^\s+|\s+$/, "")
## cursor <= end of text
@@ -168,12 +187,22 @@ private
else # trailing spaces
v + (" " * (x - @question.length - v.length))
end
+
+ # ncurses returns a ASCII-8BIT (binary) string, which
+ # bytes presumably are of current charset encoding. we force_encoding
+ # so that the char representation / string is tagged will be the
+ # system locale and also hopefully the terminal/input encoding. an
+ # incorrectly configured terminal encoding (not matching the system
+ # encoding) will produce erronous results, but will also do that for
+ # a lot of other programs since it is impossible to detect which is
+ # which and what encoding the inputted byte chars are supposed to have.
+ v.force_encoding($encoding).fix_encoding!
end
def remove_extra_space
return nil unless @field
- Ncurses::Form.form_driver @form, Ncurses::Form::REQ_VALIDATION
+ form_driver_key Ncurses::Form::REQ_VALIDATION
x = Ncurses.curx
v = @field.field_buffer(0).gsub(/^\s+|\s+$/, "")
v_index = x - @question.length
@@ -205,6 +234,7 @@ private
end
def set_cursed_value v
+ v = "" if v.nil?
@field.set_field_buffer 0, v
end
@@ -216,8 +246,8 @@ private
## this is almost certainly unnecessary, but it's the only way
## i could get ncurses to remember my form's value
def nop
- Ncurses::Form.form_driver @form, " ".ord
- Ncurses::Form.form_driver @form, Ncurses::Form::REQ_DEL_PREV
+ form_driver_char " "
+ form_driver_key Ncurses::Form::REQ_DEL_PREV
end
end
end
diff --git a/lib/sup/thread.rb b/lib/sup/thread.rb
index f1414ba..bb47ad7 100644
--- a/lib/sup/thread.rb
+++ b/lib/sup/thread.rb
@@ -1,3 +1,5 @@
+# encoding: UTF-8
+#
## Herein lies all the code responsible for threading messages. It's
## basically an online version of the JWZ threading algorithm:
## http://www.jwz.org/doc/threading.html
@@ -172,7 +174,7 @@ class Container
def each_with_stuff parent=nil
yield self, 0, parent
- @children.each do |c|
+ @children.sort_by(&:sort_key).each do |c|
c.each_with_stuff(self) { |cc, d, par| yield cc, d + 1, par }
end
end
@@ -239,6 +241,10 @@ class Container
indent += 3
@children.each { |c| c.dump_recursive f, indent, false, self }
end
+
+ def sort_key
+ empty? ? [Time.now.to_i, ""] : [@message.date.to_i, @message.id]
+ end
end
## A set of threads, so a forest. Is integrated with the index and
@@ -387,6 +393,12 @@ class ThreadSet
m.refs.any? { |ref_id| @messages.member? ref_id }
end
+ def delete_message message
+ el = @messages[message.id]
+ return unless el.message
+ el.message = nil
+ end
+
## the heart of the threading code
def add_message message
el = @messages[message.id]
diff --git a/lib/sup/time.rb b/lib/sup/time.rb
index c076e19..7675bab 100644
--- a/lib/sup/time.rb
+++ b/lib/sup/time.rb
@@ -60,6 +60,8 @@ EOS
end
TO_NICE_S_MAX_LEN = 9 # e.g. "Yest.10am"
+
+ ## This is how a thread date is displayed in thread-index-mode
def to_nice_s from=Time.now
Redwood::HookManager.run("time-to-nice-string", :time => self, :from => from) || default_to_nice_s(from)
end
@@ -71,13 +73,21 @@ EOS
strftime "%b %e"
else
if is_the_same_day? from
- strftime("%l:%M%p").downcase # emulate %P (missing on ruby 1.8 darwin)
+ format = $config[:time_mode] == "24h" ? "%k:%M" : "%l:%M%p"
+ strftime(format).downcase
elsif is_the_day_before? from
- "Yest." + nearest_hour.strftime("%l%p").downcase # emulate %P
+ format = $config[:time_mode] == "24h" ? "%kh" : "%l%p"
+ "Yest." + nearest_hour.strftime(format).downcase
else
strftime "%b %e"
end
end
end
+
+ ## This is how a message date is displayed in thread-view-mode
+ def to_message_nice_s from=Time.now
+ format = $config[:time_mode] == "24h" ? "%B %e %Y %k:%M" : "%B %e %Y %l:%M%p"
+ strftime format
+ end
end
diff --git a/lib/sup/undo.rb b/lib/sup/undo.rb
index 9ccf84a..070fbed 100644
--- a/lib/sup/undo.rb
+++ b/lib/sup/undo.rb
@@ -8,7 +8,7 @@ module Redwood
## undo the archival action
class UndoManager
- include Singleton
+ include Redwood::Singleton
def initialize
@@actionlist = []
diff --git a/lib/sup/update.rb b/lib/sup/update.rb
index d386801..53d400a 100644
--- a/lib/sup/update.rb
+++ b/lib/sup/update.rb
@@ -12,7 +12,7 @@ module Redwood
## single "view". Luckily, that's true.)
class UpdateManager
- include Singleton
+ include Redwood::Singleton
def initialize
@targets = {}
diff --git a/lib/sup/util.rb b/lib/sup/util.rb
index cd68fe0..f5ddd92 100644
--- a/lib/sup/util.rb
+++ b/lib/sup/util.rb
@@ -1,3 +1,5 @@
+# encoding: utf-8
+
require 'thread'
require 'lockfile'
require 'mime/types'
@@ -5,6 +7,8 @@ require 'pathname'
require 'set'
require 'enumerator'
require 'benchmark'
+require 'unicode'
+require 'fileutils'
## time for some monkeypatching!
class Symbol
@@ -30,7 +34,7 @@ class Lockfile
def dump_lock_id lock_id = @lock_id
"host: %s\npid: %s\nppid: %s\ntime: %s\nuser: %s\npname: %s\n" %
lock_id.values_at('host','pid','ppid','time','user', 'pname')
- end
+ end
def lockinfo_on_disk
h = load_lock_id IO.read(path)
@@ -42,6 +46,17 @@ class Lockfile
def touch_yourself; touch path end
end
+class File
+ # platform safe file.link which attempts a copy if hard-linking fails
+ def self.safe_link src, dest
+ begin
+ File.link src, dest
+ rescue
+ FileUtils.copy src, dest
+ end
+ end
+end
+
class Pathname
def human_size
s =
@@ -113,6 +128,23 @@ module RMail
end
class Header
+
+ # Convert to ASCII before trying to match with regexp
+ class Field
+
+ class << self
+ def parse(field)
+ field = field.dup.to_s
+ field = field.fix_encoding!.ascii
+ if field =~ EXTRACT_FIELD_NAME_RE
+ [ $1, $'.chomp ]
+ else
+ [ "", Field.value_strip(field) ]
+ end
+ end
+ end
+ end
+
## Be more cautious about invalid content-type headers
## the original RMail code calls
## value.strip.split(/\s*;\s*/)[0].downcase
@@ -233,14 +265,22 @@ class Object
end
class String
- ## nasty multibyte hack for ruby 1.8. if it's utf-8, split into chars using
- ## the utf8 regex and count those. otherwise, use the byte length.
def display_length
- if RUBY_VERSION < '1.9.1' && ($encoding == "UTF-8" || $encoding == "utf8")
- # scan hack is somewhat slow, worth trying to cache
- @display_length ||= scan(/./u).size
- else
- size
+ @display_length ||= Unicode.width(self.fix_encoding!, false)
+
+ # if Unicode.width fails and returns -1, fall back to
+ # regular String#length, see pull-request: #256.
+ if @display_length < 0
+ @display_length = self.length
+ end
+
+ @display_length
+ end
+
+ def slice_by_display_length len
+ each_char.each_with_object "" do |c, buffer|
+ len -= c.display_length
+ buffer << c if len >= 0
end
end
@@ -327,20 +367,69 @@ class String
def wrap len
ret = []
s = self
- while s.length > len
- cut = s[0 ... len].rindex(/\s/)
+ while s.display_length > len
+ cut = s.slice_by_display_length(len).rindex(/\s/)
if cut
ret << s[0 ... cut]
s = s[(cut + 1) .. -1]
else
- ret << s[0 ... len]
- s = s[len .. -1]
+ ret << s.slice_by_display_length(len)
+ s = s[ret.last.length .. -1]
end
end
ret << s
end
+ # Fix the damn string! make sure it is valid utf-8, then convert to
+ # user encoding.
+ #
+ # Not Ruby 1.8 compatible
+ def fix_encoding!
+ # first try to encode to utf-8 from whatever current encoding
+ encode!('UTF-8', :invalid => :replace, :undef => :replace)
+
+ # do this anyway in case string is set to be UTF-8, encoding to
+ # something else (UTF-16 which can fully represent UTF-8) and back
+ # ensures invalid chars are replaced.
+ encode!('UTF-16', 'UTF-8', :invalid => :replace, :undef => :replace)
+ encode!('UTF-8', 'UTF-16', :invalid => :replace, :undef => :replace)
+
+ fail "Could not create valid UTF-8 string out of: '#{self.to_s}'." unless valid_encoding?
+
+ # now convert to $encoding
+ encode!($encoding, :invalid => :replace, :undef => :replace)
+
+ fail "Could not create valid #{$encoding.inspect} string out of: '#{self.to_s}'." unless valid_encoding?
+
+ self
+ end
+
+ # transcode the string if original encoding is know
+ # fix if broken.
+ #
+ # Not Ruby 1.8 compatible
+ def transcode to_encoding, from_encoding
+ begin
+ encode!(to_encoding, from_encoding, :invalid => :replace, :undef => :replace)
+
+ unless valid_encoding?
+ # fix encoding (through UTF-8)
+ encode!('UTF-16', from_encoding, :invalid => :replace, :undef => :replace)
+ encode!(to_encoding, 'UTF-16', :invalid => :replace, :undef => :replace)
+ end
+
+ rescue Encoding::ConverterNotFoundError
+ debug "Encoding converter not found for #{from_encoding.inspect} or #{to_encoding.inspect}, fixing string: '#{self.to_s}', but expect weird characters."
+ fix_encoding!
+ end
+
+ fail "Could not create valid #{to_encoding.inspect} string out of: '#{self.to_s}'." unless valid_encoding?
+
+ self
+ end
+
def normalize_whitespace
+ fix_encoding!
gsub(/\t/, " ").gsub(/\r/, "")
end
@@ -382,12 +471,8 @@ class String
out << b.chr
end
end
- out.force_encoding Encoding::UTF_8 if out.respond_to? :force_encoding
- out
- end
-
- def transcode src_encoding=$encoding
- Iconv.easy_decode $encoding, src_encoding, self
+ out = out.fix_encoding! # this should now be an utf-8 string of ascii
+ # compat chars.
end
unless method_defined? :ascii_only?
@@ -547,39 +632,41 @@ end
## classes that inherit this can define initialize. however, you cannot call
## .new on the class. To get the instance of the class, call .instance;
## to create the instance, call init.
-module Singleton
- module ClassMethods
- def instance; @instance; end
- def instantiated?; defined?(@instance) && !@instance.nil?; end
- def deinstantiate!; @instance = nil; end
- def method_missing meth, *a, &b
- raise "no #{name} instance defined in method call to #{meth}!" unless defined? @instance
-
- ## if we've been deinstantiated, just drop all calls. this is
- ## useful because threads that might be active during the
- ## cleanup process (e.g. polling) would otherwise have to
- ## special-case every call to a Singleton object
- return nil if @instance.nil?
-
- # Speed up further calls by defining a shortcut around method_missing
- if meth.to_s[-1,1] == '='
- # Argh! Inconsistency! Setters do not work like all the other methods.
- class_eval "def self.#{meth}(a); @instance.send :#{meth}, a; end"
- else
- class_eval "def self.#{meth}(*a, &b); @instance.send :#{meth}, *a, &b; end"
- end
+module Redwood
+ module Singleton
+ module ClassMethods
+ def instance; @instance; end
+ def instantiated?; defined?(@instance) && !@instance.nil?; end
+ def deinstantiate!; @instance = nil; end
+ def method_missing meth, *a, &b
+ raise "no #{name} instance defined in method call to #{meth}!" unless defined? @instance
+
+ ## if we've been deinstantiated, just drop all calls. this is
+ ## useful because threads that might be active during the
+ ## cleanup process (e.g. polling) would otherwise have to
+ ## special-case every call to a Singleton object
+ return nil if @instance.nil?
+
+ # Speed up further calls by defining a shortcut around method_missing
+ if meth.to_s[-1,1] == '='
+ # Argh! Inconsistency! Setters do not work like all the other methods.
+ class_eval "def self.#{meth}(a); @instance.send :#{meth}, a; end"
+ else
+ class_eval "def self.#{meth}(*a, &b); @instance.send :#{meth}, *a, &b; end"
+ end
- @instance.send meth, *a, &b
- end
- def init *args
- raise "there can be only one! (instance)" if instantiated?
- @instance = new(*args)
+ @instance.send meth, *a, &b
+ end
+ def init *args
+ raise "there can be only one! (instance)" if instantiated?
+ @instance = new(*args)
+ end
end
- end
- def self.included klass
- klass.private_class_method :allocate, :new
- klass.extend ClassMethods
+ def self.included klass
+ klass.private_class_method :allocate, :new
+ klass.extend ClassMethods
+ end
end
end
@@ -658,27 +745,3 @@ class FinishLine
end
end
-class Iconv
- def self.easy_decode target, orig_charset, text
- if text.respond_to? :force_encoding
- text = text.dup
- text.force_encoding Encoding::BINARY
- end
- charset = case orig_charset
- when /UTF[-_ ]?8/i then "utf-8"
- when /(iso[-_ ])?latin[-_ ]?1$/i then "ISO-8859-1"
- when /iso[-_ ]?8859[-_ ]?15/i then 'ISO-8859-15'
- when /unicode[-_ ]1[-_ ]1[-_ ]utf[-_]7/i then "utf-7"
- when /^euc$/i then 'EUC-JP' # XXX try them all?
- when /^(x-unknown|unknown[-_ ]?8bit|ascii[-_ ]?7[-_ ]?bit)$/i then 'ASCII'
- else orig_charset
- end
-
- begin
- returning(Iconv.iconv(target + "//IGNORE", charset, text + " ").join[0 .. -2]) { |str| str.check }
- rescue Errno::EINVAL, Iconv::InvalidEncoding, Iconv::InvalidCharacter, Iconv::IllegalSequence, String::CheckError
- debug "couldn't transcode text from #{orig_charset} (#{charset}) to #{target} (#{text[0 ... 20].inspect}...): got #{$!.class} (#{$!.message})"
- text.ascii
- end
- end
-end
diff --git a/lib/sup/util/ncurses.rb b/lib/sup/util/ncurses.rb
new file mode 100644
index 0000000..fcc3d27
--- /dev/null
+++ b/lib/sup/util/ncurses.rb
@@ -0,0 +1,274 @@
+require 'ncursesw'
+require 'sup/util'
+
+if defined? Ncurses
+module Ncurses
+
+ ## Helper class for storing keycodes
+ ## and multibyte characters.
+ class CharCode < String
+ ## Status code allows us to detect
+ ## printable characters and control codes.
+ attr_reader :status
+
+ ## Reads character from user input.
+ def self.nonblocking_getwch
+ # If we get input while we're shelled, we'll ignore it for the
+ # moment and use Ncurses.sync to wait until the shell_out is done.
+ begin
+ s, c = Redwood::BufferManager.shelled? ? Ncurses.sync { nil } : Ncurses.get_wch
+ break if s != Ncurses::ERR
+ end until IO.select([$stdin], nil, nil, 2)
+ [s, c]
+ end
+
+ ## Returns empty singleton.
+ def self.empty
+ Empty.instance
+ end
+
+ ## Creates new instance of CharCode
+ ## that keeps a given keycode.
+ def self.keycode(c)
+ generate c, Ncurses::KEY_CODE_YES
+ end
+
+ ## Creates new instance of CharCode
+ ## that keeps a printable character.
+ def self.character(c)
+ generate c, Ncurses::OK
+ end
+
+ ## Generates new object like new
+ ## but for empty or erroneous objects
+ ## it returns empty singleton.
+ def self.generate(c = nil, status = Ncurses::OK)
+ if status == Ncurses::ERR || c.nil? || c === Ncurses::ERR
+ empty
+ else
+ new(c, status)
+ end
+ end
+
+ ## Gets character from input.
+ ## Pretends ctrl-c's are ctrl-g's.
+ def self.get handle_interrupt=true
+ begin
+ status, code = nonblocking_getwch
+ generate code, status
+ rescue Interrupt => e
+ raise e unless handle_interrupt
+ keycode Ncurses::KEY_CANCEL
+ end
+ end
+
+ ## Enables dumb mode for any new instance.
+ def self.dumb!
+ @dumb = true
+ end
+
+ ## Asks if dumb mode was set
+ def self.dumb?
+ defined?(@dumb) && @dumb
+ end
+
+ def initialize(c = "", status = Ncurses::OK)
+ @status = status
+ c = "" if c.nil?
+ return super("") if status == Ncurses::ERR
+ c = enc_char(c) if c.is_a?(Fixnum)
+ super c.length > 1 ? c[0,1] : c
+ end
+
+ ## Proxy method for String's replace
+ def replace(c)
+ return self if c.object_id == object_id
+ if c.is_a?(self.class)
+ @status = c.status
+ super(c)
+ else
+ @status = Ncurses::OK
+ c = "" if c.nil?
+ c = enc_char(c) if c.is_a?(Fixnum)
+ super c.length > 1 ? c[0,1] : c
+ end
+ end
+
+ def to_character ; character? ? self : "<#{code}>" end ## Returns character or code as a string
+ def to_keycode ; keycode? ? code : Ncurses::ERR end ## Returns keycode or ERR if it's not a keycode
+ def to_sequence ; bytes.to_a end ## Returns unpacked sequence of bytes for a character
+ def code ; ord end ## Returns decimal representation of a character
+ def is_keycode?(c) ; keycode? && code == c end ## Tests if keycode matches
+ def is_character?(c); character? && self == c end ## Tests if character matches
+ def try_keycode ; keycode? ? code : nil end ## Returns dec. code if keycode, nil otherwise
+ def try_character ; character? ? self : nil end ## Returns character if character, nil otherwise
+ def keycode ; try_keycode end ## Alias for try_keycode
+ def character ; try_character end ## Alias for try_character
+ def character? ; dumb? || @status == Ncurses::OK end ## Returns true if character
+ def character! ; @status = Ncurses::OK ; self end ## Sets character flag
+ def keycode? ; dumb? || @status == Ncurses::KEY_CODE_YES end ## Returns true if keycode
+ def keycode! ; @status = Ncurses::KEY_CODE_YES ; self end ## Sets keycode flag
+ def keycode=(c) ; replace(c); keycode! ; self end ## Sets keycode
+ def present? ; not empty? end ## Proxy method
+ def printable? ; character? end ## Alias for character?
+ def dumb? ; self.class.dumb? end ## True if we cannot distinguish keycodes from characters
+
+ # Empty singleton that
+ # keeps GC from going crazy.
+ class Empty < CharCode
+ include Redwood::Singleton
+
+ ## Wrap methods that may change us
+ ## and generate new object instead.
+ [ :"[]=", :"<<", :replace, :insert, :prepend, :append, :concat, :force_encoding, :setbyte ].
+ select{ |m| public_method_defined?(m) }.
+ concat(public_instance_methods.grep(/!\z/)).
+ each do |m|
+ class_eval <<-EVAL
+ def #{m}(*args)
+ CharCode.new.#{m}(*args)
+ end
+ EVAL
+ end
+
+ ## proxy with class-level instance variable delegation
+ def self.dumb?
+ superclass.dumb? or !!@dumb
+ end
+
+ def self.empty
+ instance
+ end
+
+ def initialize
+ super("", Ncurses::ERR)
+ end
+
+ def empty? ; true end ## always true
+ def present? ; false end ## always false
+ def clear ; self end ## always self
+
+ self
+ end.init # CharCode::Empty
+
+ private
+
+ ## Tries to make external character right.
+ def enc_char(c)
+ begin
+ character = c.chr($encoding)
+ rescue RangeError, ArgumentError
+ begin
+ character = [c].pack('U')
+ rescue RangeError
+ begin
+ character = c.chr
+ rescue
+ begin
+ character = [c].pack('C')
+ rescue
+ character = ""
+ @status = Ncurses::ERR
+ end
+ end
+ end
+ character.fix_encoding!
+ end
+ end
+ end # class CharCode
+
+ def rows
+ lame, lamer = [], []
+ stdscr.getmaxyx lame, lamer
+ lame.first
+ end
+
+ def cols
+ lame, lamer = [], []
+ stdscr.getmaxyx lame, lamer
+ lamer.first
+ end
+
+ def curx
+ lame, lamer = [], []
+ stdscr.getyx lame, lamer
+ lamer.first
+ end
+
+ ## Create replacement wrapper for form_driver_w (), which is not (yet) a standard
+ ## function in ncurses. Some systems (Mac OS X) does not have a working
+ ## form_driver that accepts wide chars. We are just falling back to form_driver, expect problems.
+ def prepare_form_driver
+ if not defined? Form.form_driver_w
+ warn "Your Ncursesw does not have a form_driver_w function (wide char aware), " \
+ "non-ASCII chars may not work on your system."
+ Form.module_eval <<-FRM_DRV, __FILE__, __LINE__ + 1
+ def form_driver_w form, status, c
+ form_driver form, c
+ end
+ module_function :form_driver_w
+ module DriverHelpers
+ def form_driver c
+ if !c.dumb? && c.printable?
+ c.each_byte do |code|
+ Ncurses::Form.form_driver @form, code
+ end
+ else
+ Ncurses::Form.form_driver @form, c.code
+ end
+ end
+ end
+ FRM_DRV
+ end # if not defined? Form.form_driver_w
+ if not defined? Ncurses.get_wch
+ warn "Your Ncursesw does not have a get_wch function (wide char aware), " \
+ "non-ASCII chars may not work on your system."
+ Ncurses.module_eval <<-GET_WCH, __FILE__, __LINE__ + 1
+ def get_wch
+ c = getch
+ c == Ncurses::ERR ? [c, 0] : [Ncurses::OK, c]
+ end
+ module_function :get_wch
+ GET_WCH
+ CharCode.dumb!
+ end # if not defined? Ncurses.get_wch
+ end
+
+ def mutex; @mutex ||= Mutex.new; end
+ def sync &b; mutex.synchronize(&b); end
+
+ module_function :rows, :cols, :curx, :mutex, :sync, :prepare_form_driver
+
+ remove_const :KEY_ENTER
+ remove_const :KEY_CANCEL
+
+ KEY_ENTER = 10
+ KEY_CANCEL = 7 # ctrl-g
+ KEY_TAB = 9
+
+ module Form
+ ## This module contains helpers that ease
+ ## using form_driver_ methods when @form is present.
+ module DriverHelpers
+ private
+
+ ## Ncurses::Form.form_driver_w wrapper for keycodes and control characters.
+ def form_driver_key c
+ form_driver CharCode.keycode(c)
+ end
+
+ ## Ncurses::Form.form_driver_w wrapper for printable characters.
+ def form_driver_char c
+ form_driver CharCode.character(c)
+ #c.is_a?(Fixnum) ? c : c.ord
+ end
+
+ ## Ncurses::Form.form_driver_w wrapper for charcodes.
+ def form_driver c
+ Ncurses::Form.form_driver_w @form, c.status, c.code
+ end
+ end # module DriverHelpers
+ end # module Form
+
+end # module Ncurses
+end # if defined? Ncurses
diff --git a/lib/sup/util/path.rb b/lib/sup/util/path.rb
new file mode 100644
index 0000000..3e24ff7
--- /dev/null
+++ b/lib/sup/util/path.rb
@@ -0,0 +1,9 @@
+module Redwood
+ module Util
+ module Path
+ def self.expand(path)
+ ::File.expand_path(path)
+ end
+ end
+ end
+end
diff --git a/lib/sup/util/query.rb b/lib/sup/util/query.rb
new file mode 100644
index 0000000..cb8ab1f
--- /dev/null
+++ b/lib/sup/util/query.rb
@@ -0,0 +1,17 @@
+module Redwood
+ module Util
+ module Query
+ class QueryDescriptionError < ArgumentError; end
+
+ def self.describe(query, fallback = nil)
+ d = query.description.force_encoding("UTF-8")
+
+ unless d.valid_encoding?
+ raise QueryDescriptionError.new(d) unless fallback
+ d = fallback
+ end
+ return d
+ end
+ end
+ end
+end
diff --git a/lib/sup/util/uri.rb b/lib/sup/util/uri.rb
new file mode 100644
index 0000000..dfb27b3
--- /dev/null
+++ b/lib/sup/util/uri.rb
@@ -0,0 +1,15 @@
+require "uri"
+
+require "sup/util/path"
+
+module Redwood
+ module Util
+ module Uri
+ def self.build(components)
+ components = components.dup
+ components[:path] = Path.expand(components[:path])
+ ::URI::Generic.build(components)
+ end
+ end
+ end
+end
diff --git a/lib/sup/version.rb b/lib/sup/version.rb
new file mode 100644
index 0000000..40f458f
--- /dev/null
+++ b/lib/sup/version.rb
@@ -0,0 +1,3 @@
+module Redwood
+ VERSION = "0.19.0"
+end
diff --git a/protocol.md b/protocol.md
deleted file mode 100644
index 1d070a5..0000000
--- a/protocol.md
+++ /dev/null
@@ -1,168 +0,0 @@
-Redwood Protocol
-================
-
-The server begins by sending a line of the form `Redwood <ver> <encodings>
-<extensions>`, where `ver` is the major protocol version (1), encodings is a
-comma-separated list of supported message encodings (e.g. `json,bert,marshal`),
-and `extensions` is a comma-separated list of protocol extensions. The server
-must advertise at least one encoding. A zero-length list of extensions is
-represented by `none`. The client replies in the same format, with the
-restrictions that the protocol version must match, `encodings` and `extensions`
-must be subsets of what the server advertised, and there must be exactly 1
-encoding specified.
-
-Requests and responses are represented as `[type, params]`, where `type`
-is a lowercase string corresponding to one of the message types specified
-below and `params` is a dictionary with string keys.
-
-Requests
---------
-
-There may be zero or more replies to a request. Multiple requests may be
-issued concurrently. There is an implicit, optional, opaque `tag` parameter to
-every request which will be returned in all replies to the request to
-aid clients in keeping multiple requests in flight. `tag` may be an
-arbitrary datastructure and for the purposes of Cancel defaults to nil.
-
-### Query
-Send a Message response for each hit on `query` starting at `offset`
-and sending a maximum of `limit` Messages responses. `raw` controls
-whether the raw message text is included in the response.
-
-#### Parameters
-* `query`: Query
-* `offset`: int
-* `limit`: int
-* `raw`: boolean
-
-#### Responses
-* multiple Message
-* one Done after all Messages
-
-
-### Count
-Send a count reply with the number of hits for `query`.
-
-#### Parameters
-* `query`: Query
-
-#### Responses
-* one Count
-
-
-### Label
-Modify the labels on all messages matching `query`. First removes the
-labels in `remove` then adds those in `add`.
-
-#### Parameters
-* `query`: Query
-* `add`: string list
-* `remove`: string list
-
-#### Responses
-* one Done
-
-
-### Add
-Add a message to the database. `raw` is the normal RFC 2822 message text.
-
-#### Parameters
-* `raw`: string
-* `labels`: string list
-
-#### Responses
-* one Done
-
-
-### Stream
-Sends a Message response whenever a new message that matches `query` is
-added with the Add request. This request will not terminate until a
-corresponding Cancel request is sent.
-
-#### Parameters
-* `query`: Query
-
-#### Responses
-multiple Message
-
-
-### Cancel
-Cancels all active requests with tag `target`. This is only required to
-be implemented for the Stream request.
-
-#### Parameters
-* `target`: string
-
-#### Responses
-one Done
-
-
-
-Responses
----------
-
-### Done
-Signifies that a request has completed successfully.
-
-
-### Message
-Represents a query result. If `raw` is present it is the raw message
-text that was previously a parameter to the Add request.
-
-#### Parameters
-* `summary`: Summary
-* `raw`: optional string
-
-
-### Count
-`count` is the number of messages matched.
-
-#### Parameters
-* `count`: int
-
-
-### Error
-
-#### Parameters
-* `type`: string
-* `message`: string
-
-
-
-Datatypes
----------
-
-### Query
-Recursive prefix-notation datastructure describing a boolean condition.
-Where `a` and `b` are Queries and `field` and `value` are strings, a
-Query can be any of the following:
-
-* `[:and, a, b, ...]`
-* `[:or, a, b, ...]`
-* `[:not, a, b]`
-* `[:term, field, value]`
-
-
-### Summary
-* `message_id`: string
-* `date`: time
-* `from`: Person
-* `to`, `cc`, `bcc`: Person list
-* `subject`: string
-* `refs`: string list
-* `replytos`: string list
-* `labels`: string list
-
-
-### Person
-* `name`: string
-* `email`: string
-
-
-TODO
-----
-
-* Protocol negotiation
- - Version
- - Compression (none, gzip, ...)
-* Specify string encodings
diff --git a/release-script.txt b/release-script.txt
deleted file mode 100644
index 4063139..0000000
--- a/release-script.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-Just a few simple steps to make a new release.
-
-vi History.txt
-vi ReleaseNotes
-vi www/index.html # and bump version number
-git rank-contributors -n -o > CONTRIBUTORS
-vi CONTRIBUTORS # and merge
-vi www/index.html # and include CONTRIBUTORS
-# ... git add, commit, etc
-git checkout -b release-<releasename>
-vi lib/sup.rb bin/* # and bump version numbers in all files
-# ... git add, commit, etc
-rake gem
-rake tarball
-gem push pkg/<gem name> # now using gemcutter
-git publish-branch
-rake upload_webpage
diff --git a/sup-files.rb b/sup-files.rb
deleted file mode 100644
index 2c7d874..0000000
--- a/sup-files.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-SUP_LIB_DIRS = %w(lib lib/sup lib/sup/modes)
-SUP_EXECUTABLES = %w(sup sup-add sup-cmd sup-config sup-dump sup-import-dump sup-recover-sources sup-server sup-sync sup-sync-back sup-tweak-labels)
-SUP_EXTRA_FILES = %w(CONTRIBUTORS README.txt LICENSE History.txt ReleaseNotes)
-SUP_FILES =
- SUP_EXTRA_FILES +
- SUP_EXECUTABLES.map { |f| "bin/#{f}" } +
- SUP_LIB_DIRS.map { |d| Dir["#{d}/*.rb"] }.flatten
-
-if $0 == __FILE__ # if executed from commandline
- puts SUP_FILES
-end
diff --git a/sup-version.rb b/sup-version.rb
deleted file mode 100644
index 1a10ed2..0000000
--- a/sup-version.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-## allow people who use development versions by running "rake gem"
-## and installing the resulting gem it to be able to do this. (gem
-## versions must be in dotted-digit notation only and can be passed
-## with the REL environment variable to "rake gem").
-SUP_VERSION = if ENV['REL']
- ENV['REL']
-else
- $:.unshift 'lib' # force loading from ./lib/ if it exists
- require 'sup'
- if Redwood::VERSION == "git"
- "999"
- else
- Redwood::VERSION
- end
-end
diff --git a/sup.gemspec b/sup.gemspec
new file mode 100644
index 0000000..9cdd6dd
--- /dev/null
+++ b/sup.gemspec
@@ -0,0 +1,53 @@
+$:.push File.expand_path("../lib", __FILE__)
+
+require 'sup/version'
+
+Gem::Specification.new do |s|
+ s.name = "sup"
+ s.version = ENV["REL"] || (::Redwood::VERSION == "git" ? "999" : ::Redwood::VERSION)
+ s.date = Time.now.strftime "%Y-%m-%d"
+ s.authors = ["William Morgan", "Gaute Hope", "Hamish Downer", "Matthieu Rakotojaona"]
+ s.email = "supmua at googlegroups.com"
+ s.summary = "A console-based email client with the best features of GMail, mutt and Emacs"
+ s.homepage = "http://supmua.org"
+ s.license = 'GPL-2'
+ s.description = <<-DESC
+ Sup is a console-based email client for people with a lot of email.
+
+ * GMail-like thread-centered archiving, tagging and muting
+ * Handling mail from multiple mbox and Maildir sources
+ * Blazing fast full-text search with a rich query language
+ * Multiple accounts - pick the right one when sending mail
+ * Ruby-programmable hooks
+ * Automatically tracking recent contacts
+DESC
+ s.post_install_message = <<-EOF
+SUP: please note that our old mailing lists have been shut down,
+ re-subscribe to supmua at googlegroups.com to discuss and follow
+ updates on sup (send email to: supmua+subscribe at googlegroups.com).
+ EOF
+
+ s.files = `git ls-files -z`.split("\x0")
+ s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
+ s.require_paths = ["lib"]
+
+ s.required_ruby_version = '>= 1.9.3'
+
+ s.add_runtime_dependency "xapian-ruby", "~> 1.2.15"
+ s.add_runtime_dependency "ncursesw", "~> 1.4.0"
+ s.add_runtime_dependency "rmail-sup", "~> 1.0.1"
+ s.add_runtime_dependency "highline"
+ s.add_runtime_dependency "trollop", ">= 1.12"
+ s.add_runtime_dependency "lockfile"
+ s.add_runtime_dependency "mime-types", "~> 1.0"
+ s.add_runtime_dependency "locale", "~> 2.0"
+ s.add_runtime_dependency "chronic", "~> 0.9.1"
+ s.add_runtime_dependency "unicode", "~> 0.4.4"
+
+ s.add_development_dependency "bundler", "~> 1.3"
+ s.add_development_dependency "rake"
+ s.add_development_dependency "minitest", "~> 4.7"
+ s.add_development_dependency "rr", "~> 1.0.5"
+ s.add_development_dependency "gpgme", ">= 2.0.2"
+end
diff --git a/test/dummy_source.rb b/test/dummy_source.rb
index da26e44..abedf71 100644
--- a/test/dummy_source.rb
+++ b/test/dummy_source.rb
@@ -12,7 +12,7 @@ class DummySource < Source
attr_accessor :messages
def initialize uri, last_date=nil, usual=true, archived=false, id=nil, labels=[]
- super uri, last_date, usual, archived, id
+ super uri, usual, archived, id
@messages = nil
end
diff --git a/test/gnupg_test_home/gpg.conf b/test/gnupg_test_home/gpg.conf
new file mode 100644
index 0000000..b590914
--- /dev/null
+++ b/test/gnupg_test_home/gpg.conf
@@ -0,0 +1 @@
+default-key 789E7011
diff --git a/test/gnupg_test_home/pubring.gpg b/test/gnupg_test_home/pubring.gpg
new file mode 100644
index 0000000..3f604d7
Binary files /dev/null and b/test/gnupg_test_home/pubring.gpg differ
diff --git a/test/gnupg_test_home/receiver_pubring.gpg b/test/gnupg_test_home/receiver_pubring.gpg
new file mode 100644
index 0000000..84cbdc3
Binary files /dev/null and b/test/gnupg_test_home/receiver_pubring.gpg differ
diff --git a/test/gnupg_test_home/receiver_secring.gpg b/test/gnupg_test_home/receiver_secring.gpg
new file mode 100644
index 0000000..d92f0b8
Binary files /dev/null and b/test/gnupg_test_home/receiver_secring.gpg differ
diff --git a/test/gnupg_test_home/receiver_trustdb.gpg b/test/gnupg_test_home/receiver_trustdb.gpg
new file mode 100644
index 0000000..937117c
Binary files /dev/null and b/test/gnupg_test_home/receiver_trustdb.gpg differ
diff --git a/test/gnupg_test_home/secring.gpg b/test/gnupg_test_home/secring.gpg
new file mode 100644
index 0000000..513777c
Binary files /dev/null and b/test/gnupg_test_home/secring.gpg differ
diff --git a/test/gnupg_test_home/sup-test-2 at foo.bar.asc b/test/gnupg_test_home/sup-test-2 at foo.bar.asc
new file mode 100644
index 0000000..26e24d5
--- /dev/null
+++ b/test/gnupg_test_home/sup-test-2 at foo.bar.asc
@@ -0,0 +1,20 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v2.0.20 (GNU/Linux)
+
+mI0EUgi0fAEEAOLAcQW96NEUSB7YE/la8X56jGW5BMX3aAixOF8LvOwMBbUK1T+U
+0H2PGIrXVcYyHcPqWRpRahbsIAldBqzffPlzMa+aqJaB1xKkNruxSoIzwPdidZMe
+l0Dxz2FDsoXD0KPyWnAYhGmQyz2MFpZxu2tlYqvwWVW//XGnk/KHvIXbABEBAAG0
+PlN1cCBUZXN0IFJlY2VpdmVyIChUZXN0IHJlY2VpdmVyIGZvciBTdXApIDxzdXAt
+dGVzdC0yQGZvby5iYXI+iL8EEwECACkFAlIItHwCGwMFCQHhM4AHCwkIBwMCAQYV
+CAIJCgsEFgIDAQIeAQIXgAAKCRAsABl+cWpykMMVBADHkQPgTz0CqKKp3k+z3dbm
+ocmI4tYNn1dOkDQqyfoBTfs6L3g4j5OE2UrguntRYyg5oon+uO5d18CQ5dY0sCw/
+o5IwyzTrxI8IocbtZvBdSb+XjLndynGuIQoqaJq9i6n1V4klFHVOna8Q9JstLfRX
+H1d4xPhnvKcaDDx/NV3X/biNBFIItHwBBADBpb43MpkrUWlg7HWJ1ZfOlxnOxrJ3
+Gz9WFNV06UbcZEuFKA/vHRjM6gWzUn5903FLuCWu3eBrq5xQfWipbp187PmocpoG
+skJ6gosLs1fMYRBjv2VbG9xJVKdKJMjqZw5FUpXKAaHr8P9jN6g2STQrbeQ8CVUK
+h7zOWRXAXSKUgwARAQABiKUEGAECAA8FAlIItHwCGwwFCQHhM4AACgkQLAAZfnFq
+cpDV1QQAzcxFXznEX92DjWxWRC7gRHgIsQk9WJnDzjtnDjSWCp3H85qeTZGZrn9W
+NoneV/S5Y7K3Mkceh4rFaANQ3zx4b05y1LFt5N/lPwIe5VB0vcPumtZum2fSGfpK
+nTXvzelcWcm2aGyUSaWvOkntWKEEt1kB5Oq6EtZoRZLMzAxLd7s=
+=aKsV
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/test/gnupg_test_home/trustdb.gpg b/test/gnupg_test_home/trustdb.gpg
new file mode 100644
index 0000000..8666c17
Binary files /dev/null and b/test/gnupg_test_home/trustdb.gpg differ
diff --git a/test/integration/test_label_service.rb b/test/integration/test_label_service.rb
new file mode 100644
index 0000000..a05b42d
--- /dev/null
+++ b/test/integration/test_label_service.rb
@@ -0,0 +1,18 @@
+require "test_helper"
+
+require "sup/service/label_service"
+
+require "tmpdir"
+
+describe Redwood::LabelService do
+ let(:tmpdir) { Dir.mktmpdir }
+ after do
+ require "fileutils"
+ FileUtils.remove_entry_secure @tmpdir unless @tmpdir.nil?
+ end
+
+ describe "#add_labels" do
+ # Integration tests are hard to write at this moment :(
+ it "add labels to all messages matching the query"
+ end
+end
diff --git a/test/messages/bad-content-transfer-encoding-1.eml b/test/messages/bad-content-transfer-encoding-1.eml
new file mode 100644
index 0000000..16cf671
--- /dev/null
+++ b/test/messages/bad-content-transfer-encoding-1.eml
@@ -0,0 +1,8 @@
+From: foo at example.org
+MIME-Version: 1.0
+Subject: Content-Transfer-Encoding:-bug in sup
+Content-Type: message/rfc822
+Content-Transfer-Encoding: nosuchcontenttransferencoding
+
+foo
+
diff --git a/test/messages/binary-content-transfer-encoding-2.eml b/test/messages/binary-content-transfer-encoding-2.eml
new file mode 100644
index 0000000..142f8d8
--- /dev/null
+++ b/test/messages/binary-content-transfer-encoding-2.eml
@@ -0,0 +1,21 @@
+From: foo at example.org
+MIME-Version: 1.0
+Content-type: multipart/report; boundary="======11647==82899======"; report-type="spam-notification"
+Subject: Important
+
+This is a multi-part message in MIME format...
+
+--======11647==82899======
+Content-Type: text/plain; charset="ISO-8859-1"
+Content-Disposition: inline
+Content-Transfer-Encoding: quoted-printable
+
+
+--======11647==82899======
+Content-Type: message/rfc822
+Content-Disposition: attachment
+Content-Transfer-Encoding: binary
+
+
+--======11647==82899======--
+
diff --git a/test/messages/missing-line.eml b/test/messages/missing-line.eml
new file mode 100644
index 0000000..adc2ea8
--- /dev/null
+++ b/test/messages/missing-line.eml
@@ -0,0 +1,9 @@
+From: foo at aol.com
+To: foo at test.com
+Subject: Encoding bug
+Content-Type: text/plain; charset="iso-8859-1"
+Content-Transfer-Encoding: quoted-printable
+
+This is =91 a test: the first line seems to disappear from the mail body but is
+still visible in the thread view.
+
diff --git a/test/test_crypto.rb b/test/test_crypto.rb
new file mode 100644
index 0000000..ae5b6eb
--- /dev/null
+++ b/test/test_crypto.rb
@@ -0,0 +1,109 @@
+# tests for sup's crypto libs
+#
+# Copyright Clint Byrum <clint at ubuntu.com> 2011. All Rights Reserved.
+# Copyright Sup Developers 2013.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+require 'test_helper'
+require 'sup'
+require 'stringio'
+require 'tmpdir'
+
+module Redwood
+
+class TestCryptoManager < ::Minitest::Unit::TestCase
+
+ def setup
+ @from_email = 'sup-test-1 at foo.bar'
+ @to_email = 'sup-test-2 at foo.bar'
+ # Use test gnupg setup
+ @orig_gnupghome = ENV['GNUPGHOME']
+ ENV['GNUPGHOME'] = File.join(File.dirname(__FILE__), 'gnupg_test_home')
+
+ @path = Dir.mktmpdir
+ Redwood::HookManager.init File.join(@path, 'hooks')
+
+ am = {:default=> {:name => "test", :email=> 'sup-test-1 at foo.bar'}}
+ Redwood::AccountManager.init am
+
+ Redwood::CryptoManager.init
+
+ if not CryptoManager.have_crypto?
+ warn "No crypto set up, crypto will not be tested. Reason: #{CryptoManager.not_working_reason}"
+ end
+ end
+
+ def teardown
+ CryptoManager.deinstantiate!
+ AccountManager.deinstantiate!
+ HookManager.deinstantiate!
+ FileUtils.rm_r @path
+
+ ENV['GNUPGHOME'] = @orig_gnupghome
+ end
+
+ def test_sign
+ if CryptoManager.have_crypto? then
+ signed = CryptoManager.sign @from_email, at to_email,"ABCDEFG"
+ assert_instance_of RMail::Message, signed
+ assert_equal "ABCDEFG", signed.body[0]
+ assert signed.body[1].body.length > 0 , "signature length must be > 0"
+ assert (signed.body[1].body.include? "-----BEGIN PGP SIGNATURE-----") , "Expecting PGP armored data"
+ end
+ end
+
+ def test_encrypt
+ if CryptoManager.have_crypto? then
+ encrypted = CryptoManager.encrypt @from_email, [@to_email], "ABCDEFG"
+ assert_instance_of RMail::Message, encrypted
+ assert (encrypted.body[1].body.include? "-----BEGIN PGP MESSAGE-----") , "Expecting PGP armored data"
+ end
+ end
+
+ def test_sign_and_encrypt
+ if CryptoManager.have_crypto? then
+ encrypted = CryptoManager.sign_and_encrypt @from_email, [@to_email], "ABCDEFG"
+ assert_instance_of RMail::Message, encrypted
+ assert (encrypted.body[1].body.include? "-----BEGIN PGP MESSAGE-----") , "Expecting PGP armored data"
+ end
+ end
+
+ def test_decrypt
+ if CryptoManager.have_crypto? then
+ encrypted = CryptoManager.encrypt @from_email, [@to_email], "ABCDEFG"
+ assert_instance_of RMail::Message, encrypted
+ assert_instance_of String, (encrypted.body[1].body)
+ decrypted = CryptoManager.decrypt encrypted.body[1], true
+ assert_instance_of Array, decrypted
+ assert_instance_of Chunk::CryptoNotice, decrypted[0]
+ assert_instance_of Chunk::CryptoNotice, decrypted[1]
+ assert_instance_of RMail::Message, decrypted[2]
+ assert_equal "ABCDEFG" , decrypted[2].body
+ end
+ end
+
+ def test_verify
+ if CryptoManager.have_crypto?
+ signed = CryptoManager.sign @from_email, @to_email, "ABCDEFG"
+ assert_instance_of RMail::Message, signed
+ assert_instance_of String, (signed.body[1].body)
+ CryptoManager.verify signed.body[0], signed.body[1], true
+ end
+ end
+end
+
+end
diff --git a/test/test_header_parsing.rb b/test/test_header_parsing.rb
index 1929e07..a2aa2a2 100644
--- a/test/test_header_parsing.rb
+++ b/test/test_header_parsing.rb
@@ -1,16 +1,20 @@
#!/usr/bin/ruby
-require 'test/unit'
+require 'test_helper'
require 'sup'
require 'stringio'
include Redwood
-class TestMBoxParsing < Test::Unit::TestCase
+class TestMBoxParsing < Minitest::Unit::TestCase
+
def setup
+ @path = Dir.mktmpdir
+ @mbox = File.join(@path, 'test_mbox')
end
def teardown
+ FileUtils.rm_r @path
end
def test_normal_headers
@@ -106,7 +110,7 @@ EOS
end
def test_from_line_splitting
- l = MBox.new StringIO.new(<<EOS)
+ l = MBox.new mbox_for_string(<<EOS)
From sup-talk-bounces at rubyforge.org Mon Apr 27 12:56:18 2009
From: Bob <bob at bob.com>
To: a dear friend
@@ -125,14 +129,14 @@ From bob at bob.com
This is the end of the email.
EOS
- offset, labels = l.next
- assert_equal 0, offset
- offset, labels = l.next
+ offset = l.next_offset 0
+ assert_equal 61, offset
+ offset = l.next_offset 61
assert_nil offset
end
def test_more_from_line_splitting
- l = MBox.new StringIO.new(<<EOS)
+ l = MBox.new mbox_for_string(<<EOS)
From sup-talk-bounces at rubyforge.org Mon Apr 27 12:56:18 2009
From: Bob <bob at bob.com>
To: a dear friend
@@ -145,13 +149,20 @@ To: a dear friend
Hello again! Would you like to buy my products?
EOS
- offset, labels = l.next
- assert_not_nil offset
+ offset = l.next_offset 0
+ refute_nil offset
- offset, labels = l.next
- assert_not_nil offset
+ offset = l.next_offset offset
+ refute_nil offset
- offset, labels = l.next
+ offset = l.next_offset offset
assert_nil offset
end
+
+ def mbox_for_string content
+ File.open(@mbox, 'w') do |f|
+ f.write content
+ end
+ "mbox://#{@mbox}"
+ end
end
diff --git a/test/test_helper.rb b/test/test_helper.rb
new file mode 100644
index 0000000..adb09b7
--- /dev/null
+++ b/test/test_helper.rb
@@ -0,0 +1,7 @@
+require "rubygems" rescue nil
+require 'minitest/autorun'
+require "rr"
+
+class Minitest::Unit::TestCase
+ include ::RR::Adapters::MiniTest
+end
diff --git a/test/test_message.rb b/test/test_message.rb
index 94b962a..6283174 100644
--- a/test/test_message.rb
+++ b/test/test_message.rb
@@ -1,6 +1,6 @@
#!/usr/bin/ruby
-require 'test/unit'
+require 'test_helper'
require 'sup'
require 'stringio'
@@ -26,12 +26,16 @@ end
module Redwood
-class TestMessage < Test::Unit::TestCase
+class TestMessage < ::Minitest::Unit::TestCase
def setup
+ @path = Dir.mktmpdir
+ Redwood::HookManager.init File.join(@path, 'hooks')
end
def teardown
+ Redwood::HookManager.deinstantiate!
+ FileUtils.rm_r @path
end
def test_simple_message
@@ -72,7 +76,7 @@ EOS
source.messages = [ message ]
source_info = 0
- sup_message = Message.new( {:source => source, :source_info => source_info } )
+ sup_message = Message.build_from_source(source, source_info)
sup_message.load_from_source!
# see how well parsing the header went
@@ -222,7 +226,7 @@ EOS
source.messages = [ message ]
source_info = 0
- sup_message = Message.new( {:source => source, :source_info => source_info } )
+ sup_message = Message.build_from_source(source, source_info)
sup_message.load_from_source!
# read the message body chunks
@@ -272,7 +276,7 @@ EOS
source.messages = [ message ]
source_info = 0
- sup_message = Message.new( {:source => source, :source_info => source_info } )
+ sup_message = Message.build_from_source(source, source_info)
sup_message.load_from_source!
to = sup_message.to
@@ -285,7 +289,7 @@ EOS
from = sup_message.from
# very basic email address check
assert_match(/\w+@\w+\.\w{2,4}/, from.email)
- assert_not_nil(from.name)
+ refute_nil(from.name)
end
@@ -318,16 +322,12 @@ EOS
source.messages = [ message ]
source_info = 0
- sup_message = Message.new( {:source => source, :source_info => source_info } )
+ sup_message = Message.build_from_source(source, source_info)
sup_message.load_from_source!
# read the message body chunks: no errors should reach this level
- chunks = nil
-
- assert_nothing_raised() do
- chunks = sup_message.load_from_source!
- end
+ chunks = sup_message.load_from_source!
# the chunks list should be empty
@@ -417,15 +417,12 @@ EOS
source.messages = [ message ]
source_info = 0
- sup_message = Message.new( {:source => source, :source_info => source_info } )
+ sup_message = Message.build_from_source(source, source_info)
sup_message.load_from_source!
# read the message body chunks
- assert_nothing_raised() do
- chunks = sup_message.load_from_source!
- end
-
+ sup_message.load_from_source!
end
def test_blank_header_lines
@@ -508,7 +505,7 @@ EOS
source.messages = [ message ]
source_info = 0
- sup_message = Message.new( {:source => source, :source_info => source_info } )
+ sup_message = Message.build_from_source(source, source_info)
sup_message.load_from_source!
# See how well parsing the message ID went.
@@ -517,7 +514,7 @@ EOS
# Look at another header field whose first line was blank.
list_unsubscribe = sup_message.list_unsubscribe
- assert_equal("<http://mailman2.widget.com/mailman/listinfo/monitor-list>, " +
+ assert_equal("<http://mailman2.widget.com/mailman/listinfo/monitor-list>,\n \t" +
"<mailto:monitor-list-request at widget.com?subject=unsubscribe>",
list_unsubscribe)
@@ -533,4 +530,3 @@ end
end
# vim:noai:ts=2:sw=2:
-
diff --git a/test/test_messages_dir.rb b/test/test_messages_dir.rb
new file mode 100644
index 0000000..6341559
--- /dev/null
+++ b/test/test_messages_dir.rb
@@ -0,0 +1,147 @@
+#!/usr/bin/ruby
+
+require 'test_helper'
+require 'sup'
+require 'stringio'
+
+require 'dummy_source'
+
+# override File.exists? to make it work with StringIO for testing.
+# FIXME: do aliasing to avoid breaking this when sup moves from
+# File.exists? to File.exist?
+
+class File
+
+ def File.exists? file
+ # puts "fake File::exists?"
+
+ if file.is_a?(StringIO)
+ return false
+ end
+ # use the different function
+ File.exist?(file)
+ end
+
+end
+
+module Redwood
+
+class TestMessagesDir < ::Minitest::Unit::TestCase
+
+ def setup
+ @path = Dir.mktmpdir
+ Redwood::HookManager.init File.join(@path, 'hooks')
+ end
+
+ def teardown
+ Redwood::HookManager.deinstantiate!
+ FileUtils.rm_r @path
+ end
+
+ def test_binary_content_transfer_encoding
+ message = ''
+ File.open 'test/messages/binary-content-transfer-encoding-2.eml' do |f|
+ message = f.read
+ end
+
+ source = DummySource.new("sup-test://test_messages")
+ source.messages = [ message ]
+ source_info = 0
+
+ sup_message = Message.build_from_source(source, source_info)
+ sup_message.load_from_source!
+
+ from = sup_message.from
+ # "from" is just a simple person item
+
+ assert_equal("foo at example.org", from.email)
+ #assert_equal("Fake Sender", from.name)
+
+ subj = sup_message.subj
+ assert_equal("Important", subj)
+
+ chunks = sup_message.load_from_source!
+ indexable_chunks = sup_message.indexable_chunks
+
+ # there should be only one chunk
+ #assert_equal(1, chunks.length)
+
+ lines = chunks[0].lines
+
+ # lines should contain an error message
+ assert (lines.join.include? "An error occurred while loading this message."), "This message should not load successfully"
+ end
+
+ def test_bad_content_transfer_encoding
+ message = ''
+ File.open 'test/messages/bad-content-transfer-encoding-1.eml' do |f|
+ message = f.read
+ end
+
+ source = DummySource.new("sup-test://test_messages")
+ source.messages = [ message ]
+ source_info = 0
+
+ sup_message = Message.build_from_source(source, source_info)
+ sup_message.load_from_source!
+
+ from = sup_message.from
+ # "from" is just a simple person item
+
+ assert_equal("foo at example.org", from.email)
+ #assert_equal("Fake Sender", from.name)
+
+ subj = sup_message.subj
+ assert_equal("Content-Transfer-Encoding:-bug in sup", subj)
+
+ chunks = sup_message.load_from_source!
+ indexable_chunks = sup_message.indexable_chunks
+
+ # there should be only one chunk
+ #assert_equal(1, chunks.length)
+
+ lines = chunks[0].lines
+
+ # lines should contain an error message
+ assert (lines.join.include? "An error occurred while loading this message."), "This message should not load successfully"
+ end
+
+ def test_missing_line
+ message = ''
+ File.open 'test/messages/missing-line.eml' do |f|
+ message = f.read
+ end
+
+ source = DummySource.new("sup-test://test_messages")
+ source.messages = [ message ]
+ source_info = 0
+
+ sup_message = Message.build_from_source(source, source_info)
+ sup_message.load_from_source!
+
+ from = sup_message.from
+ # "from" is just a simple person item
+
+ assert_equal("foo at aol.com", from.email)
+ #assert_equal("Fake Sender", from.name)
+
+ subj = sup_message.subj
+ assert_equal("Encoding bug", subj)
+
+ chunks = sup_message.load_from_source!
+ indexable_chunks = sup_message.indexable_chunks
+
+ # there should be only one chunk
+ #assert_equal(1, chunks.length)
+
+ lines = chunks[0].lines
+
+ badline = lines[0]
+ assert (badline.display_length > 0), "The length of this line should greater than 0: #{badline}"
+
+ end
+end
+
+end
+
+# vim:noai:ts=2:sw=2:
diff --git a/test/test_server.rb b/test/test_server.rb
deleted file mode 100644
index a49d309..0000000
--- a/test/test_server.rb
+++ /dev/null
@@ -1,106 +0,0 @@
-#!/usr/bin/ruby
-# encoding: utf-8
-
-require 'test/unit'
-require 'iconv'
-require 'stringio'
-require 'tmpdir'
-require 'fileutils'
-require 'thread'
-require 'eventmachine'
-require 'sup'
-require 'sup/server'
-
-Thread.abort_on_exception = true
-
-module EM
- # Run the reactor in a new thread. This is useful for using EventMachine
- # alongside synchronous code. It is recommended to use EM.error_handler to
- # detect when an exception terminates the reactor thread.
- def self.spawn_reactor_thread
- fail "reactor already started" if EM.reactor_running?
- q = ::Queue.new
- Thread.new { EM.run { q << nil } }
- q.pop
- end
-
- # Stop the reactor and wait for it to finish. This is the counterpart to #spawn_reactor_thread.
- def self.kill_reactor_thread
- fail "reactor is not running" unless EM.reactor_running?
- fail "current thread is running the reactor" if EM.reactor_thread?
- EM.stop
- EM.reactor_thread.join
- end
-end
-
-class QueueingClient < EM::P::RedwoodClient
- def initialize
- super
- @q = Queue.new
- @readyq = Queue.new
- end
-
- def receive_message type, tag, params
- @q << [type, tag, params]
- end
-
- def connection_established
- @readyq << nil
- end
-
- def wait_until_ready
- @readyq.pop
- end
-
- def read
- @q.pop
- end
-
- alias write send_message
-end
-
-class TestServer < Test::Unit::TestCase
- def setup
- port = rand(1000) + 30000
- EM.spawn_reactor_thread
- @path = Dir.mktmpdir
- socket_path = File.join(@path, 'socket')
- Redwood::HookManager.init File.join(@path, 'hooks')
- Redwood::SourceManager.init
- Redwood::SourceManager.load_sources File.join(@path, 'sources.yaml')
- Redwood::Index.init @path
- Redwood::SearchManager.init File.join(@path, 'searches')
- Redwood::Index.load
- @server = EM.start_server socket_path,
- Redwood::Server, Redwood::Index.instance
- @client = EM.connect socket_path, QueueingClient
- @client.wait_until_ready
- end
-
- def teardown
- FileUtils.rm_r @path if passed?
- puts "not cleaning up #{@path}" unless passed?
- %w(Index SearchManager SourceManager HookManager).each do |x|
- Redwood.const_get(x.to_sym).deinstantiate!
- end
- EM.kill_reactor_thread
- end
-
- def test_invalid_request
- @client.write 'foo', '1'
- check @client.read, 'error', '1'
- end
-
- def test_query
- @client.write 'query', '1', 'query' => 'type:mail'
- check @client.read, 'done', '1'
- end
-
- def check resp, type, tag, args={}
- assert_equal type.to_s, resp[0]
- assert_equal tag.to_s, resp[1]
- args.each do |k,v|
- assert_equal v, resp[2][k.to_s]
- end
- end
-end
diff --git a/test/test_yaml_migration.rb b/test/test_yaml_migration.rb
new file mode 100644
index 0000000..3e01cc0
--- /dev/null
+++ b/test/test_yaml_migration.rb
@@ -0,0 +1,85 @@
+require "test_helper"
+
+require "sup"
+require "psych"
+
+if RUBY_VERSION < "2.1"
+describe "Sup's YAML util" do
+ describe "Module#yaml_properties" do
+ def build_class_with_name name, &b
+ Class.new do
+ meta_cls = class << self; self; end
+ meta_cls.send(:define_method, :name) { name }
+ class_exec(&b) unless b.nil?
+ end
+ end
+
+ after do
+ Psych.load_tags = {}
+ Psych.dump_tags = {}
+ end
+
+ it "defines YAML tag for class" do
+ cls = build_class_with_name 'Cls' do
+ yaml_properties
+ end
+
+ expected_yaml_tag = "!supmua.org,2006-10-01/Cls"
+
+ Psych.load_tags[expected_yaml_tag].must_equal cls
+ Psych.dump_tags[cls].must_equal expected_yaml_tag
+
+ end
+
+ it "Loads legacy YAML format as well" do
+ cls = build_class_with_name 'Cls' do
+ yaml_properties :id
+ attr_accessor :id
+ def initialize id
+ @id = id
+ end
+ end
+
+ Psych.load_tags["!masanjin.net,2006-10-01/Cls"].must_equal cls
+
+ yaml = <<EOF
+--- !masanjin.net,2006-10-01/Cls
+id: ID
+EOF
+ loaded = YAML.load(yaml)
+
+ loaded.id.must_equal 'ID'
+ loaded.must_be_kind_of cls
+ end
+
+ it "Dumps & loads w/ state re-initialized" do
+ cls = build_class_with_name 'Cls' do
+ yaml_properties :id
+ attr_accessor :id
+ attr_reader :flag
+
+ def initialize id
+ @id = id
+ @flag = true
+ end
+ end
+
+ instance = cls.new 'ID'
+
+ dumped = YAML.dump(instance)
+ loaded = YAML.load(dumped)
+
+ dumped.must_equal <<-EOF
+--- !supmua.org,2006-10-01/Cls
+id: ID
+ EOF
+
+ loaded.id.must_equal 'ID'
+ assert loaded.flag
+ end
+ end
+end
+
+else
+ puts "Some YAML tests are skipped on Ruby 2.1.0 and newer."
+end
diff --git a/test/test_yaml_regressions.rb b/test/test_yaml_regressions.rb
new file mode 100644
index 0000000..1ab978e
--- /dev/null
+++ b/test/test_yaml_regressions.rb
@@ -0,0 +1,17 @@
+require 'test_helper'
+
+# Requiring 'yaml' before 'sup' in 1.9.x would get Psych loaded first
+# and becoming the default yamler.
+require 'yaml'
+require 'sup'
+
+module Redwood
+ class TestYamlRegressions < ::Minitest::Unit::TestCase
+ def test_yamling_hash
+ hsh = {:foo => 42}
+ reloaded = YAML.load(hsh.to_yaml)
+
+ assert_equal reloaded, hsh
+ end
+ end
+end
diff --git a/test/unit/service/test_label_service.rb b/test/unit/service/test_label_service.rb
new file mode 100644
index 0000000..7c46e34
--- /dev/null
+++ b/test/unit/service/test_label_service.rb
@@ -0,0 +1,19 @@
+require "test_helper"
+
+require "sup/service/label_service"
+
+describe Redwood::LabelService do
+ describe "#add_labels" do
+ it "add labels to all messages matching the query" do
+ q = 'is:starred'
+ label = 'superstarred'
+ message = mock!.add_label(label).subject
+ index = mock!.find_messages(q){ [message] }.subject
+ mock(index).update_message_state(message)
+ mock(index).save_index
+
+ service = Redwood::LabelService.new(index)
+ service.add_labels q, label
+ end
+ end
+end
diff --git a/test/unit/test_horizontal_selector.rb b/test/unit/test_horizontal_selector.rb
new file mode 100644
index 0000000..6cc252c
--- /dev/null
+++ b/test/unit/test_horizontal_selector.rb
@@ -0,0 +1,40 @@
+require "test_helper"
+
+require "sup/horizontal_selector"
+
+describe Redwood::HorizontalSelector do
+ let(:values) { %w[foo at example.com bar at example.com] }
+ let(:strange_value) { "strange at example.com" }
+
+ before do
+ @selector = Redwood::HorizontalSelector.new(
+ 'Acc:', values, [])
+ end
+
+ it "init w/ the first value selected" do
+ first_value = values.first
+ @selector.val.must_equal first_value
+ end
+
+ it "stores value for selection" do
+ second_value = values[1]
+ @selector.set_to second_value
+ @selector.val.must_equal second_value
+ end
+
+ describe "for unknown value" do
+ it "cannot select unknown value" do
+ @selector.wont_be :can_set_to?, strange_value
+ end
+
+ it "refuses selecting unknown value" do
+ old_value = @selector.val
+
+ assert_raises Redwood::HorizontalSelector::UnknownValue do
+ @selector.set_to strange_value
+ end
+
+ @selector.val.must_equal old_value
+ end
+ end
+end
diff --git a/test/unit/util/test_query.rb b/test/unit/util/test_query.rb
new file mode 100644
index 0000000..b31a6be
--- /dev/null
+++ b/test/unit/util/test_query.rb
@@ -0,0 +1,46 @@
+# encoding: utf-8
+
+require "test_helper"
+
+require "sup/util/query"
+require "xapian"
+
+describe Redwood::Util::Query do
+ describe ".describe" do
+ it "returns a UTF-8 description of query" do
+ query = Xapian::Query.new "テスト"
+ life = "生活: "
+
+ assert_raises Encoding::CompatibilityError do
+ _ = life + query.description
+ end
+
+ desc = Redwood::Util::Query.describe(query)
+ _ = (life + desc) # No exception thrown
+ end
+
+ it "returns a valid UTF-8 description of bad input" do
+ msg = "asdfa \xc3\x28 åasdf"
+ query = Xapian::Query.new msg
+ life = 'hæi'
+
+ # this is now possibly UTF-8 string with possibly invalid chars
+ assert_raises Redwood::Util::Query::QueryDescriptionError do
+ desc = Redwood::Util::Query.describe (query)
+ end
+
+ assert_raises Encoding::CompatibilityError do
+ _ = life + query.description
+ end
+ end
+
+ it "returns a valid UTF-8 fallback description of bad input" do
+ msg = "asdfa \xc3\x28 åasdf"
+ query = Xapian::Query.new msg
+
+ desc = Redwood::Util::Query.describe(query, "invalid query")
+
+ assert_equal("invalid query", desc)
+ end
+ end
+end
diff --git a/test/unit/util/test_string.rb b/test/unit/util/test_string.rb
new file mode 100644
index 0000000..0d10d08
--- /dev/null
+++ b/test/unit/util/test_string.rb
@@ -0,0 +1,57 @@
+# encoding: utf-8
+
+require "test_helper"
+
+require "sup/util"
+
+describe "Sup's String extension" do
+ describe "#display_length" do
+ let :data do
+ [
+ ['some words', 10,],
+ ['中文', 4,],
+ ['ä', 1,],
+ ]
+ end
+
+ it "calculates display length of a string" do
+ data.each do |(str, length)|
+ str.display_length.must_equal length
+ end
+ end
+ end
+
+ describe "#slice_by_display_length(len)" do
+ let :data do
+ [
+ ['some words', 6, 'some w'],
+ ['中文', 2, '中'],
+ ['älpha', 3, 'älp'],
+ ]
+ end
+
+ it "slices string by display length" do
+ data.each do |(str, length, sliced)|
+ str.slice_by_display_length(length).must_equal sliced
+ end
+ end
+ end
+
+ describe "#wrap" do
+ let :data do
+ [
+ ['some words', 6, ['some', 'words']],
+ ['some words', 80, ['some words']],
+ ['中文', 2, ['中', '文']],
+ ['中文', 5, ['中文']],
+ ['älpha', 3, ['älp', 'ha']],
+ ]
+ end
+
+ it "wraps string by display length" do
+ data.each do |(str, length, wrapped)|
+ str.wrap(length).must_equal wrapped
+ end
+ end
+ end
+end
diff --git a/test/unit/util/test_uri.rb b/test/unit/util/test_uri.rb
new file mode 100644
index 0000000..137d5af
--- /dev/null
+++ b/test/unit/util/test_uri.rb
@@ -0,0 +1,19 @@
+require "test_helper.rb"
+
+require "sup/util/uri"
+
+describe Redwood::Util::Uri do
+ describe ".build" do
+ it "builds uri from hash" do
+ components = {:path => "/var/mail/foo", :scheme => "mbox"}
+ uri = Redwood::Util::Uri.build(components)
+ uri.to_s.must_equal "mbox:/var/mail/foo"
+ end
+
+ it "expands ~ in path" do
+ components = {:path => "~/foo", :scheme => "maildir"}
+ uri = Redwood::Util::Uri.build(components)
+ uri.to_s.must_equal "maildir:#{ENV["HOME"]}/foo"
+ end
+ end
+end
diff --git a/www/index.html b/www/index.html
deleted file mode 100644
index 19ebb0a..0000000
--- a/www/index.html
+++ /dev/null
@@ -1,224 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
- <head>
- <title>Sup</title>
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
- <link rel="stylesheet" href="main.css" type="text/css" />
- </head>
-
- <body>
- <h1>Sup</h1>
-
- <blockquote>
- “Finally a mail client that does what we want, how we want it.”
- </blockquote>
-
- <blockquote>
- “Every other client we've tried is intolerable.”
- </blockquote>
-
- <blockquote>
- “It's just what I wanted, but I hadn't realized what I wanted until I saw it.”
- </blockquote>
-
- <blockquote>
- “Sup is almost to the point where I could jump ship from mutt.”
- </blockquote>
-
- <blockquote>
- “I was previously intrigued by a gmail-styled
- mutt-killer written in Ruby, but having actually spent a few
- hours reading the docs, and trying out all the keys, and
- reading the docs again, and trying the keys out again, and then
- actually engaging in a bunch of practice usage runs, I now
- officially can't fucking wait for the future of this thing; it
- has my full attention.”
- </blockquote>
-
- <p>
- Sup is a console-based email client for people with a lot of email.
- It supports tagging, very fast full-text search, automatic contact-
- list management, custom code insertion via a hook system, and more.
- If you're the type of person who treats email as an extension of your
- long-term memory, Sup is for you.
- </p>
-
- <p>
- Sup makes it easy to:
- </p>
-
- <ul>
- <li>
- Handle massive amounts of email.
- </li>
-
- <li>
- Mix email from different sources: mbox files and maildirs.
- For remote sources (IMAP, IMAPS, ssh+file), use another
- tool (offlineimap, fetchmail, rsync) to grab local copies.
- </li>
-
- <li>
- Instantaneously search over your entire email collection.
- Search over body text, or use a query language to combine
- search predicates in any way.
- </li>
-
- <li>
- Handle multiple accounts. Replying to email sent to a
- particular account will use the correct SMTP server, signature,
- and from address.
- </li>
-
- <li>
- Add custom code to handle certain types of messages or to
- handle certain types of text within messages.
- </li>
-
- <li>
- Organize email with user-defined labels, automatically track
- recent contacts, and much more!
- </li>
- </ul>
-
- <p>
- The goal of Sup is to become the email client of choice for nerds
- everywhere.
- </p>
-
- <h2>Screenshots</h2>
-
- <ul id="screenshots">
- <li><a href="ss1.png"><img src="ss1-small.png" alt="Sup screenshot 1" /></a></li>
- <li><a href="ss2.png"><img src="ss2-small.png" alt="Sup screenshot 2" /></a></li>
- <li><a href="ss3.png"><img src="ss3-small.png" alt="Sup screenshot 3" /></a></li>
- <li><a href="ss4.png"><img src="ss4-small.png" alt="Sup screenshot 4" /></a></li>
- <li><a href="ss5.png"><img src="ss5-small.png" alt="Sup screenshot 5" /></a></li>
- <li><a href="ss6.png"><img src="ss6-small.png" alt="Sup screenshot 6" /></a></li>
- </ul>
-
- <h2>Documentation</h2>
-
- <p>
- Please read the <a href="README.txt">README</a>, the <a
- href="FAQ.txt">FAQ</a>, the <a href="NewUserGuide.txt">new user guide</a>
- and the <a href="Philosophy.txt">philosophical statement</a>.
- </p>
-
- <p> Please also read and contribute to the <a href="http://sup.rubyforge.org/wiki/wiki.pl">Sup wiki</a>. </p>
-
- <h2>Status</h2>
-
- <p>
- The current version of Sup is 0.12.1, released 2011-01-23. This is a
- beta release. It supports mbox and Maildir mailstores.
- </p>
-
- <p>To be notified by email of Sup releases, subscribe to the
- <a href="http://rubyforge.org/mailman/listinfo/sup-announce">sup-announce mailing list</a>. One email per release.
- </p>
-
- <!-- <p>Issue and release status is available on the <a href="ditz/">Sup ditz page</a>.</p> -->
- <p>Sup news can often be see on <a href="http://all-thing.net/label/sup/">William's blog</a>.</p>
-
- <h2>Bug reports</h2>
- <p>Find a problem with Sup? Or have a feature you'd like to see? <a href="http://masanjin.net/sup-bugs/issue?@template=item">Submit
- it</a> to the <a href="http://masanjin.net/sup-bugs/">official Sup issue
- tracker</a>.
-
- <h2>Getting it</h2>
-
- <p>
- You can download Sup releases from the <a
- href="http://rubyforge.org/projects/sup/">Sup RubyForge page</a>.
- If you have RubyGems installed, simply command your computer to "gem
- install sup".
- </p>
-
- <p>
- If you're interested in development, you can clone the git repository like so: <code>git clone git://gitorious.org/sup/mainline.git</code>. You can also browse the <a
- href="http://gitorious.org/projects/sup">Sup Gitorious
- repository</a>. You may consider subscribing to the sup-devel list (below).
- </p>
-
- <h2>Make some new friends</h2>
-
- <p> If you'd like to meet hot single Sup users in your area, try the <a
- href="http://rubyforge.org/mailman/listinfo/sup-talk">sup-talk mailing list</a> (<a href="http://rubyforge.org/pipermail/sup-talk/">archives</a>).
- </p>
-
- <p> If you'd like to meet some grumpy old programmers to talk about Sup internals with, try the <a
- href="http://rubyforge.org/mailman/listinfo/sup-devel">sup-devel mailing list</a> (<a href="http://rubyforge.org/pipermail/sup-devel/">archives</a>).
-
-
- <h2>Credit</h2>
-
- <p>
- Sup is brought to you by <a href="http://masanjin.net/">William Morgan</a> and the following honorable contributors:
- <ul>
- <li> William Morgan </li>
- <li> Rich Lane </li>
- <li> Ismo Puustinen </li>
- <li> Nicolas Pouillard </li>
- <li> Eric Sherman </li>
- <li> Michael Stapelberg </li>
- <li> Ben Walton </li>
- <li> Mike Stipicevic </li>
- <li> Marcus Williams </li>
- <li> Lionel Ott </li>
- <li> Tero Tilus </li>
- <li> Ingmar Vanhassel </li>
- <li> Mark Alexander </li>
- <li> Gaute Hope </li>
- <li> Christopher Warrington </li>
- <li> W. Trevor King </li>
- <li> Gaudenz Steinlin </li>
- <li> Richard Brown </li>
- <li> Marc Hartstein </li>
- <li> Sascha Silbe </li>
- <li> Israel Herraiz </li>
- <li> Anthony Martinez </li>
- <li> Hamish Downer </li>
- <li> Bo Borgerson </li>
- <li> William Erik Baxter </li>
- <li> Michael Hamann </li>
- <li> Grant Hollingworth </li>
- <li> Adeodato Simó </li>
- <li> Daniel Schoepe </li>
- <li> Jason Petsod </li>
- <li> Steve Goldman </li>
- <li> Edward Z. Yang </li>
- <li> Decklin Foster </li>
- <li> Cameron Matheson </li>
- <li> Carl Worth </li>
- <li> Jeff Balogh </li>
- <li> Andrew Pimlott </li>
- <li> Alex Vandiver </li>
- <li> Peter Harkins </li>
- <li> Kornilios Kourtis </li>
- <li> Giorgio Lando </li>
- <li> Damien Leone </li>
- <li> Benoît PIERRE </li>
- <li> Alvaro Herrera </li>
- <li> Jonah </li>
- <li> Adam Lloyd </li>
- <li> Todd Eisenberger </li>
- <li> ian </li>
- <li> Steven Walter </li>
- <li> ian </li>
- <li> Jon M. Dugan </li>
- <li> Gregor Hoffleit </li>
- <li> Stefan Lundström </li>
- <li> Kirill Smelkov </li>
- </ul>
- </p>
-
- <p>
- Sup is made possible by the <a href="http://xapian.org">Xapian</a> search engine,
- and by <a href="http://www.lickey.com/">Matt Armstrong</a> and his
- tragically abandoned <a href="http://www.rfc20.org/rubymail/">RubyMail</a>
- package.
- </p>
- </body>
-</html>
diff --git a/www/main.css b/www/main.css
deleted file mode 100644
index 63246fa..0000000
--- a/www/main.css
+++ /dev/null
@@ -1,36 +0,0 @@
-body
-{
- background: white;
- color: #404040;
- margin-bottom: 2em;
- margin-left: auto;
- margin-right: auto;
- width: 90%;
-}
-
-a, a:visited
-{
- border-bottom: 1px dotted #03c;
- background: inherit;
- color: #03c;
- text-decoration: none;
-}
-
-#screenshots
-{
- margin: 0;
- padding: 0;
-}
-#screenshots li
-{
- display: inline;
- list-style: none;
-}
-#screenshots a
-{
- border-bottom: none;
-}
-#screenshots img
-{
- border: 0;
-}
diff --git a/www/ss1.png b/www/ss1.png
deleted file mode 100644
index a5c43d4..0000000
Binary files a/www/ss1.png and /dev/null differ
diff --git a/www/ss2.png b/www/ss2.png
deleted file mode 100644
index f9b6df7..0000000
Binary files a/www/ss2.png and /dev/null differ
diff --git a/www/ss3.png b/www/ss3.png
deleted file mode 100644
index 4038cb5..0000000
Binary files a/www/ss3.png and /dev/null differ
diff --git a/www/ss4.png b/www/ss4.png
deleted file mode 100644
index eb79f3e..0000000
Binary files a/www/ss4.png and /dev/null differ
diff --git a/www/ss5.png b/www/ss5.png
deleted file mode 100644
index aa367ae..0000000
Binary files a/www/ss5.png and /dev/null differ
diff --git a/www/ss6.png b/www/ss6.png
deleted file mode 100644
index 68ff5bf..0000000
Binary files a/www/ss6.png and /dev/null differ
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-ruby-extras/sup-mail.git
More information about the Pkg-ruby-extras-commits
mailing list