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.
- 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
|