ruby-mpd 0.1.4

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.
data/lib/mpdserver.rb ADDED
@@ -0,0 +1,1206 @@
1
+ #
2
+ #== mpdserver.rb
3
+ #
4
+ # This is the test server for librmpd. It is a 'shallow' server,
5
+ # it implements only the client/server protocol in a highly
6
+ # scriptable manner. This means you can set up your own simple
7
+ # test music database for testing an mpd client. You can now
8
+ # distribute your unit tests (you do have unit tests, yes?) along
9
+ # with a test database (a YAML file), and anyone can check that
10
+ # your client is in working order.
11
+ #
12
+ #== Usage
13
+ #
14
+ # The MPD Server is a subclass of GServer, so you have a lot of
15
+ # flexibility at your disposal. The constructor of the server object
16
+ # takes the port, an optional YAML database file, and any args for GServer.
17
+ #
18
+ # The YAML database file can be one of your own creation, or you can use
19
+ # one supplied by librmpd (default)
20
+ #
21
+ # Example:
22
+ #
23
+ # require 'rubygems'
24
+ # require 'librmpd'
25
+ # require 'mpdserver'
26
+ #
27
+ # server = MPDTestServer.new 7700
28
+ # server.start
29
+ #
30
+ # You can then enable auditing to see what commands are run by a client:
31
+ #
32
+ # server.audit = true
33
+ #
34
+ # This will print any commands from a client to stdout
35
+ #
36
+ #=== Unit Testing
37
+ #
38
+ # For unit testing a client using the test server, I recommend using the
39
+ # set up and tear down methods to initialize and destroy a test server.
40
+ #
41
+ # def setup
42
+ # @server = MPDTestServer.new 7700
43
+ # @server.start
44
+ # end
45
+ #
46
+ # def teardown
47
+ # @server.stop
48
+ # end
49
+ #
50
+ # This will ensure you are using a clean server instance for each test.
51
+
52
+ require 'gserver'
53
+ require 'yaml'
54
+
55
+ class MPDTestServer < GServer
56
+
57
+ def initialize( port, db_file = nil, *args )
58
+ super port, *args
59
+
60
+ if db_file.nil?
61
+ db_file = __FILE__.gsub(/\/[^\/]*$/, '') + '/../data/database.yaml'
62
+ end
63
+
64
+ @status = {
65
+ :volume => 0,
66
+ :repeat => 0,
67
+ :random => 0,
68
+ :playlist => 1,
69
+ :state => 'stop',
70
+ :xfade => 0
71
+ }
72
+ @elapsed_time = 0
73
+ @current_song = nil
74
+ @database = YAML::load( File.open( db_file ) )
75
+ @songs = @database[0]
76
+ @playlists = @database[1]
77
+ @artists = []
78
+ @albums = []
79
+ @titles = []
80
+ @the_playlist = []
81
+ @playback_thread = nil
82
+ @filetree = {:name =>'', :dirs =>[], :songs =>[]}
83
+ @songs.each_with_index do |song,i|
84
+ song['id'] = i
85
+ if !song['artist'].nil? and !@artists.include? song['artist']
86
+ @artists << song['artist']
87
+ end
88
+ if !song['album'].nil? and !@albums.include? song['album']
89
+ @albums << song['album']
90
+ end
91
+ if !song['title'].nil?
92
+ @titles << song['title']
93
+ end
94
+ if !song['file'].nil?
95
+ dirs = song['file'].split '/'
96
+ dirs.pop
97
+ the_dir = @filetree
98
+ dirs.each do |d|
99
+ found = nil
100
+ the_dir[:dirs].each do |sub|
101
+ if sub[:name] == d
102
+ found = sub
103
+ break
104
+ end
105
+ end
106
+ if found.nil?
107
+ found = {:name => d, :dirs =>[], :songs =>[]}
108
+ the_dir[:dirs] << found
109
+ end
110
+ the_dir = found
111
+ end # End dirs.each
112
+ the_dir[:songs] << song
113
+ end # End if !song['file'].nil?
114
+ end # End @songs.each
115
+
116
+ sort_dir @filetree
117
+ @artists.sort!
118
+ @albums.sort!
119
+ @titles.sort!
120
+ end
121
+
122
+ def start
123
+ super
124
+
125
+ @playback_thread = Thread.new(@status, self) do |status, server|
126
+ while not server.stopped?
127
+ if status[:state] == 'play'
128
+ song = server.get_current_song
129
+ if song.nil?
130
+ server.elapsed_time = 0
131
+ status[:state] = 'stop'
132
+ next
133
+ end
134
+
135
+ status[:time] = "#{server.elapsed_time}:#{song['time']}"
136
+ status[:bitrate] = 192
137
+ status[:audio] = '44100:16:2'
138
+
139
+ if server.elapsed_time >= song['time'].to_i
140
+ server.elapsed_time = 0
141
+ server.next_song
142
+ end
143
+
144
+ server.elapsed_time = server.elapsed_time + 1
145
+ elsif status[:state] == 'pause'
146
+ song = server.get_current_song
147
+ if song.nil?
148
+ server.elapsed_time = 0
149
+ status[:state] = 'stop'
150
+ next
151
+ end
152
+ status[:time] = "#{server.elapsed_time}:#{song['time']}"
153
+ status[:bitrate] = 192
154
+ status[:audio] = '44100:16:2'
155
+ else
156
+ status[:time] = nil
157
+ status[:bitrate] = nil
158
+ status[:audio] = nil
159
+ server.elapsed_time = 0
160
+ end
161
+ sleep 1
162
+ end
163
+ end
164
+ end
165
+
166
+ def serve( sock )
167
+ command_list = []
168
+ in_cmd_list = false
169
+ in_ok_list = false
170
+ the_error = nil
171
+ sock.puts 'OK MPD 0.11.5'
172
+ begin
173
+ while line = sock.gets
174
+
175
+ args = build_args line
176
+
177
+ cmd = args.shift
178
+
179
+ if cmd == 'command_list_begin' and args.length == 0 and !in_cmd_list
180
+ in_cmd_list = true
181
+ log 'MPD: Starting Command List' if audit
182
+ elsif cmd == 'command_list_ok_begin' and args.length == 0 and !in_cmd_list
183
+ in_cmd_list = true
184
+ in_ok_list = true
185
+ log 'MPD: Starting Command OK List' if audit
186
+ elsif cmd == 'command_list_end' and in_cmd_list
187
+ log 'MPD: Running Command List' if audit
188
+
189
+ the_ret = true
190
+ command_list.each_with_index do |set,i|
191
+ the_ret = do_cmd sock, set[0], set[1]
192
+
193
+ if audit
194
+ log "MPD Command List: CMD ##{i}: \"#{set[0]}(#{set[1].join(', ')})\": " + (the_ret ? 'successful' : 'failed')
195
+ end
196
+
197
+ break unless the_ret
198
+
199
+ sock.puts 'list_OK' if in_ok_list
200
+
201
+ end
202
+
203
+ sock.puts 'OK' if the_ret
204
+
205
+ command_list.clear
206
+ in_cmd_list = false
207
+ in_ok_list = false
208
+ else
209
+ if in_cmd_list
210
+ command_list << [cmd, args]
211
+ else
212
+ ret = do_cmd sock, cmd, args
213
+ sock.puts 'OK' if ret
214
+ if audit
215
+ log "MPD Command \"#{cmd}(#{args.join(', ')})\": " + (ret ? 'successful' : 'failed')
216
+ end # End if audit
217
+ end # End if in_cmd_list
218
+ end # End if cmd == 'comand_list_begin' ...
219
+ end # End while line = sock.gets
220
+ rescue
221
+ end
222
+ end
223
+
224
+ def do_cmd( sock, cmd, args )
225
+ case cmd
226
+ when 'add'
227
+ if args.length == 0
228
+ # Add the entire database
229
+ @songs.each do |s|
230
+ s['_mod_ver'] = @status[:playlist]
231
+ incr_version
232
+ @the_playlist << s
233
+ end
234
+ return true
235
+ else
236
+ # Add a single entry
237
+ the_song = nil
238
+ @songs.each do |s|
239
+ if s['file'] == args[0]
240
+ the_song = s
241
+ break
242
+ end
243
+ end
244
+
245
+ if the_song.nil?
246
+ dir = locate_dir(args[0])
247
+ if not dir.nil?
248
+ # Add the dir
249
+ add_dir_to_pls dir
250
+ return true
251
+ else
252
+ return(cmd_fail(sock,'ACK [50@0] {add} directory or file not found'))
253
+ end
254
+ else
255
+ the_song['_mod_ver'] = @status[:playlist]
256
+ incr_version
257
+ @the_playlist << the_song
258
+ return true
259
+ end
260
+ end
261
+ when 'clear'
262
+ args_check( sock, cmd, args, 0 ) do
263
+ incr_version
264
+ @the_playlist = []
265
+ @current_song = nil
266
+ return true
267
+ end
268
+ when 'clearerror'
269
+ args_check( sock, cmd, args, 0 ) do
270
+ the_error = nil
271
+ return true
272
+ end
273
+ when 'close'
274
+ sock.close
275
+ return true
276
+ when 'crossfade'
277
+ args_check( sock, cmd, args, 1 ) do |args|
278
+ if is_int(args[0]) and args[0].to_i >= 0
279
+ @status[:xfade] = args[0].to_i
280
+ return true
281
+ else
282
+ return(cmd_fail(sock,"ACK [2@0] {crossfade} \"#{args[0]}\" is not a integer >= 0"))
283
+ end
284
+ end
285
+ when 'currentsong'
286
+ args_check( sock, cmd, args, 0 ) do
287
+ if @current_song != nil and @current_song < @the_playlist.length
288
+ send_song sock, @the_playlist[@current_song]
289
+ end
290
+ return true
291
+ end
292
+ when 'delete'
293
+ args_check( sock, cmd, args, 1 ) do |args|
294
+ if is_int args[0]
295
+ if args[0].to_i < 0 or args[0].to_i >= @the_playlist.length
296
+ return(cmd_fail(sock,"ACK [50@0] {delete} song doesn't exist: \"#{args[0]}\""))
297
+ else
298
+ @the_playlist.delete_at args[0].to_i
299
+ args[0].to_i.upto @the_playlist.length - 1 do |i|
300
+ @the_playlist[i]['_mod_ver'] = @status[:playlist]
301
+ end
302
+ incr_version
303
+ return true
304
+ end
305
+ else
306
+ return(cmd_fail('ACK [2@0] {delete} need a positive integer'))
307
+ end
308
+ end
309
+ when 'deleteid'
310
+ args_check( sock, cmd, args, 1 ) do |args|
311
+ if is_int args[0]
312
+ the_song = nil
313
+ @the_playlist.each do |song|
314
+ if song['id'] == args[0].to_i
315
+ the_song = song
316
+ break
317
+ end
318
+ end
319
+
320
+ if not the_song.nil?
321
+ index = @the_playlist.index the_song
322
+ @the_playlist.delete the_song
323
+ index.upto @the_playlist.length - 1 do |i|
324
+ @the_playlist[i]['_mod_ver'] = @status[:playlist]
325
+ end
326
+ incr_version
327
+ return true
328
+ else
329
+ return(cmd_fail(sock,"ACK [50@0] {deleteid} song id doesn't exist: \"#{args[0]}\""))
330
+ end
331
+ else
332
+ return(cmd_fail(sock,'ACK [2@0] {deleteid} need a positive integer'))
333
+ end
334
+ end
335
+ when 'find'
336
+ args_check( sock, cmd, args, 2 ) do |args|
337
+ if args[0] != 'album' and args[0] != 'artist' and args[0] != 'title'
338
+ return(cmd_fail(sock,'ACK [2@0] {find} incorrect arguments'))
339
+ else
340
+ if args[0] == 'album'
341
+ @songs.each do |song|
342
+ if song['album'] == args[1]
343
+ send_song sock, song
344
+ end
345
+ end
346
+ elsif args[0] == 'artist'
347
+ @songs.each do |song|
348
+ if song['artist'] == args[1]
349
+ send_song sock, song
350
+ end
351
+ end
352
+ elsif args[0] == 'title'
353
+ @songs.each do |song|
354
+ if song['title'] == args[1]
355
+ send_song sock, song
356
+ end
357
+ end
358
+ end
359
+ return true
360
+ end
361
+ end
362
+ when 'kill'
363
+ args_check( sock, cmd, args, 0 ) do
364
+ sock.close
365
+ return true
366
+ end
367
+ when 'list'
368
+ args_check( sock, cmd, args, 1..2 ) do |args|
369
+ if args[0] != 'album' and args[0] != 'artist' and args[0] != 'title'
370
+ return(cmd_fail(sock,"ACK [2@0] {list} \"#{args[0]}\" is not known"))
371
+ elsif args[0] == 'artist' and args.length > 1
372
+ return(cmd_fail(sock,'ACK [2@0] {list} should be "Album" for 3 arguments'))
373
+ else
374
+ if args[0] == 'artist'
375
+ # List all Artists
376
+ @artists.each do |artist|
377
+ sock.puts "Artist: #{artist}"
378
+ end
379
+ return true
380
+ elsif args[0] == 'title'
381
+ # List all Titles
382
+ @titles.each do |title|
383
+ sock.puts "Title: #{title}"
384
+ end
385
+ return true
386
+ else
387
+ if args.length == 2
388
+ # List all Albums by Artist
389
+ # artist == args[1]
390
+ listed = []
391
+ @songs.each do |song|
392
+ if song['artist'] == args[1]
393
+ if not song['album'].nil? and !listed.include? song['album']
394
+ sock.puts "Album: #{song['album']}"
395
+ listed << song['album']
396
+ end
397
+ end
398
+ end
399
+ return true
400
+ else
401
+ # List all Albums
402
+ @albums.each do |album|
403
+ sock.puts "Album: #{album}"
404
+ end
405
+ return true
406
+ end
407
+ end
408
+ end
409
+ end
410
+ when 'listall'
411
+ args_check( sock, cmd, args, 0..1 ) do |args|
412
+ if args.length == 0
413
+ @filetree[:dirs].each do |d|
414
+ send_dir sock, d, false
415
+ end
416
+ else
417
+ was_song = false
418
+ @songs.each do |song|
419
+ if song['file'] == args[0]
420
+ sock.puts "file: #{song['file']}"
421
+ was_song = true
422
+ break
423
+ end
424
+ end
425
+
426
+ if was_song
427
+ return true
428
+ end
429
+
430
+ dir = locate_dir args[0]
431
+ if not dir.nil?
432
+ parents = args[0].split '/'
433
+ parents.pop
434
+ parents = parents.join '/'
435
+ parents += '/' unless parents.length == 0
436
+ send_dir sock, dir, false, parents
437
+ else
438
+ return(cmd_fail(sock,'ACK [50@0] {listall} directory or file not found'))
439
+ end
440
+ end
441
+ return true
442
+ end
443
+ when 'listallinfo'
444
+ args_check( sock, cmd, args, 0..1 ) do |args|
445
+ if args.length == 0
446
+ @filetree[:dirs].each do |d|
447
+ send_dir sock, d, true
448
+ end
449
+ else
450
+ was_song = false
451
+ @songs.each do |song|
452
+ if song['file'] == args[0]
453
+ send_song song
454
+ was_song = true
455
+ break
456
+ end
457
+ end
458
+
459
+ if was_song
460
+ return true
461
+ end
462
+
463
+ dir = locate_dir args[0]
464
+ if not dir.nil?
465
+ parents = args[0].split '/'
466
+ parents.pop
467
+ parents = parents.join '/'
468
+ parents += '/' unless parents.length == 0
469
+ send_dir sock, dir, true, parents
470
+ else
471
+ return(cmd_fail(sock,'ACK [50@0] {listallinfo} directory or file not found'))
472
+ end
473
+ end
474
+ return true
475
+ end
476
+ when 'load'
477
+ args_check( sock, cmd, args, 1 ) do
478
+ # incr_version for each song loaded
479
+ pls = args[0] + '.m3u'
480
+ the_pls = nil
481
+ @playlists.each do |p|
482
+ if p['file'] == pls
483
+ the_pls = p
484
+ break
485
+ end
486
+ end
487
+
488
+ unless the_pls.nil?
489
+ the_pls['songs'].each do |song|
490
+ song['_mod_ver'] = @status[:playlist]
491
+ @the_playlist << song
492
+ incr_version
493
+ end
494
+ else
495
+ return(cmd_fail(sock,"ACK [50@0] {load} playlist \"#{args[0]}\" not found"))
496
+ end
497
+ end
498
+ when 'lsinfo'
499
+ args_check( sock, cmd, args, 0..1 ) do
500
+ if args.length == 0
501
+ @filetree[:dirs].each do |d|
502
+ sock.puts "directory: #{d[:name]}"
503
+ d[:songs].each do |s|
504
+ send_song sock, s
505
+ end
506
+ end
507
+ @playlists.each do |pls|
508
+ sock.puts "playlist: #{pls['file'].gsub( /\.m3u$/, '' )}"
509
+ end
510
+ else
511
+ dir = locate_dir args[0]
512
+ if dir.nil?
513
+ return(cmd_fail(sock,"ACK [50@0] {lsinfo} directory not found"))
514
+ else
515
+ dir[:dirs].each do |d|
516
+ sock.puts "directory: #{args[0] + '/' + d[:name]}"
517
+ end
518
+ dir[:songs].each do |s|
519
+ send_song sock, s
520
+ end
521
+ end
522
+ end
523
+ return true
524
+ end
525
+ when 'move'
526
+ args_check( sock, cmd, args, 2 ) do |args|
527
+ if !is_int args[0]
528
+ return(cmd_fail(sock,"ACK [2@0] {move} \"#{args[0]}\" is not a integer"))
529
+ elsif !is_int args[1]
530
+ return(cmd_fail(sock,"ACK [2@0] {move} \"#{args[1]}\" is not a integer"))
531
+ elsif args[0].to_i < 0 or args[0].to_i >= @the_playlist.length
532
+ return(cmd_fail(sock,"ACK [50@0] {move} song doesn't exist: \"#{args[0]}\""))
533
+ elsif args[1].to_i < 0 or args[1].to_i >= @the_playlist.length
534
+ return(cmd_fail(sock,"ACK [50@0] {move} song doesn't exist: \"#{args[1]}\""))
535
+ else
536
+ tmp = @the_playlist.delete_at args[0].to_i
537
+ @the_playlist.insert args[1].to_i, tmp
538
+ if args[0].to_i < args[1].to_i
539
+ args[0].to_i.upto args[1].to_i do |i|
540
+ @the_playlist[i]['_mod_ver'] = @status[:playlist]
541
+ end
542
+ else
543
+ args[1].to_i.upto args[0].to_i do |i|
544
+ @the_playlist[i]['_mod_ver'] = @status[:playlist]
545
+ end
546
+ end
547
+ incr_version
548
+ return true
549
+ end
550
+ end
551
+ when 'moveid'
552
+ args_check( sock, cmd, args, 2 ) do |args|
553
+ if !is_int args[0]
554
+ return(cmd_fail(sock,"ACK [2@0] {moveid} \"#{args[0]}\" is not a integer"))
555
+ elsif !is_int args[1]
556
+ return(cmd_fail(sock,"ACK [2@0] {moveid} \"#{args[1]}\" is not a integer"))
557
+ elsif args[1].to_i < 0 or args[1].to_i >= @the_playlist.length
558
+ return(cmd_fail(sock,"ACK [50@0] {moveid} song doesn't exist: \"#{args[1]}\""))
559
+ else
560
+ # Note: negative args should be checked
561
+ the_song = nil
562
+ index = -1
563
+ @the_playlist.each_with_index do |song,i|
564
+ if song['id'] == args[0].to_i
565
+ the_song = song
566
+ index = i
567
+ end
568
+ end
569
+ if the_song.nil?
570
+ return(cmd_fail(sock,"ACK [50@0] {moveid} song id doesn't exist: \"#{args[0]}\""))
571
+ end
572
+ tmp = @the_playlist.delete_at index
573
+ @the_playlist.insert args[1].to_i, tmp
574
+ if index < args[1].to_i
575
+ index.upto args[1].to_i do |i|
576
+ @the_playlist[i]['_mod_ver'] = @status[:playlist]
577
+ end
578
+ else
579
+ args[1].to_i.upto index do |i|
580
+ @the_playlist[i]['_mod_ver'] = @status[:playlist]
581
+ end
582
+ end
583
+ incr_version
584
+ return true
585
+ end
586
+ end
587
+ when 'next'
588
+ args_check( sock, cmd, args, 0 ) do
589
+ if @status[:state] != 'stop'
590
+ next_song
591
+ @elapsed_time = 0
592
+ @status[:state] = 'play'
593
+ end
594
+ return true
595
+ end
596
+ when 'pause'
597
+ args_check( sock, cmd, args, 0..1 ) do |args|
598
+ if args.length > 0 and not is_bool args[0]
599
+ return(cmd_fail(sock,"ACK [2@0] {pause} \"#{args[0]}\" is not 0 or 1"))
600
+ end
601
+
602
+ if @status[:state] != 'stop'
603
+ if args.length == 1
604
+ @status[:state] = ( args[0] == '1' ? 'pause' : 'play' )
605
+ else
606
+ @status[:state] = ( @status[:state] == 'pause' ? 'play' : 'pause' )
607
+ end
608
+ end
609
+
610
+ return true
611
+ end
612
+ when 'password'
613
+ args_check( sock, cmd, args, 1 ) do |args|
614
+ return true if args[0] == 'test'
615
+ return(cmd_fail(sock,"ACK [3@0] {password} incorrect password"))
616
+ end
617
+ when 'ping'
618
+ args_check( sock, cmd, args, 0 ) do
619
+ return true
620
+ end
621
+ when 'play'
622
+ args_check( sock, cmd, args, 0..1 ) do |args|
623
+ if args.length > 0 and !is_int(args[0])
624
+ return(cmd_fail(sock,'ACK [2@0] {play} need a positive integer'))
625
+ else
626
+ args.clear if args[0] == '-1'
627
+ if args.length == 0
628
+ if @the_playlist.length > 0 and @status[:state] != 'play'
629
+ @current_song = 0 if @current_song.nil?
630
+ @elapsed_time = 0
631
+ @status[:state] = 'play'
632
+ end
633
+ else
634
+ if args[0].to_i < 0 or args[0].to_i >= @the_playlist.length
635
+ return(cmd_fail(sock,"ACK [50@0] {play} song doesn't exist: \"#{args[0]}\""))
636
+ end
637
+
638
+ @current_song = args[0].to_i
639
+ @elapsed_time = 0
640
+ @status[:state] = 'play'
641
+ end
642
+ return true
643
+ end
644
+ end
645
+ when 'playid'
646
+ args_check( sock, cmd, args, 0..1 ) do |args|
647
+ if args.length > 0 and !is_int(args[0])
648
+ return(cmd_fail(sock,'ACK [2@0] {playid} need a positive integer'))
649
+ else
650
+ args.clear if args[0] == '-1'
651
+ if args.length == 0
652
+ if @the_playlist.length > 0 and @status[:state] != 'play'
653
+ @current_song = 0 if @current_song.nil?
654
+ @elapsed_time = 0
655
+ @status[:state] = 'play'
656
+ end
657
+ else
658
+ index = nil
659
+ @the_playlist.each_with_index do |s,i|
660
+ if s['id'] == args[0].to_i
661
+ index = i
662
+ break;
663
+ end
664
+ end
665
+
666
+ return(cmd_fail(sock,"ACK [50@0] {playid} song id doesn't exist: \"#{args[0]}\"")) if index.nil?
667
+
668
+ @current_song = index
669
+ @elapsed_time = 0
670
+ @status[:state] = 'play'
671
+ end
672
+ return true
673
+ end
674
+ end
675
+ when 'playlist'
676
+ log 'MPD Warning: Call to Deprecated API: "playlist"' if audit
677
+ args_check( sock, cmd, args, 0 ) do
678
+ @the_playlist.each_with_index do |v,i|
679
+ sock.puts "#{i}:#{v['file']}"
680
+ end
681
+ return true
682
+ end
683
+ when 'playlistinfo'
684
+ args_check( sock, cmd, args, 0..1 ) do |args|
685
+ if args.length > 0 and !is_int(args[0])
686
+ return(cmd_fail(sock,'ACK [2@0] {playlistinfo} need a positive integer'))
687
+ else
688
+ args.clear if args.length > 0 and args[0].to_i < 0
689
+ if args.length != 0
690
+ if args[0].to_i >= @the_playlist.length
691
+ return(cmd_fail(sock,"ACK [50@0] {playlistinfo} song doesn't exist: \"#{args[0]}\""))
692
+ else
693
+ song = @the_playlist[args[0].to_i]
694
+ send_song sock, song
695
+ sock.puts "Pos: #{args[0].to_i}"
696
+ sock.puts "Id: #{song['id']}"
697
+ return true
698
+ end
699
+ else
700
+ @the_playlist.each_with_index do |song,i|
701
+ send_song sock, song
702
+ sock.puts "Pos: #{i}"
703
+ sock.puts "Id: #{song['id']}"
704
+ end
705
+ return true
706
+ end
707
+ end
708
+ end
709
+ when 'playlistid'
710
+ args_check( sock, cmd, args, 0..1 ) do |args|
711
+ if args.length > 0 and !is_int(args[0])
712
+ return(cmd_fail(sock,'ACK [2@0] {playlistid} need a positive integer'))
713
+ else
714
+ song = nil
715
+ pos = nil
716
+ args.clear if args[0].to_i < 0
717
+ if args.length != 0
718
+ @the_playlist.each_with_index do |s,i|
719
+ if s['id'] == args[0].to_i
720
+ song = s
721
+ pos = i
722
+ break;
723
+ end
724
+ end
725
+
726
+ return(cmd_fail(sock,"ACK [50@0] {playlistid} song id doesn't exist: \"#{args[0]}\"")) if song.nil?
727
+
728
+ send_song sock, song
729
+ sock.puts "Pos: #{pos}"
730
+ return true
731
+ else
732
+ @the_playlist.each_with_index do |song,i|
733
+ send_song sock, song
734
+ sock.puts "Pos: #{i}"
735
+ end
736
+ return true
737
+ end
738
+ end
739
+ end
740
+ when 'plchanges'
741
+ args_check( sock, cmd, args, 1 ) do |args|
742
+ if args.length > 0 and !is_int(args[0])
743
+ return(cmd_fail(sock,'ACK [2@0] {plchanges} need a positive integer'))
744
+ else
745
+ # Note: args[0] < 0 just return OK...
746
+ @the_playlist.each_with_index do |song,i|
747
+ if args[0].to_i > @status[:playlist] or song['_mod_ver'] >= args[0].to_i or song['_mod_ver'] == 0
748
+ send_song sock, song
749
+ sock.puts "Pos: #{i}"
750
+ end
751
+ end
752
+ return true
753
+ end
754
+ end
755
+ when 'plchangesposid'
756
+ args_check( sock, cmd, args, 1 ) do |args|
757
+ if args.length > 0 and !is_int(args[0])
758
+ return(cmd_fail(sock,'ACK [2@0] {plchangesposid} need a positive integer'))
759
+ else
760
+ # Note: args[0] < 0 just return OK...
761
+ @the_playlist.each_with_index do |song,i|
762
+ if args[0].to_i > @status[:playlist] or song['_mod_ver'] >= args[0].to_i or song['_mod_ver'] == 0
763
+ sock.puts "cpos: #{i}"
764
+ sock.puts "Id: #{song['id']}"
765
+ end
766
+ end
767
+ return true
768
+ end
769
+ end
770
+ when 'previous'
771
+ args_check( sock, cmd, args, 0 ) do
772
+ return true if @status[:state] == 'stop'
773
+ prev_song
774
+ @elapsed_time = 0
775
+ @status[:state] = 'play'
776
+ return true
777
+ end
778
+ when 'random'
779
+ args_check( sock, cmd, args, 1 ) do |args|
780
+ if is_bool args[0]
781
+ @status[:random] = args[0].to_i
782
+ return true
783
+ elsif is_int args[0]
784
+ return(cmd_fail(sock,"ACK [2@0] {random} \"#{args[0]}\" is not 0 or 1"))
785
+ else
786
+ return(cmd_fail(sock,'ACK [2@0] {random} need an integer'))
787
+ end
788
+ end
789
+ when 'repeat'
790
+ args_check( sock, cmd, args, 1 ) do |args|
791
+ if is_bool args[0]
792
+ @status[:repeat] = args[0].to_i
793
+ return true
794
+ elsif is_int args[0]
795
+ return(cmd_fail(sock,"ACK [2@0] {repeat} \"#{args[0]}\" is not 0 or 1"))
796
+ else
797
+ return(cmd_fail(sock,'ACK [2@0] {repeat} need an integer'))
798
+ end
799
+ end
800
+ when 'rm'
801
+ args_check( sock, cmd, args, 1 ) do |args|
802
+ rm_pls = args[0] + '.m3u'
803
+ the_pls = -1
804
+ @playlists.each_with_index do |pls,i|
805
+ the_pls = i if pls['file'] == rm_pls
806
+ end
807
+
808
+ if the_pls != -1
809
+ @playlists.delete_at the_pls
810
+ return true
811
+ else
812
+ return(cmd_fail(sock,"ACK [50@0] {rm} playlist \"#{args[0]}\" not found"))
813
+ end
814
+ end
815
+ when 'save'
816
+ args_check( sock, cmd, args, 1 ) do |args|
817
+ new_playlist = {'file' => args[0]+'.m3u', 'songs' => @the_playlist}
818
+ @playlists << new_playlist
819
+ return true
820
+ end
821
+ when 'search'
822
+ args_check( sock, cmd, args, 2 ) do |args|
823
+ if args[0] != 'title' and args[0] != 'artist' and args[0] != 'album' and args[0] != 'filename'
824
+ return(cmd_fail(sock,'ACK [2@0] {search} incorrect arguments'))
825
+ end
826
+ args[0] = 'file' if args[0] == 'filename'
827
+ @songs.each do |song|
828
+ data = song[args[0]]
829
+ if not data.nil? and data.downcase.include? args[1]
830
+ send_song sock, song
831
+ end
832
+ end
833
+ return true
834
+ end
835
+ when 'seek'
836
+ args_check( sock, cmd, args, 2 ) do |args|
837
+ if !is_int args[0]
838
+ return(cmd_fail(sock,"ACK [2@0] {seek} \"#{args[0]}\" is not a integer"))
839
+ elsif !is_int args[1]
840
+ return(cmd_fail(sock,"ACK [2@0] {seek} \"#{args[1]}\" is not a integer"))
841
+ else
842
+ if args[0].to_i > @the_playlist.length or args[0].to_i < 0
843
+ return(cmd_fail(sock,"ACK [50@0] {seek} song doesn't exist: \"#{args[0]}\""))
844
+ end
845
+ args[1] = '0' if args[1].to_i < 0
846
+ song = @the_playlist[args[0].to_i]
847
+ if args[1].to_i >= song['time'].to_i
848
+ if args[0].to_i + 1 < @the_playlist.length
849
+ @current_song = args[0].to_i + 1
850
+ @elapsed_time = 0
851
+ @status[:state] = 'play' unless @status[:state] == 'pause'
852
+ else
853
+ @current_song = nil
854
+ @elapsed_time = 0
855
+ @status[:state] = 'stop'
856
+ end
857
+ else
858
+ @current_song = args[0].to_i
859
+ @elapsed_time = args[1].to_i
860
+ @status[:state] = 'play' unless @status[:state] == 'pause'
861
+ end
862
+ return true
863
+ end
864
+ end
865
+ when 'seekid'
866
+ args_check( sock, cmd, args, 2 ) do |args|
867
+ if !is_int args[0]
868
+ return(cmd_fail(sock,"ACK [2@0] {seekid} \"#{args[0]}\" is not a integer"))
869
+ elsif !is_int args[1]
870
+ return(cmd_fail(sock,"ACK [2@0] {seekid} \"#{args[1]}\" is not a integer"))
871
+ else
872
+ pos = nil
873
+ song = nil
874
+ @the_playlist.each_with_index do |s,i|
875
+ if s['id'] == args[0].to_i
876
+ song = s
877
+ pos = i
878
+ break;
879
+ end
880
+ end
881
+
882
+ if song.nil?
883
+ return(cmd_fail(sock,"ACK [50@0] {seekid} song id doesn't exist: \"#{args[0]}\""))
884
+ end
885
+
886
+ args[1] = '0' if args[1].to_i < 0
887
+ if args[1].to_i >= song['time'].to_i
888
+ if pos + 1 < @the_playlist.length
889
+ @current_song = pos + 1
890
+ @elapsed_time = 0
891
+ @status[:state] = 'play' unless @status[:state] == 'pause'
892
+ else
893
+ @current_song = nil
894
+ @elapsed_time = 0
895
+ @status[:state] = 'stop'
896
+ end
897
+ else
898
+ @current_song = pos
899
+ @elapsed_time = args[1].to_i
900
+ @status[:state] = 'play' unless @status[:state] == 'pause'
901
+ end
902
+ return true
903
+ end
904
+ end
905
+ when 'setvol'
906
+ args_check( sock, cmd, args, 1 ) do |args|
907
+ if !is_int args[0]
908
+ return(cmd_fail(sock,'ACK [2@0] {setvol} need an integer'))
909
+ else
910
+ # Note: args[0] < 0 actually sets the vol val to < 0
911
+ @status[:volume] = args[0].to_i
912
+ return true
913
+ end
914
+ end
915
+ when 'shuffle'
916
+ args_check( sock, cmd, args, 0 ) do
917
+ @the_playlist.each do |s|
918
+ s['_mod_ver'] = @status[:playlist]
919
+ end
920
+ incr_version
921
+ @the_playlist.reverse!
922
+ return true
923
+ end
924
+ when 'stats'
925
+ args_check( sock, cmd, args, 0 ) do
926
+ # artists
927
+ sock.puts "artists: #{@artists.size}"
928
+ # albums
929
+ sock.puts "albums: #{@albums.size}"
930
+ # songs
931
+ sock.puts "songs: #{@songs.size}"
932
+ # uptime
933
+ sock.puts "uptime: 500"
934
+ # db_playtime
935
+ time = 0
936
+ @songs.each do |s|
937
+ time += s['time'].to_i
938
+ end
939
+ sock.puts "db_playtime: #{time}"
940
+ # db_update
941
+ sock.puts "db_update: 1159418502"
942
+ # playtime
943
+ sock.puts "playtime: 10"
944
+ return true
945
+ end
946
+ when 'status'
947
+ args_check( sock, cmd, args, 0 ) do
948
+ @status.each_pair do |key,val|
949
+ sock.puts "#{key}: #{val}" unless val.nil?
950
+ end
951
+ sock.puts "playlistlength: #{@the_playlist.length}"
952
+
953
+ if @current_song != nil and @the_playlist.length > @current_song
954
+ sock.puts "song: #{@current_song}"
955
+ sock.puts "songid: #{@the_playlist[@current_song]['id']}"
956
+ end
957
+
958
+ @status[:updating_db] = nil
959
+ return true
960
+ end
961
+ when 'stop'
962
+ args_check( sock, cmd, args, 0 ) do
963
+ @status[:state] = 'stop'
964
+ @status[:time] = nil
965
+ @status[:bitrate] = nil
966
+ @status[:audio] = nil
967
+ return true
968
+ end
969
+ when 'swap'
970
+ args_check( sock, cmd, args, 2 ) do |args|
971
+ if !is_int args[0]
972
+ return(cmd_fail(sock,"ACK [2@0] {swap} \"#{args[0]}\" is not a integer"))
973
+ elsif !is_int args[1]
974
+ return(cmd_fail(sock,"ACK [2@0] {swap} \"#{args[1]}\" is not a integer"))
975
+ elsif args[0].to_i >= @the_playlist.length or args[0].to_i < 0
976
+ return(cmd_fail(sock,"ACK [50@0] {swap} song doesn't exist: \"#{args[0]}\""))
977
+ elsif args[1].to_i >= @the_playlist.length or args[1].to_i < 0
978
+ return(cmd_fail(sock,"ACK [50@0] {swap} song doesn't exist: \"#{args[1]}\""))
979
+ else
980
+ tmp = @the_playlist[args[1].to_i]
981
+ @the_playlist[args[1].to_i] = @the_playlist[args[0].to_i]
982
+ @the_playlist[args[0].to_i] = tmp
983
+ @the_playlist[args[0].to_i]['_mod_ver'] = @status[:playlist]
984
+ @the_playlist[args[1].to_i]['_mod_ver'] = @status[:playlist]
985
+ incr_version
986
+ return true
987
+ end
988
+ end
989
+ when 'swapid'
990
+ args_check( sock, cmd, args, 2 ) do |args|
991
+ if !is_int args[0]
992
+ return(cmd_fail(sock,"ACK [2@0] {swapid} \"#{args[0]}\" is not a integer"))
993
+ elsif !is_int args[1]
994
+ return(cmd_fail(sock,"ACK [2@0] {swapid} \"#{args[1]}\" is not a integer"))
995
+ else
996
+ from = nil
997
+ to = nil
998
+ @the_playlist.each_with_index do |song,i|
999
+ if song['id'] == args[0].to_i
1000
+ from = i
1001
+ elsif song['id'] == args[1].to_i
1002
+ to = i
1003
+ end
1004
+ end
1005
+ if from.nil?
1006
+ return(cmd_fail(sock,"ACK [50@0] {swapid} song id doesn't exist: \"#{args[0]}\""))
1007
+ elsif to.nil?
1008
+ return(cmd_fail(sock,"ACK [50@0] {swapid} song id doesn't exist: \"#{args[1]}\""))
1009
+ end
1010
+ tmp = @the_playlist[to]
1011
+ @the_playlist[to] = @the_playlist[from]
1012
+ @the_playlist[from] = tmp
1013
+ @the_playlist[to]['_mod_ver'] = @status[:playlist]
1014
+ @the_playlist[from]['_mod_ver'] = @status[:playlist]
1015
+
1016
+ incr_version
1017
+ return true
1018
+ end
1019
+ end
1020
+ when 'update'
1021
+ args_check( sock, cmd, args, 0..1 ) do |args|
1022
+ incr_version
1023
+ sock.puts 'updating_db: 1'
1024
+ @status[:updating_db] = '1'
1025
+ return true
1026
+ end
1027
+ when 'volume'
1028
+ log 'MPD Warning: Call to Deprecated API: "volume"' if audit
1029
+ args_check( sock, cmd, args, 1 ) do |args|
1030
+ if !is_int args[0]
1031
+ return(cmd_fail(sock,'ACK [2@0] {volume} need an integer'))
1032
+ else
1033
+ # Note: args[0] < 0 subtract from the volume
1034
+ @status[:volume] += args[0].to_i
1035
+ return true
1036
+ end
1037
+ end
1038
+ else
1039
+ return(cmd_fail(sock,"ACK [5@0] {} unknown command #{cmd}"))
1040
+ end # End Case cmd
1041
+ end
1042
+
1043
+ def get_current_song
1044
+ if @current_song != nil and @current_song < @the_playlist.length
1045
+ return @the_playlist[@current_song]
1046
+ else
1047
+ return nil
1048
+ end
1049
+ end
1050
+
1051
+ def prev_song
1052
+ return if @current_song.nil?
1053
+ if @current_song == 0
1054
+ @elapsed_time = 0
1055
+ else
1056
+ @current_song -= 1
1057
+ end
1058
+ end
1059
+
1060
+ def next_song
1061
+ return if @current_song.nil?
1062
+ @current_song = (@current_song +1 < @the_playlist.length ? @current_song +1 : nil)
1063
+ end
1064
+
1065
+ def elapsed_time=( new_time )
1066
+ @elapsed_time = new_time
1067
+ end
1068
+
1069
+ def elapsed_time
1070
+ @elapsed_time
1071
+ end
1072
+
1073
+ def incr_version
1074
+ if @status[:playlist] == 2147483647
1075
+ @status[:playlist] = 1
1076
+ @the_playlist.each do |song|
1077
+ song['_mod_ver'] = 0
1078
+ end
1079
+ else
1080
+ @status[:playlist] += 1
1081
+ end
1082
+ end
1083
+
1084
+ def cmd_fail( sock, msg )
1085
+ sock.puts msg
1086
+ return false
1087
+ end
1088
+
1089
+ def build_args( line )
1090
+ ret = []
1091
+ word = ''
1092
+ escaped = false
1093
+ in_quote = false
1094
+
1095
+ line.strip!
1096
+
1097
+ line.each_byte do |c|
1098
+ c = c.chr
1099
+ if c == ' ' and !in_quote
1100
+ ret << word unless word.empty?
1101
+ word = ''
1102
+ elsif c == '"' and !escaped
1103
+ if in_quote
1104
+ in_quote = false
1105
+ else
1106
+ in_quote = true
1107
+ end
1108
+ ret << word unless word.empty?
1109
+ word = ''
1110
+ else
1111
+ escaped = (c == '\\')
1112
+ word += c
1113
+ end
1114
+ end
1115
+
1116
+ ret << word unless word.empty?
1117
+
1118
+ return ret
1119
+ end
1120
+
1121
+ def args_check( sock, cmd, argv, argc )
1122
+ if (argc.kind_of? Range and argc.include?(argv.length)) or
1123
+ (argv.length == argc)
1124
+ yield argv
1125
+ else
1126
+ sock.puts "ACK [2@0] {#{cmd}} wrong number of arguments for \"#{cmd}\""
1127
+ end
1128
+ end
1129
+
1130
+ def is_int( val )
1131
+ val =~ /^[-+]?[0-9]*$/
1132
+ end
1133
+
1134
+ def is_bool( val )
1135
+ val == '0' or val == '1'
1136
+ end
1137
+
1138
+ def locate_dir( path )
1139
+ dirs = path.split '/'
1140
+
1141
+ the_dir = @filetree
1142
+ dirs.each do |d|
1143
+ found = nil
1144
+ the_dir[:dirs].each do |sub|
1145
+ if sub[:name] == d
1146
+ found = sub
1147
+ break
1148
+ end
1149
+ end
1150
+ if found.nil?
1151
+ return nil
1152
+ else
1153
+ the_dir = found
1154
+ end
1155
+ end
1156
+
1157
+ return the_dir
1158
+ end
1159
+
1160
+ def send_song( sock, song )
1161
+ return if song.nil?
1162
+ sock.puts "file: #{song['file']}"
1163
+ song.each_pair do |key,val|
1164
+ sock.puts "#{key.capitalize}: #{val}" unless key == 'file' or key == '_mod_ver'
1165
+ end
1166
+ end
1167
+
1168
+ def send_dir( sock, dir, allinfo, path = '' )
1169
+ sock.puts "directory: #{path}#{dir[:name]}"
1170
+
1171
+ dir[:songs].each do |song|
1172
+ if allinfo
1173
+ send_song sock, song
1174
+ else
1175
+ sock.puts "file: #{song['file']}"
1176
+ end
1177
+ end
1178
+
1179
+ dir[:dirs].each do |d|
1180
+ send_dir(sock, d, allinfo, dir[:name] + '/')
1181
+ end
1182
+ end
1183
+
1184
+ def add_dir_to_pls( dir )
1185
+ dir[:songs].each do |song|
1186
+ song['_mod_ver'] = @status[:playlist]
1187
+ incr_version
1188
+ @the_playlist << song
1189
+ end
1190
+
1191
+ dir[:dirs].each do |d|
1192
+ add_dir_to_pls d
1193
+ end
1194
+ end
1195
+
1196
+ def sort_dir( dir )
1197
+ dir[:dirs].sort! do |x,y|
1198
+ x[:name] <=> y[:name]
1199
+ end
1200
+
1201
+ dir[:dirs].each do |d|
1202
+ sort_dir d
1203
+ end
1204
+ end
1205
+
1206
+ end