resque-aps 0.9.9 → 0.9.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -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