suj-pusher 0.2.3 → 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -59,7 +59,7 @@ To start the server run:
59
59
  To stop the server run:
60
60
 
61
61
  ```
62
- /path/to/bin/pusher start -H localhost -P 6379 -b 0 -n pusher -p /var/run/pids
62
+ /path/to/bin/pusher stop -H localhost -P 6379 -b 0 -n pusher -p /var/run/pids
63
63
  ```
64
64
 
65
65
  If you set a piddir (using -p option) when starting the server then you must supply the same option when stopping or restarting the server.
@@ -1,12 +1,15 @@
1
1
  require "eventmachine"
2
2
  require "iobuffer"
3
-
4
3
  require "base64"
4
+ require 'suj/pusher/monkey/vash'
5
+
5
6
  module Suj
6
7
  module Pusher
7
8
  class APNConnection < EM::Connection
8
9
  include Suj::Pusher::Logger
9
10
 
11
+ @@last_id = 0
12
+
10
13
  ERRORS = {
11
14
  0 => "No errors encountered",
12
15
  1 => "Processing error",
@@ -26,12 +29,16 @@ module Suj
26
29
  @disconnected = true
27
30
  @pool = pool
28
31
  @options = options
32
+ @processing_ids = Vash.new
29
33
  @cert_key = Digest::SHA1.hexdigest(@options[:cert])
30
34
  @cert_file = File.join(Suj::Pusher.config.certs_path, @cert_key)
31
35
  @buffer = IO::Buffer.new
36
+ self.comm_inactivity_timeout = 60 # Close after 60 sec of inactivity
37
+
32
38
  File.open(@cert_file, "w") do |f|
33
39
  f.write @options[:cert]
34
40
  end
41
+
35
42
  @ssl_options = {
36
43
  private_key_file: @cert_file,
37
44
  cert_chain_file: @cert_file,
@@ -39,6 +46,10 @@ module Suj
39
46
  }
40
47
  end
41
48
 
49
+ def options
50
+ @options
51
+ end
52
+
42
53
  def disconnected?
43
54
  @disconnected
44
55
  end
@@ -47,22 +58,39 @@ module Suj
47
58
  begin
48
59
  @notifications = []
49
60
  data[:apn_ids].each do |apn_id|
50
- @notifications << Suj::Pusher::ApnNotification.new(data.merge({token: apn_id}))
61
+ if ! @pool.valid_token?(@cert_key, apn_id)
62
+ warn "Skipping invalid #{apn_id} APN id"
63
+ next
64
+ end
65
+ notification = Suj::Pusher::ApnNotification.new(data.merge({token: apn_id, id: @@last_id}))
66
+ @processing_ids[@@last_id.to_s] = apn_id
67
+ @@last_id += 1
68
+ @notifications << notification
51
69
  end
52
- if ! disconnected?
53
- info "APN delivering data"
54
- send_data(@notifications.join)
55
- info "APN push notification sent"
56
- @notifications = nil
57
- info "APN delivered data"
58
- else
59
- info "APN connection unavailable"
70
+
71
+ @processing_ids.cleanup!
72
+ info "Processing list size #{@processing_ids.size}"
73
+
74
+ if @notifications.empty?
75
+ warn "No valid tokens, skip message"
76
+ return
60
77
  end
78
+
79
+ if disconnected?
80
+ warn "Connection not ready yet, queue notifications for later"
81
+ return
82
+ end
83
+
84
+ info "APN delivering data"
85
+ send_data(@notifications.join)
86
+ @notifications = nil
87
+ info "APN delivered data"
61
88
  rescue Suj::Pusher::ApnNotification::PayloadTooLarge => e
62
89
  error "APN notification payload too large."
63
90
  debug @notifications.join.inspect
64
91
  rescue => ex
65
92
  error "APN notification error : #{ex}"
93
+ error ex.backtrace
66
94
  end
67
95
  end
68
96
 
@@ -85,6 +113,10 @@ module Suj
85
113
  cmd, status, id = data.unpack("CCN")
86
114
  if cmd != 8
87
115
  error "APN push response command differs from 8"
116
+ elsif status == 8
117
+ token = @processing_ids[id.to_s]
118
+ @pool.invalidate_token(@cert_key, token)
119
+ warn "APN invalid (id: #{id}) token #{token}"
88
120
  elsif status != 0
89
121
  error "APN push error received: #{ERRORS[status]} for id #{id}"
90
122
  end
@@ -94,13 +126,13 @@ module Suj
94
126
  def connection_completed
95
127
  info "APN Connection established..."
96
128
  @disconnected = false
97
- if ! @notifications.nil?
98
- info "EST - APN delivering data"
99
- send_data(@notifications.join)
100
- info "APN push notification sent"
101
- @notifications = nil
102
- info "EST - APN delivered data"
103
- end
129
+
130
+ return if @notifications.nil? || @notifications.empty?
131
+
132
+ info "EST - APN delivering data"
133
+ send_data(@notifications.join)
134
+ @notifications = nil
135
+ info "EST - APN delivered data"
104
136
  end
105
137
 
106
138
  def unbind
@@ -12,7 +12,7 @@ module Suj
12
12
  @disconnected = true
13
13
  @options = options
14
14
  @pool = pool
15
- @cert_key = Digest::SHA1.hexdigest("FEEDBACK" + @options[:cert])
15
+ @cert_key = Digest::SHA1.hexdigest(@options[:cert])
16
16
  @cert_file = File.join(Suj::Pusher.config.certs_path, @cert_key)
17
17
  @buffer = IO::Buffer.new
18
18
  self.comm_inactivity_timeout = 10 # Close after 10 sec of inactivity
@@ -24,6 +24,7 @@ module Suj
24
24
  cert_chain_file: @cert_file,
25
25
  verify_peer: false
26
26
  }
