music_blender 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. data/.gitignore +5 -0
  2. data/Gemfile +2 -0
  3. data/Gemfile.lock +67 -0
  4. data/ID3TAGS.txt +26 -0
  5. data/LICENSE +20 -0
  6. data/README.md +41 -0
  7. data/Rakefile +27 -0
  8. data/bin/blend +5 -0
  9. data/db/migrate/001_create_root_folders.rb +10 -0
  10. data/db/migrate/002_create_tracks.rb +15 -0
  11. data/db/migrate/003_rename_root_folders_table_to_music_folders.rb +6 -0
  12. data/db/migrate/004_create_artists.rb +11 -0
  13. data/db/migrate/005_add_missing_column_to_tracks.rb +5 -0
  14. data/db/schema.rb +47 -0
  15. data/lib/music_blender.rb +26 -0
  16. data/lib/music_blender/artist.rb +6 -0
  17. data/lib/music_blender/bootstrap.rb +32 -0
  18. data/lib/music_blender/db_adapter.rb +58 -0
  19. data/lib/music_blender/id3_adapter.rb +70 -0
  20. data/lib/music_blender/music_folder.rb +51 -0
  21. data/lib/music_blender/player.rb +83 -0
  22. data/lib/music_blender/player_monitor.rb +77 -0
  23. data/lib/music_blender/shell.rb +59 -0
  24. data/lib/music_blender/track.rb +68 -0
  25. data/lib/music_blender/version.rb +3 -0
  26. data/music_blender.gemspec +28 -0
  27. data/test/factories/artists.rb +7 -0
  28. data/test/factories/music_folders.rb +15 -0
  29. data/test/factories/tracks.rb +12 -0
  30. data/test/music/point1sec.mp3 +0 -0
  31. data/test/music/subfolder/insubfolder.mp3 +0 -0
  32. data/test/music/test1.txt +0 -0
  33. data/test/music/test2.txt +0 -0
  34. data/test/test_helper.rb +53 -0
  35. data/test/unit/artist_test.rb +9 -0
  36. data/test/unit/bootstrap_test.rb +32 -0
  37. data/test/unit/db_adapter_test.rb +37 -0
  38. data/test/unit/id3_adapter_test.rb +42 -0
  39. data/test/unit/music_folder_test.rb +92 -0
  40. data/test/unit/player_monitor_test.rb +70 -0
  41. data/test/unit/player_test.rb +76 -0
  42. data/test/unit/shell_test.rb +71 -0
  43. data/test/unit/track_test.rb +48 -0
  44. metadata +250 -0
@@ -0,0 +1,9 @@
1
+ require 'test_helper'
2
+
3
+ module MusicBlender
4
+ class ArtistTest < MiniTest::Unit::TestCase
5
+ def test_instantiation
6
+ assert_kind_of(Artist,create(:artist))
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,32 @@
1
+ require 'test_helper'
2
+
3
+ module MusicBlender
4
+ class BootstrapTest < MiniTest::Unit::TestCase
5
+ attr_reader :bootstrap, :mock_shell
6
+
7
+ describe Bootstrap do
8
+ let(:bootstrap) { Bootstrap.new }
9
+ let(:mock_shell) { mock('shell')}
10
+ let(:mock_music_folder) { mock('music_folder') }
11
+
12
+ before do
13
+ Shell.stubs(:new).returns(mock_shell)
14
+ MusicFolder.stubs(:current).returns(mock_music_folder)
15
+ end
16
+
17
+ def test_call
18
+ DbAdapter.any_instance.expects(:spin_up)
19
+ mock_music_folder.expects(:load_tracks)
20
+ mock_music_folder.expects(:update_missing_flags)
21
+ mock_shell.expects(:run).throws(:exited)
22
+ bootstrap.call
23
+ end
24
+
25
+ def test_config
26
+ assert_equal(mock_music_folder,bootstrap.send(:music_folder))
27
+ end
28
+
29
+ end
30
+ end
31
+ end
32
+
@@ -0,0 +1,37 @@
1
+ require 'test_helper'
2
+
3
+ module MusicBlender
4
+ class DbAdapterTest < MiniTest::Unit::TestCase
5
+
6
+ describe DbAdapter do
7
+ let(:mock_connection) { mock('db_connection') }
8
+ let(:adapter) { DbAdapter.new }
9
+
10
+ before do
11
+ ActiveRecord::Base.stubs(:establish_connection).returns(mock_connection)
12
+ ActiveRecord::Base.stubs(:connection).returns(mock_connection)
13
+ ActiveRecord::Base.stubs('logger').returns(true)
14
+ end
15
+
16
+ def test_spinup_without_prior_db
17
+ File.expects(:exist?).returns(false)
18
+ adapter.expects(:load)
19
+ ActiveRecord::Migrator.expects(:migrate).never
20
+
21
+ adapter.spin_up
22
+ end
23
+
24
+ def test_spinup_with_prior_db
25
+ File.expects(:exist?).returns(true)
26
+ ActiveRecord::Migrator.expects(:migrate)
27
+ File.expects(:open).with(adapter.send(:path_to_schema),'w:utf-8').yields(mock('schema_file'))
28
+ ActiveRecord::SchemaDumper.expects(:dump)
29
+ adapter.expects(:load).never
30
+
31
+ adapter.spin_up
32
+ end
33
+ end
34
+
35
+ end
36
+ end
37
+
@@ -0,0 +1,42 @@
1
+ require 'test_helper'
2
+
3
+ module MusicBlender
4
+ class Id3AdapterTest < MiniTest::Unit::TestCase
5
+
6
+ describe Id3Adapter do
7
+ let(:path) { "#{MUSIC_PATH}/point1sec.mp3" }
8
+ let(:adapter) { Id3Adapter.new(path,nil) }
9
+
10
+ describe 'loading data' do
11
+
12
+ def test_loads_artist
13
+ assert_equal('DefBeats', adapter.artist)
14
+ end
15
+
16
+ def test_loads_title
17
+ assert_equal('Silent MP3 10th-of-a-sec', adapter.title)
18
+ end
19
+
20
+ def test_loads_rating
21
+ assert_equal(1, adapter.rating)
22
+ end
23
+ end
24
+
25
+ describe 'setting data' do
26
+
27
+ def test_sets_rating
28
+ assert_difference('adapter.rating' => 41) do
29
+ adapter.set_rating(42)
30
+ end
31
+ ensure
32
+ adapter.send(:v2tag).remove_frame(adapter.send(:rating_frame))
33
+ adapter.send(:tag_file).save
34
+ end
35
+
36
+ end
37
+
38
+ end
39
+
40
+ end
41
+ end
42
+
@@ -0,0 +1,92 @@
1
+ require 'test_helper'
2
+ require 'ostruct'
3
+
4
+ module MusicBlender
5
+ class MusicFolderTest < MiniTest::Unit::TestCase
6
+
7
+ describe MusicFolder do
8
+ let(:music_folder) { create(:music_folder_with_tracks) }
9
+ let(:mock_id3_tag) { mock('tag') }
10
+
11
+ before do
12
+ Track.any_instance.stubs(:rating_frame => OpenStruct.new(:text => '5'))
13
+ mock_id3_tag.stubs(:title => 'Foo', :artist => 'Bar')
14
+ Track.any_instance.stubs(:import_id3_tag_attributes)
15
+ end
16
+
17
+ def test_pick_a_track
18
+ assert_includes(music_folder.tracks,music_folder.pick_a_track)
19
+ end
20
+
21
+ describe 'loading new tracks' do
22
+
23
+ before do
24
+ Track.any_instance.unstub(:import_id3_tag_attributes)
25
+ create(:track, :music_folder => music_folder, :relative_path => 'a')
26
+ music_folder.stubs(:relative_paths => ['a','b','c'])
27
+ end
28
+
29
+ def test_load_new_tracks
30
+ assert_difference('Track.count' => 2) do
31
+ music_folder.load_tracks
32
+ end
33
+ end
34
+
35
+ end
36
+
37
+ describe 'update missing flags' do
38
+ let(:track) { create(:track) }
39
+
40
+ def test_updates_missing_flags
41
+ refute(track.missing?)
42
+ track.music_folder.update_missing_flags
43
+ assert(track.reload.missing?)
44
+ end
45
+ end
46
+
47
+ describe 'music_path' do
48
+
49
+ def test_uses_constant
50
+ assert_equal(MUSIC_PATH, MusicFolder.music_path)
51
+ end
52
+
53
+ end
54
+
55
+ describe 'current' do
56
+
57
+ before do
58
+ MusicFolder.instance_variable_set(:@current, nil)
59
+ end
60
+
61
+ describe 'when one does not already exist' do
62
+
63
+ before do
64
+ MusicFolder.stubs(:music_path).returns('/some/non/existent/path/dsedfget')
65
+ end
66
+
67
+ def test_create_music_folder
68
+ assert_difference('MusicFolder.count') do
69
+ MusicFolder.current
70
+ end
71
+ end
72
+
73
+ end
74
+
75
+ describe 'when one does already exist' do
76
+ let(:music_folder) { create(:music_folder) }
77
+
78
+ before do
79
+ MusicFolder.stubs(:music_path).returns(music_folder.path)
80
+ end
81
+
82
+ def test_fetch_current_music_folder
83
+ assert_difference('MusicFolder.count' => 0) do
84
+ MusicFolder.current
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+
@@ -0,0 +1,70 @@
1
+ require 'test_helper'
2
+ require 'tempfile'
3
+
4
+ module MusicBlender
5
+ class PlayerMonitorTest < MiniTest::Unit::TestCase
6
+
7
+ describe PlayerMonitor do
8
+ attr_reader :mock_stderr_writable, :mock_stdout_writable, :thread
9
+
10
+ let (:mock_player) { mock('player') }
11
+ let (:monitor) { PlayerMonitor.new(mock_player) }
12
+
13
+ before do
14
+ mock_player.stubs(:logger => Logger.new('/dev/null'))
15
+ mock_stdout_readable, @mock_stdout_writable = IO.pipe
16
+ mock_stderr_readable, @mock_stderr_writable = IO.pipe
17
+ mock_player.stubs(:stdout).returns(mock_stdout_readable)
18
+ mock_player.stubs(:stderr).returns(mock_stderr_readable)
19
+ @thread = Thread.new { monitor.run }
20
+ end
21
+
22
+ after do
23
+ thread.exit
24
+ end
25
+
26
+ def test_should_process_i_messages
27
+ assert_equal(nil,monitor.song_name)
28
+ write_to_player('@I Foobar')
29
+ assert_equal('Foobar',monitor.song_name)
30
+ end
31
+
32
+ def test_should_process_f_messages
33
+ write_to_player('@F 1 2 3 4')
34
+ assert_equal(1,monitor.frames)
35
+ assert_equal(2,monitor.frames_remaining)
36
+ assert_equal(3,monitor.seconds)
37
+ assert_equal(4,monitor.seconds_remaining)
38
+ end
39
+
40
+ def test_should_process_p_stop_messages
41
+ write_to_player('@P 0')
42
+ assert_equal(0,monitor.stop_pause_status)
43
+ assert_equal(false,monitor.playing)
44
+ end
45
+
46
+ def test_should_process_p_paused_messages
47
+ write_to_player('@P 1')
48
+ assert_equal(1,monitor.stop_pause_status)
49
+ assert_equal(false,monitor.playing)
50
+ end
51
+
52
+ def test_should_process_p_resumed_messages
53
+ write_to_player('@P 2')
54
+ assert_equal(2,monitor.stop_pause_status)
55
+ assert_equal(true,monitor.playing)
56
+ end
57
+
58
+ #######
59
+ private
60
+ #######
61
+
62
+ def write_to_player(string)
63
+ mock_stdout_writable.write(string)
64
+ mock_stdout_writable.close
65
+ sleep 0.002
66
+ end
67
+
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,76 @@
1
+ require 'test_helper'
2
+
3
+ module MusicBlender
4
+ class PlayerTest < MiniTest::Unit::TestCase
5
+
6
+ describe Player do
7
+ let(:player) { Player.new }
8
+ let(:mock_stdin) { mock('stdin') }
9
+ let(:mock_stdout) { mock('stdout') }
10
+ let(:mock_stderr) { mock('stderr') }
11
+ let(:mock_wait_thread) { mock('wait_thread') }
12
+ let(:mock_monitor_thread) { mock('monitor_thread') }
13
+
14
+ before do
15
+ Open3.expects(:popen3).returns(mock_stdin,mock_stdout,mock_stderr,mock_wait_thread)
16
+ Thread.expects(:new).returns(mock_monitor_thread)
17
+ player.stubs(:config)
18
+ end
19
+
20
+ describe 'play' do
21
+ let(:track1) { create(:track) }
22
+ let(:track2) { create(:track) }
23
+ let(:music_folder) { create(:music_folder) }
24
+
25
+ before do
26
+ player.stubs(:music_folder).returns(music_folder)
27
+ music_folder.stubs(:pick_a_track).returns(track1)
28
+ mock_stdin.stubs(:puts)
29
+ end
30
+
31
+ def test_loads_a_song
32
+ mock_stdin.expects(:puts).with(regexp_matches(/^LOAD #{track1.full_path}/))
33
+ player.play
34
+ end
35
+
36
+ def test_sets_last_played_at
37
+ player.stubs(:current_track => track1)
38
+ assert_operator(track1.last_played_at,:<,1.minute.ago)
39
+ player.play
40
+ assert_operator(track1.last_played_at,:>,1.minute.ago)
41
+ end
42
+
43
+ end
44
+
45
+ def test_should_pause
46
+ mock_stdin.expects(:puts).with('PAUSE')
47
+ player.pause
48
+ end
49
+
50
+ def test_should_stop
51
+ mock_stdin.expects(:puts).with('STOP')
52
+ player.stop
53
+ end
54
+
55
+ def test_should_quit
56
+ mock_stdin.expects(:puts).with('QUIT')
57
+ player.quit
58
+ end
59
+
60
+ describe 'delegated methods' do
61
+ let(:mock_monitor) { mock('monitor') }
62
+
63
+ before do
64
+ PlayerMonitor.stubs(:new => mock_monitor)
65
+ end
66
+
67
+ def test_should_get_status_string
68
+ mock_monitor.expects(:stop_pause_status => 1)
69
+ assert_equal('Paused', player.status_string)
70
+ end
71
+
72
+ end
73
+
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,71 @@
1
+ require 'test_helper'
2
+
3
+ module MusicBlender
4
+ class ShellTest < MiniTest::Unit::TestCase
5
+ attr_reader :mock_player, :mock_scanner
6
+
7
+ describe Shell do
8
+ let(:mock_player) { mock('player') }
9
+ let(:shell) { Shell.new }
10
+
11
+ before do
12
+ mock_player.stubs(:stop)
13
+ mock_player.stubs(:quit)
14
+ Shell.stubs(:player).returns(mock_player)
15
+ end
16
+
17
+ def test_gracefully_fail_to_execute_command
18
+ assert_equal('',$stdout.string)
19
+ run_shell(:ping)
20
+ assert_match(/Unrecognized Command: ping/,$stdout.string)
21
+ end
22
+
23
+ describe 'info' do
24
+ before do
25
+ Shell.expects(:print_info)
26
+ end
27
+
28
+ def test_execute_info
29
+ assert_equal('',$stdout.string)
30
+ run_shell(:info)
31
+ refute_equal('',$stdout.string)
32
+ end
33
+ end
34
+
35
+ describe 'player commands' do
36
+ def test_execute_player_commands
37
+ [:play,:stop,:quit].each do |command|
38
+ mock_player.expects(command)
39
+ run_shell(command)
40
+ end
41
+ end
42
+ end
43
+
44
+ describe 'commands with arguments' do
45
+ let(:track) { create(:track) }
46
+
47
+ before do
48
+ mock_player.stubs(:current_track => track)
49
+ end
50
+
51
+ def test_executes_command_with_arguments
52
+ assert_equal('',$stdout.string)
53
+ assert_difference('track.rating' => -31) do
54
+ run_shell('rate 11')
55
+ end
56
+ assert_match(/Rating Updated To: 11/,$stdout.string)
57
+ end
58
+ end
59
+
60
+ #######
61
+ private
62
+ #######
63
+
64
+ def run_shell(command)
65
+ shell.stubs(:gets).returns(command.to_s).then.returns('exit')
66
+ assert_throws(:exited) { shell.run }
67
+ end
68
+
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,48 @@
1
+ require 'test_helper'
2
+
3
+ module MusicBlender
4
+ class TrackTest < MiniTest::Unit::TestCase
5
+
6
+ describe Track do
7
+ let(:track) { create(:track) }
8
+ let(:mock_id3_tag) { mock('tag') }
9
+
10
+ def setup
11
+ Track.any_instance.stubs(:rating_frame => OpenStruct.new(:text => '5'))
12
+ mock_id3_tag.stubs(:title => 'Foo', :artist => 'Bar')
13
+ Track.any_instance.stubs(:id3v2_tag => mock_id3_tag)
14
+ end
15
+
16
+ def test_full_path
17
+ assert_kind_of(String,track.full_path)
18
+ end
19
+
20
+
21
+ describe 'except_recently_played scope' do
22
+ attr_reader :track1, :track2, :track3
23
+
24
+ before do
25
+ @track1 = create(:track, :last_played_at => 3.days.ago)
26
+ @track2 = create(:track, :last_played_at => 2.days.ago)
27
+ @track3 = create(:track, :last_played_at => 1.days.ago)
28
+ end
29
+
30
+ def test_does_not_include_recently_played
31
+ assert_includes(Track.except_recently_played,track1)
32
+ track1.update_column(:last_played_at, Time.now)
33
+ refute_includes(Track.except_recently_played,track1)
34
+ end
35
+
36
+ describe 'most_recent scope' do
37
+
38
+ def test_gets_most_recent_2_tracks
39
+ refute_includes(Track.most_recent(2),track1)
40
+ track1.update_column(:last_played_at, Time.now)
41
+ assert_includes(Track.most_recent(2),track1)
42
+ end
43
+
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end