message_dir 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in message.gemspec
4
+ gemspec
5
+
6
+ gem "simple_uuid"
7
+
8
+ group :development do
9
+ gem "rspec"
10
+ end
data/README.rdoc ADDED
@@ -0,0 +1,64 @@
1
+ = Message dir
2
+
3
+ A directory, with messages. Like your Maildir/
4
+
5
+ Meant to be used to safely store incoming messages from any slow connection
6
+
7
+ == Message dir DSL
8
+
9
+ # create
10
+ dir = MessageDir.new("/path/to/store")
11
+ msg = dir.new do |fh|
12
+ fh.puts info_from_slow_connection
13
+ end
14
+
15
+ # move a message to cur/
16
+ dir.cur(msg)
17
+ msg.cur!
18
+
19
+ # get a list of all messages
20
+ dir.messages
21
+
22
+ # get a list of all messages in new/
23
+ dir.messages(:new)
24
+
25
+ # or cur
26
+ dir.messages(:cur)
27
+
28
+ # work on a message (moves it to tmp/ for the duration of a block)
29
+ dir.process(msg, File::WRONLY|File::APPEND) do |fh|
30
+ fh.puts more_info
31
+ end
32
+
33
+ msg.process(File::WRONLY|File::TRUNC) do |fh|
34
+ fh.puts new_contents
35
+ end
36
+
37
+ # remove a message
38
+ msg = dir.messages(:new).first
39
+ dir.rm(msg) # => raise exception, we dont like to remove 'new' messages
40
+
41
+ msg = dir.msgs(:cur).first
42
+ msg.rm!
43
+
44
+ # lock a message
45
+ msg.lock! do
46
+ # something with the locked message
47
+ end
48
+ msg.locked? # => false
49
+
50
+ dir.lock(msg) do
51
+ end
52
+
53
+ == Message acts as file
54
+
55
+ A message is not an implementation of IO or File but it acts in the same way.
56
+ Use that to your advantage.
57
+
58
+ == Message safety
59
+
60
+ A lot is done to ensure the safe handling of messages
61
+
62
+ * Messages are created and processed in tmp/
63
+ * A message fh is always opened with File::SYNC - slow but trustworthy
64
+ * Locks are carried over processes (and crashes) using .lock files
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
@@ -0,0 +1,131 @@
1
+ class MessageDir
2
+ class Message
3
+ def initialize(master, path, uuid)
4
+ @master = master
5
+ @path = path
6
+ @uuid = uuid
7
+ @io = nil
8
+
9
+ self.create if !File.exists?(self.path)
10
+ @locked = File.exists?(self.lock_file)
11
+ open
12
+ end
13
+
14
+ def self.load(master, path)
15
+ self.new(master, File.dirname(path), SimpleUUID::UUID.new(File.basename(path)))
16
+ end
17
+
18
+ def path
19
+ File.join(@path, @uuid.to_guid)
20
+ end
21
+
22
+ def guid
23
+ @uuid.to_guid
24
+ end
25
+
26
+ def spot
27
+ File.basename(@path).to_sym
28
+ end
29
+
30
+ def create
31
+ open(File::CREAT|File::WRONLY) do |f|
32
+ # write 1 byte and truncate to force existance of the file
33
+ f.syswrite("i")
34
+ f.truncate(0)
35
+ end
36
+ end
37
+
38
+ def process(mode=File::RDONLY, &block)
39
+ @master.process(self, mode, &block)
40
+ end
41
+
42
+ def open(mode=File::RDONLY, &block)
43
+ close
44
+
45
+ # make sure them files are synced
46
+ mode |= File::SYNC unless mode & File::SYNC == File::SYNC
47
+
48
+ @io = File.open(self.path, mode, &block)
49
+ end
50
+
51
+ def fh
52
+ @io
53
+ end
54
+
55
+ def move(path)
56
+ raise "Can't move a locked file" if locked?
57
+
58
+ close
59
+ FileUtils.mv(self.path, File.join(path, self.guid))
60
+ @path = path
61
+ open
62
+ end
63
+
64
+ def rm
65
+ @master.rm(self)
66
+ end
67
+
68
+ def close
69
+ @io.close unless closed?
70
+ rescue
71
+ true
72
+ end
73
+
74
+ def closed?
75
+ @io.closed?
76
+ rescue
77
+ return true
78
+ end
79
+
80
+ def cur!
81
+ @master.cur(self)
82
+ end
83
+
84
+ def ==(other)
85
+ other.path == self.path && self.guid == other.guid
86
+ end
87
+
88
+ def locked?
89
+ @locked == true ? true : false || File.exists?(lock_file)
90
+ end
91
+
92
+ def lock_file
93
+ "#{path}.lock"
94
+ end
95
+
96
+ def lock
97
+ return if locked?
98
+
99
+ File.open(lock_file, File::CREAT|File::WRONLY) do |lf|
100
+ res = lf.flock(File::LOCK_EX|File::LOCK_NB)
101
+ raise "Could not obtain a lock on the lock file" if res == false
102
+ lf.flock(File::LOCK_UN)
103
+ end
104
+
105
+ if block_given?
106
+ yield
107
+ unlock
108
+ else
109
+ @locked = true
110
+ end
111
+ end
112
+ alias_method :lock!, :lock
113
+
114
+ def unlock
115
+ File.open(lock_file, File::CREAT|File::WRONLY) do |lf|
116
+ res = lf.flock(File::LOCK_EX|File::LOCK_NB)
117
+ raise "Could not obtain a lock on the lock file" if res == false
118
+ lf.flock(File::LOCK_UN)
119
+ File.unlink(lock_file)
120
+ end
121
+ @locked = false
122
+ end
123
+
124
+ def method_missing(method, *args, &block)
125
+ super
126
+ rescue NameError, NoMethodError => e
127
+ @io.send(method, *args, &block)
128
+ end
129
+
130
+ end
131
+ end
@@ -0,0 +1,3 @@
1
+ class MessageDir
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,110 @@
1
+ require "message_dir/version"
2
+ require "message_dir/message"
3
+ require 'simple_uuid'
4
+ require 'fileutils'
5
+
6
+ class MessageDir
7
+ SPOTS = %w(tmp new cur)
8
+
9
+ attr_reader :path
10
+
11
+ def initialize(path)
12
+ @path = MessageDir::Path.new(path)
13
+
14
+ create_tree
15
+ end
16
+
17
+ def create_tree
18
+ SPOTS.each do |spot|
19
+ FileUtils.mkdir_p @path.send(spot)
20
+ end
21
+ end
22
+
23
+ def new(&block)
24
+ uuid = SimpleUUID::UUID.new
25
+ msg = Message.new(self, path.new, uuid)
26
+
27
+ if block_given?
28
+ begin
29
+ msg.move(path.tmp)
30
+
31
+ msg.open(File::TRUNC|File::WRONLY) do |fh|
32
+ yield fh
33
+ end
34
+
35
+ msg.move(path.new)
36
+ rescue Exception => ex
37
+ # silently ignore errors
38
+ ensure
39
+ # make sure the message is written
40
+ msg.close
41
+ end
42
+ end
43
+
44
+ msg.open(File::RDONLY)
45
+ msg.seek(0,0)
46
+
47
+ return msg
48
+ end
49
+
50
+ def cur(msg)
51
+ return if msg.spot == :cur
52
+ msg.move(path.cur)
53
+ end
54
+
55
+ def messages(where=nil)
56
+ spots = where.nil? ? SPOTS : [ where ]
57
+
58
+ # find all the wanted paths
59
+ paths = spots.collect do |spot|
60
+ Dir[File.join(path.send(spot), '*')].select do |path|
61
+ path !~ /\.lock$/ && File.file?(path)
62
+ end
63
+ end.flatten
64
+
65
+ # load all the wanted messages
66
+ paths.collect do |path|
67
+ Message.load(self, path)
68
+ end
69
+ end
70
+ alias_method :msgs, :messages
71
+
72
+ def message(uuid)
73
+ uuid = uuid.to_guid if uuid.is_a? SimpleUUID::UUID
74
+ messages.select { |msg| msg.guid == uuid }.first
75
+ end
76
+
77
+ def process(msg, mode=File::RDONLY, &block)
78
+ raise "No block given" if !block_given?
79
+
80
+ # move to tmp and open with block for the given mode
81
+ before = msg.spot
82
+ msg.move(path.tmp) unless before == :tmp
83
+ msg.lock do
84
+ msg.open(mode, &block)
85
+ end
86
+
87
+ # move back re-open with default mode
88
+ msg.move(path.send(before)) unless before == :tmp
89
+ msg.open
90
+ end
91
+
92
+ def rm(msg)
93
+ return false if msg.locked?
94
+ File.unlink(msg.path) == 1 ? true : false
95
+ end
96
+
97
+ class Path
98
+ def initialize(root)
99
+ @root = root
100
+ end
101
+
102
+ def method_missing(method, *args, &block)
103
+ super
104
+ rescue NoMethodError => e
105
+ if [ :new, :cur, :tmp ].include?(method)
106
+ return File.join(@root, method.to_s)
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "message_dir/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "message_dir"
7
+ s.version = MessageDir::VERSION
8
+ s.authors = ["Hartog C. de Mik"]
9
+ s.email = ["hartog.de.mik@gmail.com"]
10
+ s.homepage = "https://github.com/coffeeaddict/message_dir"
11
+ s.summary = %q{Maildir like messages}
12
+ s.description = %q{Handle slow network messages with care}
13
+
14
+ s.rubyforge_project = "message_dir"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_dependency(%q<simple_uuid>, ['~> 0.2'])
22
+ end
@@ -0,0 +1,301 @@
1
+ require 'spec_helper'
2
+ require 'tmpdir'
3
+
4
+ describe MessageDir do
5
+ before :each do
6
+ @path = Dir.mktmpdir(nil, '/tmp')
7
+ end
8
+
9
+ after :each do
10
+ FileUtils.remove_entry_secure @path
11
+ end
12
+
13
+ it "should create a tree structure at a designated directory" do
14
+ expect {
15
+ MessageDir.new(@path)
16
+ }.to change {
17
+ %w(tmp new cur).collect do |spot|
18
+ File.directory?(File.join(@path, spot))
19
+ end
20
+ }
21
+ end
22
+
23
+ subject { MessageDir.new(@path) }
24
+
25
+ it "should create a new message" do
26
+ msg = subject.new
27
+ File.exists?(msg.path).should == true
28
+ msg.path.should =~ Regexp.new(subject.path.new)
29
+ end
30
+
31
+ it "should provide new messages open for reading" do
32
+ msg = subject.new
33
+
34
+ msg.size.should == 0
35
+
36
+ expect { msg.syswrite("failure") }.to raise_error(IOError)
37
+ end
38
+
39
+ it "should provide new messages in block style" do
40
+ msg = subject.new do |fh|
41
+ fh.puts "I is a message"
42
+ end
43
+
44
+ msg.path.should =~ Regexp.new(subject.path.new)
45
+
46
+ msg.pos.should == 0
47
+
48
+ expect { msg.syswrite("failure") }.to raise_error(IOError)
49
+
50
+ msg.read.should == "I is a message\n"
51
+ end
52
+
53
+ it "should handle exceptions when creating messages" do
54
+ expect {
55
+ subject.new do |fh|
56
+ raise "an error"
57
+ end
58
+ }.to_not raise_error
59
+
60
+ msg = subject.new do |fh|
61
+ fh.puts "some info"
62
+ raise
63
+ end
64
+
65
+ msg.should_not be_nil
66
+ msg.read.should == "some info\n"
67
+ end
68
+
69
+ it "should place a new message in tmp during creation" do
70
+ msg = subject.new do |fh|
71
+ fh.puts "I am not in new"
72
+ raise
73
+ end
74
+
75
+ msg.path.should =~ Regexp.new(subject.path.tmp)
76
+ end
77
+
78
+ it "should move a message to cur" do
79
+ msg = subject.new do |fh|
80
+ fh.puts "I am now in cur"
81
+ end
82
+
83
+ msg.read.should == "I am now in cur\n"
84
+
85
+ subject.cur(msg)
86
+
87
+ msg.should_not be_closed
88
+ msg.path.should =~ Regexp.new(subject.path.cur)
89
+
90
+ msg.read.should == "I am now in cur\n"
91
+ end
92
+
93
+ it "should move a message to cur over the message" do
94
+ msg = subject.new do |fh|
95
+ fh.puts "i am to be curred"
96
+ end
97
+
98
+ msg.cur!
99
+
100
+ msg.should_not be_closed
101
+ msg.path.should =~ Regexp.new(subject.path.cur)
102
+ msg.read.should == "i am to be curred\n"
103
+ end
104
+
105
+ describe "listing messages" do
106
+ it "should list no messages when empty" do
107
+ subject.messages
108
+ subject.messages.should be_empty
109
+ end
110
+
111
+ it "should list all messages" do
112
+ 3.times do |i|
113
+ msg = subject.new do |fh|
114
+ fh.puts "i am message #{i}"
115
+ end
116
+ end
117
+
118
+ subject.messages.count.should == 3
119
+ end
120
+
121
+ it "should list all messages in new" do
122
+ 3.times do |i|
123
+ msg = subject.new do |fh|
124
+ fh.puts "i am message #{i}"
125
+ end
126
+
127
+ msg.cur! if i == 1
128
+ end
129
+
130
+ subject.msgs(:new).count.should == 2
131
+ end
132
+
133
+ it "should list all messages in cur" do
134
+ 3.times do |i|
135
+ msg = subject.new do |fh|
136
+ fh.puts "i am message #{i}"
137
+ end
138
+
139
+ msg.cur! if i != 1
140
+ end
141
+
142
+ subject.msgs(:cur).count.should == 2
143
+ end
144
+
145
+ it "should list all messages in tmp" do
146
+ 3.times do |i|
147
+ msg = subject.new do |fh|
148
+ fh.puts "i am message #{i}"
149
+ end
150
+ msg.move(subject.path.tmp)
151
+ end
152
+
153
+ subject.msgs(:tmp).count.should == 3
154
+ end
155
+ end
156
+
157
+ describe "accessing messages" do
158
+ before :each do
159
+ 3.times { subject.new }
160
+ 3.times { subject.new.cur! }
161
+ end
162
+
163
+ it "should provide access to a message in new" do
164
+ subject.msgs(:new).should be_any
165
+ subject.msgs(:new).first.should be_kind_of(MessageDir::Message)
166
+ end
167
+
168
+ it "should provide access to a message in cur" do
169
+ subject.msgs(:cur).should be_any
170
+ subject.msgs(:cur).first.should be_kind_of(MessageDir::Message)
171
+ end
172
+
173
+ it "should provide access to a message based on UUID" do
174
+ msg = subject.msgs(:new).first
175
+ same = subject.message(msg.guid)
176
+
177
+ same.should be_kind_of(MessageDir::Message)
178
+
179
+ msg.should == same
180
+ end
181
+ end
182
+
183
+ describe "locking" do
184
+ before :each do
185
+ 3.times { subject.new }
186
+ end
187
+
188
+ it "should not lock messages by default" do
189
+ msg = subject.msgs.first
190
+ msg.should_not be_locked
191
+ end
192
+
193
+ it "should lock messages" do
194
+ msg = subject.msgs.first
195
+ msg.lock
196
+ msg.should be_locked
197
+ end
198
+
199
+ it "should unlock messages" do
200
+ msg = subject.msgs.first
201
+ msg.lock
202
+ msg.should be_locked
203
+
204
+ msg.unlock
205
+ msg.should_not be_locked
206
+ end
207
+
208
+ it "should lock message in block style" do
209
+ msg = subject.msgs.first
210
+ msg.should_not be_locked
211
+
212
+ msg.lock do
213
+ msg.should be_locked
214
+
215
+ msg.open(File::WRONLY|File::APPEND) do |fh|
216
+ fh.puts "Placed in a locked file"
217
+ end
218
+ end
219
+
220
+ msg.should_not be_locked
221
+ end
222
+ end
223
+
224
+
225
+ describe "processing" do
226
+ before :each do
227
+ 3.times do |i|
228
+ msg = subject.new do |fh|
229
+ fh.puts "I am new"
230
+ end
231
+
232
+ msg.cur! if i == 1
233
+ end
234
+ end
235
+
236
+ it "should process messages" do
237
+ msg = subject.msgs.first
238
+ subject.process(msg, File::WRONLY|File::TRUNC) do |fh|
239
+ fh.puts "I am processed"
240
+ end
241
+
242
+ msg.read.should == "I am processed\n"
243
+ end
244
+
245
+ it "should process messages read-only per default" do
246
+ msg = subject.msgs.first
247
+ msg.process do |fh|
248
+ expect { fh.syswrite(" ") }.to raise_error(IOError)
249
+ end
250
+ end
251
+
252
+ it "should move messages to tmp for processing" do
253
+ msg = subject.msgs(:cur).first
254
+
255
+ msg.process do |fh|
256
+ fh.path.should =~ Regexp.new(subject.path.tmp)
257
+ msg.path.should =~ Regexp.new(subject.path.tmp)
258
+ end
259
+
260
+ msg.path.should =~ Regexp.new(subject.path.cur)
261
+ end
262
+
263
+ it "should lock messages when processing" do
264
+ msg = subject.msgs(:cur).first
265
+ msg.should_not be_locked
266
+ msg.process do |fh|
267
+ msg.should be_locked
268
+ msg.lock_file.should =~ Regexp.new(subject.path.tmp)
269
+ end
270
+
271
+ msg.should_not be_locked
272
+ end
273
+ end
274
+
275
+ describe "removing" do
276
+ before :each do
277
+ 3.times do |i|
278
+ msg = subject.new do |fh|
279
+ fh.puts "I am new"
280
+ end
281
+
282
+ msg.cur! if i == 1
283
+ end
284
+ end
285
+
286
+ it "should remove a message" do
287
+ msg = subject.msgs.last
288
+
289
+ subject.rm(msg).should be_true
290
+ File.exists?(msg.path).should be_false
291
+ end
292
+
293
+ it "should not remove a message when locked" do
294
+ msg = subject.msgs.last
295
+ msg.lock
296
+
297
+ msg.rm.should_not be_true
298
+ File.exists?(msg.path).should_not be_false
299
+ end
300
+ end
301
+ end
@@ -0,0 +1,111 @@
1
+ require 'spec_helper'
2
+ require 'simple_uuid'
3
+
4
+ describe MessageDir::Message do
5
+ before :all do
6
+ @dir = MessageDir.new(Dir.mktmpdir(nil, '/tmp'))
7
+ end
8
+
9
+ after :all do
10
+ FileUtils.remove_entry_secure @dir.path
11
+ end
12
+
13
+ describe "On initialize" do
14
+ before :each do
15
+ uuid = SimpleUUID::UUID.new
16
+ File.open(File.join(@dir.path, "new", uuid.to_guid), "w") do |f|
17
+ f.puts "hello"
18
+ end
19
+ @new = uuid.to_guid
20
+
21
+ uuid = SimpleUUID::UUID.new
22
+ File.open(File.join(@dir.path, "cur", uuid.to_guid), "w") do |f|
23
+ f.puts "world"
24
+ end
25
+ @cur = uuid.to_guid
26
+ end
27
+
28
+ it "should not open a message without a guid" do
29
+ msg = MessageDir::Message.new(@dir)
30
+ msg.fh.should be_nil
31
+ end
32
+
33
+ it "should not create a message with a non existing guid" do
34
+ msg = MessageDir::Message.new(@dir, "24319b5c-5cc8-11e1-8312-67c6faefd181")
35
+ msg.fh.should be_nil
36
+ end
37
+
38
+ it "should open a message with an existing guid" do
39
+ msg = MessageDir::Message.new(@dir, @new)
40
+ msg.fh.should be_kind_of(File)
41
+ msg.fh.should_not be_closed
42
+ end
43
+
44
+ it "should find a message in cur as well" do
45
+ msg = MessageDir::Message.new(@dir, @cur)
46
+ msg.fh.should be_kind_of(File)
47
+ msg.fh.should_not be_closed
48
+ end
49
+ end
50
+
51
+ describe "With new" do
52
+ before :each do
53
+ @msg = MessageDir::Message.new(@dir)
54
+ end
55
+
56
+ it "should create a message in new" do
57
+ @msg.new do |fh|
58
+ fh.puts "i am the new message"
59
+ end
60
+
61
+ @msg.uuid.should be_kind_of(SimpleUUID::UUID)
62
+ @msg.guid.should be_kind_of(String)
63
+ @msg.guid.should_not be_empty
64
+
65
+ @msg.fh.should_not be_closed
66
+ @msg.fh.read.should =~ /i am the new message/
67
+
68
+ @msg.spot.should == :new
69
+ @msg.path.should =~ /new\//
70
+ end
71
+
72
+ it "should keep a message in tmp while not done" do
73
+ Thread.new do
74
+ @msg.new do |fh|
75
+ fh.puts "a part"
76
+ sleep 4
77
+ fh.puts "and another"
78
+ end
79
+ end
80
+
81
+ sleep 1
82
+
83
+ files = Dir[File.join(@dir.path, "tmp", "*")]
84
+ files.count.should == 1
85
+ end
86
+ end
87
+
88
+ describe "Current" do
89
+ it "should move a message to cur/" do
90
+ msg = MessageDir::Message.new(@dir).new do |fh|
91
+ fh.puts "i am the message"
92
+ end
93
+
94
+ msg.spot.should == :new
95
+
96
+ msg.cur
97
+ msg.spot.should == :cur
98
+ msg.path.should =~ /cur\//
99
+ end
100
+
101
+ it "should keep an open filehandle" do
102
+ msg = MessageDir::Message.new(@dir).new do |fh|
103
+ fh.puts "i am the message"
104
+ end.cur
105
+
106
+ msg.spot.should == :cur
107
+ msg.fh.should_not be_closed
108
+ end
109
+ end
110
+
111
+ end
@@ -0,0 +1,2 @@
1
+ require 'message_dir'
2
+ require 'fileutils'
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: message_dir
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.1
6
+ platform: ruby
7
+ authors:
8
+ - Hartog C. de Mik
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2012-03-07 00:00:00 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: simple_uuid
17
+ prerelease: false
18
+ requirement: &id001 !ruby/object:Gem::Requirement
19
+ none: false
20
+ requirements:
21
+ - - ~>
22
+ - !ruby/object:Gem::Version
23
+ version: "0.2"
24
+ type: :runtime
25
+ version_requirements: *id001
26
+ description: Handle slow network messages with care
27
+ email:
28
+ - hartog.de.mik@gmail.com
29
+ executables: []
30
+
31
+ extensions: []
32
+
33
+ extra_rdoc_files: []
34
+
35
+ files:
36
+ - .gitignore
37
+ - Gemfile
38
+ - README.rdoc
39
+ - Rakefile
40
+ - lib/message_dir.rb
41
+ - lib/message_dir/message.rb
42
+ - lib/message_dir/version.rb
43
+ - message_dir.gemspec
44
+ - spec/objects/message_dir_spec.rb
45
+ - spec/objects/message_spec_old.rb
46
+ - spec/spec_helper.rb
47
+ homepage: https://github.com/coffeeaddict/message_dir
48
+ licenses: []
49
+
50
+ post_install_message:
51
+ rdoc_options: []
52
+
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: "0"
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: "0"
67
+ requirements: []
68
+
69
+ rubyforge_project: message_dir
70
+ rubygems_version: 1.8.16
71
+ signing_key:
72
+ specification_version: 3
73
+ summary: Maildir like messages
74
+ test_files: []
75
+