music_blender 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +67 -0
- data/ID3TAGS.txt +26 -0
- data/LICENSE +20 -0
- data/README.md +41 -0
- data/Rakefile +27 -0
- data/bin/blend +5 -0
- data/db/migrate/001_create_root_folders.rb +10 -0
- data/db/migrate/002_create_tracks.rb +15 -0
- data/db/migrate/003_rename_root_folders_table_to_music_folders.rb +6 -0
- data/db/migrate/004_create_artists.rb +11 -0
- data/db/migrate/005_add_missing_column_to_tracks.rb +5 -0
- data/db/schema.rb +47 -0
- data/lib/music_blender.rb +26 -0
- data/lib/music_blender/artist.rb +6 -0
- data/lib/music_blender/bootstrap.rb +32 -0
- data/lib/music_blender/db_adapter.rb +58 -0
- data/lib/music_blender/id3_adapter.rb +70 -0
- data/lib/music_blender/music_folder.rb +51 -0
- data/lib/music_blender/player.rb +83 -0
- data/lib/music_blender/player_monitor.rb +77 -0
- data/lib/music_blender/shell.rb +59 -0
- data/lib/music_blender/track.rb +68 -0
- data/lib/music_blender/version.rb +3 -0
- data/music_blender.gemspec +28 -0
- data/test/factories/artists.rb +7 -0
- data/test/factories/music_folders.rb +15 -0
- data/test/factories/tracks.rb +12 -0
- data/test/music/point1sec.mp3 +0 -0
- data/test/music/subfolder/insubfolder.mp3 +0 -0
- data/test/music/test1.txt +0 -0
- data/test/music/test2.txt +0 -0
- data/test/test_helper.rb +53 -0
- data/test/unit/artist_test.rb +9 -0
- data/test/unit/bootstrap_test.rb +32 -0
- data/test/unit/db_adapter_test.rb +37 -0
- data/test/unit/id3_adapter_test.rb +42 -0
- data/test/unit/music_folder_test.rb +92 -0
- data/test/unit/player_monitor_test.rb +70 -0
- data/test/unit/player_test.rb +76 -0
- data/test/unit/shell_test.rb +71 -0
- data/test/unit/track_test.rb +48 -0
- metadata +250 -0
@@ -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
|