qup 1.4.0 → 1.4.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/qup.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module Qup
2
2
  # The Current Version of the library
3
- VERSION = '1.4.0'
3
+ VERSION = '1.4.1'
4
4
 
5
5
  class Error < StandardError; end
6
6
 
@@ -28,6 +28,12 @@ class Qup::Adapter::Kestrel
28
28
  @client.flush(@name)
29
29
  end
30
30
 
31
+ # Internal: Remove the Queue if possible
32
+ #
33
+ # Returns nothing
34
+ def destroy
35
+ @client.delete(@name)
36
+ end
31
37
 
32
38
  # Internal: return the number of Messages on the Queue
33
39
  #
@@ -32,6 +32,16 @@ class Qup::Adapter::Kestrel
32
32
  ::Qup::Subscriber.new( self, subscriber_queue( name ) )
33
33
  end
34
34
 
35
+ # Internal: Destroy the topic
36
+ #
37
+ # Returns nothing
38
+ def destroy
39
+ subscribers.each do |name, sub|
40
+ sub.destroy
41
+ end
42
+ @client.delete( @name )
43
+ end
44
+
35
45
 
36
46
  # Internal: Return the number of Subscribers to this Topic
37
47
  #
@@ -43,15 +53,7 @@ class Qup::Adapter::Kestrel
43
53
  #
44
54
  # Returns integer
45
55
  def subscriber_count
