ruby-mpd 0.1.5 → 0.1.7
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +100 -45
- data/lib/ruby-mpd.rb +42 -83
- data/lib/ruby-mpd/exceptions.rb +33 -0
- data/lib/ruby-mpd/parser.rb +24 -7
- data/lib/ruby-mpd/playlist.rb +5 -6
- data/lib/ruby-mpd/plugins/channels.rb +2 -5
- data/lib/ruby-mpd/plugins/controls.rb +2 -5
- data/lib/ruby-mpd/plugins/database.rb +44 -6
- data/lib/ruby-mpd/plugins/information.rb +7 -4
- data/lib/ruby-mpd/plugins/playback_options.rb +3 -3
- data/lib/ruby-mpd/plugins/playlists.rb +1 -3
- data/lib/ruby-mpd/plugins/queue.rb +3 -5
- data/lib/ruby-mpd/plugins/reflection.rb +3 -3
- data/lib/ruby-mpd/song.rb +9 -3
- data/ruby-mpd.gemspec +1 -1
- data/{tests → test}/libtests.rb +78 -89
- metadata +6 -11
- data/AUTHORS +0 -1
- data/DOC.rdoc +0 -78
- data/data/database.yaml +0 -347
- data/examples/rmpc.rb +0 -67
- data/examples/tailmpc.rb +0 -115
- data/lib/mpdserver.rb +0 -1206
- data/tests/servertests.rb +0 -3405
data/README.rdoc
CHANGED
@@ -26,7 +26,7 @@ Then, make a new MPD instance:
|
|
26
26
|
|
27
27
|
mpd = MPD.new 'localhost', 6600
|
28
28
|
|
29
|
-
You can also omit the host and
|
29
|
+
You can also omit the host and port, and it will use the defaults.
|
30
30
|
|
31
31
|
mpd = MPD.new 'localhost'
|
32
32
|
mpd = MPD.new
|
@@ -43,7 +43,7 @@ When you are done, disconnect by calling disconnect.
|
|
43
43
|
be fixed by enabling callbacks (see the Callbacks section) or by issuing a
|
44
44
|
`ping` command at certain intervals
|
45
45
|
|
46
|
-
Once connected, you can issue commands to talk to the server
|
46
|
+
Once connected, you can issue commands to talk to the server.
|
47
47
|
|
48
48
|
mpd.connect
|
49
49
|
|
@@ -52,34 +52,45 @@ Once connected, you can issue commands to talk to the server
|
|
52
52
|
song = mpd.current_song
|
53
53
|
puts "Current Song: #{song.artist} - #{song.title}"
|
54
54
|
|
55
|
-
You can find documentation
|
55
|
+
You can find command documentation can be found {here}[http://www.rubydoc.info/github/archSeer/ruby-mpd/master/MPD].
|
56
56
|
|
57
57
|
== Commands
|
58
58
|
|
59
|
+
Some commands require URI paths. ruby-mpd allows you to use MPD::Song objects directly
|
60
|
+
and it extracts the file paths behind the scenes.
|
61
|
+
|
62
|
+
song = mpd.songs_by_artist('Elvis Presley').first # => MPD::Song
|
63
|
+
mpd.add song
|
64
|
+
|
65
|
+
=== Options
|
66
|
+
|
59
67
|
Some commands accept "option hashes" besides their default values. For example, +#move+
|
60
68
|
accepts an ID key instead of the position:
|
61
69
|
|
62
70
|
mpd.move(1, 10) # => move first song to position 10.
|
63
|
-
|
64
|
-
mpd.move(:id => 1, 10) # => move the song with the ID of 1 to position 10.
|
71
|
+
mpd.move({:id => 1}, 10) # => move the song with the ID of 1 to position 10.
|
65
72
|
|
66
73
|
Commands that accept ID's: +#move+, +#delete+, +#play+, +#song_priority+. +#seek+
|
67
|
-
accepts both +:pos+ and +:id+. *Note*:
|
74
|
+
accepts both +:pos+ and +:id+. *Note*: +#swap+ and +#swapid+ are still separate!
|
68
75
|
|
69
76
|
=== Ranges
|
70
77
|
|
71
78
|
Some commands also allow ranges instead of numbers, specifying a range of songs.
|
72
|
-
ruby-mpd correctly handles inclusive and exclusive ranges (1..10 vs 1...10).
|
73
|
-
|
79
|
+
ruby-mpd correctly handles inclusive and exclusive ranges (1..10 vs 1...10). Negative
|
80
|
+
range end means that we want the range to span until the end of the list.
|
81
|
+
|
82
|
+
For example, +#queue+ allows us to return only a subset of the queue:
|
74
83
|
|
75
84
|
mpd.queue.count # => 20
|
76
85
|
|
77
86
|
mpd.queue(1..10).count # => 10
|
78
87
|
|
79
|
-
|
88
|
+
mpd.queue(5..-1).count # => 15 (from 5 to the end of the range)
|
89
|
+
mpd.queue(5...-1).count # => 15 (does the same)
|
80
90
|
|
81
|
-
|
91
|
+
Move also allows specifying ranges to move a range of songs instead of just one.
|
82
92
|
|
93
|
+
mpd.move 1, 10 # => move song 1 to position 10.
|
83
94
|
mpd.move 1..3, 10 # => move songs 1, 2 and 3 to position 10 (and 11 and 12).
|
84
95
|
|
85
96
|
Commands that support ranges: +#delete+, +#move+, +#queue+, +#song_priority+, +#shuffle+,
|
@@ -88,7 +99,7 @@ Commands that support ranges: +#delete+, +#move+, +#queue+, +#song_priority+, +#
|
|
88
99
|
== Searching
|
89
100
|
|
90
101
|
Searching is case insensitive by default. To enable case sensitivity, pass a third
|
91
|
-
argument
|
102
|
+
argument with the key +:case_sensitive+. This does not work for Playlist#searchadd.
|
92
103
|
|
93
104
|
mpd.search(:artist, 'MyArtiSt', :case_sensitive => true)
|
94
105
|
|
@@ -104,7 +115,7 @@ Playlists are one of the objects that map the MPD commands onto a simple to use
|
|
104
115
|
object. Instead of going trough all those function calls, passing data along to
|
105
116
|
get your results, you simply use the object in an object-oriented way:
|
106
117
|
|
107
|
-
mpd.playlists #
|
118
|
+
mpd.playlists # => [MPD::Playlist, MPD::Playlist...]
|
108
119
|
|
109
120
|
playlist = mpd.playlists.first
|
110
121
|
|
@@ -124,8 +135,8 @@ action on them (add, delete, rename).
|
|
124
135
|
|
125
136
|
MPD::Playlist.new(mpd, 'name')
|
126
137
|
|
127
|
-
Currently, one also has to pass in the MPD
|
128
|
-
|
138
|
+
Currently, one also has to pass in the MPD instance, as playlists are tied to a
|
139
|
+
certain connection.
|
129
140
|
|
130
141
|
== Callbacks
|
131
142
|
|
@@ -139,24 +150,24 @@ the server without having to worry about timeouts.
|
|
139
150
|
To make use of callbacks, we need to:
|
140
151
|
|
141
152
|
1. Setup a callback to be called when something happens.
|
142
|
-
2. Connect to the server with callbacks
|
153
|
+
2. Connect to the server with callbacks enabled.
|
143
154
|
|
144
155
|
Firstly, we need to create a callback block and subscribe it, so that will get
|
145
156
|
triggered whenever a specific event happens. When the callback is triggered,
|
146
157
|
it will also recieve the new values of the event that happened.
|
147
158
|
|
148
|
-
So how do we do this? We use the MPD#on method, which sets it all up for us. The
|
159
|
+
So how do we do this? We use the +MPD#on+ method, which sets it all up for us. The
|
149
160
|
argument takes a symbol with the name of the event. The function also requires a block,
|
150
161
|
which is our actual callback that will get called.
|
151
162
|
|
152
163
|
mpd.on :volume do |volume|
|
153
|
-
puts "Volume was set to #{volume}"
|
164
|
+
puts "Volume was set to #{volume}!"
|
154
165
|
end
|
155
166
|
|
156
167
|
One can also use separate methods or Procs and whatnot, just pass them in as a parameter.
|
157
168
|
|
158
169
|
# Proc
|
159
|
-
proc = Proc.new {|volume| puts "Volume was set to #{volume}"
|
170
|
+
proc = Proc.new {|volume| puts "Volume was set to #{volume}!" }
|
160
171
|
mpd.on :volume, &proc
|
161
172
|
|
162
173
|
# Method
|
@@ -167,32 +178,33 @@ One can also use separate methods or Procs and whatnot, just pass them in as a p
|
|
167
178
|
method = self.method(:volume_change)
|
168
179
|
mpd.on :volume, &method
|
169
180
|
|
170
|
-
ruby-mpd supports callbacks for any of the keys returned by MPD#status
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
181
|
+
ruby-mpd supports callbacks for any of the keys returned by +MPD#status+, as well as +:connection+.
|
182
|
+
Here's the full list of events, along with the variables it will return:
|
183
|
+
|
184
|
+
* *volume*: The volume level as an Integer between 0-100.
|
185
|
+
* *repeat*: true or false
|
186
|
+
* *random*: true or false
|
187
|
+
* *single*: true or false
|
188
|
+
* *consume*: true or false
|
189
|
+
* *playlist*: 31-bit unsigned Integer, the playlist version number.
|
190
|
+
* *playlistlength*: Integer, the length of the playlist
|
191
|
+
* *state*: :play, :stop, or :pause, state of the playback.
|
192
|
+
* *song*: An MPD::Song object, representing the current song.
|
193
|
+
* *songid*: playlist songid of the current song stopped on or playing.
|
194
|
+
* *nextsong*: playlist song number of the next song to be played.
|
195
|
+
* *nextsongid*: playlist songid of the next song to be played.
|
196
|
+
* *time*: Returns two variables, *+total+* and *+elapsed+*, Integers representing seconds.
|
197
|
+
* *elapsed*: Float, representing total time elapsed within the current song, but with higher accuracy.
|
198
|
+
* *bitrate*: instantaneous bitrate in kbps.
|
199
|
+
* *xfade*: crossfade in seconds
|
200
|
+
* *mixrampdb*: mixramp threshold in dB (Float)
|
201
|
+
* *mixrampdelay*: mixrampdelay in seconds
|
202
|
+
* *audio*: Returns three variables: sampleRate, bits and channels.
|
203
|
+
* *updating_db*: job id
|
204
|
+
* *error*: if there is an error, returns message here
|
205
|
+
|
206
|
+
* *connection*: Are we connected to the daemon? true or false
|
207
|
+
|
196
208
|
Note that if the callback returns more than one value, the callback needs more arguments
|
197
209
|
in order to recieve those values:
|
198
210
|
|
@@ -208,10 +220,53 @@ in order to recieve those values:
|
|
208
220
|
Finally, the easiest step. In order for callbacks to work, connect to the server
|
209
221
|
with callbacks enabled:
|
210
222
|
|
211
|
-
|
223
|
+
mpd.connect true
|
212
224
|
|
213
225
|
Easy as pie. The above will connect to the server like normal, but this time it will
|
214
226
|
create a new thread that loops until you issue a `disconnect`. This loop checks the
|
215
227
|
server, then sleeps for two tenths of a second, then loops. Because it's continuously
|
216
228
|
polling the server, there's the added benefit of your client not being disconnected
|
217
229
|
due to inactivity.
|
230
|
+
|
231
|
+
== Not yet implemented
|
232
|
+
|
233
|
+
This section documents the features that are missing in this library at the moment.
|
234
|
+
|
235
|
+
=== Command lists
|
236
|
+
|
237
|
+
Command lists are not implemented yet. The proposed API would look like:
|
238
|
+
|
239
|
+
mpd.command_list do
|
240
|
+
volume 80
|
241
|
+
repeat true
|
242
|
+
status
|
243
|
+
end
|
244
|
+
|
245
|
+
What makes me not so eager to implement this is that MPD returns all values one after
|
246
|
+
another. This gets fixed with +command_list_ok_begin+, which returns +list_OK+ for every
|
247
|
+
command used, however then we still get more than one response, and I can't think of a
|
248
|
+
reasonable way to retun all of them back to the user. Maybe just ignore the return values?
|
249
|
+
|
250
|
+
=== Idle
|
251
|
+
|
252
|
+
To implement idle, what is needed is a lock that prevents sending commands to the daemon
|
253
|
+
while waiting for the response (except +noidle+). An intermediate solution would be to
|
254
|
+
queue the commands to send them later, when idle has returned the response.
|
255
|
+
|
256
|
+
Idle seems like a possible way to reimplement callbacks; make a separate connection
|
257
|
+
and just use idle and when it returns, simply use idle again and again.
|
258
|
+
|
259
|
+
=== Tests
|
260
|
+
|
261
|
+
Tests fail at the moment, as they are 6 years old. The entire MPD server mock class
|
262
|
+
either needs to be rewritten, or a mpd.conf along with a sample database and instructions
|
263
|
+
for a controlled environment needs to be written.
|
264
|
+
|
265
|
+
== TODO list
|
266
|
+
|
267
|
+
* MPD::Song, MPD::Directory.
|
268
|
+
* Make stickers a mixin for Playlist, Song, Directory...
|
269
|
+
* Namespace queue
|
270
|
+
* Make parsing per-command (because playlist is an integer in the :status command and
|
271
|
+
a string in :listplaylists, and :time is an array in status and integer in songs)
|
272
|
+
* Per command parsing should also skip :directory and :playlist keys inside #songs command.
|
data/lib/ruby-mpd.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'socket'
|
2
2
|
require 'thread'
|
3
3
|
|
4
|
+
require 'ruby-mpd/exceptions'
|
4
5
|
require 'ruby-mpd/song'
|
5
6
|
require 'ruby-mpd/parser'
|
6
7
|
require 'ruby-mpd/playlist'
|
@@ -16,27 +17,6 @@ require 'ruby-mpd/plugins/outputs'
|
|
16
17
|
require 'ruby-mpd/plugins/reflection'
|
17
18
|
require 'ruby-mpd/plugins/channels'
|
18
19
|
|
19
|
-
# TODO: object oriented: song commands and dir commands
|
20
|
-
# in MPD::Song, MPD::Directory.
|
21
|
-
# Make stickers a mixin for Playlist, Song, Directory...
|
22
|
-
# TODO: Namespace queue?
|
23
|
-
# TODO: fix parser to use build_group also
|
24
|
-
|
25
|
-
# command list as a do block
|
26
|
-
# mpd.command_list do
|
27
|
-
# volume 10
|
28
|
-
# play xyz
|
29
|
-
# end
|
30
|
-
|
31
|
-
# Playlist#rename -> Playlist#name= ?
|
32
|
-
# MPD::Playlist.new(mpd, 'name') no mpd?
|
33
|
-
|
34
|
-
# make it possible to use MPD::Song objects instead of filepath strings
|
35
|
-
|
36
|
-
# commands missing: #plchangeposid, #lsinfo, #listall.
|
37
|
-
|
38
|
-
# error codes stored in ack.h
|
39
|
-
|
40
20
|
# @!macro [new] error_raise
|
41
21
|
# @raise (see #send_command)
|
42
22
|
# @!macro [new] returnraise
|
@@ -45,10 +25,6 @@ require 'ruby-mpd/plugins/channels'
|
|
45
25
|
|
46
26
|
# The main class/namespace of the MPD client.
|
47
27
|
class MPD
|
48
|
-
|
49
|
-
# Standard MPD error.
|
50
|
-
class MPDError < StandardError; end
|
51
|
-
|
52
28
|
include Parser
|
53
29
|
|
54
30
|
include Plugins::Information
|
@@ -64,8 +40,6 @@ class MPD
|
|
64
40
|
|
65
41
|
# The version of the MPD protocol the server is using.
|
66
42
|
attr_reader :version
|
67
|
-
# A list of tags MPD accepts.
|
68
|
-
attr_reader :tags
|
69
43
|
|
70
44
|
# Initialize an MPD object with the specified hostname and port.
|
71
45
|
# When called without arguments, 'localhost' and 6600 are used.
|
@@ -74,6 +48,7 @@ class MPD
|
|
74
48
|
@port = port
|
75
49
|
@socket = nil
|
76
50
|
@version = nil
|
51
|
+
@tags = nil
|
77
52
|
|
78
53
|
@stop_cb_thread = false
|
79
54
|
@mutex = Mutex.new
|
@@ -85,15 +60,18 @@ class MPD
|
|
85
60
|
# that specific event happens.
|
86
61
|
#
|
87
62
|
# mpd.on :volume do |volume|
|
88
|
-
# puts "Volume was set to #{volume}"
|
63
|
+
# puts "Volume was set to #{volume}!"
|
89
64
|
# end
|
90
65
|
#
|
91
66
|
# One can also define separate methods or Procs and whatnot,
|
92
67
|
# just pass them in as a parameter.
|
93
68
|
#
|
94
|
-
# method = Proc.new {|volume| puts "Volume was set to #{volume}"
|
69
|
+
# method = Proc.new {|volume| puts "Volume was set to #{volume}!" }
|
95
70
|
# mpd.on :volume, &method
|
96
71
|
#
|
72
|
+
# @param [Symbol] event The event we wish to listen for.
|
73
|
+
# @param [Proc, Method] block The actual callback.
|
74
|
+
# @return [void]
|
97
75
|
def on(event, &block)
|
98
76
|
@callbacks[event] ||= []
|
99
77
|
@callbacks[event].push block
|
@@ -101,6 +79,7 @@ class MPD
|
|
101
79
|
|
102
80
|
# Triggers an event, running it's callbacks.
|
103
81
|
# @param [Symbol] event The event that happened.
|
82
|
+
# @return [void]
|
104
83
|
def emit(event, *args)
|
105
84
|
@callbacks[event] ||= []
|
106
85
|
@callbacks[event].each do |handle|
|
@@ -120,7 +99,7 @@ class MPD
|
|
120
99
|
# @return [true] Successfully connected.
|
121
100
|
# @raise [MPDError] If connect is called on an already connected instance.
|
122
101
|
def connect(callbacks = false)
|
123
|
-
raise
|
102
|
+
raise ConnectionError, 'Already connected!' if self.connected?
|
124
103
|
|
125
104
|
@socket = File.exists?(@hostname) ? UNIXSocket.new(@hostname) : TCPSocket.new(@hostname, @port)
|
126
105
|
@version = @socket.gets.chomp.gsub('OK MPD ', '') # Read the version
|
@@ -182,14 +161,16 @@ class MPD
|
|
182
161
|
# Disconnect from the MPD daemon. This has no effect if the client is not
|
183
162
|
# connected. Reconnect using the {#connect} method. This will also stop
|
184
163
|
# the callback thread, thus disabling callbacks.
|
164
|
+
# @return [Boolean] True if successfully disconnected, false otherwise.
|
185
165
|
def disconnect
|
186
166
|
@stop_cb_thread = true
|
187
167
|
|
188
|
-
return if
|
168
|
+
return false if !@socket
|
189
169
|
|
190
170
|
@socket.puts 'close'
|
191
171
|
@socket.close
|
192
172
|
@socket = nil
|
173
|
+
return true
|
193
174
|
end
|
194
175
|
|
195
176
|
# Kills the MPD process.
|
@@ -198,8 +179,9 @@ class MPD
|
|
198
179
|
send_command :kill
|
199
180
|
end
|
200
181
|
|
201
|
-
# Used for authentication with the server
|
182
|
+
# Used for authentication with the server.
|
202
183
|
# @param [String] pass Plaintext password
|
184
|
+
# @macro returnraise
|
203
185
|
def password(pass)
|
204
186
|
send_command :password, pass
|
205
187
|
end
|
@@ -210,64 +192,26 @@ class MPD
|
|
210
192
|
send_command :ping
|
211
193
|
end
|
212
194
|
|
213
|
-
|
214
|
-
|
215
|
-
# Lists all of the albums in the database.
|
216
|
-
# The optional argument is for specifying an artist to list
|
217
|
-
# the albums for
|
218
|
-
#
|
219
|
-
# @return [Array<String>] An array of album names.
|
220
|
-
def albums(artist = nil)
|
221
|
-
list :album, artist
|
222
|
-
end
|
223
|
-
|
224
|
-
# Lists all of the artists in the database.
|
225
|
-
#
|
226
|
-
# @return [Array<String>] An array of artist names.
|
227
|
-
def artists
|
228
|
-
list :artist
|
229
|
-
end
|
230
|
-
|
231
|
-
# List all of the directories in the database, starting at path.
|
232
|
-
# If path isn't specified, the root of the database is used
|
233
|
-
#
|
234
|
-
# @return [Array<String>] Array of directory names
|
235
|
-
def directories(path = nil)
|
236
|
-
response = send_command :listall, path
|
237
|
-
return response[:directory]
|
238
|
-
end
|
239
|
-
|
240
|
-
# List all of the files in the database, starting at path.
|
241
|
-
# If path isn't specified, the root of the database is used
|
195
|
+
# Used to send a command to the server, and to recieve the reply.
|
196
|
+
# Reply gets parsed. Synchronized on a mutex to be thread safe.
|
242
197
|
#
|
243
|
-
#
|
244
|
-
|
245
|
-
|
246
|
-
return response[:file]
|
247
|
-
end
|
248
|
-
|
249
|
-
# List all of the songs by an artist.
|
250
|
-
#
|
251
|
-
# @return [Array<MPD::Song>]
|
252
|
-
def songs_by_artist(artist)
|
253
|
-
search :artist, artist
|
254
|
-
end
|
255
|
-
|
256
|
-
# Used to send a command to the server. This synchronizes
|
257
|
-
# on a mutex to be thread safe
|
198
|
+
# Can be used to get low level direct access to MPD daemon. Not
|
199
|
+
# recommended, should be just left for internal use by other
|
200
|
+
# methods.
|
258
201
|
#
|
259
202
|
# @return (see #handle_server_response)
|
260
203
|
# @raise [MPDError] if the command failed.
|
261
204
|
def send_command(command, *args)
|
262
|
-
raise
|
205
|
+
raise ConnectionError, "Not connected to the server!" if @socket.nil?
|
263
206
|
|
264
207
|
@mutex.synchronize do
|
265
208
|
begin
|
266
209
|
@socket.puts convert_command(command, *args)
|
267
|
-
|
210
|
+
response = handle_server_response
|
211
|
+
return parse_response(command, response)
|
268
212
|
rescue Errno::EPIPE
|
269
213
|
@socket = nil
|
270
|
-
raise
|
214
|
+
raise ConnectionError, 'Broken pipe (got disconnected)'
|
271
215
|
end
|
272
216
|
end
|
273
217
|
end
|
@@ -275,8 +219,7 @@ class MPD
|
|
275
219
|
private
|
276
220
|
|
277
221
|
# Handles the server's response (called inside {#send_command}).
|
278
|
-
# Repeatedly reads the server's response from the socket
|
279
|
-
# processes the output.
|
222
|
+
# Repeatedly reads the server's response from the socket.
|
280
223
|
#
|
281
224
|
# @return (see Parser#build_response)
|
282
225
|
# @return [true] If "OK" is returned.
|
@@ -302,11 +245,27 @@ class MPD
|
|
302
245
|
|
303
246
|
if !error
|
304
247
|
return true if msg.empty?
|
305
|
-
return
|
248
|
+
return msg
|
306
249
|
else
|
307
250
|
err = error.match(/^ACK \[(?<code>\d+)\@(?<pos>\d+)\] \{(?<command>.*)\} (?<message>.+)$/)
|
308
|
-
raise
|
251
|
+
raise SERVER_ERRORS[err[:code].to_i], "[#{err[:command]}] #{err[:message]}"
|
309
252
|
end
|
310
253
|
end
|
311
254
|
|
255
|
+
SERVER_ERRORS = {
|
256
|
+
1 => NotListError,
|
257
|
+
2 => ServerArgumentError,
|
258
|
+
3 => IncorrectPassword,
|
259
|
+
4 => PermissionError,
|
260
|
+
5 => ServerError,
|
261
|
+
|
262
|
+
50 => NotFound,
|
263
|
+
51 => PlaylistMaxError,
|
264
|
+
52 => SystemError,
|
265
|
+
53 => PlaylistLoadError,
|
266
|
+
54 => AlreadyUpdating,
|
267
|
+
55 => NotPlaying,
|
268
|
+
56 => AlreadyExists
|
269
|
+
}
|
270
|
+
|
312
271
|
end
|