sparks 0.3 → 0.4

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.
@@ -1,5 +1,14 @@
1
1
  # Sparks changelog
2
2
 
3
+ ## Version 0.4 (20 January 2013)
4
+
5
+ - broke everything. sorry.
6
+ - streaming now works for more than a few minutes!
7
+ - handles both very small and very large JSON objects
8
+ - uses Yajl for streaming JSON parsing
9
+ - uses net-http-persistent to make repeated requests much faster
10
+ - removed Room and Campfire classes, now super...minimal. or something.
11
+
3
12
  ## Version 0.3 (7 October 2011)
4
13
 
5
14
  - added support for play and tweet message types (@imajes)
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
@@ -1,124 +1,170 @@
1
- require 'uri'
2
- require 'json'
3
- require 'net/https'
1
+ require 'logger'
2
+ require 'net/http/persistent'
3
+ require 'yajl'
4
4
 
5
5
  # sparks, a tiny Campfire library
6
6
 
7
7
  # Usage:
8
- # c = Sparks::Campfire.new('subdomain', 'abc123')
9
- # r = c.room_named "Room Name"
10
- # r.say "hi there"
11
- # r.paste "class Foo\nend"
12
- module Sparks
13
- class Room
14
- attr_accessor :id
15
-
16
- def initialize api, name, id
17
- @api = api
18
- @name = name
19
- @id = id
20
- end
8
+ # c = Sparks.new('subdomain', 'abc123')
9
+ # r = c.room "Room Name"
10
+ # c.say r["id"], "hi there"
11
+ # c.paste r["id"], "class Foo\nend"
12
+ class Sparks
13
+ attr_reader :logger
14
+
15
+ def initialize subdomain, token, opts = {}
16
+ @base = URI("https://#{subdomain}.campfirenow.com")
17
+ @token = token
18
+ @logger = opts[:logger] || Logger.new(STDOUT)
19
+ @http = Net::HTTP::Persistent.new("sparks")
20
+ @http.ca_file = opts[:ca_file] if opts[:ca_file]
21
+ @rooms ||= {}
22
+ end
21
23
 
22
- def method_missing method, *args, &block
23
- if @api.respond_to? method
24
- args.unshift(@id)
25
- @api.send method, *args, &block
26
- end
27
- end
24
+ def me
25
+ user("me")
28
26
  end
29
27
 
30
- class Campfire
31
- attr_reader :uri, :token, :pass
28
+ def user(id)
29
+ req("/users/#{id}")[:user]
30
+ end
32
31
 
33
- def initialize subdomain, token, opts = {}
34
- @uri = URI.parse("https://#{subdomain}.campfirenow.com")
35
- @token = token
36
- @pass = 'x'
32
+ def room_named(name)
33
+ req("/rooms")[:rooms].find{|r| r[:name] == name }
34
+ end
37
35
 
38
- @http = Net::HTTP.new(uri.host, uri.port)
39
- @http.use_ssl = true
40
- @http.verify_mode = OpenSSL::SSL::VERIFY_PEER
41
- campfire_ca_certs = File.expand_path("../rapidssl.crt", __FILE__)
42
- @http.ca_file = opts[:ca_file] || campfire_ca_certs
36
+ def room(id)
37
+ @rooms[id] ||= begin
38
+ req("/room/#{id.to_s}")[:room]
43
39
  end
40
+ end
44
41
 
45
- def room_named name
46
- r = rooms.find{|r| r["name"] == name }
47
- r ? Room.new(self, name, r["id"]) : nil
48
- end
42
+ def join(id)
43
+ req("/room/#{id}/join", :post)
44
+ end
49
45
 
50
- def rooms
51
- @http.start do |http|
52
- req = Net::HTTP::Get.new "/rooms.json"
53
- req['Content-Type'] = 'application/json'
54
- req.basic_auth token, pass
55
- begin
56
- JSON.parse(http.request(req).body)["rooms"]
57
- rescue JSON::ParserError
58
- {}
59
- end
60
- end
61
- end
46
+ def leave(id)
47
+ req("/room/#{id}/leave", :post)
48
+ end
62
49
 
