partygoer-client 0.0.1

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.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/partygoer-client.rb +255 -0
  3. metadata +87 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 053e21a4269bd421f557de8de80731c6133761f7
4
+ data.tar.gz: a9e251d6b5921e6405420978476aa031eea9316b
5
+ SHA512:
6
+ metadata.gz: 41a95ef97a7962e7ad3bb739fa51483144e76c207ffabc4cc094609d6dbb971139bcd6afaa36f184a21e99663efd7ba9630cbe0686437f2f4667b93d4089d86d
7
+ data.tar.gz: 52885d9f45667085d1cca488fc12b2afe534ba2c6f96b09c6d91ab47abf8298072bc9f7107719bc9f0e7fbb6bf95a45eb5224b98349f5bf18f5a2a3059df1687
@@ -0,0 +1,255 @@
1
+ #!/usr/bin/env ruby
2
+ require 'faye'
3
+ require 'eventmachine'
4
+ require 'json'
5
+ require 'rest-client'
6
+ require 'logger'
7
+
8
+ # Connects to a partygoer instance, performs an initial poll, and then polls
9
+ # every 5 minutes. In between polls the client stays in sync using faye to
10
+ # recieve push updates
11
+ class PartyGoerClient
12
+ attr_reader :queued, :playing, :played
13
+
14
+ @@logger = Logger.new('partygoerclient.log', 2, 1_000_000)
15
+ @@logger.formatter = proc do |severity, datetime, _progname, msg|
16
+ "\n\n#{datetime} #{severity}:\n\t#{msg}"
17
+ end
18
+ @@logger.level = Logger::DEBUG
19
+
20
+ def initialize(server)
21
+ @server = server
22
+
23
+ @played = []
24
+ @queued = []
25
+ @playing = nil
26
+
27
+ @played_lock = Mutex.new
28
+ @queued_lock = Mutex.new
29
+ @playing_lock = Mutex.new
30
+
31
+ @event_loop = nil
32
+
33
+ @api = RestClient::Resource.new("#{@server}/api/queue", headers:
34
+ { content_type: 'application/json', accept: 'application/json' })
35
+ @client = Faye::Client.new("#{@server}:9292/faye")
36
+ @@logger.info { "Client initialized with server: #{server}" }
37
+ @is_playing = false
38
+ start
39
+ end
40
+
41
+ def playing=(track)
42
+ payload = { track: track }
43
+ @api['/playing'].put(payload)
44
+ @@logger.info { "Set now playing to #{track['name']}" }
45
+ rescue
46
+ @@logger.error {
47
+ "Could not set now playing to #{track.inspect}:\n\t#{$!}\n\t#{$@.join("\n\t")}"
48
+ }
49
+ end
50
+
51
+ def suggest(track)
52
+ case track
53
+ when String
54
+ payload = { track: { spotify_uri: track } }
55
+ when Hash
56
+ payload = { track: track }
57
+ end
58
+ EM.defer(proc { @api['/tracks'].post(payload) })
59
+ @@logger.info { "Suggested #{track.inspect}" }
60
+ rescue
61
+ @@logger.error("Failed to suggest track: #{track.inspect}\n#{$!}\n#{$@.join("\n\t")}")
62
+ end
63
+
64
+ def skip?
65
+ if @playing['num_upvotes'] == 0
66
+ @playing['num_downvotes'] >= 2
67
+ else
68
+ @playing['num_downvotes'] >= (2 * @playing['num_upvotes'])
69
+ end
70
+ rescue
71
+ @@logger.error("Failed to check skip criteria:\n#{$!}\n#{$@.join("\n\t")}")
72
+ end
73
+
74
+ def up_next
75
+ @queued[1]
76
+ rescue
77
+ @@logger.error("Failed to check up next:\n#{$!}\n#{$@.join("\n\t")}")
78
+ end
79
+
80
+ def start
81
+ pull_update
82
+ return if @event_loop
83
+ @@logger.info { 'Starting event loop' }
84
+ start_event_loop
85
+
86
+ at_exit do
87
+ stop
88
+ end
89
+ end
90
+
91
+ def stop
92
+ return unless @event_loop
93
+ @@logger.info { 'Stopping event loop' }
94
+ EM.stop_event_loop
95
+ @event_loop.kill
96
+ @event_loop = nil
97
+ end
98
+
99
+ # aliases
100
+ def now_playing
101
+ playing
102
+ end
103
+
104
+ def currently_queued
105
+ queued
106
+ end
107
+
108
+ def recently_played
109
+ played
110
+ end
111
+
112
+ private
113
+ def start_event_loop
114
+ @event_loop = Thread.new do
115
+ begin
116
+ EM.run do
117
+ EM.add_periodic_timer(300) { EM.defer { pull_update } }
118
+
119
+ @client.subscribe('/track/vote') do |message|
120
+ EM.defer(on_voted(message))
121
+ end
122
+
123
+ @client.subscribe('/track/create') do |message|
124
+ EM.defer(on_created(message))
125
+ end
126
+
127
+ @client.subscribe('/queue/next') do |message|
128
+ EM.defer(on_next(message))
129
+ end
130
+ end
131
+ rescue
132
+ @@logger.critical { "Could not start event loop:\n\t#{$!}\n\t#{$@.join("\n\t")}" }
133
+ stop
134
+ raise $!
135
+ end
136
+ end
137
+ rescue
138
+ @@logger.critical { "Could not start event loop:\n\t#{$!}\n\t#{$@.join("\n\t")}" }
139
+ stop
140
+ raise $!
141
+ end
142
+
143
+
144
+ def on_voted(track)
145
+ proc {
146
+ @@logger.debug { "Track voted on: #{track.inspect}" }
147
+ begin
148
+ @queued_lock.synchronize{
149
+ index = @queued.find_index {
150
+ |item| item['id'] == track['id']
151
+ }
152
+ @queued[index] = track
153
+ }
154
+ rescue
155
+ @@logger.error { "Error in on_voted:\n\t#{$!}\n\t#{$@.join("\n\t")}" }
156
+ ensure
157
+ sort!
158
+ end
159
+ }
160
+ end
161
+
162
+ def on_created(track)
163
+ proc {
164
+ @@logger.debug { "Track created: #{track.inspect}" }
165
+ begin
166
+ @queued_lock.synchronize{
167
+ @queued << track
168
+ }
169
+ rescue
170
+ @@logger.error { "Error in on_created:\n\t#{$!}\n\t#{$@.join("\n\t")}" }
171
+ ensure
172
+ sort!
173
+ end
174
+ }
175
+ end
176
+
177
+ def on_next(track)
178
+ proc {
179
+ @@logger.debug { "Next track called: #{track.inspect}" }
180
+ begin
181
+ @playing_lock.synchronize {
182
+ @playing['playing'] = false
183
+ @played_lock.synchronize { @played << @playing }
184
+ @queued_lock.synchronize {
185
+ @queued.delete_if {
186
+ |item| item['id'] == @playing['id']
187
+ }
188
+ index = @queued.find_index {
189
+ |item| item['id'] == track['id']
190
+ }
191
+ @queued[index] = track
192
+ }
193
+ @playing = track
194
+ }
195
+ rescue
196
+ @@logger.error { "Error in on_next:\n\t#{$!}\n\t#{$@.join("\n\t")}" }
197
+ ensure
198
+ sort!
199
+ end
200
+ }
201
+ end
202
+
203
+ def pull_update
204
+ @@logger.info { "Polling for update" }
205
+ begin
206
+ body = JSON.parse(@api['/recently_played'].get.body)
207
+ @played_lock.synchronize { @played = body }
208
+ rescue
209
+ @@logger.error { "Could not fetch recently played:\n\t#{$!}\n\t#{$@.join("\n\t")}" }
210
+ end
211
+ begin
212
+ body = JSON.parse(@api['/tracks'].get.body)
213
+ @queued_lock.synchronize { @queued = body }
214
+ rescue
215
+ @@logger.error { "Could not fetch queue:\n\t#{$!}\n\t#{$@.join("\n\t")}" }
216
+ end
217
+ begin
218
+ body = JSON.parse(@api['/playing'].get.body)
219
+ @playing_lock.synchronize { @playing = body }
220
+ rescue
221
+ @@logger.error { "Could not fetch now playing:\n\t#{$!}\n\t#{$@.join("\n\t")}" }
222
+ end
223
+ sort!
224
+ end
225
+
226
+ def sort!
227
+ begin
228
+ @played_lock.synchronize{
229
+ @played.sort_by! { |track| track['id'] }
230
+ }
231
+ @@logger.debug { 'Sorted recently played' }
232
+ rescue
233
+ @@logger.error { "Could not sort recently played:\n\t#{$!}\n\t#{$@.join("\n\t")}" }
234
+ end
235
+ begin
236
+ @queued_lock.synchronize{
237
+ @queued.sort_by! { |track|
238
+ i = track['playing'] ? 0 : 1
239
+ [i, -1 * track['score'], track['id']]
240
+ }
241
+ }
242
+ @@logger.debug { 'Sorted queued' }
243
+ rescue
244
+ @@logger.error { "Could not sort the queue:\n\t#{$!}\n\t#{$@.join("\n\t")}" }
245
+ end
246
+ begin
247
+ @playing_lock.synchronize{
248
+ @playing = @queued.select { |track| track['playing'] }.first
249
+ }
250
+ @@logger.debug { 'Updated now playing' }
251
+ rescue
252
+ @@logger.error { "Could not update now playing:\n\t#{$!}\n\t#{$@.join("\n\t")}" }
253
+ end
254
+ end
255
+ end
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: partygoer-client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Andrew Hamon
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-10-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faye
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: eventmachine
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rest-client
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.7'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.7'
55
+ description: Ruby client for partygoer
56
+ email: andrew@hamon.cc
57
+ executables: []
58
+ extensions: []
59
+ extra_rdoc_files: []
60
+ files:
61
+ - lib/partygoer-client.rb
62
+ homepage: http://github.com/andrewhamon/partygoer-client
63
+ licenses:
64
+ - MIT
65
+ metadata: {}
66
+ post_install_message:
67
+ rdoc_options: []
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ requirements: []
81
+ rubyforge_project:
82
+ rubygems_version: 2.4.2
83
+ signing_key:
84
+ specification_version: 4
85
+ summary: Ruby client for partygoer
86
+ test_files: []
87
+ has_rdoc: