turntabler 0.1.4 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # master
2
2
 
3
+ ## 0.2.0 / 2013-02-16
4
+
5
+ * Respect the keepalive update interval from API responses
6
+ * Add official support for trigger custom events
7
+ * Rename RoomDirectory#list to RoomDirectory#all
8
+ * Add full support for playlists API
9
+ * Fix Modlist example not sending messages to the room
10
+
3
11
  ## 0.1.4 / 2012-01-08
4
12
 
5
13
  * Fix Bop / ChatBot / Switch examples not working
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012 Aaron Pfeifer
1
+ Copyright (c) 2013 Aaron Pfeifer
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -50,6 +50,7 @@ At a high level, this project features:
50
50
  * HTTP / Web Socket interface implementations
51
51
  * Room state / user list management
52
52
  * DSL syntax support
53
+ * Custom events
53
54
 
54
55
  Turntable features include management of:
55
56
 
@@ -117,40 +118,47 @@ Turntabler.run do
117
118
  end
118
119
 
119
120
  # Authorized user interactions
120
- user = client.user # => #<Turntabler::AuthorizedUser:0x95631b0 @email="ben.zelano@gmail.com" ...>
121
- user.fan_of # => [#<Turntabler::User:0x95ccb38 @id="d5616b31654e8b22a7a1eef0">, ...]
122
- user.fans # => [#<Turntabler::User:0x95dd1cc @id="d5616b31654e8b22a7a1eef0">, ...]
123
- user.playlist.songs # => [#<Turntabler::Song:0x9610b44 @album="Abbey Road" ...>, ...]
124
- user.blocks # => [#<Turntabler::User:0x9792724 @id="19125d4da3b09562b2cf68b6">, ...]
125
- user.buddies # => [#<Turntabler::User:0x9792580 @id="efff38aeb7b9334164c1b630">, ...]
121
+ user = client.user # => #<Turntabler::AuthorizedUser @email="ben.zelano@gmail.com" ...>
122
+ user.fan_of # => [#<Turntabler::User @id="d5616b31654e8b22a7a1eef0">, ...]
123
+ user.fans # => [#<Turntabler::User @id="d5616b31654e8b22a7a1eef0">, ...]
124
+ user.playlist.songs # => [#<Turntabler::Song @album="Abbey Road" ...>, ...]
125
+ user.blocks # => [#<Turntabler::User @id="19125d4da3b09562b2cf68b6">, ...]
126
+ user.buddies # => [#<Turntabler::User @id="efff38aeb7b9334164c1b630">, ...]
126
127
 
127
128
  # Room Directory
128
- client.rooms.list(:favorites => true) # => []
129
- client.rooms.list(:genre => :rock) # => [#<Turntabler::Room:0x985d474 @id="4e4986bb14169c5f241318a6", ...>, ...]
130
- client.rooms.list(:genre => :rock, :available_djs => true, :minimum_listeners => 5)
131
- # => [#<Turntabler::Room:0x91c1d04 @id="4f4a5874a3f75128aa006c17", ...>, ...]
132
- client.rooms.with_friends # => [#<Turntabler::Room:0x9674c98 @id="50b4c1e2df5bcf4af666f876", ...>, ...]
133
- client.room('4dff1eac14169c565800892e').listeners # => #<Set: {#<Turntabler::User:0x96340bc @id="4e1341e2a3f75114d003c591" ...>, ...}>
129
+ client.rooms.all(:favorites => true) # => []
130
+ client.rooms.all(:genre => :rock) # => [#<Turntabler::Room @id="4e4986bb14169c5f241318a6", ...>, ...]
131
+ client.rooms.all(:genre => :rock, :available_djs => true, :minimum_listeners => 5)
132
+ # => [#<Turntabler::Room @id="4f4a5874a3f75128aa006c17", ...>, ...]
133
+ client.rooms.with_friends # => [#<Turntabler::Room @id="50b4c1e2df5bcf4af666f876", ...>, ...]
134
+ client.room('4dff1eac14169c565800892e').listeners # => #<Set: {#<Turntabler::User @id="4e1341e2a3f75114d003c591" ...>, ...}>
134
135
 
135
136
  # Room interaction
136
137
  client.rooms.create("My Test Room #{rand}").enter # => true
137
- room = client.room # => #<Turntabler::Room:0x99a16dc @name="My Test Room 0.24300857307298018" ...>
138
+ room = client.room # => #<Turntabler::Room @name="My Test Room 0.24300857307298018" ...>
138
139
  room.add_as_favorite # => true
139
140
  room.become_dj # => true
140
141
  room.say "Hey guys!" # => true
141
142
 
142
143
  # User interaction
143
- listeners = room.listeners # => #<Set: {#<Turntabler::User:0x95631b0 @id="309ba75b6385b83e110923bd" ..., ...}>
144
+ listeners = room.listeners # => #<Set: {#<Turntabler::User @id="309ba75b6385b83e110923bd" ..., ...}>
144
145
  listeners.each do |listener|
145
- listener.messages # => [#<Turntabler::Message:0x99aa1ec @content="Hey man!" ...>, ...]
146
+ listener.messages # => [#<Turntabler::Message @content="Hey man!" ...>, ...]
146
147
  listener.website # => "http://mypersonalwebsite.com"
147
148
  listener.facebook_url # => "https://www.facebook.com/firstname.lastname"
148
- listener.sticker_placements # => [#<Turntabler::StickerPlacement:0x9861024 @angle=0 ...>, ...]
149
+ listener.sticker_placements # => [#<Turntabler::StickerPlacement @angle=0 ...>, ...]
149
150
  listener.say "Welcome to the room!" # => true
150
151
  end
151
152
 
153
+ # Playlist interaction
154
+ client.user.playlists.all # => [#<Turntabler::Playlist @id="default" ...>, ...]
155
+ client.user.playlists.create("rock") # => #<Turntabler::Playlist @id="rock" ...>
156
+ client.user.playlist # => #<Turntabler::Playlist @id="default" ...>
157
+ client.user.playlist("rock").activate # => true
158
+ client.user.playlist("rock").songs # => []
159
+
152
160
  # Songs
153
- songs = client.search_song('Rolling Stones') # => [#<Turntabler::Song:0x983c198 @album="Tattoo You (2009 Remaster)" ...>, ...]
161
+ songs = client.search_song('Rolling Stones') # => [#<Turntabler::Song @album="Tattoo You (2009 Remaster)" ...>, ...]
154
162
  songs.each do
155
163
  song.enqueue # => true
156
164
  end
