resque-aps 0.9.9 → 0.9.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,166 +3,177 @@ require 'openssl'
3
3
  module Resque
4
4
  module Plugins
5
5
  module Aps
6
- class Application
7
- include Resque::Plugins::Aps::Helper
8
- extend Resque::Plugins::Aps::Helper
6
+ class Application
7
+ include Resque::Plugins::Aps::Helper
8
+ extend Resque::Plugins::Aps::Helper
9
9
 
10
- attr_accessor :name, :cert_file, :cert_passwd
10
+ attr_accessor :name, :cert_file, :cert_passwd
11
11
 
12
- @queue = "apple_push_service"
12
+ @queue = "apple_push_service"
13
13
 
14
- def inspect
15
- "#<#{self.class.name} #{name.inspect}, #{cert_passwd.inspect}, #{cert_file.inspect}>"
16
- end
14
+ def inspect
15
+ "#<#{self.class.name} #{name.inspect}, #{cert_passwd.inspect}, #{cert_file.inspect}>"
16
+ end
17
17
 
18
- def self.perform(*args)
19
- count = 0
20
- start = Time.now
21
- app_name = args[0]
22
- Resque.aps_application(app_name).socket do |socket, app|
23
- while true
24
- n = Resque.dequeue_aps(app_name)
25
- if n.nil?
26
- if app.aps_nil_notification_retry? count, start
27
- next
28
- else
29
- break
18
+ def self.perform(*args)
19
+ count = 0
20
+ start = Time.now
21
+ app_name = args[0]
22
+ Resque.aps_application(app_name).socket do |socket, app|
23
+ while true
24
+ n = Resque.dequeue_aps(app_name)
25
+ if n.nil?
26
+ if app.aps_nil_notification_retry? count, start
27
+ next
28
+ else
29
+ break
30
+ end
31
+ end
32
+
33
+ app.before_aps_write n
34
+ begin
35
+ n.batch_id = count + 1
36
+ n.expiry = (Time.now.utc + 1.hour).to_i
37
+ socket.write(n.formatted)
38
+ app.after_aps_write n
39
+ count += 1
40
+ unless (resp = socket.read).blank?
41
+ logger.error "Failure response: #{resp.inspect}" if logger
42
+ break
43
+ end
44
+ rescue
45
+ logger.error Application.application_exception($!, app_name) if logger
46
+ app.failed_aps_write n, $!
47
+ logger.error "Sent #{count} notifications before failure." if logger
48
+ redis.rpush(aps_application_queue_key(app_name), encode(n.to_hash))
49
+ break
50
+ end
30
51
  end
31
52
  end
53
+ logger.info("Sent #{count} #{app_name} notifications in batch over #{Time.now - start} sec.") if logger
54
+ ensure
55
+ Resque.dequeue_aps_application(app_name)
56
+ end
57
+
58
+ #
59
+ # Create the TCP and SSL sockets for sending the notification
60
+ #
61
+ def self.create_sockets(cert, passphrase, host, port)
62
+ ctx = OpenSSL::SSL::SSLContext.new
63
+ ctx.key = OpenSSL::PKey::RSA.new(cert, passphrase)
64
+ ctx.cert = OpenSSL::X509::Certificate.new(cert)
65
+
66
+ s = TCPSocket.new(host, port)
67
+ ssl = OpenSSL::SSL::SSLSocket.new(s, ctx)
68
+ ssl.sync = true
69
+
70
+ return s, ssl
71
+ end
32
72
 
33
- app.before_aps_write n
73
+ #
74
+ # Close the sockets
75
+ #
76
+ def self.close_sockets(socket, ssl_socket)
34
77
  begin
35
- socket.write(n.formatted)
36
- app.after_aps_write n
37
- count += 1
78
+ if ssl_socket
79
+ ssl_socket.close
80
+ end
38
81
  rescue
39
- logger.error Application.application_exception($!, name) if logger
40
- app.failed_aps_write n, $!
82
+ Resque.logger.error("#{$!}: #{$!.backtrace.join("\n")}") if Resque.logger
41
83
  end
42
- end
43
- end
44
- logger.info("Sent #{count} #{app_name} notifications in batch over #{Time.now - start} sec.") if logger
45
- end
46
-
47
- #
48
- # Create the TCP and SSL sockets for sending the notification
49
- #
50
- def self.create_sockets(cert, passphrase, host, port)
51
- ctx = OpenSSL::SSL::SSLContext.new
52
- ctx.key = OpenSSL::PKey::RSA.new(cert, passphrase)
53
- ctx.cert = OpenSSL::X509::Certificate.new(cert)
54
-
55
- s = TCPSocket.new(host, port)
56
- ssl = OpenSSL::SSL::SSLSocket.new(s, ctx)
57
- ssl.sync = true
58
-
59
- return s, ssl
60
- end
61
84
 
62
- #
63
- # Close the sockets
64
- #
65
- def self.close_sockets(socket, ssl_socket)
66
- begin
67
- if ssl_socket
68
- ssl_socket.close
85
+ begin
86
+ if socket
87
+ socket.close
88
+ end
89
+ rescue
90
+ Resque.logger.error("#{$!}: #{$!.backtrace.join("\n")}") if Resque.logger
91
+ end
69
92
  end
70
- rescue
71
- Resque.logger.error("#{$!}: #{$!.backtrace.join("\n")}") if Resque.logger
72
- end
73
93
 
74
- begin
75
- if socket
76
- socket.close
94
+ def self.application_exception(exception, name)
95
+ exc = Exception.new("#{exception} (#{name})")
96
+ exc.set_backtrace(exception.backtrace)
97
+ return exc
77
98
  end
78
- rescue
79
- Resque.logger.error("#{$!}: #{$!.backtrace.join("\n")}") if Resque.logger
80
- end
81
- end
82
-
83
- def self.application_exception(exception, name)
84
- exc = Exception.new("#{exception} (#{name})")
85
- exc.set_backtrace(exception.backtrace)
86
- return exc
87
- end
88
99
 
