VRFS-3728 merge

This commit is contained in:
Jonathan Kolyer 2015-12-04 05:29:04 +00:00
commit bcca86da8f
134 changed files with 3739 additions and 2291 deletions

View File

@ -44,7 +44,7 @@ gem 'rails3-jquery-autocomplete'
gem 'activeadmin' #, github: 'activeadmin', branch: '0-6-stable'
gem 'mime-types', '1.25'
gem 'meta_search'
gem 'fog', "~> 1.32.0"
gem 'fog'
gem 'unf', '0.1.3' #optional fog dependency
gem 'country-select'
gem 'aasm', '3.0.16'

View File

@ -0,0 +1,34 @@
ActiveAdmin.register JamRuby::DownloadTracker, :as => 'DownloadTrackers' do
menu :label => 'Download Trackers', :parent => 'JamTracks'
config.batch_actions = false
config.filters = true
config.per_page = 50
filter :remote_ip
index do
column 'User' do |oo| oo.user ? link_to(oo.user.email, oo.user.admin_url, {:title => oo.user.email}) : '' end
column 'Remote IP' do |oo| oo.remote_ip end
column 'JamTrack' do |oo| oo.jam_track end
column 'Paid' do |oo| oo.paid end
column 'Blacklisted?' do |oo| IpBlacklist.listed(oo.remote_ip) ? 'Yes' : 'No' end
column "" do |oo|
link_to 'Blacklist This IP', "download_trackers/#{oo.id}/blacklist_by_ip"
end
end
member_action :blacklist_by_ip, :method => :get do
tracker = DownloadTracker.find(params[:id])
if !IpBlacklist.listed(tracker.remote_ip)
ip = IpBlacklist.new
ip.remote_ip = tracker.remote_ip
ip.save!
end
redirect_to admin_download_trackers_path, :notice => "IP address #{tracker.remote_ip} blacklisted."
end
end

View File

@ -32,6 +32,7 @@ ActiveAdmin.register_page "Fake Purchaser" do
jam_track_right.user = user
jam_track_right.jam_track = jam_track
jam_track_right.is_test_purchase = true
jam_track_right.version = jam_track.version
jam_track_right.save!
count = count + 1
end

View File

@ -0,0 +1,41 @@
ActiveAdmin.register_page "Giftcarduploads" do
menu :label => 'Gift Cards Upload', :parent => 'JamTracks'
page_action :upload_giftcards, :method => :post do
GiftCard.transaction do
puts params
file = params[:jam_ruby_gift_card][:csv]
array_of_arrays = CSV.read(file.tempfile.path)
array_of_arrays.each do |row|
if row.length != 1
raise "UKNONWN CSV FORMAT! Must be 1 column"
end
code = row[0]
gift_card = GiftCard.new
gift_card.code = code
gift_card.card_type = params[:jam_ruby_gift_card][:card_type]
gift_card.origin = file .original_filename
gift_card.save!
end
redirect_to admin_giftcarduploads_path, :notice => "Created #{array_of_arrays.length} gift cards!"
end
end
content do
semantic_form_for GiftCard.new, :url => admin_giftcarduploads_upload_giftcards_path, :builder => ActiveAdmin::FormBuilder do |f|
f.inputs "Upload Gift Cards" do
f.input :csv, as: :file, required: true, :label => "A single column CSV that contains ONE type of gift card (5 JamTrack, 10 JamTrack, etc)"
f.input :card_type, required:true, as: :select, :collection => JamRuby::GiftCard::CARD_TYPES
end
f.actions
end
end
end

View File

@ -0,0 +1,24 @@
ActiveAdmin.register JamRuby::GiftCard, :as => 'GiftCards' do
menu :label => 'Gift Cards', :parent => 'JamTracks'
config.batch_actions = false
config.filters = true
config.per_page = 50
scope("Redeemed Most Recently", default: true) { |scope| scope.where('user_id IS NOT NULL').order('updated_at DESC') }
scope("Available") { |scope| scope.where('user_id is NULL') }
filter :card_type
filter :origin
filter :code
index do
column 'User' do |oo| oo.user ? link_to(oo.user.email, oo.user.admin_url, {:title => oo.user.email}) : '' end
column 'Code' do |oo| oo.code end
column 'Card Type' do |oo| oo.card_type end
column 'Origin' do |oo| oo.origin end
column 'Created' do |oo| oo.created_at end
end
end

View File

@ -11,7 +11,8 @@ ActiveAdmin.register JamRuby::JamTrack, :as => 'JamTracks' do
scope("Default", default: true) { |scope| scope }
scope("Onboarding TODO") { |scope| scope.where('onboarding_exceptions is not null') }
scope("Tency Only") { |scope| scope.joins('INNER JOIN jam_track_licensors as licensors ON jam_tracks.licensor_id = licensors.id').where("licensors.name = 'Tency Music'") }
scope("Onboarding TODO w/ Tency Only") { |scope| scope.joins('INNER JOIN jam_track_licensors as licensors ON jam_tracks.licensor_id = licensors.id').where("licensors.name = 'Tency Music'").where('onboarding_exceptions is not null') }
scope("TimTracks Only") { |scope| scope.joins('INNER JOIN jam_track_licensors as licensors ON jam_tracks.licensor_id = licensors.id').where("licensors.name = 'Tim Waurick'") }
# scope("Onboarding TODO w/ Tency Only") { |scope| scope.joins('INNER JOIN jam_track_licensors as licensors ON jam_tracks.licensor_id = licensors.id').where("licensors.name = 'Tency Music'").where('onboarding_exceptions is not null') }
form :partial => 'form'

View File

@ -15,5 +15,11 @@ class EmailController < ApplicationController
headers['Content-Type'] ||= 'text/csv'
@users = User.where(subscribe_email: true)
# if specified, return only users that have redeemed or bought a JamTrack
if params[:any_jam_track]
@users = @users.select('DISTINCT users.id, email, first_name, last_name').joins(:sales => :sale_line_items).where("sale_line_items.product_type = 'JamTrack'")
end
end
end

View File

@ -13,6 +13,7 @@
= f.input :publisher, :input_html => { :rows=>1, :maxlength=>1000 }
= f.input :licensor, collection: JamRuby::JamTrackLicensor.all, include_blank: true
= f.input :genres
= f.input :year
= 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 :price, :required => true, :input_html => {type: 'numeric'}

View File

@ -0,0 +1,9 @@
class JamRuby::GiftCard
attr_accessor :csv
def process_csv
end
end

View File

@ -2,28 +2,5 @@ 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.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

View File

@ -310,3 +310,8 @@ web_playable_jamtracks.sql
affiliate_partner_rate.sql
track_downloads.sql
jam_track_lang_idx.sql
giftcard.sql
add_description_to_crash_dumps.sql
acappella.sql
purchasable_gift_cards.sql
versionable_jamtracks.sql

2
db/up/acappella.sql Normal file
View File

@ -0,0 +1,2 @@
INSERT INTO genres (id, description) values ('acapella', 'A Capella');
ALTER TABLE jam_track_licensors ADD COLUMN slug VARCHAR UNIQUE;

View File

@ -0,0 +1 @@
ALTER TABLE crash_dumps ADD COLUMN description VARCHAR(20000);

13
db/up/giftcard.sql Normal file
View File

