shooting_star 3.1.0 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,8 +1,29 @@
1
+ *** 3.2.0 / 2007-08-09
2
+ + 3 major enhancements:
3
+ + Automatic activation/deactivation of flash client.
4
+ + Ported meteor_strike.swf from CS3 to mtasc.
5
+ + Heart beat.
6
+ - Serialized event execution.
7
+ + 7 minor enhancements:
8
+ + shooting_star.yml is processed by ERB.
9
+ + Updated implementation of observers.
10
+ + Updated chat generator with connection notification.
11
+ + Added sample chat observer script (see script/chat_observer).
12
+ + Separated views of xhr and flash html into views/.
13
+ + Added an event which will fire when connection is established.
14
+ + Added debugging mode to meteor_strike.
15
+ + 6 minor spec changes:
16
+ + Changed default pid file location from log/ to tmp/pids/.
17
+ + options[:connected] obsoleted. Please use options[:event] instead.
18
+ + Became independent from alias_method_chain in order to support applications
19
+ which use older versions (<1.2) of ruby on rails.
20
+ + Added :debug option to meteor_strike helper.
21
+ + Added :heartbeart option to meteor_strike helper.
22
+ + Added :noflash option to meteor_strike helper.
23
+
1
24
  *** 3.1.0 / 2007-07-18
2
25
  + 1 major enhancement:
3
26
  + Added 2 meteor_strike functions 'tuneInOut' and 'tuneOutIn'.
4
- - Automatic activation/deactivation of flash client.
5
- - Serialized event execution.
6
27
 
7
28
  + 1 minor enhancement:
8
29
  + FileUtil is used instead of `shell command`.
data/Manifest.txt CHANGED
@@ -24,6 +24,11 @@ vendor/plugins/meteor_strike/Rakefile
24
24
  vendor/plugins/meteor_strike/init.rb
25
25
  vendor/plugins/meteor_strike/lib
26
26
  vendor/plugins/meteor_strike/lib/meteor_strike.rb
27
+ vendor/plugins/meteor_strike/lib/meteor_strike
28
+ vendor/plugins/meteor_strike/lib/meteor_strike/helper.rb
29
+ vendor/plugins/meteor_strike/views
30
+ vendor/plugins/meteor_strike/views/xhr.rhtml
31
+ vendor/plugins/meteor_strike/views/flash.rhtml
27
32
  vendor/plugins/meteor_strike/test
28
33
  vendor/plugins/meteor_strike/test/meteor_strike_test.rb
29
34
  vendor/plugins/meteor_strike/generators
data/Rakefile CHANGED
@@ -37,7 +37,7 @@ desc 'default gem task.'
37
37
  task :gem => 'gem:default'
38
38
 
39
39
  desc 'update generator template files.'
40
- task 'update_generator_template_files' do
40
+ task 'update_generator_template_files' => :swf do
41
41
  templates = 'vendor/plugins/meteor_strike/generators/meteor/templates'
42
42
  cp 'app/controllers/meteor_controller.rb',
43
43
  templates + '/controller.rb'
@@ -51,7 +51,21 @@ task 'update_generator_template_files' do
51
51
  templates + '/unit_test.rb'
52
52
  cp 'test/functional/meteor_controller_test.rb',
53
53
  templates + '/functional_test.rb'
54
+ cp 'public/meteor_strike.swf',
55
+ templates + '/meteor_strike.swf'
54
56
  end
55
57
 
56
58
  desc 'test all tests'
57
59
  task 'test:all' => [:test, 'test:plugins', 'test:exts', 'test:libs']
60
+
61
+ desc 'default task'
62
+ task :default => 'test:all'
63
+
64
+ desc 'make swf file'
65
+ task :swf => 'public/meteor_strike.swf'
66
+
67
+ file 'public/meteor_strike.swf' => 'as/meteor_strike.as' do
68
+ sh ['mtasc -version 6 -header 300:300:30',
69
+ '-swf public/meteor_strike.swf',
70
+ '-main as/meteor_strike.as'].join(' ')
71
+ end
data/lib/shooting_star.rb CHANGED
@@ -4,14 +4,15 @@ require 'drb/drb'
4
4
  require 'yaml'
5
5
  require 'ftools'
6
6
  require 'fileutils'
7
+ require 'erb'
7
8
  require 'shooting_star/config'
8
9
  require 'shooting_star/shooter'
9
10
 
10
11
  module ShootingStar
11
- VERSION = '3.1.0'
12
+ VERSION = '3.2.0'
12
13
  CONFIG = Config.new(
13
14
  :config => 'config/shooting_star.yml',
14
- :pid_file => 'log/shooting_star.pid',
15
+ :pid_file => 'tmp/pids/shooting_star.pid',
15
16
  :log_file => 'log/shooting_star.log',
16
17
  :daemon => false,
17
18
  :slient => false,
@@ -24,12 +25,14 @@ module ShootingStar
24
25
  @log_file = nil
25
26
  end
26
27
  config_file = options[:config] || CONFIG.config
27
- CONFIG.merge!(YAML.load_file(config_file)) if File.exist?(config_file)
28
+ if File.exist?(config_file)
29
+ CONFIG.merge!(YAML.load(ERB.new(open(config_file).read).result))
30
+ end
28
31
  CONFIG.merge!(options)
29
32
  end
30
33
 
31
34
  def self.shooter
32
- @@shooter ||= DRbObject.new_with_uri(CONFIG.shooter.uri)
35
+ @@shooter ||= DRb.start_service && DRbObject.new(nil, CONFIG.shooter.uri)
33
36
  end
34
37
 
35
38
  # install config file and plugin
@@ -48,6 +51,8 @@ module ShootingStar
48
51
  end
49
52
  log_dir = File.join(base_dir, 'log')
50
53
  FileUtils.mkdir_p(log_dir) unless File.exist?(log_dir)
54
+ pid_dir = File.join(base_dir, 'tmp/pids')
55
+ FileUtils.mkdir_p(pid_dir) unless File.exist?(pid_dir)
51
56
  plugin_dir = File.join(base_dir, 'vendor/plugins')
52
57
  FileUtils.mkdir_p(plugin_dir) unless File.exist?(plugin_dir)
53
58
  meteor_strike_dir = File.join(plugin_dir, 'meteor_strike')
@@ -84,7 +89,7 @@ module ShootingStar
84
89
  @log_file.close if @log_file
85
90
  end
86
91
  log "shooting_star service started."
87
- Process.kill(:ALRM, Process.ppid) if CONFIG.daemon
92
+ Process.kill(:ALRM, Process.ppid) rescue nil if CONFIG.daemon
88
93
  block.call if block
89
94
  end
90
95
  end
@@ -111,7 +116,7 @@ module ShootingStar
111
116
  end
112
117
 
113
118
  def self.report
114
- puts "#{'-' * 79}\nconnections channel name\n#{'-' * 79}"
119
+ puts "#{'-' * 79}\nconnections channel_name\n#{'-' * 79}"
115
120
  total_connections = 0
116
121
  shooter.channels.each do |channel_path|
117
122
  count = shooter.count(channel_path)
@@ -121,6 +126,13 @@ module ShootingStar
121
126
  total_connections += count
122
127
  end
123
128
  puts "#{'-' * 79}\n%11d %s\n#{'-' * 79}" % [total_connections, 'TOTAL']
129
+ total_observers = 0
130
+ puts "observers channel_name\n#{'-' * 79}"
131
+ shooter.observers.each do |channel_path, observers|
132
+ puts "%11d %s" % [observers.size, channel_path]
133
+ total_observers += observers.size
134
+ end
135
+ puts "#{'-' * 79}\n%11d %s\n#{'-' * 79}" % [total_observers, 'TOTAL']
124
136
  end
125
137
 
126
138
  def self.timestamp
@@ -1,23 +1,22 @@
1
- require 'set'
2
-
3
1
  module ShootingStar
4
2
  class Channel
5
3
  attr_reader :path, :waiters
6
4
  @@channels = {}
5
+ @@observers = Hash.new{|h,k| h[k] = Hash.new}
7
6
 
8
7
  def initialize(channel_path)
9
8
  @path = channel_path
10
9
  @waiters = Hash.new
11
- @observers = Set.new
12
10
  @@channels[path] = self
13
11
  end
14
12
 
15
13
  def transmit(id, params)
16
14
  if event = params[:event]
17
- @observers.each do |obs|
15
+ observers = @@observers.has_key?(@path) ? @@observers[@path].dup : nil
16
+ observers.each do |name, obs|
18
17
  begin obs.__send__(event, params) if obs.respond_to?(event)
19
- rescue Exception; @observers.delete(obs) end
20
- end
18
+ rescue Exception; Channel.ignore(@path, name) end
19
+ end if observers
21
20
  end
22
21
  @waiters.each do |signature, server|
23
22
  server.commit if server.respond(id, params)
@@ -30,12 +29,19 @@ module ShootingStar
30
29
  end
31
30
 
32
31
  def leave(server) @waiters.delete(server.signature) end
33
- def observe(observer) @observers << observer end
34
- def ignore(observer) @observers.delete(observer) end
35
-
36
- def self.[](channel_path); @@channels[channel_path] end
32
+ def self.[](channel_path) @@channels[channel_path] end
37
33
  def self.list; @@channels.keys end
38
34
  def self.sweep; @@channels.delete_if{|k,v| v.waiters.empty?} end
35
+ def self.observers; @@observers end
36
+
37
+ def self.observe(channel_path, observer)
38
+ @@observers[channel_path][observer.name] = observer
39
+ end
40
+
41
+ def self.ignore(channel_path, name)
42
+ @@observers[channel_path].delete(name)
43
+ @@observers.delete(channel_path) if @@observers[channel_path].empty?
44
+ end
39
45
 
40
46
  def self.cleanup(channel_path)
41
47
  if @@channels[channel_path] && @@channels[channel_path].waiters.empty?
@@ -25,6 +25,7 @@ module ShootingStar
25
25
 
26
26
  # receive the data sent from client.
27
27
  def receive_data(data)
28
+ return if data.length == 0
28
29
  return send_policy_file if @data.length == 0 &&
29
30
  data == "<policy-file-request/>"
30
31
  @data += data
@@ -64,7 +65,7 @@ module ShootingStar
64
65
  @uid = @@uids[@signature] ||= @params['uid']
65
66
  @tag = @@tags[@signature] ||=
66
67
  (@params['tag'] || '').split(',').map{|i| CGI.unescape(i)}
67
- unless @@servers[@signature] || @type == 'rc'
68
+ if !@@servers[@signature] && @type != 'rc'
68
69
  notify(:event => :enter, :uid => @uid, :tag => @tag)
69
70
  log "Connected: #{@uid}"
70
71
  end
@@ -165,6 +166,8 @@ module ShootingStar
165
166
  event_id = ShootingStar::timestamp
166
167
  log "Event(#{event_id}): #{@channel_path}:#{params.inspect}"
167
168
  Channel[@channel_path].transmit("event-#{event_id}", params)
169
+ rescue Exception => e
170
+ log "ERROR: #{e.message}\n#{e.backtrace.join("\n")}"
168
171
  end
169
172
 
170
173
  # wait for commands or events until they occur. if they're already in
@@ -194,7 +197,7 @@ module ShootingStar
194
197
  query += "&" + FormEncoder.encode(params) if params
195
198
  @execution += <<-"EOH"
196
199
  (function(){
197
- var ms1 = document.getElementById('meteor-strike-1');
200
+ var ms1 = document.getElementById('meteor-strike-1-form');
198
201
  var box = ms1 ? ms1.parentNode : document.body;
199
202
  var iframe = document.createElement('iframe');
200
203
  var remove = function(){box.removeChild(iframe)};
@@ -214,9 +217,16 @@ module ShootingStar
214
217
  assets = URI.parse(@params['execute'])
215
218
  assets.path = '/javascripts/prototype.js'
216
219
  assets.query = assets.fragment = nil
220
+ query = @query.sub(%r[\&sig=\d+], '')
221
+ query += "&" + FormEncoder.encode(:event => :init, :type => :xhr)
222
+ heartbeat = @params['heartbeat'].to_i
217
223
  send_data "HTTP/1.1 200 OK\nContent-Type: text/html\n\n" +
218
224
  <<-"EOH"
219
- <html><head><script type="text/javascript" src="#{assets}"></script>
225
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
226
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
227
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"><head>
228
+ <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
229
+ <script type="text/javascript" src="#{assets}"></script>
220
230
  <script type="text/javascript">
221
231
  //<![CDATA[
222
232
  var connect = function(reconnect)
@@ -232,10 +242,15 @@ module ShootingStar
232
242
  request.transport.abort();
233
243
  };
234
244
  Event.observe(window, 'unload', disconnect);
245
+ if(#{heartbeat} > 0) setTimeout(function(){
246
+ if(request.transport.readyState == 1) disconnect(), connect(true);
247
+ }, [#{heartbeat}, 60].max() * 1000);
235
248
  };
236
249
  setTimeout(function(){connect(false)}, 0);
237
250
  //]]>
238
- </script></head><body></body></html>
251
+ </script></head><body>
252
+ <iframe src="#{@params['execute']}/0?#{query}"></iframe>
253
+ </body></html>
239
254
  EOH
240
255
  rescue Exception
241
256
  ensure
@@ -3,11 +3,11 @@ require 'shooting_star/channel'
3
3
  # DRbObject
4
4
  module ShootingStar
5
5
  class Shooter
6
- # broadcast message
7
- def shoot(channel_path, id, tag)
6
+ # broadcast/multicast message
7
+ def shoot(channel_path, id, tag, options = {})
8
8
  return unless Channel[channel_path]
9
- log "Shot: #{channel_path}:#{id}:#{tag.join(',')}"
10
- Channel[channel_path].transmit(id, :tag => tag)
9
+ log "Shot: #{channel_path}:#{id}:#{tag.join(',')}:#{options}"
10
+ Channel[channel_path].transmit(id, options.merge(:tag => tag))
11
11
  end
12
12
 
13
13
  # update client properties
@@ -18,6 +18,7 @@ module ShootingStar
18
18
 
19
19
  def signature; ShootingStar::timestamp end
20
20
  def channels; Channel.list end
21
+ def observers; Channel.observers end
21
22
  def sweep; Channel.sweep end
22
23
 
23
24
  # count up listeners
@@ -56,12 +57,8 @@ module ShootingStar
56
57
 
57
58
  # observe server side events
58
59
  def observe(channel_path, observer)
59
- Channel[channel_path].observe(observer)
60
- end
61
-
62
- # ignore server side events
63
- def ignore(channel_path, observer)
64
- Channel[channel_path].ignore(observer)
60
+ Channel.observe(channel_path, observer)
61
+ rescue Exception
65
62
  end
66
63
 
67
64
  private
@@ -6,14 +6,16 @@ require 'thread'
6
6
  $command_line = 'echo "testing"'
7
7
 
8
8
  class ShootingStarTest < Test::Unit::TestCase
9
- module TestObserver
10
- def self.enter(params) @params = params end
11
- def self.params; @params end
9
+ class TestObserver
10
+ include DRb::DRbUndumped
11
+ def name; 'test-observer' end
12
+ def enter(params) @params = params end
13
+ def params; @params end
12
14
  end
13
15
 
14
16
  def setup
15
17
  @config = ShootingStar.configure :silent => true,
16
- :pid_file => 'log/shooting_star.test.pid',
18
+ :pid_file => 'tmp/pids/shooting_star.test.pid',
17
19
  :log_file => 'log/shooting_star.test.log',
18
20
  :server => {:host => '127.0.0.1', :port => 8081},
19
21
  :shooter => {:uri => 'druby://127.0.0.1:7124'}
@@ -69,22 +71,25 @@ class ShootingStarTest < Test::Unit::TestCase
69
71
  assert_not_nil client2
70
72
  shooter = DRbObject.new_with_uri('druby://127.0.0.1:7124')
71
73
  assert_not_nil shooter
72
- shooter.observe('test/channel', TestObserver)
74
+ observer = TestObserver.new
75
+ assert_not_nil observer
76
+ shooter.observe('test/channel', observer)
73
77
  mutex = Mutex.new
74
78
  mutex.lock
75
- assert_nil TestObserver.params
79
+ assert_nil observer.params
76
80
  Thread.new do
77
81
  send(client1, "POST", "test/channel", "#{@query}&__t__=c")
78
82
  mutex.unlock
79
83
  end
80
84
  mutex.lock
81
- assert_nil TestObserver.params
85
+ assert_nil observer.params
82
86
  Thread.new do
83
87
  send(client2, "POST", "test/channel", "#{@query2}&__t__=c")
84
88
  mutex.unlock
85
89
  end
86
90
  mutex.lock
87
- assert_equal :enter, TestObserver.params[:event]
91
+ assert_not_nil observer.params
92
+ assert_equal :enter, observer.params[:event]
88
93
  assert_not_nil result1 = client1.read
89
94
  assert_not_nil result1.index('meteor/strike/event-')
90
95
  shooter.shoot("test/channel", 12, [])
@@ -21,4 +21,12 @@ class <%= class_name %>Controller < ApplicationController
21
21
  page[:chat_message].focus
22
22
  end
23
23
  end
24
+
25
+ def connect
26
+ @chat = <%= class_name %>.new(
27
+ :name => '(* system *)',
28
+ :created_at => Time.now,
29
+ :message => "connection established on #{params[:client_type]}.")
30
+ render :action => 'show'
31
+ end
24
32
  end
@@ -8,4 +8,11 @@
8
8
  <%%= render_component :action => 'show', :id => chat.id %>
9
9
  <%% end %>
10
10
  </ul>
11
- <%%= meteor_strike '<%= file_name %>' %>
11
+ <%%= meteor_strike '<%= file_name %>', :event => %Q{
12
+ switch(params.event){
13
+ case 'init':
14
+ new Ajax.Updater('chat-list', #{url_for(:action => 'connect').to_json}, {
15
+ insertion: Insertion.Top, parameters: {client_type: params.type}});
16
+ break;
17
+ }
18
+ } %>
@@ -1,22 +1,31 @@
1
1
  require 'drb/drb'
2
2
 
3
3
  class Meteor < ActiveRecord::Base
4
+ DEFAULT_SERVER_URI = 'localhost:8080'
5
+ DEFAULT_SHOOTER_URI = 'druby://localhost:7123'
6
+
4
7
  class Shooter
5
8
  COUNTERS = [:count, :count_with]
6
9
  LISTINGS = [:listeners, :listeners_with, :channels, :signatures]
10
+ EITHEROFS = [:signature]
11
+ MAX_RETRY = 10
7
12
 
8
13
  def initialize(config)
9
- config['shooting_star'] ||= {'shooter' => 'druby://localhost:7123'}
14
+ config['shooting_star'] ||= {'shooter' => Meteor::DEFAULT_SHOOTER_URI}
10
15
  uris = config['shooting_star']['shooter']
11
- @shooters = [uris].flatten.map{|uri| DRbObject.new_with_uri(uri)}
16
+ @shooters = [uris].flatten.map{|uri| DRbObject.new(nil, uri)}
12
17
  end
13
18
 
14
19
  COUNTERS.each do |m|
15
- define_method(m){|*a| call(m, *a).inject(0){|r,c| r += c}}
20
+ eval "def #{m}(*a, &b) call('#{m}',*a,&b).inject(0){|r,c| r+=c} end"
16
21
  end
17
22
 
18
23
  LISTINGS.each do |m|
19
- define_method(m){|*a| call(m, *a).inject([]){|r,c| r.concat c}}
24
+ eval "def #{m}(*a, &b) call('#{m}',*a,&b).inject([]){|r,c|r.concat c} end"
25
+ end
26
+
27
+ EITHEROFS.each do |m|
28
+ eval "def #{m}(*a, &b) round_robin('#{m}',*a,&b) end"
20
29
  end
21
30
 
22
31
  def method_missing(method, *args, &block)
@@ -29,21 +38,44 @@ class Meteor < ActiveRecord::Base
29
38
  begin
30
39
  result << shooter.__send__(method, *args, &block)
31
40
  rescue Exception => e
32
- Meteor.logger.error "#{e.message}\n#{e.backtrace.join("\n")}"
41
+ Meteor::logger.error "#{e.message}\n#{e.backtrace.join("\n")}"
33
42
  end
34
43
  result
35
44
  end || []
36
45
  end
46
+
47
+ def round_robin(method, *args, &block)
48
+ @round_robin_counter = (@round_robin_counter.to_i + 1) % @shooters.size
49
+ @shooters[@round_robin_counter].__send__(method, *args, &block)
50
+ rescue Exception
51
+ (@retry_counter = @retry_counter.to_i + 1) <= MAX_RETRY ? retry : raise
52
+ end
53
+ end
54
+
55
+ def self.config
56
+ returning(configurations[RAILS_ENV]['shooting_star'] || {}) do |config|
57
+ config['server'] ||= DEFAULT_SERVER_URI
58
+ config['shooter'] ||= DEFAULT_SHOOTER_URI
59
+ end
60
+ end
61
+
62
+ def self.servers
63
+ config['server'].kind_of?(Array) ? config['server'] : [config['server']]
64
+ end
65
+
66
+ def self.server
67
+ @round_robin_counter = (@round_robin_counter.to_i + 1) % servers.size
68
+ servers[@round_robin_counter]
37
69
  end
38
70
 
39
71
  def self.shooter
40
- @@shooter ||= ::Meteor::Shooter.new(configurations[RAILS_ENV])
72
+ @@shooter ||= DRb.start_service && Shooter.new(configurations[RAILS_ENV])
41
73
  end
42
74
 
43
- def self.shoot(channel, javascript, tag = [])
75
+ def self.shoot(channel_path, javascript, tag = [], options = {})
44
76
  meteor = Meteor.new(:javascript => javascript)
45
77
  returning(meteor.save) do |succeeded|
46
- shooter.shoot(channel, meteor.id, tag) if succeeded
78
+ shooter.shoot(channel_path, meteor.id, tag, options) if succeeded
47
79
  end
48
80
  end
49
81
  end
@@ -1,16 +1,19 @@
1
- <html>
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
2
4
  <head>
3
- <%= javascript_tag %Q{
4
- (function(){
5
- var channel = #{@channel.to_json};
6
- var javascript = #{@javascript.to_json};
7
- var execute = function(){
8
- var meteorStrike = parent.parent.meteorStrike[channel];
9
- if(meteorStrike) meteorStrike.execute(javascript);
10
- else setTimeout(execute, 0);
11
- }
12
- execute();
13
- })();
14
- }%>
5
+ <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
6
+ <%= javascript_tag %Q{
7
+ (function(){
8
+ var channel = #{@channel.to_json};
9
+ var javascript = #{@javascript.to_json};
10
+ var execute = function(){
11
+ var ms = parent.parent.meteorStrike[channel];
12
+ if(ms) ms.execute(javascript);
13
+ else setTimeout(execute, 0);
14
+ };
15
+ execute();
16
+ })();
17
+ }%>
15
18
  </head>
16
19
  </html>
@@ -1,172 +1,5 @@
1
- require 'uri'
2
- require 'md5'
1
+ require 'meteor_strike/helper'
3
2
 
4
3
  module MeteorStrike
5
- module Helper
6
- def self.included(base)
7
- base.class_eval do
8
- alias_method_chain :form_tag, :timestamp
9
- end
10
- end
11
-
12
- def meteor_strike(channel, options = {})
13
- if !options[:cache] && !@meteor_strike
14
- cc = controller.headers['Cache-Control'] || ''
15
- cc += ', ' unless cc.empty?
16
- cc += 'no-store, no-cache, must-revalidate, max-age=0, '
17
- cc += 'post-check=0, pre-check=0'
18
- controller.headers['Cache-Control'] = cc
19
- end
20
- @meteor_strike ||= 0 and @meteor_strike += 1
21
- config = ActiveRecord::Base.configurations[RAILS_ENV]['shooting_star']
22
- config ||= {}
23
- config['server'] ||= 'localhost:8080'
24
- config['shooter'] ||= 'druby://localhost:7123'
25
- server = config['server'].kind_of?(Array) ?
26
- config['server'][rand(config['server'].length)] : config['server']
27
- shooting_star_uri = "#{server}/#{channel}"
28
- if config['random_subdomain'] && /[A-z]/ === server
29
- subdomain = (1..6).map{(rand(26)+?a).chr}.to_s
30
- shooting_star_uri = [subdomain, shooting_star_uri].join('.')
31
- end
32
- uri = url_for(:only_path => false).split('/')[0..2].join('/')
33
- uid = options[:uid] ? CGI.escape(options[:uid].to_s) : ''
34
- tags = options[:tag] || []
35
- tag = tags.map{|i| CGI.escape(i.to_s)}.join(',')
36
- update_uri = "#{uri}/meteor/update"
37
- sig = Meteor.shooter.signature
38
- iframe_id = "meteor-strike-#{@meteor_strike}"
39
- host_port = (server.split(':') << '80')[0..1].join(':')
40
- flash_vars = [
41
- "channel=#{channel}", "tag=#{tag}", "uid=#{uid}", "sig=#{sig}",
42
- "base_uri=#{uri}", "server=#{host_port}"].join('&')
43
- flash_html = flash_tag(flash_vars) unless options[:noflash]
44
- <<-"EOH"
45
- <div style="position: absolute; top: -99999px; left: -99999px">
46
- <iframe id="#{iframe_id}" name="#{iframe_id}"></iframe>
47
- <form id="#{iframe_id}-form" target="#{iframe_id}" method="POST"
48
- action="http://#{shooting_star_uri}">
49
- <input name="execute" value="#{uri}/meteor/strike" />
50
- <input name="tag" /><input name="uid" /><input name="sig" />
51
- </form>
52
- <script type="text/javascript">
53
- //<![CDATA[
54
- var meteorStrike = meteorStrike || $H();
55
- Event.observe(window, 'load', function(){
56
- var channel = #{channel.to_json};
57
- var UID = #{uid.to_json}, TAGS = #{tags.to_json};
58
- var encodeTags = function(tags){
59
- var encode = function(i){return encodeURIComponent(i)};
60
- return $A(tags).uniq().map(encode).join(',');
61
- };
62
- var ms = meteorStrike[channel] = meteorStrike[channel] || new Object;
63
- ms.getTags = function(){return TAGS};
64
- ms.getUid = function(){return UID};
65
- ms.execute = function(js){eval(js)};
66
- ms.event = function(params){#{options[:event]}};
67
- ms.update = function(uid, tags){
68
- new Ajax.Request(#{update_uri.to_json}, {postBody: $H({
69
- channel: channel, uid: uid || UID,
70
- tag: encodeTags(tags || TAGS), sig: #{sig.to_json}
71
- }).toQueryString(), asynchronous: true});
72
- UID = uid, TAGS = tags;
73
- };
74
- ms.tuneIn = function(tags){
75
- ms.update(UID, TAGS.concat(tags || []).uniq());
76
- };
77
- ms.tuneOut = function(tags){
78
- ms.update(UID, Array.prototype.without.apply(TAGS, tags));
79
- };
80
- ms.tuneInOut = function(tagsIn, tagsOut){
81
- var tags = TAGS.concat(tagsIn || []).uniq();
82
- ms.update(UID, Array.prototype.without.apply(tags, tagsOut));
83
- };
84
- ms.tuneOutIn = function(tagsOut, tagsIn){
85
- var tags = Array.prototype.without.apply(TAGS, tagsOut);
86
- ms.update(UID, tags.concat(tagsIn || []).uniq());
87
- };
88
- try{
89
- var noflash = #{options[:noflash].to_json};
90
- if(noflash || !flashVersion || flashVersion < 6){
91
- setTimeout(function(){
92
- var form = $("#{iframe_id}-form");
93
- form.uid.value = #{uid.to_json};
94
- form.tag.value = #{tag.to_json};
95
- form.sig.value = #{sig.to_json};
96
- form.submit();
97
- setTimeout(function(){#{options[:connected]}}, 0);
98
- }, 0);
99
- }
100
- }catch(e){}
101
- });
102
- function meteor_strike_#{@meteor_strike}_DoFSCommand(command, args){
103
- switch(command){
104
- case 'execute': eval(args); break;
105
- case 'event':
106
- if(args == 'connect') (function(){#{options[:connected]}})();
107
- break;
108
- }
109
- }
110
- if(navigator.appName && navigator.appName.indexOf("Microsoft") != -1 &&
111
- navigator.userAgent.indexOf("Windows") != -1 &&
112
- navigator.userAgent.indexOf("Windows 3.1") == -1)
113
- {
114
- document.write([
115
- '<script language="VBScript"\\>',
116
- 'On Error Resume Next',
117
- ['Sub meteor_strike_', #{@meteor_strike},
118
- '_FSCommand(ByVal command, ByVal args)'].join(''),
119
- [' Call meteor_strike_', #{@meteor_strike},
120
- '_DoFSCommand(command, args)'].join(''),
121
- 'End Sub', '</script\\>'].join(#{"\n".to_json}));
122
- }
123
- //]]>
124
- </script>#{flash_html}</div>
125
- EOH
126
- end
127
-
128
- private
129
- # Workaround for Safari's strange behaviour after back navigation.
130
- # Safari never posts if form elements are not modified since back
131
- # navigation. So we append timestamp before submitting.
132
- def form_tag_with_timestamp(urlop = {}, options = {}, *arg, &block)
133
- options = options.stringify_keys
134
- (options['onsubmit'] ||= '').insert(0, %Q[
135
- if(!this.__ts__){
136
- var ts = document.createElement('input');
137
- ts.name = ts.id = '__ts__';
138
- ts.type = 'hidden';
139
- this.appendChild(ts);
140
- }
141
- this.__ts__.value = new Number(new Date()).toString(32);
142
- ]) unless /^get$/i === options['method']
143
- form_tag_without_timestamp(urlop, options, *arg, &block)
144
- end
145
-
146
- def flash_tag(flash_vars)
147
- flash_code_base = ['http://fpdownload.macromedia.com/',
148
- 'pub/shockwave/cabs/flash/swflash.cab#version=6,0,0,0'].join('')
149
- swf_path = File.join(RAILS_ROOT, 'public/meteor_strike.swf')
150
- swf_timestamp = File.mtime(swf_path).to_i
151
- <<-"EOH"
152
- <object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000"
153
- codebase="#{flash_code_base}" width="0" height="0"
154
- id="meteor_strike_#{@meteor_strike}">
155
- <param name="allowScriptAccess" value="sameDomain" />
156
- <param name="FlashVars" value="#{flash_vars}" />
157
- <param name="movie" value="/meteor_strike.swf?#{swf_timestamp}" />
158
- <param name="menu" value="false" />
159
- <param name="quality" value="high" />
160
- <param name="devicefont" value="true" />
161
- <param name="bgcolor" value="#ffffff" />
162
- <embed src="/meteor_strike.swf?#{swf_timestamp}" menu="false"
163
- quality="high" devicefont="true" bgcolor="#ffffff" width="0" height="0"
164
- swLiveConnect="true" id="meteor_strike_#{@meteor_strike}"
165
- name="meteor_strike_#{@meteor_strike}" flashvars="#{flash_vars}"
166
- allowScriptAccess="sameDomain" type="application/x-shockwave-flash"
167
- pluginspage="http://www.macromedia.com/go/getflashplayer" />
168
- </object>
169
- EOH
170
- end
171
- end
4
+ PLUGIN_ROOT = File.join(File.dirname(__FILE__), '..')
172
5
  end
@@ -0,0 +1,71 @@
1
+ require 'set'
2
+ require 'md5'
3
+
4
+ module MeteorStrike
5
+ module Helper
6
+ def self.included(base)
7
+ base.class_eval do
8
+ alias_method :form_tag_without_timestamp, :form_tag
9
+ alias_method :form_tag, :form_tag_with_timestamp
10
+ end
11
+ end
12
+
13
+ def meteor_strike(channel, options = {})
14
+ if !options[:cache] && !@meteor_strike
15
+ cc = controller.headers['Cache-Control'] || ''
16
+ cc += ', ' unless cc.empty?
17
+ cc += 'no-store, no-cache, must-revalidate, max-age=0, '
18
+ cc += 'post-check=0, pre-check=0'
19
+ controller.headers['Cache-Control'] = cc
20
+ end
21
+ @meteor_strike ||= 0 and @meteor_strike += 1
22
+ config = Meteor::config
23
+ server = Meteor::server
24
+ shooting_star_uri = "#{server}/#{channel}"
25
+ if config['random_subdomain'] && /[A-z]/ === server
26
+ subdomain = (1..6).map{(rand(26)+?a).chr}.to_s
27
+ shooting_star_uri = [subdomain, shooting_star_uri].join('.')
28
+ end
29
+ uri = url_for(:only_path => false).split('/')[0..2].join('/')
30
+ uid = options[:uid] ? CGI.escape(options[:uid].to_s) : ''
31
+ tags = options[:tag] || []
32
+ tag = tags.map{|i| CGI.escape(i.to_s)}.join(',')
33
+ update_uri = "#{uri}/meteor/update"
34
+ sig = Meteor.shooter.signature
35
+ iframe_id = "meteor-strike-#{@meteor_strike}"
36
+ host_port = (server.split(':') << '80')[0..1].join(':')
37
+ flash_vars = ["channel=#{channel}", "tag=#{tag}", "uid=#{uid}",
38
+ "sig=#{sig}", "base_uri=#{uri}", "server=#{host_port}",
39
+ "heartbeat=#{options[:heartbeat]}", "debug=#{options[:debug].to_json}"
40
+ ].join('&')
41
+ unless options[:noflash]
42
+ @flash_html = render :use_full_path => false,
43
+ :file => File.join(PLUGIN_ROOT, 'views/flash.rhtml'),
44
+ :locals => {:flash_vars => flash_vars}
45
+ end
46
+ render :file => File.join(PLUGIN_ROOT, 'views/xhr.rhtml'),
47
+ :use_full_path => false, :locals => {:iframe_id => iframe_id,
48
+ :shooting_star_uri => shooting_star_uri, :uri => uri,
49
+ :channel => channel, :uid => uid, :tag => tag, :tags => tags,
50
+ :sig => sig, :options => options, :update_uri => update_uri}
51
+ end
52
+
53
+ private
54
+ # Workaround for Safari's strange behaviour after back navigation.
55
+ # Safari never posts if form elements are not modified since back
56
+ # navigation. So we append timestamp before submitting.
57
+ def form_tag_with_timestamp(urlop = {}, options = {}, *arg, &block)
58
+ options = options.stringify_keys
59
+ (options['onsubmit'] ||= '').insert(0, %Q[
60
+ if(!this.__ts__){
61
+ var ts = document.createElement('input');
62
+ ts.name = ts.id = '__ts__';
63
+ ts.type = 'hidden';
64
+ this.appendChild(ts);
65
+ }
66
+ this.__ts__.value = new Number(new Date()).toString(32);
67
+ ]) unless /^get$/i === options['method']
68
+ form_tag_without_timestamp(urlop, options, *arg, &block)
69
+ end
70
+ end
71
+ end
@@ -1,11 +1,12 @@
1
1
  require 'test/unit'
2
2
  require 'rubygems'
3
3
  require 'active_support'
4
- require File.join(File.dirname(__FILE__), '../lib/meteor_strike')
5
4
  begin
6
5
  require 'redgreen'
7
6
  rescue
8
7
  end
8
+ $: << File.join(File.dirname(__FILE__), '../lib')
9
+ require 'meteor_strike'
9
10
 
10
11
  class MeteorStrikeTest < Test::Unit::TestCase
11
12
  def test_meteor_strike
@@ -0,0 +1,23 @@
1
+ <%
2
+ flash_code_base = ['http://fpdownload.macromedia.com/',
3
+ 'pub/shockwave/cabs/flash/swflash.cab#version=6,0,0,0'].join('')
4
+ swf_path = File.join(RAILS_ROOT, 'public/meteor_strike.swf')
5
+ swf_timestamp = File.mtime(swf_path).to_i
6
+ %>
7
+ <object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000"
8
+ codebase="<%= flash_code_base %>" width="300" height="300"
9
+ id="meteor_strike_<%= @meteor_strike %>">
10
+ <param name="allowScriptAccess" value="sameDomain" />
11
+ <param name="FlashVars" value="<%= flash_vars %>" />
12
+ <param name="movie" value="/meteor_strike.swf?<%= swf_timestamp %>" />
13
+ <param name="menu" value="false" />
14
+ <param name="quality" value="high" />
15
+ <param name="devicefont" value="true" />
16
+ <param name="bgcolor" value="#ffffff" />
17
+ <embed src="/meteor_strike.swf?<%= swf_timestamp %>" menu="false"
18
+ quality="high" devicefont="true" bgcolor="#ffffff" width="300" height="300"
19
+ swLiveConnect="true" id="meteor_strike_<%= @meteor_strike %>"
20
+ name="meteor_strike_<%= @meteor_strike %>" flashvars="<%= flash_vars %>"
21
+ allowScriptAccess="sameDomain" type="application/x-shockwave-flash"
22
+ pluginspage="http://www.macromedia.com/go/getflashplayer" />
23
+ </object>
@@ -0,0 +1,110 @@
1
+ <div style="position: absolute;
2
+ <%- if options[:debug] -%>
3
+ top: 0px; right: 0px;
4
+ <%- else -%>
5
+ top: -99999px; left: -99999px;
6
+ <%- end -%>">
7
+ <iframe id="<%= iframe_id %>" name="<%= iframe_id %>"></iframe>
8
+ <form id="<%= iframe_id %>-form" target="<%= iframe_id %>" method="POST"
9
+ action="http://<%= shooting_star_uri %>">
10
+ <input name="execute" value="<%= uri %>/meteor/strike" />
11
+ <input name="tag" /><input name="uid" /><input name="sig" />
12
+ <input name="heartbeat" value="<%= options[:heartbeat] %>" />
13
+ </form>
14
+ <%= javascript_tag %Q{
15
+ var meteorStrike = meteorStrike || $H();
16
+ meteorStrike.getFlashVersion = function(){
17
+ if(#{options[:noflash].to_json}) return 0;
18
+ var version = 0, ua = navigator.userAgent;
19
+ if(navigator.plugins && navigator.mimeTypes.length){
20
+ var x = navigator.plugins["Shockwave Flash"];
21
+ if(x && x.description) {
22
+ version = parseInt(x.description.replace(/([a-zA-Z]|\\s)+/, ""
23
+ ).replace(/(\\s+r|\\s+b[0-9]+)/, ".").split(".")[0]);
24
+ }
25
+ }else if(!ua || ua.indexOf("Windows CE") == -1){
26
+ try{
27
+ var axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash");
28
+ try{
29
+ axo.AllowScriptAccess = 'always';
30
+ version = axo.GetVariable('$version').split(' ')[1].split(",")[0];
31
+ }catch(e){version = 6}
32
+ }catch(e){}
33
+ }
34
+ return version;
35
+ };
36
+ (function(){
37
+ var channel = #{channel.to_json};
38
+ var UID = #{uid.to_json}, TAGS = #{tags.to_json};
39
+ var encodeTags = function(tags){
40
+ var encode = function(i){return encodeURIComponent(i)};
41
+ return $A(tags).uniq().map(encode).join(',');
42
+ };
43
+ var ms = meteorStrike[channel] = meteorStrike[channel] || new Object;
44
+ ms.getTags = function(){return TAGS};
45
+ ms.getUid = function(){return UID};
46
+ ms.execute = function(js){eval(js)};
47
+ ms.event = function(params){
48
+ if(params.event == 'init'){
49
+ if(ms.connection) return Element.remove('#{iframe_id}');
50
+ ms.connection = params.type;
51
+ }
52
+ (function(){#{options[:event]}})();
53
+ };
54
+ ms.update = function(uid, tags){
55
+ new Ajax.Request(#{update_uri.to_json}, {postBody: $H({
56
+ channel: channel, uid: uid || UID,
57
+ tag: encodeTags(tags || TAGS), sig: #{sig.to_json}
58
+ }).toQueryString(), asynchronous: true});
59
+ UID = uid, TAGS = tags;
60
+ };
61
+ ms.tuneIn = function(tags){
62
+ ms.update(UID, TAGS.concat(tags || []).uniq());
63
+ };
64
+ ms.tuneOut = function(tags){
65
+ ms.update(UID, Array.prototype.without.apply(TAGS, tags));
66
+ };
67
+ ms.tuneInOut = function(tagsIn, tagsOut){
68
+ var tags = TAGS.concat(tagsIn || []).uniq();
69
+ ms.update(UID, Array.prototype.without.apply(tags, tagsOut));
70
+ };
71
+ ms.tuneOutIn = function(tagsOut, tagsIn){
72
+ var tags = Array.prototype.without.apply(TAGS, tagsOut);
73
+ ms.update(UID, tags.concat(tagsIn || []).uniq());
74
+ };
75
+ setTimeout(ms.connector = function(){
76
+ if(ms.connection) return;
77
+ var form = $("#{iframe_id}-form");
78
+ form.uid.value = #{uid.to_json};
79
+ form.tag.value = #{tag.to_json};
80
+ form.sig.value = #{sig.to_json};
81
+ form.submit();
82
+ setTimeout(ms.connector, 3000);
83
+ }, meteorStrike.getFlashVersion() >= 6 ? 3000 : 0);
84
+ })();
85
+ function meteor_strike_#{@meteor_strike}_DoFSCommand(command, args){
86
+ var ms = meteorStrike[#{channel.to_json}];
87
+ switch(command){
88
+ case 'execute': ms.execute(args); break;
89
+ case 'event':
90
+ if(args == 'connect') ms.event({event: 'init', type: 'flash'});
91
+ break;
92
+ }
93
+ }
94
+ if(navigator.appName && navigator.appName.indexOf("Microsoft") != -1 &&
95
+ navigator.userAgent.indexOf("Windows") != -1 &&
96
+ navigator.userAgent.indexOf("Windows 3.1") == -1){
97
+ document.write([
98
+ '<script language="VBScript"\\>',
99
+ 'On Error Resume Next',
100
+ ['Sub meteor_strike_', #{@meteor_strike},
101
+ '_FSCommand(ByVal command, ByVal args)'].join(''),
102
+ [' Call meteor_strike_', #{@meteor_strike},
103
+ '_DoFSCommand(command, args)'].join(''),
104
+ 'End Sub', '</script\\>'].join(#{"\n".to_json}));
105
+ }
106
+ if(meteorStrike.getFlashVersion() >= 6){
107
+ document.write(#{@flash_html.to_json});
108
+ }
109
+ } %>
110
+ </div>
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.3
3
3
  specification_version: 1
4
4
  name: shooting_star
5
5
  version: !ruby/object:Gem::Version
6
- version: 3.1.0
7
- date: 2007-07-18 00:00:00 +09:00
6
+ version: 3.2.0
7
+ date: 2007-08-09 00:00:00 +09:00
8
8
  summary: Our goal is development of practical comet server which will be achieving over 100,000 simultaneous connections per host. On this purpose, we abandon portability and use system calls depending on particular OS such as epoll and kqueue.
9
9
  require_paths:
10
10
  - lib
@@ -56,6 +56,11 @@ files:
56
56
  - vendor/plugins/meteor_strike/init.rb
57
57
  - vendor/plugins/meteor_strike/lib
58
58
  - vendor/plugins/meteor_strike/lib/meteor_strike.rb
59
+ - vendor/plugins/meteor_strike/lib/meteor_strike
60
+ - vendor/plugins/meteor_strike/lib/meteor_strike/helper.rb
61
+ - vendor/plugins/meteor_strike/views
62
+ - vendor/plugins/meteor_strike/views/xhr.rhtml
63
+ - vendor/plugins/meteor_strike/views/flash.rhtml
59
64
  - vendor/plugins/meteor_strike/test
60
65
  - vendor/plugins/meteor_strike/test/meteor_strike_test.rb
61
66
  - vendor/plugins/meteor_strike/generators