sparks 0.3 → 0.4

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