27
+ info "APN feedback #{@cert_key}: Creating"
27
28
  end
28
29
 
29
30
  def disconnected?
@@ -31,7 +32,7 @@ module Suj
31
32
  end
32
33
 
33
34
  def post_init
34
- info "APN Feedback Connection init "
35
+ info "APN feedback #{@cert_key}: Connection init "
35
36
  start_tls(@ssl_options)
36
37
  end
37
38
 
@@ -48,19 +49,20 @@ module Suj
48
49
  while @buffer.size >= 38
49
50
  timestamp, size = @buffer.read(6).unpack("Nn")
50
51
  token = @buffer.read(size)
51
- puts " FEEDBACK TIMESTAMP: #{timestamp} SIZE: #{size} TOKEN: #{token}"
52
+ info "APN feedback #{@cert_key}: TIMESTAMP: #{timestamp} SIZE: #{size} TOKEN: #{token}"
53
+ @pool.invalidate_token(@cert_key, token)
52
54
  end
53
55
  end
54
56
 
55
57
  def connection_completed
56
- info "APN Feedback Connection established..."
58
+ info "APN feedback #{@cert_key}: Connection established..."
57
59
  @disconnected = false
58
60
  end
59
61
 
60
62
  def unbind
61
- info "APN Feedback Connection closed..."
63
+ info "APN feedback #{@cert_key}: Connection closed..."
62
64
  @disconnected = true
63
- @pool.remove_connection(@cert_key)
65
+ @pool.remove_feedback_connection(@cert_key)
64
66
  end
65
67
  end
66
68
  end
@@ -9,6 +9,7 @@ module Suj
9
9
 
10
10
  def initialize(options = {})
11
11
  @token = options[:token]
12
+ @id = options[:id]
12
13
  @ttl = options[:time_to_live] || 0
13
14
  @options = options
14
15
  raise InvalidToken if @token.nil? || (@token.length != 64)
@@ -38,17 +39,10 @@ module Suj
38
39
  end
39
40
 
40
41
  def encode_data
41
- # identifier = 0
42
- # expiry = get_expiry
43
- # size = [payload].pack("a*").size
44
- # data_array = [1, identifier, expiry, 32, @token, size, payload]
45
- # info("PAYLOAD: #{data_array}")
46
- # data_array.pack("cNNnH*na*")
47
-
48
42
  items = [
49
43
  [1, 32, @token ], # token
50
44
  [2, payload.bytesize, payload ], # payload
51
- [3, 4, OpenSSL::Random::random_bytes(4) ], # random identifier
45
+ [3, 4, @id], # id identifier
52
46
  [4, 4, get_expiry ], # expiration date
53
47
  [5, 1, 10 ] # high priority
54
48
  ]
