maildir 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.rdoc +5 -3
- data/Rakefile +2 -2
- data/VERSION +1 -1
- data/lib/maildir.rb +0 -1
- data/lib/maildir/keywords.rb +17 -10
- data/lib/maildir/subdirs.rb +5 -0
- data/maildir.gemspec +6 -2
- data/test/test_helper.rb +13 -3
- data/test/test_keywords.rb +16 -0
- data/test/test_maildir.rb +1 -1
- data/test/test_serializers.rb +1 -1
- data/test/test_subdirs.rb +46 -0
- metadata +6 -1
data/README.rdoc
CHANGED
@@ -6,7 +6,9 @@ A ruby library for reading and writing messages in the maildir format.
|
|
6
6
|
|
7
7
|
See http://cr.yp.to/proto/maildir.html and http://en.wikipedia.org/wiki/Maildir
|
8
8
|
|
9
|
-
|
9
|
+
"Two words: no locks." -- Daniel J. Berstein
|
10
|
+
|
11
|
+
The maildir format allows multiple processes to read and write arbitrary messages without file locks.
|
10
12
|
|
11
13
|
New messages are initially written to a "tmp" directory with an automatically-generated unique filename. After the message is written, it's moved to the "new" directory where other processes may read it.
|
12
14
|
|
@@ -104,9 +106,9 @@ It's trivial to create a custom serializer. Implement the following two methods:
|
|
104
106
|
load(path)
|
105
107
|
dump(data, path)
|
106
108
|
|
107
|
-
==
|
109
|
+
== Contributors
|
108
110
|
|
109
|
-
|
111
|
+
Niklas E. Cathor (http://github.com/nilclass) added subdir & courierimapkeywords support
|
110
112
|
|
111
113
|
== Copyright
|
112
114
|
|
data/Rakefile
CHANGED
@@ -15,7 +15,7 @@ begin
|
|
15
15
|
gemspec.description = "A ruby library for reading and writing arbitrary messages in DJB's maildir format"
|
16
16
|
gemspec.email = "aaron@ktheory.com"
|
17
17
|
gemspec.homepage = "http://github.com/ktheory/maildir"
|
18
|
-
gemspec.authors = ["Aaron Suggs"]
|
18
|
+
gemspec.authors = ["Aaron Suggs", "Niklas E. Cathor"]
|
19
19
|
gemspec.add_development_dependency "shoulda", ">= 0"
|
20
20
|
gemspec.add_development_dependency "mail", ">= 0"
|
21
21
|
gemspec.add_development_dependency "json", ">= 0"
|
@@ -29,4 +29,4 @@ end
|
|
29
29
|
desc "Run benchmarks"
|
30
30
|
task :bench do
|
31
31
|
load File.join(File.dirname(__FILE__), "benchmarks", "runner")
|
32
|
-
end
|
32
|
+
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.5.0
|
data/lib/maildir.rb
CHANGED
data/lib/maildir/keywords.rb
CHANGED
@@ -1,22 +1,27 @@
|
|
1
1
|
# implements IMAP Keywords as used by the Courier Mail Server
|
2
2
|
# see http://www.courier-mta.org/imap/README.imapkeywords.html for details
|
3
3
|
|
4
|
-
require 'ftools'
|
5
4
|
require 'maildir'
|
6
5
|
module Maildir::Keywords
|
7
6
|
def self.included(base)
|
8
7
|
Maildir::Message.send(:include, MessageExtension)
|
9
8
|
end
|
10
9
|
|
10
|
+
def keyword_dir
|
11
|
+
@keyword_dir ||= File.join(path, 'courierimapkeywords')
|
12
|
+
Dir.mkdir(@keyword_dir) unless File.directory?(@keyword_dir)
|
13
|
+
return @keyword_dir
|
14
|
+
end
|
15
|
+
|
11
16
|
# process contents of courierimapkeywords/ directory as described in README.imapkeywords
|
12
17
|
def read_keywords
|
13
18
|
messages = (list(:cur) + list(:new)).inject({}) { |m, msg| m[msg.unique_name] = msg ; m }
|
14
19
|
t = Time.now.to_i / 300
|
15
|
-
keyword_dir = File.join(path, 'courierimapkeywords')
|
16
20
|
keywords = []
|
17
21
|
state = :head
|
18
22
|
# process :list
|
19
|
-
File.
|
23
|
+
list_file = File.join(keyword_dir, ':list')
|
24
|
+
File.open(list_file).each_line do |line|
|
20
25
|
line.strip!
|
21
26
|
if state == :head
|
22
27
|
if line.empty?
|
@@ -30,7 +35,7 @@ module Maildir::Keywords
|
|
30
35
|
msg.set_keywords(ids.split(/\s/).map {|id| keywords[id.to_i - 1] })
|
31
36
|
end
|
32
37
|
end
|
33
|
-
end
|
38
|
+
end if File.exist?(list_file)
|
34
39
|
# collect keyword files
|
35
40
|
keyword_files = (Dir.entries(keyword_dir) - %w(. .. :list)).inject({}) do |keyword_files, file|
|
36
41
|
if file =~ /^\.(\d+)\.(.*)$/
|
@@ -39,7 +44,7 @@ module Maildir::Keywords
|
|
39
44
|
else
|
40
45
|
n = t + 1
|
41
46
|
key = file
|
42
|
-
|
47
|
+
FileUtils.mv(File.join(keyword_dir, file), File.join(keyword_dir, ".#{n}.#{key}"))
|
43
48
|
end
|
44
49
|
if msg = messages[key]
|
45
50
|
(keyword_files[key] ||= []) << [n, key]
|
@@ -49,13 +54,15 @@ module Maildir::Keywords
|
|
49
54
|
File.unlink(fname)
|
50
55
|
end
|
51
56
|
end
|
57
|
+
next(keyword_files)
|
52
58
|
end
|
53
59
|
# process keyword files
|
54
60
|
keyword_files.each_pair do |key, files|
|
55
61
|
files.sort! { |a, b| a[0] <=> b[0] }
|
56
|
-
files[0..-2].each { |f| File.unlink(File.join(keyword_dir, ".#{f.join('.')}")) } if
|
62
|
+
files[0..-2].each { |f| File.unlink(File.join(keyword_dir, ".#{f.join('.')}")) } if files.last[0] < t
|
57
63
|
msg = messages[key]
|
58
|
-
|
64
|
+
file = (File.exist?(File.join(keyword_dir, files.last[1])) ? files.last[1] : ".#{files.last.join('.')}")
|
65
|
+
current_keywords = File.read(File.join(keyword_dir, file)).split(/\s+/)
|
59
66
|
msg.set_keywords(current_keywords)
|
60
67
|
if (add = (current_keywords - keywords)).any?
|
61
68
|
keywords += add
|
@@ -72,7 +79,7 @@ module Maildir::Keywords
|
|
72
79
|
@keywords[key] = msg.keywords
|
73
80
|
end
|
74
81
|
}
|
75
|
-
|
82
|
+
FileUtils.mv(tmp_file, list_file)
|
76
83
|
end
|
77
84
|
|
78
85
|
def keywords(key)
|
@@ -88,9 +95,9 @@ module Maildir::Keywords
|
|
88
95
|
|
89
96
|
# sets given keywords on the message.
|
90
97
|
def keywords=(list)
|
91
|
-
tmp_fname = File.join(maildir.path, 'tmp', unique_name)
|
98
|
+
tmp_fname = File.join(@maildir.path, 'tmp', unique_name)
|
92
99
|
File.open(tmp_fname, 'w') { |f| f.write(list.join("\n")) }
|
93
|
-
|
100
|
+
FileUtils.mv(tmp_fname, File.join(@maildir.keyword_dir, unique_name))
|
94
101
|
end
|
95
102
|
|
96
103
|
# sets @keywords to the given list
|
data/lib/maildir/subdirs.rb
CHANGED
@@ -11,6 +11,10 @@ module Maildir::Subdirs
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
+
def name
|
15
|
+
root? ? ROOT_NAME : subdir_parts(path).last
|
16
|
+
end
|
17
|
+
|
14
18
|
def create_subdir(name)
|
15
19
|
raise ArgumentError.new("'name' may not contain delimiter character (#{DELIM})") if name.include?(DELIM)
|
16
20
|
full_name = (root? ? [] : subdir_parts(File.basename(path))).push(name).unshift('').join(DELIM)
|
@@ -54,6 +58,7 @@ module Maildir::Subdirs
|
|
54
58
|
private
|
55
59
|
|
56
60
|
def subdir_parts(path)
|
61
|
+
path.sub!(/\/$/, '') # remove trailing slash
|
57
62
|
parts = (path.split(DELIM) - [''])
|
58
63
|
# some clients (e.g. Thunderbird) mess up namespaces so subdirs
|
59
64
|
# end up looking like '.INBOX.Trash' instead of '.Trash'
|
data/maildir.gemspec
CHANGED
@@ -5,10 +5,10 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{maildir}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.5.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
-
s.authors = ["Aaron Suggs"]
|
11
|
+
s.authors = ["Aaron Suggs", "Niklas E. Cathor"]
|
12
12
|
s.date = %q{2010-01-24}
|
13
13
|
s.description = %q{A ruby library for reading and writing arbitrary messages in DJB's maildir format}
|
14
14
|
s.email = %q{aaron@ktheory.com}
|
@@ -35,9 +35,11 @@ Gem::Specification.new do |s|
|
|
35
35
|
"lib/maildir/unique_name.rb",
|
36
36
|
"maildir.gemspec",
|
37
37
|
"test/test_helper.rb",
|
38
|
+
"test/test_keywords.rb",
|
38
39
|
"test/test_maildir.rb",
|
39
40
|
"test/test_message.rb",
|
40
41
|
"test/test_serializers.rb",
|
42
|
+
"test/test_subdirs.rb",
|
41
43
|
"test/test_unique_name.rb"
|
42
44
|
]
|
43
45
|
s.homepage = %q{http://github.com/ktheory/maildir}
|
@@ -47,9 +49,11 @@ Gem::Specification.new do |s|
|
|
47
49
|
s.summary = %q{Read & write messages in the maildir format}
|
48
50
|
s.test_files = [
|
49
51
|
"test/test_helper.rb",
|
52
|
+
"test/test_keywords.rb",
|
50
53
|
"test/test_maildir.rb",
|
51
54
|
"test/test_message.rb",
|
52
55
|
"test/test_serializers.rb",
|
56
|
+
"test/test_subdirs.rb",
|
53
57
|
"test/test_unique_name.rb"
|
54
58
|
]
|
55
59
|
|
data/test/test_helper.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
|
-
require 'rubygems'
|
2
1
|
require 'test/unit'
|
3
2
|
require 'shoulda'
|
4
|
-
require 'fileutils'
|
5
3
|
require 'maildir'
|
6
4
|
|
7
5
|
# Require all the serializers
|
@@ -20,7 +18,19 @@ def temp_maildir
|
|
20
18
|
Maildir.new("/tmp/maildir_test")
|
21
19
|
end
|
22
20
|
|
23
|
-
#
|
21
|
+
# create the subdir tree:
|
22
|
+
# | INBOX
|
23
|
+
# |-- a
|
24
|
+
# | |-- x
|
25
|
+
# | |-- y
|
26
|
+
# |-- b
|
27
|
+
def setup_subdirs(maildir)
|
28
|
+
%w(a b a.x a.y).each do |x|
|
29
|
+
Maildir.new(File.join(maildir.path, ".#{x}"))
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Test that objects are neither nil nor empty
|
24
34
|
def assert_not_empty(obj, msg='')
|
25
35
|
assert !obj.nil? && !obj.empty?, msg
|
26
36
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'maildir/keywords'
|
3
|
+
class TestKeywords < Test::Unit::TestCase
|
4
|
+
context "A message" do
|
5
|
+
setup do
|
6
|
+
@data = "foo\n"
|
7
|
+
@msg = Maildir::Message.create(temp_maildir, @data)
|
8
|
+
end
|
9
|
+
|
10
|
+
should "remember keywords" do
|
11
|
+
kw = %w(Junk Seen)
|
12
|
+
@msg.keywords = kw
|
13
|
+
assert (@msg.keywords & kw).size == 2
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/test/test_maildir.rb
CHANGED
data/test/test_serializers.rb
CHANGED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'maildir/subdirs'
|
3
|
+
class TestSubdirs < Test::Unit::TestCase
|
4
|
+
context "A maildir" do
|
5
|
+
setup do
|
6
|
+
FakeFS::FileSystem.clear
|
7
|
+
@maildir = temp_maildir
|
8
|
+
setup_subdirs(@maildir)
|
9
|
+
end
|
10
|
+
|
11
|
+
should "have subdirs" do
|
12
|
+
assert @maildir.subdirs.any?
|
13
|
+
end
|
14
|
+
|
15
|
+
should "be called INBOX" do
|
16
|
+
assert @maildir.name == 'INBOX'
|
17
|
+
end
|
18
|
+
|
19
|
+
should "include direct subdirs" do
|
20
|
+
subdir_names = @maildir.subdirs.map(&:name)
|
21
|
+
assert subdir_names.include?('a') && subdir_names.include?('b')
|
22
|
+
end
|
23
|
+
|
24
|
+
should "not include deeper subdirs" do
|
25
|
+
subdir_names = @maildir.subdirs.map(&:name)
|
26
|
+
assert ! subdir_names.include?('x') && ! subdir_names.include?('a.x')
|
27
|
+
end
|
28
|
+
|
29
|
+
should "create more subdirs" do
|
30
|
+
@maildir.create_subdir("test")
|
31
|
+
assert @maildir.subdirs.map(&:name).include?("test")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context "A subdir" do
|
36
|
+
setup do
|
37
|
+
FakeFS::FileSystem.clear
|
38
|
+
@maildir = temp_maildir
|
39
|
+
setup_subdirs(@maildir)
|
40
|
+
end
|
41
|
+
|
42
|
+
should "include more subdirs" do
|
43
|
+
assert_not_empty @maildir.subdirs.select{ |sd| sd.name == 'a'}.first.subdirs
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
metadata
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: maildir
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aaron Suggs
|
8
|
+
- Niklas E. Cathor
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
@@ -80,9 +81,11 @@ files:
|
|
80
81
|
- lib/maildir/unique_name.rb
|
81
82
|
- maildir.gemspec
|
82
83
|
- test/test_helper.rb
|
84
|
+
- test/test_keywords.rb
|
83
85
|
- test/test_maildir.rb
|
84
86
|
- test/test_message.rb
|
85
87
|
- test/test_serializers.rb
|
88
|
+
- test/test_subdirs.rb
|
86
89
|
- test/test_unique_name.rb
|
87
90
|
has_rdoc: true
|
88
91
|
homepage: http://github.com/ktheory/maildir
|
@@ -114,7 +117,9 @@ specification_version: 3
|
|
114
117
|
summary: Read & write messages in the maildir format
|
115
118
|
test_files:
|
116
119
|
- test/test_helper.rb
|
120
|
+
- test/test_keywords.rb
|
117
121
|
- test/test_maildir.rb
|
118
122
|
- test/test_message.rb
|
119
123
|
- test/test_serializers.rb
|
124
|
+
- test/test_subdirs.rb
|
120
125
|
- test/test_unique_name.rb
|