89
- def initialize(attributes)
90
- attributes.each do |k, v|
91
- respond_to?(:"#{k}=") ? send(:"#{k}=", v) : raise(Resque::Plugins::Aps::UnknownAttributeError, "unknown attribute: #{k}")
92
- end
93
- end
100
+ def initialize(attributes)
101
+ attributes.each do |k, v|
102
+ respond_to?(:"#{k}=") ? send(:"#{k}=", v) : raise(Resque::Plugins::Aps::UnknownAttributeError, "unknown attribute: #{k}")
103
+ end
104
+ end
94
105
 
95
- def socket(cert = nil, certp = nil, host = nil, port = nil, &block)
96
- logger.debug("resque-aps: ssl_socket(#{name})") if logger
97
- exc = nil
98
-
99
- begin
100
- socket, ssl_socket = Application.create_sockets(cert || File.read(cert_file),
101
- certp || cert_passwd,
102
- host || Resque.aps_gateway_host,
103
- port || Resque.aps_gateway_port)
104
- rescue
105
- raise Application.application_exception($!, name)
106
- end
106
+ def socket(cert = nil, certp = nil, host = nil, port = nil, &block)
107
+ logger.debug("resque-aps: ssl_socket(#{name})") if logger
108
+ exc = nil
109
+
110
+ begin
111
+ socket, ssl_socket = Application.create_sockets(cert || File.read(cert_file),
112
+ certp || cert_passwd,
113
+ host || Resque.aps_gateway_host,
114
+ port || Resque.aps_gateway_port)
115
+ rescue
116
+ raise Application.application_exception($!, name)
117
+ end
107
118
 
108
- begin
109
- ssl_socket.connect
110
- yield ssl_socket, self if block_given?
111
- rescue
112
- exc = Application.application_exception($!, name)
113
- if $!.message =~ /^SSL_connect .* certificate (expired|revoked)/
114
- notify_aps_admin exc
115
- end
116
- raise exc
117
- ensure
118
- Application.close_sockets(socket, ssl_socket)
119
- end
119
+ begin
120
+ ssl_socket.connect
121
+ yield ssl_socket, self if block_given?
122
+ rescue
123
+ exc = Application.application_exception($!, name)
124
+ if $!.message =~ /^SSL_connect .* certificate (expired|revoked)/
125
+ notify_aps_admin exc
126
+ end
127
+ raise exc
128
+ ensure
129
+ Application.close_sockets(socket, ssl_socket)
130
+ end
120
131
 
121
- exc
122
- end
132
+ exc
133
+ end
123
134
 
124
- def to_hash
125
- {'name' => name, 'cert_file' => cert_file, 'cert_passwd' => cert_passwd}
126
- end
135
+ def to_hash
136
+ {'name' => name, 'cert_file' => cert_file, 'cert_passwd' => cert_passwd}
137
+ end
127
138
 
128
- def to_json
129
- to_hash.to_json
130
- end
139
+ def to_json
140
+ to_hash.to_json
141
+ end
131
142
 
132
- def before_aps_write(notification)
133
- logger.debug("ResqueAps[before_write]: #{notification}") if logger
134
- end
143
+ def before_aps_write(notification)
144
+ logger.debug("ResqueAps[before_write]: #{notification}") if logger
145
+ end
135
146
 
136
- def after_aps_write(notification)
137
- logger.debug("ResqueAps[after_write]: #{notification}") if logger
138
- end
147
+ def after_aps_write(notification)
148
+ logger.debug("ResqueAps[after_write]: #{notification}") if logger
149
+ end
139
150
 
140
- def failed_aps_write(notification, exception)
141
- logger.error("ResqueAps[write_failed]: #{exception} (#{notification}): #{exception.backtrace.join("\n")}") if logger
142
- end
151
+ def failed_aps_write(notification, exception)
152
+ logger.error("ResqueAps[write_failed]: #{exception} (#{notification}): #{exception.backtrace.join("\n")}") if logger
153
+ end
143
154
 
144
- def notify_aps_admin(exception)
145
- end
155
+ def notify_aps_admin(exception)
156
+ end
146
157
 
147
- def aps_nil_notification_retry?(sent_count, start_time)
148
- false
149
- end
158
+ def aps_nil_notification_retry?(sent_count, start_time)
159
+ false
160
+ end
150
161
 
151
- def before_aps_read
152
- logger.debug("ResqueAps[before_read]:") if logger
153
- end
162
+ def before_aps_read
163
+ logger.debug("ResqueAps[before_read]:") if logger
164
+ end
154
165
 
155
- def after_aps_read(feedback)
156
- logger.debug("ResqueAps[after_read]: #{feedback.to_s}") if logger
157
- end
166
+ def after_aps_read(feedback)
167
+ logger.debug("ResqueAps[after_read]: #{feedback.to_s}") if logger
168
+ end
158
169
 
159
- def aps_read_error(exception)
160
- logger.error("ResqueAps[read_error]: #{exception} (#{name}): #{exception.backtrace.join("\n")}") if logger
161
- end
170
+ def aps_read_error(exception)
171
+ logger.error("ResqueAps[read_error]: #{exception} (#{name}): #{exception.backtrace.join("\n")}") if logger
172
+ end
162
173
 
163
- def aps_read_failed
164
- logger.error("ResqueAps[read_failed]: Bad data on the socket (#{name})") if logger
165
- end
174
+ def aps_read_failed
175
+ logger.error("ResqueAps[read_failed]: Bad data on the socket (#{name})") if logger
176
+ end
166
177
 
167
178
  end
168
179
  end
@@ -3,90 +3,90 @@ require 'timeout'
3
3
  module Resque
4
4
  module Plugins
5
5
  module Aps
6
- class Feedback
7
- include Resque::Plugins::Aps::Helper
8
- extend Resque::Plugins::Aps::Helper
6
+ class Feedback
7
+ include Resque::Plugins::Aps::Helper
8
+ extend Resque::Plugins::Aps::Helper
9
9
 
