Permalink
Browse files

initial commit

  • Loading branch information...
0 parents commit 7361684c3343cb19ac6e44ff3dc80e265f3258af @tompesman committed Jun 15, 2012
@@ -0,0 +1,4 @@
+.bundle/
+log/*.log
+pkg/
+Gemfile.lock
16 Gemfile
@@ -0,0 +1,16 @@
+source "http://rubygems.org"
+
+# Declare your gem's dependencies in push-c2dm.gemspec.
+# Bundler will treat runtime dependencies like base dependencies, and
+# development dependencies will be added by default to the :development group.
+gemspec
+
+# Declare any dependencies that are still in development here instead of in
+# your gemspec. These might include edge Rails or gems from your path or
+# Git. Remember to move these dependencies to your gemspec before releasing
+# your gem to rubygems.org.
+
+# To use debugger
+# gem 'ruby-debug19', :require => 'ruby-debug'
+
+gem 'push-core', :path => "~/code/push/push-core"
@@ -0,0 +1,20 @@
+Copyright 2012 YOURNAME
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,3 @@
+# PushC2dm
+
+This project rocks and uses MIT-LICENSE.
@@ -0,0 +1,7 @@
+require 'net/http'
+require 'net/https'
+require 'push-c2dm/version'
+require 'push/message_c2dm'
+require 'push/feedback_c2dm'
+require 'push/daemon/c2dm'
+require 'push/daemon/c2dm_support/connection_c2dm'
@@ -0,0 +1,3 @@
+module PushC2dm
+ VERSION = "0.0.1.pre"
+end
@@ -0,0 +1,24 @@
+module Push
+ module Daemon
+ class C2dm
+ attr_accessor :configuration
+ def initialize(options)
+ self.configuration = options
+ end
+
+ def pushconnections
+ self.configuration[:connections]
+ end
+
+ def totalconnections
+ pushconnections
+ end
+
+ def connectiontype
+ C2dmSupport::ConnectionC2dm
+ end
+
+ def stop; end
+ end
+ end
+end
@@ -0,0 +1,90 @@
+module Push
+ module Daemon
+ module C2dmSupport
+ class ConnectionC2dm
+ attr_reader :response, :name
+ AUTH_URL = "https://www.google.com/accounts/ClientLogin"
+ PUSH_URL = "https://android.apis.google.com/c2dm/send"
+
+ def initialize(provider, i)
+ @provider = provider
+ @name = "ConnectionC2dm #{i}"
+
+ @email = @provider.configuration[:email]
+ @password = @provider.configuration[:password]
+ end
+
+ def connect
+ @auth_token = fetch_auth_token
+ @last_use = Time.now
+ uri = URI.parse(PUSH_URL)
+ @connection = open_http(uri.host, uri.port)
+ @connection.start
+ Push::Daemon.logger.info("[#{@name}] Connected to #{PUSH_URL}")
+ end
+
+ def write(data)
+ @response = notification_request(data)
+
+ # the response can be one of three codes:
+ # 200 (success)
+ # 401 (auth failed)
+ # 503 (retry later with exponential backoff)
+ # see more documentation here: http://code.google.com/android/c2dm/#testing
+ if @response.code.eql? "200"
+ # look for the header 'Update-Client-Auth' in the response you get after sending
+ # a message. It indicates that this is the token to be used for the next message to send.
+ @response.header.each_header do |key, value|
+ if key.capitalize == "Update-Client-Auth".capitalize
+ Push::Daemon.logger.info("[#{@name}] Received new authentication token")
+ @auth_token = value
+ end
+ end
+
+ elsif @response.code.eql? "401"
+ # auth failed. Refresh auth key and requeue
+ @auth_token = fetch_auth_token
+ @response = notification_request(data)
+
+ elsif response.code.eql? "503"
+ # service un-available.
+ end
+ end
+
+ private
+
+ def open_http(host, port)
+ http = Net::HTTP.new(host, port)
+ http.use_ssl = true
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
+ return http
+ end
+
+ def fetch_auth_token
+ data = "accountType=HOSTED_OR_GOOGLE&Email=#{@email}&Passwd=#{@password}&service=ac2dm&source=push"
+ headers = { 'Content-Type' => 'application/x-www-form-urlencoded' }
+ uri = URI.parse(AUTH_URL)
+ http = open_http(uri.host, uri.port)
+ response = http.post(uri.path, data, headers)
+ return response.body[/Auth=(.*)/, 1]
+ end
+
+ def notification_request(data)
+ headers = { "Authorization" => "GoogleLogin auth=#{@auth_token}",
+ "Content-type" => "application/x-www-form-urlencoded",
+ "Content-length" => "#{data.length}" }
+ uri = URI.parse(PUSH_URL)
+
+ # Timeout on the http connection is 5 minutes, reconnect after 5 minutes
+ if @last_use + 5.minutes < Time.now
+ @connection.finish
+ @connection.start
+ end
+ @last_use = Time.now
+
+ @connection.post(uri.path, data, headers)
+ end
+ end
+ end
+ end
+end
@@ -0,0 +1,5 @@
+module Push
+ class FeedbackC2dm < Push::Feedback
+
+ end
+end
@@ -0,0 +1,52 @@
+module Push
+ class MessageC2dm < Push::Message
+ validates :collapse_key, :presence => true
+ # TODO: validates max size -> The message size limit is 1024 bytes.
+ # TODO" QuotaExceeded — Too many messages sent by the sender. Retry after a while.
+ # TODO: DeviceQuotaExceeded — Too many messages sent by the sender to a specific device. Retry after a while.
+
+ store :properties, accessors: [:collapse_key, :delay_when_idle, :payload ]
+
+ def to_message
+ as_hash.map{|k, v| "&#{k}=#{URI.escape(v.to_s)}"}.reduce{|k, v| k + v}
+ end
+
+ def use_connection
+ Push::Daemon::C2dmSupport::ConnectionC2dm
+ end
+
+ private
+
+ def as_hash
+ json = ActiveSupport::OrderedHash.new
+ json['registration_id'] = self.device
+ json['collapse_key'] = self.collapse_key
+ json['delay_when_idle'] = "1" if self.delay_when_idle == true
+ self.payload.each { |k, v| json["data.#{k.to_s}"] = v.to_s } if self.payload
+ json
+ end
+
+ def check_for_error(connection)
+ response = connection.response
+ error_type = response.body[/Error=(.*)/, 1]
+ if response.code.eql? "200" and error_type
+ error = Push::DeliveryError.new(response.code, id, error_type, "C2DM")
+
+ # if error_type is one of the following, the registration_id (device) should
+ # not be used anymore
+ if ["InvalidRegistration", "NotRegistered"].index(error_type)
+ with_database_reconnect_and_retry(connection.name) do
+ Push::FeedbackC2dm.create!(:failed_at => Time.now, :device => device)
+ end
+ end
+
+ Push::Daemon.logger.error("[#{connection.name}] Error received.")
+ raise error if error
+ elsif !response.code.eql? "200"
+ error = Push::DeliveryError.new(response.code, id, response.description, "C2DM")
+ Push::Daemon.logger.error("[#{connection.name}] Error received.")
+ raise error if error
+ end
+ end
+ end
+end
@@ -0,0 +1,23 @@
+$:.push File.expand_path("../lib", __FILE__)
+
+# Maintain your gem's version:
+require "push-c2dm/version"
+
+# Describe your gem and declare its dependencies:
+Gem::Specification.new do |s|
+ s.name = "push-c2dm"
+ s.version = PushC2dm::VERSION
+ s.authors = ["Tom Pesman"]
+ s.email = ["tom@tnux.net"]
+ s.homepage = "https://github.com/tompesman/push-c2dm"
+ s.summary = "C2DM (Android) part of the modular push daemon."
+ s.description = "Plugin with C2DM specific push information."
+
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
+ s.files = `git ls-files lib`.split("\n") + ["README.md", "MIT-LICENSE"]
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
+ s.require_paths = ["lib"]
+
+ s.add_dependency "push-core", "0.0.1.pre"
+ s.add_development_dependency "sqlite3"
+end

0 comments on commit 7361684

Please sign in to comment.