partygoer-client 0.0.1

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