@@ -58,7 +52,7 @@ module Suj
58
52
  frame_data =
59
53
  items[0].pack("CnH*") +
60
54
  items[1].pack("CnA*") +
61
- items[2].pack("CnA*") +
55
+ items[2].pack("CnN") +
62
56
  items[3].pack("CnN") +
63
57
  items[4].pack("CnC")
64
58
 
@@ -12,15 +12,105 @@ module Suj
12
12
  APN_GATEWAY = "gateway.push.apple.com"
13
13
  FEEDBACK_SANDBOX = "feedback.sandbox.push.apple.com"
14
14
  FEEDBACK_GATEWAY = "feedback.push.apple.com"
15
+ CONNECTION_EXPIRY = 60*60*24*30 # Approx 1 month
16
+ TOKEN_EXPIRY = 60*60*24*7 # Approx 1 week
15
17
  APN_PORT = 2195
16
18
  FEEDBACK_PORT = 2196
17
19
 
20
+ class UnknownConnection < StandardError; end
21
+
18
22
  def initialize(daemon)
19
23
  @pool = {}
24
+ @feedback_pool = {}
20
25
  @daemon = daemon
21
26
  @mutex = Mutex.new
27
+ @feedback_mutex = Mutex.new
28
+ @invalid_tokens = Vash.new
29
+ end
30
+
31
+ def get_connection(options = {})
32
+ if options.has_key?(:cert)
33
+ if options.has_key?(:development) && options[:development]
34
+ return apn_sandbox_connection(options)
35
+ else
36
+ return apn_connection(options)
37
+ end
38
+ elsif options.has_key?(:api_key)
39
+ return gcm_connection(options)
40
+ elsif options.has_key?(:secret) && options.has_key?(:sid)
41
+ return wns_connection(options)
42
+ elsif options.has_key?(:wpnotificationclass)
43
+ return wpns_connection(options)
44
+ end
45
+ raise UnknownConnection
46
+ end
47
+
48
+ def get_feedback_connection(options = {})
49
+ if options.has_key?(:cert)
50
+ if options.has_key?(:development) && options[:development]
51
+ return feedback_sandbox_connection(options)
52
+ else
53
+ return feedback_connection(options)
54
+ end
55
+ end
56
+ return nil
57
+ end
58
+
59
+ def remove_feedback_connection(key)
60
+ @feedback_mutex.synchronize {
61
+ info "Removing feedback connection #{key}"
62
+ @feedback_pool.delete(key)
63
+ }
64
+ end
65
+
66
+ def remove_connection(key)
67
+ @mutex.synchronize {
68
+ info "Removing connection #{key}"
69
+ info "Connection not found" unless @pool.delete(key)
70
+ }
71
+ end
72
+
73
+ def invalidate_token(conn, token)
74
+ @invalid_tokens[conn, CONNECTION_EXPIRY] = Vash.new if @invalid_tokens[conn].nil?
75
+ @invalid_tokens[conn][token, TOKEN_EXPIRY] = Time.now.to_s
76
+ end
77
+
78
+ def valid_token?(conn, token)
79
+ return true if ! @invalid_tokens[conn]
80
+ return false if @invalid_tokens[conn].has_key?(token)
81
+ return true
82
+ end
83
+
84
+ # Method that creates APN feedback connections
85
+ def feedback
86
+ @invalid_tokens.cleanup!
87
+ @invalid_tokens.each { |k,v| v.cleanup! if v }
88
+
89
+ @pool.each do |k, conn|
90
+ next if ! conn.is_a?(Suj::Pusher::APNConnection)
91
+ conn = get_feedback_connection(conn.options)
92
+ end
22
93
  end
23
94
 
95
+ def close
96
+ @mutex.synchronize {
97
+ @pool.each do |k, conn|
98
+ conn.close_connection_after_writing
99
+ end
100
+ }
101
+ @feedback_mutex.synchronize {
102
+ @feedback_pool.each do |k, conn|
103
+ conn.close_connection
104
+ end
105
+ }
106
+ end
107
+
108
+ def pending_connections?
109
+ @pool.size + @feedback_pool.size > 0
110
+ end
111
+
112
+ private
113
+
24
114
  def apn_connection(options = {})
25
115
  cert = Digest::SHA1.hexdigest options[:cert]
26
116
  info "APN connection #{cert}"
@@ -31,26 +121,25 @@ module Suj
31
121
 
32
122
  def apn_sandbox_connection(options = {})
33
123
  cert = Digest::SHA1.hexdigest options[:cert]
34
- info "APN connection #{cert}"
124
+ info "APN sandbox connection #{cert}"
35
125
  @mutex.synchronize do
36
126
  @pool[cert] ||= EM.connect(APN_SANDBOX, APN_PORT, APNConnection, self, options)
37
127
  end
38
128
  end
39
129
 
40
130
  def feedback_connection(options = {})
41
- cert = Digest::SHA1.hexdigest("FEEDBACK" + options[:cert])
42
- info "APN Feedback connection #{cert}"
43
- @mutex.synchronize do
44
- @pool[cert] ||= EM.connect(FEEDBACK_GATEWAY, FEEDBACK_PORT, APNFeedbackConnection, self, options)
131
+ cert = Digest::SHA1.hexdigest(options[:cert])
132
+ info "Get APN feedback connection #{cert}"
133
+ @feedback_mutex.synchronize do
134
+ @feedback_pool[cert] ||= EM.connect(FEEDBACK_GATEWAY, FEEDBACK_PORT, APNFeedbackConnection, self, options)
45
135
  end
46
136
  end
47
137
 
48
138
  def feedback_sandbox_connection(options = {})
49
- info "Feedback connection"
50
- cert = Digest::SHA1.hexdigest("FEEDBACK" + options[:cert])
51
- info "APN Sandbox Feedback connection #{cert}"
52
- @mutex.synchronize do
53
- @pool[cert] ||= EM.connect(FEEDBACK_SANDBOX, FEEDBACK_PORT, APNFeedbackConnection, self, options)
139
+ cert = Digest::SHA1.hexdigest(options[:cert])
140
+ info "Get APN sandbox feedback connection #{cert}"
141
+ @feedback_mutex.synchronize do
142
+ @feedback_pool[cert] ||= EM.connect(FEEDBACK_SANDBOX, FEEDBACK_PORT, APNFeedbackConnection, self, options)
54
143
  end
55
144
  end
56
145
 
@@ -63,14 +152,6 @@ module Suj
63
152
  end
64
153
  end
65
154
 
66
- def apn_connection(options = {})
67
- cert = Digest::SHA1.hexdigest options[:cert]
68
- info "APN connection #{cert}"
69
- @mutex.synchronize do
70
- @pool[cert] ||= EM.connect(APN_GATEWAY, APN_PORT, APNConnection, self, options)
71
- end
72
- end
73
-
74
155
  def wns_connection(options = {})
75
156
  cert = Digest::SHA1.hexdigest options[:secret]
76
157
  info "WNS connection #{cert}"
@@ -89,11 +170,6 @@ module Suj
89
170
  end
90
171
  end
91
172
 
92
- def remove_connection(key)
93
- info "Removing connection #{key}"
94
- info "Connection not found" unless @pool.delete(key)
95
- end
96
-
97
173
  end
98
174
 
99
175
  end
@@ -5,6 +5,7 @@ require 'openssl'
5
5
  require 'em-hiredis'
6
6
  require "multi_json"
7
7
  require 'fileutils'
8
+ require 'fiber'
8
9
 
9
10
  module Suj
10
11
  module Pusher
@@ -16,125 +17,92 @@ module Suj
16
17
  def start
17
18
  info "Starting pusher daemon"
18
19
  info " subsribe to push messages from #{redis_url} namespace #{redis_namespace}"
19
- @last_feedback = Time.now
20
- @last_sandbox_feedback = Time.now
21
20
  EM.run do
22
- wait_msg
23
- end
24
- end
25
21
 
26
- def stop
27
- info "Stopping daemon process"
28
- begin
29
- EM.stop
30
- rescue
31
- end
32
- info "Stopped daemon process"
33
- end
34
-
35
- private
36
-
37
- def wait_msg
38
- defer = redis.brpop "#{redis_namespace}:#{MSG_QUEUE}", 0
39
- defer.callback do |_, msg|
40
- begin
41
- info "RECEIVED MESSAGE"
42
- data = Hash.symbolize_keys(MultiJson.load(msg))
43
- send_notification(data)
44
- info "SENT MESSAGE"
45
- retrieve_feedback(data)
46
- rescue MultiJson::LoadError
47
- warn("Received invalid json data, discarding msg")
48
- rescue => e
49
- error("Error sending notification : #{e}")
50
- error e.backtrace
22
+ EM.add_periodic_timer(FEEDBACK_TIME) do
23
+ info "Starting APN feedback service"
24
+ pool.feedback
51
25
  end
52
- EM.next_tick { wait_msg }
53
- end
54
- defer.errback do |e|
55
- error e
56
- EM.next_tick { wait_msg }
57
- end
58
- end
59
26
 
60
- def send_notification(msg)
61
- if msg.has_key?(:cert)
62
- if msg.has_key?(:development) && msg[:development]
63
- send_apn_sandbox_notification(msg)
64
- else
65
- send_apn_notification(msg)
66
- end
67
- elsif msg.has_key?(:api_key)
68
- send_gcm_notification(msg)
69
- elsif msg.has_key?(:secret) && msg.has_key?(:sid)
70
- #wns push notification
71
- send_wns_notification(msg)
72
- elsif msg.has_key?(:wpnotificationclass)
73
- #send wpns push notification
74
- send_wpns_notification(msg)
75
- else
76
- warn "Could not determine push notification service."
27
+ Fiber.new {
28
+ loop
29
+ }.resume
77
30
  end
78
31
  end
79
32
 
80
- def retrieve_feedback(msg)
81
- if msg.has_key?(:cert)
82
- if msg.has_key?(:development) && msg[:development]
83
- feedback_sandbox_connection(msg)
84
- else
85
- feedback_connection(msg)
86
- end
33
+ def stop
34
+ pool.close
35
+ redis.close
36
+
37
+ if ! pool.pending_connections?
38
+ EM.stop
39
+ info "Stopped daemon process"
40
+ else
41
+ info "Waiting for pending connections"
42
+ EM.add_periodic_timer(5) {
43
+ if ! pool.pending_connections?
44
+ EM.stop
45
+ info "Stopped daemon process"
46
+ end
47
+ }
87
48
  end
88
49
  end
89
50
 
90
- def send_apn_notification(msg)
91
- info "Sending APN notification via connection #{Digest::SHA1.hexdigest(msg[:cert])}"
92
- conn = pool.apn_connection(msg)
93
- conn.deliver(msg)
94
- # msg[:apn_ids].each do |apn_id|
95
- # conn.deliver(msg.merge({token: apn_id}))
96
- # end
97
- end
51
+ private
98
52
 
99
- def send_apn_sandbox_notification(msg)
100
- info "Sending APN sandbox notification via connection #{Digest::SHA1.hexdigest(msg[:cert])}"
101
- conn = pool.apn_sandbox_connection(msg)
53
+ def loop
54
+ msg = get_msg
55
+ return next_tick { loop } if msg.nil?
56
+ conn = get_connection(msg)
57
+ return next_tick { loop } if conn.nil?
102
58
  conn.deliver(msg)
103
- # msg[:apn_ids].each do |apn_id|
104
- # conn.deliver(msg.merge({token: apn_id}))
105
- # end
106
- end
107
-
108
- def feedback_connection(msg)
109
- return Time.now - @last_feedback < FEEDBACK_TIME
110
- info "Get feedback information"
111
- conn = pool.feedback_connection(msg)
112
- @last_feedback = Time.now
59
+ next_tick { loop }
113
60
  end
114
61
 
115
- def feedback_sandbox_connection(msg)
116
- return if Time.now - @last_sandbox_feedback < FEEDBACK_TIME
117
- info "Get feedback sandbox information"
118
- conn = pool.feedback_sandbox_connection(msg)
119
- @last_sandbox_feedback = Time.now
62
+ def next_tick(&blk)
63
+ EM.next_tick { Fiber.new { blk.call }.resume }
120
64
  end
121
65
 
