* VRFS-1089 - jamadmin creation of recordings done

This commit is contained in:
Seth Call 2014-02-13 16:41:50 +00:00
parent 69523e0e07
commit 5ba4e97dca
39 changed files with 532 additions and 181 deletions

View File

@ -99,7 +99,12 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do
controller do
autocomplete :user, :email, :full => true
# this actually searches on first name, last name, and email, because of get_autocomplete_items defined below
autocomplete :user, :email, :full => true, :display_value => :autocomplete_display_name
def get_autocomplete_items(parameters)
items = User.select("DISTINCT email, first_name, last_name, id").where(["email ILIKE ? OR first_name ILIKE ? OR last_name ILIKE ?", "%#{parameters[:term]}%", "%#{parameters[:term]}%", "%#{parameters[:term]}%"])
end
def create
@jam_ruby_user = JamRuby::User.new(params[:jam_ruby_user])

View File

@ -10,19 +10,37 @@ ActiveAdmin.register JamRuby::Recording, :as => 'Recording' do
form :partial => 'form'
controller do
def initialize_client_tokens(params)
recording = params[:jam_ruby_recording]
return params unless recording
recorded_tracks = recording[:recorded_tracks_attributes]
return params unless recorded_tracks
recorded_tracks.each do |key, recorded_track|
recorded_track[:client_id] = nil if recorded_track[:client_id] == ""
recorded_track[:client_track_id] = nil if recorded_track[:client_track_id] == ""
recorded_track[:track_id] = nil if recorded_track[:track_id] == ""
recorded_track[:client_id] ||= recorded_track[:user_id] if recorded_track[:user_id]
recorded_track[:client_track_id] ||= SecureRandom.uuid
recorded_track[:track_id] ||= SecureRandom.uuid
end
params
end
def new
@recording = JamRuby::Recording.new
super
end
def create
owner = User.find_by_email!(params[:jam_ruby_recording][:owner])
band = User.find_by_band!(params[:jam_ruby_recording][:band])
recording = Recording.new
recording.owner = owner
recording.band = band
recording.save
redirect_to("/admin/recordings/#{recording.id}")
params.merge! initialize_client_tokens(params)
create!
end
def edit
@ -30,26 +48,12 @@ ActiveAdmin.register JamRuby::Recording, :as => 'Recording' do
super
end
def update
owner = User.find_by_email!(params[:jam_ruby_recording][:owner])
band = Band.find_by_name!(params[:jam_ruby_recording][:band])
recorded_tracks = params[:jam_ruby_recording][:recorded_tracks]
recorded_tracks.each do |recorded_track|
track = RecordedTrack.new
track.client_id ||= 'fake'
track.track_id ||= 'fake'
track.client_track_id ||= 'fake'
track.sound ||= 'stereo'
track.recording = resource
track.user = User.find_by_email!(recorded_track[:user])
track.instrument = Instrument.find_by_instrument!(recorded_track[:instrument])
resource.recorded_tracks << track
params.merge! initialize_client_tokens(params)
update! do |format|
format.html { redirect_to edit_admin_recording_url(params[:jam_ruby_recording]) }
end
resource.owner = owner
resource.band = band
resource.save
redirect_to("/admin/recordings/#{resource.id}")
end
end

View File

@ -1,11 +1,12 @@
// //= require active_admin/base
//= require jquery
//= require jquery_ujs
//= require jquery.ui.core
//= require jquery.ui.widget
//= require jquery.ui.datepicker
//= require jquery.ui.dialog
//= require jquery.ui.autocomplete
//= require jquery-ui
// require jquery.ui.core
// require jquery.ui.widget
// require jquery.ui.datepicker
// require jquery.ui.dialog
// require jquery.ui.autocomplete
//= require cocoon
//= require active_admin/application
//= require autocomplete-rails

View File

@ -2,10 +2,16 @@
class ImageUploader < CarrierWave::Uploader::Base
include CarrierWaveDirect::Uploader
include CarrierWave::MimeTypes
process :set_content_type
def initialize(*)
super
JamRuby::UploaderConfiguration.set_aws_public_configuration(self)
end
# Add a white list of extensions which are allowed to be uploaded.
def extension_white_list
%w(jpg jpeg gif png)

View File

@ -1,10 +1,15 @@
= f.inputs name: 'Claimed Recording' do
= link_to_remove_association "Delete Claimed Recording", f, class: 'button', style: 'margin-left:10px; position: relative; top: -37px; left: 50px;'
%ol.nested-fields
= f.input :name, :hint => 'This is entered in the post-recording dialog. It will be displayed in jam-web.'
= f.input :description, :hint => 'This is entered in the post-recording dialog. It will be displayed in jam-web.'
= f.input :user, :as => :autocomplete, :url => autocomplete_user_email_admin_users_path, :input_html => { id: "jam_ruby_claimed_recording_user_id", name: "", id_element: "#WILL_BE_OVERRIDDEN_BY_JS_IN_RECORDED_FORM" }
= f.input :user_id, :as => :hidden, :input_html => { class: "jam_ruby_claimed_recording[user_id]" }
= f.input :genre, collection: Genre.all, :member_label => :description
= f.input :is_public
= f.input :is_downloadable
= link_to_remove_association "Delete Claimed Recording", f, class: 'button', style: 'margin-left:10px'
%div{style: 'display:none'}
= #f.input :should_retry, :as => :hidden, :input_html => {:value => '0' }

View File