@@ -171,6 +179,43 @@ can do with turntabler. For a *complete* list, see the API documentation, espec
171
179
  For additional examples, see the [examples](https://github.com/obrie/turntabler/tree/master/examples)
172
180
  directory in the repository.
173
181
 
182
+ ### Custom events
183
+
184
+ In addition to the default Turntable events supported out of the box, turntabler
185
+ also allows you to define your own events. This is particularly useful in cases
186
+ where you may want to provide extensions on top of the turntabler library for
187
+ others to use. These extensions may be higher-order events, such as a user
188
+ reaching their maximum play count for a turn or a user timing out.
189
+
190
+ For example:
191
+
192
+ ```ruby
193
+ require 'turntabler'
194
+
195
+ EMAIL = ENV['EMAIL']
196
+ PASSWORD = ENV['PASSWORD']
197
+
198
+ # Register custom events
199
+ Turntabler.events :user_greeted
200
+
201
+ Turntabler.run do
202
+ client = Turntabler::Client.new(EMAIL, PASSWORD)
203
+
204
+ # Events
205
+ client.on :user_spoke do |message|
206
+ if (message.content =~ /^\/hello$/)
207
+ # Trigger the custom event
208
+ client.trigger(:user_greeted, message.sender)
209
+ end
210
+ end
211
+
212
+ # Handle custom event
213
+ client.on :user_greeted do |user|
214
+ client.room.say "Hey! How are you #{message.sender.name}?"
215
+ end
216
+ end
217
+ ```
218
+
174
219
  ## Additional Topics
175
220
 
176
221
  ### Differences with existing libraries
@@ -332,7 +377,7 @@ Notice that in this example the syntax is essentially the same except that we're
332
377
  one level out and need to interact directly with the `Turntabler::Client`
333
378
  instance itself.
334
379
 
335
- ## Usage
380
+ ## Deployment
336
381
 
337
382
  ### Web Server Usage
338
383
 
@@ -17,7 +17,7 @@ GEM
17
17
  faye-websocket (0.4.6)
18
18
  eventmachine (>= 0.12.0)
19
19
  http_parser.rb (0.5.3)
20
- turntabler (0.1.4)
20
+ turntabler (0.2.0)
21
21
  em-http-request
22
22
  em-synchrony
23
23
  faye-websocket
data/examples/modlist.rb CHANGED
@@ -13,7 +13,7 @@ TT.run(EMAIL, PASSWORD, :room => ROOM) do
13
13
  on :user_spoke do |message|
14
14
  # Response to "/mod" command
15
15
  if moderator_ids.include?(message.sender.id) && message.content =~ /^\/mod$/
16
- user.say("Yo #{message.sender.name}, it looks like you are a bot moderator!")
16
+ room.say("Yo #{message.sender.name}, it looks like you are a bot moderator!")
17
17
  end
18
18
  end
19
19
  end
@@ -1,4 +1,4 @@
1
- require 'turntabler/playlist'
1
+ require 'turntabler/playlist_directory'
2
2
  require 'turntabler/preferences'
3
3
  require 'turntabler/user'
4
4
  require 'turntabler/sticker_placement'
@@ -32,6 +32,10 @@ module Turntabler
32
32
  # @return [String]
33
33
  attribute :password
34
34
 
35
+ # The user's custom playlists
36
+ # @return [Turntabler::PlaylistDirectory]
37
+ attr_reader :playlists
38
+
35
39
  # The user's current Turntable preferences
36
40
  # @return [Turntabler::Preferences]
37
41
  attr_reader :preferences
@@ -39,7 +43,7 @@ module Turntabler
39
43
  # @api private
40
44
  def initialize(client, *)
41
45
  @status = 'available'
42
- @playlists = {}
46
+ @playlists = PlaylistDirectory.new(client)
43
47
  @preferences = Preferences.new(client)
44
48
  super
45
49
  end
@@ -192,7 +196,7 @@ module Turntabler
192
196
  # user.playlist # => #<Turntabler::Playlist id="default" ...>
193
197
  # user.playlist("rock") # => #<Turntabler::Playlist id="rock" ...>
194
198
  def playlist(id = 'default')
195
- @playlists[id] ||= Playlist.new(client, :_id => id)
199
+ playlists.build(:_id => id)
196
200
  end
197
201
 
198
202
  # Gets the stickers that have been purchased by this user.
@@ -245,8 +249,10 @@ module Turntabler
245
249
  def update_status(status = self.status)
246
250
  assert_valid_values(status, *%w(available unavailable away))
247
251
 
248
- api('presence.update', :status => status)
252
+ result = api('presence.update', :status => status)
253
+ client.reset_keepalive(result['interval']) if result['interval']
249
254
  self.attributes = {'status' => status}
255
+
250
256
  true
251
257
  end
252
258
  end
@@ -99,7 +99,7 @@ module Turntabler
99
99
 
100
100
  # Create a new connection to the given url
101
101
  @connection = Connection.new(url, :timeout => timeout, :params => {:clientid => id, :userid => user.id, :userauth => user.auth})
102
- @connection.handler = lambda {|data| on_message(data)}
102
+ @connection.handler = lambda {|data| trigger(data.delete('command'), data)}
103
103
  @connection.start
104
104
 
105
105
  # Wait until the connection is authenticated
@@ -116,8 +116,8 @@ module Turntabler
116
116
  # @return [true]
117
117
  def close(allow_reconnect = false)
118
118
  if @connection
119
- @update_timer.cancel if @update_timer
120
- @update_timer = nil
119
+ @keepalive_timer.cancel if @keepalive_timer
120
+ @keepalive_timer = nil
121
121
  @connection.close
122
122
 
123
123
  wait do |&resume|
@@ -439,21 +439,64 @@ module Turntabler
439
439
  songs || raise(Error, 'Search failed to complete')
440
440
  end
441
441
 
442
- # Callback when a message has been received from Turntable. This will run
443
- # any handlers registered for the event associated with the message.
442
+ # Triggers callback handlers for the given Turntable command. This should
443
+ # either be invoked when responses are received for Turntable or when
444
+ # triggering custom events.
444
445
  #
445
- # @api private
446
- # @param [Hash<String, Object>] data The message data received
447
- # @return nil
448
- def on_message(data)
449
- if Event.command?(data['command'])
450
- event = Event.new(self, data)
446
+ # @note If the command is unknown, it will simply get skipped and not raise an exception
447
+ # @param [Symbol] command The name of the command triggered. This is typically the same name as the event.
448
+ # @param [Array] args The arguments to be processed by the event
449
+ # @return [true]
450
+ #
451
+ # == Triggering custom events
452
+ #
453
+ # After defining custom events, `trigger` can be used to invoke any handler
454
+ # that's been registered for that event. The argument list passed into
455
+ # `trigger` will be passed, exactly as specified, to the registered
456
+ # handlers.
457
+ #
458
+ # @example
459
+ # # Define names of events
460
+ # Turntabler.events(:no_args, :one_arg, :multiple_args)
461
+ #
462
+ # # ...
463
+ #
464
+ # # Register handlers
465
+ # client.on(:no_args) { }
466
+ # client.on(:one_arg) {|arg| }
467
+ # client.on(:multiple_args) {|arg1, arg2| }
468
+ #
469
+ # # Trigger handlers registered for events
470
+ # client.trigger(:no_args) # => true
471
+ # client.trigger(:one_arg, 1) # => true
472
+ # client.trigger(:multiple_args, 1, 2) # => true
473
+ def trigger(command, *args)
474
+ command = command.to_sym if command
475
+
476
+ if Event.command?(command)
477
+ event = Event.new(self, command, args)
451
478
  handlers = @event_handlers[event.name] || []
452
479
  handlers.each do |handler|
453
480
  success = handler.run(event)
454
481
  handlers.delete(handler) if success && handler.once
455
482
  end
456
483
  end
484
+
485
+ true
486
+ end
487
+
488
+ # Resets the keepalive timer to run at the given interval.
489
+ #
490
+ # @param [Fixnum] interval The frequency with which keepalives get sent (in seconds)
491
+ # @api private
492
+ def reset_keepalive(interval = 10)
493
+ if !@keepalive_timer || @keepalive_interval != interval
494
+ @keepalive_interval = interval
495
+
496
+ # Periodically update the user's status to remain available
497
+ @keepalive_timer.cancel if @keepalive_timer
498
+ @keepalive_timer = EM::Synchrony.add_periodic_timer(interval) { user.update(:status => user.status) }
499
+ end
457
500
  end
458
501
 
459
502
  private
@@ -470,10 +513,7 @@ module Turntabler
470
513
  user.authenticate
471
514
  user.fan_of
472
515
  user.update(:status => user.status)
473
-
474
- # Periodically update the user's status to remain available
475
- @update_timer.cancel if @update_timer
476
- @update_timer = EM::Synchrony.add_periodic_timer(10) { user.update(:status => user.status) }
516
+ reset_keepalive
477
517
  end
478
518
 
479
519
  # Callback when the session has ended. This will automatically reconnect if
@@ -488,7 +528,7 @@ module Turntabler
488
528
  if @reconnect && allow_reconnect
489
529
  EM::Synchrony.add_timer(@reconnect_wait) do
490
530
  room ? room.enter : connect(url)
491
- on_message('command' => 'reconnected')
531
+ trigger(:reconnected)
492
532
  end
493
533
  end
494
534
  end
@@ -35,7 +35,7 @@ module Turntabler
35
35
  # @param [String] command The command to check for the existence of
36
36
  # @return [Boolean] +true+ if the command exists, otherwise +false+
37
37
  def command?(command)
38
- command && commands.include?(command.to_sym)
38
+ commands.include?(command)
39
39
  end
40
40
  end
41
41
 
@@ -79,7 +79,7 @@ module Turntabler
79
79
  data['user'].map do |attrs|
80
80
  user = room.build_user(attrs)
81
81
  room.listeners << user
82
- user
82
+ [user]
83
83
  end
84
84
  end
85
85
 
@@ -88,7 +88,7 @@ module Turntabler
88
88
  data['user'].map do |attrs|
89
89
  user = room.build_user(attrs)
90
90
  room.listeners.delete(user)
91
- user
91
+ [user]
92
92
  end
93
93
  end
94
94
 
@@ -122,7 +122,8 @@ module Turntabler
122
122
  handle :dj_added, :add_dj do
123
123
  new_djs = []
124
124
  data['user'].each_with_index do |attrs, index|
125
- new_djs << user = room.build_user(attrs.merge('placements' => data['placements'][index]))
125
+ user = room.build_user(attrs.merge('placements' => data['placements'][index]))
126
+ new_djs << [user]
126
127
  room.djs << user
127
128
  end
128
129
  new_djs
@@ -133,7 +134,7 @@ module Turntabler
133
134
  data['user'].map do |attrs|
134
135
  user = room.build_user(attrs)
135
136
  room.djs.delete(user)
136
- user
137
+ [user]
137
138
  end
138
139
  end
139
140
 
@@ -153,14 +154,14 @@ module Turntabler
153
154
 
154
155
  # There are no more songs to play in the room
155
156
  handle :song_unavailable, :nosong do
156
- client.on_message('command' => 'song_ended') if room.current_song
157
+ client.trigger(:song_ended) if room.current_song
157
158
  room.attributes = data['room'].merge('current_song' => nil)
158
159
  nil
159
160
  end
160
161
 
161
162
  # A new song has started playing
162
163
  handle :song_started, :newsong do
163
- client.on_message('command' => 'song_ended') if room.current_song
164
+ client.trigger(:song_ended) if room.current_song
164
165
  room.attributes = data['room']
165
166
  room.current_song
166
167
  end
@@ -190,13 +191,13 @@ module Turntabler
190
191
 
191
192
  # A song was skipped due to a copyright claim
192
193
  handle :song_blocked do
193
- client.on_message('command' => 'song_ended') if room.current_song
194
+ client.trigger(:song_ended) if room.current_song
194
195
  Song.new(client, data)
195
196
  end
196
197
 
197
198
  # A song was skipped due to a limit on # of plays per hour
198
199
  handle :song_limited, :dmca_error do
199
- client.on_message('command' => 'song_ended') if room.current_song
200
+ client.trigger(:song_ended) if room.current_song
200
201
  Song.new(client, data)
201
202
  end
202
203
 
@@ -207,7 +208,7 @@ module Turntabler
207
208
 
208
209
  # A song search has completed and the results are available
209
210
  handle :search_completed, :search_complete do
210
- [data['docs'].map {|attrs| Song.new(client, attrs)}]
211
+ [[data['docs'].map {|attrs| Song.new(client, attrs)}]]
211
212
  end
212
213
 
213
214
  # A song search failed to complete
@@ -217,25 +218,29 @@ module Turntabler
217
218
  # @return [String]
218
219
  attr_reader :name
219
220
 
221
+ # The raw arguments list from the event
222
+ # @return [Array<Object>]
223
+ attr_reader :args
224
+
220
225
  # The raw hash of data parsed from the event
221
226
  # @return [Hash<String, Object>]
222
227
  attr_reader :data
223
228
 
224
- # The typecasted results parsed from the data
225
- # @return [Array]
229
+ # The typecasted results args parsed from the event
230
+ # @return [Array<Array<Object>>]
226
231
  attr_reader :results
227
232
 
228
233
  # Creates a new event triggered with the given data
229
234
  #
230
235
  # @param [Turntabler::Client] client The client that this event is bound to
231
236
  # @param [Hash] data The response data from Turntable
232
- def initialize(client, data)
237
+ def initialize(client, command, args)
233
238
  @client = client
234
- @data = data
235
- command = data['command'].to_sym
239
+ @args = args
240
+ @data = args[0]
236
241
  @name = self.class.commands[command]
237
242
  @results = __send__("typecast_#{command}_event")
238
- @results = [@results] unless @results.is_a?(Array)
243
+ @results = [[@results].compact] unless @results.is_a?(Array)
239
244
  end
240
245
 
241
246
  private
@@ -46,9 +46,9 @@ module Turntabler
46
46
  def run(event)
47
47
  if conditions_match?(event.data)
48
48
  # Run the block for each individual result
49
- event.results.each do |result|
49
+ event.results.each do |args|
50
50
  begin
51
- @block.call(*[result].compact)
51
+ @block.call(*args)
52
52
  rescue StandardError => ex
53
53
  logger.error(([ex.message] + ex.backtrace) * "\n")
54
54
  end
@@ -5,6 +5,14 @@ module Turntabler
5
5
  # Represents a collection of songs managed by the user and that can be played
6
6
  # within a room
7
7
  class Playlist < Resource
8
+ # Allow the id to be set via the "name" attribute
9
+ # @return [String]
10
+ attribute :id, :name, :load => false
11
+
12
+ # Whether this is the currently active playlist
13
+ # @return [Boolean]
14
+ attribute :active, :load => false
15
+
8
16
  # The songs that have been added to this playlist
9
17
  # @return [Array<Turntabler::Song>]
10
18
  attribute :songs, :list do |songs|
@@ -30,6 +38,60 @@ module Turntabler
30
38
  super()
31
39
  end
32
40
 
41
+ # Updates this playlist's information.
42
+ #
43
+ # @param [Hash] attributes The attributes to update
44
+ # @option attributes [String] :id
45
+ # @return [true]
46
+ # @raise [ArgumentError] if an invalid attribute or value is specified
47
+ # @raise [Turntabler::Error] if the command fails
48
+ # @example
49
+ # playlist.update(:id => "rock") # => true
50
+ def update(attributes = {})
51
+ assert_valid_keys(attributes, :id)
52
+
53
+ # Update id
54
+ id = attributes.delete(:id)
55
+ update_id(id) if id
56
+
57
+ true
58
+ end
59
+
60
+ # Whether this is the currently active playlist
61
+ #
62
+ # @return [Boolean]
63
+ # @raise [Turntabler::Error] if the command fails
64
+ # @example
65
+ # playlist.active # => true
66
+ def active
67
+ @active = client.user.playlists.all.any? {|playlist| playlist == self && playlist.active?} if @active.nil?
68
+ @active
69
+ end
70
+
71
+ # Changes this playlist to be used for queueing new songs with the room.
72
+ #
73
+ # @return [true]
74
+ # @raise [Turntabler::Error] if the command fails
75
+ # @example
76
+ # playlist.switch # => true
77
+ def activate
78
+ api('playlist.switch')
79
+ self.attributes = {'active' => true}
80
+ true
81
+ end
82
+
83
+ # Permanently deletes this playlist and the list of songs within it. If this
84
+ # is the currently active playlist, the "default" playlist will become active.
85
+ #
86
+ # @return [true]
87
+ # @raise [Turntabler::Error] if the command fails
88
+ # @example
89
+ # playlist.delete # => true
90
+ def delete
91
+ api('playlist.delete')
92
+ true
93
+ end
94
+
33
95
  # Gets the song with the given id.
34
96
  #
35
97
  # @param [String] song_id The id for the song
@@ -42,6 +104,13 @@ module Turntabler
42
104
  end
43
105
 
44
106
  private
107
+ # Updates the name used to identify this playlist
108
+ def update_id(id)
109
+ client.api('playlist.rename', :old_playlist_name => self.id, :new_playlist_name => id)
110
+ self.attributes = {'name' => id}
111
+ true
112
+ end
113
+
45
114
  def api(command, options = {})
46
115
  options[:playlist_name] = id
47
116
  super
@@ -0,0 +1,70 @@
1
+ require 'turntabler/playlist'
2
+
3
+ module Turntabler
4
+ # Provides a set of helper methods for interacting with a user's playlists.
5
+ class PlaylistDirectory
6
+ include Assertions
7
+
8
+ # @api private
9
+ def initialize(client)
10
+ @client = client
11
+ @playlists = {}
12
+ end
13
+
14
+ # Creates a new playlist with the given id. This should only be used if
15
+ # the playlist doesn't already exist.
16
+ #
17
+ # @note This will automatically enter the playlist when it is created
18
+ # @param [String] id The unique identifier of the playlist
19
+ # @return [Turntabler::Playlist]
20
+ # @raise [Turntabler::Error] if the command fails
21
+ # @example
22
+ # playlists.create("Rock") # => #<Turntabler::Playlist ...>
23
+ def create(id)
24
+ api('playlist.create', :playlist_name => id)
25
+ build(:_id => id)
26
+ end
27
+
28
+ # Gets the list of playlists created.
29
+ #
30
+ # @return [Array<Turntabler::Playlist>]
31
+ # @raise [Turntabler::Error] if the command fails
32
+ # @example
33
+ # playlists.all # => [#<Turntabler::Playlist ...>, ...]
34
+ def all
35
+ data = api('playlist.list_all')
36
+ data['list'].map {|attrs| build(attrs)}
37
+ end
38
+
39
+ # Gets the playlist represented by the given attributes.
40
+ #
41
+ # If the playlist hasn't been previously accessed, then a new Playlist
42
+ # instance will get created.
43
+ #
44
+ # @api private
45
+ # @param [Hash] attrs The attributes representing the playlist
46
+ # @return [Turntabler::Playlist]
47
+ def build(attrs)
48
+ playlist = Playlist.new(client, attrs)
49
+
50
+ # Update existing in cache or cache a new playlist
51
+ if existing = @playlists[playlist.id]
52
+ playlist = existing
53
+ playlist.attributes = attrs
54
+ else
55
+ @playlists[playlist.id] = playlist
56
+ end
57
+
58
+ playlist
59
+ end
60
+
61
+ private
62
+ # The client that all APIs filter through
63
+ attr_reader :client
64
+
65
+ # Runs the given API command on the client.
66
+ def api(command, options = {})
67
+ client.api(command, options)
68
+ end
69
+ end
70
+ end
@@ -54,11 +54,11 @@ module Turntabler
54
54
  # @raise [ArgumentError] if an invalid option or value is specified
55
55
  # @raise [Turntabler::Error] if the command fails
56
56
  # @example
57
- # rooms.list # => [#<Turntabler::Room ...>, ...]
58
- # rooms.list(:favorites => true) # => [#<Turntabler::Room ...>, ...]
59
- # rooms.list(:available_djs => true, :genre => :rock) # => [#<Turntabler::Room ...>, ...]
60
- # rooms.list(:sort => :random) # => [#<Turntabler::Room ...>, ...]
61
- def list(options = {})
57
+ # rooms.all # => [#<Turntabler::Room ...>, ...]
58
+ # rooms.all(:favorites => true) # => [#<Turntabler::Room ...>, ...]
59
+ # rooms.all(:available_djs => true, :genre => :rock) # => [#<Turntabler::Room ...>, ...]
60
+ # rooms.all(:sort => :random) # => [#<Turntabler::Room ...>, ...]
61
+ def all(options = {})
62
62
  assert_valid_keys(options, :limit, :skip, :favorites, :available_djs, :genre, :minimum_listeners, :sort)
63
63
  assert_valid_values(options[:genre], :rock, :electronic, :indie, :hiphop, :pop, :dubstep) if options[:genre]
64
64
  assert_valid_values(options[:sort], :created, :listeners, :random) if options[:sort]
@@ -2,8 +2,8 @@ module Turntabler
2
2
  # The current version of the library
3
3
  module Version
4
4
  MAJOR = 0
5
- MINOR = 1
6
- PATCH = 4
5
+ MINOR = 2
6
+ PATCH = 0
7
7
  STRING = [MAJOR, MINOR, PATCH].join(".")
8
8
  end
9
9
  end
data/lib/turntabler.rb CHANGED
@@ -4,6 +4,7 @@ require 'em-synchrony'
4
4
  # Turntable.FM API for Ruby
5
5
  module Turntabler
6
6
  autoload :Client, 'turntabler/client'
7
+ autoload :Event, 'turntabler/event'
7
8
 
8
9
  class << self
9
10
  # The logger to use for all Turntable messages. By default, everything is
@@ -97,6 +98,25 @@ module Turntabler
97
98
  EM.synchrony { run(*args, &block) }
98
99
  end
99
100
  end
101
+
102
+ # Defines one or more custom events for which handlers can be registered
103
+ # and triggered via Turntabler::Client#on and Turntabler::Client#trigger,
104
+ # respectively.
105
+ #
106
+ # @note Events must be defined before handlers can be registered for them
107
+ # @param [Array<String>] names The names of the events to define
108
+ # @raise [ArgumentError] if the event has already been defined
109
+ # @example
110
+ # Turntabler::Client.events :custom_event1, :custom_event2
111
+ def events(*names)
112
+ names.each do |name|
113
+ if Event.command?(name)
114
+ raise ArgumentError, "Event :#{name} is already defined"
115
+ else
116
+ Event.handle(name) { [args] }
117
+ end
118
+ end
119
+ end
100
120
  end
101
121
 
102
122
  @logger = Logger.new(STDOUT)
metadata CHANGED
@@ -1,121 +1,94 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: turntabler
3
- version: !ruby/object:Gem::Version
4
- version: 0.1.4
3
+ version: !ruby/object:Gem::Version
5
4
  prerelease:
5
+ version: 0.2.0
6
6
  platform: ruby
7
- authors:
7
+ authors:
8
8
  - Aaron Pfeifer
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-01-08 00:00:00.000000000 Z
13
- dependencies:
14
- - !ruby/object:Gem::Dependency
12
+
13
+ date: 2013-02-16 00:00:00 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
15
16
  name: em-synchrony
16
- requirement: !ruby/object:Gem::Requirement
17
- none: false
18
- requirements:
19
- - - ! '>='
20
- - !ruby/object:Gem::Version
21
- version: '0'
22
- type: :runtime
23
17
  prerelease: false
24
- version_requirements: !ruby/object:Gem::Requirement
18
+ requirement: &id001 !ruby/object:Gem::Requirement
25
19
  none: false
26
- requirements:
27
- - - ! '>='
28
- - !ruby/object:Gem::Version
29
- version: '0'
30
- - !ruby/object:Gem::Dependency
31
- name: em-http-request
32
- requirement: !ruby/object:Gem::Requirement
33
- none: false
34
- requirements:
35
- - - ! '>='
36
- - !ruby/object:Gem::Version
37
- version: '0'
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
38
24
  type: :runtime
25
+ version_requirements: *id001
26
+ - !ruby/object:Gem::Dependency
27
+ name: em-http-request
39
28
  prerelease: false
40
- version_requirements: !ruby/object:Gem::Requirement
29
+ requirement: &id002 !ruby/object:Gem::Requirement
41
30
  none: false
42
- requirements:
43
- - - ! '>='
44
- - !ruby/object:Gem::Version
45
- version: '0'
46
- - !ruby/object:Gem::Dependency
47
- name: faye-websocket
48
- requirement: !ruby/object:Gem::Requirement
49
- none: false
50
- requirements:
51
- - - ! '>='
52
- - !ruby/object:Gem::Version
53
- version: '0'
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: "0"
54
35
  type: :runtime
36
+ version_requirements: *id002
37
+ - !ruby/object:Gem::Dependency
38
+ name: faye-websocket
55
39
  prerelease: false
56
- version_requirements: !ruby/object:Gem::Requirement
40
+ requirement: &id003 !ruby/object:Gem::Requirement
57
41
  none: false
58
- requirements:
59
- - - ! '>='
60
- - !ruby/object:Gem::Version
61
- version: '0'
62
- - !ruby/object:Gem::Dependency
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: "0"
46
+ type: :runtime
47
+ version_requirements: *id003
48
+ - !ruby/object:Gem::Dependency
63
49
  name: rake
64
- requirement: !ruby/object:Gem::Requirement
65
- none: false
66
- requirements:
67
- - - ! '>='
68
- - !ruby/object:Gem::Version
69
- version: '0'
70
- type: :development
71
50
  prerelease: false
72
- version_requirements: !ruby/object:Gem::Requirement
73
- none: false
74
- requirements:
75
- - - ! '>='
76
- - !ruby/object:Gem::Version
77
- version: '0'
78
- - !ruby/object:Gem::Dependency
79
- name: rspec
80
- requirement: !ruby/object:Gem::Requirement
51
+ requirement: &id004 !ruby/object:Gem::Requirement
81
52
  none: false
82
- requirements:
83
- - - ~>
84
- - !ruby/object:Gem::Version
85
- version: '2.11'
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
86
57
  type: :development
58
+ version_requirements: *id004
59
+ - !ruby/object:Gem::Dependency
60
+ name: rspec
87
61
  prerelease: false
88
- version_requirements: !ruby/object:Gem::Requirement
62
+ requirement: &id005 !ruby/object:Gem::Requirement
89
63
  none: false
90
- requirements:
64
+ requirements:
91
65
  - - ~>
92
- - !ruby/object:Gem::Version
93
- version: '2.11'
94
- - !ruby/object:Gem::Dependency
95
- name: simplecov
96
- requirement: !ruby/object:Gem::Requirement
97
- none: false
98
- requirements:
99
- - - ! '>='
100
- - !ruby/object:Gem::Version
101
- version: '0'
66
+ - !ruby/object:Gem::Version
67
+ version: "2.11"
102
68
  type: :development
69
+ version_requirements: *id005
70
+ - !ruby/object:Gem::Dependency
71
+ name: simplecov
103
72
  prerelease: false
104
- version_requirements: !ruby/object:Gem::Requirement
73
+ requirement: &id006 !ruby/object:Gem::Requirement
105
74
  none: false
106
- requirements:
107
- - - ! '>='
108
- - !ruby/object:Gem::Version
109
- version: '0'
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: "0"
79
+ type: :development
80
+ version_requirements: *id006
110
81
  description: Turntable.FM API for Ruby
111
82
  email: aaron.pfeifer@gmail.com
112
83
  executables: []
84
+
113
85
  extensions: []
114
- extra_rdoc_files:
86
+
87
+ extra_rdoc_files:
115
88
  - README.md
116
89
  - CHANGELOG.md
117
90
  - LICENSE
118
- files:
91
+ files:
119
92
  - .gitignore
120
93
  - .rspec
121
94
  - .yardopts
@@ -149,6 +122,7 @@ files:
149
122
  - lib/turntabler/loggable.rb
150
123
  - lib/turntabler/message.rb
151
124
  - lib/turntabler/playlist.rb
125
+ - lib/turntabler/playlist_directory.rb
152
126
  - lib/turntabler/preferences.rb
153
127
  - lib/turntabler/resource.rb
154
128
  - lib/turntabler/room.rb
@@ -165,33 +139,36 @@ files:
165
139
  - turntabler.gemspec
166
140
  homepage: http://github.com/obrie/turntabler
167
141
  licenses: []
142
+
168
143
  post_install_message:
169
- rdoc_options:
144
+ rdoc_options:
170
145
  - --line-numbers
171
146
  - --inline-source
172
147
  - --title
173
148
  - turntabler
174
149
  - --main
175
150
  - README.md
176
- require_paths:
151
+ require_paths:
177
152
  - lib
178
- required_ruby_version: !ruby/object:Gem::Requirement
153
+ required_ruby_version: !ruby/object:Gem::Requirement
179
154
  none: false
180
- requirements:
181
- - - ! '>='
182
- - !ruby/object:Gem::Version
183
- version: '0'
184
- required_rubygems_version: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: "0"
159
+ required_rubygems_version: !ruby/object:Gem::Requirement
185
160
  none: false
186
- requirements:
187
- - - ! '>='
188
- - !ruby/object:Gem::Version
189
- version: '0'
161
+ requirements:
162
+ - - ">="
163
+ - !ruby/object:Gem::Version
164
+ version: "0"
190
165
  requirements: []
166
+
191
167
  rubyforge_project:
192
168
  rubygems_version: 1.8.24
193
169
  signing_key:
194
170
  specification_version: 3
195
171
  summary: Turntable.FM API for Ruby
196
172
  test_files: []
173
+
197
174
  has_rdoc: