jimmy_jukebox 0.2.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/LICENSE.txt +340 -0
  2. data/bin/jload_jukebox +29 -0
  3. data/bin/jplay_jukebox +49 -0
  4. data/bin/load_jukebox +29 -0
  5. data/bin/play_jukebox +49 -0
  6. data/lib/jimmy_jukebox/artists.rb +45 -0
  7. data/lib/jimmy_jukebox/song_loader.rb +119 -0
  8. data/lib/jimmy_jukebox/songs/ArtTatum.yml +169 -0
  9. data/lib/jimmy_jukebox/songs/ArtieShaw.yml +581 -0
  10. data/lib/jimmy_jukebox/songs/BennieMoten.yml +108 -0
  11. data/lib/jimmy_jukebox/songs/BennyGoodman.yml +402 -0
  12. data/lib/jimmy_jukebox/songs/BillieHoliday.yml +64 -0
  13. data/lib/jimmy_jukebox/songs/BixBeiderbecke.yml +96 -0
  14. data/lib/jimmy_jukebox/songs/CharlieChristian.yml +9 -0
  15. data/lib/jimmy_jukebox/songs/CharlieParker.yml +33 -0
  16. data/lib/jimmy_jukebox/songs/ColemanHawkins.yml +17 -0
  17. data/lib/jimmy_jukebox/songs/CountBasie.yml +45 -0
  18. data/lib/jimmy_jukebox/songs/DizzyGillespie.yml +4 -0
  19. data/lib/jimmy_jukebox/songs/DjangoReinhardt.yml +76 -0
  20. data/lib/jimmy_jukebox/songs/DukeEllington.yml +159 -0
  21. data/lib/jimmy_jukebox/songs/EarlHines.yml +99 -0
  22. data/lib/jimmy_jukebox/songs/FletcherHenderson.yml +159 -0
  23. data/lib/jimmy_jukebox/songs/JamesPJohnson.yml +9 -0
  24. data/lib/jimmy_jukebox/songs/JellyRollMorton.yml +90 -0
  25. data/lib/jimmy_jukebox/songs/KingOliver.yml +61 -0
  26. data/lib/jimmy_jukebox/songs/LionelHampton.yml +149 -0
  27. data/lib/jimmy_jukebox/songs/LouisArmstrong.yml +151 -0
  28. data/lib/jimmy_jukebox/songs/MilesDavis.yml +9 -0
  29. data/lib/jimmy_jukebox/songs/OriginalDixielandJazzBand.yml +46 -0
  30. data/lib/jimmy_jukebox/songs/RedNorvo.yml +40 -0
  31. data/lib/jimmy_jukebox/songs/SidneyBechet.yml +26 -0
  32. data/lib/jimmy_jukebox/user_config.rb +202 -0
  33. data/lib/jimmy_jukebox/version.rb +3 -0
  34. data/lib/jimmy_jukebox.rb +120 -0
  35. data/roadmap.txt +20 -0
  36. data/spec/jimmy_jukebox_spec.rb +161 -0
  37. data/spec/song_loader_spec.rb +190 -0
  38. data/spec/spec_helper.rb +5 -0
  39. data/spec/user_config_spec.rb +148 -0
  40. metadata +192 -0
@@ -0,0 +1,3 @@
1
+ module JimmyJukebox
2
+ VERSION = '0.2.6'
3
+ end
@@ -0,0 +1,120 @@
1
+ module JimmyJukebox
2
+
3
+ # make system call and get pid so you can terminate process
4
+ def system_yield_pid(*cmd)
5
+ # would like to use Process.respond_to?(:fork) but JRuby mistakenly returns true
6
+ begin
7
+ pid = fork do # creates and runs block in subprocess (which will terminate with status 0), capture subprocess pid
8
+ exec(*cmd) # replaces current process with system call
9
+ exit! 127 # exit process and return exit status 127; should never be reached
10
+ end
11
+ rescue NotImplementedError
12
+ require 'rubygems'
13
+ require 'spoon'
14
+ pid = Spoon.spawnp(*cmd)
15
+ #raise "*** fork() not supported ***" unless Process.respond_to?(:fork)
16
+ end
17
+ yield pid if block_given? # call block, passing in the subprocess pid
18
+ Process.waitpid(pid) # Waits for a child process to exit, returns its process id, and sets $? to a Process::Status object
19
+ $? # return Process::Status object with instance methods .stopped?, .exited?, .exitstatus; see: http://www.ruby-doc.org/core/classes/Process/Status.html
20
+ end
21
+
22
+ class Jukebox
23
+
24
+ require 'jimmy_jukebox/user_config'
25
+
26
+ attr_reader :loop, :current_song_paused, :playing_pid
27
+
28
+ def initialize
29
+ @user_config = UserConfig.new
30
+ end
31
+
32
+ def play_loop
33
+ @loop = true
34
+ while @loop do
35
+ play
36
+ end
37
+ end
38
+
39
+ def play
40
+ begin
41
+ play_random_song(@user_config.songs)
42
+ rescue SystemExit, Interrupt => e
43
+ terminate_current_song
44
+ puts "\nMusic terminated by user"
45
+ exit
46
+ end
47
+ end
48
+
49
+ def quit
50
+ stop_looping
51
+ terminate_current_song
52
+ end
53
+
54
+ def skip_song
55
+ terminate_current_song
56
+ end
57
+
58
+ def pause_current_song
59
+ @current_song_paused = true
60
+ # jruby doesn't seem to handle system() correctly
61
+ # trying backticks
62
+ # system("kill -s STOP #{@playing_pid}") if @playing_pid
63
+ `kill -s STOP #{@playing_pid}` if @playing_pid
64
+ end
65
+
66
+ def unpause_current_song
67
+ @current_song_paused = false
68
+ # jruby doesn't seem to handle system() correctly
69
+ # trying backticks
70
+ #system("kill -s CONT #{@playing_pid}") if @playing_pid
71
+ `kill -s CONT #{@playing_pid}` if @playing_pid
72
+ end
73
+
74
+ private
75
+
76
+ def stop_looping
77
+ @loop = false
78
+ end
79
+
80
+ def play_random_song(songs)
81
+ terminate_current_song
82
+ raise "JimmyJukebox has no songs to play!" if songs.length == 0
83
+ music_file = songs[rand(songs.length)]
84
+ play_file(music_file)
85
+ end
86
+
87
+ def terminate_current_song
88
+ if @playing_pid
89
+ @current_song_paused = false
90
+ # killing processes seems problematic in JRuby
91
+ # I've tried several approaches, and nothing seems reliable
92
+ Process.kill("SIGKILL",@playing_pid)
93
+ #Process.kill("SIGTERM",@playing_pid)
94
+ #`kill #{@playing_pid}` if @playing_pid
95
+ @playing_pid = nil
96
+ end
97
+ end
98
+
99
+ def play_file(music_file)
100
+ # TODO: refactor the duplicate code below into a method
101
+ if music_file =~ /\.mp3$/i && @user_config.mp3_player
102
+ process_status = play_file_with(music_file, @user_config.mp3_player)
103
+ elsif music_file =~ /\.ogg$/i && @user_config.ogg_player
104
+ process_status = play_file_with(music_file, @user_config.ogg_player)
105
+ else
106
+ raise "Attempted to play a file format this program cannot play"
107
+ end
108
+ process_status.exitstatus.to_i == 0 ? (@playing_pid = nil) : (raise "Experienced a problem playing a song")
109
+ end
110
+
111
+ def play_file_with(music_file,player)
112
+ puts "Press Ctrl-C to stop the music and exit this program"
113
+ system_yield_pid(player, File.expand_path(music_file)) do |pid|
114
+ @playing_pid = pid
115
+ end
116
+ end
117
+
118
+ end
119
+
120
+ end
data/roadmap.txt ADDED
@@ -0,0 +1,20 @@
1
+ TODO:
2
+ figure out why Macs running Ruby 1.8 won't skip song
3
+ add file downloading error handling
4
+ make playable on Windows
5
+ call strip on music directory names
6
+ chmod ug+rx downloaded music files?
7
+
8
+ Buddy Bolden
9
+ Bunk Johnson
10
+ Cab Calloway
11
+ Glenn Miller
12
+ Fats Waller
13
+ Ben Webster
14
+ Paul Whiteman
15
+ http://www.archive.org/search.php?query=paul%20whiteman%20AND%20mediatype%3Aaudio
16
+ Thelonious Monk
17
+ http://www.archive.org/details/1920s-redNicholsHisFivePennies-01-10
18
+ http://www.archive.org/details/1920sJazz-01-90
19
+ http://www.archive.org/details/LilArmstrong-01-10
20
+
@@ -0,0 +1,161 @@
1
+ require 'spec_helper'
2
+ require 'fakefs/safe'
3
+ require 'jimmy_jukebox'
4
+ include JimmyJukebox
5
+
6
+ # Override exec() to prevent songs from actually playing
7
+ # Instead, start a brief sleep process
8
+ module Kernel
9
+ alias :real_exec :exec
10
+
11
+ def exec(*cmd)
12
+ real_exec("sleep 0.2")
13
+ end
14
+ end
15
+
16
+ describe Jukebox do
17
+
18
+ before(:all) do
19
+ #ARGV.clear
20
+ ARGV.pop
21
+ end
22
+
23
+ context "with no command line parameter" do
24
+
25
+ it "can be instantiated" do
26
+ jj = Jukebox.new
27
+ jj.should_not be_nil
28
+ jj.quit
29
+ end
30
+
31
+ #it "raises exception when no songs available"
32
+ # lambda do
33
+ # jj = Jukebox.new
34
+ # end.should raise_error
35
+ #end
36
+
37
+ it "generates a non-empty song list" do
38
+ jj = Jukebox.new
39
+ jj.instance_variable_get(:@user_config).songs.should_not be_nil
40
+ jj.instance_variable_get(:@user_config).songs.should_not be_empty
41
+ jj.instance_variable_get(:@user_config).songs.length.should be > 0
42
+ end
43
+
44
+ it "generates a non-empty song list with only mp3 & ogg files" do
45
+ jj = Jukebox.new
46
+ jj.instance_variable_get(:@user_config).songs.each do |song|
47
+ song.should match(/.*\.mp3|.*\.ogg/i)
48
+ end
49
+ end
50
+
51
+ it "can play" do
52
+ jj = Jukebox.new
53
+ thread = Thread.new do
54
+ jj.play
55
+ end
56
+ sleep 0.2
57
+ jj.instance_variable_get(:@playing_pid).should_not be_nil
58
+ jj.should_receive(:terminate_current_song)
59
+ jj.quit
60
+ end
61
+
62
+ it "can play_loop" do
63
+ jj = Jukebox.new
64
+ thread = Thread.new do
65
+ jj.play_loop
66
+ end
67
+ sleep 0.1
68
+ song1 = jj.playing_pid
69
+ song1.should_not be_nil
70
+ jj.loop.should be_true
71
+ sleep 0.2
72
+ song2 = jj.playing_pid
73
+ song2.should_not be_nil
74
+ song2.should_not == song1
75
+ jj.quit
76
+ end
77
+
78
+ it "can skip a song" do
79
+ jj = Jukebox.new
80
+ thread = Thread.new do
81
+ jj.play_loop
82
+ end
83
+ sleep 0.2
84
+ song_1 = jj.playing_pid
85
+ jj.skip_song
86
+ sleep 0.2
87
+ song_2 = jj.playing_pid
88
+ jj.skip_song
89
+ sleep 0.2
90
+ song_3 = jj.playing_pid
91
+ song_1.should_not == song_2 || song_2.should_not == song_3
92
+ jj.quit
93
+ end
94
+
95
+ it "can pause the current song" do
96
+ jj = Jukebox.new
97
+ thread = Thread.new do
98
+ jj.play
99
+ end
100
+ sleep 0.1
101
+ song_1 = jj.playing_pid
102
+ jj.pause_current_song
103
+ song_2 = jj.playing_pid
104
+ song_1.should == song_2
105
+ jj.current_song_paused.should be_true
106
+ jj.quit
107
+ end
108
+
109
+ it "can unpause a paused song" do
110
+ jj = Jukebox.new
111
+ thread = Thread.new do
112
+ jj.play
113
+ end
114
+ sleep 0.05
115
+ song_1 = jj.playing_pid
116
+ jj.current_song_paused.should be_false
117
+ jj.pause_current_song
118
+ song_2 = jj.playing_pid
119
+ jj.current_song_paused.should be_true
120
+ song_2.should == song_1
121
+ jj.unpause_current_song
122
+ jj.current_song_paused.should be_false
123
+ song_3 = jj.playing_pid
124
+ jj.current_song_paused.should be_false
125
+ jj.pause_current_song
126
+ song_4 = jj.playing_pid
127
+ jj.current_song_paused.should be_true
128
+ song_4.should == song_3
129
+ jj.unpause_current_song
130
+ jj.current_song_paused.should be_false
131
+ jj.quit
132
+ end
133
+ end
134
+
135
+ context "with valid music directory as command line parameter" do
136
+
137
+ before(:each) do
138
+ ARGV.delete_if { |val| true }
139
+ ARGV << File.expand_path("~/Music")
140
+ end
141
+
142
+ it "can skip a song" do
143
+ jj = Jukebox.new
144
+ thread = Thread.new do
145
+ jj.play_loop
146
+ end
147
+ sleep 0.2
148
+ song_1 = jj.playing_pid
149
+ jj.skip_song
150
+ sleep 0.2
151
+ song_2 = jj.playing_pid
152
+ jj.skip_song
153
+ sleep 0.2
154
+ song_3 = jj.playing_pid
155
+ song_1.should_not == song_2 || song_2.should_not == song_3
156
+ jj.quit
157
+ end
158
+
159
+ end
160
+
161
+ end
@@ -0,0 +1,190 @@
1
+ require 'spec_helper'
2
+ require 'rubygems'
3
+ require 'rspec/mocks'
4
+ require 'fakeweb' # apparently must be required before fakefs
5
+ FakeWeb.allow_net_connect = false
6
+ require 'fakefs/safe'
7
+ require 'jimmy_jukebox/song_loader'
8
+
9
+ describe JimmyJukebox::SongLoader do
10
+
11
+ #before(:all) do
12
+ # If using fakefs without safe, make sure we're using FakeFS gem,
13
+ # not the real file system!
14
+ # File.directory?("/home").should be_false
15
+ #end
16
+
17
+ before(:all) do
18
+ #ARGV.delete_if { |val| true }
19
+ #ARGV.clear
20
+ ARGV.pop
21
+ end
22
+
23
+ before(:each) do
24
+ @sl = JimmyJukebox::SongLoader
25
+ end
26
+
27
+ describe "#create_save_dir" do
28
+
29
+ it "should create a directory" do
30
+ FakeFS do
31
+ topdir = File.join("/home","user_name4","Music")
32
+ subdir = File.join(topdir, "rock", "Beatles")
33
+ File.directory?(subdir).should be_false
34
+ @sl.create_save_dir(subdir)
35
+ File.directory?(subdir).should be_true
36
+ end
37
+ end
38
+
39
+ end
40
+
41
+ describe "#version_of_song_in_any_dir?" do
42
+
43
+ it "should return true if song in top of directory tree" do
44
+ FakeFS do
45
+ topdir = "/home/user_name1/Music"
46
+ songname = "Paperback_Writer.mp3"
47
+ File.directory?(topdir).should be_false
48
+ FileUtils.mkdir_p(topdir)
49
+ File.directory?(topdir).should be_true
50
+ Dir.chdir(topdir)
51
+ File.exists?(songname).should be_false
52
+ FileUtils.touch(songname)
53
+ File.exists?(songname).should be_true
54
+ @sl.version_of_song_in_any_dir?(songname,topdir).should be_true
55
+ end
56
+ end
57
+
58
+ it "should return true if song in subdirectory" do
59
+ FakeFS do
60
+ topdir = "/home/user_name2/Music"
61
+ subdir = File.join(topdir, "rock", "Beatles")
62
+ songname = "Paperback_Writer.mp3"
63
+ File.directory?(subdir).should be_false
64
+ FileUtils.mkdir_p(subdir)
65
+ FileUtils.touch(File.join(subdir, songname))
66
+ File.exists?(File.join(subdir, songname)).should be_true
67
+ @sl.version_of_song_in_any_dir?(songname,subdir).should be_true
68
+ end
69
+ end
70
+
71
+ it "should return false if song not in directory tree" do
72
+ FakeFS do
73
+ topdir = "/home/user_name3/Music"
74
+ subdir = File.join(topdir, "rock", "Beatles")
75
+ songname = "Paperback_Writer.mp3"
76
+ FileUtils.mkdir_p(subdir)
77
+ File.exists?(File.join(subdir, songname)).should be_false
78
+ @sl.version_of_song_in_any_dir?(songname,subdir).should be_false
79
+ end
80
+ end
81
+
82
+ end
83
+
84
+ describe "test defaults" do
85
+
86
+ it "should have a user_config" do
87
+ @sl.instance_variable_get(:@user_config).should_not be_nil
88
+ end
89
+
90
+ it "should have a user_config with a non-nil default_music_dir" do
91
+ @sl.instance_variable_get(:@user_config).default_music_dir.should == File.expand_path("~/Music")
92
+ end
93
+
94
+ it "should have a SUPPORTED_MUSIC_TYPES of '/\.mp3$|\.ogg$/i'" do
95
+ @sl::SUPPORTED_MUSIC_TYPES.should == /\.mp3$|\.ogg$/i
96
+ end
97
+
98
+ end
99
+
100
+ describe "#original_dixieland_jazz_band without dirname" do
101
+
102
+ context "no songs in directory" do
103
+
104
+ before(:each) do
105
+ @dirname = File.expand_path(@sl.instance_variable_get(:@user_config).default_music_dir + '/JAZZ/Original_Dixieland_Jazz_Band')
106
+ @sl.stub!(:version_of_song_in_any_dir?).and_return(false)
107
+ end
108
+
109
+ it "should try to download many songs" do
110
+ @sl.should_receive(:open).at_least(25).times
111
+ @sl.send(:original_dixieland_jazz_band)
112
+ end
113
+
114
+ end
115
+
116
+ end
117
+
118
+ describe "#charlie_christian without dirname" do
119
+
120
+ context "no songs yet downloaded" do
121
+
122
+ it "should try to download many songs" do
123
+ dirname = File.expand_path(@sl.instance_variable_get(:@user_config).default_music_dir + '/JAZZ/Charlie_Christian')
124
+ @sl.stub!(:version_of_song_in_any_dir?).and_return(false)
125
+ @sl.should_receive(:open).exactly(8).times
126
+ @sl.charlie_christian
127
+ File.exists?(dirname).should be_true
128
+ end
129
+
130
+ end
131
+
132
+ context "all songs already downloaded" do
133
+
134
+ it "should not download any songs" do
135
+ dirname = File.expand_path(@sl.instance_variable_get(:@user_config).default_music_dir + '/JAZZ/Charlie_Christian')
136
+ @sl.stub!(:version_of_song_in_any_dir?).and_return(true)
137
+ @sl.should_not_receive(:open)
138
+ @sl.charlie_christian
139
+ end
140
+
141
+ end
142
+
143
+ end
144
+
145
+ describe "#lionel_hampton without dirname" do
146
+
147
+ it "should try to download many songs" do
148
+ dirname = File.expand_path(@sl.instance_variable_get(:@user_config).default_music_dir + '/JAZZ/Lionel_Hampton')
149
+ @sl.stub!(:version_of_song_in_any_dir?).and_return(false)
150
+ @sl.should_receive(:open).at_least(50).times
151
+ @sl.lionel_hampton
152
+ File.exists?(dirname).should be_true
153
+ end
154
+
155
+ end
156
+
157
+ describe "#dizzy_gillespie with dirname" do
158
+
159
+ it "should try to download three songs" do
160
+ dirname = File.expand_path(@sl.instance_variable_get(:@user_config).default_music_dir + '/JAZZ/Dizzy_Gillespie')
161
+ @sl.stub!(:version_of_song_in_any_dir?).and_return(false)
162
+ @sl.should_receive(:open).exactly(3).times
163
+ @sl.dizzy_gillespie(dirname)
164
+ end
165
+
166
+ it "should successfully download three songs" do
167
+ pending("use FakeWeb")
168
+ FakeWeb.register_uri(:any, "http://www.archive.org/download/DizzyGillespie-GroovinHigh/02.GroovinHigh.mp3", :response => "/home/james/Music/JAZZ/Dizzy_Gillespie/Groovin' High 1945.mp3")
169
+ FakeWeb.register_uri(:any, "http://www.archive.org/download/DizzyGillespie-Manteca/01Manteca.ogg", :response => "/home/james/Music/JAZZ/Dizzy_Gillespie/01Manteca.ogg")
170
+ FakeWeb.register_uri(:any, "http://www.archive.org/download/DizzyGillespieLouisArmstrong-UmbrellaMan/DizzyGillespieLouisArmstrong-UmbrellaMan.mp3", :response => "/home/james/Music/JAZZ/Dizzy_Gillespie/DizzyGillespieLouisArmstrong-UmbrellaMan.mp3")
171
+ dirname = File.expand_path("/home/user_name6/non-existent-dir")
172
+ File.exists?(dirname).should be_false
173
+ @sl.dizzy_gillespie(dirname)
174
+ #File.exists?(dirname + "/02.GroovinHigh.mp3").should be_true
175
+ #File.exists?(dirname + "/01Manteca.ogg").should be_true
176
+ #File.exists?(dirname + "/DizzyGillespieLouisArmstrong-UmbrellaMan.mp3").should be_true
177
+ FakeWeb.clean_registry
178
+ end
179
+
180
+ end
181
+
182
+ describe "call to non-existent method" do
183
+
184
+ it "should raise NoMethodError" do
185
+ lambda { @sl.non_existent_method }.should raise_error(NoMethodError)
186
+ end
187
+
188
+ end
189
+
190
+ end