Implement track/stream
* Add Streamer class * Use FactoryGirl for factories * Add sha256 field to tracks * Add mime_type field to tracks
This commit is contained in:
parent
47fa969617
commit
2f6a447416
@ -9,10 +9,10 @@ class TracksController < ApplicationController
|
||||
|
||||
def create
|
||||
@track = Track.new(:name => params[:track][:name])
|
||||
if params[:track][:file]
|
||||
@track.uploaded_file = params[:track][:file]
|
||||
end
|
||||
if @track.save
|
||||
if @track.save_with_file(
|
||||
params[:track][:file].tempfile,
|
||||
params[:track][:file].content_type
|
||||
)
|
||||
redirect_to @track
|
||||
else
|
||||
render :new
|
||||
@ -20,5 +20,8 @@ class TracksController < ApplicationController
|
||||
end
|
||||
|
||||
def stream
|
||||
track = Track.find params[:id]
|
||||
self.content_type = track.mime_type
|
||||
self.response_body = Streamer.new(track.filepath)
|
||||
end
|
||||
end
|
||||
|
@ -1,17 +1,18 @@
|
||||
class Track < ActiveRecord::Base
|
||||
validates_presence_of :name
|
||||
validates_presence_of :mime_type
|
||||
validates_presence_of :sha256
|
||||
|
||||
after_create :save_file
|
||||
|
||||
def uploaded_file=(file)
|
||||
@file = file
|
||||
def filepath
|
||||
"#{Rails.root}/data/tracks/#{sha256}"
|
||||
end
|
||||
|
||||
def save_file
|
||||
if @file
|
||||
File.open("#{Rails.root}/data/tracks/#{id}", 'w') do |f|
|
||||
f.write @file.tempfile.read
|
||||
end
|
||||
end
|
||||
def save_with_file(file, mime_type)
|
||||
self.sha256 = Digest::SHA256.file(file.path).hexdigest
|
||||
self.mime_type = mime_type
|
||||
File.open(filepath, 'w') do |f|
|
||||
f.write file.read
|
||||
end
|
||||
save!
|
||||
end
|
||||
end
|
||||
|
@ -1,3 +1,3 @@
|
||||
%h1= @track.name
|
||||
%audio{:src => stream_track_path(@track)}
|
||||
%audio{:src => stream_track_path(@track), :controls => true, :autoplay => true}
|
||||
Your browser does not support the audio element
|
||||
|
@ -13,7 +13,7 @@ module Scube
|
||||
# -- all .rb files in that directory are automatically loaded.
|
||||
|
||||
# Custom directories with classes and modules you want to be autoloadable.
|
||||
# config.autoload_paths += %W(#{config.root}/extras)
|
||||
config.autoload_paths += %W(#{config.root}/lib)
|
||||
|
||||
# Only load the plugins named here, in the order given (default is alphabetical).
|
||||
# :all can be used as a placeholder for all plugins not explicitly named.
|
||||
|
@ -0,0 +1,9 @@
|
||||
class AddMimeTypeFieldToTracks < ActiveRecord::Migration
|
||||
def self.up
|
||||
add_column :tracks, :mime_type, :string
|
||||
end
|
||||
|
||||
def self.down
|
||||
remove_column :tracks, :mime_type
|
||||
end
|
||||
end
|
9
db/migrate/20110726101228_add_sha256_field_to_tracks.rb
Normal file
9
db/migrate/20110726101228_add_sha256_field_to_tracks.rb
Normal file
@ -0,0 +1,9 @@
|
||||
class AddSha256FieldToTracks < ActiveRecord::Migration
|
||||
def self.up
|
||||
add_column :tracks, :sha256, :string
|
||||
end
|
||||
|
||||
def self.down
|
||||
remove_column :tracks, :sha256
|
||||
end
|
||||
end
|
@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended to check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(:version => 20110713182005) do
|
||||
ActiveRecord::Schema.define(:version => 20110726101228) do
|
||||
|
||||
create_table "playlists", :force => true do |t|
|
||||
t.string "name"
|
||||
@ -22,6 +22,8 @@ ActiveRecord::Schema.define(:version => 20110713182005) do
|
||||
t.string "name"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.string "mime_type"
|
||||
t.string "sha256"
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -1,3 +1,3 @@
|
||||
Given /^a playlist named "([^"]*)"$/ do |name|
|
||||
Playlist.create!(:name => name)
|
||||
Factory.create(:playlist, :name => name)
|
||||
end
|
||||
|
@ -1,5 +1,7 @@
|
||||
Given /^a track named "([^"]*)"$/ do |name|
|
||||
@track = Track.create!(:name => name)
|
||||
@track = Factory.create(:track, :name => name)
|
||||
file = File.new("#{Rails.root}/features/fixtures/test.mp3")
|
||||
@track.save_with_file(file, 'audio/mpeg')
|
||||
end
|
||||
|
||||
Then /^I should see an audio player$/ do
|
||||
@ -8,4 +10,6 @@ end
|
||||
|
||||
Then /^it should provide an audio stream for "([^"]*)"$/ do |name|
|
||||
page.should have_xpath "//audio[@src='#{stream_track_path(@track)}']"
|
||||
get find('audio')[:src]
|
||||
last_response.status.should == 200
|
||||
end
|
||||
|
@ -13,7 +13,7 @@ Feature: Tracks
|
||||
Given I am on the playlists page
|
||||
When I follow "Add a track"
|
||||
And I fill in "Name" with "Mega song"
|
||||
And I attach the file "features/data/test.mp3" to "File"
|
||||
And I attach the file "features/fixtures/test.mp3" to "File"
|
||||
And I press "Upload"
|
||||
Then I should see "Mega song" within "h1"
|
||||
|
||||
|
12
lib/streamer.rb
Normal file
12
lib/streamer.rb
Normal file
@ -0,0 +1,12 @@
|
||||
class Streamer
|
||||
def initialize(file)
|
||||
@file = file
|
||||
end
|
||||
|
||||
def each
|
||||
f = File.new(@file, 'r')
|
||||
while data = f.read(4096) do
|
||||
yield data
|
||||
end
|
||||
end
|
||||
end
|
@ -9,6 +9,25 @@ describe TracksController do
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET stream' do
|
||||
let(:track) { Factory.create(:track) }
|
||||
|
||||
it 'streams the requested track' do
|
||||
get :stream, :id => track.id.to_s
|
||||
response.should be_success
|
||||
end
|
||||
|
||||
it 'creates a streamer instance' do
|
||||
Streamer.should_receive(:new).with(track.filepath)
|
||||
get :stream, :id => track.id.to_s
|
||||
end
|
||||
|
||||
it 'returns the track mime-type as content-type' do
|
||||
get :stream, :id => track.id.to_s
|
||||
response.content_type.should == track.mime_type
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET new' do
|
||||
it 'assigns a new track as @track' do
|
||||
get :new
|
||||
@ -18,48 +37,52 @@ describe TracksController do
|
||||
|
||||
describe 'POST create' do
|
||||
let(:track) { mock_model(Track).as_null_object }
|
||||
let(:file) {
|
||||
file = mock(Rack::Test::UploadedFile)
|
||||
file.stub(:tempfile => File.new("#{Rails.root}/spec/fixtures/test.mp3"))
|
||||
file.stub(:content_type => 'audio/ogg')
|
||||
file
|
||||
}
|
||||
before { Track.stub(:new).and_return(track) }
|
||||
|
||||
it 'creates a new track' do
|
||||
attributes = Factory.attributes_for(:track)
|
||||
Track.should_receive(:new).
|
||||
with(Factory.attributes_for(:track)).
|
||||
with({:name => attributes[:name]}).
|
||||
and_return(track)
|
||||
post :create, :track => Factory.attributes_for(:track)
|
||||
post :create, :track => {
|
||||
:name => attributes[:name],
|
||||
:file => file
|
||||
}
|
||||
end
|
||||
|
||||
it 'saves the track' do
|
||||
track.should_receive(:save)
|
||||
post :create, :track => {}
|
||||
end
|
||||
|
||||
it 'saves the file uploaded for the track' do
|
||||
file = mock(Rack::Test::UploadedFile)
|
||||
track.should_receive(:uploaded_file=).with(file)
|
||||
it 'saves the track with a file' do
|
||||
track.should_receive(:save_with_file).
|
||||
with(file.tempfile, 'audio/ogg')
|
||||
post :create, :track => { :file => file }
|
||||
end
|
||||
|
||||
context 'when the track saves successfully' do
|
||||
it 'redirects to the track page' do
|
||||
post :create, :track => Factory.attributes_for(:track)
|
||||
post :create, :track => { :file => file }
|
||||
response.should redirect_to(track)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the track fails to save' do
|
||||
before do
|
||||
track.stub(:save).and_return(false)
|
||||
track.stub(:save_with_file).and_return(false)
|
||||
end
|
||||
|
||||
it 'assigns the track as @track' do
|
||||
post :create, :track => {}
|
||||
post :create, :track => { :file => file }
|
||||
assigns[:track].should == track
|
||||
end
|
||||
|
||||
it 'renders the new template' do
|
||||
post :create, :track => {}
|
||||
post :create, :track => { :file => file }
|
||||
response.should render_template('new')
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
BIN
spec/data/test.mp3
Normal file
BIN
spec/data/test.mp3
Normal file
Binary file not shown.
@ -5,5 +5,7 @@ FactoryGirl.define do
|
||||
|
||||
factory :track do
|
||||
name 'Mega song'
|
||||
mime_type 'audio/ogg'
|
||||
sha256 '94a5486a69a7261da350c57f9e5a1eaa789e08752cfc56a1989976a6ad82f7a8'
|
||||
end
|
||||
end
|
||||
|
19
spec/lib/streamer_spec.rb
Normal file
19
spec/lib/streamer_spec.rb
Normal file
@ -0,0 +1,19 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe Streamer do
|
||||
describe '#each' do
|
||||
let(:stream) { Streamer.new("#{Rails.root}/spec/fixtures/test.mp3") }
|
||||
|
||||
it 'returns file content' do
|
||||
chunks = ''
|
||||
stream.each { |c| chunks << c }
|
||||
chunks.should == File.read("#{Rails.root}/spec/fixtures/test.mp3")
|
||||
end
|
||||
|
||||
it 'returns content in multiple chunks' do
|
||||
count = 0
|
||||
stream.each { count += 1 }
|
||||
count.should be >= 2
|
||||
end
|
||||
end
|
||||
end
|
@ -9,30 +9,51 @@ describe Track do
|
||||
end
|
||||
|
||||
context 'when name empty' do
|
||||
before do
|
||||
track.name = ''
|
||||
end
|
||||
|
||||
before { track.name = '' }
|
||||
it { should_not be_valid }
|
||||
end
|
||||
|
||||
describe '#uploaded_file=' do
|
||||
let(:track) { Track.new :name => 'Mega song' }
|
||||
context 'when mime_type empty' do
|
||||
before { track.mime_type = '' }
|
||||
it { should_not be_valid }
|
||||
end
|
||||
|
||||
it 'saves an uploaded file' do
|
||||
filepath = "#{Rails.root}/spec/fixtures/test.mp3"
|
||||
file = mock(Rack::Test::UploadedFile)
|
||||
file.stub(
|
||||
:tempfile => File.new(filepath),
|
||||
:content_type => 'audio/mpeg'
|
||||
)
|
||||
track.uploaded_file = file
|
||||
track.save
|
||||
File.read("#{Rails.root}/data/tracks/#{track.id.to_s}").should == File.read(filepath)
|
||||
context 'when sha256 empty' do
|
||||
before { track.sha256 = '' }
|
||||
it { should_not be_valid }
|
||||
end
|
||||
|
||||
describe '#filepath' do
|
||||
it 'returns the path to the track file' do
|
||||
track.filepath.should == "#{Rails.root}/data/tracks/#{track.sha256}"
|
||||
end
|
||||
end
|
||||
|
||||
describe '#save_with_file' do
|
||||
let(:file) { File.new("#{Rails.root}/spec/fixtures/test.mp3") }
|
||||
|
||||
it 'calls save' do
|
||||
Track.any_instance.should_receive(:save!)
|
||||
track.save_with_file(file, 'audio/ogg')
|
||||
end
|
||||
|
||||
it 'saves the file content' do
|
||||
track.save_with_file(file, 'audio/ogg')
|
||||
File.read(track.filepath).should == File.read(file.path)
|
||||
end
|
||||
|
||||
it 'saves the file SHA256 digest' do
|
||||
track.save_with_file(file, 'audio/ogg')
|
||||
track.sha256.should == Digest::SHA256.file(file.path).hexdigest
|
||||
end
|
||||
|
||||
it 'saves the file mime type' do
|
||||
track.save_with_file(file, 'audio/mpeg')
|
||||
track.mime_type.should == 'audio/mpeg'
|
||||
end
|
||||
|
||||
after do
|
||||
`rm -rf #{Rails.root}/data/tracks/*`
|
||||
`rm -f #{Rails.root}/data/tracks/*`
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -20,6 +20,16 @@ describe 'tracks/show.html.haml' do
|
||||
rendered.should have_selector('audio[src]')
|
||||
end
|
||||
|
||||
it 'provides controls' do
|
||||
render
|
||||
rendered.should have_selector('audio[controls]')
|
||||
end
|
||||
|
||||
it 'has autoplay activated' do
|
||||
render
|
||||
rendered.should have_selector('audio[autoplay]')
|
||||
end
|
||||
|
||||
it 'displays a text fallback for UA without support' do
|
||||
render
|
||||
rendered.should have_selector('audio',
|
||||
|
Loading…
x
Reference in New Issue
Block a user