@ -0,0 +1,13 @@
CREATE TABLE gift_cards (
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
code VARCHAR(64) UNIQUE NOT NULL,
user_id VARCHAR (64) REFERENCES users(id) ON DELETE CASCADE,
card_type VARCHAR(64) NOT NULL,
origin VARCHAR(200),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX gift_card_user_id_idx ON gift_cards(user_id);
ALTER TABLE users ADD COLUMN gifted_jamtracks INTEGER DEFAULT 0;

View File

@ -0,0 +1,24 @@
CREATE TABLE gift_card_types (
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
card_type VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO gift_card_types (id, card_type) VALUES ('jam_tracks_5', 'jam_tracks_5');
INSERT INTO gift_card_types (id, card_type) VALUES ('jam_tracks_10', 'jam_tracks_10');
CREATE TABLE gift_card_purchases (
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id VARCHAR(64) NOT NULL REFERENCES users(id) ON DELETE SET NULL,
gift_card_type_id VARCHAR(64) REFERENCES gift_card_types(id) ON DELETE SET NULL,
recurly_adjustment_uuid VARCHAR(500),
recurly_adjustment_credit_uuid VARCHAR(500),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
ALTER TABLE sale_line_items ADD COLUMN gift_card_purchase_id VARCHAR(64) REFERENCES gift_card_purchases(id);

View File

@ -0,0 +1 @@
ALTER TABLE jam_track_rights ADD COLUMN version VARCHAR NOT NULL DEFAULT '0';

View File

@ -253,6 +253,9 @@ require "jam_ruby/models/musician_search"
require "jam_ruby/models/band_search"
require "jam_ruby/import/tency_stem_mapping"
require "jam_ruby/models/jam_track_search"
require "jam_ruby/models/gift_card"
require "jam_ruby/models/gift_card_purchase"
require "jam_ruby/models/gift_card_type"
include Jampb

View File

@ -19,6 +19,8 @@ module ValidationMessages
# sessions
SESSION_NOT_FOUND = "Session not found."
NOT_FOUND = 'not found'
# genres
RECORDING_GENRE_LIMIT_EXCEEDED = "No more than 1 genre is allowed."
BAND_GENRE_LIMIT_EXCEEDED = "No more than 3 genres are allowed."

View File

@ -29,10 +29,187 @@ module JamRuby
end
def finish(reason, detail)
@@log.info("JamTrackImporter:#{self.name} #{reason}")
self.reason = reason
self.detail = detail
end
def import_click_track(jam_track)
# we need to download the click track, if it exists.
Dir.mktmpdir do |tmp_dir|
@@log.info("importing clicking track for #{jam_track.original_artist}:#{jam_track.name}")
if jam_track.click_track
@@log.info("already has click track: #{jam_track.original_artist}:#{jam_track.name}")
finish('success', 'already_has_click_track')
return
end
click_track_file = jam_track.click_track_file
if click_track_file.nil?
@@log.info("no click track for #{jam_track.original_artist}:#{jam_track.name}")
finish('success', 'no_click_track')
return
end
original_filename = click_track_file[:original_filename]
if original_filename.nil?
@@log.info("no click track s3 path for #{jam_track.original_artist}:#{jam_track.name}")
finish('no_original_source', 'click track is missing s3 path:' + click_track_file.id)
return
end
#wav_file = File.join(tmp_dir, File.basename(click_track_file[:original_filename]))
#JamTrackImporter.song_storage_manager.download(click_track_file[:original_filename], wav_file)
JamTrack.transaction do
click_track = jam_track.click_track
if click_track.nil?
click_track = JamTrackTrack.new
click_track.original_filename = click_track_file[:original_filename]
click_track.original_audio_s3_path = click_track_file[:original_filename]
click_track.track_type = 'Click'
click_track.part = 'Clicktrack'
click_track.instrument_id = 'computer'
click_track.jam_track = jam_track
click_track.position = 10000
if !click_track.save
@@log.error("unable to create jamtrack click track #{click_track.errors.inspect}")
finish("jam_track_click", "unable to create: #{click_track.errors.inspect}")
return false
end
end
jam_track.increment_version!
# with the click track in hand, flesh out the details
synchronize_audio_track(jam_track, tmp_dir, false, click_track)
finish('success', nil)
end
end
end
def generate_jmep(jam_track)
if !jam_track.blank?
finish('success', 'jmep already exists')
return
else
# we need to download the click track, if it exists.
Dir.mktmpdir do |tmp_dir|
master_track = jam_track.master_track
click_track = jam_track.click_track_file
if master_track.nil?
finish('no_master_track', nil)
return
end
master_track_file = File.join(tmp_dir, File.basename(master_track[:url_48]))
begin
JamTrackImporter.private_s3_manager.download(master_track.url_by_sample_rate(44), master_track_file)
rescue Exception => e
@@log.error("unable to download master track")
finish("no-download-master", master_track.url_by_sample_rate(44))
return
end
if click_track
click_track_file = File.join(tmp_dir, File.basename(click_track[:original_filename]))
JamTrackImporter.song_storage_manager.download(click_track[:original_filename], click_track_file)
else
# we'll use the master for click analysis. not ideal, but would work
click_track_file = master_track_file
end
start_time = determine_start_time(master_track_file, tmp_dir, master_track[:url])
# bpm comes from git clone http://www.pogo.org.uk/~mark/bpm-tools.git
sox="sox #{Shellwords.escape(click_track_file)} -t raw -r 44100 -e float -c 1 - | bpm"
cmd = "bash -c #{Shellwords.escape(sox)}"
@@log.debug("executing cmd #{cmd}")
output=`#{cmd}`
result_code = $?.to_i
if result_code == 0
bpm = output.to_f
@@log.debug("bpm: #{bpm} start_time: #{start_time}")
metro_fin = "#{Time.at(start_time).utc.strftime("%H:%M:%S")}:#{((start_time - start_time.to_i) * 1000).round}"
jmep = ""
jmep << "# created via code using bpm/silence detection (bpm:#{bpm})\r\n"
jmep << "prelude@10.0 #number of seconds before music starts\r\n"
jmep << "metro_fin@#{metro_fin} bpm=#{bpm}, ticks=8, pmode=stream, name=Beep, play=mono"
@@log.info("jmep generated: #{jmep}")
jam_track.jmep_text = jmep
if jam_track.save
finish('success', nil)
else
@@log.error("jamtrack did not save. #{jam_track.errors.inspect}")
finish("no-save", "jamtrack did not save. #{jam_track.errors.inspect}")
return
end
else
finish("bpm-fail", "failed to run bpm: #{output}")
return
end
end
end
end
def determine_start_time(audio_file, tmp_dir, original_filename)
burp_gaps = ['0.3', '0.2', '0.1', '0.05']
out_wav = File.join(tmp_dir, 'stripped.wav')
total_time_command = "soxi -D \"#{audio_file}\""
total_time = `#{total_time_command}`.to_f
result_code = -20
stripped_time = total_time # default to the case where we just start the preview at the beginning
burp_gaps.each do |gap|
command_strip_lead_silence = "sox \"#{audio_file}\" \"#{out_wav}\" silence 1 #{gap} 1%"
@@log.debug("stripping silence: " + command_strip_lead_silence)
output = `#{command_strip_lead_silence}`
result_code = $?.to_i
if result_code == 0
stripped_time_command = "soxi -D \"#{out_wav}\""
stripped_time_test = `#{stripped_time_command}`.to_f
if stripped_time_test < 1 # meaning a very short duration
@@log.warn("could not determine the start of non-silence. assuming beginning")
stripped_time = total_time # default to the case where we just start the preview at the beginning
else
stripped_time = stripped_time_test # accept the measured time of the stripped file and move on by using break
break
end
else
@@log.warn("unable to determine silence for jam_track #{original_filename}, #{output}")
stripped_time = total_time # default to the case where we just start the preview at the beginning
end
end
preview_start_time = total_time - stripped_time
preview_start_time
end
def synchronize_preview_dev(jam_track)
jam_track.jam_track_tracks.each do |track|
@ -231,6 +408,7 @@ module JamRuby
original_artist = parsed_metalocation[1]
name = parsed_metalocation[2]
JamTrackImporter.summaries[:unique_artists] << original_artist
success = dry_run_metadata(metadata, original_artist, name)
@ -269,6 +447,11 @@ module JamRuby
@storage_format == 'Tency'
end
def is_tim_tracks_storage?
assert_storage_set
@storage_format == 'TimTracks'
end
def assert_storage_set
raise "no storage_format set" if @storage_format.nil?
end
@ -276,7 +459,7 @@ module JamRuby
def parse_metalocation(metalocation)
# metalocation = mapped/4 Non Blondes - What's Up - 6475/meta.yml
if is_tency_storage?
if is_tency_storage? || is_tim_tracks_storage?
suffix = '/meta.yml'
@ -305,15 +488,21 @@ module JamRuby
return nil
end
last_dash = metalocation.rindex('-')
if last_dash
song = metalocation[(first_dash+3)...last_dash].strip
if is_tim_tracks_storage?
song = metalocation[(first_dash+3)..-1].strip
bits << song
else
finish("invalid_metalocation", "metalocation not valid #{metalocation}")
return nil
elsif is_tency_storage?
last_dash = metalocation.rindex('-')
if last_dash
song = metalocation[(first_dash+3)...last_dash].strip
bits << song
else
finish("invalid_metalocation", "metalocation not valid #{metalocation}")
return nil
end
end
bits << 'meta.yml'
bits
else
@ -338,24 +527,14 @@ module JamRuby
end
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
@ -494,7 +673,6 @@ module JamRuby
jam_track.year = metadata[:year]
jam_track.genres = determine_genres(metadata)
jam_track.language = determine_language(metadata)
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 = nil
jam_track.reproduction_royalty = true
@ -507,16 +685,25 @@ module JamRuby
jam_track.alternative_license_status = false
jam_track.hfa_license_desired = true
jam_track.server_fixation_date = Time.now
jam_track.slug = metadata['slug']
unless jam_track.slug
jam_track.generate_slug
end
if is_tency_storage?
jam_track.vendor_id = metadata[:id]
jam_track.licensor = JamTrackLicensor.find_by_name('Tency Music')
jam_track.licensor = JamTrackLicensor.find_by_name!('Tency Music')
#add_licensor_metadata('Tency Music', metalocation)
elsif is_tim_tracks_storage?
jam_track.vendor_id = metadata[:id]
jam_track.licensor = JamTrackLicensor.find_by_name!('Tim Waurick')
end
jam_track.slug = metadata['slug']
if jam_track.slug.nil?
jam_track.generate_slug
end
jam_track.plan_code = metadata["plan_code"]
if jam_track.plan_code.nil?
jam_track.gen_plan_code
end
else
if !options[:resync_audio]
#@@log.debug("#{self.name} skipped because it already exists in database")
@ -664,8 +851,11 @@ module JamRuby
instrument = 'other'
part = 'Bouzouki'
elsif potential_instrument == 'claps' || potential_instrument == 'hand claps'
instrument = 'computer'
instrument = 'other'
part = 'Claps'
elsif potential_instrument == 'snaps' || potential_instrument == 'snap'
instrument = 'other'
part = 'Snaps'
else
found_instrument = Instrument.find_by_id(potential_instrument)
if found_instrument
@ -774,7 +964,6 @@ module JamRuby
end
end
end
end
@ -900,6 +1089,10 @@ module JamRuby
if track.track_type == 'Master'
instrument_weight = 1000
end
if track.track_type == 'Click'
instrument_weight = 10000
end
end
@ -976,7 +1169,22 @@ module JamRuby
end
sorted_tracks[sorted_tracks.length - 1].position = 1000
# get click/master tracks position re-set correctly
last_track = sorted_tracks[sorted_tracks.length - 1]
second_to_last = sorted_tracks[sorted_tracks.length - 2]
if last_track.track_type == 'Master'
last_track.position = 1000
elsif last_track.track_type == 'Click'
last_track.position = 10000
end
if second_to_last.track_type == 'Master'
second_to_last.position = 1000
elsif second_to_last.track_type == 'Click'
second_to_last.position = 10000
end
sorted_tracks
end
@ -1082,7 +1290,6 @@ module JamRuby
@@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 #{unknowns}"
@ -1093,6 +1300,13 @@ module JamRuby
elsif parsed_wav[:type] == :clickwav
file.file_type = 'ClickWav'
addt_files << file
# and also add a JamTrackTrack for this click track
track.track_type = 'Click'
track.part = 'Clicktrack'
track.instrument_id = 'computer'
track.position = 10000
tracks << track
elsif parsed_wav[:type] == :precount
file.file_type = 'Precount'
file.precount_num = parsed_wav[:precount_num]
@ -1142,97 +1356,8 @@ module JamRuby
begin
Dir.mktmpdir do |tmp_dir|
generate_mp3_aac_stem(jam_track, tmp_dir, skip_audio_upload)
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::song_storage_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)
jam_track.save!
# convert entire master ogg file to mp3, and push both to public destination
if track.track_type == 'Master'
preview_succeeded = synchronize_master_preview(track, tmp_dir, ogg_44100, ogg_44100_digest)
if !preview_succeeded
return false
end
elsif track.track_type == 'Track'
synchronize_track_preview(track, tmp_dir, ogg_44100)
end
end
track.save!
synchronize_audio_track(jam_track, tmp_dir, skip_audio_upload, track)
end
end
rescue Exception => e
@ -1243,6 +1368,139 @@ module JamRuby
return true
end
def synchronize_audio_track(jam_track, tmp_dir, skip_audio_upload, track)
basename = File.basename(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"
# make a 44100 version, and a 48000 version
mp3_48000_filename = File.basename(basename, ".wav") + "-48000.mp3"
aac_48000_filename = File.basename(basename, ".wav") + "-48000.aac"
ogg_44100_s3_path = track.filename(ogg_44100_filename)
ogg_48000_s3_path = track.filename(ogg_48000_filename)
mp3_48000_s3_path = track.filename(mp3_48000_filename)
aac_48000_s3_path = track.filename(aac_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
track["url_mp3_48"] = mp3_48000_filename
track["md5_mp3_48"] = 'md5'
track["length_mp3_48"] = 1
track["url_aac_48"] = aac_48000_filename
track["md5_aac_48"] = 'md5'
track["length_aac_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::song_storage_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
# now create mp3 and aac files
mp3_48000 = File.join(tmp_dir, File.basename(basename, ".wav") + "-48000.mp3")
aac_48000 = File.join(tmp_dir, File.basename(basename, ".wav") + "-48000.aac")
`ffmpeg -i "#{wav_file}" -ar 48000 -ab 192k "#{mp3_48000}"`
`ffmpeg -i "#{wav_file}" -c:a libfdk_aac -b:a 192k "#{aac_48000}"`
# upload the new ogg files to s3
@@log.debug("uploading mp3 48000 to #{mp3_48000_s3_path}")
jamkazam_s3_manager.upload(mp3_48000_s3_path, mp3_48000)
@@log.debug("uploading aac 48000 to #{aac_48000_s3_path}")
jamkazam_s3_manager.upload(aac_48000_s3_path, aac_48000)
mp3_48000_digest = ::Digest::MD5.file(mp3_48000)
# and finally update the JamTrackTrack with the new info
track["url_mp3_48"] = mp3_48000_s3_path
track["md5_mp3_48"] = mp3_48000_digest.hexdigest
track["length_mp3_48"] = File.new(mp3_48000).size
track["url_aac_48"] = aac_48000_s3_path
track["md5_aac_48"] = ::Digest::MD5.file(aac_48000).hexdigest
track["length_aac_48"] = File.new(aac_48000).size
synchronize_duration(jam_track, ogg_44100)
jam_track.save!
# convert entire master ogg file to mp3, and push both to public destination
if track.track_type == 'Master'
preview_succeeded = synchronize_master_preview(track, tmp_dir, ogg_44100, ogg_44100_digest)
if !preview_succeeded
return false
end
elsif track.track_type == 'Track' || track.track_type == 'Click'
synchronize_track_preview(track, tmp_dir, ogg_44100)
end
end
track.save!
end
def generate_mp3_aac_stem(jam_track, tmp_dir, skip_audio_upload)
jam_track.jam_track_tracks.each do |track|
@ -1252,6 +1510,7 @@ module JamRuby
next
end
puts "track.original_audio_s3_path #{track.original_audio_s3_path}"
basename = File.basename(track.original_audio_s3_path)
s3_dirname = File.dirname(track.original_audio_s3_path)
@ -1262,6 +1521,7 @@ module JamRuby
mp3_48000_s3_path = track.filename(mp3_48000_filename)
aac_48000_s3_path = track.filename(aac_48000_filename)
puts "mp3_48000_s3_path #{mp3_48000_s3_path}"
track.skip_uploader = true
if skip_audio_upload
@ -1341,58 +1601,15 @@ module JamRuby
def synchronize_track_preview(track, tmp_dir, ogg_44100)
out_wav = File.join(tmp_dir, 'stripped.wav')
burp_gaps = ['0.3', '0.2', '0.1', '0.05']
total_time_command = "soxi -D \"#{ogg_44100}\""
total_time = `#{total_time_command}`.to_f
result_code = -20
stripped_time = total_time # default to the case where we just start the preview at the beginning
burp_gaps.each do |gap|
command_strip_lead_silence = "sox \"#{ogg_44100}\" \"#{out_wav}\" silence 1 #{gap} 1%"
@@log.debug("stripping silence: " + command_strip_lead_silence)
output = `#{command_strip_lead_silence}`
result_code = $?.to_i
if result_code == 0
stripped_time_command = "soxi -D \"#{out_wav}\""
stripped_time_test = `#{stripped_time_command}`.to_f
if stripped_time_test < 1 # meaning a very short duration
@@log.warn("could not determine the start of non-silencea. assuming beginning")
stripped_time = total_time # default to the case where we just start the preview at the beginning
else
stripped_time = stripped_time_test # accept the measured time of the stripped file and move on by using break
break
end
else
@@log.warn("unable to determine silence for jam_track #{track.original_filename}, #{output}")
stripped_time = total_time # default to the case where we just start the preview at the beginning
end
end
preview_start_time = total_time - stripped_time
preview_start_time = determine_start_time(ogg_44100, tmp_dir, track.original_filename)
# this is in seconds; convert to integer milliseconds
preview_start_time = (preview_start_time * 1000).to_i
preview_start_time = nil if preview_start_time < 0
preview_start_time = 0 if preview_start_time < 0
track.preview_start_time = preview_start_time
if track.preview_start_time
@@log.debug("determined track start time to be #{track.preview_start_time}")
else
@@log.debug("determined track start time to be #{track.preview_start_time}")
end
track.process_preview(ogg_44100, tmp_dir) if track.preview_start_time
if track.preview_generate_error
@ -1606,6 +1823,8 @@ module JamRuby
def song_storage_manager
if is_tency_storage?
tency_s3_manager
elsif is_tim_tracks_storage?
tim_tracks_s3_manager
else
s3_manager
end
@ -1619,6 +1838,10 @@ module JamRuby
@tency_s3_manager ||= S3Manager.new('jamkazam-tency', APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key)
end
def tim_tracks_s3_manager
@tim_tracks_s3_manager ||= S3Manager.new('jamkazam-timtracks', APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key)
end
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
@ -1657,10 +1880,32 @@ module JamRuby
@storage_format == 'Tency'
end
def is_tim_tracks_storage?
assert_storage_set
@storage_format == 'TimTracks'
end
def assert_storage_set
raise "no storage_format set" if @storage_format.nil?
end
def iterate_tim_tracks_song_storage(&blk)
count = 0
song_storage_manager.list_directories('mapped').each do |song|
@@log.debug("searching through song directory '#{song}'")
metalocation = "#{song}meta.yml"
metadata = load_metalocation(metalocation)
blk.call(metadata, metalocation)
count += 1
#break if count > 100
end
end
def iterate_tency_song_storage(&blk)
count = 0
song_storage_manager.list_directories('mapped').each do |song|
@ -1700,6 +1945,10 @@ module JamRuby
iterate_tency_song_storage do |metadata, metalocation|
blk.call(metadata, metalocation)
end
elsif is_tim_tracks_storage?
iterate_tim_tracks_song_storage do |metadata, metalocation|
blk.call(metadata, metalocation)
end
else
iterate_default_song_storage do |metadata, metalocation|
blk.call(metadata, metalocation)
@ -1915,6 +2164,73 @@ module JamRuby
end
end
def import_click_track(jam_track)
importer = JamTrackImporter.new
importer.name = jam_track.name
importer.import_click_track(jam_track)
importer
end
def generate_jmep(jam_track)
importer = JamTrackImporter.new
importer.name = jam_track.name
importer.generate_jmep(jam_track)
importer
end
def import_click_tracks
importers = []
JamTrack.all.each do |jam_track|
#jam_track = JamTrack.find('126')
importers << import_click_track(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 generate jmep.")
@@log.error("#{importer.name} reason=#{importer.reason}")
@@log.error("#{importer.name} detail=#{importer.detail}")
end
else
@@log.error("NULL IMPORTER")
end
end
end
def generate_jmeps
importers = []
JamTrack.all.each do |jam_track|
importers << generate_jmep(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 generate jmep.")
@@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_previews
importers = []
@ -2328,7 +2644,12 @@ module JamRuby
else
begin
data = s3_manager.read_all(metalocation)
return YAML.load(data)
meta = YAML.load(data)
if is_tim_tracks_storage?
meta[:genres] = ['acapella']
end
meta
rescue AWS::S3::Errors::NoSuchKey
return nil
end
@ -2395,3 +2716,4 @@ module JamRuby
end
end
end

View File

@ -41,7 +41,7 @@ module JamRuby
jam_file_opts=""
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
next if jam_track_track.track_type == "Master" # master mixes do not go into the JKZ
# 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.url_by_sample_rate(sample_rate))
@ -52,7 +52,8 @@ module JamRuby
step = bump_step(jam_track_right, step)
copy_url_to_file(track_url, track_filename)
jam_file_opts << " -i #{Shellwords.escape("#{track_filename}+#{jam_track_track.part}")}"
part = jam_track_track.track_type == 'Click' ? 'ClickTrack' : jam_track_track.part
jam_file_opts << " -i #{Shellwords.escape("#{track_filename}+#{part}")}"
end
#puts "LS + " + `ls -la '#{tmp_dir}'`

View File

@ -119,18 +119,16 @@ class JamRuby::AffiliatePartner < ActiveRecord::Base
end
def should_attribute_sale?(shopping_cart)
if shopping_cart.is_jam_track?
if created_within_affiliate_window(shopping_cart.user, Time.now)
product_info = shopping_cart.product_info
# subtract the total quantity from the freebie quantity, to see how much we should attribute to them
real_quantity = product_info[:quantity].to_i - product_info[:marked_for_redeem].to_i
{fee_in_cents: (1.99 * 100 * real_quantity * rate.to_f).round}
else
false
end
if created_within_affiliate_window(shopping_cart.user, Time.now)
product_info = shopping_cart.product_info
# subtract the total quantity from the freebie quantity, to see how much we should attribute to them
real_quantity = product_info[:quantity].to_i - product_info[:marked_for_redeem].to_i
{fee_in_cents: (product_info[:price] * 100 * real_quantity * rate.to_f).round}
else
raise 'shopping cart type not implemented yet'
false
end
end
def cumulative_earnings_in_dollars

View File

@ -15,20 +15,47 @@ module JamRuby
ShoppingCart.where(anonymous_user_id: @id).order('created_at DESC')
end
def destroy_all_shopping_carts
ShoppingCart.destroy_all(anonymous_user_id: @id)
end
def destroy_jam_track_shopping_carts
ShoppingCart.destroy_all(anonymous_user_id: @id, cart_type: JamTrack::PRODUCT_TYPE)
end
def admin
false
end
def has_redeemable_jamtrack
raise "not a cookied anonymous user" if @cookies.nil?
APP_CONFIG.one_free_jamtrack_per_user && !@cookies[:redeemed_jamtrack]
end
def gifted_jamtracks
0
end
def free_jamtracks
if has_redeemable_jamtrack
1
else
0
end
end
def show_free_jamtrack?
ShoppingCart.user_has_redeemable_jam_track?(self)
end
def signup_hint
SignupHint.where(anonymous_user_id: @id).where('expires_at > ?', Time.now).first
end
def reload
end
end
end

View File

@ -97,6 +97,7 @@ module JamRuby
body = "IP Address: #{remote_ip}\n"
body << "Download Count: #{violation['count']}\n"
body << "Add to blacklist: #{IpBlacklist.admin_url}"
body << "Check Activity: #{IpBlacklist.admin_activity_url(remote_ip)}"
AdminMailer.alerts({
subject:"Single IP Access Violation. IP:#{remote_ip}",

View File

@ -0,0 +1,36 @@
# represents the gift card you hold in your hand
module JamRuby
class GiftCard < ActiveRecord::Base
@@log = Logging.logger[GiftCard]
JAM_TRACKS_5 = 'jam_tracks_5'
JAM_TRACKS_10 = 'jam_tracks_10'
CARD_TYPES =
[
JAM_TRACKS_5,
JAM_TRACKS_10
]
belongs_to :user, class_name: "JamRuby::User"
validates :card_type, presence: true, inclusion: {in: CARD_TYPES}
validates :code, presence: true, uniqueness: true
after_save :check_gifted
def check_gifted
if user && user_id_changed?
if card_type == JAM_TRACKS_5
user.gifted_jamtracks += 5
elsif card_type == JAM_TRACKS_10
user.gifted_jamtracks += 10
else
raise "unknown card type #{card_type}"
end
user.save!
end
end
end
end

View File

@ -0,0 +1,17 @@
# reperesents the gift card you buy from the site (but physical gift card is modeled by GiftCard)
module JamRuby
class GiftCardPurchase < ActiveRecord::Base
@@log = Logging.logger[GiftCardPurchase]
attr_accessible :user, :gift_card_type
def name
gift_card_type.sale_display
end
# who purchased the card?
belongs_to :user, class_name: "JamRuby::User"
belongs_to :gift_card_type, class_name: "JamRuby::GiftCardType"
end
end

View File

@ -0,0 +1,70 @@
# reperesents the gift card you buy from the site (but physical gift card is modeled by GiftCard)
module JamRuby
class GiftCardType < ActiveRecord::Base
@@log = Logging.logger[GiftCardType]
PRODUCT_TYPE = 'GiftCardType'
JAM_TRACKS_5 = 'jam_tracks_5'
JAM_TRACKS_10 = 'jam_tracks_10'
CARD_TYPES =
[
JAM_TRACKS_5,
JAM_TRACKS_10
]
validates :card_type, presence: true, inclusion: {in: CARD_TYPES}
def self.jam_track_5
GiftCardType.find(JAM_TRACKS_5)
end
def self.jam_track_10
GiftCardType.find(JAM_TRACKS_10)
end
def name
sale_display
end
def price
if card_type == JAM_TRACKS_5
10.00
elsif card_type == JAM_TRACKS_10
20.00
else
raise "unknown card type #{card_type}"
end
end
def sale_display
if card_type == JAM_TRACKS_5
'JamTracks Gift Card (5)'
elsif card_type == JAM_TRACKS_10
'JamTracks Gift Card (10)'
else
raise "unknown card type #{card_type}"
end
end
def plan_code
if card_type == JAM_TRACKS_5
"jamtrack-giftcard-5"
elsif card_type == JAM_TRACKS_10
"jamtrack-giftcard-10"
else
raise "unknown card type #{card_type}"
end
end
def sales_region
'Worldwide'
end
def to_s
sale_display
end
end
end

View File

@ -15,6 +15,10 @@ module JamRuby
APP_CONFIG.admin_root_url + "/admin/ip_blacklists/"
end
def self.admin_activity_url(remote_ip)
APP_CONFIG.admin_root_url + "/admin/download_trackers?q[remote_ip_equals]=#{URI.escape(remote_ip)}&commit=Filter&order=id_desc"
end
def admin_url
APP_CONFIG.admin_root_url + "/admin/ip_blacklists/" + id
end

View File

@ -19,7 +19,7 @@ module JamRuby
:reproduction_royalty, :public_performance_royalty, :reproduction_royalty_amount,
:licensor_royalty_amount, :pro_royalty_amount, :plan_code, :initial_play_silence, :jam_track_tracks_attributes,
:jam_track_tap_ins_attributes, :genre_ids, :version, :jmep_json, :jmep_text, :pro_ascap, :pro_bmi, :pro_sesac, :duration,
:server_fixation_date, :hfa_license_status, :hfa_license_desired, :alternative_license_status, :hfa_license_number, :hfa_song_code, :album_title, as: :admin
:server_fixation_date, :hfa_license_status, :hfa_license_desired, :alternative_license_status, :hfa_license_number, :hfa_song_code, :album_title, :year, as: :admin
validates :name, presence: true, length: {maximum: 200}
validates :plan_code, presence: true, uniqueness: true, length: {maximum: 50 }
@ -86,6 +86,10 @@ module JamRuby
after_save :sync_reproduction_royalty
after_save :sync_onboarding_exceptions
def increment_version!
self.version = version.to_i + 1
save!
end
def sync_reproduction_royalty
@ -155,6 +159,9 @@ module JamRuby
true
end
def sale_display
"JamTrack: " + name
end
def duplicate_positions?
counter = {}
jam_track_tracks.each do |track|
@ -338,7 +345,7 @@ module JamRuby
query = query.where('genre_id = ? ', options[:genre])
end
query = query.where("jam_track_tracks.instrument_id = '#{options[:instrument]}' and jam_track_tracks.track_type != 'Master'") unless options[:instrument].blank?
query = query.where("jam_track_tracks.instrument_id = '#{options[:instrument]}' and jam_track_tracks.track_type = 'Track'") unless options[:instrument].blank?
query = query.where("jam_tracks.sales_region = '#{options[:availability]}'") unless options[:availability].blank?
# FIXME: n+1 queries for rights and genres
@ -347,9 +354,8 @@ module JamRuby
# :jam_track_rights,
# :genres])
# { genres_jam_tracks: :genre },
query = query.includes([{ jam_track_tracks: :instrument },
{ genres_jam_tracks: :genre },
:jam_track_tap_ins])
# query = query.includes([{ jam_track_tracks: :instrument },
# { genres_jam_tracks: :genre }])
count = query.total_entries
@ -432,13 +438,20 @@ module JamRuby
end
end
def click_track_file
JamTrackFile.where(jam_track_id: self.id).where(file_type: 'ClickWav').first
end
def click_track
JamTrackTrack.where(jam_track_id: self.id).where(track_type: 'Click').first
end
def master_track
JamTrackTrack.where(jam_track_id: self.id).where(track_type: 'Master').first
end
def stem_tracks
JamTrackTrack.where(jam_track_id: self.id).where(track_type: 'Track')
JamTrackTrack.where(jam_track_id: self.id).where("track_type = 'Track' or track_type = 'Click'")
end
def can_download?(user)
@ -465,6 +478,27 @@ module JamRuby
def generate_slug
self.slug = sluggarize(original_artist) + '-' + sluggarize(name)
if licensor
raise "no slug on licensor #{licensor.id}" if licensor.slug.nil?
self.slug << "-" + licensor.slug
end
end
def gen_plan_code
# 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
self.plan_code = "jamtrack-#{artist_code[0...20]}-#{name_code}"
if licensor
raise "no slug on licensor #{licensor.id}" if licensor.slug.nil?
self.plan_code << "-" + licensor.slug
end
self.plan_code = self.plan_code[0...50] # make sure it's a max of 50 long
end
def to_s
@ -492,5 +526,30 @@ module JamRuby
Digest::MD5.hexdigest(dates)
end
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.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
end

View File

@ -27,9 +27,18 @@ module JamRuby
"jam_track_files"
end
def licensor_suffix
suffix = ''
if jam_track.licensor
raise "no licensor name" if jam_track.licensor.name.nil?
suffix = " - #{jam_track.licensor.name}"
end
suffix
end
# create name of the file
def filename(original_name)
"#{store_dir}/#{jam_track.original_artist}/#{jam_track.name}/#{original_name}"
"#{store_dir}/#{jam_track.original_artist}/#{jam_track.name}#{licensor_suffix}/#{original_name}"
end
def manually_uploaded_filename

View File

@ -4,7 +4,7 @@ module JamRuby
table_name = 'jam_track_licensors'
attr_accessible :name, :description, :attention, :address_line_1, :address_line_2,
:city, :state, :zip_code, :contact, :email, :phone, as: :admin
:city, :state, :zip_code, :contact, :email, :phone, :slug, as: :admin
validates :name, presence: true, uniqueness: true, length: {maximum: 200}
validates :description, length: {maximum: 1000}

View File

@ -83,6 +83,11 @@ module JamRuby
end
end
if parsed["count-in"]
all_quiet = false
tweaked = true
end
if all_quiet
errors.add(:settings, 'are all muted')
end

View File

@ -14,6 +14,7 @@ module JamRuby
belongs_to :last_mixdown, class_name: 'JamRuby::JamTrackMixdown', foreign_key: 'last_mixdown_id', inverse_of: :jam_track_right
belongs_to :last_stem, class_name: 'JamRuby::JamTrackTrack', foreign_key: 'last_stem_id', inverse_of: :jam_track_right
validates :version, presence: true
validates :user, presence: true
validates :jam_track, presence: true
validates :is_test_purchase, inclusion: {in: [true, false]}
@ -133,8 +134,33 @@ module JamRuby
end
end
def cleanup_old_package!
if self.jam_track.version != self.version
delete_s3_files
self[:url_48] = nil
self[:url_44] = nil
self.signing_queued_at = nil
self.signing_started_at_48 = nil
self.signing_started_at_44 = nil
self.last_signed_at = nil
self.current_packaging_step = nil
self.packaging_steps = nil
self.should_retry = false
self.signing_44 = false
self.signing_48 = false
self.signed_44 = false
self.signed_48 = false
self.queued = false
self.version = self.jam_track.version
self.save!
end
end
# 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(sample_rate=48)
# delete any package that's out dated
cleanup_old_package!
state = signing_state(sample_rate)
if state == 'SIGNED' || state == 'SIGNING' || state == 'QUEUED'
false
@ -148,9 +174,9 @@ module JamRuby
# @return true if signed && file exists for the sample_rate specifed:
def ready?(sample_rate=48)
if sample_rate==48
self.signed_48 && self.url_48.present? && self.url_48.file.exists?
self.signed_48 && self.url_48.present? && self.url_48.file.exists? && self.version == self.jam_track.version
else
self.signed_44 && self.url_44.present? && self.url_44.file.exists?
self.signed_44 && self.url_44.present? && self.url_44.file.exists? && self.version == self.jam_track.version
end
end

View File

@ -75,7 +75,7 @@ module JamRuby
sqlstr = "'#{instruments.join("','")}'"
rel = rel.joins(:jam_track_tracks)
rel = rel.where("jam_track_tracks.instrument_id IN (#{sqlstr})")
rel = rel.where("jam_track_tracks.track_type != 'Master'")
rel = rel.where("jam_track_tracks.track_type = 'Track'")
end
end

View File

@ -6,7 +6,7 @@ module JamRuby
include JamRuby::S3PublicManagerMixin
# there should only be one Master per JamTrack, but there can be N Track per JamTrack
TRACK_TYPE = %w{Track Master}
TRACK_TYPE = %w{Track Master Click}
@@log = Logging.logger[JamTrackTrack]
@ -41,9 +41,19 @@ module JamRuby
"jam_track_tracks"
end
def licensor_suffix
suffix = ''
if jam_track.licensor
raise "no licensor name" if jam_track.licensor.name.nil?
suffix = " - #{jam_track.licensor.name}"
end
suffix
end
# create name of the file
def filename(original_name)
"#{store_dir}/#{jam_track.original_artist}/#{jam_track.name}/#{original_name}"
"#{store_dir}/#{jam_track.original_artist}/#{jam_track.name}#{licensor_suffix}/#{original_name}"
end
# create name of the preview file.
@ -54,7 +64,7 @@ module JamRuby
end
def preview_directory
"jam_track_previews/#{jam_track.original_artist}/#{jam_track.name}"
"jam_track_previews/#{jam_track.original_artist}/#{jam_track.name}#{licensor_suffix}"
end
def has_preview?
@ -192,10 +202,10 @@ module JamRuby
# input is the original ogg file for the track. tmp_dir is where this code can safely generate output stuff and have it cleaned up later
def process_preview(input, tmp_dir)
raise "Does not include AAC generation. Must be updated before used."
uuid = SecureRandom.uuid
output = File.join(tmp_dir, "#{uuid}.ogg")
output_mp3 = File.join(tmp_dir, "#{uuid}.mp3")
output_aac = File.join(tmp_dir, "#{uuid}.aac")
start = self.preview_start_time.to_f / 1000
stop = start + 20
@ -225,35 +235,55 @@ module JamRuby
@@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.base64digest)
convert_aac_cmd = "#{APP_CONFIG.ffmpeg_path} -i \"#{output}\" -c:a libfdk_aac -b:a 192k \"#{output_aac}\""
@@log.debug("converting to aac using: " + convert_aac_cmd)
self.skip_uploader = true
convert_output = `#{convert_aac_cmd}`
original_ogg_preview_url = self["preview_url"]
original_mp3_preview_url = self["preview_mp3_url"]
result_code = $?.to_i
# 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 result_code != 0
@@log.debug("fail #{result_code}")
@preview_generate_error = "unable to execute aac convert command #{convert_output}"
else
# 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"
ogg_digest = ::Digest::MD5.file(output)
mp3_digest = ::Digest::MD5.file(output_mp3)
aac_digest = ::Digest::MD5.file(output_aac)
self["preview_md5"] = ogg_md5 = ogg_digest.hexdigest
self["preview_mp3_md5"] = mp3_md5 = mp3_digest.hexdigest
self["preview_aac_md5"] = aac_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.base64digest)
@@log.debug("uploading aac preview to #{self.preview_filename('aac')}")
s3_public_manager.upload(self.preview_filename(aac_md5, 'aac'), output_aac, content_type: 'audio/aac', content_md5: aac_digest.base64digest)
self.skip_uploader = true
original_ogg_preview_url = self["preview_url"]
original_mp3_preview_url = self["preview_mp3_url"]
original_aac_preview_url = self["preview_aac_url"]
self["preview_url"] = self.preview_filename(ogg_md5, 'ogg')
self["preview_length"] = File.new(output).size
self["preview_mp3_url"] = self.preview_filename(mp3_md5, 'mp3')
self["preview_mp3_length"] = File.new(output_mp3).size
self["preview_aac_url"] = self.preview_filename(aac_md5, 'aac')
self["preview_aac_length"] = File.new(output_aac).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"]
s3_public_manager.delete(original_aac_preview_url) if original_aac_preview_url && original_aac_preview_url != track["preview_aac_url"]
rescue
puts "UNABLE TO CLEANUP OLD PREVIEW URL"
end
end
end

View File

@ -92,53 +92,15 @@ module JamRuby
transaction.save!
# now that we have the transaction saved, we also need to delete the jam_track_right if this is a refund, or voided
if transaction.transaction_type == 'refund' || transaction.transaction_type == 'void'
sale = Sale.find_by_recurly_invoice_id(transaction.invoice_id)
if sale && sale.is_jam_track_sale?
if sale.sale_line_items.length == 1
if sale.recurly_total_in_cents == transaction.amount_in_cents
line_item = sale.sale_line_items[0]
jam_track = line_item.product
jam_track_right = jam_track.right_for_user(transaction.user) if jam_track
if jam_track_right
line_item.affiliate_refunded = true
line_item.affiliate_refunded_at = Time.now
line_item.save!
if sale
AdminMailer.recurly_alerts(transaction.user, {
subject: "ACTION REQUIRED: #{transaction.user.email} has refund on invoice",
body: "You will have to manually revoke any JamTrackRights in our database for the appropriate JamTracks"
}).deliver
jam_track_right.destroy
# associate which JamTrack we assume this is related to in this one success case
transaction.jam_track = jam_track
transaction.save!
AdminMailer.recurly_alerts(transaction.user, {
subject: "NOTICE: #{transaction.user.email} has had JamTrack: #{jam_track.name} revoked",
body: "A #{transaction.transaction_type} event came from Recurly for sale with Recurly invoice ID #{sale.recurly_invoice_id}. We deleted their right to the track in our own database as a result."
}).deliver
else
AdminMailer.recurly_alerts(transaction.user, {
subject: "NOTICE: #{transaction.user.email} got a refund, but unable to find JamTrackRight to delete",
body: "This should just mean the user already has no rights to the JamTrackRight when the refund came in. Not a big deal, but sort of weird..."
}).deliver
end
else
AdminMailer.recurly_alerts(transaction.user, {
subject: "ACTION REQUIRED: #{transaction.user.email} got a refund it was not for total value of a JamTrack sale",
body: "We received a #{transaction.transaction_type} notice for an amount that was not the same as the original sale. So, no action was taken in the database. sale total: #{sale.recurly_total_in_cents}, refund amount: #{transaction.amount_in_cents}"
}).deliver
end
else
AdminMailer.recurly_alerts(transaction.user, {
subject: "ACTION REQUIRED: #{transaction.user.email} has refund on invoice with multiple JamTracks",
body: "You will have to manually revoke any JamTrackRights in our database for the appropriate JamTracks"
}).deliver
end
else
AdminMailer.recurly_alerts(transaction.user, {
subject: "ACTION REQUIRED: #{transaction.user.email} has refund with no correlator to sales",

View File

@ -69,27 +69,12 @@ module JamRuby
}
end
def self.preview_invoice(current_user, shopping_carts)
line_items = {jam_tracks: []}
shopping_carts_jam_tracks = []
shopping_carts_subscriptions = []
shopping_carts.each do |shopping_cart|
if shopping_cart.is_jam_track?
shopping_carts_jam_tracks << shopping_cart
else
# XXX: this may have to be revisited when we actually have something other than JamTracks for puchase
shopping_carts_subscriptions << shopping_cart
end
def self.ios_purchase(current_user, jam_track, receipt)
jam_track_right = JamRuby::JamTrackRight.find_or_create_by_user_id_and_jam_track_id(current_user.id, jam_track.id) do |jam_track_right|
jam_track_right.redeemed = false
jam_track_right.version = jam_track.version
end
jam_track_items = preview_invoice_jam_tracks(current_user, shopping_carts_jam_tracks)
line_items[:jam_tracks] = jam_track_items if jam_track_items
# TODO: process shopping_carts_subscriptions
line_items
end
# place_order will create one or more sales based on the contents of shopping_carts for the current user
@ -99,19 +84,14 @@ module JamRuby
def self.place_order(current_user, shopping_carts)
sales = []
shopping_carts_jam_tracks = []
shopping_carts_subscriptions = []
shopping_carts.each do |shopping_cart|
if shopping_cart.is_jam_track?
shopping_carts_jam_tracks << shopping_cart
else
# XXX: this may have to be revisited when we actually have something other than JamTracks for puchase
shopping_carts_subscriptions << shopping_cart
end
if Sale.is_mixed(shopping_carts)
# the controller checks this too; this is just an extra-level of sanity checking
return sales
end
jam_track_sale = order_jam_tracks(current_user, shopping_carts_jam_tracks)
jam_track_sale = order_jam_tracks(current_user, shopping_carts)
sales << jam_track_sale if jam_track_sale
# TODO: process shopping_carts_subscriptions
@ -119,22 +99,52 @@ module JamRuby
sales
end
def self.preview_invoice_jam_tracks(current_user, shopping_carts_jam_tracks)
### XXX TODO;
# we currently use a fake plan in Recurly to estimate taxes using the Pricing.Attach metod in Recurly.js
def self.is_only_freebie(shopping_carts)
free = true
shopping_carts.each do |cart|
free = cart.product_info[:free]
# if we were to implement this the right way (ensure adjustments are on the account as necessary), then it would be better (more correct)
# just a pain to implement
if !free
break
end
end
free
end
def self.is_only_freebie(shopping_carts_jam_tracks)
shopping_carts_jam_tracks.length == 1 && shopping_carts_jam_tracks[0].product_info[:free]
# we don't allow mixed shopping carts :/
def self.is_mixed(shopping_carts)
free = false
non_free = false
shopping_carts.each do |cart|
if cart.product_info[:free]
free = true
else
non_free = true
end
end
free && non_free
end
# this method will either return a valid sale, or throw a RecurlyClientError or ActiveRecord validation error (save! failed)
# it may return an nil sale if the JamTrack(s) specified by the shopping carts are already owned
def self.order_jam_tracks(current_user, shopping_carts_jam_tracks)
def self.order_jam_tracks(current_user, shopping_carts)
shopping_carts_jam_tracks = []
shopping_carts_subscriptions = []
shopping_carts_gift_cards = []
shopping_carts.each do |shopping_cart|
if shopping_cart.is_jam_track?
shopping_carts_jam_tracks << shopping_cart
elsif shopping_cart.is_gift_card?
shopping_carts_gift_cards << shopping_cart
else
# XXX: this may have to be revisited when we actually have something other than JamTracks for puchase
raise "unknown shopping cart type #{shopping_cart.cart_type}"
shopping_carts_subscriptions << shopping_cart
end
end
client = RecurlyClient.new
@ -143,8 +153,8 @@ module JamRuby
sale = create_jam_track_sale(current_user)
if sale.valid?
if is_only_freebie(shopping_carts_jam_tracks)
sale.process_jam_tracks(current_user, shopping_carts_jam_tracks, nil)
if is_only_freebie(shopping_carts)
sale.process_shopping_carts(current_user, shopping_carts, nil)
sale.recurly_subtotal_in_cents = 0
sale.recurly_tax_in_cents = 0
@ -159,11 +169,13 @@ module JamRuby
return sale
end
sale_line_item = sale.sale_line_items[0]
sale_line_item.recurly_tax_in_cents = 0
sale_line_item.recurly_total_in_cents = 0
sale_line_item.recurly_currency = 'USD'
sale_line_item.recurly_discount_in_cents = 0
sale.sale_line_items.each do |sale_line_item|
sale_line_item = sale.sale_line_items[0]
sale_line_item.recurly_tax_in_cents = 0
sale_line_item.recurly_total_in_cents = 0
sale_line_item.recurly_currency = 'USD'
sale_line_item.recurly_discount_in_cents = 0
end
sale.save
else
@ -173,7 +185,7 @@ module JamRuby
purge_pending_adjustments(account)
created_adjustments = sale.process_jam_tracks(current_user, shopping_carts_jam_tracks, account)
created_adjustments = sale.process_shopping_carts(current_user, shopping_carts, account)
# now invoice the sale ... almost done
@ -229,13 +241,13 @@ module JamRuby
sale
end
def process_jam_tracks(current_user, shopping_carts_jam_tracks, account)
def process_shopping_carts(current_user, shopping_carts, account)
created_adjustments = []
begin
shopping_carts_jam_tracks.each do |shopping_cart|
process_jam_track(current_user, shopping_cart, account, created_adjustments)
shopping_carts.each do |shopping_cart|
process_shopping_cart(current_user, shopping_cart, account, created_adjustments)
end
rescue Recurly::Error, NoMethodError => x
# rollback any adjustments created if error
@ -251,7 +263,7 @@ module JamRuby
end
def process_jam_track(current_user, shopping_cart, account, created_adjustments)
def process_shopping_cart(current_user, shopping_cart, account, created_adjustments)
recurly_adjustment_uuid = nil
recurly_adjustment_credit_uuid = nil
@ -259,15 +271,20 @@ module JamRuby
shopping_cart.reload
# get the JamTrack in this shopping cart
jam_track = shopping_cart.cart_product
cart_product = shopping_cart.cart_product
if jam_track.right_for_user(current_user)
# if the user already owns the JamTrack, we should just skip this cart item, and destroy it
# if this occurs, we have to reload every shopping_cart as we iterate. so, we do at the top of the loop
ShoppingCart.remove_jam_track_from_cart(current_user, shopping_cart)
return
if shopping_cart.is_jam_track?
jam_track = cart_product
if jam_track.right_for_user(current_user)
# if the user already owns the JamTrack, we should just skip this cart item, and destroy it
# if this occurs, we have to reload every shopping_cart as we iterate. so, we do at the top of the loop
ShoppingCart.remove_jam_track_from_cart(current_user, shopping_cart)
return
end
end
if account
# ask the shopping cart to create the correct Recurly adjustment attributes for a JamTrack
adjustments = shopping_cart.create_adjustment_attributes(current_user)
@ -300,38 +317,70 @@ module JamRuby
# if the sale line item is invalid, blow up the transaction
unless sale_line_item.valid?
@log.error("sale item invalid! #{sale_line_item.errors.inspect}")
@@log.error("sale item invalid! #{sale_line_item.errors.inspect}")
puts("sale item invalid! #{sale_line_item.errors.inspect}")
Stats.write('web.recurly.purchase.sale_invalid', {message: sale_line_item.errors.to_s, value: 1})
raise RecurlyClientError.new(sale_line_item.errors)
end
# create a JamTrackRight (this needs to be in a transaction too to make sure we don't make these by accident)
jam_track_right = JamRuby::JamTrackRight.find_or_create_by_user_id_and_jam_track_id(current_user.id, jam_track.id) do |jam_track_right|
jam_track_right.redeemed = shopping_cart.free?
end
if shopping_cart.is_jam_track?
jam_track = cart_product
# also if the purchase was a free one, then update the user record to no longer allow redeemed jamtracks
if shopping_cart.free?
User.where(id: current_user.id).update_all(has_redeemable_jamtrack: false)
current_user.has_redeemable_jamtrack = false # make sure model reflects the truth
end
# this can't go in the block above, as it's here to fix bad subscription UUIDs in an update path
if jam_track_right.recurly_adjustment_uuid != recurly_adjustment_uuid
jam_track_right.recurly_adjustment_uuid = recurly_adjustment_uuid
jam_track_right.recurly_adjustment_credit_uuid = recurly_adjustment_credit_uuid
unless jam_track_right.save
raise RecurlyClientError.new(jam_track_right.errors)
# create a JamTrackRight (this needs to be in a transaction too to make sure we don't make these by accident)
jam_track_right = JamRuby::JamTrackRight.find_or_create_by_user_id_and_jam_track_id(current_user.id, jam_track.id) do |jam_track_right|
jam_track_right.redeemed = shopping_cart.free?
jam_track_right.version = jam_track.version
end
# also if the purchase was a free one, then:
# first, mark the free has_redeemable_jamtrack field if that's still true
# and if still they have more free things, then redeem the giftable_jamtracks
if shopping_cart.free?
if user.has_redeemable_jamtrack
User.where(id: current_user.id).update_all(has_redeemable_jamtrack: false)
current_user.has_redeemable_jamtrack = false
else
User.where(id: current_user.id).update_all(gifted_jamtracks: current_user.gifted_jamtracks - 1)
current_user.gifted_jamtracks = current_user.gifted_jamtracks - 1
end
end
# this can't go in the block above, as it's here to fix bad subscription UUIDs in an update path
if jam_track_right.recurly_adjustment_uuid != recurly_adjustment_uuid
jam_track_right.recurly_adjustment_uuid = recurly_adjustment_uuid
jam_track_right.recurly_adjustment_credit_uuid = recurly_adjustment_credit_uuid
unless jam_track_right.save
raise RecurlyClientError.new(jam_track_right.errors)
end
end
# blow up the transaction if the JamTrackRight did not get created
raise RecurlyClientError.new(jam_track_right.errors) if jam_track_right.errors.any?
elsif shopping_cart.is_gift_card?
gift_card_type = cart_product
raise "gift card is null" if gift_card_type.nil?
raise if current_user.nil?
shopping_cart.quantity.times do |item|
gift_card_purchase = GiftCardPurchase.new(
{
user: current_user,
gift_card_type: gift_card_type
})
unless gift_card_purchase.save
raise RecurlyClientError.new(gift_card_purchase.errors)
end
end
else
raise 'unknown shopping cart type: ' + shopping_cart.cart_type
end
# delete the shopping cart; it's been dealt with
shopping_cart.destroy if shopping_cart
# blow up the transaction if the JamTrackRight did not get created
raise RecurlyClientError.new(jam_track_right.errors) if jam_track_right.errors.any?
end
@ -361,7 +410,7 @@ module JamRuby
def self.create_jam_track_sale(user)
sale = Sale.new
sale.user = user
sale.sale_type = JAMTRACK_SALE
sale.sale_type = JAMTRACK_SALE # gift cards and jam tracks are sold with this type of sale
sale.order_total = 0
sale.save
sale

View File

@ -4,14 +4,16 @@ module JamRuby
JAMBLASTER = 'JamBlaster'
JAMCLOUD = 'JamCloud'
JAMTRACK = 'JamTrack'
GIFTCARD = 'GiftCardType'
belongs_to :sale, class_name: 'JamRuby::Sale'
belongs_to :jam_track, class_name: 'JamRuby::JamTrack'
belongs_to :jam_track_right, class_name: 'JamRuby::JamTrackRight'
belongs_to :gift_card, class_name: 'JamRuby::GiftCard'
belongs_to :affiliate_referral, class_name: 'JamRuby::AffiliatePartner', foreign_key: :affiliate_referral_id
has_many :recurly_transactions, class_name: 'JamRuby::RecurlyTransactionWebHook', inverse_of: :sale_line_item, foreign_key: 'subscription_id', primary_key: 'recurly_subscription_uuid'
validates :product_type, inclusion: {in: [JAMBLASTER, JAMCLOUD, JAMTRACK]}
validates :product_type, inclusion: {in: [JAMBLASTER, JAMCLOUD, JAMTRACK, GIFTCARD]}
validates :unit_price, numericality: {only_integer: false}
validates :quantity, numericality: {only_integer: true}
validates :free, numericality: {only_integer: true}
@ -21,9 +23,19 @@ module JamRuby
validates :recurly_plan_code, presence:true
validates :sale, presence:true
def is_jam_track?
product_type == JAMTRACK
end
def is_gift_card?
product_type == GIFTCARD
end
def product
if product_type == JAMTRACK
JamTrack.find_by_id(product_id)
elsif product_type == GIFTCARD
GiftCardType.find_by_id(product_id)
else
raise 'unsupported product type'
end

View File

@ -12,6 +12,8 @@ module JamRuby
attr_accessible :quantity, :cart_type, :product_info
attr_accessor :skip_mix_check
validates_uniqueness_of :cart_id, scope: [:cart_type, :user_id, :anonymous_user_id]
belongs_to :user, :inverse_of => :shopping_carts, :class_name => "JamRuby::User", :foreign_key => "user_id"
@ -20,12 +22,13 @@ module JamRuby
validates :cart_type, presence: true
validates :cart_class_name, presence: true
validates :marked_for_redeem, numericality: {only_integer: true}
validate :not_mixed
default_scope order('created_at DESC')
def product_info
product = self.cart_product
{name: product.name, price: product.price, product_id: cart_id, plan_code: product.plan_code, real_price: real_price(product), total_price: total_price(product), quantity: quantity, marked_for_redeem: marked_for_redeem, free: free?, sales_region: product.sales_region} unless product.nil?
{type: cart_type, name: product.name, price: product.price, product_id: cart_id, plan_code: product.plan_code, real_price: real_price(product), total_price: total_price(product), quantity: quantity, marked_for_redeem: marked_for_redeem, free: free?, sales_region: product.sales_region, sale_display:product.sale_display} unless product.nil?
end
# multiply quantity by price
@ -38,6 +41,31 @@ module JamRuby
(quantity - marked_for_redeem) * product.price
end
def not_mixed
return if @skip_mix_check
existing_carts = []
this_user = any_user()
if this_user
existing_carts = this_user.shopping_carts
end
existing_carts = existing_carts.to_a
existing_carts << self
if Sale.is_mixed(existing_carts)
if free?
errors.add(:base, "You can not add a free JamTrack to a cart with non-free items. Please clear out your cart.")
return false
else
errors.add(:base, "You can not add a non-free JamTrack to a cart containing free items. Please clear out your cart.")
return false
end
end
false
end
def cart_product
self.cart_class_name.classify.constantize.find_by_id(self.cart_id) unless self.cart_class_name.blank?
@ -51,7 +79,18 @@ module JamRuby
marked_for_redeem == quantity
end
def any_user
if user
user
elsif anonymous_user_id
AnonymousUser.new(anonymous_user_id, nil)
else
nil
end
end
def self.create user, product, quantity = 1, mark_redeem = false
cart = ShoppingCart.new
if user.is_a?(User)
cart.user = user
@ -72,39 +111,42 @@ module JamRuby
cart_type == JamTrack::PRODUCT_TYPE
end
def is_gift_card?
cart_type == GiftCardType::PRODUCT_TYPE
end
# returns an array of adjustments for the shopping cart
def create_adjustment_attributes(current_user)
raise "not a jam track" unless is_jam_track?
raise "not a jam track or gift card" unless is_jam_track? || is_gift_card?
info = self.product_info
if free?
# create the credit, then the pseudo charge
[
{
accounting_code: PURCHASE_FREE_CREDIT,
currency: 'USD',
unit_amount_in_cents: -(info[:total_price] * 100).to_i,
description: "JamTrack: " + info[:name] + " (Credit)",
description: info[:sale_display] + " (Credit)",
tax_exempt: true
},
{
accounting_code: PURCHASE_FREE,
currency: 'USD',
unit_amount_in_cents: (info[:total_price] * 100).to_i,
description: "JamTrack: " + info[:name],
description: info[:sale_display],
tax_exempt: true
}
]
else
[
{
accounting_code: PURCHASE_NORMAL,
currency: 'USD',
unit_amount_in_cents: (info[:total_price] * 100).to_i,
description: "JamTrack: " + info[:name],
description: info[:sale_display],
tax_exempt: false
}
]
@ -113,8 +155,13 @@ module JamRuby
def self.move_to_user(user, anonymous_user, shopping_carts)
shopping_carts.each do |shopping_cart|
mark_redeem = ShoppingCart.user_has_redeemable_jam_track?(user)
cart = ShoppingCart.create(user, shopping_cart.cart_product, shopping_cart.quantity, mark_redeem)
if shopping_cart.is_jam_track?
mark_redeem = ShoppingCart.user_has_redeemable_jam_track?(user)
cart = ShoppingCart.create(user, shopping_cart.cart_product, shopping_cart.quantity, mark_redeem)
else
cart = ShoppingCart.create(user, shopping_cart.cart_product, shopping_cart.quantity, false)
end
end
anonymous_user.destroy_all_shopping_carts
@ -134,28 +181,32 @@ module JamRuby
# if no shpping carts have been marked, then mark it redeemable
# should be wrapped in a TRANSACTION
def self.user_has_redeemable_jam_track?(any_user)
mark_redeem = false
if APP_CONFIG.one_free_jamtrack_per_user && any_user.has_redeemable_jamtrack
mark_redeem = true # start out assuming we can redeem...
if any_user.has_redeemable_jamtrack || any_user.gifted_jamtracks > 0
free_in_cart = 0
any_user.shopping_carts.each do |shopping_cart|
# but if we find any shopping cart item already marked for redeem, then back out of mark_redeem=true
if shopping_cart.cart_type == JamTrack::PRODUCT_TYPE && shopping_cart.marked_for_redeem > 0
mark_redeem = false
break
if shopping_cart.cart_type == JamTrack::PRODUCT_TYPE
free_in_cart += shopping_cart.marked_for_redeem
end
end
any_user.free_jamtracks > free_in_cart
else
false
end
mark_redeem
end
# adds a jam_track to cart, checking for promotions
def self.add_jam_track_to_cart(any_user, jam_track)
def self.add_jam_track_to_cart(any_user, jam_track, clear:false)
cart = nil
ShoppingCart.transaction do
if any_user.has_redeemable_jamtrack
# if you still have a freebie available to you, or if you are an anonymous user, we make sure there is nothing else in your shopping cart
any_user.destroy_all_shopping_carts
if clear
# if you are an anonymous user, we make sure there is nothing else in your shopping cart ... keep it clean for the 'new user rummaging around for a freebie scenario'
any_user.destroy_jam_track_shopping_carts
any_user.reload
end
mark_redeem = ShoppingCart.user_has_redeemable_jam_track?(any_user)
@ -164,23 +215,66 @@ module JamRuby
cart
end
def self.add_item_to_cart(any_user, item)
cart = nil
ShoppingCart.transaction do
cart = ShoppingCart.create(any_user, item, 1, false)
end
cart
end
# deletes a jam track from the shopping cart, updating redeem flag as necessary
def self.remove_jam_track_from_cart(any_user, cart)
ShoppingCart.transaction do
cart.destroy
# check if we should move the redemption
# so that user.shopping_carts reflects truth
any_user.reload
# check if we should move the redemption around automatically
mark_redeem = ShoppingCart.user_has_redeemable_jam_track?(any_user)
carts = any_user.shopping_carts
# if we find any carts on the account, mark one redeemable
# if we find any carts on the account that are not redeemed, mark first one redeemable
if mark_redeem && carts.length > 0
carts[0].redeem(mark_redeem)
carts[0].save
carts.each do |cart|
if cart.marked_for_redeem == 0
if cart.quantity > 1
raise 'unknown situation for redeemption juggling'
end
cart.redeem(mark_redeem)
cart.save
break
end
end
end
end
end
def self.remove_item_from_cart(any_user, cart)
ShoppingCart.transaction do
cart.destroy
end
end
# if the number of items in the shopping cart is less than gifted_jamtracks on the user, then fix them all up
def self.apply_gifted_jamtracks(user)
jam_track_carts = user.shopping_carts.where(cart_type:JamTrack::PRODUCT_TYPE)
if jam_track_carts.count > user.gifted_jamtracks
# just whack everything in their shopping cart
user.destroy_all_shopping_carts
return
end
jam_track_carts.each do |cart|
cart.skip_mix_check = true
cart.marked_for_redeem = 1
cart.save!
end
end
def port(user, anonymous_user)
ShoppingCart.transaction do

View File

@ -40,7 +40,7 @@ module JamRuby
attr_accessible :first_name, :last_name, :email, :city, :password, :password_confirmation, :state, :country, :birth_date, :subscribe_email, :terms_of_service, :original_fpfile, :cropped_fpfile, :cropped_large_fpfile, :cropped_s3_path, :cropped_large_s3_path, :photo_url, :large_photo_url, :crop_selection
# updating_password corresponds to a lost_password
attr_accessor :updating_password, :updating_email, :updated_email, :update_email_confirmation_url, :administratively_created, :current_password, :setting_password, :confirm_current_password, :updating_avatar, :updating_progression_field, :mods_json
attr_accessor :updating_password, :updating_email, :updated_email, :update_email_confirmation_url, :administratively_created, :current_password, :setting_password, :confirm_current_password, :updating_avatar, :updating_progression_field, :mods_json, :expecting_gift_card
belongs_to :icecast_server_group, class_name: "JamRuby::IcecastServerGroup", inverse_of: :users, foreign_key: 'icecast_server_group_id'
@ -148,6 +148,11 @@ module JamRuby
# events
has_many :event_sessions, :class_name => "JamRuby::EventSession"
# gift cards
has_many :gift_cards, :class_name=> "JamRuby::GiftCard"
has_many :gift_card_purchases, :class_name=> "JamRuby::GiftCardPurchase"
# affiliate_partner
has_one :affiliate_partner, :class_name => "JamRuby::AffiliatePartner", :foreign_key => :partner_user_id, inverse_of: :partner_user
belongs_to :affiliate_referral, :class_name => "JamRuby::AffiliatePartner", :foreign_key => :affiliate_referral_id, :counter_cache => :referral_user_count
@ -194,6 +199,7 @@ module JamRuby
validates :terms_of_service, :acceptance => {:accept => true, :on => :create, :allow_nil => false }
validates :reuse_card, :inclusion => {:in => [true, false]}
validates :has_redeemable_jamtrack, :inclusion => {:in => [true, false]}
validates :gifted_jamtracks, presence: true, :numericality => { :less_than_or_equal_to => 100 }
validates :subscribe_email, :inclusion => {:in => [nil, true, false]}
validates :musician, :inclusion => {:in => [true, false]}
validates :show_whats_next, :inclusion => {:in => [nil, true, false]}
@ -214,6 +220,7 @@ module JamRuby
validate :email_case_insensitive_uniqueness
validate :update_email_case_insensitive_uniqueness, :if => :updating_email
validate :validate_mods
validate :presence_gift_card, :if => :expecting_gift_card
scope :musicians, where(:musician => true)
scope :fans, where(:musician => false)
@ -233,6 +240,18 @@ module JamRuby
end
end
def has_any_free_jamtracks
has_redeemable_jamtrack || gifted_jamtracks > 0
end
def free_jamtracks
(has_redeemable_jamtrack ? 1 : 0) + gifted_jamtracks
end
def show_free_jamtrack?
ShoppingCart.user_has_redeemable_jam_track?(self)
end
def failed_qualification(reason)
self.last_failed_certified_gear_at = DateTime.now
self.last_failed_certified_gear_reason = reason
@ -255,6 +274,12 @@ module JamRuby
end
end
def presence_gift_card
if self.gift_cards.length == 0
errors.add(:gift_card, ValidationMessages::NOT_FOUND)
end
end
def validate_current_password
# checks if the user put in their current password (used when changing your email, for instance)
errors.add(:current_password, ValidationMessages::NOT_YOUR_PASSWORD) if should_confirm_existing_password? && !valid_password?(self.current_password)
@ -1025,6 +1050,7 @@ module JamRuby
reuse_card = options[:reuse_card]
signup_hint = options[:signup_hint]
affiliate_partner = options[:affiliate_partner]
gift_card = options[:gift_card]
user = User.new
@ -1036,6 +1062,9 @@ module JamRuby
user.terms_of_service = terms_of_service
user.musician = musician
user.reuse_card unless reuse_card.nil?
user.gifted_jamtracks = 0
user.has_redeemable_jamtrack = true
# FIXME: Setting random password for social network logins. This
# is because we have validations all over the place on this.
@ -1140,8 +1169,22 @@ module JamRuby
end
end
found_gift_card = nil
# if a gift card value was passed in, then try to find that gift card and apply it to user
if gift_card
user.expecting_gift_card = true
found_gift_card = GiftCard.where(code:gift_card).where(user_id:nil).first
user.gift_cards << found_gift_card if found_gift_card
end
user.save
if found_gift_card
user.reload
ShoppingCart.apply_gifted_jamtracks(user)
end
# if the user has just one, free jamtrack in their shopping cart, and it matches the signup hint, then auto-buy it
# only_freebie_in_cart =
# signup_hint &&
@ -1181,6 +1224,7 @@ module JamRuby
end
end
end
user.reload if user.id# gift card adding gifted_jamtracks doesn't reflect here until reload
user
end # def signup
@ -1636,6 +1680,11 @@ module JamRuby
ShoppingCart.where("user_id=?", self).destroy_all
end
def destroy_jam_track_shopping_carts
ShoppingCart.destroy_all(anonymous_user_id: @id, cart_type: JamTrack::PRODUCT_TYPE)
end
def unsubscribe_token
self.class.create_access_token(self)
end

View File

@ -10,9 +10,11 @@ module JamRuby
include JamRuby::S3ManagerMixin
TAP_IN_PADDING = 2
MAX_PAN = 90
MIN_PAN = -90
KNOCK_SECONDS = 0.035
attr_accessor :mixdown_package_id, :settings, :mixdown_package, :mixdown, :step
@queue = :jam_track_mixdown_packager
@ -55,6 +57,8 @@ module JamRuby
@mixdown = @mixdown_package.jam_track_mixdown
@settings = JSON.parse(@mixdown.settings)
process_jmep
track_settings
# compute the step count
@ -102,6 +106,236 @@ module JamRuby
vol != 1.0 || pan != 0
end
def process_jmep
@start_points = []
@initial_padding = 0.0
speed = @settings['speed'] || 0
@speed_factor = 1.0 + (-speed.to_f / 100.0)
@inverse_speed_factor = 1 - (-speed.to_f / 100)
log.info("speed factor #{@speed_factor}")
jmep = @mixdown.jam_track.jmep_json
if jmep
jmep = JSON.parse(jmep)
end
if jmep.nil?
log.debug("no jmep")
return
end
events = jmep["Events"]
return if events.nil? || events.length == 0
metronome = nil
events.each do |event|
if event.has_key?("metronome")
metronome = event["metronome"]
break
end
end
if metronome.nil? || metronome.length == 0
log.debug("no metronome events for jmep", jmep)
return
end
@start_points = metronome.select { |x| puts x.inspect; x["action"] == "start" }
log.debug("found #{@start_points.length} metronome start points")
start_point = @start_points[0]
if start_point
start_time = parse_time(start_point["ts"])
if start_time < 2.0
padding = start_time - 2.0
@initial_padding = padding.abs
@initial_tap_in = start_time
end
end
if @speed_factor != 1.0
metronome.length.times do |count|
# we expect to find metronome start/stop grouped
if count % 2 == 0
start = metronome[count]
stop = metronome[count + 1]
if start["action"] != "start" || stop["action"] != "stop"
# bail out
log.error("found de-coupled metronome events #{start.to_json} | #{stop.to_json}")
next
end
bpm = start["bpm"].to_f
stop_time = parse_time(stop['ts'])
ticks = stop['ticks'].to_i
new_bpm = bpm * @inverse_speed_factor
new_stop_time = stop_time * @speed_factor
new_start_time = new_stop_time - (60.0/new_bpm * ticks)
log.info("original bpm:#{bpm} start: #{parse_time(start["ts"])} stop: #{stop_time}")
log.info("updated bpm:#{new_bpm} start: #{new_start_time} stop: #{new_stop_time}")
stop["ts"] = new_stop_time
start["ts"] = new_start_time
start["bpm"] = new_bpm
stop["bpm"] = new_bpm
@tap_in_initial_silence = (@initial_tap_in + @initial_padding) * @speed_factor
end
end
end
@start_points = metronome.select { |x| puts x.inspect; x["action"] == "start" }
end
# format like: "-0:00:02:820"
def parse_time(ts)
if ts.is_a?(Float)
return ts
end
time = 0.0
negative = false
if ts.start_with?('-')
negative = true
end
# parse time_format
bits = ts.split(':').reverse
bit_position = 0
bits.each do |bit|
if bit_position == 0
# milliseconds
milliseconds = bit.to_f
time += milliseconds/1000
elsif bit_position == 1
# seconds
time += bit.to_f
elsif bit_position == 2
# minutes
time += 60 * bit.to_f
elsif bit_position == 3
# hours
# not bothering
end
bit_position += 1
end
if negative
time = 0.0 - time
end
time
end
def path_to_resources
File.join(File.dirname(File.expand_path(__FILE__)), '../../../lib/jam_ruby/app/assets/sounds')
end
def knock_file
if long_sample_rate == 44100
knock = File.join(path_to_resources, 'knock44.wav')
else
knock = File.join(path_to_resources, 'knock48.wav')
end
log.debug("knock file path: " + knock)
knock
end
def create_silence(tmp_dir, segment_count, duration)
file = File.join(tmp_dir, "#{segment_count}.wav")
# -c 2 means stereo
cmd("sox -n -r #{long_sample_rate} -c 2 #{file} trim 0.0 #{duration}", "silence")
file
end
def create_tapin_track(tmp_dir)
return nil if @start_points.length == 0
segment_count = 0
#initial_silence = @initial_tap_in + @initial_padding
initial_silence = @tap_in_initial_silence
#log.info("tapin data: initial_tap_in: #{@initial_tap_in}, initial_padding: #{@initial_padding}, initial_silence: #{initial_silence}")
time_points = []
files = []
if initial_silence > 0
files << create_silence(tmp_dir, segment_count, initial_silence)
time_points << {type: :silence, ts: initial_silence}
segment_count += 1
end
time_cursor = nil
@start_points.each do |start_point|
tap_time = parse_time(start_point["ts"])
if !time_cursor.nil?
between_silence = tap_time - time_cursor
files << create_silence(tmp_dir, segment_count, between_silence)
time_points << {type: :silence, ts: between_silence}
end
time_cursor = tap_time
bpm = start_point["bpm"].to_f
tick_silence = 60.0/bpm - KNOCK_SECONDS
ticks = start_point["ticks"].to_i
ticks.times do |tick|
files << knock_file
files << create_silence(tmp_dir, segment_count, tick_silence)
time_points << {type: :knock, ts: KNOCK_SECONDS}
time_points << {type: :silence, ts: tick_silence}
time_cursor + 60.0/bpm
segment_count += 1
end
end
log.info("time points for tap-in: #{time_points.inspect}")
# do we need to pad with time? not sure
sequence_cmd = "sox "
files.each do |file|
sequence_cmd << "\"#{file}\" "
end
count_in = File.join(tmp_dir, "count-in.wav")
sequence_cmd << "\"#{count_in}\""
cmd(sequence_cmd, "count_in")
@count_in_file = count_in
count_in
end
# creates a list of tracks to actually mix
def track_settings
altered_tracks = @settings["tracks"] || []
@ -113,6 +347,15 @@ module JamRuby
stems = @mixdown.jam_track.stem_tracks
@track_count = stems.length
@include_count_in = @settings["count-in"] && @start_points.length > 0 && @mixdown_package.encrypt_type.nil?
# temp
# @include_count_in = true
if @include_count_in
@track_count += 1
end
stems.each do |stem|
vol = 1.0
@ -139,10 +382,14 @@ module JamRuby
# if we didn't deliberately skip this one, and if there was no 'match' (meaning user did not specify), then we leave this in unchanged
if !skipped && !match
@track_settings << {stem:stem, vol:vol, pan:pan}
@track_settings << {stem: stem, vol: vol, pan: pan}
end
end
if @include_count_in
@track_settings << {count_in: true, vol: 1.0, pan: 0}
end
@track_settings
end
@ -153,14 +400,14 @@ module JamRuby
# k = f(i) = (i)/(2*MAX_PAN) + 0.5
# so f(MIN_PAN) = -0.5 + 0.5 = 0
k = ((pan * (1.0))/ (2.0 * MAX_PAN )) + 0.5
k = ((pan * (1.0))/ (2.0 * MAX_PAN)) + 0.5
l, r = 0
if k == 0
l = 0.0
r = 1.0
else
l = Math.sqrt(k)
l = Math.sqrt(k)
r = Math.sqrt(1-k)
end
@ -169,24 +416,26 @@ module JamRuby
def package
puts @settings.inspect
puts @track_count
puts @track_settings
puts @track_settings.count
log.info("Settings: #{@settings.to_json}")
Dir.mktmpdir do |tmp_dir|
# download all files
@track_settings.each do |track|
jam_track_track = track[:stem]
file = File.join(tmp_dir, jam_track_track.id + '.ogg')
if track[:count_in]
file = create_tapin_track(tmp_dir)
bump_step(@mixdown_package)
else
jam_track_track = track[:stem]
bump_step(@mixdown_package)
file = File.join(tmp_dir, jam_track_track.id + '.ogg')
# download each track needed
s3_manager.download(jam_track_track.url_by_sample_rate(@mixdown_package.sample_rate), file)
bump_step(@mixdown_package)
# download each track needed
s3_manager.download(jam_track_track.url_by_sample_rate(@mixdown_package.sample_rate), file)
end
track[:file] = file
end
@ -206,6 +455,8 @@ module JamRuby
apply_vol_and_pan tmp_dir
create_silence_padding tmp_dir
mix tmp_dir
pitch_speed tmp_dir
@ -218,6 +469,7 @@ module JamRuby
@track_settings.each do |track|
jam_track_track = track[:stem]
count_in = track[:count_in]
file = track[:file]
unless should_alter_volume? track
@ -235,7 +487,11 @@ module JamRuby
# sox claps.wav claps-remixed.wav remix 1v1.0 2v1.0
volumed_file = File.join(tmp_dir, jam_track_track.id + '-volumed.ogg')
if count_in
volumed_file = File.join(tmp_dir, 'count-in' + '-volumed.ogg')
else
volumed_file = File.join(tmp_dir, jam_track_track.id + '-volumed.ogg')
end
cmd("sox \"#{file}\" \"#{volumed_file}\" remix 1v#{channel_r} 2v#{channel_l}", 'vol_pan')
@ -244,6 +500,29 @@ module JamRuby
end
end
def create_silence_padding(tmp_dir)
if @initial_padding > 0 && @include_count_in
@padding_file = File.join(tmp_dir, "initial_padding.ogg")
# -c 2 means stereo
cmd("sox -n -r #{long_sample_rate} -c 2 #{@padding_file} trim 0.0 #{@initial_padding}", "initial_padding")
@track_settings.each do |track|
next if track[:count_in]
input = track[:volumed_file]
output = input[0..-5] + '-padded.ogg'
padd_cmd = "sox '#{@padding_file}' '#{input}' '#{output}'"
cmd(padd_cmd, "pad_track_with_silence")
track[:volumed_file] = output
end
end
end
# output is @mix_file
def mix(tmp_dir)
@ -251,6 +530,11 @@ module JamRuby
@mix_file = File.join(tmp_dir, "mix.ogg")
pitch = @settings['pitch'] || 0
speed = @settings['speed'] || 0
# if there is only one track to mix, we need to skip mixing (sox will barf if you try to mix one file), but still divide by number of tracks
if @track_settings.count == 1
mix_divide = 1.0/@track_count
@ -263,6 +547,11 @@ module JamRuby
cmd = "sox -m"
mix_divide = 1.0/@track_count
@track_settings.each do |track|
# if pitch/shifted, we lay the tap-in after pitch/speed shift
# next if (pitch != 0 || speed != 0) && track[:count_in]
next if track[:count_in]
volumed_file = track[:volumed_file]
cmd << " -v #{mix_divide} \"#{volumed_file}\""
end
@ -275,6 +564,13 @@ module JamRuby
end
def long_sample_rate
sample_rate = 48000
if @mixdown_package.sample_rate != 48
sample_rate = 44100
end
sample_rate
end
# output is @speed_mix_file
def pitch_speed tmp_dir
@ -300,17 +596,21 @@ module JamRuby
# usage: sbsms infile<.wav|.aif|.mp3|.ogg> outfile<.ogg> rate[0.01:100] halfsteps[-48:48] outSampleRateInHz
sample_rate = 48000
if @mixdown_package.sample_rate != 48
sample_rate = 44100
end
sample_rate = long_sample_rate
# rate comes in as a percent (like 5, -5 for 5%, -5%). We need to change that to 1.05/
sbsms_speed = speed/100.0
sbsms_speed = 1.0 + sbsms_speed
sbsms_pitch = pitch
cmd( "sbsms \"#{@mix_file}\" \"#{@speed_mix_file}\" #{sbsms_speed} #{sbsms_pitch} #{sample_rate}", 'speed_pitch_shift')
cmd("sbsms \"#{@mix_file}\" \"#{@speed_mix_file}\" #{sbsms_speed} #{sbsms_pitch} #{sample_rate}", 'speed_pitch_shift')
end
if @include_count_in
# lay the tap-ins over the recording
layered = File.join(tmp_dir, "layered_speed_mix.ogg")
cmd("sox -m '#{@count_in_file}' '#{@speed_mix_file}' '#{layered}'", "layer_tap_in")
@speed_mix_file = layered
end
end
@ -337,7 +637,7 @@ module JamRuby
length = File.size(output)
computed_md5 = Digest::MD5.new
File.open(output, 'rb').each {|line| computed_md5.update(line)}
File.open(output, 'rb').each { |line| computed_md5.update(line) }
md5 = computed_md5.to_s
@mixdown_package.finish_sign(s3_url, private_key, length, md5.to_s)
@ -399,7 +699,7 @@ module JamRuby
end
private_key_file = File.join(tmp_dir, 'skey.pem')
File.open(private_key_file, 'w') {|f| f.write(private_key) }
File.open(private_key_file, 'w') { |f| f.write(private_key) }
log.debug("PRIVATE KEY")
log.debug(private_key)

View File

@ -19,6 +19,8 @@ FactoryGirl.define do
terms_of_service true
last_jam_audio_latency 5
reuse_card true
has_redeemable_jamtrack true
gifted_jamtracks 0
#u.association :musician_instrument, factory: :musician_instrument, user: u
@ -858,4 +860,18 @@ FactoryGirl.define do
legalese Faker::Lorem.paragraphs(6).join("\n\n")
end
factory :gift_card, class: 'JamRuby::GiftCard' do
sequence(:code) {n.to_s}
card_type JamRuby::GiftCardType::JAM_TRACKS_5
end
factory :gift_card_type, class: 'JamRuby::GiftCardType' do
card_type JamRuby::GiftCardType::JAM_TRACKS_5
end
factory :gift_card_purchase, class: 'JamRuby::GiftCardPurchase' do
association :user, factory: :user
end
end

View File

@ -234,6 +234,17 @@ describe JamTrack do
query.size.should == 2
end
it "deals with aggregration (regression)" do
query, pager, count = JamTrack.index({sort_by: 'jamtrack', artist: 'K.C. And The Sunshine Band'}, user)
count.should == 0
jam_track1 = FactoryGirl.create(:jam_track_with_tracks, name: 'Take a Chance On Me', original_artist: 'K.C. And The Sunshine Band')
query, pager, count = JamTrack.index({sort_by: 'jamtrack', artist: 'K.C. And The Sunshine Band'}, user)
count.should == 1
end
end
describe "validations" do

View File

@ -135,7 +135,7 @@ describe RecurlyTransactionWebHook do
RecurlyTransactionWebHook.create_from_xml(document)
JamTrackRight.find_by_id(jam_track_right.id).should be_nil
JamTrackRight.find_by_id(jam_track_right.id).should_not be_nil
end
it "deletes jam_track_right when voided" do
@ -154,7 +154,7 @@ describe RecurlyTransactionWebHook do
RecurlyTransactionWebHook.create_from_xml(document)
JamTrackRight.find_by_id(jam_track_right.id).should be_nil
JamTrackRight.find_by_id(jam_track_right.id).should_not be_nil
end
end

View File

@ -6,6 +6,7 @@ describe SaleLineItem do
let(:user) {FactoryGirl.create(:user)}
let(:user2) {FactoryGirl.create(:user)}
let(:jam_track) {FactoryGirl.create(:jam_track)}
let(:gift_card) {FactoryGirl.create(:gift_card_type, card_type: GiftCardType::JAM_TRACKS_10)}
describe "associations" do
@ -23,7 +24,7 @@ describe SaleLineItem do
describe "state" do
it "success" do
it "jam track success" do
sale = Sale.create_jam_track_sale(user)
shopping_cart = ShoppingCart.create(user, jam_track)
sale_line_item = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, 'some_recurly_uuid', nil, nil)
@ -37,5 +38,20 @@ describe SaleLineItem do
success: true
})
end
it "gift card success" do
sale = Sale.create_jam_track_sale(user)
shopping_cart = ShoppingCart.create(user, gift_card)
sale_line_item = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, 'some_recurly_uuid', nil, nil)
transaction = FactoryGirl.create(:recurly_transaction_web_hook, subscription_id: 'some_recurly_uuid')
sale_line_item.reload
sale_line_item.state.should eq({
void: false,
refund: false,
fail: false,
success: true
})
end
end
end

View File

@ -5,6 +5,24 @@ describe Sale do
let(:user) {FactoryGirl.create(:user)}
let(:user2) {FactoryGirl.create(:user)}
let(:jam_track) {FactoryGirl.create(:jam_track)}
let(:jam_track2) {FactoryGirl.create(:jam_track)}
let(:jam_track3) {FactoryGirl.create(:jam_track)}
let(:gift_card) {GiftCardType.jam_track_5}
def assert_free_line_item(sale_line_item, jamtrack)
sale_line_item.recurly_tax_in_cents.should be_nil
sale_line_item.recurly_total_in_cents.should be_nil
sale_line_item.recurly_currency.should be_nil
sale_line_item.recurly_discount_in_cents.should be_nil
sale_line_item.product_type.should eq(JamTrack::PRODUCT_TYPE)
sale_line_item.unit_price.should eq(jamtrack.price)
sale_line_item.quantity.should eq(1)
sale_line_item.free.should eq(1)
sale_line_item.sales_tax.should be_nil
sale_line_item.shipping_handling.should eq(0)
sale_line_item.recurly_plan_code.should eq(jamtrack.plan_code)
sale_line_item.product_id.should eq(jamtrack.id)
end
describe "index" do
it "empty" do
@ -47,7 +65,11 @@ describe Sale do
let(:user) {FactoryGirl.create(:user)}
let(:jamtrack) { FactoryGirl.create(:jam_track) }
let(:jamtrack2) { FactoryGirl.create(:jam_track) }
let(:jamtrack3) { FactoryGirl.create(:jam_track) }
let(:jamtrack4) { FactoryGirl.create(:jam_track) }
let(:jam_track_price_in_cents) { (jamtrack.price * 100).to_i }
let(:gift_card_price_in_cents) { (gift_card.price * 100).to_i }
let(:client) { RecurlyClient.new }
let(:billing_info) {
info = {}
@ -75,6 +97,77 @@ describe Sale do
end
end
it "for a gift card" do
shopping_cart = ShoppingCart.create user, gift_card, 1, false
client.find_or_create_account(user, billing_info)
sales = Sale.place_order(user, [shopping_cart])
user.reload
user.sales.length.should eq(1)
sales.should eq(user.sales)
sale = sales[0]
sale.recurly_invoice_id.should_not be_nil
sale.recurly_subtotal_in_cents.should eq(gift_card_price_in_cents)
sale.recurly_tax_in_cents.should eq(0)
sale.recurly_total_in_cents.should eq(gift_card_price_in_cents)
sale.recurly_currency.should eq('USD')
sale.order_total.should eq(gift_card.price)
sale.sale_line_items.length.should == 1
sale_line_item = sale.sale_line_items[0]
# validate we are storing pricing info from recurly
sale_line_item.recurly_tax_in_cents.should eq(0)
sale_line_item.recurly_total_in_cents.should eq(gift_card_price_in_cents)
sale_line_item.recurly_currency.should eq('USD')
sale_line_item.recurly_discount_in_cents.should eq(0)
sale_line_item.product_type.should eq(GiftCardType::PRODUCT_TYPE)
sale_line_item.unit_price.should eq(gift_card.price)
sale_line_item.quantity.should eq(1)
sale_line_item.free.should eq(0)
sale_line_item.sales_tax.should be_nil
sale_line_item.shipping_handling.should eq(0)
sale_line_item.recurly_plan_code.should eq(gift_card.plan_code)
sale_line_item.product_id.should eq(gift_card.id)
sale_line_item.recurly_subscription_uuid.should be_nil
sale_line_item.recurly_adjustment_uuid.should_not be_nil
sale_line_item.recurly_adjustment_credit_uuid.should be_nil
sale_line_item.recurly_adjustment_uuid.should_not be_nil
# verify subscription is in Recurly
recurly_account = client.get_account(user)
adjustments = recurly_account.adjustments
adjustments.should_not be_nil
adjustments.should have(1).items
purchase= adjustments[0]
purchase.unit_amount_in_cents.should eq((gift_card.price * 100).to_i)
purchase.accounting_code.should eq(ShoppingCart::PURCHASE_NORMAL)
purchase.description.should eq("JamTracks Gift Card (5)")
purchase.state.should eq('invoiced')
purchase.uuid.should eq(sale_line_item.recurly_adjustment_uuid)
invoices = recurly_account.invoices
invoices.should have(1).items
invoice = invoices[0]
invoice.uuid.should eq(sale.recurly_invoice_id)
invoice.line_items.should have(1).items # should have single adjustment associated
invoice.line_items[0].should eq(purchase)
invoice.subtotal_in_cents.should eq((gift_card.price * 100).to_i)
invoice.total_in_cents.should eq((gift_card.price * 100).to_i)
invoice.state.should eq('collected')
# verify jam_track_rights data
user.gift_card_purchases.should_not be_nil
user.gift_card_purchases.should have(1).items
user.gift_card_purchases.last.gift_card_type.should eq(GiftCardType.jam_track_5)
user.has_redeemable_jamtrack.should be_true
sale_line_item.affiliate_referral.should be_nil
sale_line_item.affiliate_referral_fee_in_cents.should be_nil
end
it "for a free jam track" do
shopping_cart = ShoppingCart.create user, jamtrack, 1, true
@ -87,6 +180,7 @@ describe Sale do
sales.should eq(user.sales)
sale = sales[0]
sale.recurly_invoice_id.should be_nil
sale.recurly_subtotal_in_cents.should eq(0)
@ -132,6 +226,69 @@ describe Sale do
user.has_redeemable_jamtrack.should be_false
end
it "for two jam tracks (1 freebie, 1 gifted), then 1 gifted/1 pay" do
user.gifted_jamtracks = 2
user.save!
shopping_cart1 = ShoppingCart.create user, jamtrack, 1, true
shopping_cart2 = ShoppingCart.create user, jamtrack2, 1, true
client.find_or_create_account(user, billing_info)
sales = Sale.place_order(user, [shopping_cart1, shopping_cart2])
user.reload
user.sales.length.should eq(1)
sale = sales[0]
sale.reload
sale.recurly_invoice_id.should be_nil
sale.recurly_subtotal_in_cents.should eq(0)
sale.recurly_tax_in_cents.should eq(0)
sale.recurly_total_in_cents.should eq(0)
sale.recurly_currency.should eq('USD')
sale.order_total.should eq(0)
sale.sale_line_items.length.should == 2
assert_free_line_item(sale.sale_line_items[0], jamtrack)
assert_free_line_item(sale.sale_line_items[1], jamtrack2)
# verify jam_track_rights data
right1 = JamTrackRight.where(user_id: user.id).where(jam_track_id: jamtrack.id).first
right2 = JamTrackRight.where(user_id: user.id).where(jam_track_id: jamtrack2.id).first
user.jam_track_rights.should have(2).items
right1.redeemed.should be_true
right2.redeemed.should be_true
user.has_redeemable_jamtrack.should be_false
user.gifted_jamtracks.should eq(1)
# OK! Now make a second purchase; this time, buy one free, one not free
shopping_cart3 = ShoppingCart.create user, jamtrack3, 1, true
client.find_or_create_account(user, billing_info)
sales = Sale.place_order(user, [shopping_cart3])
user.reload
user.sales.length.should eq(2)
sale = sales[0]
sale.reload
sale.recurly_invoice_id.should be_nil
sale.recurly_subtotal_in_cents.should eq(0)
sale.recurly_tax_in_cents.should eq(0)
sale.recurly_total_in_cents.should eq(0)
sale.recurly_currency.should eq('USD')
sale.order_total.should eq(0)
sale.sale_line_items.length.should == 1
assert_free_line_item(sale.sale_line_items[0], jamtrack3)
end
it "for a free jam track with an affiliate association" do
partner = FactoryGirl.create(:affiliate_partner)
user.affiliate_referral = partner

View File

@ -3,8 +3,15 @@ require 'spec_helper'
describe ShoppingCart do
let(:user) { FactoryGirl.create(:user) }
let(:jam_track) {FactoryGirl.create(:jam_track) }
let(:jam_track2) {FactoryGirl.create(:jam_track) }
let(:jam_track) { FactoryGirl.create(:jam_track) }
let(:jam_track2) { FactoryGirl.create(:jam_track) }
let(:jam_track3) { FactoryGirl.create(:jam_track) }
let(:jam_track4) { FactoryGirl.create(:jam_track) }
let(:jam_track5) { FactoryGirl.create(:jam_track) }
let(:jam_track6) { FactoryGirl.create(:jam_track) }
let(:jam_track7) { FactoryGirl.create(:jam_track) }
let(:gift_card) {FactoryGirl.create(:gift_card_type)}
let(:gift_card2) {FactoryGirl.create(:gift_card_type)}
before(:each) do
ShoppingCart.delete_all
@ -13,6 +20,9 @@ describe ShoppingCart do
it "can reference a shopping cart" do
shopping_cart = ShoppingCart.create user, jam_track, 1
shopping_cart.errors.any?.should be_false
shopping_cart.valid?.should be_true
user.reload
ShoppingCart.count.should == 1
user.shopping_carts.count.should == 1
user.shopping_carts[0].product_info[:name].should == jam_track.name
@ -21,18 +31,21 @@ describe ShoppingCart do
user.shopping_carts[0].quantity.should == 1
end
it "maintains only one fre JamTrack in ShoppingCart" do
cart1 = ShoppingCart.add_jam_track_to_cart(user, jam_track)
it "maintains only one free JamTrack in ShoppingCart" do
cart1 = ShoppingCart.add_jam_track_to_cart(user, jam_track, clear: true)
cart1.should_not be_nil
cart1.errors.any?.should be_false
user.reload
cart2 = ShoppingCart.add_jam_track_to_cart(user, jam_track)
cart2 = ShoppingCart.add_jam_track_to_cart(user, jam_track, clear: true)
cart2.errors.any?.should be_false
user.reload
user.shopping_carts.length.should eq(1)
cart3 = ShoppingCart.add_jam_track_to_cart(user, jam_track2)
cart3.errors.any?.should be_false
cart3 = ShoppingCart.add_item_to_cart(user, gift_card)
cart3.errors.any?.should be_true
user.reload
user.shopping_carts.length.should eq(1)
cart4 = ShoppingCart.add_jam_track_to_cart(user, jam_track2, clear: true)
cart4.errors.any?.should be_false
user.reload
user.shopping_carts.length.should eq(1)
end
@ -48,24 +61,159 @@ describe ShoppingCart do
cart2.errors.any?.should be_true
end
it "a second giftcard just adds quantity" do
end
describe "redeemable behavior" do
it "removes redeemable item to shopping cart (maintains only one in cart)" do
user.has_redeemable_jamtrack.should be_true
cart1 = ShoppingCart.add_jam_track_to_cart(user, jam_track, clear: true)
cart1.should_not be_nil
cart1.errors.any?.should be_false
cart1.marked_for_redeem.should eq(1)
user.reload
cart2 = ShoppingCart.add_jam_track_to_cart(user, jam_track2, clear: true)
cart2.should_not be_nil
cart2.errors.any?.should be_false
cart2.marked_for_redeem.should eq(1)
ShoppingCart.find_by_id(cart1.id).should be nil
ShoppingCart.remove_jam_track_from_cart(user, cart2)
user.reload
user.shopping_carts.length.should eq(0)
ShoppingCart.find_by_id(cart2.id).should be nil
end
end
describe "multiple free jamtracks" do
before(:each) do
user.gifted_jamtracks = 5
user.save!
end
it "user can add and remove jamtracks without issue, until 'mixed' free/non-free is hit" do
cart1 = ShoppingCart.add_jam_track_to_cart(user, jam_track)
cart1.should_not be_nil
cart1.errors.any?.should be_false
user.reload
cart2 = ShoppingCart.add_jam_track_to_cart(user, jam_track2)
cart2.should_not be_nil
cart2.errors.any?.should be_false
user.reload
user.shopping_carts.length.should eq(2)
cart1.marked_for_redeem.should eq(1)
cart2.marked_for_redeem.should eq(1)
ShoppingCart.remove_jam_track_from_cart(user, jam_track)
user.shopping_carts.length.should eq(0)
cart2.reload
cart3 = ShoppingCart.add_jam_track_to_cart(user, jam_track3)
cart3.errors.any?.should be_false
user.reload
user.shopping_carts.length.should eq(3)
cart1.marked_for_redeem.should eq(1)
cart2.marked_for_redeem.should eq(1)
cart3.marked_for_redeem.should eq(1)
cart4 = ShoppingCart.add_jam_track_to_cart(user, jam_track4)
cart4.errors.any?.should be_false
user.reload
user.shopping_carts.length.should eq(4)
cart1.marked_for_redeem.should eq(1)
cart2.marked_for_redeem.should eq(1)
cart3.marked_for_redeem.should eq(1)
cart4.marked_for_redeem.should eq(1)
cart5 = ShoppingCart.add_jam_track_to_cart(user, jam_track5)
cart5.errors.any?.should be_false
user.reload
user.shopping_carts.length.should eq(5)
cart1.marked_for_redeem.should eq(1)
cart2.marked_for_redeem.should eq(1)
cart3.marked_for_redeem.should eq(1)
cart4.marked_for_redeem.should eq(1)
cart5.marked_for_redeem.should eq(1)
cart6 = ShoppingCart.add_jam_track_to_cart(user, jam_track6)
cart6.errors.any?.should be_false
user.reload
user.shopping_carts.length.should eq(6)
cart1.marked_for_redeem.should eq(1)
cart2.marked_for_redeem.should eq(1)
cart3.marked_for_redeem.should eq(1)
cart4.marked_for_redeem.should eq(1)
cart5.marked_for_redeem.should eq(1)
cart6.marked_for_redeem.should eq(1)
cart7 = ShoppingCart.add_jam_track_to_cart(user, jam_track7)
cart7.errors.any?.should be_true
user.reload
user.shopping_carts.length.should eq(6)
cart1.marked_for_redeem.should eq(1)
cart2.marked_for_redeem.should eq(1)
cart3.marked_for_redeem.should eq(1)
cart4.marked_for_redeem.should eq(1)
cart5.marked_for_redeem.should eq(1)
cart6.marked_for_redeem.should eq(1)
end
end
describe "gift cards" do
it "can not add multiple of same type" do
cart1 = ShoppingCart.add_item_to_cart(user, gift_card)
cart1.should_not be_nil
cart1.errors.any?.should be_false
user.reload
user.has_redeemable_jamtrack = true
user.shopping_carts.length.should eq(1)
user.shopping_carts[0].quantity.should eql(1)
cart2 = ShoppingCart.add_item_to_cart(user, gift_card)
cart2.should_not be_nil
# it's the same type, so it's blocked
cart2.errors.any?.should be_true
cart2.errors[:cart_id].should eq(["has already been taken"])
end
end
describe "mixed" do
it "non-free then free" do
# you shouldn't be able to add a free after a non-free
user.has_redeemable_jamtrack = false
user.save!
cart1 = ShoppingCart.add_jam_track_to_cart(user, jam_track)
cart1.should_not be_nil
cart1.errors.any?.should be_false
user.has_redeemable_jamtrack = true
user.save!
user.reload
cart2 = ShoppingCart.add_jam_track_to_cart(user, jam_track2)
cart2.errors.any?.should be_true
cart2.errors[:base].should eq(["You can not add a free JamTrack to a cart with non-free items. Please clear out your cart."])
user.shopping_carts.length.should eq(1)
end
it "free then non-free" do
cart1 = ShoppingCart.add_jam_track_to_cart(user, jam_track)
cart1.should_not be_nil
cart1.errors.any?.should be_false
user.reload
cart2 = ShoppingCart.add_jam_track_to_cart(user, jam_track2)
cart2.errors.any?.should be_true
cart2.errors[:base].should eq(["You can not add a non-free JamTrack to a cart containing free items. Please clear out your cart."])
user.shopping_carts.length.should eq(1)
end
end
end

View File

@ -95,13 +95,13 @@ end
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(:deletion, {pre_count: true, reset_ids:false, :except => %w[instruments genres icecast_server_groups jamcompany jamisp geoipblocks geoipisp geoiplocations cities regions countries generic_state spatial_ref_sys] })
DatabaseCleaner.clean_with(:deletion, {pre_count: true, reset_ids:false, :except => %w[gift_card_types instruments genres icecast_server_groups jamcompany jamisp geoipblocks geoipisp geoiplocations cities regions countries generic_state spatial_ref_sys] })
end
config.around(:each) do |example|
# set no_transaction: true as metadata on your test to use deletion strategy instead
if example.metadata[:no_transaction]
DatabaseCleaner.strategy = :deletion, {pre_count: true, reset_ids:false, :except => %w[instruments genres icecast_server_groups jamcompany jamisp geoipblocks geoipisp geoiplocations cities regions countries generic_state spatial_ref_sys] }
DatabaseCleaner.strategy = :deletion, {pre_count: true, reset_ids:false, :except => %w[gift_card_types instruments genres icecast_server_groups jamcompany jamisp geoipblocks geoipisp geoiplocations cities regions countries generic_state spatial_ref_sys] }
else
DatabaseCleaner.strategy = :transaction
end

Binary file not shown.

After

Width:  |  Height:  |  Size: 821 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -40,8 +40,8 @@
var template= context.JK.fillTemplate($('#template-account-profile-avatar').html(), {
"fp_apikey" : gon.fp_apikey,
"data-fp-store-path" : createStorePath(userDetail) + createOriginalFilename(userDetail),
"fp_policy" : filepicker_policy.policy,
"fp_signature" : filepicker_policy.signature
"fp_policy" : encodeURIComponent(filepicker_policy.policy),
"fp_signature" : encodeURIComponent(filepicker_policy.signature)
});
$('#account-profile-avatar-content-scroller').html(template);
@ -202,7 +202,6 @@
renderNoAvatar(avatarSpace);
}
else {
rest.getFilepickerPolicy({handle: fpfile.url})
.done(function(filepickerPolicy) {
avatarSpace.children().remove();

View File

@ -137,6 +137,9 @@
else if(type === ALERT_NAMES.VIDEO_WINDOW_CLOSED) {
context.VideoActions.videoWindowClosed()
}
else if (type === ALERT_NAMES.VST_CHANGED) {
context.ConfigureTracksActions.onVstChanged()
}
else if((!context.JK.CurrentSessionModel || !context.JK.CurrentSessionModel.inSession()) &&
(ALERT_NAMES.INPUT_IO_RATE == type || ALERT_NAMES.INPUT_IO_JTR == type || ALERT_NAMES.OUTPUT_IO_RATE == type || ALERT_NAMES.OUTPUT_IO_JTR== type)) {
// squelch these events if not in session

View File

@ -15,6 +15,7 @@
var $templatePurchasedJamTrack = null;
var $thanksPanel = null;
var $jamTrackInBrowser = null;
var $giftCardPurchased = null;
var $purchasedJamTrack = null;
var $purchasedJamTrackHeader = null;
var $purchasedJamTracks = null;
@ -75,9 +76,17 @@
else {
$thanksPanel.removeClass('hidden')
handleJamTracksPurchased(purchaseResponse.jam_tracks)
handleGiftCardsPurchased(purchaseResponse.gift_cards)
}
}
function handleGiftCardsPurchased(gift_cards) {
// were any GiftCards purchased?
if(gift_cards && gift_cards.length > 0) {
$giftCardPurchased.removeClass('hidden')
}
}
function handleJamTracksPurchased(jamTracks) {
// were any JamTracks purchased?
var jamTracksPurchased = jamTracks && jamTracks.length > 0;
@ -194,6 +203,7 @@
$templatePurchasedJamTrack = $('#template-purchased-jam-track');
$thanksPanel = $screen.find(".thanks-panel");
$jamTrackInBrowser = $screen.find(".thanks-detail.jam-tracks-in-browser");
$giftCardPurchased = $screen.find('.thanks-detail.gift-card')
$purchasedJamTrack = $thanksPanel.find(".thanks-detail.purchased-jam-track");
$purchasedJamTrackHeader = $purchasedJamTrack.find(".purchased-jam-track-header");
$purchasedJamTracks = $purchasedJamTrack.find(".purchased-list")

View File

@ -135,15 +135,7 @@
}
}
function displayTax(effectiveQuantity, item_tax, total_with_tax) {
var totalTax = 0;
var totalPrice = 0;
var unitTax = item_tax * effectiveQuantity;
totalTax += unitTax;
var totalUnitPrice = total_with_tax * effectiveQuantity;
totalPrice += totalUnitPrice;
function displayTax(totalTax, totalPrice) {
$screen.find('.order-right-page .order-items-value.taxes').text('$' + totalTax.toFixed(2))
$screen.find('.order-right-page .order-items-value.grand-total').text('$' + totalPrice.toFixed(2))
@ -181,8 +173,16 @@
taxRate = 0.0825;
}
var unitTax = 1.99 * taxRate;
displayTax(effectiveQuantity, unitTax, 1.99 + unitTax)
var estimatedTax = 0;
var estimatedTotal = 0;
context._.each(carts, function(cart) {
var cart_quantity = cart.product_info.quantity - cart.product_info.marked_for_redeem
estimatedTax += cart.product_info.price * cart_quantity * taxRate;
estimatedTotal += cart.product_info.price * cart_quantity;
})
displayTax(Math.round(estimatedTax*100)/100, Math.round((estimatedTotal + estimatedTax)*100)/100)
}
else {
checkoutUtils.configureRecurly()

View File

@ -95,7 +95,7 @@
$reuseExistingCardChk.iCheck(userDetail.reuse_card && userDetail.has_recurly_account ? 'check' : 'uncheck').attr('checked', userDetail.reuse_card)
// show appropriate prompt text based on whether user has a free jamtrack
if(user.free_jamtrack) {
if(user.has_redeemable_jamtrack) {
$freeJamTrackPrompt.removeClass('hidden')
}
else {

View File

@ -55,6 +55,16 @@ class CheckoutUtils
return carts[0].product_info.free
hasOnlyFreeItemsInShoppingCart: (carts) =>
if carts.length == 0
return false
for cart in carts
if !cart.product_info.free
return false
return true
configureRecurly: () =>
unless @configuredRecurly
context.recurly.configure(gon.global.recurly_public_api_key)

View File

@ -61,7 +61,10 @@
})
}
app.layout.showDialog('client-update')
if(!app.layout.isDialogShowing('client-update')) {
app.layout.showDialog('client-update')
}
//$('#client_update').show()
//$('#client_update_overlay').show()
}
@ -192,6 +195,11 @@
function runCheck(product, version, uri, size, currentVersion) {
if (app.clientUpdating) {
logger.debug("client is already updating; skipping")
return
}
if(currentVersion === undefined) {
currentVersion = context.jamClient.ClientUpdateVersion();
@ -302,7 +310,7 @@
$(document).on(EVENTS.SESSION_ENDED, function(e, data){
if(app.clientUpdating) {
updateClientUpdateDialog("update-start", { uri: updateUri })
updateClientUpdateDialog("update-start", { uri: updateUri})
}
});

View File

@ -50,7 +50,7 @@
$browserJamTrackBtn.click(function() {
app.layout.closeDialog('getting-started')
window.location = '/client#/jamtrack/search'
window.location = '/client#/jamtrack'
return false;
})
@ -69,9 +69,9 @@
function beforeShow() {
app.user().done(function(user) {
var jamtrackRule = user.free_jamtrack ? 'has-free-jamtrack' : 'no-free-jamtrack'
var jamtrackRule = user.has_redeemable_jamtrack ? 'has-free-jamtrack' : 'no-free-jamtrack'
$jamTrackSection.removeClass('has-free-jamtrack').removeClass('no-free-jamtrack').addClass(jamtrackRule)
if(user.free_jamtrack) {
if(user.has_redeemable_jamtrack) {
$jamTracksLimitedTime.removeClass('hidden')
}
})

View File

@ -369,8 +369,8 @@ context.JK.DownloadJamTrack = class DownloadJamTrack
@trackDetail = context.jamClient.JamTrackGetTrackDetail ("#{@jamTrack.id}-#{@sampleRateForFilename}")
if @trackDetail.version?
@logger.error("after invalidating package, the version is still wrong!")
throw "after invalidating package, the version is still wrong!"
@logger.error("after invalidating package, the version is still wrong!", @trackDetail)
throw "after invalidating package, the version is still wrong! #{@trackDetail.version}"
switch @trackDetail.key_state
when 'pending'

View File

@ -126,7 +126,8 @@
RECORDING_DONE :48, //the recording writer thread is done
VIDEO_WINDOW_OPENED :49, //video window opened
VIDEO_WINDOW_CLOSED :50,
LAST_ALERT : 51
VST_CHANGED: 51, // VST state changed
LAST_ALERT : 52
}
// recreate eThresholdType enum from MixerDialog.h
context.JK.ALERT_TYPES = {
@ -190,7 +191,8 @@
48: {"title": "", "message": ""}, // RECORDING_DONE
49: {"title": "", "message": ""}, // VIDEO_WINDOW_OPENED
50: {"title": "", "message": ""}, // VIDEO_WINDOW_CLOSED
51: {"title": "", "message": ""} // LAST_ALERT
51: {"title": "", "message": ""}, // VST_CHANGED
52: {"title": "", "message": ""} // LAST_ALERT
};
// add the alert's name to the ALERT_TYPES structure

View File

@ -1187,6 +1187,7 @@
});
deferred.done(function(user) {
context.JK.currentUserFreeJamTrack = user.show_free_jamtrack
window.UserActions.loaded(user)
})
@ -1791,12 +1792,28 @@
}
function addJamtrackToShoppingCart(options) {
return $.ajax({
var deferred = $.ajax({
type: "POST",
url: '/api/shopping_carts/add_jamtrack?' + $.param(options),
dataType: "json",
contentType: 'application/json'
});
deferred.done(function(response) {
window.UserActions.modify(response)
})
return deferred
}
function addGiftCardToShoppingCart(options) {
var deferred = $.ajax({
type: "POST",
url: '/api/shopping_carts/add_gift_card?' + $.param(options),
dataType: "json",
contentType: 'application/json'
});
return deferred
}
function getShoppingCarts() {
@ -1810,12 +1827,17 @@
}
function removeShoppingCart(options) {
return $.ajax({
var deferred = $.ajax({
type: "DELETE",
url: '/api/shopping_carts?' + $.param(options),
dataType: "json",
contentType: 'application/json'
})
deferred.done(function(response) {
window.UserActions.modify(response)
})
return deferred
}
function clearShoppingCart(options) {
@ -1986,6 +2008,17 @@
});
}
function redeemGiftCard(data) {
var id = getId(data);
return $.ajax({
type: "POST",
url: '/api/users/' + id + '/gift_cards',
dataType: "json",
contentType: 'application/json',
data: JSON.stringify(data),
});
}
function portOverCarts() {
return $.ajax({
type: "POST",
@ -2166,6 +2199,7 @@
this.enqueueJamTrack = enqueueJamTrack;
this.getBackingTracks = getBackingTracks;
this.addJamtrackToShoppingCart = addJamtrackToShoppingCart;
this.addGiftCardToShoppingCart = addGiftCardToShoppingCart;
this.getShoppingCarts = getShoppingCarts;
this.removeShoppingCart = removeShoppingCart;
this.clearShoppingCart = clearShoppingCart;
@ -2190,6 +2224,7 @@
this.playJamTrack = playJamTrack;
this.createSignupHint = createSignupHint;
this.createAlert = createAlert;
this.redeemGiftCard = redeemGiftCard;
this.signup = signup;
this.portOverCarts = portOverCarts;
return this;

View File

@ -1,484 +0,0 @@
$ = jQuery
context = window
context.JK ||= {}
context.JK.JamTrackScreen=class JamTrackScreen
LIMIT = 10
instrument_logo_map = context.JK.getInstrumentIconMap24()
constructor: (@app) ->
@EVENTS = context.JK.EVENTS
@logger = context.JK.logger
@screen = null
@content = null
@scroller = null
@genre = null
@artist = null
@instrument = null
@availability = null
@nextPager = null
@noMoreJamtracks = null
@currentPage = 0
@next = null
@currentQuery = this.defaultQuery()
@expanded = null
@shownHelperBubbles = false
beforeShow:(data) =>
this.setFilterFromURL()
if context.JK.currentUserId?
@app.user().done((user) =>
@user = user
this.refresh()
).fail((arg) =>
@logger.error("app.user.done failed: " + JSON.stringify(arg))
@logger.debug(arg.statusCode);
throw 'fail should not occur if user is available'
)
else
this.refresh()
unless @shownHelperBubbles
@shownHelperBubbles = true
@startHelperBubbles()
afterShow:(data) =>
context.JK.Tracking.jamtrackBrowseTrack(@app)
beforeHide: () =>
this.clearCtaHelpTimeout()
this.clearBandFilterHelpTimeout()
this.clearMasterHelpTimeout()
this.clearResults();
events:() =>
@genre.on 'change', this.search
@artist.on 'change', this.search
@instrument.on 'change', this.search
@availability.on 'change', this.search
clearResults:() =>
@currentPage = 0
@content.empty()
@noMoreJamtracks.hide()
@next = null
startHelperBubbles: () =>
@showBandFilterHelpTimeout = setTimeout(@showBandFilterHelp, 3500)
showBandFilterHelp: () =>
context.JK.HelpBubbleHelper.jamtrackBrowseBand(@artist.closest('.easydropdown-wrapper'), $('body'))
@showMasterHelpDueTime = new Date().getTime() + 11000 # 6000 ms for band tooltip to display, and 5 seconds of quiet time
@scroller.on('scroll', @masterHelpScrollWatch)
@scroller.on('scroll', @clearBubbles)
@showMasterHelpTimeout = setTimeout(@showMasterHelp, @masterHelpDueTime())
clearBubbles: () =>
if @helpBubble?
@helpBubble.btOff()
@helpBubble = null
# computes when we should show the master help bubble
masterHelpDueTime: () =>
dueTime = @showMasterHelpDueTime - new Date().getTime()
if dueTime <= 0
dueTime = 2000
dueTime
# computes when we should show the master help bubble
ctaHelpDueTime: () =>
dueTime = @showCtaHelpDueTime - new Date().getTime()
if dueTime <= 0
dueTime = 2000
dueTime
# if the user scrolls, reset the master help due time
masterHelpScrollWatch: () =>
@clearMasterHelpTimeout()
@showMasterHelpTimeout = setTimeout(@showMasterHelp, @masterHelpDueTime() + 2000)
# if the user scrolls, reset the master help due time
ctaHelpScrollWatch: () =>
@clearCtaHelpTimeout()
@showCtaHelpTimeout = setTimeout(@showCtaHelp, @ctaHelpDueTime() + 2000)
showCtaHelp: () =>
@scroller.off('scroll', @ctaHelpScrollWatch)
@clearCtaHelpTimeout()
cutoff = @scroller.offset().top;
@screen.find('.jamtrack-actions').each((i, element) =>
$element = $(element)
if ($element.offset().top >= cutoff)
@helpBubble = context.JK.HelpBubbleHelper.jamtrackBrowseCta($element, $('body'))
return false
else
return true
)
showMasterHelp: () =>
@scroller.off('scroll', @masterHelpScrollWatch)
@clearMasterHelpTimeout()
# don't show the help if the user has already clicked a preview
unless @userPreviewed
cutoff = @scroller.offset().top;
@screen.find('.jamtrack-preview[data-track-type="Master"]').each((i, element) =>
$element = $(element)
if ($element.offset().top >= cutoff)
@helpBubble = context.JK.HelpBubbleHelper.jamtrackBrowseMasterMix($element.find('.play-button'), $('body'))
return false
else
return true
)
@showCtaHelpDueTime = new Date().getTime() + 11000
@scroller.on('scroll', @ctaHelpScrollWatch)
@showCtaHelpTimeout = setTimeout(@showCtaHelp, @ctaHelpDueTime()) # 6000 ms for bubble show time, and 5000ms for delay
previewPlayed: () =>
@userPreviewed = true
clearCtaHelpTimeout:() =>
if @showCtaHelpTimeout?
clearTimeout(@showCtaHelpTimeout)
@showCtaHelpTimeout = null
clearBandFilterHelpTimeout: () =>
if @showBandFilterHelpTimeout?
clearTimeout(@showBandFilterHelpTimeout)
@showBandFilterHelpTimeout = null
clearMasterHelpTimeout: () =>
if @showMasterHelpTimeout?
clearTimeout(@showMasterHelpTimeout)
@showMasterHelpTimeout = null
setFilterFromURL:() =>
# Grab parms from URL for artist, instrument, and availability
parms=this.getParams()
if(parms.artist?)
@artist.val(parms.artist)
else
@artist.val('')
if(parms.instrument?)
@instrument.val(parms.instrument)
else
@instrument.val('')
if(parms.availability?)
@availability.val(parms.availability)
else
@availability.val('')
if window.history.replaceState #ie9 proofing
window.history.replaceState({}, "", "/client#/jamtrackBrowse")
getParams:() =>
params = {}
q = window.location.href.split("?")[1]
if q?
q = q.split('#')[0]
raw_vars = q.split("&")
for v in raw_vars
[key, val] = v.split("=")
params[key] = decodeURIComponent(val)
params
setFilterState: (state) =>
if state
@genre.easyDropDown('enable').removeAttr('disabled')
@artist.easyDropDown('enable').removeAttr('disabled')
@instrument.easyDropDown('enable').removeAttr('disabled')
@availability.easyDropDown('enable').removeAttr('disabled')
else
@genre.easyDropDown('disable').attr('disabled', 'disabled')
@artist.easyDropDown('disable').attr('disabled', 'disabled')
@instrument.easyDropDown('disable').attr('disabled', 'disabled')
@availability.easyDropDown('disable').attr('disabled', 'disabled')
refresh:() =>
this.clearResults()
@currentQuery = this.buildQuery()
that = this
this.setFilterState(false)
rest.getJamTracks(@currentQuery).done((response) =>
that.handleJamtrackResponse(response)
).fail( (jqXHR) =>
that.clearResults()
that.noMoreJamtracks.show()
that.app.notifyServerError jqXHR, 'Jamtrack Unavailable'
).always () =>
that.setFilterState(true)
search:() =>
this.refresh()
false
defaultQuery:() =>
query =
per_page: LIMIT
page: @currentPage+1
if @next
query.since = @next
query
buildQuery:() =>
@currentQuery = this.defaultQuery()
# genre filter
# var genres = @screen.find('#jamtrack_genre').val()
# if (genres !== undefined) {
# @currentQuery.genre = genres
# }
# instrument filter
instrument = @instrument.val()
if instrument?
@currentQuery.instrument = instrument
# artist filter
art = @artist.val()
if art?
@currentQuery.artist = art
# availability filter
availability = @availability.val()
if availability?
@currentQuery.availability = availability
@currentQuery
handleJamtrackResponse:(response) =>
@next = response.next
this.renderJamtracks(response)
if response.next == null
# if we less results than asked for, end searching
@scroller.infinitescroll 'pause'
if @currentPage == 0 and response.jamtracks.length == 0
@content.append '<td colspan="3" class="no-jamtracks-msg\'>No JamTracks found.</div>'
if @currentPage > 0
@noMoreJamtracks.show()
# there are bugs with infinitescroll not removing the 'loading'.
# it's most noticeable at the end of the list, so whack all such entries
$('.infinite-scroll-loader').remove()
else
@currentPage++
this.buildQuery()
this.registerInfiniteScroll()
registerInfiniteScroll:() =>
that = this
@scroller.infinitescroll {
behavior: 'local'
navSelector: '#jamtrackScreen .btn-next-pager'
nextSelector: '#jamtrackScreen .btn-next-pager'
binder: @scroller
dataType: 'json'
appendCallback: false
prefill: false
bufferPx: 100
loading:
msg: $('<div class="infinite-scroll-loader">Loading ...</div>')
img: '/assets/shared/spinner.gif'
path: (page) =>
'/api/jamtracks?' + $.param(that.buildQuery())
}, (json, opts) =>
this.handleJamtrackResponse(json)
@scroller.infinitescroll 'resume'
playJamtrack:(e) =>
e.preventDefault()
addToCartJamtrack:(e) =>
e.preventDefault()
$target = $(e.target)
params = id: $target.attr('data-jamtrack-id')
isFree = $(e.target).is('.is_free')
rest.addJamtrackToShoppingCart(params).done((response) =>
if(isFree)
if context.JK.currentUserId?
context.JK.currentUserFreeJamTrack = true # make sure the user sees no more free notices
context.location = '/client#/redeemComplete'
else
# now make a rest call to buy it
context.location = '/client#/redeemSignup'
else
context.location = '/client#/shoppingCart'
).fail @app.ajaxError
licenseUSWhy:(e) =>
e.preventDefault()
@app.layout.showDialog 'jamtrack-availability-dialog'
handleExpanded:(trackElement) =>
jamTrack = trackElement.data('jamTrack')
expanded = trackElement.data('expanded')
expand = !expanded
trackElement.data('expanded', expand)
detailArrow = trackElement.find('.jamtrack-detail-btn')
if expand
trackElement.find('.extra').removeClass('hidden')
detailArrow.html('hide tracks <a class="details-arrow arrow-up"></a>')
for track in jamTrack.tracks
trackElement.find("[jamtrack-track-id='#{track.id}']").removeClass('hidden')
else
trackElement.find('.extra').addClass('hidden')
detailArrow.html('show all tracks <a class="details-arrow arrow-down"></a>')
count = 0
for track in jamTrack.tracks
if count < 6
trackElement.find("[jamtrack-track-id='#{track.id}']").removeClass('hidden')
else
trackElement.find("[jamtrack-track-id='#{track.id}']").addClass('hidden')
count++
registerEvents:(parent) =>
#@screen.find('.jamtrack-detail-btn').on 'click', this.showJamtrackDescription
parent.find('.play-button').on 'click', this.playJamtrack
parent.find('.jamtrack-add-cart').on 'click', this.addToCartJamtrack
parent.find('.license-us-why').on 'click', this.licenseUSWhy
parent.find('.jamtrack-detail-btn').on 'click', this.toggleExpanded
# @screen.find('.jamtrack-preview').each (index, element) =>
# new JK.JamTrackPreview(data.app, $element, jamTrack, track, {master_shows_duration: true})
rerenderJamtracks:() =>
if @currentData?
@clearResults()
@renderJamtracks(@currentData)
false
computeWeight: (jam_track_track, instrument) =>
weight = switch
when jam_track_track.track_type == 'Master' then 0
when jam_track_track.instrument?.id == instrument then 1 + jam_track_track.position
else 10000 + jam_track_track.position
renderJamtracks:(data) =>
@currentData = data
that = this
for jamtrack in data.jamtracks
jamtrackExpanded = this.expanded==jamtrack.id
trackRow = _.clone(jamtrack)
trackRow.track_cnt = jamtrack.tracks.length
trackRow.tracks = []
# if an instrument is selected by the user, then re-order any jam tracks with a matching instrument to the top
instrument = @instrument.val()
if instrument?
jamtrack.tracks.sort((a, b) =>
aWeight = @computeWeight(a, instrument)
bWeight = @computeWeight(b, instrument)
return aWeight - bWeight
)
for track in jamtrack.tracks
trackRow.tracks.push(track)
if track.track_type=='Master'
track.instrument_desc = "Master"
else
inst = '../assets/content/icon_instrument_default24.png'
if track.instrument?
if track.instrument.id in instrument_logo_map
inst = instrument_logo_map[track.instrument.id].asset
track.instrument_desc = track.instrument.description
track.instrument_url = inst
if track.part != ''
track.instrument_desc += ' (' + track.part + ')'
free_state = if context.JK.currentUserFreeJamTrack then 'free' else 'non-free'
is_free = free_state == 'free'
options =
jamtrack: trackRow
expanded: false
free_state: free_state,
is_free: is_free
@jamtrackItem = $(context._.template($('#template-jamtrack').html(), options, variable: 'data'))
that.renderJamtrack(@jamtrackItem, jamtrack)
that.registerEvents(@jamtrackItem)
renderJamtrack:(jamtrackElement, jamTrack) =>
jamtrackElement.data('jamTrack', jamTrack)
jamtrackElement.data('expanded', true)
@content.append jamtrackElement
#if this.expanded==jamTrack.id
for track in jamTrack.tracks
trackRow = jamtrackElement.find("[jamtrack-track-id='#{track.id}']")
previewElement = trackRow.find(".jamtrack-preview")
preview = new JK.JamTrackPreview(@app, previewElement, jamTrack, track, {master_shows_duration: true, color:'gray'})
$(preview).on(@EVENTS.PREVIEW_PLAYED, @previewPlayed)
this.handleExpanded(jamtrackElement, false)
showJamtrackDescription:(e) =>
e.preventDefault()
@description = $(e.target).parent('.detail-arrow').next()
if @description.css('display') == 'none'
@description.show()
else
@description.hide()
toggleExpanded:(e) =>
e.preventDefault()
jamtrackRecord = $(e.target).parents('.jamtrack-record')
jamTrackId = jamtrackRecord.attr("jamtrack-id")
this.handleExpanded(jamtrackRecord)
initialize:() =>
screenBindings =
'beforeShow': this.beforeShow
'afterShow': this.afterShow
'beforeHide' : this.beforeHide
@app.bindScreen 'jamtrackBrowse', screenBindings
@screen = $('#jamtrack-find-form')
@scroller = @screen.find('.content-body-scroller')
@content = @screen.find('.jamtrack-content')
@genre = @screen.find('#jamtrack_genre')
@artist = @screen.find('#jamtrack_artist')
@instrument = @screen.find('#jamtrack_instrument')
@availability = @screen.find('#jamtrack_availability')
@nextPager = @screen.find('a.btn-next-pager')
@noMoreJamtracks = @screen.find('.end-of-jamtrack-list')
if @screen.length == 0
throw new Error('@screen must be specified')
if @scroller.length == 0
throw new Error('@scroller must be specified')
if @content.length == 0
throw new Error('@content must be specified')
if @noMoreJamtracks.length == 0
throw new Error('@noMoreJamtracks must be specified')
#if(@genre.length == 0) throw new Error("@genre must be specified")
if @artist.length == 0
throw new Error('@artist must be specified')
if @instrument.length == 0
throw new Error('@instrument must be specified')
#if @availability.length == 0
# throw new Error('@availability must be specified')
this.events()

View File

@ -1,80 +0,0 @@
$ = jQuery
context = window
context.JK ||= {}
context.JK.JamTrackLanding = class JamTrackLanding
constructor: (@app) ->
@rest = context.JK.Rest()
@client = context.jamClient
@logger = context.JK.logger
@screen = null
@noFreeJamTrack = null
@freeJamTrack = null
@bandList = null
@noBandsFound = null
initialize:() =>
screenBindings =
'beforeShow': @beforeShow
'afterShow': @afterShow
#@app.bindScreen('jamtrackLanding', screenBindings)
@screen = $('#jamtrackLanding')
@noFreeJamTrack = @screen.find('.no-free-jamtrack')
@freeJamTrack = @screen.find('.free-jamtrack')
@bandList = @screen.find('#band_list')
@noBandsFound = @screen.find('#no_bands_found')
beforeShow:() =>
@noFreeJamTrack.addClass('hidden')
@freeJamTrack.addClass('hidden')
afterShow:() =>
if context.JK.currentUserId
@app.user().done(@onUser)
else
@onUser({free_jamtrack: gon.global.one_free_jamtrack_per_user})
onUser:(user) =>
if user.free_jamtrack
@freeJamTrack.removeClass('hidden')
else
@noFreeJamTrack.removeClass('hidden')
# Get artist names and build links
@rest.getJamTrackArtists({group_artist: true, per_page:100})
.done(this.buildArtistLinks)
.fail(this.handleFailure)
# Bind links to action that will open the jam_tracks list view filtered to given artist_name:
# artist_name
this.bindArtistLinks()
buildArtistLinks:(response) =>
# Get artist names and build links
@logger.debug("buildArtest links response", response)
artists = response.artists
$("#band_list>li:not('#no_bands_found')").remove()
if artists.length==0
@noBandsFound.removeClass("hidden")
else
@noBandsFound.addClass("hidden")
# client#/jamtrack
for artist in artists
artistLink = "<a href='client?artist=#{encodeURIComponent(artist.original_artist)}#/jamtrack/search' class='artist-link' artist='#{artist.original_artist}'>#{artist.original_artist} (#{artist.song_count})</a>"
@bandList.append("<li>#{artistLink}</li>")
# We don't want to do a full page load if this is clicked on here:
bindArtistLinks:() =>
that=this
@bandList.on "click", "a.artist-link", (event)->
context.location="client#/jamtrack/search"
if window.history.replaceState # ie9 proofing
window.history.replaceState({}, "", this.href)
event.preventDefault()
handleFailure:(error) =>

View File

@ -1,670 +0,0 @@
(function(context,$) {
"use strict";
context.JK = context.JK || {};
context.JK.OrderScreen = function(app) {
var EVENTS = context.JK.EVENTS;
var logger = context.JK.logger;
var $screen = null;
var $templateOrderContent = null;
var $templatePurchasedJamTrack = null;
var $navigation = null;
var $billingInfo = null;
var $shippingInfo = null;
var $paymentMethod = null;
var $shippingAddress = null;
var $shippingAsBilling = null;
var $paymentInfoPanel = null;
var $orderPanel = null;
var $thanksPanel = null;
var $jamTrackInBrowser = null;
var $purchasedJamTrack = null;
var $purchasedJamTrackHeader = null;
var $purchasedJamTracks = null;
var $orderContent = null;
var userDetail = null;
var step = null;
var billing_info = null;
var shipping_info = null;
var shipping_as_billing = null;
var downloadJamTracks = [];
var purchasedJamTracks = null;
var purchasedJamTrackIterator = 0;
function beforeShow() {
beforeShowPaymentInfo();
resetJamTrackDownloadInfo();
}
function beforeShowPaymentInfo() {
step = 2;
renderNavigation();
renderAccountInfo();
$("#order_error").addClass("hidden")
}
function resetJamTrackDownloadInfo() {
$purchasedJamTrack.addClass('hidden');
$purchasedJamTracks.children().remove()
$jamTrackInBrowser.hide('hidden');
}
function renderAccountInfo() {
rest.getUserDetail()
.done(populateAccountInfo)
.error(app.ajaxError);
}
function populateAccountInfo(user) {
userDetail = user;
if (userDetail.has_recurly_account) {
rest.getBillingInfo()
.done(function(response) {
$billingInfo.find("#billing-first-name").val(response.first_name);
$billingInfo.find("#billing-last-name").val(response.last_name);
$billingInfo.find("#billing-address1").val(response.address1);
$billingInfo.find("#billing-address2").val(response.address2);
$billingInfo.find("#billing-city").val(response.city);
$billingInfo.find("#billing-state").val(response.state);
$billingInfo.find("#billing-zip").val(response.zip);
$billingInfo.find("#billing-country").val(response.country);
$shippingAddress.find("#shipping-first-name").val(response.first_name);
$shippingAddress.find("#shipping-last-name").val(response.last_name);
$shippingAddress.find("#shipping-address1").val(response.address1);
$shippingAddress.find("#shipping-address2").val(response.address2);
$shippingAddress.find("#shipping-city").val(response.city);
$shippingAddress.find("#shipping-state").val(response.state);
$shippingAddress.find("#shipping-zip").val(response.zip);
$shippingAddress.find("#shipping-country").val(response.country);
})
.error(app.ajaxError);
}
else {
$billingInfo.find("#billing-first-name").val(userDetail.first_name);
$billingInfo.find("#billing-last-name").val(userDetail.last_name);
$billingInfo.find("#billing-city").val(userDetail.city);
$billingInfo.find("#billing-state").val(userDetail.state);
$billingInfo.find("#billing-country").val(userDetail.country);
$shippingAddress.find("#shipping-first-name").val(userDetail.first_name);
$shippingAddress.find("#shipping-last-name").val(userDetail.last_name);
$shippingAddress.find("#shipping-city").val(userDetail.city);
$shippingAddress.find("#shipping-state").val(userDetail.state);
$shippingAddress.find("#shipping-country").val(userDetail.country);
}
}
function afterShow(data) {
// XXX : style-test code
// moveToThanks({jam_tracks: [{id: 14, jam_track_right_id: 11, name: 'Back in Black'}, {id: 15, jam_track_right_id: 11, name: 'In Bloom'}, {id: 16, jam_track_right_id: 11, name: 'Love Bird Supreme'}]});
}
function beforeHide() {
if(downloadJamTracks) {
context._.each(downloadJamTracks, function(downloadJamTrack) {
downloadJamTrack.destroy();
downloadJamTrack.root.remove();
})
downloadJamTracks = [];
}
purchasedJamTracks = null;
purchasedJamTrackIterator = 0;
}
// TODO: Refactor: this function is long and fraught with many return points.
function next(e) {
e.preventDefault();
$("#order_error").addClass("hidden")
// validation
var billing_first_name = $billingInfo.find("#billing-first-name").val();
var billing_last_name = $billingInfo.find("#billing-last-name").val();
var billing_address1 = $billingInfo.find("#billing-address1").val();
var billing_address2 = $billingInfo.find("#billing-address2").val();
var billing_city = $billingInfo.find("#billing-city").val();
var billing_state = $billingInfo.find("#billing-state").val();
var billing_zip = $billingInfo.find("#billing-zip").val();
var billing_country = $billingInfo.find("#billing-country").val();
if (!billing_first_name) {
$billingInfo.find('#divBillingFirstName .error-text').remove();
$billingInfo.find('#divBillingFirstName').addClass("error");
$billingInfo.find('#billing-first-name').after("<ul class='error-text'><li>First Name is required</li></ul>");
return false;
}
else {
$billingInfo.find('#divBillingFirstName').removeClass("error");
}
if (!billing_last_name) {
$billingInfo.find('#divBillingLastName .error-text').remove();
$billingInfo.find('#divBillingLastName').addClass("error");
$billingInfo.find('#billing-last-name').after("<ul class='error-text'><li>Last Name is required</li></ul>");
return false;
}
else {
$billingInfo.find('#divBillingLastName').removeClass("error");
}
if (!billing_address1) {
$billingInfo.find('#divBillingAddress1 .error-text').remove();
$billingInfo.find('#divBillingAddress1').addClass("error");
$billingInfo.find('#billing-address1').after("<ul class='error-text'><li>Address is required</li></ul>");
return false;
}
else {
$billingInfo.find('#divBillingAddress1').removeClass("error");
}
if (!billing_zip) {
$billingInfo.find('#divBillingZip .error-text').remove();
$billingInfo.find('#divBillingZip').addClass("error");
$billingInfo.find('#billing-zip').after("<ul class='error-text'><li>Zip code is required</li></ul>");
return false;
}
else {
$billingInfo.find('#divBillingZip').removeClass("error");
}
if (!billing_state) {
$billingInfo.find('#divBillingState .error-text').remove();
$billingInfo.find('#divBillingState').addClass("error");
$billingInfo.find('#billing-zip').after("<ul class='error-text'><li>State is required</li></ul>");
return false;
}
else {
$billingInfo.find('#divBillingState').removeClass("error");
}
if (!billing_city) {
$billingInfo.find('#divBillingCity .error-text').remove();
$billingInfo.find('#divBillingCity').addClass("error");
$billingInfo.find('#billing-city').after("<ul class='error-text'><li>City is required</li></ul>");
return false;
}
else {
$billingInfo.find('#divBillingCity').removeClass("error");
}
if (!billing_country) {
$billingInfo.find('#divBillingCountry .error-text').remove();
$billingInfo.find('#divBillingCountry').addClass("error");
$billingInfo.find('#billing-country').after("<ul class='error-text'><li>Country is required</li></ul>");
return false;
}
else {
$billingInfo.find('#divBillingCountry').removeClass("error");
}
shipping_as_billing = $shippingAsBilling.is(":checked");
var shipping_first_name, shipping_last_name, shipping_address1, shipping_address2;
var shipping_city, shipping_state, shipping_zip, shipping_country;
if (!shipping_as_billing) {
shipping_first_name = $shippingAddress.find("#shipping-first-name").val();
shipping_last_name = $shippingAddress.find("#shipping-last-name").val();
shipping_address1 = $shippingAddress.find("#shipping-address1").val();
shipping_address2 = $shippingAddress.find("#shipping-address2").val();
shipping_city = $shippingAddress.find("#shipping-city").val();
shipping_state = $shippingAddress.find("#shipping-state").val();
shipping_zip = $shippingAddress.find("#shipping-zip").val();
shipping_country = $shippingAddress.find("#shipping-country").val();
if (!shipping_first_name) {
$shippingAddress.find('#divShippingFirstName .error-text').remove();
$shippingAddress.find('#divShippingFirstName').addClass("error");
$shippingAddress.find('#shipping-first-name').after("<ul class='error-text'><li>First Name is required</li></ul>");
return false;
}
else {
$shippingInfo.find('#divShippingFirstName').removeClass("error");
}
if (!shipping_last_name) {
$shippingAddress.find('#divShippingLastName .error-text').remove();
$shippingAddress.find('#divShippingLastName').addClass("error");
$shippingAddress.find('#shipping-last-name').after("<ul class='error-text'><li>Last Name is required</li></ul>");
return false;
}
else {
$shippingInfo.find('#divShippingLastName').removeClass("error");
}
if (!shipping_address1) {
$shippingAddress.find('#divShippingAddress1 .error-text').remove();
$shippingAddress.find('#divShippingAddress1').addClass("error");
$shippingAddress.find('#shipping-address1').after("<ul class='error-text'><li>Address is required</li></ul>");
return false;
}
else {
$shippingInfo.find('#divShippingAddress1').removeClass("error");
}
if (!shipping_zip) {
$shippingAddress.find('#divShippingZip .error-text').remove();
$shippingAddress.find('#divShippingZip').addClass("error");
$shippingAddress.find('#shipping-zip').after("<ul class='error-text'><li>Zip code is required</li></ul>");
return false;
}
else {
$shippingInfo.find('#divShippingZip').removeClass("error");
}
if (!shipping_state) {
$shippingAddress.find('#divShippingState .error-text').remove();
$shippingAddress.find('#divShippingState').addClass("error");
$shippingAddress.find('#shipping-zip').after("<ul class='error-text'><li>State is required</li></ul>");
return false;
}
else {
$shippingInfo.find('#divShippingState').removeClass("error");
}
if (!shipping_city) {
$shippingAddress.find('#divShippingCity .error-text').remove();
$shippingAddress.find('#divShippingCity').addClass("error");
$shippingAddress.find('#shipping-city').after("<ul class='error-text'><li>City is required</li></ul>");
return false;
}
else {
$shippingInfo.find('#divShippingCity').removeClass("error");
}
if (!shipping_country) {
$shippingAddress.find('#divShippingCountry .error-text').remove();
$shippingAddress.find('#divShippingCountry').addClass("error");
$shippingAddress.find('#shipping-country').after("<ul class='error-text'><li>Country is required</li></ul>");
return false;
}
else {
$shippingAddress.find('#divShippingCountry').removeClass("error");
}
}
var card_name = $paymentMethod.find("#card-name").val();
var card_number = $paymentMethod.find("#card-number").val();
var card_year = $paymentMethod.find("#card_expire-date_1i").val();
var card_month = $paymentMethod.find("#card_expire-date_2i").val();
var card_verify = $paymentMethod.find("#card-verify").val();
if (!card_name) {
$paymentMethod.find('#divCardName .error-text').remove();
$paymentMethod.find('#divCardName').addClass("error");
$paymentMethod.find('#card-name').after("<ul class='error-text'><li>Card Name is required</li></ul>");
return false;
} else {
$paymentMethod.find('#divCardName').removeClass("error");
}
if (!card_number) {
$paymentMethod.find('#divCardNumber .error-text').remove();
$paymentMethod.find('#divCardNumber').addClass("error");
$paymentMethod.find('#card-number').after("<ul class='error-text'><li>Card Number is required</li></ul>");
return false;
} else if (!$.payment.validateCardNumber(card_number)) {
$paymentMethod.find('#divCardNumber .error-text').remove();
$paymentMethod.find('#divCardNumber').addClass("error");
$paymentMethod.find('#card-number').after("<ul class='error-text'><li>Card Number is not valid</li></ul>");
return false;
} else {
$paymentMethod.find('#divCardNumber').removeClass("error");
}
if (!$.payment.validateCardExpiry(card_month, card_year)) {
$paymentMethod.find('#divCardExpiry .error-text').remove();
$paymentMethod.find('#divCardExpiry').addClass("error");
$paymentMethod.find('#card-expiry').after("<ul class='error-text'><li>Card Number is not valid</li></ul>");
} else {
$paymentMethod.find('#divCardExpiry').removeClass("error");
}
if (!card_verify) {
$paymentMethod.find('#divCardVerify .error-text').remove();
$paymentMethod.find('#divCardVerify').addClass("error");
$paymentMethod.find('#card-verify').after("<ul class='error-text'><li>Card Verification Value is required</li></ul>");
return false;
} else if(!$.payment.validateCardCVC(card_verify)) {
$paymentMethod.find('#divCardVerify .error-text').remove();
$paymentMethod.find('#divCardVerify').addClass("error");
$paymentMethod.find('#card-verify').after("<ul class='error-text'><li>Card Verification Value is not valid.</li></ul>");
return false;
} else {
$paymentMethod.find('#divCardVerify').removeClass("error");
}
billing_info = {};
shipping_info = {};
billing_info.first_name = billing_first_name;
billing_info.last_name = billing_last_name;
billing_info.address1 = billing_address1;
billing_info.address2 = billing_address2;
billing_info.city = billing_city;
billing_info.state = billing_state;
billing_info.country = billing_country;
billing_info.zip = billing_zip;
billing_info.number = card_number;
billing_info.month = card_month;
billing_info.year = card_year;
billing_info.verification_value = card_verify;
if (shipping_as_billing) {
shipping_info = $.extend({},billing_info);
delete shipping_info.number;
delete shipping_info.month;
delete shipping_info.year;
delete shipping_info.verification_value;
} else {
shipping_info.first_name = shipping_first_name;
shipping_info.last_name = shipping_last_name;
shipping_info.address1 = shipping_address1;
shipping_info.address2 = shipping_address2;
shipping_info.city = shipping_city;
shipping_info.state = shipping_state;
shipping_info.country = shipping_country;
shipping_info.zip = shipping_zip;
}
$paymentInfoPanel.find("#payment-info-next").addClass("disabled");
$paymentInfoPanel.find("#payment-info-next").off("click");
rest.createRecurlyAccount({billing_info: billing_info})
.done(function() {
moveToOrder();
$paymentInfoPanel.find("#payment-info-next").removeClass("disabled");
$paymentInfoPanel.find("#payment-info-next").on("click", next);
})
.fail(errorHandling);
}
function errorHandling(xhr, ajaxOptions, thrownError) {
$.each(xhr.responseJSON.errors, function(key, error) {
if (key == 'number') {
$paymentMethod.find('#divCardNumber .error-text').remove();
$paymentMethod.find('#divCardNumber').addClass("error");
$paymentMethod.find('#card-number').after("<ul class='error-text'><li>" + error + "</li></ul>");
}
else if (key == 'verification_value') {
$paymentMethod.find('#divCardVerify .error-text').remove();
$paymentMethod.find('#divCardVerify').addClass("error");
$paymentMethod.find('#card-verify').after("<ul class='error-text'><li>" + error + "</li></ul>");
}
});
$paymentInfoPanel.find("#payment-info-next").addClass("disabled");
$paymentInfoPanel.find("#payment-info-next").on('click', next);
}
function orderErrorHandling(xhr, ajaxOptions, thrownError) {
var message = "Error submitting payment: "
$.each(xhr.responseJSON.errors, function(key, error) {
message += key + ": " + error
})
$("#order_error").text(message)
$("#order_error").removeClass("hidden")
$orderContent.find(".place-order").on('click', placeOrder)
}
function beforeShowOrder() {
step = 3;
renderNavigation();
populateOrderPage();
}
function clearOrderPage() {
$orderContent.empty();
}
function populateOrderPage() {
clearOrderPage();
rest.getShoppingCarts()
.done(renderOrderPage)
.fail(app.ajaxError);
}
function renderOrderPage(carts) {
var data = {}
var sub_total = 0.0
var taxes = 0.0
$.each(carts, function(index, cart) {
sub_total += parseFloat(cart.product_info.price) * parseFloat(cart.quantity)
});
data.grand_total = (sub_total + taxes).toFixed(2)
data.sub_total = sub_total.toFixed(2)
data.taxes = taxes.toFixed(2)
data.carts = carts
data.billing_info = billing_info
data.shipping_info = shipping_info
data.shipping_as_billing = shipping_as_billing
var orderContentHtml = $(
context._.template(
$templateOrderContent.html(),
data,
{variable: 'data'}
)
)
$orderContent.append(orderContentHtml)
$orderPanel.find(".change-payment-info").on('click', moveToPaymentInfo)
$orderContent.find(".place-order").on('click', placeOrder)
}
function moveToOrder() {
$paymentInfoPanel.addClass("hidden");
$orderPanel.removeClass("hidden");
beforeShowOrder();
}
function moveToThanks(purchaseResponse) {
$("#order_error").addClass("hidden")
$paymentInfoPanel.addClass("hidden")
$orderPanel.addClass("hidden")
$thanksPanel.removeClass("hidden")
rest.clearShoppingCart()
beforeShowOrder()
handleJamTracksPurchased(purchaseResponse.jam_tracks)
}
function handleJamTracksPurchased(jamTracks) {
// were any JamTracks purchased?
var jamTracksPurchased = jamTracks && jamTracks.length > 0;
if(jamTracksPurchased) {
if(gon.isNativeClient) {
startDownloadJamTracks(jamTracks)
}
else {
$jamTrackInBrowser.removeClass('hidden');
}
}
}
function startDownloadJamTracks(jamTracks) {
// there can be multiple purchased JamTracks, so we cycle through them
purchasedJamTracks = jamTracks;
// populate list of jamtracks purchased, that we will iterate through graphically
context._.each(jamTracks, function(jamTrack) {
var downloadJamTrack = new context.JK.DownloadJamTrack(app, jamTrack, 'small');
var $purchasedJamTrack = $(context._.template(
$templatePurchasedJamTrack.html(),
jamTrack,
{variable: 'data'}
));
$purchasedJamTracks.append($purchasedJamTrack)
// show it on the page
$purchasedJamTrack.append(downloadJamTrack.root)
downloadJamTracks.push(downloadJamTrack)
})
iteratePurchasedJamTracks();
}
function iteratePurchasedJamTracks() {
if(purchasedJamTrackIterator < purchasedJamTracks.length ) {
var downloadJamTrack = downloadJamTracks[purchasedJamTrackIterator++];
// make sure the 'purchasing JamTrack' section can be seen
$purchasedJamTrack.removeClass('hidden');
// the widget indicates when it gets to any transition; we can hide it once it reaches completion
$(downloadJamTrack).on(EVENTS.JAMTRACK_DOWNLOADER_STATE_CHANGED, function(e, data) {
if(data.state == downloadJamTrack.states.synchronized) {
logger.debug("jamtrack " + downloadJamTrack.jamTrack.name + " synchronized;")
//downloadJamTrack.root.remove();
downloadJamTrack.destroy();
// go to the next JamTrack
iteratePurchasedJamTracks()
}
})
logger.debug("jamtrack " + downloadJamTrack.jamTrack.name + " downloader initializing")
// kick off the download JamTrack process
downloadJamTrack.init()
// XXX style-test code
// downloadJamTrack.transitionError("package-error", "The server failed to create your package.")
}
else {
logger.debug("done iterating over purchased JamTracks")
$purchasedJamTrackHeader.text('All purchased JamTracks have been downloaded successfully! You can now play them in a session.')
}
}
function moveToPaymentInfo(e) {
e.preventDefault();
$paymentInfoPanel.removeClass("hidden");
$orderPanel.addClass("hidden");
beforeShowPaymentInfo();
}
function toggleShippingAsBilling(e) {
e.preventDefault();
var shipping_as_billing = $(e.target).is(':checked');
if (!shipping_as_billing) {
$shippingAddress.removeClass("hidden");
}
else {
$shippingAddress.addClass("hidden");
}
}
function placeOrder(e) {
e.preventDefault();
$orderContent.find(".place-order").off('click')
rest.getShoppingCarts()
.done(function(carts) {
var jam_track_ids = _.map(carts, function(cart){
return cart.product_info.product_id
})
rest.placeOrder({jam_tracks: jam_track_ids})
.done(moveToThanks)
.fail(orderErrorHandling);
}
).fail(app.ajaxError);
}
function events() {
$paymentInfoPanel.find("#payment-info-next").on('click', next);
$shippingAsBilling.on('ifChanged', toggleShippingAsBilling);
}
function reset() {
}
function renderNavigation() {
$navigation.html("");
var navigationHtml = $(
context._.template(
$('#template-checkout-navigation').html(),
{current: step},
{variable: 'data'}
)
);
$navigation.append(navigationHtml);
}
function initializeControls() {
$("form.payment-info").iCheck({
checkboxClass: 'icheckbox_minimal',
radioClass: 'iradio_minimal',
inheritClass: true
});
// Use jquery.payment to limit characters and length:
$paymentMethod.find("#card-number").payment('formatCardNumber');
$paymentMethod.find("#card-verify").payment('formatCardCVC');
}
function initialize() {
var screenBindings = {
'beforeShow': beforeShow,
'afterShow': afterShow,
'beforeHide' : beforeHide
};
app.bindScreen('order', screenBindings);
$screen = $("#orderScreen");
$templateOrderContent = $("#template-order-content");
$templatePurchasedJamTrack = $('#template-purchased-jam-track');
$paymentInfoPanel = $screen.find(".checkout-payment-info");
$orderPanel = $screen.find(".order-panel");
$thanksPanel = $screen.find(".thanks-panel");
$jamTrackInBrowser = $screen.find(".thanks-detail.jam-tracks-in-browser");
$purchasedJamTrack = $thanksPanel.find(".thanks-detail.purchased-jam-track");
$purchasedJamTrackHeader = $purchasedJamTrack.find(".purchased-jam-track-header");
$purchasedJamTracks = $purchasedJamTrack.find(".purchased-list")
$navigation = $screen.find(".checkout-navigation-bar");
$billingInfo = $paymentInfoPanel.find(".billing-address");
$shippingInfo = $paymentInfoPanel.find(".shipping-address");
$paymentMethod = $paymentInfoPanel.find(".payment-method");
$shippingAddress = $paymentInfoPanel.find(".shipping-address-detail");
$shippingAsBilling = $paymentInfoPanel.find("#shipping-as-billing");
$orderContent = $orderPanel.find(".order-content");
if($screen.length == 0) throw "$screen must be specified";
if($navigation.length == 0) throw "$navigation must be specified";
initializeControls();
events();
}
this.initialize = initialize;
return this;
}
})(window,jQuery);

View File

@ -4,17 +4,11 @@ MIX_MODES = context.JK.MIX_MODES
@JamTrackFilterScreen = React.createClass({
mixins: [Reflux.listenTo(@AppStore,"onAppInit")]
mixins: [Reflux.listenTo(@AppStore,"onAppInit"), Reflux.listenTo(@UserStore,"onUserChanged")]
LIMIT: 20
instrument_logo_map: context.JK.getInstrumentIconMap24()
computeWeight: (jam_track_track, instrument) ->
weight = switch
when jam_track_track.track_type == 'Master' then 0
when jam_track_track.instrument?.id == instrument then 1 + jam_track_track.position
else 10000 + jam_track_track.position
render: () ->
searchText = if @state.first_search then 'SEARCH' else 'SEARCH AGAIN'
@ -36,19 +30,21 @@ MIX_MODES = context.JK.MIX_MODES
)
for track in jamtrack.tracks
trackRow.tracks.push(track)
if track.track_type=='Master'
track.instrument_desc = "Master"
else
inst = '../assets/content/icon_instrument_default24.png'
if track.instrument?
if track.instrument.id in @instrument_logo_map
inst = @instrument_logo_map[track.instrument.id].asset
track.instrument_desc = track.instrument.description
track.instrument_url = inst
if track.part != ''
track.instrument_desc += ' (' + track.part + ')'
if track.track_type == 'Master' || track.track_type == 'Track'
trackRow.tracks.push(track)
if track.track_type == 'Master'
track.instrument_desc = "Master"
else if track.track_type == 'Track'
inst = '../assets/content/icon_instrument_default24.png'
if track.instrument?
if track.instrument.id in @instrument_logo_map
inst = @instrument_logo_map[track.instrument.id].asset
track.instrument_desc = track.instrument.description
track.instrument_url = inst
if track.part != ''
track.instrument_desc += ' (' + track.part + ')'
trackRow.free_state = if @state.is_free then 'free' else 'non-free'
@ -76,10 +72,10 @@ MIX_MODES = context.JK.MIX_MODES
</div>`
actionBtn = null
if jamtrack.is_free
actionBtn = `<a className="jamtrack-add-cart button-orange is_free" href="#" data-jamtrack-id={jamtrack.id}> GET IT FREE!</a>`
else if jamtrack.purchased
if jamtrack.purchased
actionBtn = `<a className="jamtrack-add-cart-disabled button-grey button-disabled" href="javascript:void(0)">PURCHASED</a>`
else if jamtrack.is_free
actionBtn = `<a className="jamtrack-add-cart button-orange is_free" href="#" data-jamtrack-id={jamtrack.id}> GET IT FREE!</a>`
else if jamtrack.added_cart
actionBtn = `<a className="jamtrack-add-cart-disabled button-grey button-disabled" href="client#/shoppingCart">ALREADY IN CART</a>`
else
@ -158,15 +154,13 @@ MIX_MODES = context.JK.MIX_MODES
</div>
</div>`
getInitialState: () ->
{search: '', type: 'user-input', jamtracks:[], show_all_artists: false, currentPage: 0, next: null, searching: false, count: 0, is_free: context.JK.currentUserFreeJamTrack}
clearResults:() ->
#@content.empty()
#@noMoreJamtracks.hide()
@setState({currentPage: 0, next: null, jamtracks:[], type: 'user-input', searching:false, is_free: context.JK.currentUserFreeJamTrack})
@setState({currentPage: 0, next: null, jamtracks:[], type: 'user-input', searching:false, is_free: @user.show_free_jamtrack})
getInitialState: () ->
{search: '', type: 'user-input', jamtracks:[], show_all_artists: false, currentPage: 0, next: null, searching: false, count: 0, is_free: context.JK.currentUserFreeJamTrack}
defaultQuery:(extra) ->
@ -305,16 +299,21 @@ MIX_MODES = context.JK.MIX_MODES
isFree = $(e.target).is('.is_free')
@rest.addJamtrackToShoppingCart(params).done((response) =>
if(isFree)
if context.JK.currentUserId?
context.JK.currentUserFreeJamTrack = true # make sure the user sees no more free notices
context.location = '/client#/redeemComplete'
if context.JK.currentUserId?
if isFree
if @user.has_redeemable_jamtrack
# this is the 1st jamtrack; let's user the user to completion
context.location = '/client#/redeemComplete'
else
# this is must be a user's gifted jamtrack, to treat them normally in that they'll go to the shopping cart
#context.location = '/client#/shoppingCart'
context.location = '/client#/redeemComplete'
else
# now make a rest call to buy it
context.location = '/client#/redeemSignup'
# this user has nothing free; so send them to shopping cart
context.location = '/client#/shoppingCart'
else
context.location = '/client#/shoppingCart'
# user not logged in; make them signup
context.location = '/client#/redeemSignup'
).fail(() => @app.ajaxError)
@ -398,4 +397,15 @@ MIX_MODES = context.JK.MIX_MODES
'afterShow': @afterShow
@app.bindScreen('jamtrack/filter', screenBindings)
onUserChanged: (userState) ->
@user = userState?.user
@setState({is_free: @user?.show_free_jamtrack})
computeWeight: (jam_track_track, instrument) ->
weight = switch
when jam_track_track.track_type == 'Master' then 0
when jam_track_track.instrument?.id == instrument then 1 + jam_track_track.position
else 10000 + jam_track_track.position
})

View File

@ -13,12 +13,13 @@ rest = context.JK.Rest()
{user: null, purchasedJamTracks: []}
onUserChanged: (userState) ->
@user = userState?.user
@onUser(userState.user) if userState.user
render: () ->
howTo = null
if @state.user?.free_jamtrack
if @user?.purchased_jamtracks_count == 0 && @user?.has_redeemable_jamtrack
howTo =
`<div className="free-jamtrack">
<span>
@ -206,11 +207,6 @@ rest = context.JK.Rest()
@processUrl()
if !context.JK.currentUserId
@onUser({free_jamtrack: context.JK.currentUserFreeJamTrack})
beforeShow: () ->
@setState({user: null})

View File

@ -4,7 +4,7 @@ MIX_MODES = context.JK.MIX_MODES
@JamTrackSearchScreen = React.createClass({
mixins: [Reflux.listenTo(@AppStore,"onAppInit")]
mixins: [Reflux.listenTo(@AppStore,"onAppInit"), Reflux.listenTo(@UserStore,"onUserChanged")]
LIMIT: 10
instrument_logo_map: context.JK.getInstrumentIconMap24()
@ -35,19 +35,22 @@ MIX_MODES = context.JK.MIX_MODES
)
###
for track in jamtrack.tracks
trackRow.tracks.push(track)
if track.track_type=='Master'
track.instrument_desc = "Master"
else
inst = '../assets/content/icon_instrument_default24.png'
if track.instrument?
if track.instrument.id in @instrument_logo_map
inst = @instrument_logo_map[track.instrument.id].asset
track.instrument_desc = track.instrument.description
track.instrument_url = inst
if track.part != ''
track.instrument_desc += ' (' + track.part + ')'
if track.track_type == 'Master' || track.track_type == 'Track'
trackRow.tracks.push(track)
if track.track_type == 'Master'
track.instrument_desc = "Master"
else if track.track_type == 'Track'
inst = '../assets/content/icon_instrument_default24.png'
if track.instrument?
if track.instrument.id in @instrument_logo_map
inst = @instrument_logo_map[track.instrument.id].asset
track.instrument_desc = track.instrument.description
track.instrument_url = inst
if track.part != ''
track.instrument_desc += ' (' + track.part + ')'
trackRow.free_state = if @state.is_free then 'free' else 'non-free'
@ -91,10 +94,10 @@ MIX_MODES = context.JK.MIX_MODES
</div>`
actionBtn = null
if jamtrack.is_free
actionBtn = `<a className="jamtrack-add-cart button-orange is_free" href="#" data-jamtrack-id={jamtrack.id}> GET IT FREE!</a>`
else if jamtrack.purchased
if jamtrack.purchased
actionBtn = `<a className="jamtrack-add-cart-disabled button-grey button-disabled" href="javascript:void(0)">PURCHASED</a>`
else if jamtrack.is_free
actionBtn = `<a className="jamtrack-add-cart button-orange is_free" href="#" data-jamtrack-id={jamtrack.id}> GET IT FREE!</a>`
else if jamtrack.added_cart
actionBtn = `<a className="jamtrack-add-cart-disabled button-grey button-disabled" href="client#/shoppingCart">ALREADY IN CART</a>`
else
@ -219,14 +222,13 @@ MIX_MODES = context.JK.MIX_MODES
clearResults:() ->
@setState({currentPage: 0, next: null, show_all_artists: false, artists:[], jamtracks:[], type: 'user-input', searching:false, artist: null, song:null, is_free: context.JK.currentUserFreeJamTrack, first_search: true})
@setState({currentPage: 0, next: null, show_all_artists: false, artists:[], jamtracks:[], type: 'user-input', searching:false, artist: null, song:null, is_free: @user.show_free_jamtrack, first_search: true})
getInitialState: () ->
{search: '', type: 'user-input', artists:[], jamtracks:[], show_all_artists: false, currentPage: 0, next: null, searching: false, first_search: true, count: 0, is_free: context.JK.currentUserFreeJamTrack}
onSelectChange: (val) ->
#@logger.debug("CHANGE #{val}")
return false unless val?
@ -435,18 +437,39 @@ MIX_MODES = context.JK.MIX_MODES
isFree = $(e.target).is('.is_free')
@rest.addJamtrackToShoppingCart(params).done((response) =>
if(isFree)
if context.JK.currentUserId?
context.JK.currentUserFreeJamTrack = true # make sure the user sees no more free notices
context.location = '/client#/redeemComplete'
if context.JK.currentUserId?
if isFree
if @user.has_redeemable_jamtrack
# this is the 1st jamtrack; let's user the user to completion
context.location = '/client#/redeemComplete'
else
# this is must be a user's gifted jamtrack, to treat them normally in that they'll go to the shopping cart
#context.location = '/client#/shoppingCart'
context.location = '/client#/redeemComplete'
else
# now make a rest call to buy it
context.location = '/client#/redeemSignup'
# this user has nothing free; so send them to shopping cart
context.location = '/client#/shoppingCart'
else
context.location = '/client#/shoppingCart'
if isFree
# user not logged in; make them signup
context.location = '/client#/redeemSignup'
else
# this user has nothing free; so send them to shopping cart
context.location = '/client#/shoppingCart'
).fail(() => @app.ajaxError)
).fail(((jqxhr) =>
handled = false
if jqxhr.status == 422
body = JSON.parse(jqxhr.responseText)
if body.errors && body.errors.base
handled = true
context.JK.Banner.showAlert("You can not have a mix of free and non-free items in your shopping cart.<br/><br/>If you want to add this new item to your shopping cart, then clear out all current items by clicking on the shopping cart icon and clicking 'delete' next to each item.")
if !handled
@app.ajaxError(arguments[0], arguments[1], arguments[2])
))
licenseUSWhy:(e) ->
e.preventDefault()
@ -510,6 +533,9 @@ MIX_MODES = context.JK.MIX_MODES
if search?
performSearch = true
@search(search.searchType, search.searchData)
else
if !@state.first_search
@search(@state.type, window.JamTrackSearchInput)
if performSearch
if window.history.replaceState #ie9 proofing
@ -517,11 +543,6 @@ MIX_MODES = context.JK.MIX_MODES
beforeShow: () ->
@setState({is_free: context.JK.currentUserFreeJamTrack})
if !@state.first_search
@search(@state.type, window.JamTrackSearchInput)
onAppInit: (@app) ->
@ -530,10 +551,14 @@ MIX_MODES = context.JK.MIX_MODES
@rest = context.JK.Rest()
@logger = context.JK.logger
screenBindings =
'beforeShow': @beforeShow
'afterShow': @afterShow
@app.bindScreen('jamtrack/search', screenBindings)
onUserChanged: (userState) ->
@user = userState?.user
@setState({is_free: @user?.show_free_jamtrack})
})

View File

@ -32,6 +32,11 @@ mixins.push(Reflux.listenTo(JamTrackPlayerStore, 'onJamTrackPlayerStoreChanged')
mixins: mixins
computeWeight: (jam_track_track) ->
weight = switch
when jam_track_track.track_type == 'Master' then 0
when jam_track_track.track_type == 'Click' then 10000
else jam_track_track.position
onJamTrackPlayerStoreChanged: (changes) ->
#logger.debug("PopupMediaControls: jamtrack changed", changes)
@ -222,9 +227,16 @@ mixins.push(Reflux.listenTo(JamTrackPlayerStore, 'onJamTrackPlayerStoreChanged')
active = jamTrack.last_stem_id?
trackOptions = []
for track in jamTrack.tracks
if track.track_type == 'Track'
jamTrack.tracks.sort((a, b) =>
aWeight = @computeWeight(a)
bWeight = @computeWeight(b)
return aWeight - bWeight
)
for track in jamTrack.tracks
if track.track_type == 'Track' || track.track_type == 'Click'
if track.instrument
instrumentId = track.instrument.id
@ -239,7 +251,6 @@ mixins.push(Reflux.listenTo(JamTrackPlayerStore, 'onJamTrackPlayerStoreChanged')
boundStemPlayClick = this.downloadStem.bind(this)
boundStemChange = this.stemChanged.bind(this)
console.log("jamTrack.lastStemId", jamTrack.last_stem_id)
myMixdowns.push `
<div key={track.id} className={classNames({'stem-track' : true, 'mixdown-display': true, 'active' : active})}>
<div className="mixdown-name">
@ -267,8 +278,12 @@ mixins.push(Reflux.listenTo(JamTrackPlayerStore, 'onJamTrackPlayerStoreChanged')
tracks = []
if jamTrack?
for track in jamTrack.tracks
if track.track_type == 'Track'
if track.instrument
if track.track_type == 'Track' || track.track_type == 'Click'
if track.track_type == 'Click'
instrumentId = track.instrument.id
instrumentDescription = 'Clicktrack'
part = ''
else if track.instrument
instrumentId = track.instrument.id
instrumentDescription = track.instrument.description
if track.part? && track.part != instrumentDescription
@ -280,9 +295,17 @@ mixins.push(Reflux.listenTo(JamTrackPlayerStore, 'onJamTrackPlayerStoreChanged')
tracks.push(`
<tr className="stem">
<td><img src={context.JK.getInstrumentIcon24(instrumentId)} className="instrument-icon" /> {instrumentDescription} {part}</td>
<td className="mute"><input type="checkbox" className="stem-mute" data-stem-id={track.id} /></td>
<td className="mute"><input type="checkbox" className="stem-mute" data-stem-id={track.id} defaultChecked={track.track_type == 'Click'}/></td>
</tr>`)
if jamTrack?.jmep?.Events? && jamTrack.jmep.Events[0].metronome?
# tap-in detected; show user tap-in option
tracks.push(`
<tr className="stem">
<td><img src={context.JK.getInstrumentIcon24('computer')} className="instrument-icon" /> Count-in</td>
<td className="mute"><input type="checkbox" className="stem-mute" data-stem-id="count-in" /></td>
</tr>`)
stems = `<div key="stems" className="stems">
<table>
<thead>
@ -632,6 +655,7 @@ mixins.push(Reflux.listenTo(JamTrackPlayerStore, 'onJamTrackPlayerStoreChanged')
else
pitch = parseInt(pitch)
count_in = false
# get mute state of all tracks
$mutes = $(@getDOMNode()).find('.stems .stem .stem-mute')
@ -642,10 +666,13 @@ mixins.push(Reflux.listenTo(JamTrackPlayerStore, 'onJamTrackPlayerStoreChanged')
stemId = $mute.attr('data-stem-id')
muted = $mute.is(':checked')
tracks.push({id: stemId, mute: muted})
if stemId == 'count-in'
count_in = !muted
else
tracks.push({id: stemId, mute: muted})
)
mixdown = {jamTrackID: @state.jamTrackState.jamTrack.id, name: name, settings: {speed:speed, pitch: pitch, tracks:tracks}}
mixdown = {jamTrackID: @state.jamTrackState.jamTrack.id, name: name, settings: {speed:speed, pitch: pitch, "count-in": count_in, tracks:tracks}}
JamTrackPlayerActions.createMixdown(mixdown, @createMixdownDone, @createMixdownFail)

View File

@ -248,7 +248,7 @@ mixins.push(Reflux.listenTo(JamTrackStore, 'onJamTrackChanged'))
trackOptions = []
for track in jamTrack.tracks
if track.track_type == 'Track'
if track.track_type == 'Track' || track.track_type == 'Click'
if track.instrument
instrumentId = track.instrument.id

View File

@ -276,8 +276,7 @@ ChannelGroupIds = context.JK.ChannelGroupIds
# All the JamTracks
mediaTracks.push(`<SessionJamTrackCategory key="JamTrackCategory" jamTrackName={this.state.jamTrackName} mixers={this.state.mediaCategoryMixer} mode={MIX_MODES.PERSONAL} />`)
# show metronome only if it's a full jamtrack
if @state.metronome? && @state.jamTrackMixdown.id == null
if @state.metronome? # && @state.jamTrackMixdown.id == null
@state.metronome.mode = MIX_MODES.PERSONAL
mediaTracks.push(`<SessionMetronome key="JamTrackMetronome" {...this.state.metronome} location="jam-track" />`)

View File

@ -0,0 +1,22 @@
context = window
@ConfigureTracksActions = Reflux.createActions({
reset: {}
trySave: {}
midiScan: {}
vstScan: {}
vstScanComplete: {}
clearVsts: {}
cancelEdit: {}
deleteTrack: {}
updateOutputs: {}
showAddNewTrack: {}
showEditTrack: {}
showEditOutputs: {}
showVstSettings: {}
associateInputsWithTrack: {}
associateInstrumentWithTrack: {}
associateVSTWithTrack: {}
associateMIDIWithTrack: {}
desiredTrackType: {}
})

View File

@ -2,5 +2,6 @@ context = window
@UserActions = Reflux.createActions({
loaded: {}
modify: {}
})

View File

@ -402,6 +402,9 @@ MIX_MODES = context.JK.MIX_MODES;
else
trackName = instrumentName
if jamTrack.track_type == 'Click'
trackName = 'Clicktrack'
data =
name: jamTrackName
trackName: trackName

View File

@ -70,7 +70,7 @@ context = window
jamTracks: () ->
if @session && @session.jam_track
@session.jam_track.tracks.filter((track)->
track.track_type == 'Track'
track.track_type == 'Track' || track.track_type == 'Click'
)
else
null

View File

@ -0,0 +1,94 @@
context = window
rest = context.JK.Rest()
@GiftCardLandingPage = React.createClass({
render: () ->
if this.state.done
ctaButtonText10 = 'sending you in...'
ctaButtonText20 = 'sending you in...'
else if this.state.processing
ctaButtonText10 = 'hold on...'
ctaButtonText20 = 'hold on...'
else
ctaButtonText10 = `<span>ADD $10 CARD<br/>TO CART</span>`
ctaButtonText20 = `<span>ADD $20 CARD<br/>TO CART</span>`
ctaButtons =
`<div className="cta-buttons">
<button className={classNames({'five-jt': true, 'gift-card': true, 'cta-button' : true, 'processing': this.state.processing})} onClick={this.ctaClick.bind(this, 5)}>{ctaButtonText10}</button>
<button className={classNames({'ten-jt': true, 'gift-card': true, 'cta-button' : true, 'processing': this.state.processing})} onClick={this.ctaClick.bind(this, 10)}>{ctaButtonText20}</button>
</div>`
`<div className="top-container">
<div className="full-row name-and-artist">
<div>
<img className="gift-card-preview" width="300" height="191" src="/assets/landing/gift_card.png" alt="gift card "/>
<h1 className="jam-track-name">$10 or $20 JAMTRACKS GIFT CARDS</h1>
<h2 className="original-artist">A PERFECT GIFT FOR THE HOLIDAYS</h2>
<div className="clearall"/>
</div>
<div className="preview-and-action-box">
<img src="/assets/landing/jamtrack_landing_arrow_1.png" className="arrow1" />
<img src="/assets/landing/jamtrack_landing_arrow_2.png" className="arrow2" />
<div className="preview-jamtrack-header gift-card">
Preview A JamTrack
<div className="jamtrack-title">"{this.props.jam_track.name}"</div>
</div>
<div className={classNames({'preview-area': true, 'gift-card': true})}>
<p>Click the play buttons below to preview the master mix and 20-second samples of all the isolated tracks.</p>
<div className="tracks previews">
</div>
<p className="gift-getter">
Get a $10 gift card (good for 5 songs) or a $20 gift card (good for 10 songs), and your happy
gift card getter can choose their favorites from our catalog of 3,700+ popular songs.
</p>
{ctaButtons}
<a className="browse-all" href="/client?search=#/jamtrack/search">or browse our catalog of 3,700+ songs</a>
</div>
</div>
</div>
<div className="row summary-text">
<p className="top-summary">
JamTracks by JamKazam are the best way to play along with your favorite songs. Far better and different than traditional
backing tracks, our JamTracks are complete multi-track professional recordings, with fully isolated tracks for each part of the music.
And our free app and Internet service are packed with features that give you unmatched creative freedom to learn, practice, record, play with others, and share your performances.
</p>
</div>
</div>`
getInitialState: () ->
{processing:false}
componentDidMount:() ->
$root = $(this.getDOMNode())
# add item to cart, create the user if necessary, and then place the order to get the free JamTrack.
ctaClick: (card_type, e) ->
e.preventDefault()
return if @state.processing
loggedIn = context.JK.currentUserId?
rest.addGiftCardToShoppingCart({id: card_type}).done((response) =>
if loggedIn
@setState({done: true})
context.location = '/client#/shoppingCart'
else
@setState({done: true})
context.location = '/client#/shoppingCart'
).fail((jqXHR, textStatus, errorMessage) =>
if jqXHR.status == 422
errors = JSON.parse(jqXHR.responseText)
cart_errors = errors?.errors?.cart_id
context.JK.app.ajaxError(jqXHR, textStatus, errorMessage)
@setState({processing:false})
)
})

View File

@ -119,10 +119,9 @@ rest = context.JK.Rest()
isFree = context.JK.currentUserFreeJamTrack
rest.addJamtrackToShoppingCart({id: @props.jam_track.id}).done((response) =>
rest.addJamtrackToShoppingCart({id: @props.jam_track.id, clear:true}).done((response) =>
if isFree
if loggedIn
context.JK.currentUserFreeJamTrack = true # make sure the user sees no more free notices
@setState({done: true})
context.location = '/client?redeemed_flow=1#/jamtrack'
else

View File

@ -0,0 +1,159 @@
context = window
rest = context.JK.Rest()
ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
@RedeemGiftCardPage = React.createClass({
render: () ->
if this.state.formErrors?
for key, value of this.state.formErrors
break
errorText = context.JK.getFullFirstError(key, @state.formErrors, {email: 'Email', password: 'Password', gift_card: 'Gift Card Code', 'terms_of_service' : 'The terms of service'})
buttonClassnames = classNames({'redeem-giftcard': true, 'button-orange': true, disabled: @state.processing || @state.done })
if @state.done
button =
`<div key="done" className="done-action">
<div>You have {this.state.gifted_jamtracks} free JamTracks on your account!</div>
<div>You can now <a className="go-browse" href="/client#/jamtrack">browse our collection</a> and redeem them.</div>
</div>`
else
button = `<button key="button" className={buttonClassnames} onClick={this.action}>REDEEM GIFT CARD</button>`
action = `<ReactCSSTransitionGroup transitionName="session-track-list">
{button}
</ReactCSSTransitionGroup>`
if context.JK.currentUserId?
form =
`<form onSubmit={this.submit}>
<label>Gift Card Code:</label><input type="text" name="code"/>
{action}
</form>`
instruments = `<p className="instructions">Enter the code from the back of your gift card to associate it with your account.</p>`
else
form =
`<form onSubmit={this.submit}>
<label>Gift Card Code:</label><input type="text" name="code"/>
<label>Email:</label><input type="text" name="email"/>
<label>Password:</label><input type="password" name="password"/>
<div className="clearall"/>
<input className="terms-checkbox" type="checkbox" name="terms" /><label className="terms-help">I have read and agree to the JamKazam <a href="/corp/terms" onClick={this.termsClicked}>terms of service</a></label>
<div className="clearall"/>
{action}
</form>`
instruments = `<p className="instructions">Enter the code from the back of your gift card to associate it with your new JamKazam account.</p>`
classes = classNames({'redeem-container': true, 'not-logged-in': !context.JK.currentUserId?, 'logged-in': context.JK.currentUserId? })
`<div className={classes}>
<div className="redeem-content">
<h2>Redeem Your Gift Card</h2>
{instruments}
{form}
<div className={classNames({'errors': true, 'active': this.state.formErrors})}>
{errorText}
</div>
</div>
</div>`
getInitialState: () ->
{formErrors: null, processing:false, gifted_jamtracks: null}
privacyPolicy: (e) ->
e.preventDefault()
context.JK.popExternalLink('/corp/privacy')
termsClicked: (e) ->
e.preventDefault()
context.JK.popExternalLink('/corp/terms')
componentDidMount:() ->
$root = $(this.getDOMNode())
$checkbox = $root.find('.terms-checkbox')
console.log("$checkbox", $checkbox)
context.JK.checkbox($checkbox)
submit: (e) ->
@action(e)
action: (e) ->
if @state.done || @state.processing
e.preventDefault()
return
if context.JK.currentUserId?
@redeem(e)
else
@signup(e)
redeem: (e) ->
e.preventDefault()
return if @state.done || @state.processing
$root = $(@getDOMNode())
$code = $root.find('input[name="code"]')
code = $code.val()
rest.redeemGiftCard({gift_card: code})
.done((response) =>
@setState({formErrors: null, processing:false, done: true, gifted_jamtracks: response.gifted_jamtracks})
).fail((jqXHR) =>
@setState({processing:false})
if jqXHR.status == 422
response = JSON.parse(jqXHR.responseText)
if response.errors
@setState({formErrors: response.errors})
else
context.JK.app.notify({title: 'Unknown Error', text: jqXHR.responseText})
else
context.JK.app.notifyServerError(jqXHR, "Unable to Redeem Giftcard")
)
signup: (e) ->
e.preventDefault()
return if @state.done || @state.processing
$root = $(@getDOMNode())
$email = $root.find('input[name="email"]')
$code = $root.find('input[name="code"]')
$password = $root.find('input[name="password"]')
terms = $root.find('input[name="terms"]').is(':checked')
@setState({processing:true})
email = $email.val()
password = $password.val()
code = $code.val()
if !code
# must pass up non-null value to indicate user is trying to redeem giftcard while creating account
code = ''
rest.signup({email: email, password: password, gift_card: code, terms: terms})
.done((response) =>
@setState({formErrors: null, processing:false, done: true, gifted_jamtracks: response.gifted_jamtracks})
).fail((jqXHR) =>
@setState({processing:false})
if jqXHR.status == 422
response = JSON.parse(jqXHR.responseText)
if response.errors
@setState({formErrors: response.errors})
else
context.JK.app.notify({title: 'Unknown Signup Error', text: jqXHR.responseText})
else
context.JK.app.notifyServerError(jqXHR, "Unable to Sign Up")
)
})

View File

@ -135,7 +135,7 @@ JamTrackActions = @JamTrackActions
@trackDetail = context.jamClient.JamTrackGetTrackDetail (fqId)
if @trackDetail.version?
logger.error("after invalidating package, the version is still wrong!")
logger.error("after invalidating package, the version is still wrong!", @trackDetail.version)
throw "after invalidating package, the version is still wrong!"
if @jamTrack.activeMixdown.client_state == 'cant_open'
@ -165,6 +165,20 @@ JamTrackActions = @JamTrackActions
# JamTrackPlay means 'load'
logger.debug("JamTrackStore: loading mixdown")
context.jamClient.JamTrackStopPlay();
if @jamTrack.jmep
if @jamTrack.activeMixdown.settings.speed?
@jamTrack.jmep.speed = @jamTrack.activeMixdown.settings.speed
else
@jamTrack.jmep.speed = 0
logger.debug("setting jmep data. speed:" + @jamTrack.jmep.speed)
context.jamClient.JamTrackLoadJmep(fqId, @jamTrack.jmep)
else
logger.debug("no jmep data for jamtrack")
result = context.jamClient.JamTrackPlay(fqId);
if !result
@jamTrack.activeMixdown.client_state = 'cant_open'

View File

@ -8,10 +8,24 @@ logger = context.JK.logger
listenables: @UserActions
init: ->
this.listenTo(context.AppStore, this.onAppInit)
onAppInit: (@app) ->
@loadAnonymousUser()
loadAnonymousUser: () ->
@user = {id: null, has_redeemable_jamtrack: context.JK.currentUserFreeJamTrack, purchased_jamtracks_count:0, show_free_jamtrack: context.JK.currentUserFreeJamTrack }
@changed()
onLoaded:(user) ->
@user = user
@changed()
onModify: (changes) ->
@user = $.extend({}, @user, changes)
@changed()
changed:() ->
@trigger({user: @user})
}

View File

@ -16,7 +16,6 @@ BackendToFrontend = {
}
BackendToFrontendFPS = {
0: 30,
1: 24,
2: 20,
@ -140,6 +139,11 @@ BackendToFrontendFPS = {
@state.videoShared = @videoShared
this.trigger(@state)
onBringVideoToFront: ->
if @videoShared
@logger.debug("BringVideoToFront")
context.jamClient.BringVideoWindowToFront();
onTestVideo: () ->
return unless context.jamClient.testVideoRender?
@ -150,7 +154,7 @@ BackendToFrontendFPS = {
onToggleVideo: () ->
if @videoShared
@onStopVideo()
@onBringVideoToFront()
else
@onStartVideo()

View File

@ -273,6 +273,7 @@
function handleRecordingStopped(recordingId, result) {
if(recordingId == "video") {
// comes from VideoRecordingStopped
return;
}
@ -324,6 +325,16 @@
function handleRecordingAborted(recordingId, result) {
if(recordingId == "video") {
// comes from AbortedVideoRecording
recordingId = result;
if (arguments.length == 2) {
result = arguments[2]
}
logger.debug("video recording aborted", result)
context.JK.Banner.showAlert("Video has stopped recording. Audio is still recording.")
//context.RecordingActions.stopRecording()
return;
}

View File

@ -31,11 +31,6 @@
function beforeShow() {
}
function afterShow(data) {
context.JK.Tracking.redeemCompleteTrack()
$noPurchasesPrompt.addClass('hidden')
@ -44,6 +39,10 @@
$purchasedJamTrackHeader.attr('status', 'in-progress')
$jamTrackInBrowser.addClass('hidden')
$jamTrackInClient.addClass('hidden')
}
function afterShow(data) {
// if there is no current user, but it apperas we have a login cookie, just refresh
@ -59,23 +58,19 @@
function handleShoppingCartResponse(carts) {
if(!checkoutUtils.hasOneFreeItemInShoppingCart(carts)) {
if(!checkoutUtils.hasOnlyFreeItemsInShoppingCart(carts)) {
// the user has multiple items in their shopping cart. They shouldn't be here.
logger.error("invalid access of redeemComplete page")
window.location = '/client#/jamtrack/search'
}
else {
// ok, we have one, free item. save it for
shoppingCartItem = carts[0];
rest.placeOrder()
.done(function(purchaseResponse) {
context.JK.currentUserFreeJamTrack = false // make sure the user sees no more free notices without having to do a full page refresh
rest.updateUser()
checkoutUtils.setLastPurchase(purchaseResponse)
jamTrackUtils.checkShoppingCart()
//app.refreshUser() // this only causes grief in tests for some reason, and with currentUserFreeJamTrack = false above, this is probably now unnecessary
prepThanks();
})

View File

@ -73,7 +73,7 @@
else if(carts.length > 1) {
// the user has multiple items in their shopping cart. They shouldn't be here.
logger.error("invalid access of redeemJamTrack page; multiple")
window.location = '/client#/jamtrack/search'
window.location = '/client#/shoppingCart'
}
else {
var item = carts[0];

View File

@ -9,6 +9,7 @@
var $screen = null;
var $content = null;
var totalCost = 0;
function beforeShow(data) {
clearContent();
@ -30,7 +31,7 @@
function proceedCheckout(e) {
e.preventDefault();
if (context.JK.currentUserFreeJamTrack) {
if (totalCost == 0) {
if(context.JK.currentUserId) {
logger.debug("proceeding to redeem complete screen because user has a free jamtrack and is logged in")
window.location = '/client#/redeemComplete'
@ -120,6 +121,8 @@
});
data.sub_total = sub_total;
totalCost = data.sub_total;
data.carts = carts;
var $cartsHtml = $(
context._.template(

View File

@ -41,6 +41,10 @@
context._.each(jam_track.tracks, function (track) {
if (track.track_type == 'Click') {
return;
}
var $element = $('<div class="jam-track-preview-holder"></div>')
$previews.append($element);

View File

@ -30,6 +30,10 @@
context._.each(jam_track.tracks, function (track) {
if (track.track_type == 'Click') {
return;
}
var $element = $('<div class="jam-track-preview-holder"></div>')
$previews.append($element);

View File

@ -40,6 +40,10 @@
context._.each(jam_track.tracks, function (track) {
if (track.track_type == 'Click') {
return;
}
var $element = $('<div class="jam-track-preview-holder"></div>')
$previews.append($element);

View File

@ -73,6 +73,10 @@
}
}
}
.thanks-detail.gift-card, .thanks-detail.jam-tracks-in-browser{
margin-top: 20px;
}
.thanks-detail.purchased-jam-track {
margin-top:20px;

View File

@ -702,4 +702,23 @@ $ReactSelectVerticalPadding: 3px;
.Select-search-prompt {
padding:3px 0 !important;
}
}
.session-track-list-enter {
opacity: 0.01;
transition: opacity .5s ease-in;
&.session-track-list-enter-active {
opacity: 1;
}
}
.session-track-list-leave {
opacity:1;
transition: opacity .5s ease-in;
&.session-track-list-leave-active {
opacity: 0.01;
}
}

View File

@ -386,22 +386,3 @@ $session-screen-divider: 1190px;
}
}
}
.session-track-list-enter {
opacity: 0.01;
transition: opacity .5s ease-in;
&.session-track-list-enter-active {
opacity: 1;
}
}
.session-track-list-leave {
opacity:1;
transition: opacity .5s ease-in;
&.session-track-list-leave-active {
opacity: 0.01;
}
}

View File

@ -74,7 +74,7 @@
display:inline-block;
}
.download-jamkazam-wrapper {
.download-jamkazam-wrapper, .back-to-browsing {
text-align:center;
display:block;
margin-top:35px;
@ -83,6 +83,7 @@
display:none;
}
}
.thanks-detail.purchased-jam-track {
margin-top:20px;

View File

@ -162,6 +162,18 @@ body.web.individual_jamtrack {
text-align: center;
}
img.gift-card-preview {
width:300px;
float: left;
margin-left: -15px; // because image has black on the left, which you can't see on back background
margin-right: 20px;
margin-bottom: 20px;
}
p.gift-getter {
margin-top:20px;
line-height:125%;
}
img.app-preview {
width: 340px;
float: left;
@ -188,6 +200,7 @@ body.web.individual_jamtrack {
}
.cta-button {
cursor:pointer;
background-color: $cta-color;
&.processing {
@ -205,6 +218,9 @@ body.web.individual_jamtrack {
.browse-all {
color: #ffb800;
text-decoration: underline;
text-align: center;
display: block;
}
p {
@ -313,6 +329,16 @@ body.web.individual_jamtrack {
border-width: 0 0 $chunkyBorderWidth;
border-style: solid;
border-color: $copy-color-on-dark;
&.gift-card {
padding:20px 0 10px;
.jamtrack-title {
margin-top:10px;
font-size:18px;
font-style:italic;
}
}
}
.preview-area {
@ -320,6 +346,11 @@ body.web.individual_jamtrack {
padding: 10px;
border-width: 0 0 $chunkyBorderWidth;
&.gift-card {
border-width: 0 0 2px;
}
border-style: solid;
border-color: $copy-color-on-dark;
@ -338,6 +369,9 @@ body.web.individual_jamtrack {
margin-bottom:10px;
}
.cta-buttons {
text-align:center;
}
.cta-button {
font-size: 24px;
color: white;
@ -348,6 +382,13 @@ body.web.individual_jamtrack {
width: 100%;
border: 1px outset buttonface;
font-family: Raleway, Arial, Helvetica, sans-serif;
&.gift-card {
font-size:16px;
width:138px;
margin:15px 5px;
display:inline-block;
}
}
}
@ -385,12 +426,7 @@ body.web.individual_jamtrack {
border: 1px outset buttonface;
font-family: Raleway, Arial, Helvetica, sans-serif;
}
.browse-all {
text-decoration: underline;
text-align: center;
display: block;
}
.privacy-policy {
text-decoration: underline;
}

View File

@ -0,0 +1,90 @@
@import "client/common";
body.web.redeem_giftcard {
h2 {
margin-bottom:20px;
}
label{
margin-bottom:4px;
color:$ColorTextTypical;
}
input{
margin-bottom:20px;
width:200px;
}
.redeem-container {
margin-left:350px;
width:400px;
padding-top:20px;
&.logged-in {
button {
margin-top:10px !important;
}
}
&.not-logged-in {
}
}
.redeem-content {
}
p.instructions {
line-height:125%;
color:$ColorTextTypical;
margin-bottom:20px;
}
button {
display:block !important;
height: 29px !important;
margin-bottom: 10px;
margin-right: 0px;
font-size: 16px !important;
padding: 7px 3px !important;
line-height:inherit !important;
margin-left:2px !important;
margin-top:15px;
}
.icheckbox_minimal {
float: left;
top: -2px;
margin-left: 0;
margin-right:10px;
}
.errors {
font-size:14px;
height:20px;
margin:0;
visibility: hidden;
color: red;
font-weight: bold;
&.active {
visibility: visible;
}
}
form {
margin-bottom:20px;
}
.terms-help {
float:left;
margin-top:-5px;
font-size:12px;
width:178px;
}
.done-action {
margin-top: 20px;
line-height: 125%;
}
}

View File

@ -3,7 +3,7 @@ class ApiJamTracksController < ApiController
# have to be signed in currently to see this screen
before_filter :api_signed_in_user, :except => [:index, :autocomplete, :show_with_artist_info, :artist_index]
before_filter :api_any_user, :only => [:index, :autocomplete, :show_with_artist_info, :artist_index]
before_filter :lookup_jam_track_right, :only => [:download,:enqueue, :show_jam_track_right, :mark_active, :download_stem]
before_filter :lookup_jam_track_right, :only => [:download, :enqueue, :show_jam_track_right, :mark_active, :download_stem]
before_filter :ip_blacklist, :only => [:download_stem, :download]
before_filter :user_blacklist, :only => [:download_stem, :download]
@ -285,6 +285,25 @@ class ApiJamTracksController < ApiController
puts "jamtrack_mixdowns #{jamtrack_mixdowns}"
end
def ios_order_placed
jam_track = JamTrack.find(params[:jam_track_id])
sales = Sale.ios_purchase(current_user, jam_track, nil)
#
# sales.each do |sale|
# if sale.is_jam_track_sale?
# sale.sale_line_items.each do |line_item|
# jam_track = line_item.product
# jam_track_right = jam_track.right_for_user(current_user)
# response[:jam_tracks] << {name: jam_track.name, id: jam_track.id, jam_track_right_id: jam_track_right.id, version: jam_track.version}
# end
# end
# end
response = {name: jam_track.name, id: jam_track.id, jam_track_right_id: jam_track.right_for_user(current_user).id, version: jam_track.version}
render :json => response, :status => 200
end
private
def lookup_jam_track_right
@jam_track_right = JamTrackRight.where("jam_track_id=? AND user_id=?", params[:id], current_user.id).first

View File

@ -124,16 +124,28 @@ class ApiRecurlyController < ApiController
def place_order
error=nil
response = {jam_tracks: []}
response = {jam_tracks: [], gift_cards: []}
if Sale.is_mixed(current_user.shopping_carts)
msg = "has free and non-free items. Try removing non-free items."
render json: {message: "Cart " + msg, errors: {cart: [msg]}}, :status => 404
return
end
sales = Sale.place_order(current_user, current_user.shopping_carts)
sales.each do |sale|
if sale.is_jam_track_sale?
sale.sale_line_items.each do |line_item|
sale.sale_line_items.each do |line_item|
if line_item.is_jam_track?
jam_track = line_item.product
jam_track_right = jam_track.right_for_user(current_user)
response[:jam_tracks] << {name: jam_track.name, id: jam_track.id, jam_track_right_id: jam_track_right.id, version: jam_track.version}
elsif line_item.is_gift_card?
gift_card = line_item.product
response[:gift_cards] << {name: gift_card.name, id: gift_card.id}
else
raise 'unknown sale line item type: ' + line_item.product_type
end
end
end

View File

@ -20,13 +20,41 @@ class ApiShoppingCartsController < ApiController
raise StateError, "Invalid JamTrack."
end
@cart = ShoppingCart.add_jam_track_to_cart(any_user, jam_track)
@cart = ShoppingCart.add_jam_track_to_cart(any_user, jam_track, clear:params[:clear])
if @cart.errors.any?
response.status = :unprocessable_entity
respond_with @cart
else
respond_with @cart, responder: ApiResponder, :status => 201
# let add_jamtrack.rabl take over
end
end
def add_gift_card
gift_card_type = nil
id = params[:id]
if id && id.to_i == 5
gift_card_type = 'jam_tracks_5'
elsif id && id.to_i == 10
gift_card_type = 'jam_tracks_10'
end
gift_card = GiftCardType.find_by_id(gift_card_type)
# verify GiftCard exists
if gift_card.nil?
raise StateError, "Invalid JamTrack."
end
@cart = ShoppingCart.add_item_to_cart(any_user, gift_card)
if @cart.errors.any?
response.status = :unprocessable_entity
respond_with @cart
else
# let add_gift_card.rabl take over
end
end
@ -42,7 +70,7 @@ class ApiShoppingCartsController < ApiController
response.statue = :unprocessable_entity
respond_with @cart
else
respond_with @cart, responder: ApiResponder, :status => 200
# let update_cart.rabl take over
end
end
@ -52,7 +80,7 @@ class ApiShoppingCartsController < ApiController
ShoppingCart.remove_jam_track_from_cart(any_user, @cart)
respond_with responder: ApiResponder, :status => 204
# let remove_cart.rabl take over
end
# take all shopping carts from anonymous user and copy them to logged in user

View File

@ -14,8 +14,8 @@ ApiUsersController < ApiController
:band_invitation_index, :band_invitation_show, :band_invitation_update, # band invitations
:set_password, :begin_update_email, :update_avatar, :delete_avatar, :generate_filepicker_policy,
:share_session, :share_recording,
:affiliate_report, :audio_latency, :broadcast_notification]
before_filter :ip_blacklist, :only => [:create]
:affiliate_report, :audio_latency, :broadcast_notification, :redeem_giftcard]
before_filter :ip_blacklist, :only => [:create, :redeem_giftcard]
respond_to :json, :except => :calendar
respond_to :ics, :only => :calendar
@ -81,6 +81,7 @@ ApiUsersController < ApiController
terms_of_service: params[:terms_of_service].to_i,
location: {:country => nil, :state => nil, :city => nil},
signup_hint: signup_hint,
gift_card: params[:gift_card],
affiliate_referral_id: cookies[:affiliate_visitor]
}
@ -598,6 +599,7 @@ ApiUsersController < ApiController
@dump.user_id = params[:user_id]
@dump.session_id = params[:session_id]
@dump.timestamp = params[:timestamp]
@dump.description = params[:description]
unless @dump.save
# There are at least some conditions on valid dumps (need client_type)
@ -919,6 +921,45 @@ ApiUsersController < ApiController
.find(params[:id])
end
def redeem_giftcard
@gift_card = GiftCard.find_by_code(params[:gift_card])
if @gift_card.nil?
render json: {errors:{gift_card: ['does not exist']}}, status: 422
return
end
if current_user.gift_cards.count >= 5
render json: {errors:{gift_card: ['has too many on account']}}, status: 422
return
end
if @gift_card.user
if @gift_card.user == current_user
render json: {errors:{gift_card: ['already redeemed by you']}}, status: 422
return
else
render json: {errors:{gift_card: ['already redeemed by another']}}, status: 422
return
end
end
@gift_card.user = current_user
@gift_card.save
if @gift_card.errors.any?
respond_with_model(@gift_card)
return
else
# apply gift card items to everything in shopping cart
current_user.reload
ShoppingCart.apply_gifted_jamtracks(current_user)
render json: {gifted_jamtracks:current_user.gifted_jamtracks}, status: 200
end
end
###################### RECORDINGS #######################
# def recording_index
# @recordings = User.recording_index(current_user, params[:id])

View File

@ -208,5 +208,33 @@ class LandingsController < ApplicationController
render 'affiliate_program', layout: 'web'
end
def redeem_giftcard
@no_landing_tag = true
@landing_tag_play_learn_earn = true
render 'redeem_giftcard', layout: 'web'
end
def buy_gift_card
@no_landing_tag = true
@landing_tag_play_learn_earn = true
@show_after_black_bar_border = true
@jam_track = JamTrack.find_by_slug('elton-john-rocket-man')
@jam_track = JamTrack.first unless @jam_track
instrument_id = nil
instrument_name = nil
instrument_count = 0
band_jam_track_count = @jam_track.band_jam_track_count
jam_track_count = JamTrack.count
@title = individual_jamtrack_title(false, params[:generic], @jam_track)
@description = individual_jamtrack_desc(false, params[:generic], @jam_track)
@page_data = {jam_track: @jam_track, all_track_count: jam_track_count, band_track_count: band_jam_track_count, band: false, generic: params[:generic], instrument: instrument_name, instrument_id: instrument_id, instrument_count: instrument_count}
gon.jam_track_plan_code = @jam_track.plan_code if @jam_track
gon.generic = params[:generic]
gon.instrument_id = instrument_id
render 'buy_gift_card', layout: 'web'
end
end

View File

@ -0,0 +1,5 @@
extends "api_shopping_carts/show"
node :show_free_jamtrack do
any_user.show_free_jamtrack?
end

Some files were not shown because too many files have changed in this diff Show More