46
- c = Set.new
47
-
48
- stats['queues'].keys.each do |k|
49
- next unless k =~ %r{\A#{@name}\+}
50
- parts = k.split("+")
51
- c << parts[1]
52
- end
53
-
54
- return c.size
56
+ subscriber_names.size
55
57
  end
56
58
 
57
59
  # Internal: Publish a Message to all the Subscribers
@@ -66,6 +68,26 @@ class Qup::Adapter::Kestrel
66
68
  #######
67
69
  private
68
70
  #######
71
+
72
+ def subscribers
73
+ subs = {}
74
+ subscriber_names.each do |sub_name|
75
+ subs[sub_name] = subscriber_queue( sub_name )
76
+ end
77
+ return subs
78
+ end
79
+
80
+ def subscriber_names
81
+ names = []
82
+
83
+ stats['queues'].keys.each do |k|
84
+ next unless k =~ %r{\A#{@name}\+}
85
+ parts = k.split("+")
86
+ names << parts[1]
87
+ end
88
+
89
+ return names
90
+ end
69
91
 
70
92
  def subscriber_queue( sub_name )
71
93
  sname = subscriber_queue_name( sub_name )
@@ -21,6 +21,8 @@ class Qup::Adapter::Maildir
21
21
  @name = name
22
22
  @queue_path = @root_path + @name
23
23
  @maildir = ::Maildir.new( @queue_path, true )
24
+ # FIXME: this is a -w for Maildir issue, remove when fixed upstream
25
+ @maildir.serializer = ::Maildir::Serializer::Base.new
24
26
  end
25
27
 
26
28
 
@@ -20,9 +20,9 @@ class Qup::Adapter::Maildir
20
20
  @root_path = ::Pathname.new( root_path )
21
21
  @name = name
22
22
  @topic_path = @root_path + @name
23
- @subscribers = Hash.new
24
23
 
25
24
  FileUtils.mkdir_p( @topic_path )
25
+ @topic_path_mtime = @topic_path.mtime
26
26
  end
27
27
 
28
28
  # Internal: Destroy the Topic
@@ -31,7 +31,7 @@ class Qup::Adapter::Maildir
31
31
  #
32
32
  # Returns nothing.
33
33
  def destroy
34
- @topic_path.rmtree
34
+ @topic_path.rmtree if @topic_path.directory?
35
35
  end
36
36
 
37
37
  # Internal: Creates a Publisher for the Topic
@@ -60,7 +60,7 @@ class Qup::Adapter::Maildir
60
60
  #
61
61
  # Returns integer
62
62
  def subscriber_count
63
- @subscribers.size
63
+ subscribers.size
64
64
  end
65
65
 
66
66
  # Internal: Publish a Message to all the Subscribers
@@ -69,7 +69,7 @@ class Qup::Adapter::Maildir
69
69
  #
70
70
  # Returns nothing
71
71
  def publish( message )
72
- @subscribers.each do |name, sub|
72
+ subscribers.each do |name, sub|
73
73
  sub.produce( message )
74
74
  end
75
75
  end
@@ -77,9 +77,22 @@ class Qup::Adapter::Maildir
77
77
  #######
78
78
  private
79
79
  #######
80
+
81
+ def subscribers
82
+ subs = {}
83
+ if @topic_path.directory? then
84
+ @topic_path.each_child do |child|
85
+ if child.directory?
86
+ name = child.basename
87
+ subs[name] = sub_queue( name )
88
+ end
89
+ end
90
+ end
91
+ return subs
92
+ end
80
93
 
81
94
  def sub_queue( name )
82
- @subscribers[name] ||= ::Qup::Adapter::Maildir::Queue.new( @topic_path, name )
95
+ ::Qup::Adapter::Maildir::Queue.new( @topic_path, name )
83
96
  end
84
97
  end
85
98
  end
@@ -33,6 +33,15 @@ class Qup::Adapter::Redis
33
33
  ::Qup::Subscriber.new( self, queue )
34
34
  end
35
35
 
36
+ # Internal: Destroy the topic and all of its subscribers
37
+ #
38
+ # Returns nothing
39
+ def destroy
40
+ subscribers.each do |name, subscriber|
41
+ subscriber.destroy
42
+ end
43
+ end
44
+
36
45
  # Internal: Return the number of Subscribers to this Topic
37
46
  #
38
47
  # Returns integer
@@ -46,8 +55,8 @@ class Qup::Adapter::Redis
46
55
  #
47
56
  # Returns nothing
48
57
  def publish( message )
49
- subscribers.each do |subscriber|
50
- @client.lpush subscriber, message
58
+ subscribers.each do |name, subscriber|
59
+ subscriber.produce( message )
51
60
  end
52
61
  end
53
62
 
@@ -55,12 +64,22 @@ class Qup::Adapter::Redis
55
64
  private
56
65
  #######
57
66
 
58
- # Private: retrieve the current list of subscribers
67
+ # Private: retrieve the current list of subscriber names
59
68
  #
60
69
  # Returns an array of subscriber queue names
61
- def subscribers
70
+ def subscriber_names
62
71
  @client.smembers @name
63
72
  end
64
73
 
74
+ # Private: return the current list of subscribers
75
+ #
76
+ # Return a Hash of subscriber names and queues
77
+ def subscribers
78
+ subs = {}
79
+ subscriber_names.each do |sname|
80
+ subs[sname] = ::Qup::Adapter::Redis::Queue.new(@uri, sname, @name)
81
+ end
82
+ return subs
83
+ end
65
84
  end
66
85
  end
@@ -19,11 +19,13 @@ shared_examples Qup::Adapter do
19
19
  q = adapter.queue( 'q' )
20
20
  q.should be_kind_of( Qup::QueueAPI )
21
21
  q.name.should eq 'q'
22
+ q.destroy
22
23
  end
23
24
 
24
25
  it 'can create a QueueAPI-like object' do
25
26
  t = adapter.topic( 't' )
26
27
  t.should be_kind_of( Qup::TopicAPI )
27
28
  t.name.should eq 't'
29
+ t.destroy
28
30
  end
29
31
  end
@@ -31,17 +31,29 @@ shared_examples Qup::TopicAPI do
31
31
  describe "subscribers" do
32
32
  before do
33
33
  @subs = []
34
+ @topic2 = adapter.topic( 'topic' )
35
+
34
36
  3.times do |x|
35
- @subs << @topic.subscriber( "sub-#{x}")
37
+ @subs << @topic2.subscriber( "sub-#{x}")
36
38
  end
37
39
  end
38
40
 
39
41
  after do
40
- @subs.each { |s| s.unsubscribe }
42
+ @topic2.destroy
43
+ @topic.subscriber_count.should eq 0
41
44
  end
42
45
 
43
- it "are counted" do
44
- @topic.subscriber_count.should eq 3
46
+ it "updates the publisher with the number of subscribers" do
47
+ start_count = @topic.subscriber_count
48
+ @topic2.subscriber_count.should eq start_count
49
+ current_count = start_count
50
+
51
+ 3.times do |x|
52
+ current_count += 1
53
+ @topic2.subscriber( "sub2-#{x}" )
54
+ @topic.subscriber_count.should eq current_count
55
+ @topic2.subscriber_count.should eq current_count
56
+ end
45
57
  end
46
58
 
47
59
  it "each receives a copy of the message" do
@@ -0,0 +1,269 @@
1
+ # vim: syntax=ruby
2
+ require 'rake/clean'
3
+ require 'digest'
4
+ #------------------------------------------------------------------------------
5
+ # If you want to Develop on this project just run 'rake develop' and you'll
6
+ # have all you need to get going. If you want to use bundler for development,
7
+ # then run 'rake develop:using_bundler'
8
+ #------------------------------------------------------------------------------
9
+ namespace :develop do
10
+
11
+ # Install all the development and runtime dependencies of this gem using the
12
+ # gemspec.
13
+ task :default do
14
+ require 'rubygems/dependency_installer'
15
+ installer = ::Gem::DependencyInstaller.new
16
+
17
+ This.set_coverage_gem
18
+
19
+ puts "Installing gem depedencies needed for development"
20
+ This.platform_gemspec.dependencies.each do |dep|
21
+ if dep.matching_specs.empty? then
22
+ puts "Installing : #{dep}"
23
+ installer.install dep
24
+ else
25
+ puts "Skipping : #{dep} -> already installed #{dep.matching_specs.first.full_name}"
26
+ end
27
+ end
28
+ puts "\n\nNow run 'rake test'"
29
+ end
30
+
31
+ # Create a Gemfile that just references the gemspec
32
+ file 'Gemfile' => :gemspec do
33
+ File.open( "Gemfile", "w+" ) do |f|
34
+ f.puts 'source "https://rubygems.org/"'
35
+ f.puts 'gemspec'
36
+ end
37
+ end
38
+
39
+ desc "Create a bundler Gemfile"
40
+ task :using_bundler => 'Gemfile' do
41
+ puts "Now you can 'bundle'"
42
+ end
43
+
44
+ # Gemfiles are build artifacts
45
+ CLOBBER << FileList['Gemfile*']
46
+ end
47
+ desc "Boostrap development"
48
+ task :develop => "develop:default"
49
+
50
+ #------------------------------------------------------------------------------
51
+ # RSpec - standard RSpec rake task
52
+ #------------------------------------------------------------------------------
53
+ begin
54
+ require 'rspec/core/rake_task'
55
+ RSpec::Core::RakeTask.new( :test ) do |t|
56
+ t.ruby_opts = %w[ -w ]
57
+ t.rspec_opts = %w[ --color --format documentation ]
58
+ end
59
+ task :default => :test
60
+ rescue LoadError
61
+ Util.task_warning( 'test' )
62
+ end
63
+
64
+
65
+ #------------------------------------------------------------------------------
66
+ # RDoc - standard rdoc rake task, although we must make sure to use a more
67
+ # recent version of rdoc since it is the one that has 'tomdoc' markup
68
+ #------------------------------------------------------------------------------
69
+ begin
70
+ gem 'rdoc' # otherwise we get the wrong task from stdlib
71
+ require 'rdoc/task'
72
+ RDoc::Task.new do |t|
73
+ t.markup = 'tomdoc'
74
+ t.rdoc_dir = 'doc'
75
+ t.main = 'README.md'
76
+ t.title = "#{This.name} #{This.version}"
77
+ t.rdoc_files.include( FileList['*.{rdoc,md,txt}'], FileList['ext/**/*.c'],
78
+ FileList['lib/**/*.rb'] )
79
+ end
80
+ rescue StandardError, LoadError
81
+ This.task_warning( 'rdoc' )
82
+ end
83
+
84
+ #------------------------------------------------------------------------------
85
+ # Coverage - optional code coverage, rcov for 1.8 and simplecov for 1.9, so
86
+ # for the moment only rcov is listed.
87
+ #------------------------------------------------------------------------------
88
+ if RUBY_VERSION < "1.9.0"
89
+ begin
90
+ require 'rcov/rcovtask'
91
+ Rcov::RcovTask.new( 'coverage' ) do |t|
92
+ t.libs << 'spec'
93
+ t.pattern = 'spec/**/*_spec.rb'
94
+ t.verbose = true
95
+ t.rcov_opts << "-x ^/" # remove all the global files
96
+ t.rcov_opts << "--sort coverage" # so we see the worst files at the top
97
+ end
98
+ rescue LoadError
99
+ This.task_warning( 'rcov' )
100
+ end
101
+ else
102
+ begin
103
+ require 'simplecov'
104
+ desc 'Run tests with code coverage'
105
+ task :coverage do
106
+ ENV['COVERAGE'] = 'true'
107
+ Rake::Task[:test].execute
108
+ end
109
+ CLOBBER << FileList["coverage"]
110
+ rescue LoadError
111
+ This.task_warning( 'simplecov' )
112
+ end
113
+ end
114
+
115
+ #------------------------------------------------------------------------------
116
+ # Manifest - We want an explicit list of thos files that are to be packaged in
117
+ # the gem. Most of this is from Hoe.
118
+ #------------------------------------------------------------------------------
119
+ namespace 'manifest' do
120
+ desc "Check the manifest"
121
+ task :check => :clean do
122
+ files = FileList["**/*", ".*"].exclude( This.exclude_from_manifest ).to_a.sort
123
+ files = files.select{ |f| File.file?( f ) }
124
+
125
+ tmp = "Manifest.tmp"
126
+ File.open( tmp, 'w' ) do |f|
127
+ f.puts files.join("\n")
128
+ end
129
+
130
+ begin
131
+ sh "diff -du Manifest.txt #{tmp}"
132
+ ensure
133
+ rm tmp
134
+ end
135
+ puts "Manifest looks good"
136
+ end
137
+
138
+ desc "Generate the manifest"
139
+ task :generate => :clean do
140
+ files = %x[ git ls-files ].split("\n").sort
141
+ files.reject! { |f| f =~ This.exclude_from_manifest }
142
+ File.open( "Manifest.txt", "w" ) do |f|
143
+ f.puts files.join("\n")
144
+ end
145
+ end
146
+ end
147
+
148
+ #------------------------------------------------------------------------------
149
+ # Fixme - look for fixmes and report them
150
+ #------------------------------------------------------------------------------
151
+ namespace :fixme do
152
+ task :default => 'manifest:check' do
153
+ This.manifest.each do |file|
154
+ next if file == __FILE__
155
+ next unless file =~ %r/(txt|rb|md|rdoc|css|html|xml|css)\Z/
156
+ puts "FIXME: Rename #{file}" if file =~ /fixme/i
157
+ IO.readlines( file ).each_with_index do |line, idx|
158
+ prefix = "FIXME: #{file}:#{idx+1}".ljust(42)
159
+ puts "#{prefix} => #{line.strip}" if line =~ /fixme/i
160
+ end
161
+ end
162
+ end
163
+
164
+ def fixme_project_root
165
+ This.project_path( '../fixme' )
166
+ end
167
+
168
+ def fixme_project_path( subtree )
169
+ fixme_project_root.join( subtree )
170
+ end
171
+
172
+ def local_fixme_files
173
+ This.manifest.select { |p| p =~ %r|^tasks/| }
174
+ end
175
+
176
+ def outdated_fixme_files
177
+ local_fixme_files.reject do |local|
178
+ upstream = fixme_project_path( local )
179
+ Digest::SHA256.file( local ) == Digest::SHA256.file( upstream )
180
+ end
181
+ end
182
+
183
+ def fixme_up_to_date?
184
+ outdated_fixme_files.empty?
185
+ end
186
+
187
+ desc "See if the fixme tools are outdated"
188
+ task :outdated => :release_check do
189
+ if fixme_up_to_date? then
190
+ puts "Fixme files are up to date."
191
+ else
192
+ outdated_fixme_files.each do |f|
193
+ puts "#{f} is outdated"
194
+ end
195
+ end
196
+ end
197
+
198
+ desc "Update outdated fixme files"
199
+ task :update => :release_check do
200
+ if fixme_up_to_date? then
201
+ puts "Fixme files are already up to date."
202
+ else
203
+ puts "Updating fixme files:"
204
+ outdated_fixme_files.each do |local|
205
+ upstream = fixme_project_path( local )
206
+ puts " * #{local}"
207
+ FileUtils.cp( upstream, local )
208
+ end
209
+ puts "Use your git commands as appropriate."
210
+ end
211
+ end
212
+ end
213
+ desc "Look for fixmes and report them"
214
+ task :fixme => "fixme:default"
215
+
216
+ #------------------------------------------------------------------------------
217
+ # Gem Specification
218
+ #------------------------------------------------------------------------------
219
+ # Really this is only here to support those who use bundler
220
+ desc "Build the #{This.name}.gemspec file"
221
+ task :gemspec do
222
+ File.open( This.gemspec_file, "wb+" ) do |f|
223
+ f.write This.platform_gemspec.to_ruby
224
+ end
225
+ end
226
+
227
+ # the gemspec is also a dev artifact and should not be kept around.
228
+ CLOBBER << This.gemspec_file.to_s
229
+
230
+ # .rbc files from ruby 2.0
231
+ CLOBBER << FileList["**/*.rbc"]
232
+
233
+ # The standard gem packaging task, everyone has it.
234
+ require 'rubygems/package_task'
235
+ ::Gem::PackageTask.new( This.platform_gemspec ) do
236
+ # nothing
237
+ end
238
+
239
+ #------------------------------------------------------------------------------
240
+ # Release - the steps we go through to do a final release, this is pulled from
241
+ # a compbination of mojombo's rakegem, hoe and hoe-git
242
+ #
243
+ # 1) make sure we are on the master branch
244
+ # 2) make sure there are no uncommitted items
245
+ # 3) check the manifest and make sure all looks good
246
+ # 4) build the gem
247
+ # 5) do an empty commit to have the commit message of the version
248
+ # 6) tag that commit as the version
249
+ # 7) push master
250
+ # 8) push the tag
251
+ # 7) pus the gem
252
+ #------------------------------------------------------------------------------
253
+ task :release_check do
254
+ unless `git branch` =~ /^\* master$/
255
+ abort "You must be on the master branch to release!"
256
+ end
257
+ unless `git status` =~ /^nothing to commit/m
258
+ abort "Nope, sorry, you have unfinished business"
259
+ end
260
+ end
261
+
262
+ desc "Create tag v#{This.version}, build and push #{This.platform_gemspec.full_name} to rubygems.org"
263
+ task :release => [ :release_check, 'manifest:check', :gem ] do
264
+ sh "git commit --allow-empty -a -m 'Release #{This.version}'"
265
+ sh "git tag -a -m 'v#{This.version}' v#{This.version}"
266
+ sh "git push origin master"
267
+ sh "git push origin v#{This.version}"
268
+ sh "gem push pkg/#{This.platform_gemspec.full_name}.gem"
269
+ end