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:
		| @@ -9,10 +9,10 @@ class TracksController < ApplicationController | |||||||
|  |  | ||||||
|   def create |   def create | ||||||
|     @track = Track.new(:name => params[:track][:name]) |     @track = Track.new(:name => params[:track][:name]) | ||||||
|     if params[:track][:file] |     if @track.save_with_file( | ||||||
|       @track.uploaded_file = params[:track][:file] |         params[:track][:file].tempfile, | ||||||
|     end |         params[:track][:file].content_type | ||||||
|     if @track.save |       ) | ||||||
|       redirect_to @track |       redirect_to @track | ||||||
|     else |     else | ||||||
|       render :new |       render :new | ||||||
| @@ -20,5 +20,8 @@ class TracksController < ApplicationController | |||||||
|   end |   end | ||||||
|  |  | ||||||
|   def stream |   def stream | ||||||
|  |     track = Track.find params[:id] | ||||||
|  |     self.content_type = track.mime_type | ||||||
|  |     self.response_body = Streamer.new(track.filepath) | ||||||
|   end |   end | ||||||
| end | end | ||||||
|   | |||||||
| @@ -1,17 +1,18 @@ | |||||||
| class Track < ActiveRecord::Base | class Track < ActiveRecord::Base | ||||||
|   validates_presence_of :name |   validates_presence_of :name | ||||||
|  |   validates_presence_of :mime_type | ||||||
|  |   validates_presence_of :sha256 | ||||||
|  |  | ||||||
|   after_create :save_file |   def filepath | ||||||
|  |    "#{Rails.root}/data/tracks/#{sha256}" | ||||||
|   def uploaded_file=(file) |  | ||||||
|     @file = file |  | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   def save_file |   def save_with_file(file, mime_type) | ||||||
|     if @file |     self.sha256 = Digest::SHA256.file(file.path).hexdigest | ||||||
|       File.open("#{Rails.root}/data/tracks/#{id}", 'w') do |f| |     self.mime_type = mime_type | ||||||
|         f.write @file.tempfile.read |     File.open(filepath, 'w') do |f| | ||||||
|       end |       f.write file.read | ||||||
|     end |     end | ||||||
|  |     save! | ||||||
|   end |   end | ||||||
| end | end | ||||||
|   | |||||||
| @@ -1,3 +1,3 @@ | |||||||
| %h1= @track.name | %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 |   Your browser does not support the audio element | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ module Scube | |||||||
|     # -- all .rb files in that directory are automatically loaded. |     # -- all .rb files in that directory are automatically loaded. | ||||||
|  |  | ||||||
|     # Custom directories with classes and modules you want to be autoloadable. |     # 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). |     # 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. |     # :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. | # 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| |   create_table "playlists", :force => true do |t| | ||||||
|     t.string   "name" |     t.string   "name" | ||||||
| @@ -22,6 +22,8 @@ ActiveRecord::Schema.define(:version => 20110713182005) do | |||||||
|     t.string   "name" |     t.string   "name" | ||||||
|     t.datetime "created_at" |     t.datetime "created_at" | ||||||
|     t.datetime "updated_at" |     t.datetime "updated_at" | ||||||
|  |     t.string   "mime_type" | ||||||
|  |     t.string   "sha256" | ||||||
|   end |   end | ||||||
|  |  | ||||||
| end | end | ||||||
|   | |||||||
| @@ -1,3 +1,3 @@ | |||||||
| Given /^a playlist named "([^"]*)"$/ do |name| | Given /^a playlist named "([^"]*)"$/ do |name| | ||||||
|   Playlist.create!(:name => name) |   Factory.create(:playlist, :name => name) | ||||||
| end | end | ||||||
|   | |||||||
| @@ -1,5 +1,7 @@ | |||||||
| Given /^a track named "([^"]*)"$/ do |name| | 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 | end | ||||||
|  |  | ||||||
| Then /^I should see an audio player$/ do | Then /^I should see an audio player$/ do | ||||||
| @@ -8,4 +10,6 @@ end | |||||||
|  |  | ||||||
| Then /^it should provide an audio stream for "([^"]*)"$/ do |name| | Then /^it should provide an audio stream for "([^"]*)"$/ do |name| | ||||||
|   page.should have_xpath "//audio[@src='#{stream_track_path(@track)}']" |   page.should have_xpath "//audio[@src='#{stream_track_path(@track)}']" | ||||||
|  |   get find('audio')[:src] | ||||||
|  |   last_response.status.should == 200 | ||||||
| end | end | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ Feature: Tracks | |||||||
|     Given I am on the playlists page |     Given I am on the playlists page | ||||||
|     When I follow "Add a track" |     When I follow "Add a track" | ||||||
|     And I fill in "Name" with "Mega song" |     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" |     And I press "Upload" | ||||||
|     Then I should see "Mega song" within "h1" |     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 | ||||||
|   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 |   describe 'GET new' do | ||||||
|     it 'assigns a new track as @track' do |     it 'assigns a new track as @track' do | ||||||
|       get :new |       get :new | ||||||
| @@ -18,48 +37,52 @@ describe TracksController do | |||||||
|  |  | ||||||
|   describe 'POST create' do |   describe 'POST create' do | ||||||
|     let(:track) { mock_model(Track).as_null_object } |     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) } |     before { Track.stub(:new).and_return(track) } | ||||||
|  |  | ||||||
|     it 'creates a new track' do |     it 'creates a new track' do | ||||||
|  |       attributes = Factory.attributes_for(:track) | ||||||
|       Track.should_receive(:new). |       Track.should_receive(:new). | ||||||
|         with(Factory.attributes_for(:track)). |         with({:name => attributes[:name]}). | ||||||
|         and_return(track) |         and_return(track) | ||||||
|       post :create, :track => Factory.attributes_for(:track) |       post :create, :track => { | ||||||
|  |         :name => attributes[:name], | ||||||
|  |         :file => file | ||||||
|  |       } | ||||||
|     end |     end | ||||||
|  |  | ||||||
|     it 'saves the track' do |     it 'saves the track with a file' do | ||||||
|       track.should_receive(:save) |       track.should_receive(:save_with_file). | ||||||
|       post :create, :track => {} |         with(file.tempfile, 'audio/ogg') | ||||||
|     end |  | ||||||
|  |  | ||||||
|     it 'saves the file uploaded for the track' do |  | ||||||
|       file = mock(Rack::Test::UploadedFile) |  | ||||||
|       track.should_receive(:uploaded_file=).with(file) |  | ||||||
|       post :create, :track => { :file => file } |       post :create, :track => { :file => file } | ||||||
|     end |     end | ||||||
|  |  | ||||||
|     context 'when the track saves successfully' do |     context 'when the track saves successfully' do | ||||||
|       it 'redirects to the track page' 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) |         response.should redirect_to(track) | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
|  |  | ||||||
|     context 'when the track fails to save' do |     context 'when the track fails to save' do | ||||||
|       before do |       before do | ||||||
|         track.stub(:save).and_return(false) |         track.stub(:save_with_file).and_return(false) | ||||||
|       end |       end | ||||||
|  |  | ||||||
|       it 'assigns the track as @track' do |       it 'assigns the track as @track' do | ||||||
|         post :create, :track => {} |         post :create, :track => { :file => file } | ||||||
|         assigns[:track].should == track |         assigns[:track].should == track | ||||||
|       end |       end | ||||||
|  |  | ||||||
|       it 'renders the new template' do |       it 'renders the new template' do | ||||||
|         post :create, :track => {} |         post :create, :track => { :file => file } | ||||||
|         response.should render_template('new') |         response.should render_template('new') | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
|  |  | ||||||
|   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 |   factory :track do | ||||||
|     name 'Mega song' |     name 'Mega song' | ||||||
|  |     mime_type 'audio/ogg' | ||||||
|  |     sha256 '94a5486a69a7261da350c57f9e5a1eaa789e08752cfc56a1989976a6ad82f7a8' | ||||||
|   end |   end | ||||||
| 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 |   end | ||||||
|  |  | ||||||
|   context 'when name empty' do |   context 'when name empty' do | ||||||
|     before do |     before { track.name = '' } | ||||||
|       track.name = '' |  | ||||||
|     end |  | ||||||
|  |  | ||||||
|     it { should_not be_valid } |     it { should_not be_valid } | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   describe '#uploaded_file=' do |   context 'when mime_type empty' do | ||||||
|     let(:track) { Track.new :name => 'Mega song' } |     before { track.mime_type = '' } | ||||||
|  |     it { should_not be_valid } | ||||||
|  |   end | ||||||
|  |  | ||||||
|     it 'saves an uploaded file' do |   context 'when sha256 empty' do | ||||||
|       filepath = "#{Rails.root}/spec/fixtures/test.mp3" |     before { track.sha256 = '' } | ||||||
|       file = mock(Rack::Test::UploadedFile) |     it { should_not be_valid } | ||||||
|       file.stub( |   end | ||||||
|         :tempfile     => File.new(filepath), |  | ||||||
|         :content_type => 'audio/mpeg' |   describe '#filepath' do | ||||||
|       ) |     it 'returns the path to the track file' do | ||||||
|       track.uploaded_file = file |       track.filepath.should == "#{Rails.root}/data/tracks/#{track.sha256}" | ||||||
|       track.save |     end | ||||||
|       File.read("#{Rails.root}/data/tracks/#{track.id.to_s}").should == File.read(filepath) |   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 |     end | ||||||
|  |  | ||||||
|     after do |     after do | ||||||
|       `rm -rf #{Rails.root}/data/tracks/*` |       `rm -f #{Rails.root}/data/tracks/*` | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| end | end | ||||||
|   | |||||||
| @@ -20,6 +20,16 @@ describe 'tracks/show.html.haml' do | |||||||
|       rendered.should have_selector('audio[src]') |       rendered.should have_selector('audio[src]') | ||||||
|     end |     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 |     it 'displays a text fallback for UA without support' do | ||||||
|       render |       render | ||||||
|       rendered.should have_selector('audio', |       rendered.should have_selector('audio', | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user