10
- @queue = "apple_push_service"
10
+ @queue = "apple_push_service"
11
11
 
12
- attr_accessor :application_name, :device_token, :received_at
12
+ attr_accessor :application_name, :device_token, :received_at
13
13
 
14
- def initialize(attributes)
15
- attributes.each do |k, v|
16
- respond_to?(:"#{k}=") ? send(:"#{k}=", v) : raise(Resque::Plugins::Aps::UnknownAttributeError, "unknown attribute: #{k}")
17
- end
18
- end
14
+ def initialize(attributes)
15
+ attributes.each do |k, v|
16
+ respond_to?(:"#{k}=") ? send(:"#{k}=", v) : raise(Resque::Plugins::Aps::UnknownAttributeError, "unknown attribute: #{k}")
17
+ end
18
+ end
19
19
 
20
- def inspect
21
- "#<#{self.class.name} #{application_name.inspect}, #{device_token.inspect}, #{received_at.inspect}>"
22
- end
20
+ def inspect
21
+ "#<#{self.class.name} #{application_name.inspect}, #{device_token.inspect}, #{received_at.inspect}>"
22
+ end
23
23
 
24
- def to_s
25
- "#{application_name} #{received_at} #{device_token}"
26
- end
24
+ def to_s
25
+ "#{application_name} #{received_at} #{device_token}"
26
+ end
27
27
 
28
- def to_hash
29
- {:application_name => application_name, :device_token => device_token, :received_at => received_at}
30
- end
28
+ def to_hash
29
+ {:application_name => application_name, :device_token => device_token, :received_at => received_at}
30
+ end
31
31
 
32
- def self.read_feedback(ssl_socket, application_name, product_id)
33
- data_str = ssl_socket.read(4)
34
- return nil unless data_str
35
- data_ary = data_str.unpack('N')
36
- return nil unless data_ary && data_ary[0]
37
- time = Time.at(data_ary[0])
32
+ def self.read_feedback(ssl_socket, application_name, product_id)
33
+ data_str = ssl_socket.read(4)
34
+ return nil unless data_str
35
+ data_ary = data_str.unpack('N')
36
+ return nil unless data_ary && data_ary[0]
37
+ time = Time.at(data_ary[0])
38
38
 
39
- data_str = ssl_socket.read(2)
40
- return nil unless data_str
41
- data_ary = data_str.unpack('n')
42
- return nil unless data_ary && data_ary[0]
43
- tl = data_ary[0]
39
+ data_str = ssl_socket.read(2)
40
+ return nil unless data_str
41
+ data_ary = data_str.unpack('n')
42
+ return nil unless data_ary && data_ary[0]
43
+ tl = data_ary[0]
44
44
 
45
- data_str = ssl_socket.read(tl)
46
- return nil unless data_str
47
- data_ary = data_str.unpack('H*')
48
- return nil unless data_ary && data_ary[0]
49
- token = data_ary[0]
45
+ data_str = ssl_socket.read(tl)
46
+ return nil unless data_str
47
+ data_ary = data_str.unpack('H*')
48
+ return nil unless data_ary && data_ary[0]
49
+ token = data_ary[0]
50
50
 
51
- feedback = Feedback.new({:received_at => time, :device_token => token, :application_name => application_name})
52
- return feedback
53
- end
51
+ feedback = Feedback.new({:received_at => time, :device_token => token, :application_name => application_name})
52
+ return feedback
53
+ end
54
54
 
55
- #
56
- # Perform a Feedback check on the APN server, for the given app key (which must be the first argument)
57
- #
58
- def self.perform(*args)
59
- app_name = args[0]
60
- start = Time.now
61
- count = 0
62
- appl = Resque.aps_application(app_name)
55
+ #
56
+ # Perform a Feedback check on the APN server, for the given app key (which must be the first argument)
57
+ #
58
+ def self.perform(*args)
59
+ app_name = args[0]
60
+ start = Time.now
61
+ count = 0
62
+ appl = Resque.aps_application(app_name)
63
63
 
64
- return unless appl
64
+ return unless appl
65
65
 
66
- appl.socket(nil, nil, Resque.aps_feedback_host, Resque.aps_feedback_port) do |socket, app|
67
- begin
68
- logger.debug("Feedback: Reading feedbacks for #{app_name}.") if logger
69
- timeout(5) do
70
- until socket.eof?
71
- app.before_aps_read
72
- feedback = read_feedback(socket, app_name, product_id)
73
- if feedback
74
- count += 1
75
- app.after_app_read(feedback)
76
- else
77
- app.aps_read_failed
66
+ appl.socket(nil, nil, Resque.aps_feedback_host, Resque.aps_feedback_port) do |socket, app|
67
+ begin
68
+ logger.debug("Feedback: Reading feedbacks for #{app_name}.") if logger
69
+ timeout(5) do
70
+ until socket.eof?
71
+ app.before_aps_read
72
+ feedback = read_feedback(socket, app_name, product_id)
73
+ if feedback
74
+ count += 1
75
+ app.after_aps_read(feedback)
76
+ else
77
+ app.aps_read_failed
78
+ end
79
+ end
78
80
  end
81
+ rescue
82
+ logger.error Application.application_exception($!, app_name) if logger
83
+ app.aps_read_error(Application.application_exception($!, app_name))
79
84
  end
80
85
  end
81
- rescue
82
- logger.error Application.application_exception($!, app_name) if logger
83
- app.aps_read_error(Application.application_exception($!, app_name))
86
+ logger.info("Read #{count} #{app_name} feedbacks over #{Time.now - start} sec.") if logger
84
87
  end
88
+
85
89
  end
86
- logger.info("Read #{count} #{app_name} feedbacks over #{Time.now - start} sec.") if logger
87
90
  end
88
-
89
- end
90
91
  end
91
- end
92
92
  end
@@ -1,197 +1,203 @@
1
1
  module Resque
2
2
  module Plugins
3
3
  module Aps
