nilclass-maildir 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,20 @@
1
+ module Maildir::Serializer
2
+ # The Maildir::Serializer::Base class reads & writes data to disk as a
3
+ # string. Other serializers (e.g. Maildir::Serializer::Mail) can extend this
4
+ # class to do some pre- and post-processing of the string.
5
+ #
6
+ # The Serializer API has two methods:
7
+ # load(path) # => returns data
8
+ # dump(data, path) # => returns number of bytes written
9
+ class Base
10
+ # Reads the file at path. Returns the contents of path.
11
+ def load(path)
12
+ File.read(path)
13
+ end
14
+
15
+ # Writes data to path. Returns number of bytes written.
16
+ def dump(data, path)
17
+ File.open(path, "w") {|file| file.write(data)}
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,13 @@
1
+ require 'json'
2
+ # Serialize messages as JSON
3
+ class Maildir::Serializer::JSON < Maildir::Serializer::Base
4
+ # Read data from path and parse it as JSON.
5
+ def load(path)
6
+ ::JSON.load(super(path))
7
+ end
8
+
9
+ # Dump data as JSON and writes it to path.
10
+ def dump(data, path)
11
+ super(data.to_json, path)
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ require 'mail'
2
+ # Serialize messages as a ruby Mail object
3
+ class Maildir::Serializer::Mail < Maildir::Serializer::Base
4
+ # Build a new Mail object from the data at path.
5
+ def load(path)
6
+ ::Mail.new(super(path))
7
+ end
8
+
9
+ # Write data to path as a Mail message.
10
+ def dump(data, path)
11
+ super(data.to_s, path)
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ # Serialize messages as Marshalled ruby objects
2
+ class Maildir::Serializer::Marshal < Maildir::Serializer::Base
3
+ # Read data from path and unmarshal it.
4
+ def load(path)
5
+ ::Marshal.load(super(path))
6
+ end
7
+
8
+ # Marshal data and write it to path.
9
+ def dump(data, path)
10
+ super(::Marshal.dump(data), path)
11
+ end
12
+ end
@@ -0,0 +1,13 @@
1
+ require 'yaml'
2
+ # Serialize messages as YAML
3
+ class Maildir::Serializer::YAML < Maildir::Serializer::Base
4
+ # Read data from path and parse it as YAML.
5
+ def load(path)
6
+ ::YAML.load(super(path))
7
+ end
8
+
9
+ # Dump data as YAML and writes it to path.
10
+ def dump(data, path)
11
+ super(data.to_yaml, path)
12
+ end
13
+ end
@@ -0,0 +1,70 @@
1
+ # implements subdirs as used by the Courier Mail Server (courier-mta.org)
2
+ require 'maildir'
3
+ module Maildir::Subdirs
4
+ ROOT_NAME = 'INBOX'
5
+ DELIM = '.'
6
+
7
+ def self.included(base)
8
+ base.instance_eval do
9
+ alias_method :inspect_without_subdirs, :inspect
10
+ alias_method :inspect, :inspect_with_subdirs
11
+ end
12
+ end
13
+
14
+ def name
15
+ root? ? ROOT_NAME : subdir_parts(path).last
16
+ end
17
+
18
+ def create_subdir(name)
19
+ raise ArgumentError.new("'name' may not contain delimiter character (#{DELIM})") if name.include?(DELIM)
20
+ full_name = (root? ? [] : subdir_parts(File.basename(path))).push(name).unshift('').join(DELIM)
21
+ md = Maildir.new(File.join(path, full_name), true)
22
+ @subdirs << md if @subdirs
23
+ md
24
+ end
25
+
26
+ # returns the logical mailbox path
27
+ def mailbox_path
28
+ @mailbox_path ||= root? ? ROOT_NAME : subdir_parts(File.basename(path)).unshift(ROOT_NAME).join(DELIM)
29
+ end
30
+
31
+ # returns an array of Maildir objects representing the direct subdirectories of this Maildir
32
+ def subdirs(only_direct=true)
33
+ if root?
34
+ @subdirs ||= (Dir.entries(path) - %w(. ..)).select {|e|
35
+ e =~ /^\./ && File.directory?(File.join(path, e)) && (only_direct ? subdir_parts(e).size == 1 : true)
36
+ }.map { |e| Maildir.new(File.join(path, e), false) }
37
+ else
38
+ my_parts = subdir_parts(File.basename(path))
39
+ @subdirs ||= root.subdirs(false).select { |md| subdir_parts(File.basename(md.path))[0..-2] == my_parts }
40
+ end
41
+ end
42
+
43
+ # Friendly inspect method
44
+ def inspect_with_subdirs
45
+ "#<#{self.class} path=#{@path} mailbox_path=#{mailbox_path}>"
46
+ end
47
+
48
+ # returns the Maildir representing the root directory
49
+ def root
50
+ root? ? self : Maildir.new(File.dirname(path), false)
51
+ end
52
+
53
+ # returns true if the parent directory doesn't look like a maildir
54
+ def root?
55
+ ! ((Dir.entries(File.dirname(path)) & %w(cur new tmp)).size == 3)
56
+ end
57
+
58
+ private
59
+
60
+ def subdir_parts(path)
61
+ path.sub!(/\/$/, '') # remove trailing slash
62
+ parts = (path.split(DELIM) - [''])
63
+ # some clients (e.g. Thunderbird) mess up namespaces so subdirs
64
+ # end up looking like '.INBOX.Trash' instead of '.Trash'
65
+ parts.shift if parts.first == ROOT_NAME
66
+ parts
67
+ end
68
+ end
69
+
70
+ Maildir.send(:include, Maildir::Subdirs)
@@ -0,0 +1,72 @@
1
+ # Class for generating unique file names for new messages
2
+ class Maildir::UniqueName
3
+ require 'thread' # For mutex support
4
+ require 'socket' # For getting the hostname
5
+ class << self
6
+ # Return a thread-safe increasing counter
7
+ def counter
8
+ @counter_mutex ||= Mutex.new
9
+ @counter_mutex.synchronize do
10
+ @counter = @counter.to_i + 1
11
+ end
12
+ end
13
+
14
+ def create
15
+ self.new.to_s
16
+ end
17
+ end
18
+
19
+ # Return a unique file name based on strategy
20
+ def initialize
21
+ # Use the same time object
22
+ @now = Time.now
23
+ end
24
+
25
+ # Return the name as a string
26
+ def to_s
27
+ [left, middle, right].join(".")
28
+ end
29
+
30
+ protected
31
+ # The left part of the unique name is the number of seconds from since the
32
+ # UNIX epoch
33
+ def left
34
+ @now.to_i.to_s
35
+ end
36
+
37
+ # The middle part contains the microsecond, the process id, and a
38
+ # per-process incrementing counter
39
+ def middle
40
+ "M#{microsecond}P#{process_id}Q#{delivery_count}"
41
+ end
42
+
43
+ # The right part is the hostname
44
+ def right
45
+ Socket.gethostname
46
+ end
47
+
48
+ def secure_random(bytes=8)
49
+ # File.read("/dev/urandom", bytes).unpack("H*")[0]
50
+ raise "Not implemented"
51
+ end
52
+
53
+ def inode
54
+ raise "Not implemented"
55
+ end
56
+
57
+ def device_number
58
+ raise "Not implemented"
59
+ end
60
+
61
+ def microsecond
62
+ @now.usec.to_s
63
+ end
64
+
65
+ def process_id
66
+ Process.pid.to_s
67
+ end
68
+
69
+ def delivery_count
70
+ self.class.counter.to_s
71
+ end
72
+ end
data/maildir.gemspec ADDED
@@ -0,0 +1,81 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{maildir}
8
+ s.version = "0.4.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Aaron Suggs", "Niklas E. Cathor"]
12
+ s.date = %q{2010-01-24}
13
+ s.description = %q{A ruby library for reading and writing arbitrary messages in DJB's maildir format}
14
+ s.email = %q{aaron@ktheory.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".gitignore",
21
+ "LICENSE",
22
+ "README.rdoc",
23
+ "Rakefile",
24
+ "VERSION",
25
+ "benchmarks/runner",
26
+ "lib/maildir.rb",
27
+ "lib/maildir/keywords.rb",
28
+ "lib/maildir/message.rb",
29
+ "lib/maildir/serializer/base.rb",
30
+ "lib/maildir/serializer/json.rb",
31
+ "lib/maildir/serializer/mail.rb",
32
+ "lib/maildir/serializer/marshal.rb",
33
+ "lib/maildir/serializer/yaml.rb",
34
+ "lib/maildir/subdirs.rb",
35
+ "lib/maildir/unique_name.rb",
36
+ "maildir.gemspec",
37
+ "test/test_helper.rb",
38
+ "test/test_keywords.rb",
39
+ "test/test_maildir.rb",
40
+ "test/test_message.rb",
41
+ "test/test_serializers.rb",
42
+ "test/test_subdirs.rb",
43
+ "test/test_unique_name.rb"
44
+ ]
45
+ s.homepage = %q{http://github.com/ktheory/maildir}
46
+ s.rdoc_options = ["--charset=UTF-8"]
47
+ s.require_paths = ["lib"]
48
+ s.rubygems_version = %q{1.3.5}
49
+ s.summary = %q{Read & write messages in the maildir format}
50
+ s.test_files = [
51
+ "test/test_serializers.rb",
52
+ "test/test_keywords.rb",
53
+ "test/test_helper.rb",
54
+ "test/test_subdirs.rb",
55
+ "test/test_message.rb",
56
+ "test/test_unique_name.rb",
57
+ "test/test_maildir.rb"
58
+ ]
59
+
60
+ if s.respond_to? :specification_version then
61
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
62
+ s.specification_version = 3
63
+
64
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
65
+ s.add_development_dependency(%q<shoulda>, [">= 0"])
66
+ s.add_development_dependency(%q<mail>, [">= 0"])
67
+ s.add_development_dependency(%q<json>, [">= 0"])
68
+ s.add_development_dependency(%q<ktheory-fakefs>, [">= 0"])
69
+ else
70
+ s.add_dependency(%q<shoulda>, [">= 0"])
71
+ s.add_dependency(%q<mail>, [">= 0"])
72
+ s.add_dependency(%q<json>, [">= 0"])
73
+ s.add_dependency(%q<ktheory-fakefs>, [">= 0"])
74
+ end
75
+ else
76
+ s.add_dependency(%q<shoulda>, [">= 0"])
77
+ s.add_dependency(%q<mail>, [">= 0"])
78
+ s.add_dependency(%q<json>, [">= 0"])
79
+ s.add_dependency(%q<ktheory-fakefs>, [">= 0"])
80
+ end
81
+ end
@@ -0,0 +1,38 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+ require 'fileutils'
5
+ require 'maildir'
6
+
7
+ # Require all the serializers
8
+ serializers = File.join(File.dirname(__FILE__), "..","lib","maildir","serializer","*")
9
+ Dir.glob(serializers).each do |serializer|
10
+ require serializer
11
+ end
12
+
13
+ # Require 'ktheory-fakefs' until issues 28, 29, and 30 are resolved in
14
+ # defunkt/fakefs. See http://github.com/defunkt/fakefs/issues
15
+ gem "ktheory-fakefs"
16
+ require 'fakefs'
17
+
18
+ # Create a reusable maildir that's cleaned up when the tests are done
19
+ def temp_maildir
20
+ Maildir.new("/tmp/maildir_test")
21
+ end
22
+
23
+ # create the subdir tree:
24
+ # | INBOX
25
+ # |-- a
26
+ # | |-- x
27
+ # | |-- y
28
+ # |-- b
29
+ def setup_subdirs(maildir)
30
+ %w(a b a.x a.y).each do |x|
31
+ Maildir.new(File.join(maildir.path, ".#{x}"))
32
+ end
33
+ end
34
+
35
+ # Useful for testing that strings defined & not empty
36
+ def assert_not_empty(obj, msg='')
37
+ assert !obj.nil? && !obj.empty?, msg
38
+ 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
@@ -0,0 +1,73 @@
1
+ require 'test_helper'
2
+ class TestMaildir < Test::Unit::TestCase
3
+
4
+ context "A maildir" do
5
+ setup do
6
+ FakeFS::FileSystem.clear
7
+ end
8
+
9
+ should "have a path" do
10
+ assert_not_empty temp_maildir.path
11
+ end
12
+
13
+ should "create subdirectories by default" do
14
+ %w(tmp new cur).each do |subdir|
15
+ subdir_path = temp_maildir.send("#{subdir}_path")
16
+ assert File.directory?(subdir_path), "Subdir #{subdir} does not exist"
17
+ end
18
+ end
19
+
20
+ should "expand paths" do
21
+ maildir = Maildir.new("~/test_maildir/")
22
+ expanded_path = File.expand_path("~/test_maildir")
23
+ expanded_path = File.join(expanded_path, "/")
24
+ assert_equal expanded_path, maildir.path
25
+ end
26
+
27
+ should "not create directories if specified" do
28
+ maildir = Maildir.new("/maildir_without_subdirs", false)
29
+ %w(tmp new cur).each do |subdir|
30
+ subdir_path = maildir.send("#{subdir}_path")
31
+ assert !File.directory?(subdir_path), "Subdir #{subdir} exists"
32
+ end
33
+ end
34
+
35
+ should "be identical to maildirs with the same path" do
36
+ new_maildir = Maildir.new(temp_maildir.path)
37
+ assert_equal temp_maildir.path, new_maildir.path
38
+ assert_equal temp_maildir, new_maildir
39
+ end
40
+
41
+ should "list a new message" do
42
+ @message = temp_maildir.add("")
43
+ messages = temp_maildir.list(:new)
44
+ assert messages.include?(@message)
45
+ end
46
+
47
+ should "list a cur message" do
48
+ @message = temp_maildir.add("")
49
+ @message.process
50
+ messages = temp_maildir.list(:cur)
51
+ assert messages.include?(@message)
52
+ end
53
+ end
54
+
55
+ context "Maildirs with the same path" do
56
+ should "be identical" do
57
+ another_maildir = Maildir.new(temp_maildir.path, false)
58
+ assert_equal temp_maildir, another_maildir
59
+ end
60
+ end
61
+
62
+ context "Maildirs with stale messages in tmp" do
63
+ should "be found" do
64
+ stale_path = File.join(temp_maildir.path, "tmp", "stale_message")
65
+ File.open(stale_path, "w"){|f| f.write("")}
66
+ stale_time = Time.now - 30*24*60*60 # 1 month ago
67
+ File.utime(stale_time, stale_time, stale_path)
68
+
69
+ stale_tmp = [temp_maildir.get("tmp/stale_message")]
70
+ assert_equal stale_tmp, temp_maildir.get_stale_tmp
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,226 @@
1
+ require 'test_helper'
2
+ class TestMessage < Test::Unit::TestCase
3
+
4
+ context "An new, unwritten message" do
5
+ setup do
6
+ FakeFS::FileSystem.clear
7
+ @message = Maildir::Message.new(temp_maildir)
8
+ end
9
+
10
+ should "be in :tmp" do
11
+ assert_equal :tmp, @message.dir
12
+ assert_match(/tmp/, @message.path)
13
+ end
14
+
15
+ should "have a unique name" do
16
+ assert_not_empty @message.unique_name
17
+ end
18
+
19
+ should "have a file name" do
20
+ assert_not_empty @message.filename
21
+ end
22
+
23
+ should "have no info" do
24
+ assert_nil @message.info
25
+ end
26
+
27
+ should "not be able to set info" do
28
+ assert_raises RuntimeError do
29
+ @message.info= "2,FRS"
30
+ end
31
+ end
32
+ end
33
+
34
+ context "A written message" do
35
+ setup do
36
+ FakeFS::FileSystem.clear
37
+ @message = Maildir::Message.new(temp_maildir)
38
+ @data = "foo"
39
+ @message.write(@data)
40
+ end
41
+
42
+ should "not be writable" do
43
+ assert_raise RuntimeError do
44
+ @message.write("nope!")
45
+ end
46
+ end
47
+
48
+ should "have no info" do
49
+ assert_nil @message.info
50
+ end
51
+
52
+ should "not be able to set info" do
53
+ assert_raises RuntimeError do
54
+ @message.info= "2,FRS"
55
+ end
56
+ end
57
+
58
+ should "be in new" do
59
+ assert_equal :new, @message.dir
60
+ assert_match(/new/, @message.path)
61
+ end
62
+
63
+ should "have a file" do
64
+ assert File.exists?(@message.path)
65
+ end
66
+
67
+ should "have the correct data" do
68
+ assert_equal @data, @message.data
69
+ end
70
+ end
71
+
72
+ context "A processed message" do
73
+ setup do
74
+ FakeFS::FileSystem.clear
75
+ @data = "foo"
76
+ @message = Maildir::Message.create(temp_maildir, @data)
77
+ @message.process
78
+ end
79
+
80
+ should "not be writable" do
81
+ assert_raise RuntimeError do
82
+ @message.write("nope!")
83
+ end
84
+ end
85
+
86
+ should "be in cur" do
87
+ assert_equal :cur, @message.dir
88
+ end
89
+
90
+ should "have info" do
91
+ assert_equal Maildir::Message::INFO, @message.info
92
+ end
93
+
94
+ should "set info" do
95
+ info = "2,FRS"
96
+ @message.info = "2,FRS"
97
+ assert_equal @message.info, info
98
+ assert_match /#{info}$/, @message.path
99
+ end
100
+
101
+ should "add and remove flags" do
102
+ @message.add_flag('S')
103
+ assert_equal ['S'], @message.flags
104
+
105
+ # Test lowercase
106
+ @message.add_flag('r')
107
+ assert_equal ['R', 'S'], @message.flags
108
+
109
+ @message.remove_flag('S')
110
+ assert_equal ['R'], @message.flags
111
+
112
+ # Test lowercase
113
+ @message.remove_flag('r')
114
+ assert_equal [], @message.flags
115
+ end
116
+
117
+ flag_tests = {
118
+ "FRS" => ['F', 'R', 'S'],
119
+ "Sr" => ['R', 'S'], # test capitalization & sorting
120
+ '' => []
121
+ }
122
+ flag_tests.each do |arg, results|
123
+ should "set flags: #{arg}" do
124
+ @message.flags = arg
125
+ assert_equal results, @message.flags
126
+ path_suffix = "#{Maildir::Message::INFO}#{results.join('')}"
127
+ assert_match /#{path_suffix}$/, @message.path
128
+ end
129
+ end
130
+ end
131
+
132
+ context "A destroyed message" do
133
+ setup do
134
+ FakeFS::FileSystem.clear
135
+ @message = Maildir::Message.create(temp_maildir, "foo")
136
+ @message.destroy
137
+ end
138
+ should "be frozen" do
139
+ assert @message.frozen?, "Message is not frozen"
140
+ end
141
+ should "have a nonexistant path" do
142
+ assert !File.exists?(@message.path), "Message path exists"
143
+ end
144
+ end
145
+
146
+ context "A message with a bad path" do
147
+ setup do
148
+ FakeFS::FileSystem.clear
149
+ @message = temp_maildir.add("")
150
+ File.delete(@message.path)
151
+ end
152
+
153
+ should "raise error for data" do
154
+ assert_raise Errno::ENOENT do
155
+ @message.data
156
+ end
157
+ assert @message.frozen?
158
+ end
159
+
160
+ should "not be processed" do
161
+ old_key = @message.key
162
+ assert_equal false, @message.process
163
+ assert @message.frozen?
164
+ end
165
+
166
+
167
+ should "reset to the old key after attempt to process" do
168
+ old_key = @message.key
169
+ @message.process
170
+ assert_equal old_key, @message.key
171
+ end
172
+ end
173
+
174
+ context "Different messages" do
175
+ setup do
176
+ FakeFS::FileSystem.clear
177
+ end
178
+
179
+ should "differ" do
180
+ @message1 = temp_maildir.add("")
181
+ @message2 = temp_maildir.add("")
182
+ assert_equal -1, @message1 <=> @message2
183
+ assert_equal 1, @message2 <=> @message1
184
+ assert_not_equal @message1, @message2
185
+ end
186
+ end
187
+
188
+ context "Identical messages" do
189
+ setup do
190
+ FakeFS::FileSystem.clear
191
+ end
192
+
193
+ should "be identical" do
194
+ @message1 = temp_maildir.add("")
195
+ another_message1 = temp_maildir.get(@message1.key)
196
+ assert_equal @message1, another_message1
197
+ end
198
+ end
199
+
200
+ context "Message#utime" do
201
+ setup do
202
+ FakeFS::FileSystem.clear
203
+ end
204
+
205
+ should "update the messages mtime" do
206
+ @message = temp_maildir.add("")
207
+ time = Time.now - 60
208
+
209
+ @message.utime(time, time)
210
+
211
+ # Time should be within 1 second of each other
212
+ assert_in_delta time, @message.mtime, 1
213
+ end
214
+
215
+ # atime not currently supported in FakeFS
216
+ # should "update the messages atime" do
217
+ # @message = temp_maildir.add("")
218
+ # time = Time.now - 60
219
+ #
220
+ # @message.utime(time, time)
221
+ #
222
+ # # Time should be within 1 second of each other
223
+ # assert_in_delta time, @message.atime, 1
224
+ # end
225
+ end
226
+ end