@ -1,15 +1,63 @@
<%= semantic_form_for([:admin, @recording], :html => {:multipart => true}, :url => @recording.new_record? ? admin_recordings_path : "/admin/recordings/#{@recording.id}") do |f| %>
<%= f.semantic_errors *f.object.errors.keys %>
<%= f.inputs do %>
<%= f.input :owner, :as => :autocomplete, :url => autocomplete_user_email_admin_users_path %>
<%= f.input :band, :as => :autocomplete, :url => autocomplete_band_name_admin_bands_path %>
%h2 Instructions
%h3 Overview
%p Make each recorded track first, then the mix, and then finally the claimed recordings.
%h3 Entering users and bands
%p Autocomplete is used to supply users and bands. Just starting typing and pick from the resulting options.
%h3 Adding Tracks and Mixes
%p To add a track or mix, you first click 'Add track' or 'Add mix', and fill out any values present. However, to upload an ogg file for a mix or track, you must first click 'Update Recording' after you initially click 'Add Track/Mix'. When the form re-displays after the update, you will then seen an upload file input. Finally, after you have uploaded an ogg file, you can then click the Download link to verify your ogg file, or mp3, in the case of mixes.
%h4 Specific to Mixes
%ul
%li When you first click 'Add Mix', there is nothing to fill out. So click 'Add Mix', then click 'Update Recording'. The page will prompt you, as well.
%li When you upload a mix ogg file, it will be converted to mp3 too. This makes the request take a little bit of time. Just wait it out.
%h3 Add Claimed Recordings
%p Once your recorded tracks are added, you then want to add one Claimed Recording for each user you want to have access to the recording. Making a claimed recording is basically making a recording 'visible' for a given user. The user must have a recorded track to have a claimed recording.
%h3 Validations
%p It should not be possible to create an invalid recording/track/mix/claim; there are a great deal of validations that will prevent you from doing something invalid. If you find otherwise, please fill out a JIRA .
<%= f.fields_for :recorded_tracks do |recorded_track| %>
<%= render 'recorded_track', f: recorded_track %>
<% end %>
= semantic_form_for([:admin, @recording], :html => {:multipart => true}, :url => @recording.new_record? ? admin_recordings_path : "/admin/recordings/#{@recording.id}") do |f|
= f.semantic_errors *f.object.errors.keys
= f.inputs name: 'Recording Fields' do
<%= link_to_add_association 'add track', f, :recorded_tracks %>
= f.input :name, :hint => 'something to remember this recording by. This is used solely for display in jam-admin; nowhere else.'
<% end %>
<%= f.actions %>
<% end %>
= f.input :owner, :as => :autocomplete, :url => autocomplete_user_email_admin_users_path, :input_html => { :id => "jam_ruby_recording_owner", :name => "", :id_element => "#jam_ruby_recording_owner_id" }
= f.input :owner_id, :as => :hidden, :input_html => { :name => "jam_ruby_recording[owner_id]" }
= f.input :band, :as => :autocomplete, :url => autocomplete_band_name_admin_bands_path, :input_html => { :id => "jam_ruby_recording_band", :name => "", :id_element => "#jam_ruby_recording_band_id" }
= f.input :band_id, :as => :hidden, :input_html => { :name => "jam_ruby_recording[band_id]" }
= f.semantic_fields_for :recorded_tracks do |recorded_track, index|
= render 'recorded_track_fields', f: recorded_track
.links
= link_to_add_association 'Add Track', f, :recorded_tracks, class: 'button', style: 'margin:20px;padding:10px 20px'
= f.semantic_fields_for :mixes do |mix, index|
= render 'mix_fields', f: mix
.links
= link_to_add_association 'Add Mix', f, :mixes, class: 'button', style: 'margin:20px; padding:10px 20px'
= f.semantic_fields_for :claimed_recordings do |claimed_recording, index|
= render 'claimed_recording_fields', f: claimed_recording
.links
= link_to_add_association 'Add Claimed Recording', f, :claimed_recordings, class: 'button', style: 'margin:20px; padding:10px 20px'
= f.actions
:javascript
$(document).ready( function() {
$('body').on('cocoon:before-insert', function(e, insertedItem) {
// handle recorded tracks
var idForHiddenUserId = $('input[class="jam_ruby_recorded_track[user_id]"]', insertedItem).attr('id');
if(idForHiddenUserId) {
$('input[id="jam_ruby_recorded_track_user_id"]', insertedItem).attr('data-id-element', '#' + idForHiddenUserId)
}
// handle claimed recordings
idForHiddenUserId = $('input[class="jam_ruby_claimed_recording[user_id]"]', insertedItem).attr('id');
if(idForHiddenUserId) {
$('input[id="jam_ruby_claimed_recording_user_id"]', insertedItem).attr('data-id-element', '#' + idForHiddenUserId)
}
});
});

View File

@ -1,18 +1,21 @@
= f.inputs name: 'Track' do
= f.inputs name: 'Mix' do
%ol.nested-fields
= puts "f.object #{f.object.url}"
= f.input :user, :as => :autocomplete, :url => autocomplete_user_email_admin_users_path, :input_html => { id: "jam_ruby_recorded_track_user_id", name: "", id_element: "#WILL_BE_OVERRIDDEN_BY_JS_IN_RECORDED_FORM" }
= f.input :user_id, :as => :hidden, :input_html => { class: "jam_ruby_recorded_track[user_id]" }
= f.input :instrument, collection: Instrument.all
= f.input :sound, :as => :select, collection: options_for_select(RecordedTrack::SOUND, 'stereo')
= f.input :url, :as => :file
- unless f.object.nil? && f.object.url.nil?
%a{href: f.object.sign_url} current file
- if f.object.new_record?
%p{style: 'margin-left:10px'}
%i before you can upload, you must select 'Update Recording'
- else
= f.input :ogg_url, :as => :file
- unless f.object.nil? || f.object[:ogg_url].nil?
.current_file_holder{style: 'margin-bottom:10px'}
%a{href: f.object.sign_url(3600, 'ogg'), style: 'padding:0 0 0 20px'} Download OGG
%a{href: f.object.sign_url(3600, 'mp3'), style: 'padding:0 0 0 20px'} Download MP3
%div{style: 'display:none'}
= f.input :should_retry, :as => :hidden, :input_html => {:value => '0' }
= link_to_remove_association "Delete Mix", f, class: 'button', style: 'margin-left:10px'
= f.input :client_id, as: :hidden
= f.input :track_id, as: :hidden
= f.input :client_track_id, as: :hidden
= link_to_remove_association "remove track", f

View File

@ -1,4 +1,25 @@
li.control-group nested-fields
div.controls
= f.input :user, :as => :autocomplete, :url => autocomplete_user_email_admin_users_path
= f.input :instrument, :collection => Instrument.all
= f.inputs name: 'Track' do
%ol.nested-fields
= f.input :user, :as => :autocomplete, :url => autocomplete_user_email_admin_users_path, :input_html => { id: "jam_ruby_recorded_track_user_id", name: "", id_element: "#WILL_BE_OVERRIDDEN_BY_JS_IN_RECORDED_FORM" }
= f.input :user_id, :as => :hidden, :input_html => { class: "jam_ruby_recorded_track[user_id]" }
= f.input :instrument, collection: Instrument.all
= f.input :sound, :as => :select, collection: options_for_select(RecordedTrack::SOUND, 'stereo')
- if f.object.new_record?
%i before you can upload, you must select 'Update Recording'
- else
= f.input :url, :as => :file
- unless f.object.nil? || f.object[:url].nil?
.current_file_holder{style: 'margin-bottom:10px'}
%a{href: f.object.sign_url(3600), style: 'padding:0 0 0 20px'} Download
%div{style: 'display:none'}
= f.input :client_id, as: :hidden
= f.input :track_id, as: :hidden
= f.input :client_track_id, as: :hidden
= link_to_remove_association "Delete Track", f, class: 'button', style: 'margin-left:10px'

View File

@ -87,7 +87,7 @@ module JamAdmin
# set to false to instead use amazon. You will also need to supply amazon secrets
config.store_artifacts_to_disk = false
config.storage_type = :fog
#config.storage_type = :fog
# these only need to be set if store_artifact_to_files = false
config.aws_access_key_id = ENV['AWS_KEY']
@ -117,5 +117,7 @@ module JamAdmin
config.twitter_app_id = ENV['TWITTER_APP_ID'] || 'nQj2oEeoJZxECC33tiTuIg'
config.twitter_app_secret = ENV['TWITTER_APP_SECRET'] || 'Azcy3QqfzYzn2fsojFPYXcn72yfwa0vG6wWDrZ3KT8'
config.ffmpeg_path = ENV['FFMPEG_PATH'] || (File.exist?('/usr/local/bin/ffmpeg') ? '/usr/local/bin/ffmpeg' : '/usr/bin/ffmpeg')
end
end

View File

@ -4,20 +4,9 @@ CarrierWave.root = Rails.root.join(Rails.public_path).to_s
CarrierWave.base_path = ENV['RAILS_RELATIVE_URL_ROOT']
CarrierWave.configure do |config|
if JamAdmin::Application.config.store_artifacts_to_disk
config.storage = :file
else
config.storage = :fog
config.fog_credentials = {
:provider => 'AWS',
:aws_access_key_id => JamAdmin::Application.config.aws_access_key_id,
:aws_secret_access_key => JamAdmin::Application.config.aws_secret_access_key,
:region => JamAdmin::Application.config.aws_region,
}
config.fog_directory = JamAdmin::Application.config.aws_bucket_public # required
config.fog_public = true # optional, defaults to true
config.fog_attributes = {'Cache-Control'=>"max-age=#{JamAdmin::Application.config.aws_cache}"} # optional, defaults to {}
end
config.storage = Rails.application.config.store_artifacts_to_disk ? :file : :fog
JamRuby::UploaderConfiguration.set_aws_private_configuration(config)
end
require 'carrierwave/orm/activerecord'

View File

@ -103,4 +103,5 @@ share_token_2.sql
large_photo_url.sql
add_secret_to_user_authorization.sql
track_connection_id_not_null.sql
recordings_all_discarded.sql
recordings_all_discarded.sql
recordings_via_admin_web.sql

7
ruby/.simplecov Normal file
View File

@ -0,0 +1,7 @@
SimpleCov.start do
add_filter "/test/"
add_filter "/bin/"
add_filter "/scripts/"
add_filter "/tmp/"
add_filter "/vendor/"
end

View File

@ -43,7 +43,8 @@ gem 'oj'
gem 'builder'
group :test do
gem "factory_girl", '4.1.0'
gem 'simplecov', '~> 0.7.1'
gem 'factory_girl', '4.1.0'
gem "rspec", "2.11"
gem 'spork', '0.9.0'
gem 'database_cleaner', '0.7.0'

View File

@ -45,8 +45,11 @@ require "jam_ruby/init"
require "jam_ruby/app/mailers/user_mailer"
require "jam_ruby/app/mailers/invited_user_mailer"
require "jam_ruby/app/mailers/corp_mailer"
require "jam_ruby/app/uploaders/uploader_configuration"
require "jam_ruby/app/uploaders/artifact_uploader"
require "jam_ruby/app/uploaders/perf_data_uploader"
require "jam_ruby/app/uploaders/recorded_track_uploader"
require "jam_ruby/app/uploaders/mix_uploader"
require "jam_ruby/lib/desk_multipass"
require "jam_ruby/amqp/amqp_connection_manager"
require "jam_ruby/message_factory"

View File

@ -1,7 +1,12 @@
# encoding: utf-8
class ArtifactUploader < CarrierWave::Uploader::Base
class ArtifactUploader < CarrierWave::Uploader::Base
def initialize(*)
super
JamRuby::UploaderConfiguration.set_aws_public_configuration(self)
end
# Include RMagick or MiniMagick support:
# include CarrierWave::RMagick
# include CarrierWave::MiniMagick

View File

@ -1,38 +1,23 @@
class RecordedTrackUploader < CarrierWave::Uploader::Base
class MixUploader < CarrierWave::Uploader::Base
# include CarrierWaveDirect::Uploader
include CarrierWave::MimeTypes
process :set_content_type
process :add_metadata
version :mp3_url do
process :create_mp3
def full_filename(file)
model.filename('mp3') if model.id
end
end
def initialize(*)
super
JamRuby::UploaderConfiguration.set_aws_private_configuration(self)
end
def add_metadata
# add this metadata to ogg file without disturbing audio
#JamRecordingId=af9f1598-2243-4c21-98c1-5e0c56da5b89
#JamTrackId=5b1c3ef4-01d7-471e-8684-e2a5743ffd26
#JamClientId=8331bcec-7810-42c1-9f39-a5c129406e85
#JamType=LocalTrack
# secret sauce is -codec copy, and a bunch of -metadata arguments.
# after done, stomp input file with new one
input_file = current_path
output_file = current_path + '.new.ogg'
ffmpeg_cmd = "#{APP_CONFIG.ffmpeg_path} -i \"#{input_file}\" -codec copy -metadata JamRecordingId=#{model.recording_id} -metadata JamTrackId=#{model.client_track_id} -metadata JamClientId=#{model.client_id} -metadata JamType=LocalTrack \"#{output_file}\""
system(ffmpeg_cmd)
unless $? == 0
raise "ffmpeg failed"
end
FileUtils.mv output_file, input_file
end
# Add a white list of extensions which are allowed to be uploaded.
def extension_white_list
%w(ogg)
@ -47,6 +32,50 @@ class RecordedTrackUploader < CarrierWave::Uploader::Base
end
def filename
model.filename if model.id
model.filename('ogg') if model.id
end
def add_metadata
# add this metadata to ogg file without disturbing audio
# JamRecordingId=438
# JamMixId=438
# JamType=Mix
# secret sauce is -codec copy, and a bunch of -metadata arguments.
# after done, stomp input file with new one
input_file = current_path
output_file = current_path + '.new.ogg'
ffmpeg_cmd = "#{APP_CONFIG.ffmpeg_path} -i \"#{input_file}\" -codec copy -metadata JamRecordingId=#{model.recording_id} -metadata JamMixId=#{model.id} -metadata JamType=Mix \"#{output_file}\""
system(ffmpeg_cmd)
unless $? == 0
raise "ffmpeg metadata copy failed"
end
FileUtils.mv output_file, input_file
end
def create_mp3
# add this metadata to mp3 file
# JamRecordingId=438
# JamMixId=438
# JamType=Mix
input_file = current_path
output_file = current_path + '.new.mp3'
ffmpeg_cmd = "#{APP_CONFIG.ffmpeg_path} -i \"#{input_file}\" -ab 128k -metadata JamRecordingId=#{model.recording_id} -metadata JamMixId=#{model.id} -metadata JamType=Mix \"#{output_file}\""
system(ffmpeg_cmd)
unless $? == 0
raise "ffmpeg mp3 convert failed"
end
model.mp3_md5 = Digest::MD5.file(output_file).hexdigest
model.mp3_length = File.size(output_file)
model.mp3_url = model.filename('mp3')
file.instance_variable_set(:@content_type, 'audio/mpeg')
FileUtils.mv output_file, input_file
end
end

View File

@ -2,6 +2,11 @@
class PerfDataUploader < CarrierWave::Uploader::Base
def initialize(*)
super
JamRuby::UploaderConfiguration.set_aws_private_configuration(self)
end
# Include RMagick or MiniMagick support:
# include CarrierWave::RMagick
# include CarrierWave::MiniMagick

View File

@ -0,0 +1,52 @@
class RecordedTrackUploader < CarrierWave::Uploader::Base
# include CarrierWaveDirect::Uploader
include CarrierWave::MimeTypes
process :set_content_type
process :add_metadata
def initialize(*)
super
JamRuby::UploaderConfiguration.set_aws_private_configuration(self)
end
def add_metadata
# add this metadata to ogg file without disturbing audio
#JamRecordingId=af9f1598-2243-4c21-98c1-5e0c56da5b89
#JamTrackId=5b1c3ef4-01d7-471e-8684-e2a5743ffd26
#JamClientId=8331bcec-7810-42c1-9f39-a5c129406e85
#JamType=LocalTrack
# secret sauce is -codec copy, and a bunch of -metadata arguments.
# after done, stomp input file with new one
input_file = current_path
output_file = current_path + '.new.ogg'
ffmpeg_cmd = "#{APP_CONFIG.ffmpeg_path} -i \"#{input_file}\" -codec copy -metadata JamRecordingId=#{model.recording_id} -metadata JamTrackId=#{model.client_track_id} -metadata JamClientId=#{model.client_id} -metadata JamType=LocalTrack \"#{output_file}\""
system(ffmpeg_cmd)
unless $? == 0
raise "ffmpeg failed"
end
FileUtils.mv output_file, input_file
end
# Add a white list of extensions which are allowed to be uploaded.
def extension_white_list
%w(ogg)
end
def store_dir
nil
end
def md5
@md5 ||= ::Digest::MD5.file(current_path).hexdigest
end
def filename
model.filename if model.id
end
end

View File

@ -0,0 +1,32 @@
module JamRuby
# https://github.com/carrierwaveuploader/carrierwave/wiki/How-to%3a-Define-different-storage-configuration-for-each-Uploader.
# these methods should be called in initialize() of uploaders to override their configuration
class UploaderConfiguration
def self.set_aws_public_configuration(config)
config.fog_credentials = {
:provider => 'AWS',
:aws_access_key_id => APP_CONFIG.aws_access_key_id,
:aws_secret_access_key => APP_CONFIG.aws_secret_access_key,
:region => APP_CONFIG.aws_region,
}
config.fog_directory = APP_CONFIG.aws_bucket_public # required
config.fog_public = true # optional, defaults to true
config.fog_attributes = {'Cache-Control'=>"max-age=#{APP_CONFIG.aws_cache}"} # optional, defaults to {}
end
def self.set_aws_private_configuration(config)
config.fog_credentials = {
:provider => 'AWS',
:aws_access_key_id => APP_CONFIG.aws_access_key_id,
:aws_secret_access_key => APP_CONFIG.aws_secret_access_key,
:region => APP_CONFIG.aws_region,
}
config.fog_directory = APP_CONFIG.aws_bucket # required
config.fog_public = false # optional, defaults to true
config.fog_attributes = {'Cache-Control'=>"max-age=#{APP_CONFIG.aws_cache}"} # optional, defaults to {}
end
end
end

View File

@ -60,6 +60,10 @@ module ValidationMessages
BAD_UPLOAD = "incorrectly uploaded"
PART_NOT_STARTED = "not started"
UPLOAD_FAILURES_EXCEEDED = "exceeded"
ONLY_ONE_MIX = "can not be multiple"
# claimed recordings
NOT_PART_OF_RECORDING = "not a part of this recording"
# music sessions
MUST_BE_A_MUSICIAN = "must be a musician"

View File

@ -8,7 +8,6 @@ module JamRuby
self.primary_key = 'id'
attr_accessible :version, :uri, :sha1, :environment, :product, as: :admin
mount_uploader :uri, ArtifactUploader
validates :version, :presence => true

View File

@ -1,12 +1,7 @@
module JamRuby
class ClaimedRecording < ActiveRecord::Base
validates :name, no_profanity: true, length: {minimum: 3, maximum: 64}, presence: true
validates :description, no_profanity: true, length: {maximum: 8000}
validates :is_public, :inclusion => {:in => [true, false]}
validates :is_downloadable, :inclusion => {:in => [true, false]}
validates :genre, presence: true
validates_uniqueness_of :recording_id, :scope => :user_id
attr_accessible :name, :description, :is_public, :is_downloadable, :genre_id, :recording_id, :user_id, as: :admin
belongs_to :recording, :class_name => "JamRuby::Recording", :inverse_of => :claimed_recordings
belongs_to :user, :class_name => "JamRuby::User", :inverse_of => :claimed_recordings
@ -15,10 +10,26 @@ module JamRuby
has_many :playing_sessions, :class_name => "JamRuby::MusicSession"
has_one :share_token, :class_name => "JamRuby::ShareToken", :inverse_of => :shareable, :foreign_key => 'shareable_id'
validates :name, no_profanity: true, length: {minimum: 3, maximum: 64}, presence: true
validates :description, no_profanity: true, length: {maximum: 8000}
validates :is_public, :inclusion => {:in => [true, false]}
validates :is_downloadable, :inclusion => {:in => [true, false]}
validates :genre, presence: true
validates :user, presence: true
validates_uniqueness_of :user_id, :scope => :recording_id
validate :user_belongs_to_recording
before_create :generate_share_token
SHARE_TOKEN_LENGTH = 8
def user_belongs_to_recording
if user && recording && !recording.users.exists?(user)
errors.add(:user, ValidationMessages::NOT_PART_OF_RECORDING)
end
end
# user must own this object
# params is a hash, and everything is optional
def update_fields(user, params)

View File

@ -7,8 +7,27 @@ module JamRuby
before_destroy :delete_s3_files
self.primary_key = 'id'
belongs_to :recording, :class_name => "JamRuby::Recording", :inverse_of => :mixes
attr_accessible :ogg_url, :should_retry, as: :admin
attr_writer :is_skip_mount_uploader
belongs_to :recording, :class_name => "JamRuby::Recording", :inverse_of => :mixes, :foreign_key => 'recording_id'
mount_uploader :ogg_url, MixUploader
before_validation do
# this should be an activeadmin only path, because it's using the mount_uploader (whereas the client does something completely different)
if ogg_url.present? && ogg_url.respond_to?(:file) && ogg_url_changed?
self.ogg_length = ogg_url.file.size
self.ogg_md5 = ogg_url.md5
self.completed = true
self.started_at = Time.now
self.completed_at = Time.now
# do not set marking_complete = true; use of marking_complete is a client-centric design,
# and setting to true causes client-centric validations
end
end
def self.schedule(recording)
@ -17,8 +36,8 @@ module JamRuby
mix = Mix.new
mix.recording = recording
mix.save
mix.ogg_url = construct_filename(mix.created_at, recording.id, mix.id, type='ogg')
mix.mp3_url = construct_filename(mix.created_at, recording.id, mix.id, type='mp3')
mix[:ogg_url] = construct_filename(mix.created_at, recording.id, mix.id, type='ogg')
mix[:mp3_url] = construct_filename(mix.created_at, recording.id, mix.id, type='mp3')
if mix.save
mix.enqueue
end
@ -83,9 +102,9 @@ module JamRuby
def s3_url(type='ogg')
if type == 'ogg'
s3_manager.s3_url(ogg_url)
s3_manager.s3_url(self[:ogg_url])
else
s3_manager.s3_url(mp3_url)
s3_manager.s3_url(self[:mp3_url])
end
@ -98,31 +117,33 @@ module JamRuby
def sign_url(expiration_time = 120, type='ogg')
# expire link in 1 minute--the expectation is that a client is immediately following this link
if type == 'ogg'
s3_manager.sign_url(self.ogg_url, {:expires => expiration_time, :response_content_type => 'audio/ogg', :secure => false})
s3_manager.sign_url(self[:ogg_url], {:expires => expiration_time, :response_content_type => 'audio/ogg', :secure => false})
else
s3_manager.sign_url(self.mp3_url, {:expires => expiration_time, :response_content_type => 'audio/mp3', :secure => false})
s3_manager.sign_url(self[:mp3_url], {:expires => expiration_time, :response_content_type => 'audio/mp3', :secure => false})
end
end
def sign_put(expiration_time = 3600 * 24, type='ogg')
if type == 'ogg'
s3_manager.sign_url(self.ogg_url, {:expires => expiration_time, :content_type => 'audio/ogg', :secure => false}, :put)
s3_manager.sign_url(self[:ogg_url], {:expires => expiration_time, :content_type => 'audio/ogg', :secure => false}, :put)
else
s3_manager.sign_url(self.mp3_url, {:expires => expiration_time, :content_type => 'audio/mp3', :secure => false}, :put)
s3_manager.sign_url(self[:mp3_url], {:expires => expiration_time, :content_type => 'audio/mp3', :secure => false}, :put)
end
end
def filename(type='ogg')
# construct a path for s3
Mix.construct_filename(self.created_at, self.recording_id, self.id, type)
end
private
def delete_s3_files
s3_manager.delete(filename(type='ogg'))
s3_manager.delete(filename(type='mp3'))
s3_manager.delete(filename(type='ogg')) if self[:ogg_url]
s3_manager.delete(filename(type='mp3')) if self[:mp3_url]
end
def filename(type='ogg')
# construct a path for s3
Mix.construct_filename(self.created_at, self.recording.id, self.id, type)
end
def self.construct_filename(created_at, recording_id, id, type='ogg')
raise "unknown ID" unless id

View File

@ -8,12 +8,17 @@ module JamRuby
self.table_name = "recorded_tracks"
self.primary_key = 'id'
attr_accessible :discard
attr_accessor :marking_complete
attr_writer :is_skip_mount_uploader
attr_accessible :discard, :user, :user_id, :instrument_id, :sound, :client_id, :track_id, :client_track_id, :url, as: :admin
SOUND = %w(mono stereo)
MAX_PART_FAILURES = 3
MAX_UPLOAD_FAILURES = 10
mount_uploader :url, RecordedTrackUploader
belongs_to :user, :class_name => "JamRuby::User", :inverse_of => :recorded_tracks
belongs_to :recording, :class_name => "JamRuby::Recording", :inverse_of => :recorded_tracks
belongs_to :instrument, :class_name => "JamRuby::Instrument"
@ -24,12 +29,28 @@ module JamRuby
validates :client_track_id, :presence => true
validates :md5, :presence => true, :if => :upload_starting?
validates :length, length: {minimum: 1, maximum: 1024 * 1024 * 256 }, if: :upload_starting? # 256 megs max. is this reasonable? surely...
validates :user, presence: true
validates :instrument, presence: true
before_destroy :delete_s3_files # DO THIS TOMORROW
before_destroy :delete_s3_files
validate :validate_fully_uploaded
validate :validate_part_complete
validate :validate_too_many_upload_failures
before_save :sanitize_active_admin
skip_callback :save, :before, :store_picture!, if: :is_skip_mount_uploader?
before_validation do
# this should be an activeadmin only path, because it's using the mount_uploader (whereas the client does something completely different)
if url.present? && url.respond_to?(:file) && url_changed?
self.length = url.file.size
self.md5 = url.md5
self.fully_uploaded = true
# do not set marking_complete = true; use of marking_complete is a client-centric design,
# and setting to true causes client-centric validations
end
end
def musician
self.user
end
@ -49,12 +70,11 @@ module JamRuby
end
def validate_fully_uploaded
if fully_uploaded && fully_uploaded_was
if marking_complete && fully_uploaded && fully_uploaded_was
errors.add(:fully_uploaded, ValidationMessages::ALREADY_UPLOADED)
end
end
def validate_part_complete
# if we see a transition from is_part_uploading from true to false, we validate
@ -77,6 +97,10 @@ module JamRuby
end
end
def sanitize_active_admin
self.user_id = nil if self.user_id == ''
end
# Copy an ephemeral track to create a saved one. Some fields are ok with defaults
def self.create_from_track(track, recording)
recorded_track = self.new
@ -89,14 +113,16 @@ module JamRuby
recorded_track.sound = track.sound
recorded_track.next_part_to_upload = 0
recorded_track.file_offset = 0
recorded_track.is_skip_mount_uploader = true
recorded_track.save
recorded_track.url = construct_filename(recorded_track.created_at, recording.id, track.client_track_id)
recorded_track.save
recorded_track.is_skip_mount_uploader = false
recorded_track
end
def sign_url(expiration_time = 120)
s3_manager.sign_url(self.url, {:expires => expiration_time, :response_content_type => 'audio/ogg', :secure => false})
s3_manager.sign_url(self[:url], {:expires => expiration_time, :response_content_type => 'audio/ogg', :secure => false})
end
def upload_start(length, md5)
@ -147,6 +173,7 @@ module JamRuby
def upload_complete
# validate from happening twice by :validate_fully_uploaded
self.fully_uploaded = true
self.marking_complete = true
save
end
@ -155,15 +182,15 @@ module JamRuby
RecordedTrack.update_all("part_failures = #{self.part_failures}", "id = '#{self.id}'")
end
def filename
# construct a path from s3
RecordedTrack.construct_filename(self.created_at, self.recording.id, self.client_track_id)
end
private
def delete_s3_files
s3_manager.delete(url)
end
def filename
# construct a path for s3
RecordedTrack.construct_filename(self.created_at, self.recording.id, self.client_track_id)
s3_manager.delete(self[:url]) if self[:url]
end
def self.construct_filename(created_at, recording_id, client_track_id)

View File

@ -25,7 +25,7 @@ module JamRuby
end
# if we detect that this just became fully uploaded -- if so, tell s3 to put the parts together
if !recorded_track.fully_uploaded_was && recorded_track.fully_uploaded
if recorded_track.marking_complete && !recorded_track.fully_uploaded_was && recorded_track.fully_uploaded
multipart_success = false
begin

View File

@ -3,24 +3,34 @@ module JamRuby
self.primary_key = 'id'
attr_accessible :name, :description, :genre, :is_public, :is_downloadable
attr_accessible :owner, :owner_id, :band, :band_id, :recorded_tracks_attributes, :mixes_attributes, :claimed_recordings_attributes, :name, :description, :genre, :is_public, :is_downloadable, as: :admin
has_many :claimed_recordings, :class_name => "JamRuby::ClaimedRecording", :inverse_of => :recording
has_many :claimed_recordings, :class_name => "JamRuby::ClaimedRecording", :inverse_of => :recording, :foreign_key => 'recording_id', :dependent => :destroy
has_many :users, :through => :recorded_tracks, :class_name => "JamRuby::User"
has_many :mixes, :class_name => "JamRuby::Mix", :inverse_of => :recording
has_many :recorded_tracks, :class_name => "JamRuby::RecordedTrack", :foreign_key => :recording_id
has_many :mixes, :class_name => "JamRuby::Mix", :inverse_of => :recording, :foreign_key => 'recording_id', :dependent => :destroy
has_many :recorded_tracks, :class_name => "JamRuby::RecordedTrack", :foreign_key => :recording_id, :dependent => :destroy
has_many :comments, :class_name => "JamRuby::RecordingComment", :foreign_key => "recording_id"
has_many :likes, :class_name => "JamRuby::RecordingLiker", :foreign_key => "recording_id"
has_many :plays, :class_name => "JamRuby::RecordingPlay", :foreign_key => "recording_id"
belongs_to :owner, :class_name => "JamRuby::User", :inverse_of => :owned_recordings
belongs_to :owner, :class_name => "JamRuby::User", :inverse_of => :owned_recordings, :foreign_key => 'owner_id'
belongs_to :band, :class_name => "JamRuby::Band", :inverse_of => :recordings
belongs_to :music_session, :class_name => "JamRuby::MusicSession", :inverse_of => :recordings
accepts_nested_attributes_for :recorded_tracks, :mixes, :claimed_recordings, allow_destroy: true
validate :not_already_recording, :on => :create
validate :not_still_finalizing_previous, :on => :create
validate :not_playback_recording, :on => :create
validate :already_stopped_recording
validate :only_one_mix
before_save :sanitize_active_admin
def sanitize_active_admin
self.owner_id = nil if self.owner_id == ''
self.band_id = nil if self.band_id == ''
end
def comment_count
self.comments.size
@ -74,6 +84,14 @@ module JamRuby
end
end
def only_one_mix
# we leave mixes as has_many because VRFS-1089 was very hard to do with has_one + cocoon add/remove
if mixes.length > 1
errors.add(:mixes, ValidationMessages::ONLY_ONE_MIX)
end
end
def recorded_tracks_for_user(user)
unless self.users.exists?(user)
raise PermissionError, "user was not in this session"
@ -195,7 +213,7 @@ module JamRuby
:recording_id => recorded_track.recording_id,
:length => recorded_track.length,
:md5 => recorded_track.md5,
:url => recorded_track.url,
:url => recorded_track[:url],
:next => recorded_track.id
}
)

View File

@ -38,7 +38,7 @@ module JamRuby
has_many :bands, :through => :band_musicians, :class_name => "JamRuby::Band"
# recordings
has_many :owned_recordings, :class_name => "JamRuby::Recording"
has_many :owned_recordings, :class_name => "JamRuby::Recording", :foreign_key => 'owner_id'
has_many :recordings, :through => :claimed_recordings, :class_name => "JamRuby::Recording"
has_many :claimed_recordings, :class_name => "JamRuby::ClaimedRecording", :inverse_of => :user
has_many :playing_claimed_recordings, :class_name => "JamRuby::MusicSession", :inverse_of => :claimed_recording_initiator
@ -212,7 +212,7 @@ module JamRuby
end
def name
return "#{first_name} #{last_name}"
"#{first_name} #{last_name}"
end
def location
@ -1126,6 +1126,16 @@ module JamRuby
iu
end
# both email and name helps someone understand/recall/verify who they are looking at
def autocomplete_display_name
"#{email} (#{name})"
end
# used by formtastic for display
def to_label
autocomplete_display_name
end
# devise compatibility
#def encrypted_password

View File

@ -109,7 +109,7 @@ describe ClaimedRecording do
make_claim
duplicate = @recording.claim(@user, "name", "description", @genre, true, true)
duplicate.valid?.should be_false
duplicate.errors[:recording_id].should == ['has already been taken']
duplicate.errors[:user_id].should == ['has already been taken']
end
end

View File

@ -41,8 +41,8 @@ describe RecordedTrack do
it "gets a url for the track" do
@recorded_track = RecordedTrack.create_from_track(@track, @recording)
@recorded_track.save.should be_true
@recorded_track.url.should == "recordings/#{@recorded_track.created_at.strftime('%m-%d-%Y')}/#{@recording.id}/track-#{@track.client_track_id}.ogg"
@recorded_track.errors.any?.should be_false
@recorded_track[:url].should == "recordings/#{@recorded_track.created_at.strftime('%m-%d-%Y')}/#{@recording.id}/track-#{@track.client_track_id}.ogg"
end
it "signs url" do

View File

@ -24,9 +24,8 @@ describe Recording do
user2_recorded_tracks.length.should == 1
user2_recorded_tracks[0].should == user2.recorded_tracks[0]
RecordedTrack.update(user1_recorded_tracks, :discard => true)
user1_recorded_tracks[0].reload
user1_recorded_tracks[0].discard.should be_true
user1_recorded_tracks[0].discard = true
user1_recorded_tracks[0].save!
end
it "should set up the recording properly when recording is started with 1 user in the session" do

View File

@ -1,3 +1,4 @@
require 'simplecov'
require 'support/utilities'
require 'active_record'
require 'jam_db'

View File