4
- class Notification
5
- include Resque::Plugins::Aps::Helper
6
- extend Resque::Plugins::Aps::Helper
4
+ class Notification
5
+ include Resque::Plugins::Aps::Helper
6
+ extend Resque::Plugins::Aps::Helper
7
7
 
8
- attr_accessor :application_name, :device_token, :payload
8
+ attr_accessor :application_name, :device_token, :payload, :batch_id, :expiry
9
9
 
10
- def initialize(attributes)
11
- attributes.each do |k, v|
12
- respond_to?(:"#{k}=") ? send(:"#{k}=", v) : raise(Resque::Plugins::Aps::UnknownAttributeError, "unknown attribute: #{k}")
13
- end
14
- end
10
+ def initialize(attributes)
11
+ attributes.each do |k, v|
12
+ respond_to?(:"#{k}=") ? send(:"#{k}=", v) : raise(Resque::Plugins::Aps::UnknownAttributeError, "unknown attribute: #{k}")
13
+ end
14
+ end
15
15
 
16
- def inspect
17
- "#<#{self.class.name} #{application_name.inspect}, #{device_token.inspect}, #{payload.inspect}>"
18
- end
16
+ def inspect
17
+ "#<#{self.class.name} #{application_name.inspect}, #{device_token.inspect}, #{payload.inspect}>"
18
+ end
19
19
 
20
- def to_s
21
- "#{device_token.inspect}, #{payload.inspect}"
22
- end
20
+ def to_s
21
+ "#{device_token.inspect}, #{payload.inspect}"
22
+ end
23
23
 
24
- def to_hash
25
- {:application_name => application_name, :device_token => device_token, :payload => payload}
26
- end
24
+ def to_hash
25
+ {:application_name => application_name, :device_token => device_token, :payload => payload}
26
+ end
27
27
 
28
- # SSL Configuration
29
- # open Keychain Access, and export the "Apple Development Push" certificate associated with your app in p12 format
30
- # Convert the certificate to PEM using openssl:
31
- # openssl pkcs12 -in mycert.p12 -out client-cert.pem -nodes -clcerts
28
+ # SSL Configuration
29
+ # open Keychain Access, and export the "Apple Development Push" certificate associated with your app in p12 format
30
+ # Convert the certificate to PEM using openssl:
31
+ # openssl pkcs12 -in mycert.p12 -out client-cert.pem -nodes -clcerts
32
32
 
33
- # To use with cross application push
34
- # Notification.new(:user => user, :current_product => product, :cross_app => { :capabilities => 'cross_app', :payload => '{aps.....}' })
33
+ # To use with cross application push
34
+ # Notification.new(:user => user, :current_product => product, :cross_app => { :capabilities => 'cross_app', :payload => '{aps.....}' })
35
35
 
36
- #
37
- # https://developer.apple.com/iphone/prerelease/library/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/ApplePushService/ApplePushService.html#//apple_ref/doc/uid/TP40008194-CH100-SW1
38
- #
39
- # Table 2-1 Keys and values of the aps dictionary
40
- # alert | string or dictionary
41
- # => If this property is included, iPhone OS displays a standard alert. You may specify a string as the value of alert or a dictionary as its value.
42
- # => If you specify a string, it becomes the message text of an alert with two buttons: Close and View. If the user taps View, the application is launched.
43
- # => Alternatively, you can specify a dictionary as the value of alert. See Table 2-2 for descriptions of the keys of this dictionary.
44
- #
45
- # badge | number
46
- # => The number to display as the badge of the application icon. If this property is absent, any badge number currently shown is removed.
47
- #
48
- # sound | string
49
- # => The name of a sound file in the application bundle. The sound in this file is played as an alert.
50
- # => If the sound file doesn’t exist or default is specified as the value, the default alert sound is played.
51
- # => The audio must be in one of the audio data formats that are compatible with system sounds; see “Preparing Custom Alert Sounds” for details.
52
- #
53
- # Table 2-2 Child properties of the alert property
54
- # body | string
55
- # => The text of the alert message.
56
- #
57
- # action-loc-key | string or null
58
- # => If a string is specified, displays an alert with two buttons, whose behavior is described in Table 2-1.
59
- # => However, iPhone OS uses the string as a key to get a localized string in the current localization to use for the right button’s title instead of “View”.
60
- # => If the value is null, the system displays an alert with a single OK button that simply dismisses the alert when tapped.
61
- #
62
- # loc-key | string
63
- # => A key to an alert-message string in a Localizable.strings file for the current localization (which is set by the user’s language preference).
64
- # => The key string can be formatted with %@ and %n$@ specifiers to take the variables specified in loc-args. See “Localized Formatted Strings” for more information.
65
- #
66
- # loc-args | array of strings
67
- # => Variable string values to appear in place of the format specifiers in loc-key. See “Localized Formatted Strings” for more information.
68
- #
69
- #
70
- # Example Result:
71
- # {
72
- # "aps" : {
73
- # "alert" : "You got your emails.",
74
- # "badge" : 9,
75
- # "sound" : "bingbong.aiff"
76
- # },
77
- # "acme1" : "bar",
78
- # "acme2" : 42
79
- #}
80
- # Or
81
- # {
82
- # "aps" : {
83
- # "alert" : {
84
- # "action-loc-key" : "PLAY",
85
- # "loc-key" : "SERVER.ERROR"
86
- # "loc-args" : ["bob", "sierra"]
87
- # },
88
- # "badge" : 9,
89
- # "sound" : "bingbong.aiff"
90
- # },
91
- # "acme1" : "bar",
92
- # "acme2" : 42
93
- #}
94
- def self.to_payload(alert = nil, badge = nil, sound = nil, app_data = nil)
95
- result = ActiveSupport::OrderedHash.new
96
- result['aps'] = ActiveSupport::OrderedHash.new
97
- result['aps']['alert'] = self.format_alert(alert) unless alert.blank?
98
- result['aps']['badge'] = badge.to_i unless badge.blank?
99
- result['aps']['sound'] = sound unless sound.blank?
100
- result.merge!(app_data) unless app_data.blank?
101
- self.to_json(result)
102
- end
36
+ #
37
+ # https://developer.apple.com/iphone/prerelease/library/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/ApplePushService/ApplePushService.html#//apple_ref/doc/uid/TP40008194-CH100-SW1
38
+ #
39
+ # Table 2-1 Keys and values of the aps dictionary
40
+ # alert | string or dictionary
41
+ # => If this property is included, iPhone OS displays a standard alert. You may specify a string as the value of alert or a dictionary as its value.
42
+ # => If you specify a string, it becomes the message text of an alert with two buttons: Close and View. If the user taps View, the application is launched.
43
+ # => Alternatively, you can specify a dictionary as the value of alert. See Table 2-2 for descriptions of the keys of this dictionary.
44
+ #
45
+ # badge | number
46
+ # => The number to display as the badge of the application icon. If this property is absent, any badge number currently shown is removed.
47
+ #
48
+ # sound | string
49
+ # => The name of a sound file in the application bundle. The sound in this file is played as an alert.
50
+ # => If the sound file doesn’t exist or default is specified as the value, the default alert sound is played.
51
+ # => The audio must be in one of the audio data formats that are compatible with system sounds; see “Preparing Custom Alert Sounds” for details.
52
+ #
53
+ # Table 2-2 Child properties of the alert property
54
+ # body | string
55
+ # => The text of the alert message.
56
+ #
57
+ # action-loc-key | string or null
58
+ # => If a string is specified, displays an alert with two buttons, whose behavior is described in Table 2-1.
59
+ # => However, iPhone OS uses the string as a key to get a localized string in the current localization to use for the right button’s title instead of “View”.
60
+ # => If the value is null, the system displays an alert with a single OK button that simply dismisses the alert when tapped.
61
+ #
62
+ # loc-key | string
63
+ # => A key to an alert-message string in a Localizable.strings file for the current localization (which is set by the user’s language preference).
64
+ # => The key string can be formatted with %@ and %n$@ specifiers to take the variables specified in loc-args. See “Localized Formatted Strings” for more information.
65
+ #
66
+ # loc-args | array of strings
67
+ # => Variable string values to appear in place of the format specifiers in loc-key. See “Localized Formatted Strings” for more information.
68
+ #
69
+ #
70
+ # Example Result:
71
+ # {
72
+ # "aps" : {
73
+ # "alert" : "You got your emails.",
74
+ # "badge" : 9,
75
+ # "sound" : "bingbong.aiff"
76
+ # },
77
+ # "acme1" : "bar",
78
+ # "acme2" : 42
79
+ #}
80
+ # Or
81
+ # {
82
+ # "aps" : {
83
+ # "alert" : {
84
+ # "action-loc-key" : "PLAY",
85
+ # "loc-key" : "SERVER.ERROR"
86
+ # "loc-args" : ["bob", "sierra"]
87
+ # },
88
+ # "badge" : 9,
89
+ # "sound" : "bingbong.aiff"
90
+ # },
91
+ # "acme1" : "bar",
92
+ # "acme2" : 42
93
+ #}
94
+ def self.to_payload(alert = nil, badge = nil, sound = nil, app_data = nil)
95
+ result = ActiveSupport::OrderedHash.new
96
+ result['aps'] = ActiveSupport::OrderedHash.new
97
+ result['aps']['alert'] = self.format_alert(alert) unless alert.blank?
98
+ result['aps']['badge'] = badge.to_i unless badge.blank?
99
+ result['aps']['sound'] = sound unless sound.blank?
100
+ result.merge!(app_data) unless app_data.blank?
101
+ self.to_json(result)
102
+ end
103
103
 
104
- #
105
- # Create an ordered hash of the data in the given alert hash
106
- #
107
- def self.format_alert(alert)
108
- if alert.is_a? Hash
109
- result = ActiveSupport::OrderedHash.new
110
- result['action-loc-key'] = alert['action-loc-key'] unless alert['action-loc-key'].blank?
111
- result['loc-key'] = alert['loc-key'] unless alert['loc-key'].blank?
112
- unless alert['loc-args'].blank?
113
- if alert['loc-args'].is_a? Hash
114
- result['loc-args'] = Array.new(alert['loc-args'].size)
115
- alert['loc-args'].map do |key,value|
116
- result['loc-args'][key.to_i] = value
104
+ #
105
+ # Create an ordered hash of the data in the given alert hash
106
+ #
107
+ def self.format_alert(alert)
108
+ if alert.is_a? Hash
109
+ result = ActiveSupport::OrderedHash.new
110
+ result['action-loc-key'] = alert['action-loc-key'] unless alert['action-loc-key'].blank?
111
+ result['loc-key'] = alert['loc-key'] unless alert['loc-key'].blank?
112
+ unless alert['loc-args'].blank?
113
+ if alert['loc-args'].is_a? Hash
114
+ result['loc-args'] = Array.new(alert['loc-args'].size)
115
+ alert['loc-args'].map do |key,value|
116
+ result['loc-args'][key.to_i] = value
117
+ end
118
+ else
119
+ result['loc-args'] = alert['loc-args']
120
+ end
117
121
  end
122
+ return result
118
123
  else
119
- result['loc-args'] = alert['loc-args']
124
+ return alert
120
125
  end
121
126
  end
122
- return result
123
- else
124
- return alert
125
- end
126
- end
127
127
 
128
- #
129
- # Generate a JSON string from the given Hash/Array which does not screw up the ordering of the Hash
130
- #
131
- def self.to_json(hash)
132
- if hash.is_a? Hash
133
- hash_keys = hash.keys
128
+ #
129
+ # Generate a JSON string from the given Hash/Array which does not screw up the ordering of the Hash
130
+ #
131
+ def self.to_json(hash)
132
+ if hash.is_a? Hash
133
+ hash_keys = hash.keys
134
134
 