63
- def post room_id, message, type = nil
64
- data = {'body' => message}
65
- data.merge!('type' => type) if type
66
- json = JSON.generate('message' => data)
50
+ def speak(id, message, type = 'TextMessage')
51
+ data = {'body' => message, 'type' => type}
52
+ json = Yajl::Encoder.encode('message' => data)
53
+ req("/room/#{id}/speak", json)
54
+ end
55
+ alias_method :say, :speak
67
56
 
68
- @http.start do |http|
69
- req = Net::HTTP::Post.new "/room/#{room_id}/speak.json"
70
- req['Content-Type'] = 'application/json'
71
- req.basic_auth token, pass
72
- http.request(req, json)
73
- end
74
- end
57
+ def paste(id, message)
58
+ speak id, message, 'PasteMessage'
59
+ end
75
60
 
76
- def speak room_id, message
77
- post room_id, message, 'TextMessage'
78
- end
79
- alias_method :say, :speak
61
+ def play(id, message)
62
+ speak id, message, 'SoundMessage'
63
+ end
80
64
 
81
- def paste room_id, message
82
- post room_id, message, 'PasteMessage'
83
- end
65
+ def tweet(id, message)
66
+ speak id, message, 'TweetMessage'
67
+ end
84
68
 
85
- def play room_id, message
86
- post room_id, message, 'SoundMessage'
87
- end
88
-
89
- def tweet room_id, message
90
- post room_id, message, 'TweetMessage'
91
- end
69
+ def watch(id)
70
+ # campfire won't let you stream until you've joined the room
71
+ join(id)
72
+
73
+ # don't allow retries if we've never connected before.
74
+ retries ||= nil
75
+
76
+ uri = URI("https://streaming.campfirenow.com") + "/room/#{id}/live.json"
77
+ logger.debug "Ready to stream from #{uri}"
78
+
79
+ request = Net::HTTP::Get.new(uri.path)
80
+ request.basic_auth @token, "x"
81
+
82
+ @http.request(uri, request) do |response|
83
+ logger.debug "Connected and streaming from room #{id}"
84
+ # connected! allow retries.
85
+ retries = 0
92
86
 
93
- def join room_id
94
- @http.start do |http|
95
- req = Net::HTTP::Post.new "/room/#{room_id}/join.xml"
96
- req.basic_auth token, pass
97
- http.request(req)
87
+ # Set up a Yajl stream parser
88
+ parser = Yajl::Parser.new(:symbolize_keys => true)
89
+ parser.on_parse_complete = -> hash { yield hash }
90
+
91
+ # Feed chunks into the stream parser
92
+ response.read_body do |chunk|
93
+ # Campfire keepalive pings
94
+ next if chunk == " "
95
+ parser << chunk
98
96
  end
99
97
  end
98
+ rescue Yajl::ParseError, # Bad JSON in the response
99
+ SystemCallError, # All Errno errors
100
+ SocketError, # Errors from socket operations
101
+ Net::HTTP::Persistent::Error, # Timeout, SSL, or connection error
102
+ Net::HTTPBadResponse, # response wasn't 2xx
103
+ Net::HTTPHeaderSyntaxError, # response header issue
104
+ Net::ProtocolError => e # not http
105
+ # pass through errors if we haven't ever connected
106
+ raise e unless retries
107
+ # if we connected at least once, try, try, again
108
+ retries += 1
109
+ logger.error "#{e.class}: #{e.message}"
110
+ logger.error "Trying to stream again in #{retries * 2}s"
111
+ sleep retries * 2
112
+ retry
113
+ end
100
114
 
101
- def watch room_id
102
- uri = URI.parse('https://streaming.campfirenow.com')
103
-
104
- x = Net::HTTP.new(uri.host, uri.port)
105
- x.use_ssl = true
106
- x.verify_mode = OpenSSL::SSL::VERIFY_NONE
107
-
108
- x.start do |http|
109
- req = Net::HTTP::Get.new "/room/#{room_id}/live.json"
110
- req.basic_auth token, pass
111
- http.request(req) do |res|
112
- res.read_body do |chunk|
113
- unless chunk.strip.empty?
114
- chunk.split("\r").each do |message|
115
- yield JSON.parse(message)
116
- end
117
- end
118
- end
119
- end
120
- end
115
+ def req(uri, body = nil)
116
+ uri = @base + (uri + ".json") unless uri.is_a?(URI)
117
+ logger.debug "#{body ? 'POST' : 'GET'} #{uri}"
118
+
119
+ if body
120
+ request = Net::HTTP::Post.new(uri.path)
121
+ request.body = body unless body == :post
122
+ else
123
+ request = Net::HTTP::Get.new(uri.path)
121
124
  end
125
+ request.content_type = "application/json"
126
+ request.basic_auth @token, "x"
127
+
128
+ retries ||= 0
129
+ response = @http.request(uri, request)
130
+ response.value # raises if response is not 2xx
131
+ parse_response(response)
132
+
133
+ rescue Net::HTTPRetriableError => e # response was 3xx
134
+ location = URI(response['location'])
135
+ logger.info "Request redirected to #{location}"
136
+ sleep 2
137
+ req(location, body)
138
+
139
+ rescue Net::HTTPServerException => e # response was 4xx
140
+ msg = "Authorization failed: HTTP #{response.code}"
141
+ msg << ": " << request.body if request.body && !request.body.empty?
142
+ raise msg
143
+
144
+ rescue SystemCallError, # All Errno errors
145
+ Net::HTTP::Persistent::Error, # Timeout, SSL, or connection error
146
+ Net::HTTPBadResponse, # response wasn't 2xx
147
+ Net::HTTPHeaderSyntaxError, # response header issue
148
+ Net::ProtocolError => e # not http
149
+ # Retry if something goes wrong
150
+ retries += 1
151
+ logger.info "Request failed: #{e.class}: #{e.message}"
152
+ logger.info "Going to retry request in #{retries * 2}s"
153
+ sleep retries * 2
154
+ retry
155
+ end
156
+
157
+ private
122
158
 
159
+ def parse_response(response)
160
+ if response.body.strip.empty?
161
+ true
162
+ else
163
+ Yajl::Parser.parse(response.body, :symbolize_keys => true)
164
+ end
165
+ rescue Yajl::ParseError
166
+ logger.debug "Couldn't parse #{res.inspect}: #{res.body.inspect}"
167
+ {}
123
168
  end
169
+
124
170
  end
@@ -1,3 +1,3 @@
1
- module Sparks
2
- VERSION = "0.3"
1
+ class Sparks
2
+ VERSION = "0.4"
3
3
  end
@@ -1,23 +1,24 @@
1
- # -*- encoding: utf-8 -*-
2
- $:.push File.expand_path("../lib", __FILE__)
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
3
  require "sparks/version"
4
4
 
5
- Gem::Specification.new do |s|
6
- s.name = "sparks"
7
- s.version = Sparks::VERSION
8
- s.platform = Gem::Platform::RUBY
9
- s.authors = ["André Arko"]
10
- s.email = ["andre@arko.net"]
11
- s.homepage = "http://github.com/indirect/sparks"
12
- s.summary = %q{A tiny Campfire client API}
13
- s.description = %q{A tiny Campfire client API that only uses the standard library}
5
+ Gem::Specification.new do |gem|
6
+ gem.name = "sparks"
7
+ gem.version = Sparks::VERSION
8
+ gem.summary = %q{A tiny Campfire client API}
9
+ gem.description = %q{Yet another Campfire client. Because oh my god so many dependencies.}
10
+ gem.authors = ["André Arko"]
11
+ gem.email = ["andre@arko.net"]
12
+ gem.homepage = "http://github.com/indirect/sparks"
14
13
 
15
- s.rubyforge_project = "sparks"
16
- s.add_dependency "json"
17
- s.add_development_dependency "bundler", "~>1.0"
14
+ gem.files = `git ls-files`.split($/)
15
+ gem.executables = gem.files.grep(%r{^bin/}) { |f| File.basename(f) }
16
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
17
+ gem.require_paths = ["lib"]
18
18
 
19
- s.files = `git ls-files`.split("\n")
20
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
21
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
22
- s.require_paths = ["lib"]
19
+ gem.add_dependency "yajl-ruby", "~> 1.1"
20
+ gem.add_dependency "net-http-persistent", "~> 2.8"
21
+
22
+ gem.add_development_dependency "rake", "~> 10.0"
23
+ gem.add_development_dependency "bundler", "~> 1.2"
23
24
  end
metadata CHANGED
@@ -1,102 +1,118 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: sparks
3
- version: !ruby/object:Gem::Version
4
- hash: 13
5
- prerelease: false
6
- segments:
7
- - 0
8
- - 3
9
- version: "0.3"
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.4'
5
+ prerelease:
10
6
  platform: ruby
11
- authors:
12
- - "Andr\xC3\xA9 Arko"
7
+ authors:
8
+ - André Arko
13
9
  autorequire:
14
10
  bindir: bin
15
11
  cert_chain: []
16
-
17
- date: 2011-10-07 00:00:00 -10:00
18
- default_executable:
19
- dependencies:
20
- - !ruby/object:Gem::Dependency
21
- name: json
12
+ date: 2013-04-04 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: yajl-ruby
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.1'
22
+ type: :runtime
22
23
  prerelease: false
23
- requirement: &id001 !ruby/object:Gem::Requirement
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.1'
30
+ - !ruby/object:Gem::Dependency
31
+ name: net-http-persistent
32
+ requirement: !ruby/object:Gem::Requirement
24
33
  none: false
25
- requirements:
26
- - - ">="
27
- - !ruby/object:Gem::Version
28
- hash: 3
29
- segments:
30
- - 0
31
- version: "0"
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '2.8'
32
38
  type: :runtime
33
- version_requirements: *id001
34
- - !ruby/object:Gem::Dependency
35
- name: bundler
36
39
  prerelease: false
37
- requirement: &id002 !ruby/object:Gem::Requirement
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '2.8'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rake
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '10.0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: bundler
64
+ requirement: !ruby/object:Gem::Requirement
38
65
  none: false
39
- requirements:
66
+ requirements:
40
67
  - - ~>
41
- - !ruby/object:Gem::Version
42
- hash: 15
43
- segments:
44
- - 1
45
- - 0
46
- version: "1.0"
68
+ - !ruby/object:Gem::Version
69
+ version: '1.2'
47
70
  type: :development
48
- version_requirements: *id002
49
- description: A tiny Campfire client API that only uses the standard library
50
- email:
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: '1.2'
78
+ description: Yet another Campfire client. Because oh my god so many dependencies.
79
+ email:
51
80
  - andre@arko.net
52
81
  executables: []
53
-
54
82
  extensions: []
55
-
56
83
  extra_rdoc_files: []
57
-
58
- files:
84
+ files:
59
85
  - .gitignore
60
86
  - CHANGELOG.md
87
+ - Gemfile
61
88
  - README.md
62
89
  - Rakefile
63
90
  - lib/rapidssl.crt
64
91
  - lib/sparks.rb
65
92
  - lib/sparks/version.rb
66
93
  - sparks.gemspec
67
- has_rdoc: true
68
94
  homepage: http://github.com/indirect/sparks
69
95
  licenses: []
70
-
71
96
  post_install_message:
72
97
  rdoc_options: []
73
-
74
- require_paths:
98
+ require_paths:
75
99
  - lib
76
- required_ruby_version: !ruby/object:Gem::Requirement
100
+ required_ruby_version: !ruby/object:Gem::Requirement
77
101
  none: false
78
- requirements:
79
- - - ">="
80
- - !ruby/object:Gem::Version
81
- hash: 3
82
- segments:
83
- - 0
84
- version: "0"
85
- required_rubygems_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ! '>='
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
107
  none: false
87
- requirements:
88
- - - ">="
89
- - !ruby/object:Gem::Version
90
- hash: 3
91
- segments:
92
- - 0
93
- version: "0"
108
+ requirements:
109
+ - - ! '>='
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
94
112
  requirements: []
95
-
96
- rubyforge_project: sparks
97
- rubygems_version: 1.3.7
113
+ rubyforge_project:
114
+ rubygems_version: 1.8.23
98
115
  signing_key:
99
116
  specification_version: 3
100
117
  summary: A tiny Campfire client API
101
118
  test_files: []
102
-