122
- def send_gcm_notification(msg)
123
- info "Sending GCM notification via connection #{msg[:api_key]}"
124
- conn = pool.gcm_connection(msg)
125
- conn.deliver(msg)
66
+ def msg_queue
67
+ @msg_queue ||= "#{redis_namespace}:#{MSG_QUEUE}"
126
68
  end
127
69
 
128
- def send_wns_notification(msg)
129
- info "Sending WNS notification via connection #{Digest::SHA1.hexdigest(msg[:secret])}"
130
- conn = pool.wns_connection(msg)
131
- conn.deliver(msg)
70
+ def get_msg
71
+ f = Fiber.current
72
+ begin
73
+ defer = redis.brpop msg_queue, 0
74
+ defer.callback do |_, msg|
75
+ info "Received message"
76
+ begin
77
+ data = Hash.symbolize_keys(MultiJson.load(msg))
78
+ f.resume(data)
79
+ rescue MultiJson::LoadError
80
+ warn("Message has bad json format, discarding")
81
+ f.resume(nil)
82
+ rescue => e
83
+ error(e)
84
+ f.resume(nil)
85
+ end
86
+ end
87
+ defer.errback do |e|
88
+ f.resume(nil)
89
+ end
90
+ rescue EventMachine::Hiredis::Error => e
91
+ info e
92
+ rescue => ex
93
+ error ex
94
+ end
95
+ return Fiber.yield
132
96
  end
133
97
 
134
- def send_wpns_notification(msg)
135
- info "Sending WPNS notification via connection #{Digest::SHA1.hexdigest(msg[:secret])}"
136
- conn = pool.wpns_connection(msg)
137
- conn.deliver(msg)
98
+ def get_connection(options)
99
+ info "Get connection"
100
+ begin
101
+ return pool.get_connection(options)
102
+ rescue ConnectionPool::UnknownConnection
103
+ error "Could not find connection"
104
+ return nil
105
+ end
138
106
  end
139
107
 
140
108
  def redis_url
@@ -0,0 +1,110 @@
1
+ #############################################################################
2
+ # Class: Vash (Ruby Volatile Hash)
3
+ # Hash that returns values only for a short time. This is useful as a cache
4
+ # where I/O is involved. The primary goal of this object is to reduce I/O
5
+ # access and due to the nature of I/O being slower then memory, you should also
6
+ # see a gain in quicker response times.
7
+ #
8
+ # For example, if Person.first found the first person from the database & cache
9
+ # was an instance of Vash then the following would only contact the database for
10
+ # the first iteration:
11
+ #
12
+ # > cache = Vash.new
13
+ # > 1000.times {cache[:person] ||= Person.first}
14
+ #
15
+ # However if you did the following immediately following that command it would
16
+ # hit the database again:
17
+ #
18
+ # > sleep 61
19
+ # > cache[:person] ||= Person.first
20
+ #
21
+ # The reason is that there is a default Time-To-Live of 60 seconds. You can
22
+ # also set a custom TTL of 10 seconds like so:
23
+ #
24
+ # > cache[:person, 10] = Person.first
25
+ #
26
+ # The Vash object will forget any answer that is requested after the specified
27
+ # TTL. It is a good idea to manually clean things up from time to time because
28
+ # it is possible that you'll cache data but never again access it and therefor
29
+ # it will stay in memory after the TTL has expired. To clean up the Vash object,
30
+ # call the method: cleanup!
31
+ #
32
+ # > sleep 11 # At this point the prior person ttl will be expired
33
+ # # but the person key and value will still exist.
34
+ # > cache # This will still show the the entire set of keys
35
+ # # regardless of the TTL, the :person will still exist
36
+ # > cache.cleanup! # All of the TTL's will be inspected and the expired
37
+ # # :person key will be deleted.
38
+ #
39
+ # The cleanup must be manually called because the purpose of the Vash is to
40
+ # lessen needless I/O calls and gain speed not to slow it down with regular
41
+ # maintenance.
42
+ class Vash < Hash
43
+ def initialize(constructor = {})
44
+ @register ||= {} # remembers expiration time of every key
45
+ if constructor.is_a?(Hash)
46
+ super()
47
+ merge(constructor)
48
+ else
49
+ super(constructor)
50
+ end
51
+ end
52
+
53
+ alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
54
+ alias_method :regular_reader, :[] unless method_defined?(:regular_reader)
55
+
56
+ def [](key)
57
+ sterilize(key)
58
+ clear(key) if expired?(key)
59
+ regular_reader(key)
60
+ end
61
+
62
+ def []=(key, *args)
63
+ # a little bit o variable hacking to support (h[key, ttl] = value), which will come
64
+ # accross as (key, [ttl, value]) whereas (h[key]=value) comes accross as (key, [value])
65
+ if args.length == 2
66
+ value, ttl = args[1], args[0]
67
+ elsif args.length == 1
68
+ value, ttl = args[0], 60
69
+ else
70
+ raise ArgumentError, "Wrong number of arguments, expected 2 or 3, received: #{args.length+1}\n"+
71
+ "Example Usage: volatile_hash[:key]=value OR volatile_hash[:key, ttl]=value"
72
+ end
73
+ sterilize(key)
74
+ ttl(key, ttl)
75
+ regular_writer(key, value)
76
+ end
77
+
78
+ def merge(hsh)
79
+ hsh.map {|key,value| self[sterile(key)] = hsh[key]}
80
+ self
81
+ end
82
+
83
+ def cleanup!
84
+ now = Time.now.to_i
85
+ @register.map {|k,v| clear(k) if v < now}
86
+ end
87
+
88
+ def clear(key)
89
+ sterilize(key)
90
+ @register.delete key
91
+ self.delete key
92
+ end
93
+
94
+ private
95
+ def expired?(key)
96
+ Time.now.to_i > @register[key].to_i
97
+ end
98
+
99
+ def ttl(key, secs=60)
100
+ @register[key] = Time.now.to_i + secs.to_i
101
+ end
102
+
103
+ def sterile(key)
104
+ String === key ? key.chomp('!').chomp('=') : key.to_s.chomp('!').chomp('=').to_sym
105
+ end
106
+
107
+ def sterilize(key)
108
+ key = sterile(key)
109
+ end
110
+ end
@@ -1,5 +1,5 @@
1
1
  module Suj
2
2
  module Pusher
3
- VERSION = '0.2.3'
3
+ VERSION = '0.2.5'
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: suj-pusher
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.2.5
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-12-09 00:00:00.000000000 Z
12
+ date: 2014-01-08 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: em-http-request
16
- requirement: &10281040 !ruby/object:Gem::Requirement
16
+ requirement: &10452580 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *10281040
24
+ version_requirements: *10452580
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: em-hiredis
27
- requirement: &10280620 !ruby/object:Gem::Requirement
27
+ requirement: &10451520 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *10280620
35
+ version_requirements: *10451520
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: multi_json
38
- requirement: &10280020 !ruby/object:Gem::Requirement
38
+ requirement: &10451080 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *10280020
46
+ version_requirements: *10451080
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: daemon-spawn
49
- requirement: &10279380 !ruby/object:Gem::Requirement
49
+ requirement: &10450660 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: '0'
55
55
  type: :runtime
56
56
  prerelease: false
57
- version_requirements: *10279380
57
+ version_requirements: *10450660
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: iobuffer
60
- requirement: &10295080 !ruby/object:Gem::Requirement
60
+ requirement: &10450060 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ! '>='
@@ -65,7 +65,7 @@ dependencies:
65
65
  version: '0'
66
66
  type: :runtime
67
67
  prerelease: false
68
- version_requirements: *10295080
68
+ version_requirements: *10450060
69
69
  description: Stand alone push notification server for APN, GCM, WNS and MPNS.
70
70
  email:
71
71
  - rd@skillupjapan.co.jp, n.bersano@skillupchile.cl
@@ -86,6 +86,7 @@ files:
86
86
  - lib/suj/pusher/gcm_connection.rb
87
87
  - lib/suj/pusher/logger.rb
88
88
  - lib/suj/pusher/monkey/hash.rb
89
+ - lib/suj/pusher/monkey/vash.rb
89
90
  - lib/suj/pusher/version.rb
90
91
  - lib/suj/pusher/wns_connection.rb
91
92
  - lib/suj/pusher/wns_notification.rb