135
- result = '{'
136
- result << hash_keys.map do |key|
137
- if hash[key].is_a?(Hash) || hash[key].is_a?(Array)
138
- "#{key.to_s.to_json}:#{to_json(hash[key])}"
139
- else
140
- "#{key.to_s.to_json}:#{hash[key].to_json}"
135
+ result = '{'
136
+ result << hash_keys.map do |key|
137
+ if hash[key].is_a?(Hash) || hash[key].is_a?(Array)
138
+ "#{key.to_s.to_json}:#{to_json(hash[key])}"
139
+ else
140
+ "#{key.to_s.to_json}:#{hash[key].to_json}"
141
+ end
142
+ end * ','
143
+ result << '}'
144
+ elsif hash.is_a? Array
145
+ result = '['
146
+ result << hash.map do |value|
147
+ if value.is_a?(Hash) || value.is_a?(Array)
148
+ "#{to_json(value)}"
149
+ else
150
+ value.to_json
151
+ end
152
+ end * ','
153
+ result << ']'
141
154
  end
142
- end * ','
143
- result << '}'
144
- elsif hash.is_a? Array
145
- result = '['
146
- result << hash.map do |value|
147
- if value.is_a?(Hash) || value.is_a?(Array)
148
- "#{to_json(value)}"
149
- else
150
- value.to_json
151
- end
152
- end * ','
153
- result << ']'
154
- end
155
- end
155
+ end
156
156
 
157
- #
158
- # The message formatted for sending in binary
159
- #
160
- def formatted
161
- Resque::Plugins::Aps::Notification.format_message_for_sending(self.device_token, self.payload)
162
- end
157
+ #
158
+ # The message formatted for sending in binary
159
+ #
160
+ def formatted
161
+ Resque::Plugins::Aps::Notification.format_message_for_sending(self.device_token, self.payload, self.batch_id, self.expiry)
162
+ end
163
163
 
164
- #
165
- # A HEX dump of the formatted message so that you can debug the binary data
166
- #
167
- def to_hex
168
- formatted.unpack('H*')
169
- end
164
+ #
165
+ # A HEX dump of the formatted message so that you can debug the binary data
166
+ #
167
+ def to_hex
168
+ formatted.unpack('H*')
169
+ end
170
170
 
171
- #
172
- # HEX version of the device token
173
- #
174
- def self.device_token_hex(device_token)
175
- #self.device_token
176
- [device_token.gsub(' ', '')].pack('H*')
177
- end
171
+ #
172
+ # HEX version of the device token
173
+ #
174
+ def self.device_token_hex(device_token)
175
+ #self.device_token
176
+ [device_token.gsub(' ', '')].pack('H*')
177
+ end
178
178
 
179
- #
180
- # Combine the device token and JSON into a binary package to send.
181
- #
182
- def self.format_message_for_sending(device_token, json)
183
- token_hex = self.device_token_hex(device_token)
184
- tl = [token_hex.length].pack('n')
185
- # puts("token length [#{tl.unpack('H*')}]")
186
- # puts("device token [#{token_hex.unpack('H*')}]")
187
- # logger.debug "Formatting #{json} for #{self.device_token}"
188
- jl = [json.length].pack('n')
189
- # puts("json length [#{jl.unpack('H*')}]")
190
- # puts("json [#{json}]")
191
- "\0#{tl}#{token_hex}#{jl}#{json}"
192
- # "\0\0 #{token_hex}\0#{json.length.chr}#{json}"
193
- end
194
- end
179
+ #
180
+ # Combine the device token and JSON into a binary package to send.
181
+ #
182
+ def self.format_message_for_sending(device_token, json, batch_id = nil, expiry = nil)
183
+ token_hex = self.device_token_hex(device_token)
184
+ tl = [token_hex.length].pack('n')
185
+ # puts("token length [#{tl.unpack('H*')}]")
186
+ # puts("device token [#{token_hex.unpack('H*')}]")
187
+ # logger.debug "Formatting #{json} for #{self.device_token}"
188
+ jl = [json.length].pack('n')
189
+ # puts("json length [#{jl.unpack('H*')}]")
190
+ # puts("json [#{json}]")
191
+ unless batch_id
192
+ "\0#{tl}#{token_hex}#{jl}#{json}"
193
+ # "\0\0 #{token_hex}\0#{json.length.chr}#{json}"
194
+ else
195
+ bid = [batch_id].pack('N')
196
+ exp = [expiry].pack('N')
197
+ "\1#{bid}#{exp}#{tl}#{token_hex}#{jl}#{json}"
198
+ end
199
+ end
200
+ end
201
+ end
195
202
  end
196
- end
197
203
  end
@@ -1,7 +1,7 @@
1
1
  module Resque
2
2
  module Plugins
3
3
  module Aps
4
- Version = '0.9.9'
4
+ Version = '0.9.10'
5
5
  end
6
6
  end
7
7
  end
data/lib/resque_aps.rb CHANGED
@@ -14,126 +14,146 @@ module Resque
14
14
  module Plugins
15
15
  module Aps
16
16
 
