Merge and fix
This commit is contained in:
parent
e71b82b15b
commit
766568f8cb
|
|
@ -41,7 +41,7 @@ gem 'uuidtools', '2.1.2'
|
||||||
gem 'jquery-rails' # , '2.3.0' # pinned because jquery-ui-rails was split from jquery-rails, but activeadmin doesn't support this gem yet
|
gem 'jquery-rails' # , '2.3.0' # pinned because jquery-ui-rails was split from jquery-rails, but activeadmin doesn't support this gem yet
|
||||||
gem 'jquery-ui-rails', '4.2.1'
|
gem 'jquery-ui-rails', '4.2.1'
|
||||||
gem 'rails3-jquery-autocomplete'
|
gem 'rails3-jquery-autocomplete'
|
||||||
gem 'activeadmin', '0.6.2'
|
gem 'activeadmin' #, github: 'activeadmin', branch: '0-6-stable'
|
||||||
gem 'mime-types', '1.25'
|
gem 'mime-types', '1.25'
|
||||||
gem 'meta_search'
|
gem 'meta_search'
|
||||||
gem 'fog', "~> 1.18.0"
|
gem 'fog', "~> 1.18.0"
|
||||||
|
|
@ -49,7 +49,7 @@ gem 'unf', '0.1.3' #optional fog dependency
|
||||||
gem 'country-select'
|
gem 'country-select'
|
||||||
gem 'aasm', '3.0.16'
|
gem 'aasm', '3.0.16'
|
||||||
gem 'postgres-copy', '0.6.0'
|
gem 'postgres-copy', '0.6.0'
|
||||||
gem 'aws-sdk' #, '1.29.1'
|
gem 'aws-sdk', '~> 1'
|
||||||
gem 'bugsnag'
|
gem 'bugsnag'
|
||||||
gem 'gon'
|
gem 'gon'
|
||||||
gem 'cocoon'
|
gem 'cocoon'
|
||||||
|
|
@ -74,6 +74,7 @@ gem 'sanitize'
|
||||||
gem 'slim'
|
gem 'slim'
|
||||||
gem 'influxdb', '0.1.8'
|
gem 'influxdb', '0.1.8'
|
||||||
gem 'influxdb-rails', '0.1.10'
|
gem 'influxdb-rails', '0.1.10'
|
||||||
|
gem 'recurly'
|
||||||
|
|
||||||
group :libv8 do
|
group :libv8 do
|
||||||
gem 'libv8', "~> 3.11.8"
|
gem 'libv8', "~> 3.11.8"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
ActiveAdmin.register Cohort, :as => 'Cohorts' do
|
||||||
|
|
||||||
|
menu :label => 'Cohorts All-time', :parent => 'Reports'
|
||||||
|
|
||||||
|
config.sort_order = 'group_start_desc'
|
||||||
|
config.batch_actions = false
|
||||||
|
config.clear_action_items!
|
||||||
|
config.filters = false
|
||||||
|
config.per_page = 50
|
||||||
|
|
||||||
|
controller do
|
||||||
|
|
||||||
|
def scoped_collection
|
||||||
|
objs = super
|
||||||
|
Cohort.alltime_cohorts!
|
||||||
|
objs.where(all_time: true).order('group_start DESC')
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
index :title => "All-Time Cohorts" do
|
||||||
|
column 'Cohort' do |cc|
|
||||||
|
div(class: :cohort_col) { cc.group_start_str }
|
||||||
|
end
|
||||||
|
|
||||||
|
column Cohort::ALLTIME_LABELS[:registered_users] do |cc|
|
||||||
|
div(class: :cohort_col) { cc.data_val(:registered_users) }
|
||||||
|
end
|
||||||
|
|
||||||
|
column Cohort::ALLTIME_LABELS[:first_downloaded_client_at] do |cc|
|
||||||
|
div(class: :cohort_col) { cc.data_val(:first_downloaded_client_at) }
|
||||||
|
end
|
||||||
|
column '%' do |cc| div(class: :cohort_col) { cc.data_val(:first_downloaded_client_at, true) } end
|
||||||
|
|
||||||
|
column Cohort::ALLTIME_LABELS[:first_certified_gear_at] do |cc|
|
||||||
|
div(class: :cohort_col) { cc.data_val(:first_certified_gear_at) }
|
||||||
|
end
|
||||||
|
column '%' do |cc| div(class: :cohort_col) { cc.data_val(:first_certified_gear_at, true) } end
|
||||||
|
|
||||||
|
column Cohort::ALLTIME_LABELS[:music_sessions_user_history] do |cc|
|
||||||
|
div(class: :cohort_col) { cc.data_val(:music_sessions_user_history) }
|
||||||
|
end
|
||||||
|
column '%' do |cc| div(class: :cohort_col) { cc.data_val(:music_sessions_user_history, true) } end
|
||||||
|
|
||||||
|
column Cohort::ALLTIME_LABELS[:jam_tracks_played] do |cc|
|
||||||
|
div(class: :cohort_col) { cc.data_val(:jam_tracks_played) }
|
||||||
|
end
|
||||||
|
column '%' do |cc| div(class: :cohort_col) { cc.data_val(:jam_tracks_played, true) } end
|
||||||
|
|
||||||
|
column Cohort::ALLTIME_LABELS[:jam_track_rights] do |cc|
|
||||||
|
div(class: :cohort_col) { cc.data_val(:jam_track_rights) }
|
||||||
|
end
|
||||||
|
column '%' do |cc| div(class: :cohort_col) { cc.data_val(:jam_track_rights, true) } end
|
||||||
|
|
||||||
|
column Cohort::ALLTIME_LABELS[:recorded_tracks] do |cc|
|
||||||
|
div(class: :cohort_col) { cc.data_val(:recorded_tracks) }
|
||||||
|
end
|
||||||
|
column '%' do |cc| div(class: :cohort_col) { cc.data_val(:recorded_tracks, true) } end
|
||||||
|
|
||||||
|
column Cohort::ALLTIME_LABELS[:friendships] do |cc|
|
||||||
|
div(class: :cohort_col) { cc.data_val(:friendships) }
|
||||||
|
end
|
||||||
|
column '%' do |cc| div(class: :cohort_col) { cc.data_val(:friendships, true) } end
|
||||||
|
|
||||||
|
column Cohort::ALLTIME_LABELS[:invited_users] do |cc|
|
||||||
|
div(class: :cohort_col) { cc.data_val(:invited_users) }
|
||||||
|
end
|
||||||
|
column '%' do |cc| div(class: :cohort_col) { cc.data_val(:invited_users, true) } end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
ActiveAdmin.register Cohort, :as => 'Cohorts Monthly' do
|
||||||
|
|
||||||
|
menu :label => 'Cohorts Monthly', :parent => 'Reports'
|
||||||
|
|
||||||
|
config.sort_order = 'group_start_desc'
|
||||||
|
config.batch_actions = false
|
||||||
|
config.clear_action_items!
|
||||||
|
config.per_page = 50
|
||||||
|
|
||||||
|
filter(:monthly_start, as: :select, collection: Cohort.monthly_starts)
|
||||||
|
|
||||||
|
controller do
|
||||||
|
def scoped_collection
|
||||||
|
args = params[:q] || {}
|
||||||
|
Cohort.monthly_cohorts!(Time.parse(args[:monthly_start_eq])) if ! args[:monthly_start_eq].nil?
|
||||||
|
super.where(all_time: false).order('group_start DESC')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
index :title => proc { "Monthly Cohorts #{params[:q] ? '('+Time.parse(params[:q][:monthly_start_eq]).strftime('%Y-%m')+')' : ''}" } do
|
||||||
|
|
||||||
|
column 'Cohort' do |cc|
|
||||||
|
div(class: :cohort_col) { cc.group_start_str }
|
||||||
|
end
|
||||||
|
|
||||||
|
column Cohort::MONTHLY_LABELS[:registered_users] do |cc|
|
||||||
|
div(class: :cohort_col) { cc.data_val(:registered_users) }
|
||||||
|
end
|
||||||
|
|
||||||
|
column Cohort::MONTHLY_LABELS[:first_downloaded_client_at] do |cc|
|
||||||
|
div(class: :cohort_col) { cc.data_val(:first_downloaded_client_at) }
|
||||||
|
end
|
||||||
|
column '%' do |cc| div(class: :cohort_col) { cc.data_val(:first_downloaded_client_at, true) } end
|
||||||
|
|
||||||
|
column Cohort::MONTHLY_LABELS[:first_certified_gear_at] do |cc|
|
||||||
|
div(class: :cohort_col) { cc.data_val(:first_certified_gear_at) }
|
||||||
|
end
|
||||||
|
column '%' do |cc| div(class: :cohort_col) { cc.data_val(:first_certified_gear_at, true) } end
|
||||||
|
|
||||||
|
column Cohort::MONTHLY_LABELS[:music_sessions_user_history_1] do |cc|
|
||||||
|
div(class: :cohort_col) { cc.data_val(:music_sessions_user_history_1) }
|
||||||
|
end
|
||||||
|
column '%' do |cc| div(class: :cohort_col) { cc.data_val(:music_sessions_user_history_1, true) } end
|
||||||
|
|
||||||
|
column Cohort::MONTHLY_LABELS[:music_sessions_user_history_2_5] do |cc|
|
||||||
|
div(class: :cohort_col) { cc.data_val(:music_sessions_user_history_2_5) }
|
||||||
|
end
|
||||||
|
column '%' do |cc| div(class: :cohort_col) { cc.data_val(:music_sessions_user_history_2_5, true) } end
|
||||||
|
|
||||||
|
column Cohort::MONTHLY_LABELS[:music_sessions_user_history_6_] do |cc|
|
||||||
|
div(class: :cohort_col) { cc.data_val(:music_sessions_user_history_6_) }
|
||||||
|
end
|
||||||
|
column '%' do |cc| div(class: :cohort_col) { cc.data_val(:music_sessions_user_history_6_, true) } end
|
||||||
|
|
||||||
|
column Cohort::MONTHLY_LABELS[:jam_tracks_played_1] do |cc|
|
||||||
|
div(class: :cohort_col) { cc.data_val(:jam_tracks_played_1) }
|
||||||
|
end
|
||||||
|
column '%' do |cc| div(class: :cohort_col) { cc.data_val(:jam_tracks_played_1, true) } end
|
||||||
|
|
||||||
|
column Cohort::MONTHLY_LABELS[:jam_tracks_played_2_5] do |cc|
|
||||||
|
div(class: :cohort_col) { cc.data_val(:jam_tracks_played_2_5) }
|
||||||
|
end
|
||||||
|
column '%' do |cc| div(class: :cohort_col) { cc.data_val(:jam_tracks_played_2_5, true) } end
|
||||||
|
|
||||||
|
column Cohort::MONTHLY_LABELS[:jam_tracks_played_6_] do |cc|
|
||||||
|
div(class: :cohort_col) { cc.data_val(:jam_tracks_played_6_) }
|
||||||
|
end
|
||||||
|
column '%' do |cc| div(class: :cohort_col) { cc.data_val(:jam_tracks_played_6_, true) } end
|
||||||
|
|
||||||
|
column Cohort::MONTHLY_LABELS[:jam_track_rights_redeemed] do |cc|
|
||||||
|
div(class: :cohort_col) { cc.data_val(:jam_track_rights_redeemed) }
|
||||||
|
end
|
||||||
|
column '%' do |cc| div(class: :cohort_col) { cc.data_val(:jam_track_rights_redeemed, true) } end
|
||||||
|
|
||||||
|
column Cohort::MONTHLY_LABELS[:jam_track_rights] do |cc|
|
||||||
|
div(class: :cohort_col) { cc.data_val(:jam_track_rights) }
|
||||||
|
end
|
||||||
|
column '%' do |cc| div(class: :cohort_col) { cc.data_val(:jam_track_rights, true) } end
|
||||||
|
|
||||||
|
column Cohort::MONTHLY_LABELS[:recorded_tracks] do |cc|
|
||||||
|
div(class: :cohort_col) { cc.data_val(:recorded_tracks) }
|
||||||
|
end
|
||||||
|
column '%' do |cc| div(class: :cohort_col) { cc.data_val(:recorded_tracks, true) } end
|
||||||
|
|
||||||
|
column Cohort::MONTHLY_LABELS[:friendships] do |cc|
|
||||||
|
div(class: :cohort_col) { cc.data_val(:friendships) }
|
||||||
|
end
|
||||||
|
column '%' do |cc| div(class: :cohort_col) { cc.data_val(:friendships, true) } end
|
||||||
|
|
||||||
|
column Cohort::MONTHLY_LABELS[:invited_users] do |cc|
|
||||||
|
div(class: :cohort_col) { cc.data_val(:invited_users) }
|
||||||
|
end
|
||||||
|
column '%' do |cc| div(class: :cohort_col) { cc.data_val(:invited_users, true) } end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
@ -3,7 +3,7 @@ ActiveAdmin.register JamRuby::CrashDump, :as => 'Crash Dump' do
|
||||||
filter :timestamp
|
filter :timestamp
|
||||||
filter :user_email, :as => :string
|
filter :user_email, :as => :string
|
||||||
filter :client_id
|
filter :client_id
|
||||||
menu :parent => 'Debug'
|
menu :parent => 'Misc'
|
||||||
|
|
||||||
index do
|
index do
|
||||||
column "Timestamp" do |post|
|
column "Timestamp" do |post|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
ActiveAdmin.register JamRuby::EmailBatch, :as => 'Batch Emails' do
|
ActiveAdmin.register JamRuby::EmailBatch, :as => 'Batch Emails' do
|
||||||
|
|
||||||
menu :label => 'Batch Emails', :parent => 'Email'
|
menu :label => 'Batch Emails', :parent => 'Misc'
|
||||||
|
|
||||||
config.sort_order = 'updated_at DESC'
|
config.sort_order = 'updated_at DESC'
|
||||||
config.batch_actions = false
|
config.batch_actions = false
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
ActiveAdmin.register JamRuby::EmailBatchScheduledSessions, :as => 'Daily Sessions' do
|
ActiveAdmin.register JamRuby::EmailBatchScheduledSessions, :as => 'Daily Sessions' do
|
||||||
|
|
||||||
menu :label => 'Daily Sessions', :parent => 'Email'
|
menu :label => 'Daily Sessions', :parent => 'Misc'
|
||||||
|
|
||||||
config.sort_order = 'updated_at DESC'
|
config.sort_order = 'updated_at DESC'
|
||||||
config.filters = false
|
config.filters = false
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
ActiveAdmin.register JamRuby::Event, :as => 'Event' do
|
ActiveAdmin.register JamRuby::Event, :as => 'Event' do
|
||||||
menu :parent => 'Events'
|
menu :parent => 'Misc'
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
ActiveAdmin.register JamRuby::EventSession, :as => 'Event Session' do
|
ActiveAdmin.register JamRuby::EventSession, :as => 'Event Session' do
|
||||||
menu :parent => 'Events'
|
menu :parent => 'Misc'
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
ActiveAdmin.register_page "Fake Purchaser" do
|
||||||
|
menu :parent => 'Misc'
|
||||||
|
|
||||||
|
|
||||||
|
page_action :bulk_jamtrack_purchase, :method => :post do
|
||||||
|
|
||||||
|
puts params.inspect
|
||||||
|
|
||||||
|
user_field = params[:jam_ruby_jam_track_right][:user]
|
||||||
|
|
||||||
|
if user_field.blank?
|
||||||
|
redirect_to admin_fake_purchaser_path, :notice => "user not specified"
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
bits = user_field.strip.split(' ')
|
||||||
|
|
||||||
|
user = User.find_by_email(bits[0])
|
||||||
|
if user.nil?
|
||||||
|
redirect_to admin_fake_purchaser_path, :notice =>"no user with email #{bits[0]}"
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if !user.admin
|
||||||
|
redirect_to admin_fake_purchaser_path, :notice =>"user is not admin"
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
JamTrack.all.each do |jam_track|
|
||||||
|
unless jam_track.right_for_user(user)
|
||||||
|
|
||||||
|
jam_track_right=JamTrackRight.new
|
||||||
|
jam_track_right.user = user
|
||||||
|
jam_track_right.jam_track = jam_track
|
||||||
|
jam_track_right.is_test_purchase = true
|
||||||
|
jam_track_right.save!
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
redirect_to admin_fake_purchaser_path, :notice => "Bought #{count} jamtracks for #{user.email}"
|
||||||
|
end
|
||||||
|
|
||||||
|
content do
|
||||||
|
|
||||||
|
semantic_form_for JamTrackRight.new, :url => admin_fake_purchaser_bulk_jamtrack_purchase_path, :builder => ActiveAdmin::FormBuilder do |f|
|
||||||
|
f.inputs "Admin User to Fake JamTrack Purchases" do
|
||||||
|
f.input :user, :as => :autocomplete, :url => autocomplete_user_email_admin_users_path, :input_html => { :id_element => "#jam_trak_right_user_id" }, hint: 'All JamTracks in the system will be \'bought\' for this user. No Recurly interaction occurs with this feature.'
|
||||||
|
end
|
||||||
|
f.actions
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -2,6 +2,6 @@ ActiveAdmin.register JamRuby::IspScoreBatch, :as => 'Isp Score Data' do
|
||||||
|
|
||||||
config.sort_order = 'created_at_desc'
|
config.sort_order = 'created_at_desc'
|
||||||
|
|
||||||
menu :parent => 'Debug'
|
menu :parent => 'Misc'
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
ActiveAdmin.register JamRuby::ArtifactUpdate, :as => 'Artifacts' do
|
ActiveAdmin.register JamRuby::ArtifactUpdate, :as => 'Artifacts' do
|
||||||
menu :label => 'Artifacts'
|
menu :label => 'Artifacts', :parent => 'Operations'
|
||||||
|
|
||||||
config.sort_order = 'product,environment'
|
config.sort_order = 'product,environment'
|
||||||
#config.batch_actions = false
|
#config.batch_actions = false
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,7 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do
|
||||||
User.select("email, first_name, last_name, id").where(["email ILIKE ? OR first_name ILIKE ? OR last_name ILIKE ?", "%#{parameters[:term]}%", "%#{parameters[:term]}%", "%#{parameters[:term]}%"])
|
User.select("email, first_name, last_name, id").where(["email ILIKE ? OR first_name ILIKE ? OR last_name ILIKE ?", "%#{parameters[:term]}%", "%#{parameters[:term]}%", "%#{parameters[:term]}%"])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@jam_ruby_user = JamRuby::User.new(params[:jam_ruby_user])
|
@jam_ruby_user = JamRuby::User.new(params[:jam_ruby_user])
|
||||||
@jam_ruby_user.administratively_created = true
|
@jam_ruby_user.administratively_created = true
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
require 'jam_ruby/recurly_client'
|
||||||
|
ActiveAdmin.register JamRuby::JamTrackRight, :as => 'JamTrackRights' do
|
||||||
|
|
||||||
|
menu :label => 'Purchased JamTracks', :parent => 'JamTracks'
|
||||||
|
|
||||||
|
config.sort_order = 'updated_at DESC'
|
||||||
|
config.batch_actions = false
|
||||||
|
|
||||||
|
#form :partial => 'form'
|
||||||
|
|
||||||
|
index do
|
||||||
|
default_actions
|
||||||
|
|
||||||
|
column "Order" do |right|
|
||||||
|
link_to("Place", order_admin_jam_track_right_path(right)) + " | " +
|
||||||
|
link_to("Refund", refund_admin_jam_track_right_path(right))
|
||||||
|
end
|
||||||
|
|
||||||
|
column "Last Name" do |right|
|
||||||
|
right.user.last_name
|
||||||
|
end
|
||||||
|
column "First Name" do |right|
|
||||||
|
right.user.first_name
|
||||||
|
end
|
||||||
|
column "Jam Track" do |right|
|
||||||
|
link_to(right.jam_track.name, admin_jam_track_right_path(right.jam_track))
|
||||||
|
# right.jam_track
|
||||||
|
end
|
||||||
|
column "Plan Code" do |right|
|
||||||
|
|
||||||
|
right.jam_track.plan_code
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
form do |f|
|
||||||
|
f.inputs 'New Jam Track Right' do
|
||||||
|
f.input :jam_track, :required=>true, collection: JamTrack.all, include_blank: false
|
||||||
|
f.input :user, :required=>true, collection: User.all, include_blank: false
|
||||||
|
end
|
||||||
|
f.actions
|
||||||
|
end
|
||||||
|
|
||||||
|
member_action :order, :method => :get do
|
||||||
|
right = JamTrackRight.where("id=?",params[:id]).first
|
||||||
|
user = right.user
|
||||||
|
jam_track = right.jam_track
|
||||||
|
client = RecurlyClient.new
|
||||||
|
billing_info = {
|
||||||
|
first_name: user.first_name,
|
||||||
|
last_name: user.last_name,
|
||||||
|
address1: 'Test Address 1',
|
||||||
|
address2: 'Test Address 2',
|
||||||
|
city: user.city,
|
||||||
|
state: user.state,
|
||||||
|
country: user.country,
|
||||||
|
zip: '12345',
|
||||||
|
number: '4111-1111-1111-1111',
|
||||||
|
month: '08',
|
||||||
|
year: '2017',
|
||||||
|
verification_value: '111'
|
||||||
|
}
|
||||||
|
|
||||||
|
begin
|
||||||
|
client.find_or_create_account(user, billing_info)
|
||||||
|
client.place_order(user, jam_track, nil, nil)
|
||||||
|
rescue RecurlyClientError=>x
|
||||||
|
redirect_to admin_jam_track_rights_path, notice: "Could not order #{jam_track} for #{user.to_s}: #{x.errors.inspect}"
|
||||||
|
else
|
||||||
|
redirect_to admin_jam_track_rights_path, notice: "Placed order of #{jam_track} for #{user.to_s}."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
member_action :refund, :method => :get do
|
||||||
|
right = JamTrackRight.where("id=?",params[:id]).first
|
||||||
|
client = RecurlyClient.new
|
||||||
|
|
||||||
|
begin
|
||||||
|
client.refund_user_subscription(right.user, right.jam_track)
|
||||||
|
rescue RecurlyClientError=>x
|
||||||
|
redirect_to admin_jam_track_rights_path, notice: "Could not issue refund on #{right.jam_track} for #{right.user.to_s}: #{x.errors.inspect}"
|
||||||
|
else
|
||||||
|
redirect_to admin_jam_track_rights_path, notice: "Issued full refund on #{right.jam_track} for #{right.user.to_s}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -22,29 +22,19 @@ ActiveAdmin.register JamRuby::JamTrack, :as => 'JamTracks' do
|
||||||
links
|
links
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
column :id
|
|
||||||
column :name
|
|
||||||
column :description
|
|
||||||
column :initial_play_silence
|
|
||||||
column :time_signature
|
|
||||||
column :status
|
|
||||||
column :recording_type
|
|
||||||
column :original_artist
|
column :original_artist
|
||||||
column :songwriter
|
column :name
|
||||||
column :publisher
|
column :onboarding_flags do |jam_track| jam_track.onboard_warnings end
|
||||||
|
column :status
|
||||||
|
column :master_track do |jam_track| jam_track.master_track.nil? ? 'None' : (link_to "Download", jam_track.master_track.url_by_sample_rate(44)) end
|
||||||
column :licensor
|
column :licensor
|
||||||
column :pro
|
|
||||||
column :genre
|
column :genre
|
||||||
column :sales_region
|
|
||||||
column :price
|
column :price
|
||||||
column :reproduction_royalty
|
column :reproduction_royalty
|
||||||
column :public_performance_royalty
|
column :public_performance_royalty
|
||||||
column :reproduction_royalty_amount
|
column :reproduction_royalty_amount
|
||||||
column :licensor_royalty_amount
|
column :licensor_royalty_amount
|
||||||
column :pro_royalty_amount
|
column :id
|
||||||
column :url
|
|
||||||
column :created_at
|
|
||||||
|
|
||||||
column :jam_track_tracks do |jam_track|
|
column :jam_track_tracks do |jam_track|
|
||||||
table_for jam_track.jam_track_tracks.order('position ASC') do
|
table_for jam_track.jam_track_tracks.order('position ASC') do
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
ActiveAdmin.register JamRuby::PromoBuzz, :as => 'Buzz' do
|
ActiveAdmin.register JamRuby::PromoBuzz, :as => 'Buzz' do
|
||||||
|
|
||||||
menu :label => 'Buzz', :parent => 'Home Page'
|
menu :label => 'Promo Buzz', :parent => 'Misc'
|
||||||
|
|
||||||
config.sort_order = 'position ASC aasm_state DESC updated_at DESC'
|
config.sort_order = 'position ASC aasm_state DESC updated_at DESC'
|
||||||
config.batch_actions = false
|
config.batch_actions = false
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
ActiveAdmin.register JamRuby::PromoLatest, :as => 'Latest' do
|
ActiveAdmin.register JamRuby::PromoLatest, :as => 'Latest' do
|
||||||
|
|
||||||
menu :label => 'Latest', :parent => 'Home Page'
|
menu :label => 'Promo Latest', :parent => 'Misc'
|
||||||
|
|
||||||
config.batch_actions = false
|
config.batch_actions = false
|
||||||
config.sort_order = ''
|
config.sort_order = ''
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
ActiveAdmin.register_page "Recurly Health" do
|
||||||
|
menu :parent => 'Misc'
|
||||||
|
|
||||||
|
content :title => "Recurly Transaction Totals" do
|
||||||
|
table_for Sale.check_integrity_of_jam_track_sales do
|
||||||
|
column "Total", :total
|
||||||
|
column "Successes", :succeeded
|
||||||
|
column "Voids", :voided
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
ActiveAdmin.register_page "Download CSV" do
|
=begin
|
||||||
menu :parent => 'Score'
|
ActiveAdmin.register_page "Download Score CSV" do
|
||||||
|
menu :parent => 'Misc'
|
||||||
|
|
||||||
page_action :create_csv, :method => :post do
|
page_action :create_csv, :method => :post do
|
||||||
|
|
||||||
|
|
@ -95,4 +96,5 @@ ActiveAdmin.register_page "Download CSV" do
|
||||||
#end
|
#end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
=end
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
=begin
|
||||||
ActiveAdmin.register JamRuby::ScoreHistory, :as => 'Score History' do
|
ActiveAdmin.register JamRuby::ScoreHistory, :as => 'Score History' do
|
||||||
menu :parent => 'Score'
|
menu :parent => 'Score'
|
||||||
|
|
||||||
|
|
@ -80,3 +81,4 @@ ActiveAdmin.register JamRuby::ScoreHistory, :as => 'Score History' do
|
||||||
column "To Client", :to_client_id
|
column "To Client", :to_client_id
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
=end
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
ActiveAdmin.register_page "Current Scoring Load" do
|
ActiveAdmin.register_page "Current Scoring Load" do
|
||||||
menu :parent => 'Score'
|
menu :parent => 'Misc'
|
||||||
|
|
||||||
content :title => "Current Scoring Load" do
|
content :title => "Current Scoring Load" do
|
||||||
table_for GetWork.summary do
|
table_for GetWork.summary do
|
||||||
|
|
|
||||||
|
|
@ -31,3 +31,7 @@
|
||||||
// .active_admin applies to any Active Admin namespace
|
// .active_admin applies to any Active Admin namespace
|
||||||
// .admin_namespace applies to the admin namespace (eg: /admin)
|
// .admin_namespace applies to the admin namespace (eg: /admin)
|
||||||
// .other_namespace applies to a custom namespace named other (eg: /other)
|
// .other_namespace applies to a custom namespace named other (eg: /other)
|
||||||
|
|
||||||
|
.cohort_col {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
class ChecksController < ApplicationController
|
||||||
|
|
||||||
|
#respond_to :json
|
||||||
|
|
||||||
|
# create or update a client_artifact row
|
||||||
|
def check_latency_tester
|
||||||
|
|
||||||
|
latency_tester_name = params[:name]
|
||||||
|
|
||||||
|
exists = Connection.where(client_type: Connection::TYPE_LATENCY_TESTER).where(client_id: latency_tester_name).first
|
||||||
|
|
||||||
|
if exists
|
||||||
|
render :text => "", :status => :ok
|
||||||
|
else
|
||||||
|
render :text => "", :status => 404
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,363 @@
|
||||||
|
require 'date'
|
||||||
|
|
||||||
|
class Cohort < ActiveRecord::Base
|
||||||
|
|
||||||
|
EARLIEST_DATE = Time.parse('2014-03-01')
|
||||||
|
TOTAL_COHORT_DATE = Time.at(0)
|
||||||
|
|
||||||
|
ALLTIME_LABELS = {
|
||||||
|
registered_users: 'Registered Users',
|
||||||
|
first_downloaded_client_at: 'DL app',
|
||||||
|
first_certified_gear_at: 'Certified Gear',
|
||||||
|
music_sessions_user_history: 'Played Online',
|
||||||
|
jam_tracks_played: 'Played JT',
|
||||||
|
jam_track_rights: 'Purchased JT',
|
||||||
|
recorded_tracks: 'Made Recording',
|
||||||
|
friendships: 'Friended',
|
||||||
|
invited_users: 'Invite Others',
|
||||||
|
}
|
||||||
|
|
||||||
|
MONTHLY_LABELS = {
|
||||||
|
registered_users: 'Registered Users',
|
||||||
|
first_downloaded_client_at: 'DL app',
|
||||||
|
first_certified_gear_at: 'Certified Gear',
|
||||||
|
music_sessions_user_history_1: 'Played Online 1',
|
||||||
|
music_sessions_user_history_2_5: 'Played Online 2-5',
|
||||||
|
music_sessions_user_history_6_: 'Played Online 6+',
|
||||||
|
jam_tracks_played_1: 'Played JT 1',
|
||||||
|
jam_tracks_played_2_5: 'Played JT 2-5',
|
||||||
|
jam_tracks_played_6_: 'Played JT 6+',
|
||||||
|
jam_track_rights_redeemed: 'Redeemed JT',
|
||||||
|
jam_track_rights: 'Purchased JT',
|
||||||
|
recorded_tracks: 'Made Recording',
|
||||||
|
friendships: 'Friended',
|
||||||
|
invited_users: 'Invite Others',
|
||||||
|
}
|
||||||
|
|
||||||
|
attr_accessible :all_time, :monthly_start
|
||||||
|
serialize :data_set, JSON
|
||||||
|
|
||||||
|
before_create do
|
||||||
|
self.data_set ||= {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.date_tuples(from,to)
|
||||||
|
prec = from.size
|
||||||
|
start = Date.new(*from)
|
||||||
|
finish = Date.new(*to)
|
||||||
|
|
||||||
|
filter_on = [:day,:mon].first(3-prec)
|
||||||
|
filter = ->(d) { filter_on.all? {|attr| d.send(attr) == 1 } }
|
||||||
|
|
||||||
|
(start..finish)
|
||||||
|
.select(&filter)
|
||||||
|
.map { |d| [d.year,d.mon,d.day].first(prec) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.earliest_cohort
|
||||||
|
starting = User.where(admin: false).order(:created_at).first.created_at
|
||||||
|
starting = EARLIEST_DATE if starting.nil? || starting < EARLIEST_DATE
|
||||||
|
starting # this is necessary to always return not null
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.cohort_group_ranges(starting=nil, ending=nil)
|
||||||
|
starting ||= self.earliest_cohort
|
||||||
|
ending ||= Time.now
|
||||||
|
dates = self.date_tuples([starting.year, starting.month], [ending.year, ending.month])
|
||||||
|
ranges = []
|
||||||
|
dates.each_with_index do |d1, idx|
|
||||||
|
d2 = dates[idx+1] || [Time.now.next_month.year,Time.now.next_month.month]
|
||||||
|
rr = Time.parse("#{d1[0]}-#{d1[1]}-1")..(Time.parse("#{d2[0]}-#{d2[1]}-1") - 1.second)
|
||||||
|
ranges << rr
|
||||||
|
end
|
||||||
|
ranges
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.generate_monthly_cohorts(monthly_start, monthly_end)
|
||||||
|
Cohort.delete_all(['all_time = ?',false])
|
||||||
|
self.cohort_group_ranges.collect do |range|
|
||||||
|
next if range.first > monthly_end
|
||||||
|
cc = Cohort.new
|
||||||
|
cc.group_start = range.first
|
||||||
|
cc.group_end = range.last
|
||||||
|
cc.monthly_start = monthly_start
|
||||||
|
cc.monthly_end = monthly_end
|
||||||
|
cc.all_time = false
|
||||||
|
cc.save!
|
||||||
|
cc
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.generate_all_time_cohorts
|
||||||
|
Cohort.delete_all("all_time = 't'")
|
||||||
|
self.cohort_group_ranges.collect do |range|
|
||||||
|
unless cc = Cohort.where(group_start: range.first).where(all_time: true).limit(1).first
|
||||||
|
cc = Cohort.new
|
||||||
|
cc.group_start = range.first
|
||||||
|
cc.group_end = range.last
|
||||||
|
cc.all_time = true
|
||||||
|
cc.save!
|
||||||
|
end
|
||||||
|
cc
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def _put_data_set(key, count, num_user)
|
||||||
|
self.data_set[key.to_s] = count
|
||||||
|
xx = (count.to_f / num_user.to_f)
|
||||||
|
self.data_set["#{key}%"] = 100.0 * xx.round(2)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.cohort_users(cohort)
|
||||||
|
User.where(created_at: cohort.group_start..cohort.group_end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def _played_online_subquery(constraint)
|
||||||
|
where = if constraint.is_a?(Range)
|
||||||
|
"played.cnt >= #{constraint.first} AND played.cnt <= #{constraint.last}"
|
||||||
|
else
|
||||||
|
"played.cnt #{constraint}"
|
||||||
|
end
|
||||||
|
start_date = all_time ? self.group_start : self.monthly_start
|
||||||
|
end_date = all_time ? self.group_end : self.monthly_end
|
||||||
|
sql =<<SQL
|
||||||
|
SELECT played.user_id FROM
|
||||||
|
(SELECT user_id, COUNT(*) cnt FROM music_sessions_user_history msuh1
|
||||||
|
WHERE
|
||||||
|
msuh1.created_at >= '#{start_date}' AND
|
||||||
|
msuh1.created_at <= '#{end_date}' AND
|
||||||
|
EXTRACT(EPOCH FROM (msuh1.session_removed_at - msuh1.created_at)) >= 900 AND
|
||||||
|
(SELECT COUNT(*) FROM music_sessions_user_history msuh2
|
||||||
|
WHERE msuh1.music_session_id = msuh2.music_session_id
|
||||||
|
) > 1
|
||||||
|
GROUP BY user_id
|
||||||
|
) played
|
||||||
|
WHERE #{where}
|
||||||
|
SQL
|
||||||
|
end
|
||||||
|
|
||||||
|
def _played_jamtrack_subquery(constraint)
|
||||||
|
where = if constraint.is_a?(Range)
|
||||||
|
"played.cnt >= #{constraint.first} AND played.cnt <= #{constraint.last}"
|
||||||
|
else
|
||||||
|
"played.cnt #{constraint}"
|
||||||
|
end
|
||||||
|
start_date = all_time ? self.group_start : self.monthly_start
|
||||||
|
end_date = all_time ? self.group_end : self.monthly_end
|
||||||
|
sql =<<SQL
|
||||||
|
SELECT played.player_id FROM
|
||||||
|
(SELECT player_id, COUNT(*) cnt FROM playable_plays pp
|
||||||
|
WHERE
|
||||||
|
pp.created_at >= '#{start_date}' AND
|
||||||
|
pp.created_at <= '#{end_date}' AND
|
||||||
|
pp.playable_type = 'JamRuby::JamTrack' /* VRFS-2916 jam_tracks.id is varchar: ADD */
|
||||||
|
GROUP BY player_id
|
||||||
|
) played
|
||||||
|
WHERE #{where}
|
||||||
|
SQL
|
||||||
|
end
|
||||||
|
|
||||||
|
def _subquery(assoc_key, num_user)
|
||||||
|
assoc = User.reflections[assoc_key]
|
||||||
|
return 0 unless assoc
|
||||||
|
start_date = all_time ? self.group_start : self.monthly_start
|
||||||
|
end_date = all_time ? self.group_end : self.monthly_end
|
||||||
|
sql =<<SQL
|
||||||
|
SELECT #{assoc.foreign_key} FROM #{assoc.class_name.constantize.table_name} tt
|
||||||
|
WHERE
|
||||||
|
tt.created_at >= '#{start_date}' AND
|
||||||
|
tt.created_at <= '#{end_date}'
|
||||||
|
SQL
|
||||||
|
if block_given?
|
||||||
|
yield_sql = yield(sql)
|
||||||
|
sql = yield_sql unless yield_sql.blank?
|
||||||
|
end
|
||||||
|
self.class.cohort_users(self).where("users.id IN (#{sql})").count
|
||||||
|
end
|
||||||
|
|
||||||
|
def _monthly!
|
||||||
|
unless 0 < num_user = self.class.cohort_users(self).count
|
||||||
|
self.update_attribute(:data_set, {})
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
self.data_set['registered_users'] = num_user
|
||||||
|
num_user = num_user.to_f
|
||||||
|
|
||||||
|
qq = self.class.cohort_users(self)
|
||||||
|
.where(first_downloaded_client_at: self.monthly_start..self.monthly_end)
|
||||||
|
_put_data_set(:first_downloaded_client_at, qq.count, num_user)
|
||||||
|
|
||||||
|
qq = self.class.cohort_users(self)
|
||||||
|
.where(first_certified_gear_at: self.monthly_start..self.monthly_end)
|
||||||
|
_put_data_set(:first_certified_gear_at, qq.count, num_user)
|
||||||
|
|
||||||
|
count = _subquery(assoc_key = :invited_users, num_user)
|
||||||
|
_put_data_set(assoc_key, count, num_user)
|
||||||
|
|
||||||
|
count = _subquery(assoc_key = :recorded_tracks, num_user)
|
||||||
|
_put_data_set(assoc_key, count, num_user)
|
||||||
|
|
||||||
|
count = _subquery(assoc_key = :jam_track_rights, num_user) do |subsql|
|
||||||
|
subsql += " AND tt.is_test_purchase = 'f' AND tt.redeemed = 'f' "
|
||||||
|
end
|
||||||
|
_put_data_set(assoc_key, count, num_user)
|
||||||
|
|
||||||
|
count = _subquery(assoc_key = :jam_track_rights, num_user) do |subsql|
|
||||||
|
subsql += " AND tt.is_test_purchase = 'f' AND tt.redeemed = 't' "
|
||||||
|
end
|
||||||
|
_put_data_set(:jam_track_rights_redeemed, count, num_user)
|
||||||
|
|
||||||
|
count = _subquery(assoc_key = :friendships, num_user)
|
||||||
|
_put_data_set(assoc_key, count, num_user)
|
||||||
|
|
||||||
|
sql = _played_online_subquery(' = 1 ')
|
||||||
|
count = self.class.cohort_users(self).where("users.id IN (#{sql})").count
|
||||||
|
_put_data_set(:music_sessions_user_history_1, count, num_user)
|
||||||
|
|
||||||
|
sql = _played_online_subquery(2..5)
|
||||||
|
count = self.class.cohort_users(self).where("users.id IN (#{sql})").count
|
||||||
|
_put_data_set(:music_sessions_user_history_2_5, count, num_user)
|
||||||
|
|
||||||
|
sql = _played_online_subquery(' >= 6')
|
||||||
|
count = self.class.cohort_users(self).where("users.id IN (#{sql})").count
|
||||||
|
_put_data_set(:music_sessions_user_history_6_, count, num_user)
|
||||||
|
|
||||||
|
sql = _played_jamtrack_subquery(' = 1 ')
|
||||||
|
count = self.class.cohort_users(self).where("users.id IN (#{sql})").count
|
||||||
|
_put_data_set(:jam_tracks_played_1, count, num_user)
|
||||||
|
|
||||||
|
sql = _played_jamtrack_subquery(2..5)
|
||||||
|
count = self.class.cohort_users(self).where("users.id IN (#{sql})").count
|
||||||
|
_put_data_set(:jam_tracks_played_2_5, count, num_user)
|
||||||
|
|
||||||
|
sql = _played_jamtrack_subquery(' >= 6')
|
||||||
|
count = self.class.cohort_users(self).where("users.id IN (#{sql})").count
|
||||||
|
_put_data_set(:jam_tracks_played_6_, count, num_user)
|
||||||
|
|
||||||
|
self.save!
|
||||||
|
end
|
||||||
|
|
||||||
|
def _all_time!
|
||||||
|
unless 0 < num_user = self.class.cohort_users(self).count
|
||||||
|
self.update_attribute(:data_set, {})
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
self.data_set['registered_users'] = num_user
|
||||||
|
num_user = num_user.to_f
|
||||||
|
|
||||||
|
count = self.class.cohort_users(self)
|
||||||
|
.where(['first_downloaded_client_at IS NOT NULL'])
|
||||||
|
.count
|
||||||
|
_put_data_set('first_downloaded_client_at', count, num_user)
|
||||||
|
|
||||||
|
count = self.class.cohort_users(self)
|
||||||
|
.where(['first_certified_gear_at IS NOT NULL'])
|
||||||
|
.count
|
||||||
|
_put_data_set('first_certified_gear_at', count, num_user)
|
||||||
|
|
||||||
|
count = _subquery(assoc_key = :invited_users, num_user)
|
||||||
|
_put_data_set(assoc_key, count, num_user)
|
||||||
|
|
||||||
|
count = _subquery(assoc_key = :recorded_tracks, num_user)
|
||||||
|
_put_data_set(assoc_key, count, num_user)
|
||||||
|
|
||||||
|
count = _subquery(assoc_key = :friendships, num_user)
|
||||||
|
_put_data_set(assoc_key, count, num_user)
|
||||||
|
|
||||||
|
count = _subquery(assoc_key = :jam_track_rights, num_user) do |subsql|
|
||||||
|
subsql += " AND tt.is_test_purchase = 'f'"
|
||||||
|
end
|
||||||
|
_put_data_set(assoc_key, count, num_user)
|
||||||
|
|
||||||
|
count = _subquery(assoc_key = :jam_tracks_played, num_user) do |subsql|
|
||||||
|
# VRFS-2916 jam_tracks.id is varchar: REMOVE
|
||||||
|
# subsql += " AND tt.jam_track_id IS NOT NULL "
|
||||||
|
# VRFS-2916 jam_tracks.id is varchar: ADD
|
||||||
|
subsql += " AND tt.playable_type = 'JamRuby::JamTrack' "
|
||||||
|
end
|
||||||
|
_put_data_set(assoc_key, count, num_user)
|
||||||
|
|
||||||
|
sql = _played_online_subquery(' >= 1')
|
||||||
|
count = self.class.cohort_users(self).where("users.id IN (#{sql})").count
|
||||||
|
_put_data_set(:music_sessions_user_history, count, num_user)
|
||||||
|
|
||||||
|
self.save!
|
||||||
|
end
|
||||||
|
|
||||||
|
def populate!
|
||||||
|
self.all_time ? _all_time! : _monthly!
|
||||||
|
end
|
||||||
|
|
||||||
|
def calculate_totals(cohorts)
|
||||||
|
self.group_start = self.group_end = TOTAL_COHORT_DATE
|
||||||
|
labels = all_time ? Cohort::ALLTIME_LABELS : Cohort::MONTHLY_LABELS
|
||||||
|
|
||||||
|
self.data_set = labels.inject({}) do |hh, (kk,vv)|
|
||||||
|
hh[kk.to_s] = hh["#{kk}%"] = 0
|
||||||
|
hh
|
||||||
|
end
|
||||||
|
|
||||||
|
labels = labels.keys.map(&:to_s)
|
||||||
|
cohorts.each do |cc|
|
||||||
|
labels.each do |key|
|
||||||
|
self.data_set[key] += cc.data_set[key].to_i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
user_total = self.data_set['registered_users'].to_f
|
||||||
|
labels.delete('registered_users')
|
||||||
|
labels.each do |key|
|
||||||
|
xx = (self.data_set[key].to_f / user_total)
|
||||||
|
self.data_set["#{key}%"] = 100.0 * xx.round(4)
|
||||||
|
end
|
||||||
|
|
||||||
|
self.save!
|
||||||
|
cohorts << self
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.alltime_cohorts!
|
||||||
|
cohorts = Cohort.generate_all_time_cohorts.each do |cc|
|
||||||
|
cc._all_time!
|
||||||
|
end
|
||||||
|
Cohort.new(all_time: true).calculate_totals(cohorts)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.monthly_cohorts!(monthly_start, monthly_end=nil)
|
||||||
|
monthly_end ||= monthly_start + 1.month - 1.second
|
||||||
|
cohorts = self.generate_monthly_cohorts(monthly_start, monthly_end).compact.each do |cc|
|
||||||
|
cc._monthly!
|
||||||
|
end
|
||||||
|
Cohort.new(all_time: false, monthly_start: monthly_start).calculate_totals(cohorts)
|
||||||
|
end
|
||||||
|
|
||||||
|
def group_start_str
|
||||||
|
is_total_cohort? ? 'Total' : self.group_start.strftime('%Y-%m')
|
||||||
|
end
|
||||||
|
|
||||||
|
def group_end_str
|
||||||
|
self.group_end.strftime('%Y-%m-%d')
|
||||||
|
end
|
||||||
|
|
||||||
|
def data_val(col, percent=false)
|
||||||
|
if percent
|
||||||
|
val = self.data_set["#{col}%"]
|
||||||
|
val ? "#{'%0.f' % val}%" : ''
|
||||||
|
else
|
||||||
|
self.data_set[col.to_s]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.monthly_starts
|
||||||
|
self.cohort_group_ranges.collect do |rr|
|
||||||
|
rr.first.to_s
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def is_total_cohort?
|
||||||
|
self.group_start == TOTAL_COHORT_DATE
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
|
@ -2,37 +2,36 @@
|
||||||
= f.semantic_errors *f.object.errors.keys
|
= f.semantic_errors *f.object.errors.keys
|
||||||
= f.inputs name: 'JamTrack fields' do
|
= f.inputs name: 'JamTrack fields' do
|
||||||
= f.input :name, :input_html => { :rows=>1, :maxlength=>200 }
|
= f.input :name, :input_html => { :rows=>1, :maxlength=>200 }
|
||||||
b style='margin-left:10px'
|
|
||||||
i
|
|
||||||
| JamTrack should only be made available (to end users) if all its sub-component are in place:
|
|
||||||
= f.input :available, as: :boolean
|
|
||||||
= f.input :description, :input_html => { :rows=>5, :maxlength=>1000 }
|
= f.input :description, :input_html => { :rows=>5, :maxlength=>1000 }
|
||||||
= f.input :initial_play_silence, :label => 'Initial Play Silence (seconds)'
|
= f.input :plan_code, :label=>'Recurly Plan Code', :required=>true, :hint => 'Must match plan code in Recurly'
|
||||||
= f.input :time_signature, collection: JamRuby::JamTrack::TIME_SIGNATURES, include_blank: false
|
//= f.input :initial_play_silence, :label => 'Initial Play Silence (seconds)'
|
||||||
= f.input :status, collection: JamRuby::JamTrack::STATUS, include_blank: false
|
= f.input :time_signature, collection: JamRuby::JamTrack::TIME_SIGNATURES, include_blank: true
|
||||||
|
= f.input :status, collection: JamRuby::JamTrack::STATUS, include_blank: false, hint: 'Only set to Production when end users should be able to purchase this JamTrack'
|
||||||
= f.input :recording_type, collection: JamRuby::JamTrack::RECORDING_TYPE, include_blank: false
|
= f.input :recording_type, collection: JamRuby::JamTrack::RECORDING_TYPE, include_blank: false
|
||||||
= f.input :original_artist, :input_html => { :rows=>2, :maxlength=>200 }
|
= f.input :original_artist, :input_html => { :rows=>1, :maxlength=>1000 }
|
||||||
= f.input :songwriter, :input_html => { :rows=>5, :maxlength=>1000 }
|
= f.input :songwriter, :input_html => { :rows=>1, :maxlength=>1000 }
|
||||||
= f.input :publisher, :input_html => { :rows=>5, :maxlength=>1000 }
|
= f.input :publisher, :input_html => { :rows=>1, :maxlength=>1000 }
|
||||||
= f.input :licensor, collection: JamRuby::JamTrackLicensor.all, include_blank: false
|
= f.input :licensor, collection: JamRuby::JamTrackLicensor.all, include_blank: true
|
||||||
= f.input :pro, collection: JamRuby::JamTrack::PRO, include_blank: false
|
|
||||||
= f.input :genre, collection: JamRuby::Genre.all, include_blank: false
|
= f.input :genre, collection: JamRuby::Genre.all, include_blank: false
|
||||||
|
= f.input :duration, hint: 'this should rarely need editing because it comes from the import process'
|
||||||
= f.input :sales_region, collection: JamRuby::JamTrack::SALES_REGION, include_blank: false
|
= f.input :sales_region, collection: JamRuby::JamTrack::SALES_REGION, include_blank: false
|
||||||
= f.input :price, :required=>true, :input_html=>{type:'numeric'}
|
= f.input :price, :required => true, :input_html => {type: 'numeric'}
|
||||||
|
= f.input :pro_ascap, :label => 'ASCAP royalties due?'
|
||||||
|
= f.input :pro_bmi, :label => 'BMI royalties due?'
|
||||||
|
= f.input :pro_sesac, :label => 'SESAC royalties due?'
|
||||||
= f.input :reproduction_royalty, :label => 'Reproduction Royalty'
|
= f.input :reproduction_royalty, :label => 'Reproduction Royalty'
|
||||||
= f.input :public_performance_royalty, :label => 'Public Performance Royalty'
|
= f.input :public_performance_royalty, :label => 'Public Performance Royalty'
|
||||||
= f.input :reproduction_royalty_amount, :required=>true, :input_html=>{type:'numeric'}
|
= f.input :reproduction_royalty_amount, :required=>true, :input_html=>{type:'numeric'}
|
||||||
= f.input :licensor_royalty_amount, :required=>true, :input_html=>{type:'numeric'}
|
= f.input :licensor_royalty_amount, :required=>true, :input_html=>{type:'numeric'}
|
||||||
= f.input :pro_royalty_amount, :required=>true, :input_html=>{type:'numeric'}
|
|
||||||
= f.input :plan_code, :label=>'Recurly Plan Code', :required=>true
|
//= f.input :url, :as => :file, :label => 'Audio File'
|
||||||
= f.input :url, :as => :file, :label => 'Audio File'
|
= f.input :jmep_text, :as => :text, :label => "JMEP Text", :input_html => {:rows => 5 }, :hint => 'Tap-Ins & Lead Silence. Examples: https://jamkazam.atlassian.net/wiki/pages/viewpage.action?pageId=39289025#JamKazamMeta-EventProcessor(JMEP)-CommonExamples'
|
||||||
|
= f.input :jmep_json, :as => :text, :label => "JMEP Json", :input_html => {:rows => 5, :readonly => true }, :hint => 'Readonly field. This is shown here just so you can see what your JMEP got converted to readily'
|
||||||
|
= f.input :version, :label => 'Version', :hint => 'Increment this value whenever you invalidate (update) the media in the JamTrack. Changing JMEP does not count as a version change; changing anything about a track (audio, instrument, part) does.'
|
||||||
|
|
||||||
= f.semantic_fields_for :jam_track_tracks do |track|
|
= f.semantic_fields_for :jam_track_tracks do |track|
|
||||||
= render 'jam_track_track_fields', f: track
|
= render 'jam_track_track_fields', f: track
|
||||||
= f.semantic_fields_for :jam_track_tap_ins do |tap_in|
|
|
||||||
= render 'jam_track_tap_in_fields', f: tap_in
|
|
||||||
.links
|
.links
|
||||||
= link_to_add_association 'Add Track', f, :jam_track_tracks, class: 'button', style: 'margin:20px;padding:10px 20px'
|
= link_to_add_association 'Add Track', f, :jam_track_tracks, class: 'button', style: 'margin:20px;padding:10px 20px'
|
||||||
= link_to_add_association 'Add Tap In', f, :jam_track_tap_ins, class: 'button', style: 'margin:20px;padding:10px 20px'
|
|
||||||
|
|
||||||
= f.actions
|
= f.actions
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
= f.inputs name: 'Jam Track Right fields' do
|
||||||
|
|
||||||
|
ol.nested-fields
|
||||||
|
= f.input :jam_track, :required=>true, collection: JamTrack.all, include_blank: false
|
||||||
|
= f.input :user, :required=>true, collection: User.all, include_blank: false
|
||||||
|
|
@ -1,21 +1,35 @@
|
||||||
= f.inputs name: 'Track fields' do
|
= f.inputs name: 'Track fields' do
|
||||||
|
|
||||||
ol.nested-fields
|
ol.nested-fields
|
||||||
= f.input :track_type, :as => :select, collection: JamRuby::JamTrackTrack::TRACK_TYPE, include_blank: false
|
= f.input :track_type, :as => :select, collection: ['Track', 'Master'], include_blank: false
|
||||||
= f.input :instrument, collection: Instrument.all, include_blank: false
|
= f.input :instrument, collection: Instrument.all, include_blank: false
|
||||||
= f.input :part, :required=>true, :input_html => { :rows=>1, :maxlength=>20, :type=>'numeric' }
|
= f.input :part, :required=>true, :input_html => { :rows=>1, :maxlength=>20, :type=>'numeric' }
|
||||||
|
|
||||||
= f.input :position
|
= f.input :position
|
||||||
|
- if !f.object.nil? && f.object.track_type != 'Master'
|
||||||
|
= f.input :preview_start_time_raw, :label => 'Preview Start Time', :hint => 'MM:SS:MLS', :as => :string
|
||||||
|
- unless f.object.nil? || f.object[:preview_url].nil?
|
||||||
|
.current_file_holder style='margin-bottom:10px'
|
||||||
|
a href=f.object.preview_public_url('ogg') style='padding:0 0 0 20px'
|
||||||
|
| Download Preview (ogg)
|
||||||
|
a href=f.object.preview_public_url('mp3') style='padding:0 0 0 20px'
|
||||||
|
| Download Preview (mp3)
|
||||||
|
|
||||||
|
// temporarily disable
|
||||||
- if f.object.new_record?
|
- if f.object.new_record?
|
||||||
p style='margin-left:10px'
|
//p style='margin-left:10px'
|
||||||
i
|
//i
|
||||||
| before you can upload, you must select 'Update JamTrack'
|
// | before you can upload, you must select 'Update JamTrack'
|
||||||
- else
|
- else
|
||||||
= f.input :url, :as => :file, :label => 'Track file'
|
// = f.input :url_48, :as => :file, :label => 'Track file (48kHz)'
|
||||||
- unless f.object.nil? || f.object[:url].nil?
|
- unless f.object.nil? || f.object[:url_48].nil?
|
||||||
.current_file_holder style='margin-bottom:10px'
|
.current_file_holder style='margin-bottom:10px'
|
||||||
a href=f.object.sign_url(3600) style='padding:0 0 0 20px'
|
a href=f.object.sign_url(3600) style='padding:0 0 0 20px'
|
||||||
| Download
|
| #{File.basename(f.object[:url_48])}
|
||||||
|
|
||||||
|
// = f.input :url_44, :as => :file, :label => 'Track file (44kHz)'
|
||||||
|
- unless f.object.nil? || f.object[:url_44].nil?
|
||||||
|
.current_file_holder style='margin-bottom:10px'
|
||||||
|
a href=f.object.sign_url(3600, 44) style='padding:0 0 0 20px'
|
||||||
|
| #{File.basename(f.object[:url_44])}
|
||||||
|
|
||||||
= link_to_remove_association "Delete Track", f, class: 'button', style: 'margin-left:10px'
|
= link_to_remove_association "Delete Track", f, class: 'button', style: 'margin-left:10px'
|
||||||
|
|
@ -147,5 +147,8 @@ module JamAdmin
|
||||||
config.influxdb_hosts = ["localhost"]
|
config.influxdb_hosts = ["localhost"]
|
||||||
config.influxdb_port = 8086
|
config.influxdb_port = 8086
|
||||||
config.influxdb_ignored_environments = ENV["INFLUXDB_ENABLED"] == '1' ? ['test', 'cucumber'] : ['test', 'cucumber', 'development']
|
config.influxdb_ignored_environments = ENV["INFLUXDB_ENABLED"] == '1' ? ['test', 'cucumber'] : ['test', 'cucumber', 'development']
|
||||||
|
|
||||||
|
config.jamtracks_dir = ENV['JAMTRACKS_DIR'] || File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "jamtracks"))
|
||||||
|
config.jmep_dir = ENV['JMEP_DIR'] || File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "jmep"))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,138 @@
|
||||||
|
class JamRuby::JamTrackTrack
|
||||||
|
|
||||||
|
# add a custom validation
|
||||||
|
|
||||||
|
attr_accessor :preview_generate_error
|
||||||
|
|
||||||
|
validate :preview
|
||||||
|
|
||||||
|
def preview
|
||||||
|
if preview_generate_error
|
||||||
|
errors.add(:preview_start_time, preview_generate_error)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# this is used by active admin/jam-admin
|
||||||
|
def preview_start_time_raw
|
||||||
|
if self.preview_start_time.nil? || self.preview_start_time.nil?
|
||||||
|
''
|
||||||
|
else
|
||||||
|
seconds = self.preview_start_time.to_f/1000
|
||||||
|
time = Time.at(seconds)
|
||||||
|
time.strftime("%M:%S:#{(self.preview_start_time % 1000).to_s.rjust(3, '0')}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# this is used by active admin/jam-admin
|
||||||
|
def preview_start_time_raw=(new_value)
|
||||||
|
|
||||||
|
value = nil
|
||||||
|
if new_value == nil || new_value == ''
|
||||||
|
value = nil
|
||||||
|
else
|
||||||
|
if new_value && new_value.kind_of?(String) && new_value.include?(':')
|
||||||
|
bits = new_value.split(':')
|
||||||
|
if bits.length != 3
|
||||||
|
raise "format of preview start time must be MM:SS:MLS"
|
||||||
|
end
|
||||||
|
|
||||||
|
value = (bits[0].to_i * 60000) + (bits[1].to_i * 1000) + (bits[2].to_i)
|
||||||
|
|
||||||
|
else
|
||||||
|
raise "format of preview start time must be MM:SS:MLS"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if !value.nil? && value != self.preview_start_time
|
||||||
|
self.preview_start_time = value
|
||||||
|
generate_preview
|
||||||
|
else
|
||||||
|
self.preview_start_time = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def generate_preview
|
||||||
|
|
||||||
|
begin
|
||||||
|
Dir.mktmpdir do |tmp_dir|
|
||||||
|
|
||||||
|
input = File.join(tmp_dir, 'in.ogg')
|
||||||
|
output = File.join(tmp_dir, 'out.ogg')
|
||||||
|
output_mp3 = File.join(tmp_dir, 'out.mp3')
|
||||||
|
|
||||||
|
start = self.preview_start_time.to_f / 1000
|
||||||
|
stop = start + 20
|
||||||
|
|
||||||
|
raise 'no track' unless self["url_44"]
|
||||||
|
|
||||||
|
s3_manager.download(self.url_by_sample_rate(44), input)
|
||||||
|
|
||||||
|
command = "sox \"#{input}\" \"#{output}\" trim #{sprintf("%.3f", start)} =#{sprintf("%.3f", stop)}"
|
||||||
|
|
||||||
|
@@log.debug("trimming using: " + command)
|
||||||
|
|
||||||
|
sox_output = `#{command}`
|
||||||
|
|
||||||
|
result_code = $?.to_i
|
||||||
|
|
||||||
|
if result_code != 0
|
||||||
|
@@log.debug("fail #{result_code}")
|
||||||
|
@preview_generate_error = "unable to execute cut command #{sox_output}"
|
||||||
|
else
|
||||||
|
# now create mp3 off of ogg preview
|
||||||
|
|
||||||
|
convert_mp3_cmd = "#{APP_CONFIG.ffmpeg_path} -i \"#{output}\" -ab 192k \"#{output_mp3}\""
|
||||||
|
|
||||||
|
@@log.debug("converting to mp3 using: " + convert_mp3_cmd)
|
||||||
|
|
||||||
|
convert_output = `#{convert_mp3_cmd}`
|
||||||
|
|
||||||
|
result_code = $?.to_i
|
||||||
|
|
||||||
|
if result_code != 0
|
||||||
|
@@log.debug("fail #{result_code}")
|
||||||
|
@preview_generate_error = "unable to execute mp3 convert command #{convert_output}"
|
||||||
|
else
|
||||||
|
ogg_digest = ::Digest::MD5.file(output)
|
||||||
|
mp3_digest = ::Digest::MD5.file(output_mp3)
|
||||||
|
self["preview_md5"] = ogg_md5 = ogg_digest.hexdigest
|
||||||
|
self["preview_mp3_md5"] = mp3_md5 = mp3_digest.hexdigest
|
||||||
|
|
||||||
|
@@log.debug("uploading ogg preview to #{self.preview_filename('ogg')}")
|
||||||
|
s3_public_manager.upload(self.preview_filename(ogg_md5, 'ogg'), output, content_type: 'audio/ogg', content_md5: ogg_digest.base64digest)
|
||||||
|
@@log.debug("uploading mp3 preview to #{self.preview_filename('mp3')}")
|
||||||
|
s3_public_manager.upload(self.preview_filename(mp3_md5, 'mp3'), output_mp3, content_type: 'audio/mpeg', content_md5: mp3_digest.base64)
|
||||||
|
|
||||||
|
self.skip_uploader = true
|
||||||
|
|
||||||
|
original_ogg_preview_url = self["preview_url"]
|
||||||
|
original_mp3_preview_url = self["preview_mp3_url"]
|
||||||
|
|
||||||
|
# and finally update the JamTrackTrack with the new info
|
||||||
|
self["preview_url"] = self.preview_filename(ogg_md5, 'ogg')
|
||||||
|
self["preview_length"] = File.new(output).size
|
||||||
|
# and finally update the JamTrackTrack with the new info
|
||||||
|
self["preview_mp3_url"] = self.preview_filename(mp3_md5, 'mp3')
|
||||||
|
self["preview_mp3_length"] = File.new(output_mp3).size
|
||||||
|
self.save!
|
||||||
|
|
||||||
|
# if all that worked, now delete old previews, if present
|
||||||
|
begin
|
||||||
|
s3_public_manager.delete(original_ogg_preview_url) if original_ogg_preview_url && original_ogg_preview_url != self["preview_url"]
|
||||||
|
s3_public_manager.delete(original_mp3_preview_url) if original_mp3_preview_url && original_mp3_preview_url != track["preview_mp3_url"]
|
||||||
|
rescue
|
||||||
|
puts "UNABLE TO CLEANUP OLD PREVIEW URL"
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
rescue Exception => e
|
||||||
|
@@log.error("error in sox command #{e.to_s}")
|
||||||
|
@preview_generate_error = e.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
class JamRuby::JamTrack
|
||||||
|
|
||||||
|
# add a custom validation
|
||||||
|
|
||||||
|
attr_accessor :preview_generate_error
|
||||||
|
|
||||||
|
before_save :jmep_json_generate
|
||||||
|
validate :jmep_text_validate
|
||||||
|
|
||||||
|
def jmep_text_validate
|
||||||
|
begin
|
||||||
|
JmepManager.execute(self.jmep_text)
|
||||||
|
rescue ArgumentError => err
|
||||||
|
errors.add(:jmep_text, err.to_s)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def jmep_json_generate
|
||||||
|
self.genre_id = nil if self.genre_id == ''
|
||||||
|
self.licensor_id = nil if self.licensor_id == ''
|
||||||
|
self.jmep_json = nil if self.jmep_json == ''
|
||||||
|
self.time_signature = nil if self.time_signature == ''
|
||||||
|
|
||||||
|
begin
|
||||||
|
self[:jmep_json] = JmepManager.execute(self.jmep_text)
|
||||||
|
rescue ArgumentError => err
|
||||||
|
#errors.add(:jmep_text, err.to_s)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -32,6 +32,7 @@ JamAdmin::Application.routes.draw do
|
||||||
|
|
||||||
match '/api/artifacts' => 'artifacts#update_artifacts', :via => :post
|
match '/api/artifacts' => 'artifacts#update_artifacts', :via => :post
|
||||||
match '/api/mix/:id/enqueue' => 'admin/mixes#mix_again', :via => :post
|
match '/api/mix/:id/enqueue' => 'admin/mixes#mix_again', :via => :post
|
||||||
|
match '/api/checks/latency_tester' => 'checks#check_latency_tester', :via => :get
|
||||||
|
|
||||||
mount Resque::Server.new, :at => "/resque"
|
mount Resque::Server.new, :at => "/resque"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ FactoryGirl.define do
|
||||||
state "NC"
|
state "NC"
|
||||||
country "US"
|
country "US"
|
||||||
terms_of_service true
|
terms_of_service true
|
||||||
|
reuse_card true
|
||||||
|
|
||||||
|
|
||||||
factory :admin do
|
factory :admin do
|
||||||
|
|
@ -40,6 +41,7 @@ FactoryGirl.define do
|
||||||
scoring_timeout Time.now
|
scoring_timeout Time.now
|
||||||
sequence(:channel_id) { |n| "Channel#{n}"}
|
sequence(:channel_id) { |n| "Channel#{n}"}
|
||||||
association :user, factory: :user
|
association :user, factory: :user
|
||||||
|
metronome_open false
|
||||||
end
|
end
|
||||||
|
|
||||||
factory :artifact_update, :class => JamRuby::ArtifactUpdate do
|
factory :artifact_update, :class => JamRuby::ArtifactUpdate do
|
||||||
|
|
@ -223,7 +225,6 @@ FactoryGirl.define do
|
||||||
factory :jam_track, :class => JamRuby::JamTrack do
|
factory :jam_track, :class => JamRuby::JamTrack do
|
||||||
sequence(:name) { |n| "jam-track-#{n}" }
|
sequence(:name) { |n| "jam-track-#{n}" }
|
||||||
sequence(:description) { |n| "description-#{n}" }
|
sequence(:description) { |n| "description-#{n}" }
|
||||||
bpm 100.1
|
|
||||||
time_signature '4/4'
|
time_signature '4/4'
|
||||||
status 'Production'
|
status 'Production'
|
||||||
recording_type 'Cover'
|
recording_type 'Cover'
|
||||||
|
|
|
||||||
2
build
2
build
|
|
@ -66,7 +66,7 @@ DEB_SERVER=http://localhost:9010/apt-`uname -p`
|
||||||
GEM_SERVER=http://localhost:9000/gems
|
GEM_SERVER=http://localhost:9000/gems
|
||||||
|
|
||||||
# if still going, then push all debs up
|
# if still going, then push all debs up
|
||||||
if [[ "$GIT_BRANCH" == *develop* || "$GIT_BRANCH" == *master* || "$GIT_BRANCH" == *release* ]]; then
|
if [[ "$GIT_BRANCH" == *develop* || "$GIT_BRANCH" == *master* || "$GIT_BRANCH" == *release* || "$GIT_BRANCH" == *feature* || "$GIT_BRANCH" == *hotfix* ]]; then
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "PUSHING DB ARTIFACTS"
|
echo "PUSHING DB ARTIFACTS"
|
||||||
|
|
|
||||||
32
db/manifest
32
db/manifest
|
|
@ -246,4 +246,34 @@ text_message_migration.sql
|
||||||
user_model_about_changes.sql
|
user_model_about_changes.sql
|
||||||
performance_samples.sql
|
performance_samples.sql
|
||||||
user_presences.sql
|
user_presences.sql
|
||||||
discard_scores_optimized.sql
|
discard_scores_optimized.sql
|
||||||
|
backing_tracks.sql
|
||||||
|
metronome.sql
|
||||||
|
recorded_backing_tracks.sql
|
||||||
|
recorded_backing_tracks_add_filename.sql
|
||||||
|
user_syncs_include_backing_tracks.sql
|
||||||
|
remove_bpm_from_jamtracks.sql
|
||||||
|
widen_user_authorization_token.sql
|
||||||
|
jam_track_version.sql
|
||||||
|
recorded_jam_track_tracks.sql
|
||||||
|
jam_track_jmep_data.sql
|
||||||
|
add_jam_track_bitrates.sql
|
||||||
|
jam_track_importer.sql
|
||||||
|
jam_track_pro_licensing_update.sql
|
||||||
|
jam_track_redeemed.sql
|
||||||
|
connection_metronome.sql
|
||||||
|
preview_jam_track_tracks.sql
|
||||||
|
cohorts.sql
|
||||||
|
jam_track_right_admin_purchase.sql
|
||||||
|
jam_track_playable_plays.sql
|
||||||
|
shopping_cart_anonymous.sql
|
||||||
|
user_reuse_card_and_reedem.sql
|
||||||
|
jam_track_id_to_varchar.sql
|
||||||
|
drop_position_unique_jam_track.sql
|
||||||
|
recording_client_metadata.sql
|
||||||
|
preview_support_mp3.sql
|
||||||
|
jam_track_duration.sql
|
||||||
|
sales.sql
|
||||||
|
show_whats_next_count.sql
|
||||||
|
recurly_adjustments.sql
|
||||||
|
signup_hints.sql
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
ALTER TABLE jam_track_tracks RENAME COLUMN url TO url_48;
|
||||||
|
ALTER TABLE jam_track_tracks RENAME COLUMN md5 TO md5_48;
|
||||||
|
ALTER TABLE jam_track_tracks RENAME COLUMN length TO length_48;
|
||||||
|
ALTER TABLE jam_track_tracks ADD COLUMN url_44 VARCHAR;
|
||||||
|
ALTER TABLE jam_track_tracks ADD COLUMN md5_44 VARCHAR;
|
||||||
|
ALTER TABLE jam_track_tracks ADD COLUMN length_44 BIGINT;
|
||||||
|
|
||||||
|
ALTER TABLE jam_track_rights RENAME COLUMN url TO url_48;
|
||||||
|
ALTER TABLE jam_track_rights RENAME COLUMN md5 TO md5_48;
|
||||||
|
ALTER TABLE jam_track_rights RENAME COLUMN length TO length_48;
|
||||||
|
ALTER TABLE jam_track_rights ADD COLUMN url_44 VARCHAR;
|
||||||
|
ALTER TABLE jam_track_rights ADD COLUMN md5_44 VARCHAR;
|
||||||
|
ALTER TABLE jam_track_rights ADD COLUMN length_44 BIGINT;
|
||||||
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE active_music_sessions ADD COLUMN backing_track_path VARCHAR(1024);
|
||||||
|
ALTER TABLE active_music_sessions ADD COLUMN backing_track_initiator_id VARCHAR(64);
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
CREATE TABLE cohorts (
|
||||||
|
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||||
|
|
||||||
|
data_set JSON NOT NULL DEFAULT '{}',
|
||||||
|
|
||||||
|
group_start TIMESTAMP NOT NULL,
|
||||||
|
group_end TIMESTAMP NOT NULL,
|
||||||
|
|
||||||
|
all_time BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
monthly_start TIMESTAMP,
|
||||||
|
monthly_end TIMESTAMP,
|
||||||
|
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX index_group_date ON cohorts USING btree (group_start);
|
||||||
|
|
||||||
|
CREATE INDEX msuh_music_session_idx ON music_sessions_user_history USING btree(music_session_id);
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
ALTER TABLE connections ADD COLUMN metronome_open BOOLEAN NOT NULL DEFAULT FALSE;
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
DROP INDEX jam_track_tracks_position_uniqkey;
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
ALTER TABLE jam_tracks ADD COLUMN duration INTEGER;
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
-- change jam_tracks PRIMARY KEY to VARCHAR(64)
|
||||||
|
|
||||||
|
-- first, drop all constraints and change the types
|
||||||
|
ALTER TABLE jam_track_tracks DROP CONSTRAINT jam_track_tracks_jam_track_id_fkey;
|
||||||
|
ALTER TABLE jam_track_tracks ALTER COLUMN jam_track_id TYPE VARCHAR(64);
|
||||||
|
ALTER TABLE jam_track_tap_ins DROP CONSTRAINT jam_track_tap_ins_jam_track_id_fkey;
|
||||||
|
ALTER TABLE jam_track_tap_ins ALTER COLUMN jam_track_id TYPE VARCHAR(64);
|
||||||
|
ALTER TABLE jam_track_rights DROP CONSTRAINT jam_track_rights_jam_track_id_fkey;
|
||||||
|
ALTER TABLE jam_track_rights ALTER COLUMN jam_track_id TYPE VARCHAR(64);
|
||||||
|
ALTER TABLE active_music_sessions ALTER COLUMN jam_track_id TYPE VARCHAR(64);
|
||||||
|
ALTER TABLE recordings DROP CONSTRAINT recordings_jam_track_id_fkey;
|
||||||
|
ALTER TABLE recordings ALTER COLUMN jam_track_id TYPE VARCHAR(64);
|
||||||
|
ALTER TABLE playable_plays DROP COLUMN jam_track_id;
|
||||||
|
|
||||||
|
|
||||||
|
-- then drop the jamtrack sequence, change it's type, and then set default to UUID
|
||||||
|
-- DROP SEQUENCE jam_tracks_next_seq;
|
||||||
|
ALTER TABLE jam_tracks ALTER COLUMN id TYPE VARCHAR(64);
|
||||||
|
ALTER TABLE jam_tracks ALTER COLUMN id SET DEFAULT uuid_generate_v4();
|
||||||
|
|
||||||
|
-- add back in all the constraints on the fk tables
|
||||||
|
ALTER TABLE jam_track_tracks ADD CONSTRAINT jam_track_tracks_jam_track_id_fkey FOREIGN KEY (jam_track_id) REFERENCES jam_tracks(id) ON DELETE CASCADE;
|
||||||
|
ALTER TABLE jam_track_tap_ins ADD CONSTRAINT jam_track_tap_ins_jam_track_id_fkey FOREIGN KEY (jam_track_id) REFERENCES jam_tracks(id) ON DELETE CASCADE;
|
||||||
|
ALTER TABLE jam_track_rights ADD CONSTRAINT jam_track_rights_jam_track_id_fkey FOREIGN KEY (jam_track_id) REFERENCES jam_tracks(id);
|
||||||
|
ALTER TABLE recordings ADD CONSTRAINT recordings_jam_track_id_fkey FOREIGN KEY (jam_track_id) REFERENCES jam_tracks(id);
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
ALTER TABLE jam_tracks DROP COLUMN available;
|
||||||
|
ALTER TABLE jam_tracks DROP COLUMN initial_play_silence;
|
||||||
|
ALTER TABLE jam_tracks ADD COLUMN metalocation VARCHAR UNIQUE;
|
||||||
|
ALTER TABLE jam_tracks ADD CONSTRAINT plan_code_unique UNIQUE (plan_code);
|
||||||
|
ALTER TABLE jam_track_rights ADD COLUMN recurly_subscription_uuid VARCHAR;
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE jam_tracks ADD COLUMN jmep_text VARCHAR;
|
||||||
|
ALTER TABLE jam_tracks ADD COLUMN jmep_json JSON;
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
ALTER TABLE playable_plays ADD COLUMN jam_track_id bigint;
|
||||||
|
ALTER TABLE playable_plays ALTER COLUMN playable_id DROP NOT NULL;
|
||||||
|
ALTER TABLE playable_plays ALTER COLUMN playable_type DROP NOT NULL;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
ALTER TABLE jam_tracks ADD COLUMN pro_ascap BOOLEAN DEFAULT FALSE NOT NULL;
|
||||||
|
ALTER TABLE jam_tracks ADD COLUMN pro_bmi BOOLEAN DEFAULT FALSE NOT NULL;
|
||||||
|
ALTER TABLE jam_tracks ADD COLUMN pro_sesac BOOLEAN DEFAULT FALSE NOT NULL;
|
||||||
|
UPDATE jam_tracks SET pro_ascap = TRUE WHERE pro = 'ASCAP';
|
||||||
|
UPDATE jam_tracks SET pro_bmi = TRUE WHERE pro = 'BMI';
|
||||||
|
UPDATE jam_tracks SET pro_sesac = TRUE WHERE pro = 'SESAC';
|
||||||
|
ALTER TABLE jam_tracks DROP COLUMN pro;
|
||||||
|
ALTER TABLE jam_tracks DROP Column pro_royalty_amount;
|
||||||
|
ALTER TABLE jam_tracks ADD COLUMN preview_start_time INTEGER;
|
||||||
|
ALTER TABLE jam_tracks RENAME COLUMN url TO preview_url;
|
||||||
|
ALTER TABLE jam_tracks RENAME COLUMN md5 TO preview_md5;
|
||||||
|
ALTER TABLE jam_tracks RENAME COLUMN length TO preview_length;
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
ALTER TABLE jam_track_rights ADD COLUMN redeemed BOOLEAN NOT NULL DEFAULT FALSE;
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
ALTER TABLE jam_track_rights ADD COLUMN is_test_purchase BOOLEAN DEFAULT FALSE NOT NULL;
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
ALTER TABLE jam_tracks ADD COLUMN version VARCHAR NOT NULL DEFAULT 0;
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE active_music_sessions ADD COLUMN metronome_active BOOLEAN NOT NULL DEFAULT FALSE;
|
||||||
|
ALTER TABLE active_music_sessions ADD COLUMN metronome_initiator_id VARCHAR(64);
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
ALTER TABLE jam_tracks DROP COLUMN preview_url;
|
||||||
|
ALTER TABLE jam_tracks DROP COLUMN preview_md5;
|
||||||
|
ALTER TABLE jam_tracks DROP COLUMN preview_length;
|
||||||
|
ALTER TABLE jam_tracks DROP COLUMN preview_start_time;
|
||||||
|
|
||||||
|
ALTER TABLE jam_track_tracks ADD COLUMN preview_url VARCHAR;
|
||||||
|
ALTER TABLE jam_track_tracks ADD COLUMN preview_md5 VARCHAR;
|
||||||
|
ALTER TABLE jam_track_tracks ADD COLUMN preview_length BIGINT;
|
||||||
|
ALTER TABLE jam_track_tracks ADD COLUMN preview_start_time INTEGER;
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
ALTER TABLE jam_track_tracks ADD COLUMN preview_mp3_url VARCHAR;
|
||||||
|
ALTER TABLE jam_track_tracks ADD COLUMN preview_mp3_md5 VARCHAR;
|
||||||
|
ALTER TABLE jam_track_tracks ADD COLUMN preview_mp3_length BIGINT;
|
||||||
|
UPDATE jam_track_tracks SET preview_url = NULL where track_type = 'Master';
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
CREATE UNLOGGED TABLE backing_tracks (
|
||||||
|
id VARCHAR(64) NOT NULL PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||||
|
filename VARCHAR(1024) NOT NULL,
|
||||||
|
|
||||||
|
connection_id VARCHAR(64) NOT NULL REFERENCES connections(id) ON DELETE CASCADE,
|
||||||
|
client_track_id VARCHAR(64) NOT NULL,
|
||||||
|
client_resource_id VARCHAR(100),
|
||||||
|
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE recorded_backing_tracks (
|
||||||
|
id BIGINT PRIMARY KEY,
|
||||||
|
user_id VARCHAR(64) REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
backing_track_id VARCHAR(64),
|
||||||
|
recording_id VARCHAR(64) NOT NULL,
|
||||||
|
|
||||||
|
client_track_id VARCHAR(64) NOT NULL,
|
||||||
|
is_part_uploading BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
next_part_to_upload INTEGER NOT NULL DEFAULT 0,
|
||||||
|
upload_id CHARACTER VARYING(1024),
|
||||||
|
part_failures INTEGER NOT NULL DEFAULT 0,
|
||||||
|
discard BOOLEAN,
|
||||||
|
download_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
md5 CHARACTER VARYING(100),
|
||||||
|
length BIGINT,
|
||||||
|
client_id VARCHAR(64) NOT NULL,
|
||||||
|
file_offset BIGINT,
|
||||||
|
|
||||||
|
url VARCHAR(1024) NOT NULL,
|
||||||
|
fully_uploaded BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
upload_failures INTEGER NOT NULL DEFAULT 0,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE recorded_backing_tracks ALTER COLUMN id SET DEFAULT nextval('tracks_next_tracker_seq');
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE recorded_backing_tracks ADD COLUMN filename VARCHAR NOT NULL;
|
||||||
|
ALTER TABLE recorded_backing_tracks ADD COLUMN last_downloaded_at TIMESTAMP WITHOUT TIME ZONE;
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
ALTER TABLE recordings ADD COLUMN jam_track_id BIGINT REFERENCES jam_tracks(id);
|
||||||
|
ALTER TABLE recordings ADD COLUMN jam_track_initiator_id VARCHAR(64) REFERENCES users(id);
|
||||||
|
|
||||||
|
CREATE TABLE recorded_jam_track_tracks (
|
||||||
|
id BIGINT PRIMARY KEY,
|
||||||
|
user_id VARCHAR(64) NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
jam_track_track_id VARCHAR(64) REFERENCES jam_track_tracks(id) NOT NULL,
|
||||||
|
recording_id VARCHAR(64) REFERENCES recordings(id) NOT NULL,
|
||||||
|
discard BOOLEAN,
|
||||||
|
timeline JSON,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE recorded_jam_track_tracks ALTER COLUMN id SET DEFAULT nextval('tracks_next_tracker_seq');
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
ALTER TABLE recordings ADD COLUMN timeline JSON;
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
ALTER TABLE sale_line_items ADD COLUMN recurly_adjustment_uuid VARCHAR(500);
|
||||||
|
ALTER TABLE sale_line_items ADD COLUMN recurly_adjustment_credit_uuid VARCHAR(500);
|
||||||
|
ALTER TABLE jam_track_rights ADD COLUMN recurly_adjustment_uuid VARCHAR(500);
|
||||||
|
ALTER TABLE jam_track_rights ADD COLUMN recurly_adjustment_credit_uuid VARCHAR(500);
|
||||||
|
ALTER TABLE sales ADD COLUMN recurly_invoice_id VARCHAR(500) UNIQUE;
|
||||||
|
ALTER TABLE sales ADD COLUMN recurly_invoice_number INTEGER;
|
||||||
|
|
||||||
|
ALTER TABLE sales ADD COLUMN recurly_subtotal_in_cents INTEGER;
|
||||||
|
ALTER TABLE sales ADD COLUMN recurly_tax_in_cents INTEGER;
|
||||||
|
ALTER TABLE sales ADD COLUMN recurly_total_in_cents INTEGER;
|
||||||
|
ALTER TABLE sales ADD COLUMN recurly_currency VARCHAR;
|
||||||
|
|
||||||
|
ALTER TABLE sale_line_items ADD COLUMN recurly_tax_in_cents INTEGER;
|
||||||
|
ALTER TABLE sale_line_items ADD COLUMN recurly_total_in_cents INTEGER;
|
||||||
|
ALTER TABLE sale_line_items ADD COLUMN recurly_currency VARCHAR;
|
||||||
|
ALTER TABLE sale_line_items ADD COLUMN recurly_discount_in_cents INTEGER;
|
||||||
|
|
||||||
|
ALTER TABLE sales ADD COLUMN sale_type VARCHAR NOT NULL DEFAULT 'jamtrack';
|
||||||
|
|
||||||
|
ALTER TABLE recurly_transaction_web_hooks ALTER COLUMN subscription_id DROP NOT NULL;
|
||||||
|
|
||||||
|
CREATE INDEX recurly_transaction_web_hooks_invoice_id_ndx ON recurly_transaction_web_hooks(invoice_id);
|
||||||
|
|
||||||
|
ALTER TABLE jam_track_rights DROP COLUMN recurly_subscription_uuid;
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
ALTER TABLE jam_tracks DROP COLUMN bpm;
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
CREATE TABLE sales (
|
||||||
|
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||||
|
user_id VARCHAR(64) NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
order_total DECIMAL NOT NULL DEFAULT 0,
|
||||||
|
shipping_info JSON,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE sale_line_items (
|
||||||
|
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||||
|
product_type VARCHAR NOT NULL,
|
||||||
|
product_id VARCHAR(64),
|
||||||
|
unit_price DECIMAL NOT NULL,
|
||||||
|
quantity INTEGER NOT NULL,
|
||||||
|
free INTEGER NOT NULL,
|
||||||
|
sales_tax DECIMAL,
|
||||||
|
shipping_handling DECIMAL NOT NULL,
|
||||||
|
recurly_plan_code VARCHAR NOT NULL,
|
||||||
|
recurly_subscription_uuid VARCHAR,
|
||||||
|
sale_id VARCHAR(64) NOT NULL REFERENCES sales(id) ON DELETE CASCADE,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE recurly_transaction_web_hooks (
|
||||||
|
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||||
|
recurly_transaction_id VARCHAR NOT NULL,
|
||||||
|
transaction_type VARCHAR NOT NULL,
|
||||||
|
subscription_id VARCHAR NOT NULL,
|
||||||
|
action VARCHAR NOT NULL,
|
||||||
|
status VARCHAR NOT NULL,
|
||||||
|
amount_in_cents INT,
|
||||||
|
user_id VARCHAR(64) NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
invoice_id VARCHAR,
|
||||||
|
invoice_number_prefix VARCHAR,
|
||||||
|
invoice_number INTEGER,
|
||||||
|
message VARCHAR,
|
||||||
|
reference VARCHAR,
|
||||||
|
transaction_at TIMESTAMP NOT NULL,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX sale_line_items_recurly_subscription_uuid_ndx ON sale_line_items(recurly_subscription_uuid);
|
||||||
|
CREATE INDEX recurly_transaction_web_hooks_subscription_id_ndx ON recurly_transaction_web_hooks(subscription_id);
|
||||||
|
CREATE UNIQUE INDEX jam_track_rights_recurly_subscription_uuid_ndx ON jam_track_rights(recurly_subscription_uuid);
|
||||||
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE shopping_carts ALTER COLUMN user_id DROP NOT NULL;
|
||||||
|
ALTER TABLE shopping_carts ADD COLUMN anonymous_user_id VARCHAR(1000);
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
ALTER TABLE users ADD COLUMN show_whats_next_count INTEGER NOT NULL DEFAULT 0;
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
CREATE TABLE signup_hints (
|
||||||
|
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||||
|
anonymous_user_id VARCHAR(64) UNIQUE,
|
||||||
|
redirect_location VARCHAR,
|
||||||
|
want_jamblaster BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
user_id VARCHAR(64) REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
expires_at TIMESTAMP,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE users ADD COLUMN want_jamblaster BOOLEAN NOT NULL DEFAULT FALSE;
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
ALTER TABLE users ADD COLUMN reuse_card BOOLEAN DEFAULT TRUE NOT NULL;
|
||||||
|
ALTER TABLE users ADD COLUMN has_redeemable_jamtrack BOOLEAN DEFAULT TRUE NOT NULL;
|
||||||
|
ALTER TABLE shopping_carts ADD COLUMN marked_for_redeem INTEGER DEFAULT 0 NOT NULL;
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
|
||||||
|
DROP VIEW user_syncs;
|
||||||
|
|
||||||
|
CREATE VIEW user_syncs AS
|
||||||
|
SELECT DISTINCT b.id AS recorded_track_id,
|
||||||
|
CAST(NULL as BIGINT) AS mix_id,
|
||||||
|
CAST(NULL as BIGINT) AS quick_mix_id,
|
||||||
|
CAST(NULL as BIGINT) AS recorded_backing_track_id,
|
||||||
|
b.id AS unified_id,
|
||||||
|
a.user_id AS user_id,
|
||||||
|
b.fully_uploaded,
|
||||||
|
recordings.created_at AS created_at,
|
||||||
|
recordings.id AS recording_id
|
||||||
|
FROM recorded_tracks a INNER JOIN recordings ON a.recording_id = recordings.id AND duration IS NOT NULL AND all_discarded = FALSE AND deleted = FALSE INNER JOIN recorded_tracks b ON a.recording_id = b.recording_id
|
||||||
|
UNION ALL
|
||||||
|
SELECT CAST(NULL AS BIGINT) AS recorded_track_id,
|
||||||
|
CAST(NULL as BIGINT) AS mix_id,
|
||||||
|
CAST(NULL as BIGINT) AS quick_mix_id,
|
||||||
|
a.id AS recorded_backing_track_id,
|
||||||
|
a.id AS unified_id,
|
||||||
|
a.user_id AS user_id,
|
||||||
|
a.fully_uploaded,
|
||||||
|
recordings.created_at AS created_at,
|
||||||
|
recordings.id AS recording_id
|
||||||
|
FROM recorded_backing_tracks a INNER JOIN recordings ON a.recording_id = recordings.id AND duration IS NOT NULL AND all_discarded = FALSE AND deleted = FALSE
|
||||||
|
UNION ALL
|
||||||
|
SELECT CAST(NULL as BIGINT) AS recorded_track_id,
|
||||||
|
mixes.id AS mix_id,
|
||||||
|
CAST(NULL as BIGINT) AS quick_mix_id,
|
||||||
|
CAST(NULL as BIGINT) AS recorded_backing_track_id,
|
||||||
|
mixes.id AS unified_id,
|
||||||
|
claimed_recordings.user_id AS user_id,
|
||||||
|
NULL as fully_uploaded,
|
||||||
|
recordings.created_at AS created_at,
|
||||||
|
recordings.id AS recording_id
|
||||||
|
FROM mixes INNER JOIN recordings ON mixes.recording_id = recordings.id INNER JOIN claimed_recordings ON recordings.id = claimed_recordings.recording_id WHERE claimed_recordings.discarded = FALSE AND deleted = FALSE
|
||||||
|
UNION ALL
|
||||||
|
SELECT CAST(NULL as BIGINT) AS recorded_track_id,
|
||||||
|
CAST(NULL as BIGINT) AS mix_id,
|
||||||
|
quick_mixes.id AS quick_mix_id,
|
||||||
|
CAST(NULL as BIGINT) AS recorded_backing_track_id,
|
||||||
|
quick_mixes.id AS unified_id,
|
||||||
|
quick_mixes.user_id,
|
||||||
|
quick_mixes.fully_uploaded,
|
||||||
|
recordings.created_at AS created_at,
|
||||||
|
recordings.id AS recording_id
|
||||||
|
FROM quick_mixes INNER JOIN recordings ON quick_mixes.recording_id = recordings.id AND duration IS NOT NULL AND all_discarded = FALSE AND deleted = FALSE;
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
alter table user_authorizations ALTER COLUMN token TYPE character varying(2000);
|
||||||
|
|
@ -50,6 +50,7 @@ describe "Deployed site at #{www}", :js => true, :type => :feature, :capybara_fe
|
||||||
end
|
end
|
||||||
|
|
||||||
it "is possible for #{user3} to sign in and not get disconnected within 30 seconds" do
|
it "is possible for #{user3} to sign in and not get disconnected within 30 seconds" do
|
||||||
|
pending "continual failures - need to debug - try using Selenium instead of PhantomJS"
|
||||||
as_monitor(user3) do
|
as_monitor(user3) do
|
||||||
sign_in_poltergeist(user3)
|
sign_in_poltergeist(user3)
|
||||||
repeat_for(30.seconds) do
|
repeat_for(30.seconds) do
|
||||||
|
|
|
||||||
|
|
@ -430,7 +430,7 @@ def assert_all_tracks_seen(users=[])
|
||||||
users.each do |user|
|
users.each do |user|
|
||||||
in_client(user) do
|
in_client(user) do
|
||||||
users.reject {|u| u==user}.each do |other|
|
users.reject {|u| u==user}.each do |other|
|
||||||
find('div.track-label', text: other.name)
|
find('div.track-label > span', text: other.name)
|
||||||
#puts user.name + " is able to see " + other.name + "\'s track"
|
#puts user.name + " is able to see " + other.name + "\'s track"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ gem 'amqp', '1.0.2'
|
||||||
gem 'will_paginate'
|
gem 'will_paginate'
|
||||||
gem 'actionmailer', '3.2.13'
|
gem 'actionmailer', '3.2.13'
|
||||||
gem 'sendgrid', '1.2.0'
|
gem 'sendgrid', '1.2.0'
|
||||||
gem 'aws-sdk' #, '1.29.1'
|
gem 'aws-sdk', '~> 1'
|
||||||
gem 'carrierwave', '0.9.0'
|
gem 'carrierwave', '0.9.0'
|
||||||
gem 'aasm', '3.0.16'
|
gem 'aasm', '3.0.16'
|
||||||
gem 'devise', '3.3.0' # 3.4.0 causes: uninitialized constant ActionController::Metal (NameError)
|
gem 'devise', '3.3.0' # 3.4.0 causes: uninitialized constant ActionController::Metal (NameError)
|
||||||
|
|
@ -49,6 +49,7 @@ gem 'iso-639'
|
||||||
gem 'rubyzip'
|
gem 'rubyzip'
|
||||||
gem 'sanitize'
|
gem 'sanitize'
|
||||||
gem 'influxdb', '0.1.8'
|
gem 'influxdb', '0.1.8'
|
||||||
|
gem 'recurly'
|
||||||
|
|
||||||
group :test do
|
group :test do
|
||||||
gem 'simplecov', '~> 0.7.1'
|
gem 'simplecov', '~> 0.7.1'
|
||||||
|
|
|
||||||
|
|
@ -7,5 +7,3 @@ Create development database 'jam_ruby'
|
||||||
Once you've created your database, migrate it:
|
Once you've created your database, migrate it:
|
||||||
`bundle exec jam_ruby up`
|
`bundle exec jam_ruby up`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ require "jam_ruby/errors/jam_argument_error"
|
||||||
require "jam_ruby/errors/conflict_error"
|
require "jam_ruby/errors/conflict_error"
|
||||||
require "jam_ruby/lib/app_config"
|
require "jam_ruby/lib/app_config"
|
||||||
require "jam_ruby/lib/s3_manager_mixin"
|
require "jam_ruby/lib/s3_manager_mixin"
|
||||||
|
require "jam_ruby/lib/s3_public_manager_mixin"
|
||||||
require "jam_ruby/lib/module_overrides"
|
require "jam_ruby/lib/module_overrides"
|
||||||
require "jam_ruby/lib/s3_util"
|
require "jam_ruby/lib/s3_util"
|
||||||
require "jam_ruby/lib/s3_manager"
|
require "jam_ruby/lib/s3_manager"
|
||||||
|
|
@ -62,11 +63,13 @@ require "jam_ruby/resque/scheduled/stats_maker"
|
||||||
require "jam_ruby/resque/google_analytics_event"
|
require "jam_ruby/resque/google_analytics_event"
|
||||||
require "jam_ruby/resque/batch_email_job"
|
require "jam_ruby/resque/batch_email_job"
|
||||||
require "jam_ruby/mq_router"
|
require "jam_ruby/mq_router"
|
||||||
|
require "jam_ruby/recurly_client"
|
||||||
require "jam_ruby/base_manager"
|
require "jam_ruby/base_manager"
|
||||||
require "jam_ruby/connection_manager"
|
require "jam_ruby/connection_manager"
|
||||||
require "jam_ruby/version"
|
require "jam_ruby/version"
|
||||||
require "jam_ruby/environment"
|
require "jam_ruby/environment"
|
||||||
require "jam_ruby/init"
|
require "jam_ruby/init"
|
||||||
|
require "jam_ruby/app/mailers/admin_mailer"
|
||||||
require "jam_ruby/app/mailers/user_mailer"
|
require "jam_ruby/app/mailers/user_mailer"
|
||||||
require "jam_ruby/app/mailers/invited_user_mailer"
|
require "jam_ruby/app/mailers/invited_user_mailer"
|
||||||
require "jam_ruby/app/mailers/corp_mailer"
|
require "jam_ruby/app/mailers/corp_mailer"
|
||||||
|
|
@ -87,6 +90,7 @@ require "jam_ruby/lib/stats.rb"
|
||||||
require "jam_ruby/amqp/amqp_connection_manager"
|
require "jam_ruby/amqp/amqp_connection_manager"
|
||||||
require "jam_ruby/database"
|
require "jam_ruby/database"
|
||||||
require "jam_ruby/message_factory"
|
require "jam_ruby/message_factory"
|
||||||
|
require "jam_ruby/models/backing_track"
|
||||||
require "jam_ruby/models/feedback"
|
require "jam_ruby/models/feedback"
|
||||||
require "jam_ruby/models/feedback_observer"
|
require "jam_ruby/models/feedback_observer"
|
||||||
#require "jam_ruby/models/max_mind_geo"
|
#require "jam_ruby/models/max_mind_geo"
|
||||||
|
|
@ -95,6 +99,8 @@ require "jam_ruby/models/max_mind_release"
|
||||||
require "jam_ruby/models/genre_player"
|
require "jam_ruby/models/genre_player"
|
||||||
require "jam_ruby/models/genre"
|
require "jam_ruby/models/genre"
|
||||||
require "jam_ruby/models/user"
|
require "jam_ruby/models/user"
|
||||||
|
require "jam_ruby/models/anonymous_user"
|
||||||
|
require "jam_ruby/models/signup_hint"
|
||||||
require "jam_ruby/models/rsvp_request"
|
require "jam_ruby/models/rsvp_request"
|
||||||
require "jam_ruby/models/rsvp_slot"
|
require "jam_ruby/models/rsvp_slot"
|
||||||
require "jam_ruby/models/rsvp_request_rsvp_slot"
|
require "jam_ruby/models/rsvp_request_rsvp_slot"
|
||||||
|
|
@ -132,8 +138,12 @@ require "jam_ruby/models/search"
|
||||||
require "jam_ruby/models/recording"
|
require "jam_ruby/models/recording"
|
||||||
require "jam_ruby/models/recording_comment"
|
require "jam_ruby/models/recording_comment"
|
||||||
require "jam_ruby/models/recording_liker"
|
require "jam_ruby/models/recording_liker"
|
||||||
|
require "jam_ruby/models/recorded_backing_track"
|
||||||
|
require "jam_ruby/models/recorded_backing_track_observer"
|
||||||
require "jam_ruby/models/recorded_track"
|
require "jam_ruby/models/recorded_track"
|
||||||
require "jam_ruby/models/recorded_track_observer"
|
require "jam_ruby/models/recorded_track_observer"
|
||||||
|
require "jam_ruby/models/recorded_video"
|
||||||
|
require "jam_ruby/models/recorded_jam_track_track"
|
||||||
require "jam_ruby/models/quick_mix"
|
require "jam_ruby/models/quick_mix"
|
||||||
require "jam_ruby/models/quick_mix_observer"
|
require "jam_ruby/models/quick_mix_observer"
|
||||||
require "jam_ruby/models/share_token"
|
require "jam_ruby/models/share_token"
|
||||||
|
|
@ -197,9 +207,13 @@ require "jam_ruby/models/score_history"
|
||||||
require "jam_ruby/models/jam_company"
|
require "jam_ruby/models/jam_company"
|
||||||
require "jam_ruby/models/user_sync"
|
require "jam_ruby/models/user_sync"
|
||||||
require "jam_ruby/models/video_source"
|
require "jam_ruby/models/video_source"
|
||||||
require "jam_ruby/models/recorded_video"
|
|
||||||
require "jam_ruby/models/text_message"
|
require "jam_ruby/models/text_message"
|
||||||
|
require "jam_ruby/models/sale"
|
||||||
|
require "jam_ruby/models/sale_line_item"
|
||||||
|
require "jam_ruby/models/recurly_transaction_web_hook"
|
||||||
require "jam_ruby/jam_tracks_manager"
|
require "jam_ruby/jam_tracks_manager"
|
||||||
|
require "jam_ruby/jam_track_importer"
|
||||||
|
require "jam_ruby/jmep_manager"
|
||||||
|
|
||||||
include Jampb
|
include Jampb
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
module JamRuby
|
||||||
|
# sends out a boring ale
|
||||||
|
class AdminMailer < ActionMailer::Base
|
||||||
|
include SendGrid
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_SENDER = "JamKazam <noreply@jamkazam.com>"
|
||||||
|
|
||||||
|
default :from => DEFAULT_SENDER
|
||||||
|
|
||||||
|
sendgrid_category :use_subject_lines
|
||||||
|
#sendgrid_enable :opentrack, :clicktrack # this makes our emails creepy, imo (seth)
|
||||||
|
sendgrid_unique_args :env => Environment.mode
|
||||||
|
|
||||||
|
def alerts(options)
|
||||||
|
mail(to: APP_CONFIG.email_alerts_alias,
|
||||||
|
body: options[:body],
|
||||||
|
content_type: "text/plain",
|
||||||
|
subject: options[:subject])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -11,7 +11,7 @@ class JamTrackTrackUploader < CarrierWave::Uploader::Base
|
||||||
|
|
||||||
# Add a white list of extensions which are allowed to be uploaded.
|
# Add a white list of extensions which are allowed to be uploaded.
|
||||||
def extension_white_list
|
def extension_white_list
|
||||||
%w(ogg)
|
%w(ogg wav)
|
||||||
end
|
end
|
||||||
|
|
||||||
def store_dir
|
def store_dir
|
||||||
|
|
@ -23,6 +23,8 @@ class JamTrackTrackUploader < CarrierWave::Uploader::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def filename
|
def filename
|
||||||
"#{model.store_dir}/#{model.filename}" if model.id
|
if model.id && !model.skip_uploader
|
||||||
|
model.manually_uploaded_filename(mounted_as)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ class JamTrackUploader < CarrierWave::Uploader::Base
|
||||||
|
|
||||||
# Add a white list of extensions which are allowed to be uploaded.
|
# Add a white list of extensions which are allowed to be uploaded.
|
||||||
def extension_white_list
|
def extension_white_list
|
||||||
%w(jkz)
|
%w(ogg)
|
||||||
end
|
end
|
||||||
|
|
||||||
def store_dir
|
def store_dir
|
||||||
|
|
@ -23,6 +23,6 @@ class JamTrackUploader < CarrierWave::Uploader::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def filename
|
def filename
|
||||||
"#{model.store_dir}/#{model.filename}" if model.id
|
"#{model.preview_filename}" if model.id && model.uploading_preview
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,45 +1,64 @@
|
||||||
<% provide(:title, 'Welcome to JamKazam!') %>
|
<% provide(:title, 'Welcome to JamKazam!') %>
|
||||||
|
|
||||||
|
|
||||||
<p>Hello <%= EmailBatchProgression::VAR_FIRST_NAME %> --
|
<p>Hello <%= EmailBatchProgression::VAR_FIRST_NAME %> --
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
<p> We're delighted that you have decided to try the JamKazam service,
|
<p> We're delighted that you have decided to try the JamKazam service,
|
||||||
and we hope that you will enjoy using JamKazam to play music with others.
|
and we hope that you will enjoy using JamKazam to play
|
||||||
Following are links to some resources that can help to get you up and running quickly.
|
music with others.
|
||||||
|
Following are some resources that can help you get oriented and get the most out of JamKazam.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<p>
|
||||||
|
|
||||||
|
<p><b style="color: white;">Getting Started</b><br/>
|
||||||
|
There are basically three kinds of setups you can use to play on JamKazam.<br/>
|
||||||
|
<ul>
|
||||||
|
<li><b style="color: white;">Built-In Audio on Your Computer</b> - You can use a Windows or Mac computer, and just use the built-in mic and headphone jack to
|
||||||
|
handle your audio. This is cheap and easy, but your audio quality will suffer, and it will also process audio very slowly,
|
||||||
|
creating problems with latency, or lag, in your sessions. Still, you can at least start experimenting with JamKazam in this way.</li>
|
||||||
|
<li><b style="color: white;">Computer with External Audio Interface</b> - You can use a Windows or Mac computer with an external audio interface that you
|
||||||
|
already own and use for recording, if you happen to have one already. If you are going to do this, or use the built-in mic/headphones on your computer, please refer
|
||||||
|
to our <a style="color: #ffcc00;" href="https://jamkazam.desk.com/customer/portal/articles/1288274-minimum-system-requirements">Minimum System Requirements</a>
|
||||||
|
to make sure your computer will work. These requirements were on the download page for the app, but you may have sped by them. Also, we'd recommend watching our
|
||||||
|
<a style="color: #ffcc00;" href="https://www.youtube.com/watch?v=DBo--aj_P1w">Getting Started Video</a> to learn more about your options here.</li>
|
||||||
|
<li><b style="color: white;">The JamBlaster</b> - JamKazam has designed a new product from the ground up to be the best way to play music online in real time. It's called the JamBlaster.
|
||||||
|
It processes audio faster than any of the thousands of combinations of computers and interfaces in use on JamKazam today, which means you can play with musicians
|
||||||
|
who are farther away from you, and closer sessions will feel/sound tighter. The JamBlaster is both a computer and an audio interface, so it also eliminates the
|
||||||
|
system requirements worries, and it "just works" so you don't have to be an audio and computer genius to get it working. This is a great product - available only
|
||||||
|
through a Kickstarter program running during a 30-day window during parts of February and March 2015. You can watch the
|
||||||
|
<a style="color: #ffcc00;" href="https://www.youtube.com/watch?v=gAJAIHMyois">JamBlaster Video</a> to learn more about this amazing new product.</li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p><b style="color: white;">JamKazam Features</b><br/>
|
||||||
|
JamKazam offers a very robust and exciting set of features for playing online and sharing your performances with others. Here are some videos you can watch
|
||||||
|
to easily get up to speed on some of the things you can do with JamKazam:<br/>
|
||||||
|
<ul>
|
||||||
|
<li><a style="color: #ffcc00;" href="https://www.youtube.com/watch?v=EZZuGcDUoWk">Creating a Session</a></li>
|
||||||
|
<li><a style="color: #ffcc00;" href="https://www.youtube.com/watch?v=xWponSJo-GU">Finding a Session</a></li>
|
||||||
|
<li><a style="color: #ffcc00;" href="https://www.youtube.com/watch?v=zJ68hA8-fLA">Playing in a Session</a></li>
|
||||||
|
<li><a style="color: #ffcc00;" href="https://www.youtube.com/watch?v=4KWklSZZxRc">Connecting with Other Musicians</a></li>
|
||||||
|
<li><a style="color: #ffcc00;" href="https://www.youtube.com/watch?v=Gn-dOqnNLoY">Working with Recordings</a></li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p><b style="color: white;">Getting Help</b><br/>
|
||||||
|
If you run into trouble and need help, please reach out to us. We will be glad to do everything we can to answer your questions and get you up and running.
|
||||||
|
You can visit our
|
||||||
|
<a style="color: #ffcc00;" href="https://jamkazam.desk.com/">Support Portal</a>
|
||||||
|
to find knowledge base articles and post questions that have
|
||||||
|
not already been answered. You can email us at support@jamkazam.com. And if you just want to chat, share tips and war stories, and hang out with fellow JamKazamers,
|
||||||
|
you can visit our <a style="color: #ffcc00;" href="http://forums.jamkazam.com/">Community Forum</a>
|
||||||
|
.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Getting Started Video<br/>
|
Again, welcome to JamKazam, and we look forward to seeing – and hearing – you online soon!
|
||||||
We recommend watching this video before you jump into the service just to get oriented. It will really help you hit the ground running:
|
|
||||||
<a style="color: #ffcc00;" href="https://www.youtube.com/watch?v=DBo--aj_P1w">https://www.youtube.com/watch?v=DBo--aj_P1w</a>
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>Best Regards,<br/>
|
||||||
Other Great Tutorial Videos<br />
|
Team JamKazam</p>
|
||||||
There are several other very great videos that will help you understand how to find and connect with other musicians on the service, create your own sessions or find and join other musicians’ sessions, play in sessions, record and share your performances, and even live broadcast your sessions to family, friends, and fans. Check these helpful videos out here:
|
|
||||||
<a style="color: #ffcc00;" href="https://jamkazam.desk.com/customer/portal/topics/673198-tutorials-on-major-features/articles">https://jamkazam.desk.com/customer/portal/topics/673198-tutorials-on-major-features/articles</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Knowledge Base Articles<br />
|
|
||||||
You can find Getting Started knowledge base articles on things like frequently asked questions (FAQ), minimum system requirements for your Windows or Mac computer, how to troubleshoot audio problems in sessions, and more here:
|
|
||||||
<a style="color: #ffcc00;" href="https://jamkazam.desk.com/customer/portal/topics/564807-getting-started/articles">https://jamkazam.desk.com/customer/portal/topics/564807-getting-started/articles</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
JamKazam Support Portal<br />
|
|
||||||
If you run into trouble and need help, please reach out to us. We will be glad to do everything we can to get you up and running. You can find our support portal here:
|
|
||||||
<a style="color: #ffcc00;" href="https://jamkazam.desk.com/">https://jamkazam.desk.com/</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
JamKazam Community Forum<br />
|
|
||||||
And if you just want to chat, share tips and war stories, and hang out with fellow JamKazamers, you can visit our community forum here:
|
|
||||||
<a style="color: #ffcc00;" href="http://forums.jamkazam.com/">http://forums.jamkazam.com/</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Please take a moment to like or follow us by clicking the icons below, and we look forward to seeing – and hearing – you online soon!
|
|
||||||
</p>
|
|
||||||
|
|
||||||
-- Team JamKazam
|
|
||||||
|
|
@ -1,27 +1,43 @@
|
||||||
Hello <%= EmailBatchProgression::VAR_FIRST_NAME %> --
|
Hello <%= EmailBatchProgression::VAR_FIRST_NAME %> --
|
||||||
|
|
||||||
We're delighted that you have decided to try the JamKazam service, and we hope that you will enjoy using JamKazam to play music with others. Following are links to some resources that can help to get you up and running quickly.
|
We're delighted that you have decided to try the JamKazam service, and we hope that you will enjoy using JamKazam to play music with others. Following are some resources that can help you get oriented and get the most out of JamKazam.
|
||||||
|
|
||||||
Getting Started Video
|
|
||||||
We recommend watching this video before you jump into the service just to get oriented. It will really help you hit the ground running:
|
|
||||||
https://www.youtube.com/watch?v=DBo--aj_P1w
|
|
||||||
|
|
||||||
Other Great Tutorial Videos
|
Getting Started
|
||||||
There are several other very great videos that will help you understand how to find and connect with other musicians on the service, create your own sessions or find and join other musicians’ sessions, play in sessions, record and share your performances, and even live broadcast your sessions to family, friends, and fans. Check these helpful videos out here:
|
---------------
|
||||||
https://jamkazam.desk.com/customer/portal/topics/673198-tutorials-on-major-features/articles
|
|
||||||
|
|
||||||
Knowledge Base Articles
|
There are basically three kinds of setups you can use to play on JamKazam.
|
||||||
You can find Getting Started knowledge base articles on things like frequently asked questions (FAQ), minimum system requirements for your Windows or Mac computer, how to troubleshoot audio problems in sessions, and more here:
|
|
||||||
https://jamkazam.desk.com/customer/portal/topics/564807-getting-started/articles
|
|
||||||
|
|
||||||
JamKazam Support Portal
|
* Built-In Audio on Your Computer - You can use a Windows or Mac computer, and just use the built-in mic and headphone jack to handle your audio. This is cheap and easy, but your audio quality will suffer, and it will also process audio very slowly, creating problems with latency, or lag, in your sessions. Still, you can at least start experimenting with JamKazam in this way.
|
||||||
If you run into trouble and need help, please reach out to us. We will be glad to do everything we can to get you up and running. You can find our support portal here:
|
|
||||||
https://jamkazam.desk.com
|
|
||||||
|
|
||||||
JamKazam Community Forum
|
* Computer with External Audio Interface - - You can use a Windows or Mac computer with an external audio interface that you already own and use for recording, if you happen to have one already. If you are going to do this, or use the built-in mic/headphones on your computer, please refer to our Minimum System Requirements at https://jamkazam.desk.com/customer/portal/articles/1288274-minimum-system-requirements to make sure your computer will work. These requirements were on the download page for the app, but you may have sped by them. Also, we'd recommend watching our Getting Started Video at https://www.youtube.com/watch?v=DBo--aj_P1w to learn more about your options here.
|
||||||
And if you just want to chat, share tips and war stories, and hang out with fellow JamKazamers, you can visit our community forum here:
|
|
||||||
http://forums.jamkazam.com
|
|
||||||
|
|
||||||
Please take a moment to like or follow us by clicking the icons below, and we look forward to seeing – and hearing – you online soon!
|
* The JamBlaster - JamKazam has designed a new product from the ground up to be the best way to play music online in real time. It's called the JamBlaster. It processes audio faster than any of the thousands of combinations of computers and interfaces in use on JamKazam today, which means you can play with musicians who are farther away from you, and closer sessions will feel/sound tighter. The JamBlaster is both a computer and an audio interface, so it also eliminates the system requirements worries, and it "just works" so you don't have to be an audio and computer genius to get it working. This is a great product - available only through a Kickstarter program running during a 30-day window during parts of February and March 2015. You can watch the JamBlaster Video at https://www.youtube.com/watch?v=gAJAIHMyois to learn more about this amazing new product.
|
||||||
|
|
||||||
|
|
||||||
|
JamKazam Features
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
JamKazam offers a very robust and exciting set of features for playing online and sharing your performances with others. Here are some videos you can watch to easily get up to speed on some of the things you can do with JamKazam:
|
||||||
|
|
||||||
|
* Creating a Session - https://www.youtube.com/watch?v=EZZuGcDUoWk
|
||||||
|
|
||||||
|
* Finding a Session - https://www.youtube.com/watch?v=xWponSJo-GU
|
||||||
|
|
||||||
|
* Playing in a Session - https://www.youtube.com/watch?v=zJ68hA8-fLA
|
||||||
|
|
||||||
|
* Connecting with Other Musicians - https://www.youtube.com/watch?v=4KWklSZZxRc
|
||||||
|
|
||||||
|
* Working with Recordings - https://www.youtube.com/watch?v=Gn-dOqnNLoY
|
||||||
|
|
||||||
|
|
||||||
|
Getting Help
|
||||||
|
------------
|
||||||
|
|
||||||
|
If you run into trouble and need help, please reach out to us. We will be glad to do everything we can to answer your questions and get you up and running. You can visit our Support Portal at https://jamkazam.desk.com/ to find knowledge base articles and post questions that have not already been answered. You can email us at support@jamkazam.com. And if you just want to chat, share tips and war stories, and hang out with fellow JamKazamers, you can visit our Community Forum at http://forums.jamkazam.com/.
|
||||||
|
|
||||||
|
Again, welcome to JamKazam, and we look forward to seeing – and hearing – you online soon!
|
||||||
|
|
||||||
|
Best Regards,
|
||||||
|
Team JamKazam
|
||||||
|
|
||||||
-- Team JamKazam
|
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@
|
||||||
<td align="left">
|
<td align="left">
|
||||||
|
|
||||||
<!-- CALL OUT BOX -->
|
<!-- CALL OUT BOX -->
|
||||||
<p style="margin-top:0px"><font size="2" color="#7FACBA" face="Arial, Helvetica, sans-serif">This email was sent to you because you have an account at <a style="color: #ffcc00;" href="http://www.jamkazam.com">JamKazam</a>. Click <a style="color: #ffcc00;" href="http://www.jamkazam.com/client#/account/profile">here to unsubscribe</a> and update your profile settings.
|
<p style="margin-top:0px"><font size="2" color="#7FACBA" face="Arial, Helvetica, sans-serif">This email was sent to you because you have an account at <a style="color: #ffcc00;" href="http://www.jamkazam.com">JamKazam</a>. Click <a style="color: #ffcc00;" href="http://www.jamkazam.com/unsubscribe/#{@user.unsubscribe_token}">here to unsubscribe</a> and update your profile settings.
|
||||||
</font></p>
|
</font></p>
|
||||||
</td></tr></table>
|
</td></tr></table>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@
|
||||||
<%= yield %>
|
<%= yield %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<% unless @suppress_user_has_account_footer == true %>
|
<% unless @user.nil? || @suppress_user_has_account_footer == true %>
|
||||||
This email was sent to you because you have an account at JamKazam / http://www.jamkazam.com. Visit your profile page to unsubscribe: http://www.jamkazam.com/client#/account/profile.
|
This email was sent to you because you have an account at JamKazam / http://www.jamkazam.com. To unsubscribe: http://www.jamkazam.com/unsubscribe/<%=@user.unsubscribe_token%>.
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
Copyright <%= Time.now.year %> JamKazam, Inc. All rights reserved.
|
Copyright <%= Time.now.year %> JamKazam, Inc. All rights reserved.
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ module JamRuby
|
||||||
udp_reachable_value = udp_reachable.nil? ? 'udp_reachable' : udp_reachable
|
udp_reachable_value = udp_reachable.nil? ? 'udp_reachable' : udp_reachable
|
||||||
|
|
||||||
sql =<<SQL
|
sql =<<SQL
|
||||||
UPDATE connections SET (channel_id, aasm_state, updated_at, music_session_id, joined_session_at, stale_time, expire_time, udp_reachable, gateway, is_network_testing) = ('#{channel_id}', '#{Connection::CONNECT_STATE.to_s}', NOW(), #{music_session_id_expression}, #{joined_session_at_expression}, #{connection_stale_time}, #{connection_expire_time}, #{udp_reachable_value}, '#{gateway}', FALSE)
|
UPDATE connections SET (channel_id, aasm_state, updated_at, music_session_id, joined_session_at, stale_time, expire_time, udp_reachable, gateway, is_network_testing, metronome_open) = ('#{channel_id}', '#{Connection::CONNECT_STATE.to_s}', NOW(), #{music_session_id_expression}, #{joined_session_at_expression}, #{connection_stale_time}, #{connection_expire_time}, #{udp_reachable_value}, '#{gateway}', FALSE, FALSE)
|
||||||
WHERE
|
WHERE
|
||||||
client_id = '#{conn.client_id}'
|
client_id = '#{conn.client_id}'
|
||||||
RETURNING music_session_id
|
RETURNING music_session_id
|
||||||
|
|
@ -311,14 +311,19 @@ SQL
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
# there are still people in the session
|
|
||||||
|
conn.exec("UPDATE active_music_sessions set backing_track_initiator_id = NULL, backing_track_path = NULL where backing_track_initiator_id = $1 and id = $2",
|
||||||
|
[user_id, previous_music_session_id])
|
||||||
|
|
||||||
|
conn.exec("UPDATE active_music_sessions set metronome_initiator_id = NULL, metronome_active = FALSE where metronome_initiator_id = $1 and id = $2",
|
||||||
|
[user_id, previous_music_session_id])
|
||||||
|
|
||||||
#ensure that there is no active claimed recording if the owner of that recording left the session
|
#ensure that there is no active claimed recording if the owner of that recording left the session
|
||||||
conn.exec("UPDATE active_music_sessions set claimed_recording_id = NULL, claimed_recording_initiator_id = NULL where claimed_recording_initiator_id = $1 and id = $2",
|
conn.exec("UPDATE active_music_sessions set claimed_recording_id = NULL, claimed_recording_initiator_id = NULL where claimed_recording_initiator_id = $1 and id = $2",
|
||||||
[user_id, previous_music_session_id])
|
[user_id, previous_music_session_id])
|
||||||
|
|
||||||
conn.exec("UPDATE active_music_sessions set jam_track_id = NULL, jam_track_initiator_id = NULL where jam_track_initiator_id = $1 and id = $2",
|
conn.exec("UPDATE active_music_sessions set jam_track_id = NULL, jam_track_initiator_id = NULL where jam_track_initiator_id = $1 and id = $2",
|
||||||
[user_id, previous_music_session_id])
|
[user_id, previous_music_session_id])
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -47,5 +47,6 @@ module NotificationTypes
|
||||||
|
|
||||||
# Jam Tracks:
|
# Jam Tracks:
|
||||||
JAM_TRACK_SIGN_COMPLETE = "JAM_TRACK_SIGN_COMPLETE"
|
JAM_TRACK_SIGN_COMPLETE = "JAM_TRACK_SIGN_COMPLETE"
|
||||||
|
JAM_TRACK_SIGN_FAILED = "JAM_TRACK_SIGN_FAILED"
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
@ -82,6 +82,8 @@ module ValidationMessages
|
||||||
MUST_BE_KNOWN_TIMEZONE = "not valid"
|
MUST_BE_KNOWN_TIMEZONE = "not valid"
|
||||||
JAM_TRACK_ALREADY_OPEN = 'another jam track already open'
|
JAM_TRACK_ALREADY_OPEN = 'another jam track already open'
|
||||||
RECORDING_ALREADY_IN_PROGRESS = "recording being made"
|
RECORDING_ALREADY_IN_PROGRESS = "recording being made"
|
||||||
|
METRONOME_ALREADY_OPEN = 'another metronome already open'
|
||||||
|
BACKING_TRACK_ALREADY_OPEN = 'another audio file already open'
|
||||||
|
|
||||||
# notification
|
# notification
|
||||||
DIFFERENT_SOURCE_TARGET = 'can\'t be same as the sender'
|
DIFFERENT_SOURCE_TARGET = 'can\'t be same as the sender'
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,18 @@
|
||||||
# initialize actionmailer
|
# initialize actionmailer
|
||||||
ActionMailer::Base.raise_delivery_errors = true
|
ActionMailer::Base.raise_delivery_errors = true
|
||||||
ActionMailer::Base.view_paths = File.expand_path('../../jam_ruby/app/views/', __FILE__)
|
ActionMailer::Base.view_paths = File.expand_path('../../jam_ruby/app/views/', __FILE__)
|
||||||
|
|
||||||
|
# Use Private API Keys to communicate with Recurly's API v2. See https://docs.recurly.com/api/basics/authentication to learn more.
|
||||||
|
case JamRuby::Environment.mode
|
||||||
|
when 'production'
|
||||||
|
Recurly.api_key = "7d623daabfc2434fa2a893bb008eb3e6"
|
||||||
|
Recurly.subdomain = 'jamkazam'
|
||||||
|
when 'development'
|
||||||
|
Recurly.api_key = "7d623daabfc2434fa2a893bb008eb3e6"
|
||||||
|
Recurly.subdomain = 'jamkazam-development'
|
||||||
|
else
|
||||||
|
Recurly.api_key = "4631527f203b41848523125b3ae51341"
|
||||||
|
Recurly.subdomain = 'jamkazam-test'
|
||||||
|
end
|
||||||
|
|
||||||
|
Recurly.default_currency = 'USD'
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,904 @@
|
||||||
|
require 'json'
|
||||||
|
require 'tempfile'
|
||||||
|
require 'open3'
|
||||||
|
require 'fileutils'
|
||||||
|
require 'open-uri'
|
||||||
|
require 'yaml'
|
||||||
|
|
||||||
|
module JamRuby
|
||||||
|
|
||||||
|
class JamTrackImporter
|
||||||
|
|
||||||
|
@@log = Logging.logger[JamTrackImporter]
|
||||||
|
|
||||||
|
attr_accessor :name
|
||||||
|
attr_accessor :reason
|
||||||
|
attr_accessor :detail
|
||||||
|
|
||||||
|
def jamkazam_s3_manager
|
||||||
|
@s3_manager ||= S3Manager.new(APP_CONFIG.aws_bucket, APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key)
|
||||||
|
end
|
||||||
|
|
||||||
|
def public_jamkazam_s3_manager
|
||||||
|
@public_s3_manager ||= S3Manager.new(APP_CONFIG.aws_bucket_public, APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key)
|
||||||
|
end
|
||||||
|
|
||||||
|
def finish(reason, detail)
|
||||||
|
self.reason = reason
|
||||||
|
self.detail = detail
|
||||||
|
end
|
||||||
|
|
||||||
|
def dry_run(metadata, metalocation)
|
||||||
|
metadata ||= {}
|
||||||
|
|
||||||
|
parsed_metalocation = parse_metalocation(metalocation)
|
||||||
|
|
||||||
|
return unless parsed_metalocation
|
||||||
|
|
||||||
|
original_artist = parsed_metalocation[1]
|
||||||
|
name = parsed_metalocation[2]
|
||||||
|
|
||||||
|
success = dry_run_metadata(metadata, original_artist, name)
|
||||||
|
|
||||||
|
return unless success
|
||||||
|
|
||||||
|
dry_run_audio(metadata, "audio/#{original_artist}/#{name}")
|
||||||
|
|
||||||
|
finish("success", nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_metalocation(metalocation)
|
||||||
|
|
||||||
|
bits = metalocation.split('/')
|
||||||
|
|
||||||
|
if bits.length != 4
|
||||||
|
finish("invalid_metalocation", "metalocation not valid #{metalocation}")
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
if bits[0] != "audio"
|
||||||
|
finish("invalid_metalocation", "first bit is not 'audio' #{metalocation}")
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
if bits[3] != 'meta.yml'
|
||||||
|
finish('invalid_metalocation', "last bit is not 'meta.yml' #{metalocation}")
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
bits
|
||||||
|
end
|
||||||
|
|
||||||
|
# if you change this, it will (at least without some work )break development usage of jamtracks
|
||||||
|
def gen_plan_code(original_artist, name)
|
||||||
|
# remove all non-alphanumeric chars from artist as well as name
|
||||||
|
artist_code = original_artist.gsub(/[^0-9a-z]/i, '').downcase
|
||||||
|
name_code = name.gsub(/[^0-9a-z]/i, '').downcase
|
||||||
|
"jamtrack-#{artist_code[0...20]}-#{name_code}"[0...50] # make sure it's a max of 50 long
|
||||||
|
end
|
||||||
|
|
||||||
|
def dry_run_metadata(metadata, original_artist, name)
|
||||||
|
|
||||||
|
self.name = metadata["name"] || name
|
||||||
|
|
||||||
|
original_artist = metadata["original_artist"] || original_artist
|
||||||
|
plan_code = metadata["plan_code"] || gen_plan_code(original_artist, self.name)
|
||||||
|
description = metadata["description"]
|
||||||
|
|
||||||
|
@@log.debug("#{self.name} original_artist=#{original_artist}")
|
||||||
|
@@log.debug("#{self.name} plan_code=#{plan_code}")
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def synchronize_metadata(jam_track, metadata, metalocation, original_artist, name)
|
||||||
|
|
||||||
|
metadata ||= {}
|
||||||
|
self.name = metadata["name"] || name
|
||||||
|
|
||||||
|
if jam_track.new_record?
|
||||||
|
jam_track.id = "#{JamTrack.count + 1}" # default is UUID, but the initial import was based on auto-increment ID, so we'll maintain that
|
||||||
|
jam_track.status = 'Staging'
|
||||||
|
jam_track.metalocation = metalocation
|
||||||
|
jam_track.original_artist = metadata["original_artist"] || original_artist
|
||||||
|
jam_track.name = self.name
|
||||||
|
jam_track.genre_id = 'rock'
|
||||||
|
jam_track.plan_code = metadata["plan_code"] || gen_plan_code(jam_track.original_artist, jam_track.name)
|
||||||
|
jam_track.price = 1.99
|
||||||
|
jam_track.reproduction_royalty_amount = 0
|
||||||
|
jam_track.licensor_royalty_amount = 0
|
||||||
|
jam_track.sales_region = 'United States'
|
||||||
|
jam_track.recording_type = 'Cover'
|
||||||
|
jam_track.description = "This is a JamTrack audio file for use exclusively with the JamKazam service. This JamTrack is a high quality cover of the #{jam_track.original_artist} song \"#{jam_track.name}\"."
|
||||||
|
else
|
||||||
|
#@@log.debug("#{self.name} skipped because it already exists in database")
|
||||||
|
finish("jam_track_exists", "")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
saved = jam_track.save
|
||||||
|
|
||||||
|
if !saved
|
||||||
|
finish("invalid_definition", jam_track.errors.inspect)
|
||||||
|
end
|
||||||
|
|
||||||
|
saved
|
||||||
|
end
|
||||||
|
|
||||||
|
# oddballs - Guitar Solo.wav
|
||||||
|
# Rocket Man Stem - Vocal Back Up
|
||||||
|
# Rocket Man Stem - Vocal Lead Double
|
||||||
|
# Rock and Roll Stem - Electric Guitar - Main - Solo
|
||||||
|
def determine_instrument(potential_instrument_original, potential_part_original = nil)
|
||||||
|
potential_instrument = potential_instrument_original.downcase
|
||||||
|
potential_part = potential_part_original.downcase if potential_part_original
|
||||||
|
|
||||||
|
instrument = nil
|
||||||
|
used_helper = false
|
||||||
|
part = nil
|
||||||
|
|
||||||
|
if potential_instrument == 'guitar'
|
||||||
|
if potential_part
|
||||||
|
if potential_part == 'acoustic'
|
||||||
|
instrument = 'acoustic guitar'
|
||||||
|
used_helper = true
|
||||||
|
elsif potential_part == 'electric'
|
||||||
|
instrument = 'electric guitar'
|
||||||
|
used_helper = true
|
||||||
|
elsif potential_part == 'acoustic solo'
|
||||||
|
instrument = 'acoustic guitar'
|
||||||
|
used_helper = true
|
||||||
|
part = 'Solo'
|
||||||
|
elsif potential_part.include?('acoustic')
|
||||||
|
used_helper = true # ambiguous
|
||||||
|
else
|
||||||
|
instrument = 'electric guitar'
|
||||||
|
used_helper = false
|
||||||
|
end
|
||||||
|
else
|
||||||
|
instrument = 'electric guitar'
|
||||||
|
end
|
||||||
|
elsif potential_instrument == 'electric gutiar' || potential_instrument == 'electric guitat'
|
||||||
|
instrument = 'electric guitar'
|
||||||
|
elsif potential_instrument == 'keys'
|
||||||
|
instrument = 'keyboard'
|
||||||
|
elsif potential_instrument == 'vocal' || potential_instrument == 'vocals'
|
||||||
|
instrument = 'voice'
|
||||||
|
elsif potential_instrument == 'bass'
|
||||||
|
instrument = 'bass guitar'
|
||||||
|
elsif potential_instrument == 'drum'
|
||||||
|
instrument = 'drums'
|
||||||
|
elsif potential_instrument == 'sound effects' || potential_instrument == 'sound efx' || potential_instrument == 'effects'
|
||||||
|
instrument = 'computer'
|
||||||
|
|
||||||
|
if potential_part_original
|
||||||
|
part = "Sound FX (#{potential_part_original})"
|
||||||
|
else
|
||||||
|
part = 'Sound FX'
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
elsif potential_instrument == "sax"
|
||||||
|
instrument = 'saxophone'
|
||||||
|
elsif potential_instrument == "vocal back up"
|
||||||
|
instrument = "voice"
|
||||||
|
part = "Back Up"
|
||||||
|
elsif potential_instrument == "vocal lead double"
|
||||||
|
instrument = "voice"
|
||||||
|
part = "Lead Double"
|
||||||
|
elsif potential_instrument == "guitar solo"
|
||||||
|
instrument = "electric guitar"
|
||||||
|
part = "Solo"
|
||||||
|
elsif potential_instrument == 'stadium crowd'
|
||||||
|
instrument = 'computer'
|
||||||
|
part = 'Crowd Noise'
|
||||||
|
elsif potential_instrument == 'cannons'
|
||||||
|
instrument = 'computer'
|
||||||
|
part = 'Cannons'
|
||||||
|
elsif potential_instrument == 'bells'
|
||||||
|
instrument = 'computer'
|
||||||
|
part = 'Bells'
|
||||||
|
elsif potential_instrument == 'percussion'
|
||||||
|
instrument = 'drums'
|
||||||
|
part = 'Percussion'
|
||||||
|
elsif potential_instrument == 'fretless bass'
|
||||||
|
instrument = 'bass guitar'
|
||||||
|
part = 'Fretless'
|
||||||
|
elsif potential_instrument == 'clock percussion'
|
||||||
|
instrument = 'computer'
|
||||||
|
part = 'Clock'
|
||||||
|
elsif potential_instrument == 'horns'
|
||||||
|
instrument = 'other'
|
||||||
|
part = 'Horns'
|
||||||
|
elsif potential_instrument == 'strings'
|
||||||
|
instrument = 'other'
|
||||||
|
part = 'Strings'
|
||||||
|
elsif potential_instrument == 'orchestration'
|
||||||
|
instrument = 'computer'
|
||||||
|
part = 'Orchestration'
|
||||||
|
elsif potential_instrument == 'claps' || potential_instrument == 'hand claps'
|
||||||
|
instrument = 'computer'
|
||||||
|
part = 'Claps'
|
||||||
|
else
|
||||||
|
found_instrument = Instrument.find_by_id(potential_instrument)
|
||||||
|
if found_instrument
|
||||||
|
instrument = found_instrument.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if !used_helper && !part
|
||||||
|
part = potential_part_original
|
||||||
|
end
|
||||||
|
|
||||||
|
part = potential_instrument_original if !part
|
||||||
|
|
||||||
|
{instrument: instrument,
|
||||||
|
part: part}
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_wav(file)
|
||||||
|
|
||||||
|
bits = file.split('/')
|
||||||
|
filename = bits[bits.length - 1] # remove all but just the filename
|
||||||
|
filename_no_ext = filename[0..-5]
|
||||||
|
comparable_filename = filename_no_ext.downcase # remove .wav
|
||||||
|
|
||||||
|
master = false
|
||||||
|
instrument = nil
|
||||||
|
part = nil
|
||||||
|
|
||||||
|
if comparable_filename.include?("master mix") || comparable_filename.include?("mastered mix")
|
||||||
|
master = true
|
||||||
|
else
|
||||||
|
stem_location = comparable_filename.index('stem -')
|
||||||
|
unless stem_location
|
||||||
|
stem_location = comparable_filename.index('stems -')
|
||||||
|
end
|
||||||
|
unless stem_location
|
||||||
|
stem_location = comparable_filename.index('stem-')
|
||||||
|
end
|
||||||
|
unless stem_location
|
||||||
|
stem_location = comparable_filename.index('stems-')
|
||||||
|
end
|
||||||
|
|
||||||
|
if stem_location
|
||||||
|
bits = filename_no_ext[stem_location..-1].split('-')
|
||||||
|
bits.collect! { |bit| bit.strip }
|
||||||
|
|
||||||
|
possible_instrument = nil
|
||||||
|
possible_part = nil
|
||||||
|
|
||||||
|
|
||||||
|
if bits.length == 2
|
||||||
|
# second bit is instrument
|
||||||
|
possible_instrument = bits[1]
|
||||||
|
elsif bits.length == 3
|
||||||
|
# second bit is instrument, third bit is part
|
||||||
|
possible_instrument = bits[1]
|
||||||
|
possible_part = bits[2]
|
||||||
|
elsif bits.length == 4
|
||||||
|
possible_instrument = bits[1]
|
||||||
|
possible_part = "#{bits[2]} #{bits[3]}"
|
||||||
|
end
|
||||||
|
|
||||||
|
result = determine_instrument(possible_instrument, possible_part)
|
||||||
|
instrument = result[:instrument]
|
||||||
|
part = result[:part]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
{filename: filename, master: master, instrument: instrument, part: part}
|
||||||
|
end
|
||||||
|
|
||||||
|
def dry_run_audio(metadata, s3_path)
|
||||||
|
all_files = fetch_wav_files(s3_path)
|
||||||
|
|
||||||
|
all_files.each do |file|
|
||||||
|
if file.end_with?('.wav')
|
||||||
|
parsed_wav = parse_wav(file)
|
||||||
|
if parsed_wav[:master]
|
||||||
|
@@log.debug("#{self.name} master! filename: #{parsed_wav[:filename]}")
|
||||||
|
else
|
||||||
|
if !parsed_wav[:instrument] || !parsed_wav[:part]
|
||||||
|
@@log.warn("#{self.name} track! instrument: #{parsed_wav[:instrument] ? parsed_wav[:instrument] : 'N/A'}, part: #{parsed_wav[:part] ? parsed_wav[:part] : 'N/A'}, filename: #{parsed_wav[:filename]} ")
|
||||||
|
else
|
||||||
|
@@log.debug("#{self.name} track! instrument: #{parsed_wav[:instrument] ? parsed_wav[:instrument] : 'N/A'}, part: #{parsed_wav[:part] ? parsed_wav[:part] : 'N/A'}, filename: #{parsed_wav[:filename]} ")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
@@log.debug("#{self.name} ignoring non-wav file #{file}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def sort_tracks(tracks)
|
||||||
|
|
||||||
|
def set_custom_weight(track)
|
||||||
|
weight = 5
|
||||||
|
case track.instrument_id
|
||||||
|
when 'electric guitar'
|
||||||
|
weight = 1
|
||||||
|
when 'acoustic guitar'
|
||||||
|
weight = 2
|
||||||
|
when 'drums'
|
||||||
|
weight = 3
|
||||||
|
when 'keys'
|
||||||
|
weight = 4
|
||||||
|
when 'computer'
|
||||||
|
weight = 10
|
||||||
|
else
|
||||||
|
weight = 5
|
||||||
|
end
|
||||||
|
if track.track_type == 'Master'
|
||||||
|
weight = 1000
|
||||||
|
end
|
||||||
|
|
||||||
|
weight
|
||||||
|
end
|
||||||
|
|
||||||
|
sorted_tracks = tracks.sort do |a, b|
|
||||||
|
a_weight = set_custom_weight(a)
|
||||||
|
b_weight = set_custom_weight(b)
|
||||||
|
|
||||||
|
a_weight <=> b_weight
|
||||||
|
end
|
||||||
|
|
||||||
|
position = 1
|
||||||
|
sorted_tracks.each do |track|
|
||||||
|
track.position = position
|
||||||
|
position = position + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
sorted_tracks[sorted_tracks.length - 1].position = 1000
|
||||||
|
|
||||||
|
sorted_tracks
|
||||||
|
end
|
||||||
|
|
||||||
|
def synchronize_audio(jam_track, metadata, s3_path, skip_audio_upload)
|
||||||
|
|
||||||
|
wav_files = fetch_wav_files(s3_path)
|
||||||
|
|
||||||
|
tracks = []
|
||||||
|
|
||||||
|
wav_files.each do |wav_file|
|
||||||
|
track = JamTrackTrack.new
|
||||||
|
track.original_audio_s3_path = wav_file
|
||||||
|
|
||||||
|
parsed_wav = parse_wav(wav_file)
|
||||||
|
|
||||||
|
if parsed_wav[:master]
|
||||||
|
track.track_type = 'Master'
|
||||||
|
track.part = 'Master'
|
||||||
|
@@log.debug("#{self.name} master! filename: #{parsed_wav[:filename]}")
|
||||||
|
else
|
||||||
|
if !parsed_wav[:instrument] || !parsed_wav[:part]
|
||||||
|
@@log.warn("#{self.name} track! instrument: #{parsed_wav[:instrument] ? parsed_wav[:instrument] : 'N/A'}, part: #{parsed_wav[:part] ? parsed_wav[:part] : 'N/A'}, filename: #{parsed_wav[:filename]} ")
|
||||||
|
else
|
||||||
|
@@log.debug("#{self.name} track! instrument: #{parsed_wav[:instrument] ? parsed_wav[:instrument] : 'N/A'}, part: #{parsed_wav[:part] ? parsed_wav[:part] : 'N/A'}, filename: #{parsed_wav[:filename]} ")
|
||||||
|
end
|
||||||
|
|
||||||
|
track.instrument_id = parsed_wav[:instrument] || 'other'
|
||||||
|
track.track_type = 'Track'
|
||||||
|
track.part = parsed_wav[:part] || 'Other'
|
||||||
|
end
|
||||||
|
|
||||||
|
tracks << track
|
||||||
|
end
|
||||||
|
|
||||||
|
tracks = sort_tracks(tracks)
|
||||||
|
|
||||||
|
jam_track.jam_track_tracks = tracks
|
||||||
|
|
||||||
|
saved = jam_track.save
|
||||||
|
|
||||||
|
if !saved
|
||||||
|
finish('invalid_audio', jam_track.errors.inspect)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
return synchronize_audio_files(jam_track, skip_audio_upload)
|
||||||
|
end
|
||||||
|
|
||||||
|
def synchronize_audio_files(jam_track, skip_audio_upload)
|
||||||
|
|
||||||
|
begin
|
||||||
|
Dir.mktmpdir do |tmp_dir|
|
||||||
|
|
||||||
|
jam_track.jam_track_tracks.each do |track|
|
||||||
|
|
||||||
|
basename = File.basename(track.original_audio_s3_path)
|
||||||
|
s3_dirname = File.dirname(track.original_audio_s3_path)
|
||||||
|
|
||||||
|
# make a 44100 version, and a 48000 version
|
||||||
|
ogg_44100_filename = File.basename(basename, ".wav") + "-44100.ogg"
|
||||||
|
ogg_48000_filename = File.basename(basename, ".wav") + "-48000.ogg"
|
||||||
|
|
||||||
|
ogg_44100_s3_path = track.filename(ogg_44100_filename)
|
||||||
|
ogg_48000_s3_path = track.filename(ogg_48000_filename)
|
||||||
|
|
||||||
|
track.skip_uploader = true
|
||||||
|
|
||||||
|
if skip_audio_upload
|
||||||
|
track["url_44"] = ogg_44100_s3_path
|
||||||
|
track["md5_44"] = 'md5'
|
||||||
|
track["length_44"] = 1
|
||||||
|
|
||||||
|
track["url_48"] = ogg_48000_s3_path
|
||||||
|
track["md5_48"] = 'md5'
|
||||||
|
track["length_48"] = 1
|
||||||
|
|
||||||
|
# we can't fake the preview as easily because we don't know the MD5 of the current item
|
||||||
|
#track["preview_md5"] = 'md5'
|
||||||
|
#track["preview_mp3_md5"] = 'md5'
|
||||||
|
#track["preview_url"] = track.preview_filename('md5', 'ogg')
|
||||||
|
#track["preview_length"] = 1
|
||||||
|
#track["preview_mp3_url"] = track.preview_filename('md5', 'mp3')
|
||||||
|
#track["preview_mp3_length"] = 1
|
||||||
|
#track["preview_start_time"] = 0
|
||||||
|
else
|
||||||
|
wav_file = File.join(tmp_dir, basename)
|
||||||
|
|
||||||
|
# bring the original wav file down from S3 to local file system
|
||||||
|
JamTrackImporter::s3_manager.download(track.original_audio_s3_path, wav_file)
|
||||||
|
|
||||||
|
sample_rate = `soxi -r "#{wav_file}"`.strip
|
||||||
|
|
||||||
|
ogg_44100 = File.join(tmp_dir, ogg_44100_filename)
|
||||||
|
ogg_48000 = File.join(tmp_dir, File.basename(basename, ".wav") + "-48000.ogg")
|
||||||
|
|
||||||
|
if sample_rate == "44100"
|
||||||
|
`oggenc "#{wav_file}" -q 6 -o "#{ogg_44100}"`
|
||||||
|
else
|
||||||
|
`oggenc "#{wav_file}" --resample 44100 -q 6 -o "#{ogg_44100}"`
|
||||||
|
end
|
||||||
|
|
||||||
|
if sample_rate == "48000"
|
||||||
|
`oggenc "#{wav_file}" -q 6 -o "#{ogg_48000}"`
|
||||||
|
else
|
||||||
|
`oggenc "#{wav_file}" --resample 48000 -q 6 -o "#{ogg_48000}"`
|
||||||
|
end
|
||||||
|
|
||||||
|
# upload the new ogg files to s3
|
||||||
|
@@log.debug("uploading 44100 to #{ogg_44100_s3_path}")
|
||||||
|
|
||||||
|
jamkazam_s3_manager.upload(ogg_44100_s3_path, ogg_44100)
|
||||||
|
|
||||||
|
@@log.debug("uploading 48000 to #{ogg_48000_s3_path}")
|
||||||
|
|
||||||
|
jamkazam_s3_manager.upload(ogg_48000_s3_path, ogg_48000)
|
||||||
|
|
||||||
|
ogg_44100_digest = ::Digest::MD5.file(ogg_44100)
|
||||||
|
# and finally update the JamTrackTrack with the new info
|
||||||
|
track["url_44"] = ogg_44100_s3_path
|
||||||
|
track["md5_44"] = ogg_44100_digest.hexdigest
|
||||||
|
track["length_44"] = File.new(ogg_44100).size
|
||||||
|
|
||||||
|
track["url_48"] = ogg_48000_s3_path
|
||||||
|
track["md5_48"] = ::Digest::MD5.file(ogg_48000).hexdigest
|
||||||
|
track["length_48"] = File.new(ogg_48000).size
|
||||||
|
|
||||||
|
synchronize_duration(jam_track, ogg_44100)
|
||||||
|
|
||||||
|
# convert entire master ogg file to mp3, and push both to public destination
|
||||||
|
preview_succeeded = synchronize_master_preview(track, tmp_dir, ogg_44100, ogg_44100_digest) if track.track_type == 'Master'
|
||||||
|
|
||||||
|
if !preview_succeeded
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
track.save!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
rescue Exception => e
|
||||||
|
finish("sync_audio_exception", e.to_s)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
def synchronize_duration(jam_track, ogg_44100)
|
||||||
|
duration_command = "soxi -D \"#{ogg_44100}\""
|
||||||
|
output = `#{duration_command}`
|
||||||
|
|
||||||
|
result_code = $?.to_i
|
||||||
|
|
||||||
|
if result_code == 0
|
||||||
|
duration = output.to_f.round
|
||||||
|
jam_track.duration = duration
|
||||||
|
else
|
||||||
|
@@log.warn("unable to determine duration for jam_track #{jam_track.name}. output #{output}")
|
||||||
|
end
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def synchronize_master_preview(track, tmp_dir, ogg_44100, ogg_digest)
|
||||||
|
|
||||||
|
begin
|
||||||
|
mp3_44100 = File.join(tmp_dir, 'output-preview-44100.mp3')
|
||||||
|
convert_mp3_cmd = "#{APP_CONFIG.ffmpeg_path} -i \"#{ogg_44100}\" -ab 192k \"#{mp3_44100}\""
|
||||||
|
@@log.debug("converting to mp3 using: " + convert_mp3_cmd)
|
||||||
|
|
||||||
|
convert_output = `#{convert_mp3_cmd}`
|
||||||
|
|
||||||
|
mp3_digest = ::Digest::MD5.file(mp3_44100)
|
||||||
|
|
||||||
|
track["preview_md5"] = ogg_md5 = ogg_digest.hexdigest
|
||||||
|
track["preview_mp3_md5"] = mp3_md5 = mp3_digest.hexdigest
|
||||||
|
|
||||||
|
# upload 44100 ogg and mp3 to public location as well
|
||||||
|
@@log.debug("uploading ogg preview to #{track.preview_filename('ogg')}")
|
||||||
|
public_jamkazam_s3_manager.upload(track.preview_filename(ogg_digest.hexdigest, 'ogg'), ogg_44100, content_type: 'audio/ogg', content_md5: ogg_digest.base64digest)
|
||||||
|
@@log.debug("uploading mp3 preview to #{track.preview_filename('mp3')}")
|
||||||
|
public_jamkazam_s3_manager.upload(track.preview_filename(mp3_digest.hexdigest, 'mp3'), mp3_44100, content_type: 'audio/mpeg', content_md5: mp3_digest.base64digest)
|
||||||
|
|
||||||
|
|
||||||
|
track.skip_uploader = true
|
||||||
|
|
||||||
|
original_ogg_preview_url = track["preview_url"]
|
||||||
|
original_mp3_preview_url = track["preview_mp3_url"]
|
||||||
|
|
||||||
|
# and finally update the JamTrackTrack with the new info
|
||||||
|
track["preview_url"] = track.preview_filename(ogg_md5, 'ogg')
|
||||||
|
track["preview_length"] = File.new(ogg_44100).size
|
||||||
|
# and finally update the JamTrackTrack with the new info
|
||||||
|
track["preview_mp3_url"] = track.preview_filename(mp3_md5, 'mp3')
|
||||||
|
track["preview_mp3_length"] = File.new(mp3_44100).size
|
||||||
|
track["preview_start_time"] = 0
|
||||||
|
|
||||||
|
if !track.save
|
||||||
|
finish("save_master_preview", track.errors.to_s)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
# if all that worked, now delete old previews, if present
|
||||||
|
begin
|
||||||
|
public_jamkazam_s3_manager.delete(original_ogg_preview_url) if original_ogg_preview_url && original_ogg_preview_url != track["preview_url"]
|
||||||
|
public_jamkazam_s3_manager.delete(original_mp3_preview_url) if original_mp3_preview_url && original_mp3_preview_url != track["preview_mp3_url"]
|
||||||
|
rescue
|
||||||
|
puts "UNABLE TO CLEANUP OLD PREVIEW URL"
|
||||||
|
end
|
||||||
|
rescue Exception => e
|
||||||
|
finish("sync_master_preview_exception", e.to_s)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
return true
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def fetch_all_files(s3_path)
|
||||||
|
JamTrackImporter::s3_manager.list_files(s3_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
def fetch_wav_files(s3_path)
|
||||||
|
files = fetch_all_files(s3_path)
|
||||||
|
files.select { |file| file.end_with?('.wav') }
|
||||||
|
end
|
||||||
|
|
||||||
|
def synchronize(jam_track, metadata, metalocation, options)
|
||||||
|
|
||||||
|
# metalocation should be audio/original artist/song name/meta.yml
|
||||||
|
|
||||||
|
metadata ||= {}
|
||||||
|
|
||||||
|
parsed_metalocation = parse_metalocation(metalocation)
|
||||||
|
|
||||||
|
return unless parsed_metalocation
|
||||||
|
|
||||||
|
original_artist = parsed_metalocation[1]
|
||||||
|
name = parsed_metalocation[2]
|
||||||
|
|
||||||
|
success = synchronize_metadata(jam_track, metadata, metalocation, original_artist, name)
|
||||||
|
|
||||||
|
return unless success
|
||||||
|
|
||||||
|
synchronized_audio = synchronize_audio(jam_track, metadata, "audio/#{original_artist}/#{name}", options[:skip_audio_upload])
|
||||||
|
|
||||||
|
return unless synchronized_audio
|
||||||
|
|
||||||
|
created_plan = synchronize_recurly(jam_track)
|
||||||
|
if created_plan
|
||||||
|
finish("success", nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def synchronize_recurly(jam_track)
|
||||||
|
begin
|
||||||
|
recurly = RecurlyClient.new
|
||||||
|
# no longer create JamTrack plans: VRFS-3028
|
||||||
|
# recurly.create_jam_track_plan(jam_track) unless recurly.find_jam_track_plan(jam_track)
|
||||||
|
rescue RecurlyClientError => x
|
||||||
|
finish('recurly_create_plan', x.errors.to_s)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
class << self
|
||||||
|
|
||||||
|
def s3_manager
|
||||||
|
@s3_manager ||= S3Manager.new(APP_CONFIG.aws_bucket_jamtracks, APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key)
|
||||||
|
end
|
||||||
|
|
||||||
|
def private_s3_manager
|
||||||
|
@s3_manager ||= S3Manager.new(APP_CONFIG.aws_bucket, APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def dry_run
|
||||||
|
s3_manager.list_directories('audio').each do |original_artist|
|
||||||
|
@@log.debug("searching through artist directory '#{original_artist}'")
|
||||||
|
|
||||||
|
songs = s3_manager.list_directories(original_artist)
|
||||||
|
songs.each do |song|
|
||||||
|
@@log.debug("searching through song directory' #{song}'")
|
||||||
|
|
||||||
|
metalocation = "#{song}meta.yml"
|
||||||
|
|
||||||
|
metadata = load_metalocation(metalocation)
|
||||||
|
|
||||||
|
jam_track_importer = JamTrackImporter.new
|
||||||
|
|
||||||
|
jam_track_importer.dry_run(metadata, metalocation)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def synchronize_jamtrack_master_preview(jam_track)
|
||||||
|
importer = JamTrackImporter.new
|
||||||
|
importer.name = jam_track.name
|
||||||
|
|
||||||
|
master_track = jam_track.master_track
|
||||||
|
|
||||||
|
if master_track
|
||||||
|
Dir.mktmpdir do |tmp_dir|
|
||||||
|
ogg_44100 = File.join(tmp_dir, 'input.ogg')
|
||||||
|
private_s3_manager.download(master_track.url_by_sample_rate(44), ogg_44100)
|
||||||
|
ogg_44100_digest = ::Digest::MD5.file(ogg_44100)
|
||||||
|
if importer.synchronize_master_preview(master_track, tmp_dir, ogg_44100, ogg_44100_digest)
|
||||||
|
importer.finish("success", nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
importer.finish('no_master_track', nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
importer
|
||||||
|
end
|
||||||
|
|
||||||
|
def synchronize_jamtrack_master_previews
|
||||||
|
importers = []
|
||||||
|
|
||||||
|
JamTrack.all.each do |jam_track|
|
||||||
|
importers << synchronize_jamtrack_master_preview(jam_track)
|
||||||
|
end
|
||||||
|
|
||||||
|
@@log.info("SUMMARY")
|
||||||
|
@@log.info("-------")
|
||||||
|
importers.each do |importer|
|
||||||
|
if importer
|
||||||
|
if importer.reason == "success" || importer.reason == "jam_track_exists"
|
||||||
|
@@log.info("#{importer.name} #{importer.reason}")
|
||||||
|
else
|
||||||
|
@@log.error("#{importer.name} failed to import.")
|
||||||
|
@@log.error("#{importer.name} reason=#{importer.reason}")
|
||||||
|
@@log.error("#{importer.name} detail=#{importer.detail}")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
@@log.error("NULL IMPORTER")
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def synchronize_duration(jam_track)
|
||||||
|
importer = JamTrackImporter.new
|
||||||
|
importer.name = jam_track.name
|
||||||
|
|
||||||
|
master_track = jam_track.master_track
|
||||||
|
if master_track
|
||||||
|
Dir.mktmpdir do |tmp_dir|
|
||||||
|
ogg_44100 = File.join(tmp_dir, 'input.ogg')
|
||||||
|
private_s3_manager.download(master_track.url_by_sample_rate(44), ogg_44100)
|
||||||
|
|
||||||
|
if importer.synchronize_duration(jam_track, ogg_44100)
|
||||||
|
jam_track.save!
|
||||||
|
importer.finish("success", nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
importer.finish('no_duration', nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
importer
|
||||||
|
end
|
||||||
|
|
||||||
|
def synchronize_durations
|
||||||
|
importers = []
|
||||||
|
|
||||||
|
JamTrack.all.each do |jam_track|
|
||||||
|
importers << synchronize_duration(jam_track)
|
||||||
|
end
|
||||||
|
|
||||||
|
@@log.info("SUMMARY")
|
||||||
|
@@log.info("-------")
|
||||||
|
importers.each do |importer|
|
||||||
|
if importer
|
||||||
|
if importer.reason == "success" || importer.reason == "jam_track_exists"
|
||||||
|
@@log.info("#{importer.name} #{importer.reason}")
|
||||||
|
else
|
||||||
|
@@log.error("#{importer.name} failed to import.")
|
||||||
|
@@log.error("#{importer.name} reason=#{importer.reason}")
|
||||||
|
@@log.error("#{importer.name} detail=#{importer.detail}")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
@@log.error("NULL IMPORTER")
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def download_master(jam_track)
|
||||||
|
importer = JamTrackImporter.new
|
||||||
|
importer.name = jam_track.name
|
||||||
|
|
||||||
|
Dir.mkdir('tmp') unless Dir.exists?('tmp')
|
||||||
|
Dir.mkdir('tmp/jam_track_masters') unless Dir.exists?('tmp/jam_track_masters')
|
||||||
|
|
||||||
|
master_track = jam_track.master_track
|
||||||
|
if master_track
|
||||||
|
ogg_44100 = File.join('tmp/jam_track_masters', "#{jam_track.original_artist} - #{jam_track.name}.ogg")
|
||||||
|
private_s3_manager.download(master_track.url_by_sample_rate(44), ogg_44100)
|
||||||
|
end
|
||||||
|
importer
|
||||||
|
end
|
||||||
|
|
||||||
|
def download_masters
|
||||||
|
importers = []
|
||||||
|
|
||||||
|
JamTrack.all.each do |jam_track|
|
||||||
|
importers << download_master(jam_track)
|
||||||
|
end
|
||||||
|
|
||||||
|
@@log.info("SUMMARY")
|
||||||
|
@@log.info("-------")
|
||||||
|
importers.each do |importer|
|
||||||
|
if importer
|
||||||
|
if importer.reason == "success"
|
||||||
|
@@log.info("#{importer.name} #{importer.reason}")
|
||||||
|
else
|
||||||
|
@@log.error("#{importer.name} failed to download.")
|
||||||
|
@@log.error("#{importer.name} reason=#{importer.reason}")
|
||||||
|
@@log.error("#{importer.name} detail=#{importer.detail}")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
@@log.error("NULL IMPORTER")
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def synchronize_all(options)
|
||||||
|
importers = []
|
||||||
|
|
||||||
|
s3_manager.list_directories('audio').each do |original_artist|
|
||||||
|
@@log.debug("searching through artist directory '#{original_artist}'")
|
||||||
|
|
||||||
|
songs = s3_manager.list_directories(original_artist)
|
||||||
|
songs.each do |song|
|
||||||
|
@@log.debug("searching through song directory' #{song}'")
|
||||||
|
|
||||||
|
metalocation = "#{song}meta.yml"
|
||||||
|
|
||||||
|
importer = synchronize_from_meta(metalocation, options)
|
||||||
|
importers << importer
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@@log.info("SUMMARY")
|
||||||
|
@@log.info("-------")
|
||||||
|
importers.each do |importer|
|
||||||
|
if importer
|
||||||
|
if importer.reason == "success" || importer.reason == "jam_track_exists"
|
||||||
|
@@log.info("#{importer.name} #{importer.reason}")
|
||||||
|
else
|
||||||
|
@@log.error("#{importer.name} failed to import.")
|
||||||
|
@@log.error("#{importer.name} reason=#{importer.reason}")
|
||||||
|
@@log.error("#{importer.name} detail=#{importer.detail}")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
@@log.error("NULL IMPORTER")
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def jam_track_dry_run(metalocation)
|
||||||
|
# see if we can find a JamTrack with this metalocation
|
||||||
|
jam_track = JamTrack.find_by_metalocation(metalocation)
|
||||||
|
|
||||||
|
meta = load_metalocation(metalocation)
|
||||||
|
|
||||||
|
if jam_track
|
||||||
|
@@log.debug("jamtrack #{jam_track.name} located by metalocation")
|
||||||
|
jam_track.dry_run(meta, metalocation)
|
||||||
|
else
|
||||||
|
jam_track = JamTrack.new
|
||||||
|
jam_track.dry_run(meta, metalocation)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def load_metalocation(metalocation)
|
||||||
|
begin
|
||||||
|
data = s3_manager.read_all(metalocation)
|
||||||
|
return YAML.load(data)
|
||||||
|
rescue AWS::S3::Errors::NoSuchKey
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_from_metalocation(meta, metalocation, options = {skip_audio_upload: false})
|
||||||
|
jam_track = JamTrack.new
|
||||||
|
sync_from_metadata(jam_track, meta, metalocation, options)
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_from_metalocation(jam_track, meta, metalocation, options)
|
||||||
|
sync_from_metadata(jam_track, meta, metalocation, options)
|
||||||
|
end
|
||||||
|
|
||||||
|
def sync_from_metadata(jam_track, meta, metalocation, options)
|
||||||
|
jam_track_importer = JamTrackImporter.new
|
||||||
|
|
||||||
|
JamTrack.transaction do
|
||||||
|
#begin
|
||||||
|
jam_track_importer.synchronize(jam_track, meta, metalocation, options)
|
||||||
|
#rescue Exception => e
|
||||||
|
# jam_track_importer.finish("unhandled_exception", e.to_s)
|
||||||
|
#end
|
||||||
|
|
||||||
|
if jam_track_importer.reason != "success"
|
||||||
|
raise ActiveRecord::Rollback
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
jam_track_importer
|
||||||
|
end
|
||||||
|
|
||||||
|
def synchronize_from_meta(metalocation, options)
|
||||||
|
# see if we can find a JamTrack with this metalocation
|
||||||
|
jam_track = JamTrack.find_by_metalocation(metalocation)
|
||||||
|
|
||||||
|
meta = load_metalocation(metalocation)
|
||||||
|
|
||||||
|
jam_track_importer = nil
|
||||||
|
if jam_track
|
||||||
|
@@log.debug("jamtrack #{jam_track.name} located by metalocation")
|
||||||
|
jam_track_importer = update_from_metalocation(jam_track, meta, metalocation, options)
|
||||||
|
else
|
||||||
|
jam_track_importer = create_from_metalocation(meta, metalocation, options)
|
||||||
|
end
|
||||||
|
|
||||||
|
if jam_track_importer.reason == "success"
|
||||||
|
@@log.info("#{jam_track_importer.name} successfully imported")
|
||||||
|
else
|
||||||
|
@@log.error("#{jam_track_importer.name} failed to import.")
|
||||||
|
@@log.error("#{jam_track_importer.name} reason=#{jam_track_importer.reason}")
|
||||||
|
@@log.error("#{jam_track_importer.name} detail=#{jam_track_importer.detail}")
|
||||||
|
end
|
||||||
|
|
||||||
|
jam_track_importer
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -11,27 +11,33 @@ module JamRuby
|
||||||
|
|
||||||
@@log = Logging.logger[JamTracksManager]
|
@@log = Logging.logger[JamTracksManager]
|
||||||
|
|
||||||
|
include JamRuby::S3ManagerMixin
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
def save_jam_track_jkz(user, jam_track)
|
|
||||||
|
|
||||||
|
def save_jam_track_jkz(user, jam_track, sample_rate=48)
|
||||||
jam_track_right = jam_track.right_for_user(user)
|
jam_track_right = jam_track.right_for_user(user)
|
||||||
raise ArgumentError if jam_track_right.nil?
|
raise ArgumentError if jam_track_right.nil?
|
||||||
save_jam_track_right_jkz(jam_track_right)
|
save_jam_track_right_jkz(jam_track_right, sample_rate)
|
||||||
end
|
end
|
||||||
|
|
||||||
def save_jam_track_right_jkz(jam_track_right)
|
def save_jam_track_right_jkz(jam_track_right, sample_rate=48)
|
||||||
jam_track = jam_track_right.jam_track
|
jam_track = jam_track_right.jam_track
|
||||||
#py_root = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "..", "jamtracks"))
|
|
||||||
py_root = APP_CONFIG.jamtracks_dir
|
py_root = APP_CONFIG.jamtracks_dir
|
||||||
Dir.mktmpdir do |tmp_dir|
|
Dir.mktmpdir do |tmp_dir|
|
||||||
jam_file_opts=""
|
jam_file_opts=""
|
||||||
jam_track.jam_track_tracks.each do |jam_track_track|
|
jam_track.jam_track_tracks.each do |jam_track_track|
|
||||||
|
|
||||||
|
next if jam_track_track.track_type != "Track" # master mixes do not go into the JKZ
|
||||||
|
|
||||||
# use the jam_track_track ID as the filename.ogg/.wav, because it's important metadata
|
# use the jam_track_track ID as the filename.ogg/.wav, because it's important metadata
|
||||||
nm = jam_track_track.id + File.extname(jam_track_track.filename)
|
nm = jam_track_track.id + File.extname(jam_track_track.url_by_sample_rate(sample_rate))
|
||||||
track_filename = File.join(tmp_dir, nm)
|
track_filename = File.join(tmp_dir, nm)
|
||||||
track_url = jam_track_track.sign_url
|
track_url = jam_track_track.sign_url(120, sample_rate)
|
||||||
|
@@log.info("downloading #{track_url} to #{track_filename}")
|
||||||
copy_url_to_file(track_url, track_filename)
|
copy_url_to_file(track_url, track_filename)
|
||||||
copy_url_to_file(track_url, File.join(".", nm))
|
jam_file_opts << " -i #{Shellwords.escape("#{track_filename}+#{jam_track_track.part}")}"
|
||||||
jam_file_opts << " -i '#{track_filename}+#{jam_track_track.part}'"
|
|
||||||
end
|
end
|
||||||
#puts "LS + " + `ls -la '#{tmp_dir}'`
|
#puts "LS + " + `ls -la '#{tmp_dir}'`
|
||||||
|
|
||||||
|
|
@ -39,10 +45,11 @@ module JamRuby
|
||||||
title=jam_track.name
|
title=jam_track.name
|
||||||
output_jkz=File.join(tmp_dir, "#{title.parameterize}.jkz")
|
output_jkz=File.join(tmp_dir, "#{title.parameterize}.jkz")
|
||||||
py_file = File.join(py_root, "jkcreate.py")
|
py_file = File.join(py_root, "jkcreate.py")
|
||||||
|
version = jam_track.version
|
||||||
@@log.info "Executing python source in #{py_file}, outputting to #{tmp_dir} (#{output_jkz})"
|
@@log.info "Executing python source in #{py_file}, outputting to #{tmp_dir} (#{output_jkz})"
|
||||||
|
|
||||||
# From http://stackoverflow.com/questions/690151/getting-output-of-system-calls-in-ruby/5970819#5970819:
|
# From http://stackoverflow.com/questions/690151/getting-output-of-system-calls-in-ruby/5970819#5970819:
|
||||||
cli = "python #{py_file} -D -k #{sku} -p #{tmp_dir}/pkey.pem -s #{tmp_dir}/skey.pem #{jam_file_opts} -o #{output_jkz} -t '#{title}'"
|
cli = "python #{py_file} -D -k #{sku} -p #{Shellwords.escape(tmp_dir)}/pkey.pem -s #{Shellwords.escape(tmp_dir)}/skey.pem #{jam_file_opts} -o #{Shellwords.escape(output_jkz)} -t #{Shellwords.escape(title)} -V #{Shellwords.escape(version)}"
|
||||||
Open3.popen3(cli) do |stdin, stdout, stderr, wait_thr|
|
Open3.popen3(cli) do |stdin, stdout, stderr, wait_thr|
|
||||||
pid = wait_thr.pid
|
pid = wait_thr.pid
|
||||||
exit_status = wait_thr.value
|
exit_status = wait_thr.value
|
||||||
|
|
@ -51,11 +58,14 @@ module JamRuby
|
||||||
#puts "stdout: #{out}, stderr: #{err}"
|
#puts "stdout: #{out}, stderr: #{err}"
|
||||||
raise ArgumentError, "Error calling python script: #{err}" if err.present?
|
raise ArgumentError, "Error calling python script: #{err}" if err.present?
|
||||||
raise ArgumentError, "Error calling python script: #{out}" if out && (out.index("No track files specified") || out.index("Cannot find file"))
|
raise ArgumentError, "Error calling python script: #{out}" if out && (out.index("No track files specified") || out.index("Cannot find file"))
|
||||||
jam_track_right[:url]
|
|
||||||
|
|
||||||
#raise ArgumentError, "output_jkz is empty #{output_jkz}" unless File.exists?(output_jkz)
|
#raise ArgumentError, "output_jkz is empty #{output_jkz}" unless File.exists?(output_jkz)
|
||||||
|
if sample_rate==48
|
||||||
jam_track_right.url.store!(File.open(output_jkz, "rb"))
|
jam_track_right.url_48.store!(File.open(output_jkz, "rb"))
|
||||||
|
else
|
||||||
|
jam_track_right.url_44.store!(File.open(output_jkz, "rb"))
|
||||||
|
end
|
||||||
|
|
||||||
jam_track_right.signed=true
|
jam_track_right.signed=true
|
||||||
jam_track_right.downloaded_since_sign=false
|
jam_track_right.downloaded_since_sign=false
|
||||||
jam_track_right.private_key=File.read("#{tmp_dir}/skey.pem")
|
jam_track_right.private_key=File.read("#{tmp_dir}/skey.pem")
|
||||||
|
|
@ -73,7 +83,7 @@ module JamRuby
|
||||||
http.request request do |response|
|
http.request request do |response|
|
||||||
response_code = response.code.to_i
|
response_code = response.code.to_i
|
||||||
unless response_code >= 200 && response_code <= 299
|
unless response_code >= 200 && response_code <= 299
|
||||||
puts "Response from server was #{response_code} / #{response.message}"
|
@@log.info "Response from server was #{response_code} / #{response.message}"
|
||||||
raise "bad status code: #{response_code}. body: #{response.body}"
|
raise "bad status code: #{response_code}. body: #{response.body}"
|
||||||
end
|
end
|
||||||
response.read_body do |chunk|
|
response.read_body do |chunk|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
require 'json'
|
||||||
|
require 'tempfile'
|
||||||
|
require 'open3'
|
||||||
|
require 'fileutils'
|
||||||
|
require 'open-uri'
|
||||||
|
|
||||||
|
module JamRuby
|
||||||
|
|
||||||
|
# Interact with external python tools to create jmep json
|
||||||
|
class JmepManager
|
||||||
|
|
||||||
|
@@log = Logging.logger[JmepManager]
|
||||||
|
|
||||||
|
class << self
|
||||||
|
|
||||||
|
def execute(jmep_text)
|
||||||
|
|
||||||
|
json = nil
|
||||||
|
|
||||||
|
if jmep_text.blank?
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
py_root = APP_CONFIG.jmep_dir
|
||||||
|
Dir.mktmpdir do |tmp_dir|
|
||||||
|
|
||||||
|
output_json = File.join(tmp_dir, "jmep.json")
|
||||||
|
input_text = File.join(tmp_dir, "jmep.txt")
|
||||||
|
|
||||||
|
# put JMEP text into input file
|
||||||
|
File.open(input_text, 'w') { |file| file.write(jmep_text) }
|
||||||
|
|
||||||
|
py_file = File.join(py_root, "jmepgen.py")
|
||||||
|
@@log.info "Executing python source in #{py_file}, outputting to #{output_json})"
|
||||||
|
|
||||||
|
# From http://stackoverflow.com/questions/690151/getting-output-of-system-calls-in-ruby/5970819#5970819:
|
||||||
|
cli = "python #{py_file} -i '#{input_text}' -o '#{output_json}'"
|
||||||
|
Open3.popen3(cli) do |stdin, stdout, stderr, wait_thr|
|
||||||
|
pid = wait_thr.pid
|
||||||
|
exit_status = wait_thr.value
|
||||||
|
err = stderr.read(1000)
|
||||||
|
out = stdout.read(1000)
|
||||||
|
|
||||||
|
raise ArgumentError, "#{out} #{err}" if exit_status != 0
|
||||||
|
|
||||||
|
json = File.read(output_json)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
json
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -44,6 +44,10 @@ module JamRuby
|
||||||
s3_bucket.objects[key].url_for(operation, options).to_s
|
s3_bucket.objects[key].url_for(operation, options).to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def public_url(key, options = @@def_opts)
|
||||||
|
s3_bucket.objects[key].public_url(options).to_s
|
||||||
|
end
|
||||||
|
|
||||||
def presigned_post(key, options = @@def_opts)
|
def presigned_post(key, options = @@def_opts)
|
||||||
s3_bucket.objects[key].presigned_post(options)
|
s3_bucket.objects[key].presigned_post(options)
|
||||||
end
|
end
|
||||||
|
|
@ -72,8 +76,15 @@ module JamRuby
|
||||||
s3_bucket.objects[filename].delete
|
s3_bucket.objects[filename].delete
|
||||||
end
|
end
|
||||||
|
|
||||||
def upload(key, filename)
|
def upload(key, filename, options={})
|
||||||
s3_bucket.objects[key].write(:file => filename)
|
options[:file] = filename
|
||||||
|
s3_bucket.objects[key].write(options)
|
||||||
|
end
|
||||||
|
|
||||||
|
def cached_upload(key, filename, options={})
|
||||||
|
options[:file] = filename
|
||||||
|
options.merge({expires: 5.years.from_now})
|
||||||
|
s3_bucket.objects[key].write(filename, options)
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete_folder(folder)
|
def delete_folder(folder)
|
||||||
|
|
@ -88,6 +99,24 @@ module JamRuby
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def read_all(key)
|
||||||
|
s = StringIO.new
|
||||||
|
s3_bucket.objects[key].read do |data|
|
||||||
|
s.write(data)
|
||||||
|
end
|
||||||
|
s.string
|
||||||
|
end
|
||||||
|
|
||||||
|
def list_files(prefix)
|
||||||
|
tree = s3_bucket.as_tree(prefix: prefix)
|
||||||
|
tree.children.select(&:leaf?).collect(&:key)
|
||||||
|
end
|
||||||
|
|
||||||
|
def list_directories(prefix)
|
||||||
|
tree = s3_bucket.as_tree(prefix: prefix)
|
||||||
|
tree.children.select(&:branch?).collect(&:prefix)
|
||||||
|
end
|
||||||
|
|
||||||
def exists?(filename)
|
def exists?(filename)
|
||||||
s3_bucket.objects[filename].exists?
|
s3_bucket.objects[filename].exists?
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@ module JamRuby
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def s3_manager(options={:public => false})
|
def s3_manager(options={:bucket => nil, :public => false})
|
||||||
@s3_manager ||= S3Manager.new(options[:public] ? app_config.aws_bucket_public : app_config.aws_bucket, app_config.aws_access_key_id, app_config.aws_secret_access_key)
|
@s3_manager ||= S3Manager.new(options[:bucket] ? options[:bucket] : (options[:public] ? app_config.aws_bucket_public : app_config.aws_bucket), app_config.aws_access_key_id, app_config.aws_secret_access_key)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
module JamRuby
|
||||||
|
module S3PublicManagerMixin
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
include AppConfig
|
||||||
|
|
||||||
|
included do
|
||||||
|
end
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def s3_public_manager()
|
||||||
|
@s3_public_manager ||= S3Manager.new(app_config.aws_bucket_public, app_config.aws_access_key_id, app_config.aws_secret_access_key)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -7,7 +7,7 @@ module JamRuby
|
||||||
|
|
||||||
self.table_name = 'active_music_sessions'
|
self.table_name = 'active_music_sessions'
|
||||||
|
|
||||||
attr_accessor :legal_terms, :max_score, :opening_jam_track, :opening_recording
|
attr_accessor :legal_terms, :max_score, :opening_jam_track, :opening_recording, :opening_backing_track, :opening_metronome, :jam_track_id
|
||||||
|
|
||||||
belongs_to :claimed_recording, :class_name => "JamRuby::ClaimedRecording", :foreign_key => "claimed_recording_id", :inverse_of => :playing_sessions
|
belongs_to :claimed_recording, :class_name => "JamRuby::ClaimedRecording", :foreign_key => "claimed_recording_id", :inverse_of => :playing_sessions
|
||||||
belongs_to :claimed_recording_initiator, :class_name => "JamRuby::User", :inverse_of => :playing_claimed_recordings, :foreign_key => "claimed_recording_initiator_id"
|
belongs_to :claimed_recording_initiator, :class_name => "JamRuby::User", :inverse_of => :playing_claimed_recordings, :foreign_key => "claimed_recording_initiator_id"
|
||||||
|
|
@ -15,6 +15,9 @@ module JamRuby
|
||||||
belongs_to :jam_track, :class_name => "JamRuby::JamTrack", :foreign_key => "jam_track_id", :inverse_of => :playing_sessions
|
belongs_to :jam_track, :class_name => "JamRuby::JamTrack", :foreign_key => "jam_track_id", :inverse_of => :playing_sessions
|
||||||
belongs_to :jam_track_initiator, :class_name => "JamRuby::User", :inverse_of => :playing_jam_tracks, :foreign_key => "jam_track_initiator_id"
|
belongs_to :jam_track_initiator, :class_name => "JamRuby::User", :inverse_of => :playing_jam_tracks, :foreign_key => "jam_track_initiator_id"
|
||||||
|
|
||||||
|
belongs_to :backing_track_initiator, :class_name => "JamRuby::User", :inverse_of => :playing_jam_tracks, :foreign_key => "backing_track_initiator_id"
|
||||||
|
belongs_to :metronome_initiator, :class_name => "JamRuby::User", :inverse_of => :playing_jam_tracks, :foreign_key => "metronome_initiator_id"
|
||||||
|
|
||||||
has_one :music_session, :class_name => "JamRuby::MusicSession", :foreign_key => 'music_session_id'
|
has_one :music_session, :class_name => "JamRuby::MusicSession", :foreign_key => 'music_session_id'
|
||||||
has_one :mount, :class_name => "JamRuby::IcecastMount", :inverse_of => :music_session, :foreign_key => 'music_session_id'
|
has_one :mount, :class_name => "JamRuby::IcecastMount", :inverse_of => :music_session, :foreign_key => 'music_session_id'
|
||||||
belongs_to :creator, :class_name => 'JamRuby::User', :foreign_key => :user_id
|
belongs_to :creator, :class_name => 'JamRuby::User', :foreign_key => :user_id
|
||||||
|
|
@ -27,6 +30,10 @@ module JamRuby
|
||||||
validate :creator_is_musician
|
validate :creator_is_musician
|
||||||
validate :validate_opening_recording, :if => :opening_recording
|
validate :validate_opening_recording, :if => :opening_recording
|
||||||
validate :validate_opening_jam_track, :if => :opening_jam_track
|
validate :validate_opening_jam_track, :if => :opening_jam_track
|
||||||
|
validate :validate_opening_backing_track, :if => :opening_backing_track
|
||||||
|
|
||||||
|
# not sure if this is helpful since if one opens, it always stays open
|
||||||
|
validate :validate_opening_metronome, :if => :opening_metronome
|
||||||
|
|
||||||
after_create :started_session
|
after_create :started_session
|
||||||
|
|
||||||
|
|
@ -73,22 +80,47 @@ module JamRuby
|
||||||
if is_jam_track_open?
|
if is_jam_track_open?
|
||||||
errors.add(:claimed_recording, ValidationMessages::JAM_TRACK_ALREADY_OPEN)
|
errors.add(:claimed_recording, ValidationMessages::JAM_TRACK_ALREADY_OPEN)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if is_backing_track_open?
|
||||||
|
errors.add(:claimed_recording, ValidationMessages::BACKING_TRACK_ALREADY_OPEN)
|
||||||
|
end
|
||||||
|
|
||||||
|
if is_metronome_open?
|
||||||
|
errors.add(:claimed_recording, ValidationMessages::METRONOME_ALREADY_OPEN)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_opening_jam_track
|
def validate_opening_jam_track
|
||||||
|
validate_other_audio(:jam_track)
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_opening_backing_track
|
||||||
|
validate_other_audio(:backing_track)
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_opening_metronome
|
||||||
|
validate_other_audio(:metronome)
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_other_audio(error_key)
|
||||||
|
# validate that there is no backing track already open in this session
|
||||||
|
if backing_track_path_was.present?
|
||||||
|
errors.add(error_key, ValidationMessages::BACKING_TRACK_ALREADY_OPEN)
|
||||||
|
end
|
||||||
|
|
||||||
# validate that there is no jam track already open in this session
|
# validate that there is no jam track already open in this session
|
||||||
unless jam_track_id_was.nil?
|
if jam_track_id_was.present?
|
||||||
errors.add(:jam_track, ValidationMessages::JAM_TRACK_ALREADY_OPEN)
|
errors.add(error_key, ValidationMessages::JAM_TRACK_ALREADY_OPEN)
|
||||||
end
|
end
|
||||||
|
|
||||||
# validate that there is no recording being made
|
# validate that there is no recording being made
|
||||||
if is_recording?
|
if is_recording?
|
||||||
errors.add(:jam_track, ValidationMessages::RECORDING_ALREADY_IN_PROGRESS)
|
errors.add(error_key, ValidationMessages::RECORDING_ALREADY_IN_PROGRESS)
|
||||||
end
|
end
|
||||||
|
|
||||||
# validate that there is no recording being played back to the session
|
# validate that there is no recording being played back to the session
|
||||||
if is_playing_recording?
|
if is_playing_recording?
|
||||||
errors.add(:jam_track, ValidationMessages::CLAIMED_RECORDING_ALREADY_IN_PROGRESS)
|
errors.add(error_key, ValidationMessages::CLAIMED_RECORDING_ALREADY_IN_PROGRESS)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -593,6 +625,14 @@ module JamRuby
|
||||||
!self.jam_track.nil?
|
!self.jam_track.nil?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def is_backing_track_open?
|
||||||
|
self.backing_track_path.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def is_metronome_open?
|
||||||
|
self.metronome_active.present?
|
||||||
|
end
|
||||||
|
|
||||||
# is this music session currently recording?
|
# is this music session currently recording?
|
||||||
def is_recording?
|
def is_recording?
|
||||||
recordings.where(:duration => nil).count > 0
|
recordings.where(:duration => nil).count > 0
|
||||||
|
|
@ -742,6 +782,35 @@ module JamRuby
|
||||||
self.save
|
self.save
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @param backing_track_path is a relative path:
|
||||||
|
def open_backing_track(user, backing_track_path)
|
||||||
|
self.backing_track_path = backing_track_path
|
||||||
|
self.backing_track_initiator = user
|
||||||
|
self.opening_backing_track = true
|
||||||
|
self.save
|
||||||
|
self.opening_backing_track = false
|
||||||
|
end
|
||||||
|
|
||||||
|
def close_backing_track
|
||||||
|
self.backing_track_path = nil
|
||||||
|
self.backing_track_initiator = nil
|
||||||
|
self.save
|
||||||
|
end
|
||||||
|
|
||||||
|
def open_metronome(user)
|
||||||
|
self.metronome_active = true
|
||||||
|
self.metronome_initiator = user
|
||||||
|
self.opening_metronome = true
|
||||||
|
self.save
|
||||||
|
self.opening_metronome = false
|
||||||
|
end
|
||||||
|
|
||||||
|
def close_metronome
|
||||||
|
self.metronome_active = false
|
||||||
|
self.metronome_initiator = nil
|
||||||
|
self.save
|
||||||
|
end
|
||||||
|
|
||||||
def self.sync(session_history)
|
def self.sync(session_history)
|
||||||
music_session = MusicSession.find_by_id(session_history.id)
|
music_session = MusicSession.find_by_id(session_history.id)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
# this was added to support the idea of an anonymous user interacting with our site; needed by the ShoppingCart
|
||||||
|
# over time it might make sense to beef this up and to use it conistently in anonymous interactions
|
||||||
|
|
||||||
|
module JamRuby
|
||||||
|
class AnonymousUser
|
||||||
|
|
||||||
|
attr_accessor :id
|
||||||
|
|
||||||
|
def initialize(id)
|
||||||
|
@id = id
|
||||||
|
end
|
||||||
|
|
||||||
|
def shopping_carts
|
||||||
|
ShoppingCart.where(anonymous_user_id: @id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy_all_shopping_carts
|
||||||
|
ShoppingCart.destroy_all(anonymous_user_id: @id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def admin
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
def has_redeemable_jamtrack
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
module JamRuby
|
||||||
|
class BackingTrack < ActiveRecord::Base
|
||||||
|
|
||||||
|
self.table_name = "backing_tracks"
|
||||||
|
self.primary_key = 'id'
|
||||||
|
|
||||||
|
default_scope order('created_at ASC')
|
||||||
|
|
||||||
|
belongs_to :connection, :class_name => "JamRuby::Connection", :inverse_of => :tracks, :foreign_key => 'connection_id'
|
||||||
|
validates :connection, presence: true
|
||||||
|
validates :client_track_id, presence: true
|
||||||
|
validates :filename, presence: true
|
||||||
|
|
||||||
|
def user
|
||||||
|
self.connection.user
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -18,15 +18,18 @@ module JamRuby
|
||||||
belongs_to :music_session, :class_name => "JamRuby::ActiveMusicSession", foreign_key: :music_session_id
|
belongs_to :music_session, :class_name => "JamRuby::ActiveMusicSession", foreign_key: :music_session_id
|
||||||
has_one :latency_tester, class_name: 'JamRuby::LatencyTester', foreign_key: :client_id, primary_key: :client_id
|
has_one :latency_tester, class_name: 'JamRuby::LatencyTester', foreign_key: :client_id, primary_key: :client_id
|
||||||
has_many :tracks, :class_name => "JamRuby::Track", :inverse_of => :connection, :foreign_key => 'connection_id', :dependent => :delete_all
|
has_many :tracks, :class_name => "JamRuby::Track", :inverse_of => :connection, :foreign_key => 'connection_id', :dependent => :delete_all
|
||||||
|
has_many :backing_tracks, :class_name => "JamRuby::BackingTrack", :inverse_of => :connection, :foreign_key => 'connection_id', :dependent => :delete_all
|
||||||
has_many :video_sources, :class_name => "JamRuby::VideoSource", :inverse_of => :connection, :foreign_key => 'connection_id', :dependent => :delete_all
|
has_many :video_sources, :class_name => "JamRuby::VideoSource", :inverse_of => :connection, :foreign_key => 'connection_id', :dependent => :delete_all
|
||||||
|
|
||||||
|
validates :metronome_open, :inclusion => {:in => [true, false]}
|
||||||
validates :as_musician, :inclusion => {:in => [true, false, nil]}
|
validates :as_musician, :inclusion => {:in => [true, false, nil]}
|
||||||
validates :client_type, :inclusion => {:in => CLIENT_TYPES}
|
validates :client_type, :inclusion => {:in => CLIENT_TYPES}
|
||||||
validates_numericality_of :last_jam_audio_latency, greater_than:0, :allow_nil => true
|
validates_numericality_of :last_jam_audio_latency, greater_than: 0, :allow_nil => true
|
||||||
validate :can_join_music_session, :if => :joining_session?
|
validate :can_join_music_session, :if => :joining_session?
|
||||||
validate :user_or_latency_tester_present
|
validate :user_or_latency_tester_present
|
||||||
|
|
||||||
after_save :require_at_least_one_track_when_in_session, :if => :joining_session?
|
# this is no longer required with the new no-input profile
|
||||||
|
#after_save :require_at_least_one_track_when_in_session, :if => :joining_session?
|
||||||
after_create :did_create
|
after_create :did_create
|
||||||
after_save :report_add_participant
|
after_save :report_add_participant
|
||||||
|
|
||||||
|
|
@ -60,11 +63,11 @@ module JamRuby
|
||||||
def state_message
|
def state_message
|
||||||
case self.aasm_state.to_sym
|
case self.aasm_state.to_sym
|
||||||
when CONNECT_STATE
|
when CONNECT_STATE
|
||||||
'Connected'
|
'Connected'
|
||||||
when STALE_STATE
|
when STALE_STATE
|
||||||
'Stale'
|
'Stale'
|
||||||
else
|
else
|
||||||
'Idle'
|
'Idle'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -83,7 +86,7 @@ module JamRuby
|
||||||
def joining_session?
|
def joining_session?
|
||||||
joining_session
|
joining_session
|
||||||
end
|
end
|
||||||
|
|
||||||
def can_join_music_session
|
def can_join_music_session
|
||||||
|
|
||||||
# puts "can_join_music_session: #{music_session_id} was #{music_session_id_was}" if music_session_id_changed?
|
# puts "can_join_music_session: #{music_session_id} was #{music_session_id_was}" if music_session_id_changed?
|
||||||
|
|
@ -181,8 +184,8 @@ module JamRuby
|
||||||
end
|
end
|
||||||
|
|
||||||
def associate_tracks(tracks)
|
def associate_tracks(tracks)
|
||||||
|
self.tracks.clear()
|
||||||
unless tracks.nil?
|
unless tracks.nil?
|
||||||
self.tracks.clear()
|
|
||||||
tracks.each do |track|
|
tracks.each do |track|
|
||||||
t = Track.new
|
t = Track.new
|
||||||
t.instrument = Instrument.find(track["instrument_id"])
|
t.instrument = Instrument.find(track["instrument_id"])
|
||||||
|
|
|
||||||
|
|
@ -10,73 +10,175 @@ module JamRuby
|
||||||
|
|
||||||
PRODUCT_TYPE = 'JamTrack'
|
PRODUCT_TYPE = 'JamTrack'
|
||||||
|
|
||||||
mount_uploader :url, JamTrackUploader
|
@@log = Logging.logger[JamTrack]
|
||||||
|
|
||||||
|
attr_accessor :uploading_preview
|
||||||
attr_accessible :name, :description, :bpm, :time_signature, :status, :recording_type,
|
attr_accessible :name, :description, :bpm, :time_signature, :status, :recording_type,
|
||||||
:original_artist, :songwriter, :publisher, :licensor, :licensor_id, :pro, :genre, :genre_id, :sales_region, :price,
|
:original_artist, :songwriter, :publisher, :licensor, :licensor_id, :pro, :genre, :genre_id, :sales_region, :price,
|
||||||
:reproduction_royalty, :public_performance_royalty, :reproduction_royalty_amount,
|
:reproduction_royalty, :public_performance_royalty, :reproduction_royalty_amount,
|
||||||
:licensor_royalty_amount, :pro_royalty_amount, :plan_code, :initial_play_silence, :jam_track_tracks_attributes,
|
:licensor_royalty_amount, :pro_royalty_amount, :plan_code, :initial_play_silence, :jam_track_tracks_attributes,
|
||||||
:jam_track_tap_ins_attributes, :available, as: :admin
|
:jam_track_tap_ins_attributes, :version, :jmep_json, :jmep_text, :pro_ascap, :pro_bmi, :pro_sesac, :duration, as: :admin
|
||||||
|
|
||||||
validates :name, presence: true, uniqueness: true, length: {maximum: 200}
|
validates :name, presence: true, uniqueness: true, length: {maximum: 200}
|
||||||
|
validates :plan_code, presence: true, uniqueness: true, length: {maximum: 50 }
|
||||||
validates :description, length: {maximum: 1000}
|
validates :description, length: {maximum: 1000}
|
||||||
validates_format_of :bpm, with: /^\d+\.*\d{0,1}$/
|
validates :time_signature, inclusion: {in: [nil] + [''] + TIME_SIGNATURES} # the empty string is needed because of activeadmin
|
||||||
validates :time_signature, inclusion: {in: [nil] + TIME_SIGNATURES}
|
|
||||||
validates :status, inclusion: {in: [nil] + STATUS}
|
validates :status, inclusion: {in: [nil] + STATUS}
|
||||||
validates :recording_type, inclusion: {in: [nil] + RECORDING_TYPE}
|
validates :recording_type, inclusion: {in: [nil] + RECORDING_TYPE}
|
||||||
validates :original_artist, length: {maximum: 200}
|
validates :original_artist, length: {maximum: 200}
|
||||||
validates :songwriter, length: {maximum: 1000}
|
validates :songwriter, length: {maximum: 1000}
|
||||||
validates :publisher, length: {maximum: 1000}
|
validates :publisher, length: {maximum: 1000}
|
||||||
validates :pro, inclusion: {in: [nil] + PRO}
|
|
||||||
validates :sales_region, inclusion: {in: [nil] + SALES_REGION}
|
validates :sales_region, inclusion: {in: [nil] + SALES_REGION}
|
||||||
validates_format_of :price, with: /^\d+\.*\d{0,2}$/
|
validates_format_of :price, with: /^\d+\.*\d{0,2}$/
|
||||||
validates :initial_play_silence, numericality: true, :allow_nil => true
|
validates :version, presence: true
|
||||||
|
validates :pro_ascap, inclusion: {in: [true, false]}
|
||||||
|
validates :pro_bmi, inclusion: {in: [true, false]}
|
||||||
|
validates :pro_sesac, inclusion: {in: [true, false]}
|
||||||
|
validates :public_performance_royalty, inclusion: {in: [nil, true, false]}
|
||||||
validates :reproduction_royalty, inclusion: {in: [nil, true, false]}
|
validates :reproduction_royalty, inclusion: {in: [nil, true, false]}
|
||||||
validates :public_performance_royalty, inclusion: {in: [nil, true, false]}
|
validates :public_performance_royalty, inclusion: {in: [nil, true, false]}
|
||||||
|
validates :duration, numericality: {only_integer: true}, :allow_nil => true
|
||||||
|
|
||||||
validates_format_of :reproduction_royalty_amount, with: /^\d+\.*\d{0,3}$/
|
validates_format_of :reproduction_royalty_amount, with: /^\d+\.*\d{0,3}$/
|
||||||
validates_format_of :licensor_royalty_amount, with: /^\d+\.*\d{0,3}$/
|
validates_format_of :licensor_royalty_amount, with: /^\d+\.*\d{0,3}$/
|
||||||
validates_format_of :pro_royalty_amount, with: /^\d+\.*\d{0,3}$/
|
|
||||||
|
|
||||||
before_save :sanitize_active_admin
|
|
||||||
|
|
||||||
belongs_to :genre, class_name: "JamRuby::Genre"
|
belongs_to :genre, class_name: "JamRuby::Genre"
|
||||||
belongs_to :licensor , class_name: 'JamRuby::JamTrackLicensor', foreign_key: 'licensor_id'
|
belongs_to :licensor , class_name: 'JamRuby::JamTrackLicensor', foreign_key: 'licensor_id'
|
||||||
|
|
||||||
has_many :jam_track_tracks, :class_name => "JamRuby::JamTrackTrack", order: 'position ASC'
|
has_many :jam_track_tracks, :class_name => "JamRuby::JamTrackTrack", order: 'track_type ASC, position ASC, part ASC, instrument_id ASC'
|
||||||
has_many :jam_track_tap_ins, :class_name => "JamRuby::JamTrackTapIn", order: 'offset_time ASC'
|
has_many :jam_track_tap_ins, :class_name => "JamRuby::JamTrackTapIn", order: 'offset_time ASC'
|
||||||
|
|
||||||
has_many :jam_track_rights, :class_name => "JamRuby::JamTrackRight" #, inverse_of: 'jam_track', :foreign_key => "jam_track_id"
|
has_many :jam_track_rights, :class_name => "JamRuby::JamTrackRight" #, inverse_of: 'jam_track', :foreign_key => "jam_track_id" # '
|
||||||
|
|
||||||
has_many :owners, :through => :jam_track_rights, :class_name => "JamRuby::User", :source => :user
|
has_many :owners, :through => :jam_track_rights, :class_name => "JamRuby::User", :source => :user
|
||||||
|
|
||||||
has_many :playing_sessions, :class_name => "JamRuby::ActiveMusicSession"
|
has_many :playing_sessions, :class_name => "JamRuby::ActiveMusicSession"
|
||||||
|
|
||||||
|
has_many :recordings, :class_name => "JamRuby::Recording"
|
||||||
|
|
||||||
|
# VRFS-2916 jam_tracks.id is varchar: REMOVE
|
||||||
|
# has_many :plays, :class_name => "JamRuby::PlayablePlay", :foreign_key => :jam_track_id, :dependent => :destroy
|
||||||
|
# VRFS-2916 jam_tracks.id is varchar: ADD
|
||||||
|
has_many :plays, :class_name => "JamRuby::PlayablePlay", :as => :playable, :dependent => :destroy
|
||||||
|
|
||||||
accepts_nested_attributes_for :jam_track_tracks, allow_destroy: true
|
accepts_nested_attributes_for :jam_track_tracks, allow_destroy: true
|
||||||
accepts_nested_attributes_for :jam_track_tap_ins, allow_destroy: true
|
accepts_nested_attributes_for :jam_track_tap_ins, allow_destroy: true
|
||||||
|
|
||||||
class << self
|
def duplicate_positions?
|
||||||
def index(options, user)
|
counter = {}
|
||||||
limit = options[:limit]
|
jam_track_tracks.each do |track|
|
||||||
limit ||= 20
|
count = counter[track.position]
|
||||||
limit = limit.to_i
|
if count.nil?
|
||||||
|
count = 0
|
||||||
|
end
|
||||||
|
counter[track.position] = count + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
duplicate = false
|
||||||
|
counter.each do|position, count|
|
||||||
|
if count > 1
|
||||||
|
duplicate = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
duplicate
|
||||||
|
end
|
||||||
|
|
||||||
|
def missing_previews?
|
||||||
|
missing_preview = false
|
||||||
|
self.jam_track_tracks.each do |track|
|
||||||
|
unless track.has_preview?
|
||||||
|
missing_preview = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
missing_preview
|
||||||
|
end
|
||||||
|
|
||||||
|
def onboard_warnings
|
||||||
|
warnings = []
|
||||||
|
warnings << 'POSITIONS' if duplicate_positions?
|
||||||
|
warnings << 'PREVIEWS'if missing_previews?
|
||||||
|
warnings << 'DURATION' if duration.nil?
|
||||||
|
warnings.join(',')
|
||||||
|
end
|
||||||
|
|
||||||
|
def band_jam_track_count
|
||||||
|
JamTrack.where(original_artist: original_artist).count
|
||||||
|
end
|
||||||
|
|
||||||
|
class << self
|
||||||
|
# @return array[artist_name(string)]
|
||||||
|
def all_artists
|
||||||
|
JamTrack.select("original_artist").
|
||||||
|
group("original_artist").
|
||||||
|
collect{|jam_track|jam_track.original_artist}
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return array[JamTrack] for given artist_name
|
||||||
|
def tracks_for_artist(artist_name)
|
||||||
|
JamTrack.where("original_artist=?", artist_name).all
|
||||||
|
end
|
||||||
|
|
||||||
|
def index(options, user)
|
||||||
|
if options[:page]
|
||||||
|
page = options[:page].to_i
|
||||||
|
per_page = options[:per_page].to_i
|
||||||
|
|
||||||
|
if per_page == 0
|
||||||
|
# try and see if limit was specified
|
||||||
|
limit = options[:limit]
|
||||||
|
limit ||= 20
|
||||||
|
limit = limit.to_i
|
||||||
|
else
|
||||||
|
limit = per_page
|
||||||
|
end
|
||||||
|
|
||||||
|
start = (page -1 )* per_page
|
||||||
|
limit = per_page
|
||||||
|
else
|
||||||
|
limit = options[:limit]
|
||||||
|
limit ||= 20
|
||||||
|
limit = limit.to_i
|
||||||
|
|
||||||
|
start = options[:start].presence
|
||||||
|
start = start.to_i || 0
|
||||||
|
|
||||||
|
page = 1 + start/limit
|
||||||
|
per_page = limit
|
||||||
|
end
|
||||||
|
|
||||||
start = options[:start].presence
|
|
||||||
start = start.to_i || 0
|
|
||||||
|
|
||||||
query = JamTrack.joins(:jam_track_tracks)
|
query = JamTrack.joins(:jam_track_tracks)
|
||||||
.paginate(page: 1 + start/limit, per_page: limit)
|
.paginate(page: page, per_page: per_page)
|
||||||
|
|
||||||
if options[:show_purchased_only]
|
if options[:show_purchased_only]
|
||||||
query = query.joins(:jam_track_rights)
|
query = query.joins(:jam_track_rights)
|
||||||
query = query.where("jam_track_rights.user_id = ?", user.id)
|
query = query.where("jam_track_rights.user_id = ?", user.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if options[:artist].present?
|
||||||
|
query = query.where("original_artist=?", options[:artist])
|
||||||
|
end
|
||||||
|
|
||||||
|
if options[:id].present?
|
||||||
|
query = query.where("jam_tracks.id=?", options[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
if options[:group_artist]
|
||||||
|
query = query.select("original_artist, array_agg(jam_tracks.id) AS id, MIN(name) AS name, MIN(description) AS description, MIN(recording_type) AS recording_type, MIN(original_artist) AS original_artist, MIN(songwriter) AS songwriter, MIN(publisher) AS publisher, MIN(sales_region) AS sales_region, MIN(price) AS price, MIN(version) AS version, MIN(genre_id) AS genre_id")
|
||||||
|
query = query.group("original_artist")
|
||||||
|
query = query.order('jam_tracks.original_artist')
|
||||||
|
else
|
||||||
|
query = query.group("jam_tracks.id")
|
||||||
|
query = query.order('jam_tracks.name')
|
||||||
|
end
|
||||||
|
|
||||||
query = query.where("jam_tracks.available = ?", true) unless user.admin
|
query = query.where("jam_tracks.status = ?", 'Production') unless user.admin
|
||||||
query = query.where("jam_tracks.genre_id = '#{options[:genre]}'") unless options[:genre].blank?
|
query = query.where("jam_tracks.genre_id = '#{options[:genre]}'") unless options[:genre].blank?
|
||||||
query = query.where("jam_track_tracks.instrument_id = '#{options[:instrument]}'") unless options[:instrument].blank?
|
query = query.where("jam_track_tracks.instrument_id = '#{options[:instrument]}'") unless options[:instrument].blank?
|
||||||
query = query.where("jam_tracks.sales_region = '#{options[:availability]}'") unless options[:availability].blank?
|
query = query.where("jam_tracks.sales_region = '#{options[:availability]}'") unless options[:availability].blank?
|
||||||
query = query.group("jam_tracks.id")
|
|
||||||
query = query.order('jam_tracks.name')
|
|
||||||
|
|
||||||
if query.length == 0
|
if query.length == 0
|
||||||
[query, nil]
|
[query, nil]
|
||||||
|
|
@ -87,69 +189,19 @@ module JamRuby
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# create storage directory that will house this jam_track, as well as
|
|
||||||
def store_dir
|
|
||||||
"jam_tracks/#{id}"
|
|
||||||
end
|
|
||||||
|
|
||||||
# create name of the file
|
|
||||||
def filename
|
|
||||||
"#{name}.jkz"
|
|
||||||
end
|
|
||||||
|
|
||||||
# creates a short-lived URL that has access to the object.
|
def master_track
|
||||||
# the idea is that this is used when a user who has the rights to this tries to download this JamTrack
|
JamTrackTrack.where(jam_track_id: self.id).where(track_type: 'Master').first
|
||||||
# we would verify their rights (can_download?), and generates a URL in response to the click so that they can download
|
|
||||||
# but the url is short lived enough so that it wouldn't be easily shared
|
|
||||||
def sign_url(expiration_time = 120)
|
|
||||||
s3_manager.sign_url(self[:url], {:expires => expiration_time, :response_content_type => 'audio/jkz', :secure => false})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def can_download?(user)
|
def can_download?(user)
|
||||||
owners.include?(user)
|
owners.include?(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
def right_for_user(user)
|
def right_for_user(user)
|
||||||
jam_track_rights.where("user_id=?", user).first
|
jam_track_rights.where("user_id=?", user).first
|
||||||
end
|
|
||||||
|
|
||||||
def self.list_downloads(user, limit = 100, since = 0)
|
|
||||||
since = 0 unless since || since == '' # guard against nil
|
|
||||||
downloads = []
|
|
||||||
|
|
||||||
user.jam_track_rights
|
|
||||||
.limit(limit)
|
|
||||||
.where('jam_track_rights.id > ?', since)
|
|
||||||
.each do |jam_track_right|
|
|
||||||
downloads << {
|
|
||||||
:type => "jam_track",
|
|
||||||
:id => jam_track_right.id.to_s,
|
|
||||||
:jam_track_id => jam_track_right.jam_track_id,
|
|
||||||
:length => jam_track_right.length,
|
|
||||||
:md5 => jam_track_right.md5,
|
|
||||||
:url => jam_track_right.url,
|
|
||||||
:created_at => jam_track_right.created_at,
|
|
||||||
:next => jam_track_right.id
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
next_id = downloads[-1][:next] if downloads.length > 0
|
|
||||||
next_id = since if next_id.nil? # echo back to the client the same value they passed in, if there are no results
|
|
||||||
|
|
||||||
{
|
|
||||||
'downloads' => downloads,
|
|
||||||
'next' => next_id.to_s
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def sanitize_active_admin
|
|
||||||
self.genre_id = nil if self.genre_id == ''
|
|
||||||
self.licensor_id = nil if self.licensor_id == ''
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -3,19 +3,24 @@ module JamRuby
|
||||||
# describes what users have rights to which tracks
|
# describes what users have rights to which tracks
|
||||||
class JamTrackRight < ActiveRecord::Base
|
class JamTrackRight < ActiveRecord::Base
|
||||||
include JamRuby::S3ManagerMixin
|
include JamRuby::S3ManagerMixin
|
||||||
attr_accessible :user, :jam_track, :user_id, :jam_track_id, :url, :md5, :length, :download_count
|
attr_accessible :user, :jam_track, :user_id, :jam_track_id, :download_count
|
||||||
|
attr_accessible :user_id, :jam_track_id, as: :admin
|
||||||
|
attr_accessible :url_48, :md5_48, :length_48, :url_44, :md5_44, :length_44
|
||||||
belongs_to :user, class_name: "JamRuby::User" # the owner, or purchaser of the jam_track
|
belongs_to :user, class_name: "JamRuby::User" # the owner, or purchaser of the jam_track
|
||||||
belongs_to :jam_track, class_name: "JamRuby::JamTrack"
|
belongs_to :jam_track, class_name: "JamRuby::JamTrack"
|
||||||
|
|
||||||
validates :user, presence:true
|
validates :user, presence:true
|
||||||
validates :jam_track, presence:true
|
validates :jam_track, presence:true
|
||||||
|
validates :is_test_purchase, inclusion: {in: [true, false]}
|
||||||
|
|
||||||
validate :verify_download_count
|
validate :verify_download_count
|
||||||
after_save :after_save
|
after_save :after_save
|
||||||
|
|
||||||
validates_uniqueness_of :user_id, scope: :jam_track_id
|
validates_uniqueness_of :user_id, scope: :jam_track_id
|
||||||
|
|
||||||
# Uploads the JKZ:
|
# Uploads the JKZ:
|
||||||
mount_uploader :url, JamTrackRightUploader
|
mount_uploader :url_48, JamTrackRightUploader
|
||||||
|
mount_uploader :url_44, JamTrackRightUploader
|
||||||
before_destroy :delete_s3_files
|
before_destroy :delete_s3_files
|
||||||
|
|
||||||
MAX_JAM_TRACK_DOWNLOADS = 1000
|
MAX_JAM_TRACK_DOWNLOADS = 1000
|
||||||
|
|
@ -30,7 +35,7 @@ module JamRuby
|
||||||
end
|
end
|
||||||
|
|
||||||
def store_dir
|
def store_dir
|
||||||
"#{jam_track.store_dir}/rights"
|
"jam_track_rights/#{created_at.strftime('%m-%d-%Y')}/#{user_id}-#{id}"
|
||||||
end
|
end
|
||||||
|
|
||||||
# create name of the file
|
# create name of the file
|
||||||
|
|
@ -59,12 +64,17 @@ module JamRuby
|
||||||
else
|
else
|
||||||
raise "Error sending notification #{self.errors}"
|
raise "Error sending notification #{self.errors}"
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
def finish_sign(length, md5)
|
|
||||||
|
def finish_sign(length, md5, bitrate)
|
||||||
self.last_signed_at = Time.now
|
self.last_signed_at = Time.now
|
||||||
self.length = length
|
if bitrate==48
|
||||||
self.md5 = md5
|
self.length_48 = length
|
||||||
|
self.md5_48 = md5
|
||||||
|
else
|
||||||
|
self.length_44 = length
|
||||||
|
self.md5_44 = md5
|
||||||
|
end
|
||||||
self.signed = true
|
self.signed = true
|
||||||
self.error_count = 0
|
self.error_count = 0
|
||||||
self.error_reason = nil
|
self.error_reason = nil
|
||||||
|
|
@ -81,36 +91,50 @@ module JamRuby
|
||||||
# the idea is that this is used when a user who has the rights to this tries to download this JamTrack
|
# the idea is that this is used when a user who has the rights to this tries to download this JamTrack
|
||||||
# we would verify their rights (can_download?), and generates a URL in response to the click so that they can download
|
# we would verify their rights (can_download?), and generates a URL in response to the click so that they can download
|
||||||
# but the url is short lived enough so that it wouldn't be easily shared
|
# but the url is short lived enough so that it wouldn't be easily shared
|
||||||
def sign_url(expiration_time = 120)
|
def sign_url(expiration_time = 120, bitrate=48)
|
||||||
s3_manager.sign_url(self[:url], {:expires => expiration_time, :secure => false})
|
field_name = (bitrate==48) ? "url_48" : "url_44"
|
||||||
|
s3_manager.sign_url(self[field_name], {:expires => expiration_time, :secure => false})
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete_s3_files
|
def delete_s3_files
|
||||||
remove_url!
|
remove_url_48!
|
||||||
|
remove_url_44!
|
||||||
end
|
end
|
||||||
|
|
||||||
def enqueue
|
|
||||||
|
def enqueue(sample_rate=48)
|
||||||
begin
|
begin
|
||||||
JamTrackRight.where(:id => self.id).update_all(:signing_queued_at => Time.now, :signing_started_at => nil, :last_signed_at => nil)
|
JamTrackRight.where(:id => self.id).update_all(:signing_queued_at => Time.now, :signing_started_at => nil, :last_signed_at => nil)
|
||||||
Resque.enqueue(JamTracksBuilder, self.id)
|
Resque.enqueue(JamTracksBuilder, self.id, sample_rate)
|
||||||
true
|
true
|
||||||
rescue Exception => e
|
rescue Exception => e
|
||||||
|
puts "e: #{e}"
|
||||||
# implies redis is down. we don't update started_at by bailing out here
|
# implies redis is down. we don't update started_at by bailing out here
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# if the job is already signed, just queued up for signing, or currently signing, then don't enqueue... otherwise fire it off
|
# if the job is already signed, just queued up for signing, or currently signing, then don't enqueue... otherwise fire it off
|
||||||
def enqueue_if_needed
|
def enqueue_if_needed(sample_rate=48)
|
||||||
state = signing_state
|
state = signing_state
|
||||||
|
|
||||||
if state == 'SIGNED' || state == 'SIGNING' || state == 'QUEUED'
|
if state == 'SIGNED' || state == 'SIGNING' || state == 'QUEUED'
|
||||||
false
|
false
|
||||||
else
|
else
|
||||||
enqueue
|
enqueue(sample_rate)
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# @return true if signed && file exists for the sample_rate specifed:
|
||||||
|
def ready?(sample_rate=48)
|
||||||
|
if sample_rate==48
|
||||||
|
self.signed && self.url_48.present? && self.url_48.file.exists?
|
||||||
|
else
|
||||||
|
self.signed && self.url_44.present? && self.url_44.file.exists?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# returns easy to digest state field
|
# returns easy to digest state field
|
||||||
# SIGNED - the package is ready to be downloaded
|
# SIGNED - the package is ready to be downloaded
|
||||||
# ERROR - the package was built unsuccessfully
|
# ERROR - the package was built unsuccessfully
|
||||||
|
|
@ -123,8 +147,6 @@ module JamRuby
|
||||||
state = nil
|
state = nil
|
||||||
if signed
|
if signed
|
||||||
state = 'SIGNED'
|
state = 'SIGNED'
|
||||||
elsif error_count > 0
|
|
||||||
state = 'ERROR'
|
|
||||||
elsif signing_started_at
|
elsif signing_started_at
|
||||||
if Time.now - signing_started_at > APP_CONFIG.signing_job_run_max_time
|
if Time.now - signing_started_at > APP_CONFIG.signing_job_run_max_time
|
||||||
state = 'SIGNING_TIMEOUT'
|
state = 'SIGNING_TIMEOUT'
|
||||||
|
|
@ -137,6 +159,8 @@ module JamRuby
|
||||||
else
|
else
|
||||||
state = 'QUEUED'
|
state = 'QUEUED'
|
||||||
end
|
end
|
||||||
|
elsif error_count > 0
|
||||||
|
state = 'ERROR'
|
||||||
else
|
else
|
||||||
state = 'QUIET' # needs to be poked to go build
|
state = 'QUIET' # needs to be poked to go build
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,6 @@ module JamRuby
|
||||||
else
|
else
|
||||||
raise "format of offset time must be MM:SS:MLS"
|
raise "format of offset time must be MM:SS:MLS"
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -3,42 +3,90 @@ module JamRuby
|
||||||
# describes an audio track (like the drums, or guitar) that comprises a JamTrack
|
# describes an audio track (like the drums, or guitar) that comprises a JamTrack
|
||||||
class JamTrackTrack < ActiveRecord::Base
|
class JamTrackTrack < ActiveRecord::Base
|
||||||
include JamRuby::S3ManagerMixin
|
include JamRuby::S3ManagerMixin
|
||||||
|
include JamRuby::S3PublicManagerMixin
|
||||||
|
|
||||||
# there should only be one Master per JamTrack, but there can be N Track per JamTrack
|
# there should only be one Master per JamTrack, but there can be N Track per JamTrack
|
||||||
TRACK_TYPE = %w{Master Track}
|
TRACK_TYPE = %w{Track Master}
|
||||||
|
|
||||||
mount_uploader :url, JamTrackTrackUploader
|
@@log = Logging.logger[JamTrackTrack]
|
||||||
|
|
||||||
attr_accessible :jam_track_id, :track_type, :instrument, :instrument_id, :position, :part, :url, as: :admin
|
|
||||||
|
# Because JamTrackImporter imports audio files now, and because also the mere presence of this causes serious issues when updating the model (because reset of url_44 to something bogus), I've removed these
|
||||||
|
#mount_uploader :url_48, JamTrackTrackUploader
|
||||||
|
#mount_uploader :url_44, JamTrackTrackUploader
|
||||||
|
|
||||||
|
attr_accessible :jam_track_id, :track_type, :instrument, :instrument_id, :position, :part, as: :admin
|
||||||
|
attr_accessible :url_44, :url_48, :md5_44, :md5_48, :length_44, :length_48, :preview_start_time_raw, as: :admin
|
||||||
|
|
||||||
|
attr_accessor :original_audio_s3_path, :skip_uploader
|
||||||
|
|
||||||
validates :position, presence: true, numericality: {only_integer: true}, length: {in: 1..1000}
|
validates :position, presence: true, numericality: {only_integer: true}, length: {in: 1..1000}
|
||||||
validates :part, length: {maximum: 20}
|
validates :part, length: {maximum: 25}
|
||||||
validates :track_type, inclusion: {in: TRACK_TYPE }
|
validates :track_type, inclusion: {in: TRACK_TYPE }
|
||||||
validates_uniqueness_of :position, scope: :jam_track_id
|
validates :preview_start_time, numericality: {only_integer: true}, length: {in: 1..1000}, :allow_nil => true
|
||||||
validates_uniqueness_of :part, scope: :jam_track_id
|
validates_uniqueness_of :part, scope: [:jam_track_id, :instrument_id]
|
||||||
# validates :jam_track, presence: true
|
# validates :jam_track, presence: true
|
||||||
|
|
||||||
belongs_to :instrument, class_name: "JamRuby::Instrument"
|
belongs_to :instrument, class_name: "JamRuby::Instrument"
|
||||||
belongs_to :jam_track, class_name: "JamRuby::JamTrack"
|
belongs_to :jam_track, class_name: "JamRuby::JamTrack"
|
||||||
|
|
||||||
|
has_many :recorded_jam_track_tracks, :class_name => "JamRuby::RecordedJamTrackTrack", :foreign_key => :jam_track_track_id, :dependent => :destroy
|
||||||
|
|
||||||
# create storage directory that will house this jam_track, as well as
|
# create storage directory that will house this jam_track, as well as
|
||||||
def store_dir
|
def store_dir
|
||||||
"#{jam_track.store_dir}/tracks"
|
"jam_track_tracks"
|
||||||
end
|
end
|
||||||
|
|
||||||
# create name of the file
|
# create name of the file
|
||||||
def filename
|
def filename(original_name)
|
||||||
track_type == 'Master' ? 'master.ogg' : "#{part}.ogg"
|
"#{store_dir}/#{jam_track.original_artist}/#{jam_track.name}/#{original_name}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# create name of the preview file.
|
||||||
|
# md5-'ed because we cache forever
|
||||||
|
def preview_filename(md5, ext='ogg')
|
||||||
|
original_name = "#{File.basename(self["url_44"], ".ogg")}-preview-#{md5}.#{ext}"
|
||||||
|
"jam_track_previews/#{jam_track.original_artist}/#{jam_track.name}/#{original_name}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def has_preview?
|
||||||
|
!self["preview_url"].nil? && !self['preview_mp3_url'].nil?
|
||||||
|
end
|
||||||
|
|
||||||
|
# generates a URL that points to a public version of the preview
|
||||||
|
def preview_public_url(media_type='ogg')
|
||||||
|
url = media_type == 'ogg' ? self[:preview_url] : self[:preview_mp3_url]
|
||||||
|
if url
|
||||||
|
s3_public_manager.public_url(url,{ :secure => false})
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def manually_uploaded_filename(mounted_as)
|
||||||
|
if track_type == 'Master'
|
||||||
|
filename("Master Mix-#{mounted_as == :url_48 ? '48000' : '44100'}.ogg")
|
||||||
|
else
|
||||||
|
filename("#{jam_track.name} Stem - #{instrument.description}-#{part}-#{mounted_as == :url_48 ? '48000' : '44100'}.ogg")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def master?
|
||||||
|
track_type == 'Master'
|
||||||
|
end
|
||||||
|
|
||||||
|
def url_by_sample_rate(sample_rate=48)
|
||||||
|
field_name = (sample_rate==48) ? "url_48" : "url_44"
|
||||||
|
self[field_name]
|
||||||
|
end
|
||||||
# creates a short-lived URL that has access to the object.
|
# creates a short-lived URL that has access to the object.
|
||||||
# the idea is that this is used when a user who has the rights to this tries to download this JamTrack
|
# the idea is that this is used when a user who has the rights to this tries to download this JamTrack
|
||||||
# we would verify their rights (can_download?), and generates a URL in response to the click so that they can download
|
# we would verify their rights (can_download?), and generates a URL in response to the click so that they can download
|
||||||
# but the url is short lived enough so that it wouldn't be easily shared
|
# but the url is short lived enough so that it wouldn't be easily shared
|
||||||
def sign_url(expiration_time = 120)
|
def sign_url(expiration_time = 120, sample_rate=48)
|
||||||
s3_manager.sign_url(self[:url], {:expires => expiration_time, :response_content_type => 'audio/ogg', :secure => false})
|
s3_manager.sign_url(url_by_sample_rate(sample_rate), {:expires => expiration_time, :response_content_type => 'audio/ogg', :secure => false})
|
||||||
end
|
end
|
||||||
|
|
||||||
def can_download?(user)
|
def can_download?(user)
|
||||||
# I think we have to make a special case for 'previews', but maybe that's just up to the controller to not check can_download?
|
# I think we have to make a special case for 'previews', but maybe that's just up to the controller to not check can_download?
|
||||||
jam_track.owners.include?(user)
|
jam_track.owners.include?(user)
|
||||||
|
|
|
||||||
|
|
@ -136,11 +136,62 @@ module JamRuby
|
||||||
def manifest
|
def manifest
|
||||||
one_day = 60 * 60 * 24
|
one_day = 60 * 60 * 24
|
||||||
|
|
||||||
|
jam_track_offset = 0
|
||||||
|
|
||||||
|
if recording.timeline
|
||||||
|
recording_timeline_data = JSON.parse(recording.timeline)
|
||||||
|
|
||||||
|
recording_start_time = recording_timeline_data["recording_start_time"]
|
||||||
|
jam_track_play_start_time = recording_timeline_data["jam_track_play_start_time"]
|
||||||
|
jam_track_recording_start_play_offset = recording_timeline_data["jam_track_recording_start_play_offset"]
|
||||||
|
|
||||||
|
jam_track_offset = -jam_track_recording_start_play_offset
|
||||||
|
end
|
||||||
|
|
||||||
manifest = { "files" => [], "timeline" => [] }
|
manifest = { "files" => [], "timeline" => [] }
|
||||||
mix_params = []
|
mix_params = []
|
||||||
|
|
||||||
|
|
||||||
|
# this 'pick limiter' logic will ensure that we set a limiter on the 1st recorded_track we come across.
|
||||||
|
pick_limiter = false
|
||||||
|
if recording.is_jamtrack_recording?
|
||||||
|
# we only use the limiter feature if this is a JamTrack recording
|
||||||
|
# by setting this to true, the 1st recorded_track in the database will be the limiter
|
||||||
|
pick_limiter = true
|
||||||
|
end
|
||||||
|
|
||||||
recording.recorded_tracks.each do |recorded_track|
|
recording.recorded_tracks.each do |recorded_track|
|
||||||
manifest["files"] << { "filename" => recorded_track.sign_url(one_day), "codec" => "vorbis", "offset" => 0 }
|
manifest["files"] << { "filename" => recorded_track.sign_url(one_day), "codec" => "vorbis", "offset" => 0, limiter:pick_limiter }
|
||||||
mix_params << { "level" => 100, "balance" => 0 }
|
pick_limiter = false
|
||||||
|
mix_params << { "level" => 1.0, "balance" => 0 }
|
||||||
|
end
|
||||||
|
|
||||||
|
recording.recorded_backing_tracks.each do |recorded_backing_track|
|
||||||
|
manifest["files"] << { "filename" => recorded_backing_track.sign_url(one_day), "codec" => "vorbis", "offset" => 0 }
|
||||||
|
mix_params << { "level" => 1.0, "balance" => 0 }
|
||||||
|
end
|
||||||
|
|
||||||
|
recording.recorded_jam_track_tracks.each do |recorded_jam_track_track|
|
||||||
|
manifest["files"] << { "filename" => recorded_jam_track_track.jam_track_track.sign_url(one_day), "codec" => "vorbis", "offset" => jam_track_offset }
|
||||||
|
# let's look for level info from the client
|
||||||
|
level = 1.0 # default value - means no effect
|
||||||
|
if recorded_jam_track_track.timeline
|
||||||
|
|
||||||
|
timeline_data = JSON.parse(recorded_jam_track_track.timeline)
|
||||||
|
|
||||||
|
# always take the 1st entry for now
|
||||||
|
first = timeline_data[0]
|
||||||
|
|
||||||
|
if first["mute"]
|
||||||
|
# mute equates to no noise
|
||||||
|
level = 0.0
|
||||||
|
else
|
||||||
|
# otherwise grab the left channel...
|
||||||
|
level = first["vol_l"]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
mix_params << { "level" => level, "balance" => 0 }
|
||||||
end
|
end
|
||||||
|
|
||||||
manifest["timeline"] << { "timestamp" => 0, "mix" => mix_params }
|
manifest["timeline"] << { "timestamp" => 0, "mix" => mix_params }
|
||||||
|
|
|
||||||
|
|
@ -307,7 +307,7 @@ module JamRuby
|
||||||
filter_approved = only_approved ? 'AND rrrs.chosen = true' : ''
|
filter_approved = only_approved ? 'AND rrrs.chosen = true' : ''
|
||||||
|
|
||||||
MusicSession.where(%Q{music_sessions.canceled = FALSE AND
|
MusicSession.where(%Q{music_sessions.canceled = FALSE AND
|
||||||
music_sessions.create_type != '#{CREATE_TYPE_QUICK_START}' AND
|
(music_sessions.create_type is NULL OR music_sessions.create_type != '#{CREATE_TYPE_QUICK_START}') AND
|
||||||
(music_sessions.scheduled_start is NULL OR music_sessions.scheduled_start > NOW() - '4 hour'::INTERVAL) AND
|
(music_sessions.scheduled_start is NULL OR music_sessions.scheduled_start > NOW() - '4 hour'::INTERVAL) AND
|
||||||
music_sessions.id in (
|
music_sessions.id in (
|
||||||
select distinct(rs.music_session_id)
|
select distinct(rs.music_session_id)
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ module JamRuby
|
||||||
session_user_history.music_session_id = music_session_id
|
session_user_history.music_session_id = music_session_id
|
||||||
session_user_history.user_id = user_id
|
session_user_history.user_id = user_id
|
||||||
session_user_history.client_id = client_id
|
session_user_history.client_id = client_id
|
||||||
session_user_history.instruments = tracks.map {|t| t[:instrument_id]}.join("|")
|
session_user_history.instruments = tracks.map {|t| t[:instrument_id]}.join("|") if tracks
|
||||||
session_user_history.save
|
session_user_history.save
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,27 @@ module JamRuby
|
||||||
class PlayablePlay < ActiveRecord::Base
|
class PlayablePlay < ActiveRecord::Base
|
||||||
self.table_name = "playable_plays"
|
self.table_name = "playable_plays"
|
||||||
|
|
||||||
belongs_to :playable, :polymorphic => :true, :counter_cache => :play_count
|
belongs_to :playable, :polymorphic => :true
|
||||||
|
# VRFS-2916 jam_tracks.id is varchar: REMOVE
|
||||||
|
#belongs_to :jam_track, :foreign_key => :jam_track_id
|
||||||
belongs_to :user, :class_name => "JamRuby::User", :foreign_key => "player_id"
|
belongs_to :user, :class_name => "JamRuby::User", :foreign_key => "player_id"
|
||||||
belongs_to :claimed_recording, :class_name => "JamRuby::ClaimedRecording", :foreign_key => "claimed_recording_id"
|
belongs_to :claimed_recording, :class_name => "JamRuby::ClaimedRecording", :foreign_key => "claimed_recording_id"
|
||||||
|
|
||||||
|
validate do
|
||||||
|
# VRFS-2916 jam_tracks.id is varchar: REMOVE
|
||||||
|
#if !playable_id && !jam_track_id
|
||||||
|
# self.errors[:base] << 'No playable instance detected'
|
||||||
|
#end
|
||||||
|
|
||||||
|
# VRFS-2916 jam_tracks.id is varchar: ADD
|
||||||
|
if !playable_id
|
||||||
|
self.errors[:base] << 'No playable instance detected'
|
||||||
|
end
|
||||||
|
|
||||||
|
if !user
|
||||||
|
self.errors[:base] << 'No user detected'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,196 @@
|
||||||
|
module JamRuby
|
||||||
|
# BackingTrack analog to JamRuby::RecordedTrack
|
||||||
|
class RecordedBackingTrack < ActiveRecord::Base
|
||||||
|
|
||||||
|
include JamRuby::S3ManagerMixin
|
||||||
|
|
||||||
|
attr_accessor :marking_complete
|
||||||
|
attr_writer :current_user
|
||||||
|
|
||||||
|
belongs_to :user, :class_name => "JamRuby::User", :inverse_of => :recorded_backing_tracks
|
||||||
|
belongs_to :recording, :class_name => "JamRuby::Recording", :inverse_of => :recorded_backing_tracks
|
||||||
|
validates :filename, :presence => true
|
||||||
|
|
||||||
|
validates :client_id, :presence => true # not a connection relation on purpose
|
||||||
|
validates :backing_track_id, :presence => true # not a track relation on purpose
|
||||||
|
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 :download_count, presence: true
|
||||||
|
|
||||||
|
before_destroy :delete_s3_files
|
||||||
|
validate :validate_fully_uploaded
|
||||||
|
validate :validate_part_complete
|
||||||
|
validate :validate_too_many_upload_failures
|
||||||
|
validate :verify_download_count
|
||||||
|
|
||||||
|
def self.create_from_backing_track(backing_track, recording)
|
||||||
|
recorded_backing_track = self.new
|
||||||
|
recorded_backing_track.recording = recording
|
||||||
|
recorded_backing_track.client_id = backing_track.connection.client_id
|
||||||
|
recorded_backing_track.backing_track_id = backing_track.id
|
||||||
|
recorded_backing_track.client_track_id = "R" + backing_track.client_track_id # Matches behavior in RecordingManager.cpp#getWavComment
|
||||||
|
recorded_backing_track.user = backing_track.connection.user
|
||||||
|
recorded_backing_track.filename = backing_track.filename
|
||||||
|
recorded_backing_track.next_part_to_upload = 0
|
||||||
|
recorded_backing_track.file_offset = 0
|
||||||
|
recorded_backing_track[:url] = construct_filename(recording.created_at, recording.id, backing_track.client_track_id)
|
||||||
|
recorded_backing_track.save
|
||||||
|
recorded_backing_track
|
||||||
|
end
|
||||||
|
|
||||||
|
def sign_url(expiration_time = 120)
|
||||||
|
s3_manager.sign_url(self[:url], {:expires => expiration_time, :response_content_type => 'audio/ogg', :secure => false})
|
||||||
|
end
|
||||||
|
|
||||||
|
def can_download?(some_user)
|
||||||
|
claimed_recording = recording.claimed_recordings.find{|claimed_recording| claimed_recording.user == some_user }
|
||||||
|
|
||||||
|
if claimed_recording
|
||||||
|
!claimed_recording.discarded
|
||||||
|
else
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def too_many_upload_failures?
|
||||||
|
upload_failures >= APP_CONFIG.max_track_upload_failures
|
||||||
|
end
|
||||||
|
|
||||||
|
def too_many_downloads?
|
||||||
|
(self.download_count < 0 || self.download_count > APP_CONFIG.max_audio_downloads) && !@current_user.admin
|
||||||
|
end
|
||||||
|
|
||||||
|
def upload_starting?
|
||||||
|
next_part_to_upload_was == 0 && next_part_to_upload == 1
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_too_many_upload_failures
|
||||||
|
if upload_failures >= APP_CONFIG.max_track_upload_failures
|
||||||
|
errors.add(:upload_failures, ValidationMessages::UPLOAD_FAILURES_EXCEEDED)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_fully_uploaded
|
||||||
|
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
|
||||||
|
if is_part_uploading_was && !is_part_uploading
|
||||||
|
if next_part_to_upload_was + 1 != next_part_to_upload
|
||||||
|
errors.add(:next_part_to_upload, ValidationMessages::INVALID_PART_NUMBER_SPECIFIED)
|
||||||
|
end
|
||||||
|
|
||||||
|
if file_offset > length
|
||||||
|
errors.add(:file_offset, ValidationMessages::FILE_OFFSET_EXCEEDS_LENGTH)
|
||||||
|
end
|
||||||
|
elsif next_part_to_upload_was + 1 == next_part_to_upload
|
||||||
|
# this makes sure we are only catching 'upload_part_complete' transitions, and not upload_start
|
||||||
|
if next_part_to_upload_was != 0
|
||||||
|
# we see that the part number was ticked--but was is_part_upload set to true before this transition?
|
||||||
|
if !is_part_uploading_was && !is_part_uploading
|
||||||
|
errors.add(:next_part_to_upload, ValidationMessages::PART_NOT_STARTED)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def verify_download_count
|
||||||
|
if (self.download_count < 0 || self.download_count > APP_CONFIG.max_audio_downloads) && !@current_user.admin
|
||||||
|
errors.add(:download_count, "must be less than or equal to 100")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def upload_start(length, md5)
|
||||||
|
#self.upload_id set by the observer
|
||||||
|
self.next_part_to_upload = 1
|
||||||
|
self.length = length
|
||||||
|
self.md5 = md5
|
||||||
|
save
|
||||||
|
end
|
||||||
|
|
||||||
|
# if for some reason the server thinks the client can't carry on with the upload,
|
||||||
|
# this resets everything to the initial state
|
||||||
|
def reset_upload
|
||||||
|
self.upload_failures = self.upload_failures + 1
|
||||||
|
self.part_failures = 0
|
||||||
|
self.file_offset = 0
|
||||||
|
self.next_part_to_upload = 0
|
||||||
|
self.upload_id = nil
|
||||||
|
self.md5 = nil
|
||||||
|
self.length = 0
|
||||||
|
self.fully_uploaded = false
|
||||||
|
self.is_part_uploading = false
|
||||||
|
save :validate => false # skip validation because we need this to always work
|
||||||
|
end
|
||||||
|
|
||||||
|
def upload_next_part(length, md5)
|
||||||
|
self.marking_complete = true
|
||||||
|
if next_part_to_upload == 0
|
||||||
|
upload_start(length, md5)
|
||||||
|
end
|
||||||
|
self.is_part_uploading = true
|
||||||
|
save
|
||||||
|
end
|
||||||
|
|
||||||
|
def upload_sign(content_md5)
|
||||||
|
s3_manager.upload_sign(self[:url], content_md5, next_part_to_upload, upload_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def upload_part_complete(part, offset)
|
||||||
|
# validated by :validate_part_complete
|
||||||
|
self.marking_complete = true
|
||||||
|
self.is_part_uploading = false
|
||||||
|
self.next_part_to_upload = self.next_part_to_upload + 1
|
||||||
|
self.file_offset = offset.to_i
|
||||||
|
self.part_failures = 0
|
||||||
|
save
|
||||||
|
end
|
||||||
|
|
||||||
|
def upload_complete
|
||||||
|
# validate from happening twice by :validate_fully_uploaded
|
||||||
|
self.fully_uploaded = true
|
||||||
|
self.marking_complete = true
|
||||||
|
save
|
||||||
|
end
|
||||||
|
|
||||||
|
def increment_part_failures(part_failure_before_error)
|
||||||
|
self.part_failures = part_failure_before_error + 1
|
||||||
|
RecordedBackingTrack.update_all("part_failures = #{self.part_failures}", "id = '#{self.id}'")
|
||||||
|
end
|
||||||
|
|
||||||
|
def stored_filename
|
||||||
|
# construct a path from s3
|
||||||
|
RecordedBacknigTrack.construct_filename(recording.created_at, self.recording.id, self.client_track_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_download_count(count=1)
|
||||||
|
self.download_count = self.download_count + count
|
||||||
|
self.last_downloaded_at = Time.now
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_s3_files
|
||||||
|
s3_manager.delete(self[:url]) if self[:url] && s3_manager.exists?(self[:url])
|
||||||
|
end
|
||||||
|
|
||||||
|
def mark_silent
|
||||||
|
destroy
|
||||||
|
|
||||||
|
# check if we have all the files we need, now that the recorded_backing_track is out of the way
|
||||||
|
recording.preconditions_for_mix?
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def self.construct_filename(created_at, recording_id, client_track_id)
|
||||||
|
raise "unknown ID" unless client_track_id
|
||||||
|
"recordings/#{created_at.strftime('%m-%d-%Y')}/#{recording_id}/backing-track-#{client_track_id}.ogg"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
module JamRuby
|
||||||
|
class RecordedBackingTrackObserver < ActiveRecord::Observer
|
||||||
|
|
||||||
|
# if you change the this class, tests really should accompany. having alot of logic in observers is really tricky, as we do here
|
||||||
|
observe JamRuby::RecordedBackingTrack
|
||||||
|
|
||||||
|
def before_validation(recorded_backing_tracks)
|
||||||
|
|
||||||
|
# if we see that a part was just uploaded entirely, validate that we can find the part that was just uploaded
|
||||||
|
if recorded_backing_tracks.is_part_uploading_was && !recorded_backing_tracks.is_part_uploading
|
||||||
|
begin
|
||||||
|
aws_part = recorded_backing_tracks.s3_manager.multiple_upload_find_part(recorded_backing_tracks[:url], recorded_backing_tracks.upload_id, recorded_backing_tracks.next_part_to_upload - 1)
|
||||||
|
# calling size on a part that does not exist will throw an exception... that's what we want
|
||||||
|
aws_part.size
|
||||||
|
rescue SocketError => e
|
||||||
|
raise # this should cause a 500 error, which is what we want. The client will retry later on 500.
|
||||||
|
rescue Exception => e
|
||||||
|
recorded_backing_tracks.errors.add(:next_part_to_upload, ValidationMessages::PART_NOT_FOUND_IN_AWS)
|
||||||
|
rescue RuntimeError => e
|
||||||
|
recorded_backing_tracks.errors.add(:next_part_to_upload, ValidationMessages::PART_NOT_FOUND_IN_AWS)
|
||||||
|
rescue
|
||||||
|
recorded_backing_tracks.errors.add(:next_part_to_upload, ValidationMessages::PART_NOT_FOUND_IN_AWS)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
# if we detect that this just became fully uploaded -- if so, tell s3 to put the parts together
|
||||||
|
if recorded_backing_tracks.marking_complete && !recorded_backing_tracks.fully_uploaded_was && recorded_backing_tracks.fully_uploaded
|
||||||
|
|
||||||
|
multipart_success = false
|
||||||
|
begin
|
||||||
|
recorded_backing_tracks.s3_manager.multipart_upload_complete(recorded_backing_tracks[:url], recorded_backing_tracks.upload_id)
|
||||||
|
multipart_success = true
|
||||||
|
rescue SocketError => e
|
||||||
|
raise # this should cause a 500 error, which is what we want. The client will retry later.
|
||||||
|
rescue Exception => e
|
||||||
|
#recorded_track.reload
|
||||||
|
recorded_backing_tracks.reset_upload
|
||||||
|
recorded_backing_tracks.errors.add(:upload_id, ValidationMessages::BAD_UPLOAD)
|
||||||
|
end
|
||||||
|
|
||||||
|
# unlike RecordedTracks, only the person who uploaded can download it, so no need to notify
|
||||||
|
|
||||||
|
# tell all users that a download is available, except for the user who just uploaded
|
||||||
|
# recorded_backing_tracks.recording.users.each do |user|
|
||||||
|
#Notification.send_download_available(recorded_backing_tracks.user_id) unless user == recorded_backing_tracks.user
|
||||||
|
# end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def after_commit(recorded_backing_track)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
# here we tick upload failure counts, or revert the state of the model, as needed
|
||||||
|
def after_rollback(recorded_backing_track)
|
||||||
|
# if fully uploaded, don't increment failures
|
||||||
|
if recorded_backing_track.fully_uploaded
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
# increment part failures if there is a part currently being uploaded
|
||||||
|
if recorded_backing_track.is_part_uploading_was
|
||||||
|
#recorded_track.reload # we don't want anything else that the user set to get applied
|
||||||
|
recorded_backing_track.increment_part_failures(recorded_backing_track.part_failures_was)
|
||||||
|
if recorded_backing_track.part_failures >= APP_CONFIG.max_track_part_upload_failures
|
||||||
|
# save upload id before we abort this bad boy
|
||||||
|
upload_id = recorded_backing_track.upload_id
|
||||||
|
begin
|
||||||
|
recorded_backing_track.s3_manager.multipart_upload_abort(recorded_backing_track[:url], upload_id)
|
||||||
|
rescue => e
|
||||||
|
puts e.inspect
|
||||||
|
end
|
||||||
|
recorded_backing_track.reset_upload
|
||||||
|
if recorded_backing_track.upload_failures >= APP_CONFIG.max_track_upload_failures
|
||||||
|
# do anything?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def before_save(recorded_backing_track)
|
||||||
|
# if we are on the 1st part, then we need to make sure we can save the upload_id
|
||||||
|
if recorded_backing_track.next_part_to_upload == 1
|
||||||
|
recorded_backing_track.upload_id = recorded_backing_track.s3_manager.multipart_upload_start(recorded_backing_track[:url])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
module JamRuby
|
||||||
|
# BackingTrack analog to JamRuby::RecordedTrack
|
||||||
|
class RecordedJamTrackTrack < ActiveRecord::Base
|
||||||
|
|
||||||
|
belongs_to :user, :class_name => "JamRuby::User", :inverse_of => :recorded_jam_track_tracks
|
||||||
|
belongs_to :recording, :class_name => "JamRuby::Recording", :inverse_of => :recorded_jam_track_tracks
|
||||||
|
belongs_to :jam_track_track, :class_name => "JamRuby::JamTrackTrack", :inverse_of => :recorded_jam_track_tracks
|
||||||
|
|
||||||
|
validates :user, presence: true
|
||||||
|
validates :jam_track_track, presence:true
|
||||||
|
|
||||||
|
def self.create_from_jam_track_track(jam_track_track, recording, user)
|
||||||
|
recorded_jam_track_track = self.new
|
||||||
|
recorded_jam_track_track.recording = recording
|
||||||
|
recorded_jam_track_track.jam_track_track = jam_track_track
|
||||||
|
recorded_jam_track_track.user = user
|
||||||
|
recorded_jam_track_track.save
|
||||||
|
recorded_jam_track_track
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue