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.
- checksums.yaml +7 -0
- data/lib/partygoer-client.rb +255 -0
- 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:
|