17
- def logger=(logger)
18
- @logger = logger
19
- end
20
-
21
- def logger
22
- unless @logger
23
- @logger = Logger.new(STDOUT)
24
- @logger.level = Logger::WARN
25
- end
26
- @logger
27
- end
28
-
29
- def aps_gateway_host=(host)
30
- @aps_gateway_host = host
31
- end
32
-
33
- def aps_gateway_host
34
- @aps_gateway_host ||= "gateway.sandbox.push.apple.com"
35
- end
36
-
37
- def aps_gateway_port=(port)
38
- @aps_gateway_port = port
39
- end
40
-
41
- def aps_gateway_port
42
- @aps_gateway_port ||= 2195
43
- end
44
-
45
- def aps_feedback_host=(host)
46
- @aps_feedback_host = host
47
- end
48
-
49
- def aps_feedback_host
50
- @aps_feedback_host ||= "feedback.sandbox.push.apple.com"
51
- end
52
-
53
- def aps_feedback_port=(port)
54
- @aps_feedback_port = port
55
- end
56
-
57
- def aps_feedback_port
58
- @aps_feedback_port ||= 2196
59
- end
60
-
61
- def aps_queue_size_lower=(size)
62
- @aps_queue_size_lower = size
63
- end
64
-
65
- def aps_queue_size_lower
66
- @aps_queue_size_lower ||= 0
67
- end
68
-
69
- def aps_queue_size_upper=(size)
70
- @aps_queue_size_upper = size
71
- end
72
-
73
- def aps_queue_size_upper
74
- @aps_queue_size_upper ||= 500
75
- end
76
-
77
- def enqueue_aps(application_name, notification)
78
- count = aps_notification_count_for_application(application_name)
79
- redis.rpush(aps_application_queue_key(application_name), encode(notification.to_hash))
80
- enqueue(Resque::Plugins::Aps::Application, application_name) if count <= aps_queue_size_lower || count >= aps_queue_size_upper
81
- true
82
- end
17
+ def logger=(logger)
18
+ @logger = logger
19
+ end
20
+
21
+ def logger
22
+ unless @logger
23
+ @logger = Logger.new(STDOUT)
24
+ @logger.level = Logger::WARN
25
+ end
26
+ @logger
27
+ end
28
+
29
+ def aps_gateway_host=(host)
30
+ @aps_gateway_host = host
31
+ end
32
+
33
+ def aps_gateway_host
34
+ @aps_gateway_host ||= "gateway.sandbox.push.apple.com"
35
+ end
36
+
37
+ def aps_gateway_port=(port)
38
+ @aps_gateway_port = port
39
+ end
40
+
41
+ def aps_gateway_port
42
+ @aps_gateway_port ||= 2195
43
+ end
44
+
45
+ def aps_feedback_host=(host)
46
+ @aps_feedback_host = host
47
+ end
48
+
49
+ def aps_feedback_host
50
+ @aps_feedback_host ||= "feedback.sandbox.push.apple.com"
51
+ end
52
+
53
+ def aps_feedback_port=(port)
54
+ @aps_feedback_port = port
55
+ end
56
+
57
+ def aps_feedback_port
58
+ @aps_feedback_port ||= 2196
59
+ end
60
+
61
+ def aps_queue_size_upper=(size)
62
+ @aps_queue_size_upper = size
63
+ end
64
+
65
+ def aps_queue_size_upper
66
+ @aps_queue_size_upper ||= 1000
67
+ end
68
+
69
+ def aps_application_job_limit=(size)
70
+ @aps_queue_size_upper = size
71
+ end
72
+
73
+ def aps_application_job_limit
74
+ @aps_application_job_limit ||= 5
75
+ end
76
+
77
+ def aps_applications_queued_count(application_name)
78
+ redis.get(aps_application_queued_key(application_name)) || 0
79
+ end
80
+
81
+ def enqueue_aps_application(application_name, override = false)
82
+ count_apps = aps_applications_queued_count(application_name)
83
+ count_not = aps_notification_count_for_application(application_name)
84
+ if override || count_apps <= 0 || (count_apps < aps_application_job_limit && (count_not > aps_queue_size_upper && count_not % (aps_queue_size_upper / 10) == 0))
85
+ enqueue(Resque::Plugins::Aps::Application, application_name)
86
+ redis.incr(aps_application_queued_key(application_name))
87
+ end
88
+ end
89
+
90
+ def dequeue_aps_application(application_name)
91
+ redis.decr(aps_application_queued_key(application_name)) if aps_applications_queued_count(application_name) > 0
92
+ end
93
+
94
+ def enqueue_aps(application_name, notification)
95
+ redis.rpush(aps_application_queue_key(application_name), encode(notification.to_hash))
96
+ enqueue_aps_application(application_name)
97
+ true
98
+ end
83
99
 
84
- def dequeue_aps(application_name)
85
- h = decode(redis.lpop(aps_application_queue_key(application_name)))
86
- return Resque::Plugins::Aps::Notification.new(h) if h
87
- nil
88
- end
89
-
90
- # Returns the number of queued notifications for a given application
91
- def aps_notification_count_for_application(application_name)
92
- redis.llen(aps_application_queue_key(application_name)).to_i
93
- end
100
+ def dequeue_aps(application_name)
101
+ h = decode(redis.lpop(aps_application_queue_key(application_name)))
102
+ return Resque::Plugins::Aps::Notification.new(h) if h
103
+ nil
104
+ end
105
+
106
+ # Returns the number of queued notifications for a given application
107
+ def aps_notification_count_for_application(application_name)
108
+ redis.llen(aps_application_queue_key(application_name)).to_i
109
+ end
94
110
 
95
- # Returns an array of queued notifications for the given application
96
- def aps_notifications_for_application(application_name, start = 0, count = 1)
97
- r = redis.lrange(aps_application_queue_key(application_name), start, count)
98
- if r
99
- r.map { |h| Resque::Plugins::Aps::Notification.new(decode(h)) }
100
- else
101
- []
102
- end
103
- end
111
+ # Returns an array of queued notifications for the given application
112
+ def aps_notifications_for_application(application_name, start = 0, count = 1)
113
+ r = redis.lrange(aps_application_queue_key(application_name), start, count)
114
+ if r
115
+ r.map { |h| Resque::Plugins::Aps::Notification.new(decode(h)) }
116
+ else
117
+ []
118
+ end
119
+ end
104
120
 