@ -77,6 +77,29 @@ def app_config
"#{external_protocol}#{external_hostname}#{(external_port == 80 || external_port == 443) ? '' : ':' + external_port.to_s}"
end
def aws_access_key_id
'AKIAJESQY24TOT542UHQ'
end
def aws_secret_access_key
'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'
end
def aws_region
'us-east-1'
end
def aws_bucket
'jamkazam-testing'
end
def aws_bucket_public
'jamkazam-testing-public'
end
def aws_cache
'315576000'
end
private

View File

@ -208,10 +208,10 @@ class ApiMusicSessionsController < ApiController
return
end
if SampleApp::Application.config.storage_type == :fog
if Rails.application.config.storage_type == :fog
uri = @perfdata.uri
s3 = AWS::S3.new(:access_key_id => SampleApp::Application.config.aws_access_key_id,
:secret_access_key => SampleApp::Application.config.aws_secret_access_key)
s3 = AWS::S3.new(:access_key_id => Rails.application.config.aws_access_key_id,
:secret_access_key => Rails.application.config.aws_secret_access_key)
bucket = s3.buckets[SampleApp::Application.config.aws_bucket]
expire = Time.now + 20.years

View File

@ -490,12 +490,11 @@ class ApiUsersController < ApiController
end
# This part is the piece that really needs to be decomposed into a library...
if SampleApp::Application.config.storage_type == :fog
s3 = AWS::S3.new(:access_key_id => SampleApp::Application.config.aws_access_key_id,
:secret_access_key => SampleApp::Application.config.aws_secret_access_key)
# Fixme: Should we use the same bucket for everything?
bucket = s3.buckets[SampleApp::Application.config.aws_bucket]
url = bucket.objects[@dump.uri].url_for(:write, :expires => SampleApp::Application.config.crash_dump_data_signed_url_timeout, :'response_content_type' => 'application/octet-stream').to_s
if Rails.application.config.storage_type == :fog
s3 = AWS::S3.new(:access_key_id => Rails.application.config.aws_access_key_id,
:secret_access_key => Rails.application.config.aws_secret_access_key)
bucket = s3.buckets[Rails.application.config.aws_bucket]
url = bucket.objects[@dump.uri].url_for(:write, :expires => Rails.application.config.crash_dump_data_signed_url_timeout, :'response_content_type' => 'application/octet-stream').to_s
logger.debug("crash_dump can upload to url #{url}")

View File

@ -24,11 +24,23 @@ child(:recording => :recording) {
}
child(:mixes => :mixes) {
attributes :id, :mp3_url, :ogg_url, :is_completed
attributes :id, :is_completed
node :mp3_url do |mix|
mix[:url]
end
node :ogg_url do |mix|
mix[:url]
end
}
child(:recorded_tracks => :recorded_tracks) {
attributes :id, :fully_uploaded, :url, :client_track_id, :client_id, :instrument_id
attributes :id, :fully_uploaded, :client_track_id, :client_id, :instrument_id
node :url do |recorded_track|
recorded_track[:url]
end
child(:user => :user) {
attributes :id, :first_name, :last_name, :name, :city, :state, :country, :photo_url

View File

@ -59,11 +59,23 @@ node(:claimed_recording, :if => lambda { |music_session| music_session.users.exi
}
child(:mixes => :mixes) {
attributes :id, :mp3_url, :ogg_url, :is_completed
attributes :id, :is_completed
node :mp3_url do |mix|
mix[:url]
end
node :ogg_url do |mix|
mix[:url]
end
}
child(:recorded_tracks => :recorded_tracks) {
attributes :id, :fully_uploaded, :url, :client_track_id, :client_id, :instrument_id
attributes :id, :fully_uploaded, :client_track_id, :client_id, :instrument_id
node :url do |recorded_track|
recorded_track[:url]
end
child(:user => :user) {
attributes :id, :first_name, :last_name, :city, :state, :country, :photo_url

View File

@ -3,7 +3,11 @@ object @recording
attributes :id, :band, :created_at, :duration, :comment_count, :like_count, :play_count
child(:recorded_tracks => :recorded_tracks) {
attributes :id, :fully_uploaded, :url, :client_track_id, :client_id, :instrument_id
attributes :id, :fully_uploaded, :client_track_id, :client_id, :instrument_id
node :url do |recorded_track|
recorded_track[:url]
end
child(:user => :user) {
attributes :id, :first_name, :last_name, :city, :state, :country, :photo_url

View File

@ -4,16 +4,8 @@ CarrierWave.root = Rails.root.join(Rails.public_path).to_s
CarrierWave.base_path = ENV['RAILS_RELATIVE_URL_ROOT']
CarrierWave.configure do |config|
config.storage = SampleApp::Application.config.storage_type
config.fog_credentials = {
:provider => 'AWS',
:aws_access_key_id => SampleApp::Application.config.aws_access_key_id,
:aws_secret_access_key => SampleApp::Application.config.aws_secret_access_key,
:region => SampleApp::Application.config.aws_region,
}
config.fog_directory = SampleApp::Application.config.aws_bucket_public # required
config.fog_public = true # optional, defaults to true
config.fog_attributes = {'Cache-Control'=>"max-age=#{SampleApp::Application.config.aws_cache}"} # optional, defaults to {}
config.storage = Rails.application.config.storage_type
JamRuby::UploaderConfiguration.set_aws_private_configuration(config)
end

View File

@ -35,7 +35,7 @@ describe ApiClaimedRecordingsController do
json["recording"]["band"].should be_nil
json["recording"]["recorded_tracks"].length.should == 1
json["recording"]["recorded_tracks"].first["id"].should == @recording.recorded_tracks.first.id
json["recording"]["recorded_tracks"].first["url"].should == @recording.recorded_tracks.first.url
json["recording"]["recorded_tracks"].first["url"].should == @recording.recorded_tracks.first[:url]
json["recording"]["recorded_tracks"].first["instrument_id"].should == @instrument.id
json["recording"]["recorded_tracks"].first["user"]["id"].should == @user.id
end