105
- def create_aps_application(name, cert_file, cert_passwd = nil)
106
- redis.set(aps_application_key(name), encode({'name' => name, 'cert_file' => cert_file, 'cert_passwd' => cert_passwd}))
107
- redis.sadd(:aps_applications, name)
108
- end
109
-
110
- def aps_application(name)
111
- h = decode(redis.get(aps_application_key(name)))
112
- return Resque::Plugins::Aps::Application.new(h) if h
113
- nil
114
- end
115
-
116
- # Returns an array of applications based on start and count
117
- def aps_application_names(start = 0, count = 1)
118
- a = redis.smembers(:aps_applications)
119
- return a if count == 0
120
- ret = a[start..(start + count)]
121
- return [] unless ret
122
- ret
123
- end
121
+ def create_aps_application(name, cert_file, cert_passwd = nil)
122
+ redis.set(aps_application_key(name), encode({'name' => name, 'cert_file' => cert_file, 'cert_passwd' => cert_passwd}))
123
+ redis.sadd(:aps_applications, name)
124
+ end
125
+
126
+ def aps_application(name)
127
+ h = decode(redis.get(aps_application_key(name)))
128
+ return Resque::Plugins::Aps::Application.new(h) if h
129
+ nil
130
+ end
131
+
132
+ # Returns an array of applications based on start and count
133
+ def aps_application_names(start = 0, count = 1)
134
+ a = redis.smembers(:aps_applications)
135
+ return a if count == 0
136
+ ret = a[start..(start + count)]
137
+ return [] unless ret
138
+ ret
139
+ end
124
140
 
125
- # Returns the number of application queues
126
- def aps_applications_count
127
- redis.smembers(:aps_applications).size
128
- end
141
+ # Returns the number of application queues
142
+ def aps_applications_count
143
+ redis.smembers(:aps_applications).size
144
+ end
129
145
 
130
- def aps_application_key(application_name)
131
- "aps:application:#{application_name}"
132
- end
133
-
134
- def aps_application_queue_key(application_name)
135
- "#{aps_application_key(application_name)}:queue"
136
- end
146
+ def aps_application_key(application_name)
147
+ "aps:application:#{application_name}"
148
+ end
149
+
150
+ def aps_application_queued_key(application_name)
151
+ "#{aps_application_key(application_name)}:queued"
152
+ end
153
+
154
+ def aps_application_queue_key(application_name)
155
+ "#{aps_application_key(application_name)}:queue"
156
+ end
137
157
  end
138
158
  end
139
159
  end
metadata CHANGED
@@ -1,12 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: resque-aps
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: false
5
- segments:
6
- - 0
7
- - 9
8
- - 9
9
- version: 0.9.9
4
+ version: 0.9.10
10
5
  platform: ruby
11
6
  authors:
12
7
  - Ashley Martens
@@ -14,73 +9,59 @@ autorequire:
14
9
  bindir: bin
15
10
  cert_chain: []
16
11
 
17
- date: 2010-08-10 00:00:00 -07:00
12
+ date: 2010-08-31 00:00:00 -07:00
18
13
  default_executable:
19
14
  dependencies:
20
15
  - !ruby/object:Gem::Dependency
21
16
  name: redis
22
- prerelease: false
23
- requirement: &id001 !ruby/object:Gem::Requirement
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
24
20
  requirements:
25
21
  - - ">="
26
22
  - !ruby/object:Gem::Version
27
- segments:
28
- - 1
29
- - 0
30
- - 7
31
23
  version: 1.0.7
32
- type: :runtime
33
- version_requirements: *id001
24
+ version:
34
25
  - !ruby/object:Gem::Dependency
35
26
  name: resque
36
- prerelease: false
37
- requirement: &id002 !ruby/object:Gem::Requirement
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
38
30
  requirements:
39
31
  - - ">="
40
32
  - !ruby/object:Gem::Version
41
- segments:
42
- - 1
43
- - 5
44
- - 0
45
33
  version: 1.5.0
46
- type: :runtime
47
- version_requirements: *id002
34
+ version:
48
35
  - !ruby/object:Gem::Dependency
49
36
  name: jeweler
50
- prerelease: false
51
- requirement: &id003 !ruby/object:Gem::Requirement
37
+ type: :development
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
52
40
  requirements:
53
41
  - - ">="
54
42
  - !ruby/object:Gem::Version
55
- segments:
56
- - 0
57
43
  version: "0"
58
- type: :development
59
- version_requirements: *id003
44
+ version:
60
45
  - !ruby/object:Gem::Dependency
61
46
  name: mocha
62
- prerelease: false
63
- requirement: &id004 !ruby/object:Gem::Requirement
47
+ type: :development
48
+ version_requirement:
49
+ version_requirements: !ruby/object:Gem::Requirement
64
50
  requirements:
65
51
  - - ">="
66
52
  - !ruby/object:Gem::Version
67
- segments:
68
- - 0
69
53
  version: "0"
70
- type: :development
71
- version_requirements: *id004
54
+ version:
72
55
  - !ruby/object:Gem::Dependency
73
56
  name: rack-test
74
- prerelease: false
75
- requirement: &id005 !ruby/object:Gem::Requirement
57
+ type: :development
58
+ version_requirement:
59
+ version_requirements: !ruby/object:Gem::Requirement
76
60
  requirements:
77
61
  - - ">="
78
62
  - !ruby/object:Gem::Version
79
- segments:
80
- - 0
81
63
  version: "0"
82
- type: :development
83
- version_requirements: *id005
64
+ version:
84
65
  description: |-
85
66
  Queuing system for Apple's Push Service on top of Resque.
86
67
  Adds methods enqueue_aps to queue a notification message.
@@ -132,20 +113,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
132
113
  requirements:
133
114
  - - ">="
134
115
  - !ruby/object:Gem::Version
135
- segments:
136
- - 0
137
116
  version: "0"
117
+ version:
138
118
  required_rubygems_version: !ruby/object:Gem::Requirement
139
119
  requirements:
140
120
  - - ">="
141
121
  - !ruby/object:Gem::Version
142
- segments:
143
- - 0
144
122
  version: "0"
123
+ version:
145
124
  requirements: []
146
125
 
147
126
  rubyforge_project:
148
- rubygems_version: 1.3.6
127
+ rubygems_version: 1.3.5
149
128
  signing_key:
150
129
  specification_version: 3
151
130
  summary: Queuing system for Apple's Push Service on top of Resque