diff --git a/admin/app/admin/jam_track_right.rb b/admin/app/admin/jam_track_right.rb index 48da8231a..e2cc0bbe9 100644 --- a/admin/app/admin/jam_track_right.rb +++ b/admin/app/admin/jam_track_right.rb @@ -1,20 +1,29 @@ require 'jam_ruby/recurly_client' ActiveAdmin.register JamRuby::JamTrackRight, :as => 'JamTrackRights' do - menu :label => 'Purchased JamTracks', :parent => 'JamTracks' + menu :label => 'Purchased JamTracks', :parent => 'Purchases' - config.sort_order = 'updated_at DESC' + config.sort_order = 'created_at DESC' config.batch_actions = false #form :partial => 'form' + filter :user_id + + filter :user_id, + :label => "USER ID", :required => false, + :wrapper_html => { :style => "list-style: none" } + + + filter :jam_track + index do default_actions - column "Order" do |right| - link_to("Place", order_admin_jam_track_right_path(right)) + " | " + - link_to("Refund", refund_admin_jam_track_right_path(right)) - end + #column "Order" do |right| + #link_to("Place", order_admin_jam_track_right_path(right)) + " | " + + # link_to("Refund", refund_admin_jam_track_right_path(right)) + #end column "Last Name" do |right| right.user.last_name @@ -23,13 +32,15 @@ ActiveAdmin.register JamRuby::JamTrackRight, :as => 'JamTrackRights' do right.user.first_name end column "Jam Track" do |right| - link_to(right.jam_track.name, admin_jam_track_right_path(right.jam_track)) + link_to(right.jam_track.name, admin_jam_track_path(right.jam_track)) # right.jam_track end column "Plan Code" do |right| - right.jam_track.plan_code end + column "Redeemed" do |right| + right.redeemed ? 'Y' : 'N' + end end @@ -42,6 +53,9 @@ ActiveAdmin.register JamRuby::JamTrackRight, :as => 'JamTrackRights' do f.actions end +=begin + + member_action :order, :method => :get do right = JamTrackRight.where("id=?",params[:id]).first user = right.user @@ -84,4 +98,5 @@ ActiveAdmin.register JamRuby::JamTrackRight, :as => 'JamTrackRights' do redirect_to admin_jam_track_rights_path, notice: "Issued full refund on #{right.jam_track} for #{right.user.to_s}" end end +=end end \ No newline at end of file diff --git a/admin/app/admin/recurly_transaction_web_hook.rb b/admin/app/admin/recurly_transaction_web_hook.rb new file mode 100644 index 000000000..0c6942d82 --- /dev/null +++ b/admin/app/admin/recurly_transaction_web_hook.rb @@ -0,0 +1,40 @@ +ActiveAdmin.register JamRuby::RecurlyTransactionWebHook, :as => 'RecurlyHooks' do + + menu :label => 'Recurly Transaction Hooks', :parent => 'Purchases' + + config.sort_order = 'created_at DESC' + config.batch_actions = false + + actions :all, :except => [:destroy] + + #form :partial => 'form' + + + filter :transaction_type, :as => :select, :collection => JamRuby::RecurlyTransactionWebHook::HOOK_TYPES + filter :user_id, + :label => "USER ID", :required => false, + :wrapper_html => { :style => "list-style: none" } + + filter :invoice_id + + form :partial => 'form' + + index do + + default_actions + + column :transaction_type + column :transaction_at + column :amount_in_cents + column 'Transaction' do |hook| link_to('Go to Recurly', Rails.application.config.recurly_root_url + "/transactions/#{hook.recurly_transaction_id}") end + column 'Invoice' do |hook| link_to(hook.invoice_number, Rails.application.config.recurly_root_url + "/invoices/#{hook.invoice_number}") end + column :admin_description + column 'User' do |hook| link_to("#{hook.user.email} (#{hook.user.name})", admin_user_path(hook.user.id)) end + + #column "Order" do |right| + #link_to("Place", order_admin_jam_track_right_path(right)) + " | " + + # link_to("Refund", refund_admin_jam_track_right_path(right)) + #end + end + +end \ No newline at end of file diff --git a/admin/app/views/admin/recurly_hooks/_form.html.slim b/admin/app/views/admin/recurly_hooks/_form.html.slim new file mode 100644 index 000000000..3e1305e23 --- /dev/null +++ b/admin/app/views/admin/recurly_hooks/_form.html.slim @@ -0,0 +1,6 @@ += semantic_form_for([:admin, resource], :html => {:multipart => true}, :url => resource.new_record? ? admin_recurly_transaction_web_hooks_path : "#{ENV['RAILS_RELATIVE_URL_ROOT']}/admin/recurly_hooks/#{resource.id}") do |f| + = f.semantic_errors *f.object.errors.keys + = f.inputs name: 'Recurly Web Hook fields' do + = f.input :admin_description, :input_html => { :rows=>1, :maxlength=>200, }, hint: "this will display on the user's payment history page" + = f.input :jam_track, collection: JamRuby::JamTrack.all, include_blank: true, hint: "Please indicate which JamTrack this refund for, if not set" + = f.actions \ No newline at end of file diff --git a/admin/config/application.rb b/admin/config/application.rb index 102a03176..9add98380 100644 --- a/admin/config/application.rb +++ b/admin/config/application.rb @@ -83,6 +83,7 @@ module JamAdmin config.external_port = ENV['EXTERNAL_PORT'] || 3000 config.external_protocol = ENV['EXTERNAL_PROTOCOL'] || 'http://' config.external_root_url = "#{config.external_protocol}#{config.external_hostname}#{(config.external_port == 80 || config.external_port == 443) ? '' : ':' + config.external_port.to_s}" + config.recurly_root_url = 'https://jamkazam-development.recurly.com' # where is rabbitmq? config.rabbitmq_host = "localhost" @@ -116,7 +117,7 @@ module JamAdmin config.email_smtp_domain = 'www.jamkazam.com' config.email_smtp_authentication = :plain config.email_smtp_user_name = 'jamkazam' - config.email_smtp_password = 'jamjamblueberryjam' + config.email_smtp_password = 'snorkeltoesniffyfarce1' config.email_smtp_starttls_auto = true config.facebook_app_id = ENV['FACEBOOK_APP_ID'] || '468555793186398' diff --git a/admin/config/environments/development.rb b/admin/config/environments/development.rb index ffd6b0e8f..b449898e8 100644 --- a/admin/config/environments/development.rb +++ b/admin/config/environments/development.rb @@ -43,4 +43,7 @@ JamAdmin::Application.configure do # Show the logging configuration on STDOUT config.show_log_configuration = true + + config.email_generic_from = 'nobody-dev@jamkazam.com' + config.email_alerts_alias = 'alerts-dev@jamkazam.com' end diff --git a/db/manifest b/db/manifest index bff3d4df6..074c9df06 100755 --- a/db/manifest +++ b/db/manifest @@ -282,3 +282,7 @@ add_genre_type.sql add_description_to_perf_samples.sql alter_genre_player_unique_constraint.sql musician_search.sql +signup_hints.sql +packaging_notices.sql +first_played_jamtrack_at.sql +payment_history.sql \ No newline at end of file diff --git a/db/up/first_played_jamtrack_at.sql b/db/up/first_played_jamtrack_at.sql new file mode 100644 index 000000000..aeddf7757 --- /dev/null +++ b/db/up/first_played_jamtrack_at.sql @@ -0,0 +1 @@ +ALTER TABLE users ADD COLUMN first_played_jamtrack_at TIMESTAMP; \ No newline at end of file diff --git a/db/up/jam_track_duration.sql b/db/up/jam_track_duration.sql index a4dbee409..7c39e03ca 100644 --- a/db/up/jam_track_duration.sql +++ b/db/up/jam_track_duration.sql @@ -1 +1,9 @@ -ALTER TABLE jam_tracks ADD COLUMN duration INTEGER; \ No newline at end of file +DO $$ + BEGIN + BEGIN + ALTER TABLE jam_tracks ADD COLUMN duration INTEGER; + EXCEPTION + WHEN duplicate_column THEN RAISE NOTICE 'column duration already exists in jam_tracks.'; + END; + END; +$$ LANGUAGE plpgsql; \ No newline at end of file diff --git a/db/up/packaging_notices.sql b/db/up/packaging_notices.sql new file mode 100644 index 000000000..d3a5de551 --- /dev/null +++ b/db/up/packaging_notices.sql @@ -0,0 +1,3 @@ +ALTER TABLE jam_track_rights ADD COLUMN packaging_steps INTEGER; +ALTER TABLE jam_track_rights ADD COLUMN current_packaging_step INTEGER; +ALTER TABLE jam_track_rights ADD COLUMN last_step_at TIMESTAMP; diff --git a/db/up/payment_history.sql b/db/up/payment_history.sql new file mode 100644 index 000000000..cb350c61d --- /dev/null +++ b/db/up/payment_history.sql @@ -0,0 +1,17 @@ +ALTER TABLE recurly_transaction_web_hooks ADD COLUMN admin_description VARCHAR; +ALTER TABLE recurly_transaction_web_hooks ADD COLUMN jam_track_id VARCHAR(64) REFERENCES jam_tracks(id); + +CREATE VIEW payment_histories AS + SELECT id AS sale_id, + CAST(NULL as VARCHAR) AS recurly_transaction_web_hook_id, + user_id, + created_at, + 'sale' AS transaction_type + FROM sales s + UNION ALL + SELECT CAST(NULL as VARCHAR) AS sale_id, + id AS recurly_transaction_web_hook_id, + user_id, + transaction_at AS created_at, + transaction_type + FROM recurly_transaction_web_hooks; \ No newline at end of file diff --git a/db/up/preview_support_mp3.sql b/db/up/preview_support_mp3.sql index 90dd1c642..8417e221b 100644 --- a/db/up/preview_support_mp3.sql +++ b/db/up/preview_support_mp3.sql @@ -1,4 +1,11 @@ -ALTER TABLE jam_track_tracks ADD COLUMN preview_mp3_url VARCHAR; -ALTER TABLE jam_track_tracks ADD COLUMN preview_mp3_md5 VARCHAR; -ALTER TABLE jam_track_tracks ADD COLUMN preview_mp3_length BIGINT; -UPDATE jam_track_tracks SET preview_url = NULL where track_type = 'Master'; \ No newline at end of file +DO $$ + BEGIN + BEGIN + ALTER TABLE jam_track_tracks ADD COLUMN preview_mp3_url VARCHAR; + ALTER TABLE jam_track_tracks ADD COLUMN preview_mp3_md5 VARCHAR; + ALTER TABLE jam_track_tracks ADD COLUMN preview_mp3_length BIGINT; + EXCEPTION + WHEN duplicate_column THEN RAISE NOTICE 'preview mp3 columns already exist in jam_tracks'; + END; + END; +$$ LANGUAGE plpgsql; \ No newline at end of file diff --git a/db/up/signup_hints.sql b/db/up/signup_hints.sql new file mode 100644 index 000000000..9a079e216 --- /dev/null +++ b/db/up/signup_hints.sql @@ -0,0 +1,12 @@ +CREATE TABLE signup_hints ( + id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(), + anonymous_user_id VARCHAR(64) UNIQUE, + redirect_location VARCHAR, + want_jamblaster BOOLEAN NOT NULL DEFAULT FALSE, + user_id VARCHAR(64) REFERENCES users(id) ON DELETE CASCADE, + expires_at TIMESTAMP, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +ALTER TABLE users ADD COLUMN want_jamblaster BOOLEAN NOT NULL DEFAULT FALSE; \ No newline at end of file diff --git a/ruby/jt_metadata.json b/ruby/jt_metadata.json new file mode 100644 index 000000000..9cf0ba195 --- /dev/null +++ b/ruby/jt_metadata.json @@ -0,0 +1 @@ +{"container_file": "/var/folders/fk/0ckzmddd4tq28kxbb09vckbr0000gn/T/d20150428-83245-1alsyg1/jam-track-19.jkz", "version": "0", "coverart": null, "rsa_priv_file": "/var/folders/fk/0ckzmddd4tq28kxbb09vckbr0000gn/T/d20150428-83245-1alsyg1/skey.pem", "tracks": [{"name": "/var/folders/fk/0ckzmddd4tq28kxbb09vckbr0000gn/T/d20150428-83245-1alsyg1/9599b114-0f66-4140-ae3a-078a4e0c0767.ogg", "trackName": "track_00"}], "rsa_pub_file": "/var/folders/fk/0ckzmddd4tq28kxbb09vckbr0000gn/T/d20150428-83245-1alsyg1/pkey.pem", "jamktrack_info": "/var/folders/fk/0ckzmddd4tq28kxbb09vckbr0000gn/T/tmp9PHU_9"} \ No newline at end of file diff --git a/ruby/lib/jam_ruby.rb b/ruby/lib/jam_ruby.rb index 579068623..8fbdb9efd 100755 --- a/ruby/lib/jam_ruby.rb +++ b/ruby/lib/jam_ruby.rb @@ -20,11 +20,12 @@ require 'resque_mailer' require 'rest-client' require 'zip' require 'csv' +require 'tzinfo' require "jam_ruby/constants/limits" require "jam_ruby/constants/notification_types" require "jam_ruby/constants/validation_messages" -require "jam_ruby/errors/permission_error" +require "jam_ruby/errors/jam_permission_error" require "jam_ruby/errors/state_error" require "jam_ruby/errors/jam_argument_error" require "jam_ruby/errors/conflict_error" @@ -100,6 +101,7 @@ require "jam_ruby/models/genre_player" require "jam_ruby/models/genre" require "jam_ruby/models/user" require "jam_ruby/models/anonymous_user" +require "jam_ruby/models/signup_hint" require "jam_ruby/models/rsvp_request" require "jam_ruby/models/rsvp_slot" require "jam_ruby/models/rsvp_request_rsvp_slot" @@ -205,6 +207,7 @@ require "jam_ruby/models/generic_state" require "jam_ruby/models/score_history" require "jam_ruby/models/jam_company" require "jam_ruby/models/user_sync" +require "jam_ruby/models/payment_history" require "jam_ruby/models/video_source" require "jam_ruby/models/text_message" require "jam_ruby/models/sale" diff --git a/ruby/lib/jam_ruby/app/mailers/admin_mailer.rb b/ruby/lib/jam_ruby/app/mailers/admin_mailer.rb index c4addb5cc..f14d8e424 100644 --- a/ruby/lib/jam_ruby/app/mailers/admin_mailer.rb +++ b/ruby/lib/jam_ruby/app/mailers/admin_mailer.rb @@ -1,6 +1,6 @@ module JamRuby # sends out a boring ale - class AdminMailer < ActionMailer::Base + class AdminMailer < ActionMailer::Base include SendGrid @@ -14,9 +14,24 @@ module JamRuby def alerts(options) mail(to: APP_CONFIG.email_alerts_alias, + from: APP_CONFIG.email_generic_from, body: options[:body], content_type: "text/plain", subject: options[:subject]) end + + def recurly_alerts(user, options) + + body = options[:body] + body << "\n\n" + body << "User " << user.admin_url + "\n" + body << "User's JamTracks " << user.jam_track_rights_admin_url + "\n" + + mail(to: APP_CONFIG.email_recurly_notice, + from: APP_CONFIG.email_generic_from, + body: body, + content_type: "text/plain", + subject: options[:subject]) + end end end diff --git a/ruby/lib/jam_ruby/app/views/layouts/user_mailer.text.erb b/ruby/lib/jam_ruby/app/views/layouts/user_mailer.text.erb index 78d40b50c..49655e237 100644 --- a/ruby/lib/jam_ruby/app/views/layouts/user_mailer.text.erb +++ b/ruby/lib/jam_ruby/app/views/layouts/user_mailer.text.erb @@ -4,8 +4,8 @@ <%= yield %> <% end %> -<% unless @suppress_user_has_account_footer == true %> -This email was sent to you because you have an account at JamKazam / http://www.jamkazam.com. Visit your profile page to unsubscribe: http://www.jamkazam.com/unsubscribe/<%=@user.unsubscribe_token%>. +<% unless @user.nil? || @suppress_user_has_account_footer == true %> +This email was sent to you because you have an account at JamKazam / http://www.jamkazam.com. To unsubscribe: http://www.jamkazam.com/unsubscribe/<%=@user.unsubscribe_token%>. <% end %> Copyright <%= Time.now.year %> JamKazam, Inc. All rights reserved. diff --git a/ruby/lib/jam_ruby/errors/jam_permission_error.rb b/ruby/lib/jam_ruby/errors/jam_permission_error.rb new file mode 100644 index 000000000..531791df1 --- /dev/null +++ b/ruby/lib/jam_ruby/errors/jam_permission_error.rb @@ -0,0 +1,5 @@ +module JamRuby + class JamPermissionError < Exception + + end +end \ No newline at end of file diff --git a/ruby/lib/jam_ruby/errors/permission_error.rb b/ruby/lib/jam_ruby/errors/permission_error.rb deleted file mode 100644 index f0b4e3a2f..000000000 --- a/ruby/lib/jam_ruby/errors/permission_error.rb +++ /dev/null @@ -1,5 +0,0 @@ -module JamRuby - class PermissionError < Exception - - end -end \ No newline at end of file diff --git a/ruby/lib/jam_ruby/jam_track_importer.rb b/ruby/lib/jam_ruby/jam_track_importer.rb index 5535f6e08..f4ce1e6de 100644 --- a/ruby/lib/jam_ruby/jam_track_importer.rb +++ b/ruby/lib/jam_ruby/jam_track_importer.rb @@ -91,13 +91,15 @@ module JamRuby true end - def synchronize_metadata(jam_track, metadata, metalocation, original_artist, name) + def synchronize_metadata(jam_track, metadata, metalocation, original_artist, name, options) metadata ||= {} self.name = metadata["name"] || name if jam_track.new_record? - jam_track.id = "#{JamTrack.count + 1}" # default is UUID, but the initial import was based on auto-increment ID, so we'll maintain that + latest_jamtrack = JamTrack.order('created_at desc').first + id = latest_jamtrack.nil? ? 1 : latest_jamtrack.id.to_i + 1 + jam_track.id = "#{id}" # default is UUID, but the initial import was based on auto-increment ID, so we'll maintain that jam_track.status = 'Staging' jam_track.metalocation = metalocation jam_track.original_artist = metadata["original_artist"] || original_artist @@ -107,13 +109,20 @@ module JamRuby jam_track.price = 1.99 jam_track.reproduction_royalty_amount = 0 jam_track.licensor_royalty_amount = 0 - jam_track.sales_region = 'United States' + jam_track.sales_region = 'Worldwide' jam_track.recording_type = 'Cover' jam_track.description = "This is a JamTrack audio file for use exclusively with the JamKazam service. This JamTrack is a high quality cover of the #{jam_track.original_artist} song \"#{jam_track.name}\"." else - #@@log.debug("#{self.name} skipped because it already exists in database") - finish("jam_track_exists", "") - return false + if !options[:resync_audio] + #@@log.debug("#{self.name} skipped because it already exists in database") + finish("jam_track_exists", "") + return false + else + # jamtrack exists, leave it be + return true + end + + end saved = jam_track.save @@ -318,24 +327,31 @@ module JamRuby def set_custom_weight(track) weight = 5 - case track.instrument_id - when 'electric guitar' - weight = 1 - when 'acoustic guitar' - weight = 2 - when 'drums' - weight = 3 - when 'keys' - weight = 4 - when 'computer' - weight = 10 - else - weight = 5 - end - if track.track_type == 'Master' - weight = 1000 + # if there are any persisted tracks, do not sort from scratch; just stick new stuff at the end + + if track.persisted? + weight = track.position + else + case track.instrument_id + when 'electric guitar' + weight = 100 + when 'acoustic guitar' + weight = 200 + when 'drums' + weight = 300 + when 'keys' + weight = 400 + when 'computer' + weight = 600 + else + weight = 500 + end + if track.track_type == 'Master' + weight = 1000 + end end + weight end @@ -346,10 +362,19 @@ module JamRuby a_weight <=> b_weight end + # default to 1, but if there are any persisted tracks, this will get manipulated to be +1 the highest persisted track position = 1 sorted_tracks.each do |track| - track.position = position - position = position + 1 + if track.persisted? + # persisted tracks should be sorted at the beginning of the sorted_tracks, + # so this just keeps moving the 'position builder' up to +1 of the last persisted track + position = track.position + 1 + else + track.position = position + position = position + 1 + end + + end sorted_tracks[sorted_tracks.length - 1].position = 1000 @@ -359,11 +384,40 @@ module JamRuby def synchronize_audio(jam_track, metadata, s3_path, skip_audio_upload) + attempt_to_match_existing_tracks = true + + # find all wav files in the JamTracks s3 bucket wav_files = fetch_wav_files(s3_path) tracks = [] wav_files.each do |wav_file| + + if attempt_to_match_existing_tracks + # try to find a matching track from the JamTrack based on the name of the 44.1 path + basename = File.basename(wav_file) + ogg_44100_filename = File.basename(basename, ".wav") + "-44100.ogg" + + found_track = nil + jam_track.jam_track_tracks.each do |jam_track_track| + + if jam_track_track["url_44"] && jam_track_track["url_44"].end_with?(ogg_44100_filename) + # found a match! + found_track = jam_track_track + break + end + end + + if found_track + @@log.debug("found a existing track to reuse") + found_track.original_audio_s3_path = wav_file + tracks << found_track + next + end + end + + @@log.debug("no existing track found; creating a new one") + track = JamTrackTrack.new track.original_audio_s3_path = wav_file @@ -388,6 +442,15 @@ module JamRuby tracks << track end + jam_track.jam_track_tracks.each do |jam_track_track| + # delete all jam_track_tracks not in the tracks array + unless tracks.include?(jam_track_track) + @@log.info("destroying removed JamTrackTrack #{jam_track_track.inspect}") + jam_track_track.destroy # should also delete s3 files associated with this jamtrack + end + end + + @@log.info("sorting tracks") tracks = sort_tracks(tracks) jam_track.jam_track_tracks = tracks @@ -481,15 +544,16 @@ module JamRuby 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 - preview_succeeded = synchronize_master_preview(track, tmp_dir, ogg_44100, ogg_44100_digest) if track.track_type == 'Master' + if track.track_type == 'Master' + preview_succeeded = synchronize_master_preview(track, tmp_dir, ogg_44100, ogg_44100_digest) - if !preview_succeeded - return false + if !preview_succeeded + return false + end end - - end track.save! @@ -596,7 +660,7 @@ module JamRuby original_artist = parsed_metalocation[1] name = parsed_metalocation[2] - success = synchronize_metadata(jam_track, metadata, metalocation, original_artist, name) + success = synchronize_metadata(jam_track, metadata, metalocation, original_artist, name, options) return unless success @@ -614,8 +678,8 @@ module JamRuby def synchronize_recurly(jam_track) begin recurly = RecurlyClient.new - # no longer create JamTrack plans: VRFS-3028 - # recurly.create_jam_track_plan(jam_track) unless recurly.find_jam_track_plan(jam_track) + # no longer create JamTrack plans: VRFS-3028 + # recurly.create_jam_track_plan(jam_track) unless recurly.find_jam_track_plan(jam_track) rescue RecurlyClientError => x finish('recurly_create_plan', x.errors.to_s) return false diff --git a/ruby/lib/jam_ruby/jam_tracks_manager.rb b/ruby/lib/jam_ruby/jam_tracks_manager.rb index 9322c3dbd..36a19c960 100644 --- a/ruby/lib/jam_ruby/jam_tracks_manager.rb +++ b/ruby/lib/jam_ruby/jam_tracks_manager.rb @@ -22,9 +22,21 @@ module JamRuby save_jam_track_right_jkz(jam_track_right, sample_rate) end + # increment the step, which causes a notification to be sent to the client so it can keep the UI fresh as the packaging step goes on + def bump_step(jam_track_right, step) + last_step_at = Time.now + jam_track_right.current_packaging_step = step + jam_track_right.last_step_at = Time.now + JamTrackRight.where(:id => jam_track_right.id).update_all(last_step_at: last_step_at, current_packaging_step: step) + SubscriptionMessage.jam_track_signing_job_change(jam_track_right) + step = step + 1 + step + end + def save_jam_track_right_jkz(jam_track_right, sample_rate=48) jam_track = jam_track_right.jam_track py_root = APP_CONFIG.jamtracks_dir + step = 0 Dir.mktmpdir do |tmp_dir| jam_file_opts="" jam_track.jam_track_tracks.each do |jam_track_track| @@ -36,6 +48,9 @@ module JamRuby track_filename = File.join(tmp_dir, nm) track_url = jam_track_track.sign_url(120, sample_rate) @@log.info("downloading #{track_url} to #{track_filename}") + + 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}")}" end @@ -48,6 +63,8 @@ module JamRuby version = jam_track.version @@log.info "Executing python source in #{py_file}, outputting to #{tmp_dir} (#{output_jkz})" + step = bump_step(jam_track_right, step) + # From http://stackoverflow.com/questions/690151/getting-output-of-system-calls-in-ruby/5970819#5970819: cli = "python #{py_file} -D -k #{sku} -p #{Shellwords.escape(tmp_dir)}/pkey.pem -s #{Shellwords.escape(tmp_dir)}/skey.pem #{jam_file_opts} -o #{Shellwords.escape(output_jkz)} -t #{Shellwords.escape(title)} -V #{Shellwords.escape(version)}" Open3.popen3(cli) do |stdin, stdout, stderr, wait_thr| diff --git a/ruby/lib/jam_ruby/lib/subscription_message.rb b/ruby/lib/jam_ruby/lib/subscription_message.rb index ec161ec8f..6be9f7d16 100644 --- a/ruby/lib/jam_ruby/lib/subscription_message.rb +++ b/ruby/lib/jam_ruby/lib/subscription_message.rb @@ -22,7 +22,7 @@ module JamRuby end def self.jam_track_signing_job_change(jam_track_right) - Notification.send_subscription_message('jam_track_right', jam_track_right.id.to_s, {signing_state: jam_track_right.signing_state}.to_json ) + Notification.send_subscription_message('jam_track_right', jam_track_right.id.to_s, {signing_state: jam_track_right.signing_state, current_packaging_step: jam_track_right.current_packaging_step, packaging_steps: jam_track_right.packaging_steps}.to_json ) end end end diff --git a/ruby/lib/jam_ruby/models/active_music_session.rb b/ruby/lib/jam_ruby/models/active_music_session.rb index 04724695c..2addd2266 100644 --- a/ruby/lib/jam_ruby/models/active_music_session.rb +++ b/ruby/lib/jam_ruby/models/active_music_session.rb @@ -7,7 +7,7 @@ module JamRuby self.table_name = 'active_music_sessions' - attr_accessor :legal_terms, :max_score, :opening_jam_track, :opening_recording, :opening_backing_track, :opening_metronome + attr_accessor :legal_terms, :max_score, :opening_jam_track, :opening_recording, :opening_backing_track, :opening_metronome, :jam_track_id belongs_to :claimed_recording, :class_name => "JamRuby::ClaimedRecording", :foreign_key => "claimed_recording_id", :inverse_of => :playing_sessions belongs_to :claimed_recording_initiator, :class_name => "JamRuby::User", :inverse_of => :playing_claimed_recordings, :foreign_key => "claimed_recording_initiator_id" diff --git a/ruby/lib/jam_ruby/models/after_signup_hint.rb b/ruby/lib/jam_ruby/models/after_signup_hint.rb deleted file mode 100644 index e5c364e7e..000000000 --- a/ruby/lib/jam_ruby/models/after_signup_hint.rb +++ /dev/null @@ -1,15 +0,0 @@ -module JamRuby - class AfterSignupHint < ActiveRecord::Base - - before_create :generate_lookup_id - - def self.delete_old - FacebookSignup.where("created_at < :week", {:week => 1.week.ago}).delete_all - end - - private - def generate_lookup_id - self.lookup_id = SecureRandom.urlsafe_base64 - end - end -end diff --git a/ruby/lib/jam_ruby/models/anonymous_user.rb b/ruby/lib/jam_ruby/models/anonymous_user.rb index 0a843185a..bd3037922 100644 --- a/ruby/lib/jam_ruby/models/anonymous_user.rb +++ b/ruby/lib/jam_ruby/models/anonymous_user.rb @@ -11,7 +11,7 @@ module JamRuby end def shopping_carts - ShoppingCart.where(anonymous_user_id: @id) + ShoppingCart.where(anonymous_user_id: @id).order('created_at DESC') end def destroy_all_shopping_carts diff --git a/ruby/lib/jam_ruby/models/band.rb b/ruby/lib/jam_ruby/models/band.rb index cfd554d2b..b4232553f 100644 --- a/ruby/lib/jam_ruby/models/band.rb +++ b/ruby/lib/jam_ruby/models/band.rb @@ -163,14 +163,14 @@ module JamRuby # ensure person creating this Band is a Musician unless user.musician? - raise PermissionError, "must be a musician" + raise JamPermissionError, "must be a musician" end band = id.blank? ? Band.new : Band.find(id) # ensure user updating Band details is a Band member unless band.new_record? || band.users.exists?(user) - raise PermissionError, ValidationMessages::USER_NOT_BAND_MEMBER_VALIDATION_ERROR + raise JamPermissionError, ValidationMessages::USER_NOT_BAND_MEMBER_VALIDATION_ERROR end band.name = params[:name] if params.has_key?(:name) diff --git a/ruby/lib/jam_ruby/models/claimed_recording.rb b/ruby/lib/jam_ruby/models/claimed_recording.rb index 7aa73440a..462ce6b58 100644 --- a/ruby/lib/jam_ruby/models/claimed_recording.rb +++ b/ruby/lib/jam_ruby/models/claimed_recording.rb @@ -41,7 +41,7 @@ module JamRuby # params is a hash, and everything is optional def update_fields(user, params) if user != self.user - raise PermissionError, "user doesn't own claimed_recording" + raise JamPermissionError, "user doesn't own claimed_recording" end self.name = params[:name] @@ -53,7 +53,7 @@ module JamRuby def discard(user) if user != self.user - raise PermissionError, "user doesn't own claimed_recording" + raise JamPermissionError, "user doesn't own claimed_recording" end ClaimedRecording.where(:id => id).update_all(:discarded => true ) @@ -96,11 +96,11 @@ module JamRuby target_user = params[:user] - raise PermissionError, "must specify current user" unless user + raise JamPermissionError, "must specify current user" unless user raise "user must be specified" unless target_user if target_user != user.id - raise PermissionError, "unable to view another user's favorites" + raise JamPermissionError, "unable to view another user's favorites" end query = ClaimedRecording.limit(limit).order('created_at DESC').offset(start) diff --git a/ruby/lib/jam_ruby/models/jam_track.rb b/ruby/lib/jam_ruby/models/jam_track.rb index b08533c1e..594358ee3 100644 --- a/ruby/lib/jam_ruby/models/jam_track.rb +++ b/ruby/lib/jam_ruby/models/jam_track.rb @@ -60,7 +60,10 @@ module JamRuby # has_many :plays, :class_name => "JamRuby::PlayablePlay", :foreign_key => :jam_track_id, :dependent => :destroy # VRFS-2916 jam_tracks.id is varchar: ADD has_many :plays, :class_name => "JamRuby::PlayablePlay", :as => :playable, :dependent => :destroy - + + # when we know what JamTrack this refund is related to, these are associated + belongs_to :recurly_transactions, class_name: 'JamRuby::RecurlyTransactionWebHook' + accepts_nested_attributes_for :jam_track_tracks, allow_destroy: true accepts_nested_attributes_for :jam_track_tap_ins, allow_destroy: true @@ -161,21 +164,75 @@ module JamRuby query = query.where("original_artist=?", options[:artist]) end + if options[:id].present? + query = query.where("jam_tracks.id=?", options[:id]) + end if options[:group_artist] query = query.select("original_artist, array_agg(jam_tracks.id) AS id, MIN(name) AS name, MIN(description) AS description, MIN(recording_type) AS recording_type, MIN(original_artist) AS original_artist, MIN(songwriter) AS songwriter, MIN(publisher) AS publisher, MIN(sales_region) AS sales_region, MIN(price) AS price, MIN(version) AS version, MIN(genre_id) AS genre_id") query = query.group("original_artist") query = query.order('jam_tracks.original_artist') else - query = query.group("jam_tracks.id") - query = query.order('jam_tracks.name') + query = query.group("jam_tracks.id") + query = query.order('jam_tracks.original_artist, jam_tracks.name') end - + query = query.where("jam_tracks.status = ?", 'Production') unless user.admin query = query.where("jam_tracks.genre_id = '#{options[:genre]}'") unless options[:genre].blank? query = query.where("jam_track_tracks.instrument_id = '#{options[:instrument]}'") unless options[:instrument].blank? - query = query.where("jam_tracks.sales_region = '#{options[:availability]}'") unless options[:availability].blank? - + query = query.where("jam_tracks.sales_region = '#{options[:availability]}'") unless options[:availability].blank? + + + if query.length == 0 + [query, nil] + elsif query.length < limit + [query, nil] + else + [query, start + limit] + end + end + + + # provides artist names and how many jamtracks are available for each + def artist_index(options, user) + if options[:page] + page = options[:page].to_i + per_page = options[:per_page].to_i + + if per_page == 0 + # try and see if limit was specified + limit = options[:limit] + limit ||= 100 + limit = limit.to_i + else + limit = per_page + end + + start = (page -1 )* per_page + limit = per_page + else + limit = options[:limit] + limit ||= 100 + limit = limit.to_i + + start = options[:start].presence + start = start.to_i || 0 + + page = 1 + start/limit + per_page = limit + end + + + query = JamTrack.paginate(page: page, per_page: per_page) + query = query.select("original_artist, count(original_artist) AS song_count") + query = query.group("original_artist") + query = query.order('jam_tracks.original_artist') + + query = query.where("jam_tracks.status = ?", 'Production') unless user.admin + query = query.where("jam_tracks.genre_id = '#{options[:genre]}'") unless options[:genre].blank? + query = query.where("jam_track_tracks.instrument_id = '#{options[:instrument]}'") unless options[:instrument].blank? + query = query.where("jam_tracks.sales_region = '#{options[:availability]}'") unless options[:availability].blank? + if query.length == 0 [query, nil] @@ -192,6 +249,10 @@ module JamRuby 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') + end + def can_download?(user) owners.include?(user) end diff --git a/ruby/lib/jam_ruby/models/jam_track_right.rb b/ruby/lib/jam_ruby/models/jam_track_right.rb index f5abb1353..a5e127d78 100644 --- a/ruby/lib/jam_ruby/models/jam_track_right.rb +++ b/ruby/lib/jam_ruby/models/jam_track_right.rb @@ -29,7 +29,7 @@ module JamRuby # try to catch major transitions: # if just queue time changes, start time changes, or signed time changes, send out a notice - if signing_queued_at_was != signing_queued_at || signing_started_at_was != signing_started_at || last_signed_at_was != last_signed_at + if signing_queued_at_was != signing_queued_at || signing_started_at_was != signing_started_at || last_signed_at_was != last_signed_at || current_packaging_step != current_packaging_step_was || packaging_steps != packaging_steps_was SubscriptionMessage.jam_track_signing_job_change(self) end end @@ -148,7 +148,11 @@ module JamRuby if signed state = 'SIGNED' elsif signing_started_at - if Time.now - signing_started_at > APP_CONFIG.signing_job_run_max_time + # the maximum amount of time the packaging job can take is 10 seconds * num steps. For a 10 track song, this will be 110 seconds. It's a bit long. + signing_job_run_max_time = packaging_steps * 10 + if Time.now - signing_started_at > signing_job_run_max_time + state = 'SIGNING_TIMEOUT' + elsif Time.now - last_step_at > APP_CONFIG.signing_step_max_time state = 'SIGNING_TIMEOUT' else state = 'SIGNING' @@ -169,6 +173,10 @@ module JamRuby def update_download_count(count=1) self.download_count = self.download_count + count self.last_downloaded_at = Time.now + + if self.signed + self.downloaded_since_sign = true + end end def self.list_keys(user, jamtracks) diff --git a/ruby/lib/jam_ruby/models/jam_track_track.rb b/ruby/lib/jam_ruby/models/jam_track_track.rb index 41514b2f8..2b74a5c53 100644 --- a/ruby/lib/jam_ruby/models/jam_track_track.rb +++ b/ruby/lib/jam_ruby/models/jam_track_track.rb @@ -10,6 +10,7 @@ module JamRuby @@log = Logging.logger[JamTrackTrack] + before_destroy :delete_s3_files # Because JamTrackImporter imports audio files now, and because also the mere presence of this causes serious issues when updating the model (because reset of url_44 to something bogus), I've removed these #mount_uploader :url_48, JamTrackTrackUploader @@ -20,6 +21,8 @@ module JamRuby attr_accessor :original_audio_s3_path, :skip_uploader + before_destroy :delete_s3_files + validates :position, presence: true, numericality: {only_integer: true}, length: {in: 1..1000} validates :part, length: {maximum: 25} validates :track_type, inclusion: {in: TRACK_TYPE } @@ -120,6 +123,13 @@ module JamRuby end end + def delete_s3_files + s3_manager.delete(self[:url_44]) if self[:url_44] && s3_manager.exists?(self[:url_44]) + s3_manager.delete(self[:url_48]) if self[:url_48] && s3_manager.exists?(self[:url_48]) + s3_public_manager.delete(self[:preview_url]) if self[:preview_url] && s3_public_manager.exists?(self[:preview_url]) + s3_public_manager.delete(self[:preview_mp3_url]) if self[:preview_mp3_url] && s3_public_manager.exists?(self[:preview_mp3_url]) + end + private def normalize_position parent = self.jam_track diff --git a/ruby/lib/jam_ruby/models/mix.rb b/ruby/lib/jam_ruby/models/mix.rb index d5c7bd364..6e894561a 100644 --- a/ruby/lib/jam_ruby/models/mix.rb +++ b/ruby/lib/jam_ruby/models/mix.rb @@ -4,6 +4,8 @@ module JamRuby MAX_MIX_TIME = 7200 # 2 hours + @@log = Logging.logger[Mix] + before_destroy :delete_s3_files self.primary_key = 'id' @@ -137,15 +139,67 @@ module JamRuby one_day = 60 * 60 * 24 jam_track_offset = 0 + jam_track_seek = 0 + + was_jamtrack_played = false if recording.timeline recording_timeline_data = JSON.parse(recording.timeline) + # did the jam track play at all? + jam_track_isplaying = recording_timeline_data["jam_track_isplaying"] recording_start_time = recording_timeline_data["recording_start_time"] jam_track_play_start_time = recording_timeline_data["jam_track_play_start_time"] jam_track_recording_start_play_offset = recording_timeline_data["jam_track_recording_start_play_offset"] - jam_track_offset = -jam_track_recording_start_play_offset + if jam_track_play_start_time != 0 + was_jamtrack_played = true + + # how long did the JamTrack play? not needed because we limit on the input tracks, which represents how long the recording is, too + jam_track_play_time = recording_timeline_data["jam_track_play_time"] + + + offset = jam_track_play_start_time - recording_start_time + + @@log.debug("base offset = #{offset}") + if offset >= 0 + # jamtrack started after recording, so buffer with silence as necessary\ + + if jam_track_recording_start_play_offset < 0 + @@log.info("prelude captured. offsetting further by #{-jam_track_recording_start_play_offset}") + # a negative jam_track_recording_start_play_offset indicates prelude, i.e., silence + # so add it to the offset to add more silence as necessary + offset = offset + -jam_track_recording_start_play_offset + jam_track_offset = offset + else + @@log.info("positive jamtrack offset; seeking into jamtrack by #{jam_track_recording_start_play_offset}") + # a positive jam_track_recording_start_play_offset means we need to cut into the jamtrack + jam_track_seek = jam_track_recording_start_play_offset + jam_track_offset = offset + end + else + # jamtrack started before recording, so we can seek into it to make up for the missing parts + + if jam_track_recording_start_play_offset < 0 + @@log.info("partial prelude captured. offset becomes jamtrack offset#{-jam_track_recording_start_play_offset}") + # a negative jam_track_recording_start_play_offset indicates prelude, i.e., silence + # so add it to the offset to add more silence as necessary + jam_track_offset = -jam_track_recording_start_play_offset + else + @@log.info("no prelude captured. offset becomes jamtrack offset=#{jam_track_recording_start_play_offset}") + + jam_track_offset = 0 + jam_track_seek = jam_track_recording_start_play_offset + end + + + # also, ignore jam_track_recording_start_play_offset - it simply matches the offset in this case + end + + @@log.info("computed values. jam_track_offset=#{jam_track_offset} jam_track_seek=#{jam_track_seek}") + + + end end manifest = { "files" => [], "timeline" => [] } @@ -154,7 +208,7 @@ module JamRuby # this 'pick limiter' logic will ensure that we set a limiter on the 1st recorded_track we come across. pick_limiter = false - if recording.is_jamtrack_recording? + if was_jamtrack_played # we only use the limiter feature if this is a JamTrack recording # by setting this to true, the 1st recorded_track in the database will be the limiter pick_limiter = true @@ -171,27 +225,29 @@ module JamRuby mix_params << { "level" => 1.0, "balance" => 0 } end - recording.recorded_jam_track_tracks.each do |recorded_jam_track_track| - manifest["files"] << { "filename" => recorded_jam_track_track.jam_track_track.sign_url(one_day), "codec" => "vorbis", "offset" => jam_track_offset } - # let's look for level info from the client - level = 1.0 # default value - means no effect - if recorded_jam_track_track.timeline + if was_jamtrack_played + recording.recorded_jam_track_tracks.each do |recorded_jam_track_track| + manifest["files"] << { "filename" => recorded_jam_track_track.jam_track_track.sign_url(one_day, sample_rate=44), "codec" => "vorbis", "offset" => jam_track_offset, "seek" => jam_track_seek } + # let's look for level info from the client + level = 1.0 # default value - means no effect + if recorded_jam_track_track.timeline - timeline_data = JSON.parse(recorded_jam_track_track.timeline) + timeline_data = JSON.parse(recorded_jam_track_track.timeline) - # always take the 1st entry for now - first = timeline_data[0] + # always take the 1st entry for now + first = timeline_data[0] - if first["mute"] - # mute equates to no noise - level = 0.0 - else - # otherwise grab the left channel... - level = first["vol_l"] + if first["mute"] + # mute equates to no noise + level = 0.0 + else + # otherwise grab the left channel... + level = first["vol_l"] + end end - end - mix_params << { "level" => level, "balance" => 0 } + mix_params << { "level" => level, "balance" => 0 } + end end manifest["timeline"] << { "timestamp" => 0, "mix" => mix_params } diff --git a/ruby/lib/jam_ruby/models/notification.rb b/ruby/lib/jam_ruby/models/notification.rb index b0cb8a3bb..db4018ab8 100644 --- a/ruby/lib/jam_ruby/models/notification.rb +++ b/ruby/lib/jam_ruby/models/notification.rb @@ -210,7 +210,7 @@ module JamRuby return "New message about session." when NotificationTypes::JAM_TRACK_SIGN_COMPLETE - return "Jam Track is ready for download." + return "JamTrack is ready for download." # recording notifications when NotificationTypes::MUSICIAN_RECORDING_SAVED diff --git a/ruby/lib/jam_ruby/models/payment_history.rb b/ruby/lib/jam_ruby/models/payment_history.rb new file mode 100644 index 000000000..4862e4dd1 --- /dev/null +++ b/ruby/lib/jam_ruby/models/payment_history.rb @@ -0,0 +1,39 @@ +module JamRuby + class PaymentHistory < ActiveRecord::Base + + self.table_name = 'payment_histories' + + belongs_to :sale + belongs_to :recurly_transaction_web_hook + + + def self.index(user, params = {}) + + limit = params[:per_page] + limit ||= 20 + limit = limit.to_i + + query = PaymentHistory.limit(limit) + .includes(sale: [:sale_line_items], recurly_transaction_web_hook:[]) + .where(user_id: user.id) + .where("transaction_type = 'sale' OR transaction_type = 'refund' OR transaction_type = 'void'") + .order('created_at DESC') + + + current_page = params[:page].nil? ? 1 : params[:page].to_i + next_page = current_page + 1 + + # will_paginate gem + query = query.paginate(:page => current_page, :per_page => limit) + + if query.length == 0 # no more results + { query: query, next_page: nil} + elsif query.length < limit # no more results + { query: query, next_page: nil} + else + { query: query, next_page: next_page } + end + + end + end +end \ No newline at end of file diff --git a/ruby/lib/jam_ruby/models/recording.rb b/ruby/lib/jam_ruby/models/recording.rb index cdb72816a..99f21c09e 100644 --- a/ruby/lib/jam_ruby/models/recording.rb +++ b/ruby/lib/jam_ruby/models/recording.rb @@ -3,7 +3,7 @@ module JamRuby @@log = Logging.logger[Recording] - attr_accessible :owner, :owner_id, :band, :band_id, :recorded_tracks_attributes, :mixes_attributes, :claimed_recordings_attributes, :name, :description, :genre, :is_public, :duration, as: :admin + attr_accessible :owner, :owner_id, :band, :band_id, :recorded_tracks_attributes, :mixes_attributes, :claimed_recordings_attributes, :name, :description, :genre, :is_public, :duration, :jam_track_id, as: :admin has_many :users, :through => :recorded_tracks, :class_name => "JamRuby::User" has_many :claimed_recordings, :class_name => "JamRuby::ClaimedRecording", :inverse_of => :recording, :foreign_key => 'recording_id', :dependent => :destroy @@ -21,6 +21,7 @@ module JamRuby belongs_to :owner, :class_name => "JamRuby::User", :inverse_of => :owned_recordings, :foreign_key => 'owner_id' belongs_to :band, :class_name => "JamRuby::Band", :inverse_of => :recordings belongs_to :music_session, :class_name => "JamRuby::ActiveMusicSession", :inverse_of => :recordings, foreign_key: :music_session_id + belongs_to :non_active_music_session, :class_name => "JamRuby::MusicSession", foreign_key: :music_session_id belongs_to :jam_track, :class_name => "JamRuby::JamTrack", :inverse_of => :recordings, :foreign_key => 'jam_track_id' belongs_to :jam_track_initiator, :class_name => "JamRuby::User", :inverse_of => :initiated_jam_track_recordings, :foreign_key => 'jam_track_initiator_id' @@ -50,7 +51,11 @@ module JamRuby end def is_jamtrack_recording? - !jam_track_id.nil? + !jam_track_id.nil? && parsed_timeline['jam_track_isplaying'] + end + + def parsed_timeline + timeline ? JSON.parse(timeline) : {} end def high_quality_mix? @@ -182,21 +187,21 @@ module JamRuby def recorded_tracks_for_user(user) unless self.users.exists?(user) - raise PermissionError, "user was not in this session" + raise JamPermissionError, "user was not in this session" end recorded_tracks.where(:user_id => user.id) end def recorded_backing_tracks_for_user(user) unless self.users.exists?(user) - raise PermissionError, "user was not in this session" + raise JamPermissionError, "user was not in this session" end recorded_backing_tracks.where(:user_id => user.id) end def has_access?(user) - users.exists?(user) + users.exists?(user) || plays.where("player_id=?", user).count != 0 end # Start recording a session. @@ -264,7 +269,7 @@ module JamRuby def claim(user, name, description, genre, is_public, upload_to_youtube=false) upload_to_youtube = !!upload_to_youtube # Correct where nil is borking save unless self.users.exists?(user) - raise PermissionError, "user was not in this session" + raise JamPermissionError, "user was not in this session" end claimed_recording = ClaimedRecording.new @@ -710,23 +715,26 @@ module JamRuby end end + def self.popular_recordings(limit = 100) + Recording.select('recordings.id').joins('inner join claimed_recordings ON claimed_recordings.recording_id = recordings.id AND claimed_recordings.is_public = TRUE').where(all_discarded: false).where(is_done: true).where(deleted: false).order('play_count DESC').limit(limit).group('recordings.id') + end private def self.validate_user_is_band_member(user, band) unless band.users.exists? user - raise PermissionError, ValidationMessages::USER_NOT_BAND_MEMBER_VALIDATION_ERROR + raise JamPermissionError, ValidationMessages::USER_NOT_BAND_MEMBER_VALIDATION_ERROR end end def self.validate_user_is_creator(user, creator) unless user.id == creator.id - raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR + raise JamPermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR end end def self.validate_user_is_musician(user) unless user.musician? - raise PermissionError, ValidationMessages::USER_NOT_MUSICIAN_VALIDATION_ERROR + raise JamPermissionError, ValidationMessages::USER_NOT_MUSICIAN_VALIDATION_ERROR end end diff --git a/ruby/lib/jam_ruby/models/recurly_transaction_web_hook.rb b/ruby/lib/jam_ruby/models/recurly_transaction_web_hook.rb index d94dca2e0..05824e322 100644 --- a/ruby/lib/jam_ruby/models/recurly_transaction_web_hook.rb +++ b/ruby/lib/jam_ruby/models/recurly_transaction_web_hook.rb @@ -1,10 +1,15 @@ module JamRuby - class RecurlyTransactionWebHook < ActiveRecord::Base + class RecurlyTransactionWebHook < ActiveRecord::Base + + attr_accessible :admin_description, :jam_track_id, as: :admin belongs_to :user, class_name: 'JamRuby::User' belongs_to :sale_line_item, class_name: 'JamRuby::SaleLineItem', foreign_key: 'subscription_id', primary_key: 'recurly_subscription_uuid', inverse_of: :recurly_transactions belongs_to :sale, class_name: 'JamRuby::Sale', foreign_key: 'invoice_id', primary_key: 'recurly_invoice_id', inverse_of: :recurly_transactions + # when we know what JamTrack this refund is related to, we set this value + belongs_to :jam_track, class_name: 'JamRuby::JamTrack' + validates :recurly_transaction_id, presence: true validates :action, presence: true validates :status, presence: true @@ -17,6 +22,9 @@ module JamRuby REFUND = 'refund' VOID = 'void' + + HOOK_TYPES = [SUCCESSFUL_PAYMENT, FAILED_PAYMENT, REFUND, VOID] + def is_credit_type? transaction_type == REFUND || transaction_type == VOID end @@ -46,6 +54,10 @@ module JamRuby end end + def admin_url + APP_CONFIG.admin_root_url + "/admin/recurly_hooks/" + id + end + # see spec for examples of XML def self.create_from_xml(document) @@ -81,6 +93,7 @@ module JamRuby # 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) @@ -91,33 +104,44 @@ module JamRuby jam_track_right = jam_track.right_for_user(transaction.user) if jam_track if jam_track_right jam_track_right.destroy - AdminMailer.alerts({ - subject:"NOTICE: #{transaction.user.email} has had JamTrack: #{jam_track.name} revoked", - body: "A void 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 + + # 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.alerts({ - 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 + 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.alerts({ - subject:"ACTION REQUIRED: #{transaction.user.email} got a refund it was not for total value of a JamTrack sale", - body: "We received a refund 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 + 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.alerts({ - 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 + 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", + body: "You will have to manually revoke any JamTrackRights in our database for the appropriate JamTracks" + }).deliver end + end transaction end diff --git a/ruby/lib/jam_ruby/models/rsvp_request.rb b/ruby/lib/jam_ruby/models/rsvp_request.rb index 9f233b973..f416640bf 100644 --- a/ruby/lib/jam_ruby/models/rsvp_request.rb +++ b/ruby/lib/jam_ruby/models/rsvp_request.rb @@ -62,7 +62,7 @@ module JamRuby invitation = Invitation.where("music_session_id = ? AND receiver_id = ?", music_session.id, user.id) if invitation.first.nil? && !music_session.open_rsvps && music_session.creator.id != user.id - raise PermissionError, "Only a session invitee can create an RSVP for this session." + raise JamPermissionError, "Only a session invitee can create an RSVP for this session." end RsvpRequest.transaction do @@ -154,7 +154,7 @@ module JamRuby # authorize the user attempting to respond to the RSVP request if music_session.creator.id != user.id - raise PermissionError, "Only the session organizer can accept or decline an RSVP request." + raise JamPermissionError, "Only the session organizer can accept or decline an RSVP request." end rsvp_request = RsvpRequest.find_by_id(rsvp_request_id) @@ -249,7 +249,7 @@ module JamRuby rsvp_request = RsvpRequest.find(params[:id]) if music_session.creator.id != user.id && rsvp_request.user_id != user.id - raise PermissionError, "Only the session organizer or RSVP creator can cancel the RSVP." + raise JamPermissionError, "Only the session organizer or RSVP creator can cancel the RSVP." end RsvpRequest.transaction do diff --git a/ruby/lib/jam_ruby/models/shopping_cart.rb b/ruby/lib/jam_ruby/models/shopping_cart.rb index ed74d2490..dc975bd40 100644 --- a/ruby/lib/jam_ruby/models/shopping_cart.rb +++ b/ruby/lib/jam_ruby/models/shopping_cart.rb @@ -12,7 +12,7 @@ module JamRuby attr_accessible :quantity, :cart_type, :product_info - validates_uniqueness_of :cart_id, scope: :cart_type + 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" @@ -25,7 +25,7 @@ module JamRuby 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} unless product.nil? + {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? end # multiply quantity by price @@ -111,6 +111,15 @@ module JamRuby end end + 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) + end + + anonymous_user.destroy_all_shopping_carts + end + def self.is_product_purchase?(adjustment) (adjustment[:accounting_code].include?(PURCHASE_FREE) || adjustment[:accounting_code].include?(PURCHASE_NORMAL)) && !adjustment[:accounting_code].include?(PURCHASE_FREE_CREDIT) end diff --git a/ruby/lib/jam_ruby/models/signup_hint.rb b/ruby/lib/jam_ruby/models/signup_hint.rb new file mode 100644 index 000000000..b415d45a4 --- /dev/null +++ b/ruby/lib/jam_ruby/models/signup_hint.rb @@ -0,0 +1,36 @@ +module JamRuby + + # some times someone comes to signup as a new user, but there is context to preserve. + # the AnyUser cookie is one way that we can track the user from pre-signup to post-signup + # anyway, once the signup is done, we check to see if there is a SignupHint, and if so, + # we use it to figure out what to do with the user after they signup + class SignupHint < ActiveRecord::Base + + + belongs_to :user, class_name: 'JamRuby::User' + + validates :redirect_location, length: {maximum: 1000} + validates :want_jamblaster, inclusion: {in: [nil, true, false]} + + def self.refresh_by_anoymous_user(anonymous_user, options = {}) + + hint = SignupHint.find_by_anonymous_user_id(anonymous_user.id) + + unless hint + hint = SignupHint.new + end + + hint.anonymous_user_id = anonymous_user.id + hint.redirect_location = options[:redirect_location] if options.has_key?(:redirect_location) + hint.want_jamblaster = options[:want_jamblaster] if options.has_key?(:want_jamblaster) + hint.expires_at = 15.minutes.from_now + hint.save + hint + end + + def self.delete_old + SignupHint.where("created_at < :week", {:week => 1.week.ago}).delete_all + end + + end +end diff --git a/ruby/lib/jam_ruby/models/track.rb b/ruby/lib/jam_ruby/models/track.rb index e430edb2d..1a8db2047 100644 --- a/ruby/lib/jam_ruby/models/track.rb +++ b/ruby/lib/jam_ruby/models/track.rb @@ -113,6 +113,7 @@ module JamRuby result = {} backing_tracks = [] unless backing_tracks + tracks = [] unless tracks Track.transaction do connection = Connection.find_by_client_id!(clientId) diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb index 4183b8b8c..73689167b 100644 --- a/ruby/lib/jam_ruby/models/user.rb +++ b/ruby/lib/jam_ruby/models/user.rb @@ -209,7 +209,7 @@ module JamRuby scope :email_opt_in, where(:subscribe_email => true) def user_progression_fields - @user_progression_fields ||= Set.new ["first_downloaded_client_at", "first_ran_client_at", "first_music_session_at", "first_real_music_session_at", "first_good_music_session_at", "first_certified_gear_at", "first_invited_at", "first_friended_at", "first_recording_at", "first_social_promoted_at" ] + @user_progression_fields ||= Set.new ["first_downloaded_client_at", "first_ran_client_at", "first_music_session_at", "first_real_music_session_at", "first_good_music_session_at", "first_certified_gear_at", "first_invited_at", "first_friended_at", "first_recording_at", "first_social_promoted_at", "first_played_jamtrack_at" ] end def update_progression_field(field_name, time = DateTime.now) @@ -812,7 +812,7 @@ module JamRuby end if user.id != updater_id - raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR + raise JamPermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR end user.easy_save(first_name, last_name, email, password, password_confirmation, musician, gender, @@ -1046,7 +1046,16 @@ module JamRuby user.photo_url = photo_url # copy over the shopping cart to the new user, if a shopping cart is provided - user.shopping_carts = any_user.shopping_carts if any_user + if any_user + user.shopping_carts = any_user.shopping_carts + if user.shopping_carts + user.shopping_carts.each do |shopping_cart| + shopping_cart.anonymous_user_id = nil # nil out the anonymous user ID; required for uniqeness constraint on ShoppingCart + end + end + end + + unless fb_signup.nil? user.update_fb_authorization(fb_signup) @@ -1596,6 +1605,15 @@ module JamRuby verifier.generate(user.id) end + # URL to jam-admin + def admin_url + APP_CONFIG.admin_root_url + "/admin/users/" + id + end + + def jam_track_rights_admin_url + APP_CONFIG.admin_root_url + "/admin/jam_track_rights?q[user_id_equals]=#{id}&commit=Filter&order=created_at DESC" + end + private def create_remember_token self.remember_token = SecureRandom.urlsafe_base64 diff --git a/ruby/lib/jam_ruby/mq_router.rb b/ruby/lib/jam_ruby/mq_router.rb index 826eb1ace..8aec53cde 100644 --- a/ruby/lib/jam_ruby/mq_router.rb +++ b/ruby/lib/jam_ruby/mq_router.rb @@ -17,7 +17,7 @@ class MQRouter end if !music_session.access? user - raise PermissionError, 'not allowed to join the specified session' + raise JamPermissionError, 'not allowed to join the specified session' end return music_session diff --git a/ruby/lib/jam_ruby/resque/audiomixer.rb b/ruby/lib/jam_ruby/resque/audiomixer.rb index fa987b69f..b03bad875 100644 --- a/ruby/lib/jam_ruby/resque/audiomixer.rb +++ b/ruby/lib/jam_ruby/resque/audiomixer.rb @@ -263,6 +263,7 @@ module JamRuby @@log.debug("manifest") @@log.debug("--------") @@log.debug(JSON.pretty_generate(@manifest)) + @@log.debug("--------") @manifest[:mix_id] = mix_id # slip in the mix_id so that the job can add it to the ogg comments diff --git a/ruby/lib/jam_ruby/resque/google_analytics_event.rb b/ruby/lib/jam_ruby/resque/google_analytics_event.rb index 0c1cbbf06..5394744f2 100644 --- a/ruby/lib/jam_ruby/resque/google_analytics_event.rb +++ b/ruby/lib/jam_ruby/resque/google_analytics_event.rb @@ -101,7 +101,9 @@ module JamRuby el: 'data', ev: data.to_s } + RestClient.post(APP_CONFIG.ga_endpoint, params: params, timeout: 8, open_timeout: 8) + @@log.info("done (#{category}, #{action})") end diff --git a/ruby/lib/jam_ruby/resque/jam_tracks_builder.rb b/ruby/lib/jam_ruby/resque/jam_tracks_builder.rb index 395d12084..329a52b4d 100644 --- a/ruby/lib/jam_ruby/resque/jam_tracks_builder.rb +++ b/ruby/lib/jam_ruby/resque/jam_tracks_builder.rb @@ -34,8 +34,20 @@ module JamRuby return end + # compute the step count + total_steps = @jam_track_right.jam_track.stem_tracks.count + 1 # the '1' represents the jkz.py invocation + # track that it's started ( and avoid db validations ) - JamTrackRight.where(:id => @jam_track_right.id).update_all(:signing_started_at => Time.now, :should_retry => false) + signing_started_at = Time.now + last_step_at = Time.now + JamTrackRight.where(:id => @jam_track_right.id).update_all(:signing_started_at => signing_started_at, :should_retry => false, packaging_steps: total_steps, current_packaging_step: 0, last_step_at: last_step_at) + # because we are skiping after_save, we have to keep the model current for the notification. A bit ugly... + @jam_track_right.current_packaging_step = 0 + @jam_track_right.packaging_steps = total_steps + @jam_track_right.signing_started_at = signing_started_at + @jam_track_right.should_retry = false + @jam_track_right.last_step_at = Time.now + SubscriptionMessage.jam_track_signing_job_change(@jam_track_right) JamRuby::JamTracksManager.save_jam_track_right_jkz(@jam_track_right, self.bitrate) # If bitrate is 48 (the default), use that URL. Otherwise, use 44kHz: diff --git a/ruby/lib/jam_ruby/resque/scheduled/cleanup_facebook_signup.rb b/ruby/lib/jam_ruby/resque/scheduled/cleanup_facebook_signup.rb index ddfb6b28f..f46946886 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/cleanup_facebook_signup.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/cleanup_facebook_signup.rb @@ -11,6 +11,7 @@ module JamRuby @@log.debug("waking up") FacebookSignup.delete_old + SignupHint.delete_old @@log.debug("done") end diff --git a/ruby/spec/jam_ruby/jam_track_importer_spec.rb b/ruby/spec/jam_ruby/jam_track_importer_spec.rb index 5a0a89cf4..a2cf96620 100644 --- a/ruby/spec/jam_ruby/jam_track_importer_spec.rb +++ b/ruby/spec/jam_ruby/jam_track_importer_spec.rb @@ -46,7 +46,7 @@ describe JamTrackImporter do let(:options) {{ skip_audio_upload:true }} it "bare minimum specification" do - importer.synchronize_metadata(jam_track, minimum_meta, metalocation, 'Artist 1', 'Song 1') + importer.synchronize_metadata(jam_track, minimum_meta, metalocation, 'Artist 1', 'Song 1', options) jam_track.plan_code.should eq('jamtrack-artist1-song1') jam_track.name.should eq("Song 1") @@ -57,7 +57,7 @@ describe JamTrackImporter do jam_track.original_artist.should eq('Artist 1') jam_track.songwriter.should be_nil jam_track.publisher.should be_nil - jam_track.sales_region.should eq('United States') + jam_track.sales_region.should eq('Worldwide') jam_track.price.should eq(1.99) end end diff --git a/ruby/spec/jam_ruby/models/jam_track_right_spec.rb b/ruby/spec/jam_ruby/models/jam_track_right_spec.rb index 530535f8c..0977703ff 100644 --- a/ruby/spec/jam_ruby/models/jam_track_right_spec.rb +++ b/ruby/spec/jam_ruby/models/jam_track_right_spec.rb @@ -134,12 +134,12 @@ describe JamTrackRight do end it "signing" do - right = FactoryGirl.create(:jam_track_right, signing_started_at: Time.now) + right = FactoryGirl.create(:jam_track_right, signing_started_at: Time.now, packaging_steps: 3, current_packaging_step:0, last_step_at:Time.now) right.signing_state.should eq('SIGNING') end it "signing timeout" do - right = FactoryGirl.create(:jam_track_right, signing_started_at: Time.now - (APP_CONFIG.signing_job_run_max_time + 1)) + right = FactoryGirl.create(:jam_track_right, signing_started_at: Time.now - 100, packaging_steps: 3, current_packaging_step:0, last_step_at:Time.now) right.signing_state.should eq('SIGNING_TIMEOUT') end diff --git a/ruby/spec/jam_ruby/models/jam_track_spec.rb b/ruby/spec/jam_ruby/models/jam_track_spec.rb index 10cec40cd..d17019df7 100644 --- a/ruby/spec/jam_ruby/models/jam_track_spec.rb +++ b/ruby/spec/jam_ruby/models/jam_track_spec.rb @@ -38,6 +38,41 @@ describe JamTrack do end end + describe "artist_index" do + before :each do + JamTrack.delete_all + end + + it "empty query" do + query, pager = JamTrack.artist_index({}, user) + query.size.should == 0 + end + + it "groups" do + jam_track1 = FactoryGirl.create(:jam_track_with_tracks, original_artist: 'artist', name: 'a') + jam_track2 = FactoryGirl.create(:jam_track_with_tracks, original_artist: 'artist', name: 'b') + + query, pager = JamTrack.artist_index({}, user) + query.size.should == 1 + + query[0].original_artist.should eq('artist') + query[0]['song_count'].should eq('2') + end + + it "sorts by name" do + jam_track1 = FactoryGirl.create(:jam_track_with_tracks, original_artist: 'blartist', name: 'a') + jam_track2 = FactoryGirl.create(:jam_track_with_tracks, original_artist: 'artist', name: 'b') + + query, pager = JamTrack.artist_index({}, user) + query.size.should == 2 + + query[0].original_artist.should eq('artist') + query[0]['song_count'].should eq('1') + query[1].original_artist.should eq('blartist') + query[1]['song_count'].should eq('1') + end + end + describe "index" do it "empty query" do query, pager = JamTrack.index({}, user) @@ -45,8 +80,8 @@ describe JamTrack do end it "sorts by name" do - jam_track1 = FactoryGirl.create(:jam_track_with_tracks, name: 'a') - jam_track2 = FactoryGirl.create(:jam_track_with_tracks, name: 'b') + jam_track1 = FactoryGirl.create(:jam_track_with_tracks, original_artist: 'artist', name: 'a') + jam_track2 = FactoryGirl.create(:jam_track_with_tracks, original_artist: 'artist', name: 'b') query, pager = JamTrack.index({}, user) query.size.should == 2 @@ -141,5 +176,4 @@ describe JamTrack do end end end -end - +end \ No newline at end of file diff --git a/ruby/spec/jam_ruby/models/payment_history_spec.rb b/ruby/spec/jam_ruby/models/payment_history_spec.rb new file mode 100644 index 000000000..b28cffec3 --- /dev/null +++ b/ruby/spec/jam_ruby/models/payment_history_spec.rb @@ -0,0 +1,48 @@ +require 'spec_helper' + +describe PaymentHistory do + + let(:user) {FactoryGirl.create(:user)} + let(:user2) {FactoryGirl.create(:user)} + let(:jam_track) {FactoryGirl.create(:jam_track)} + + before(:each) do + + end + + describe "index" do + it "empty" do + result = PaymentHistory.index(user) + result[:query].length.should eq(0) + result[:next].should eq(nil) + end + + it "one" 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, nil, 'some_adjustment_uuid', nil) + + result = PaymentHistory.index(user) + result[:query].length.should eq(1) + result[:next].should eq(nil) + end + + it "user filtered correctly" 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, nil, 'some_adjustment_uuid', nil) + + result = PaymentHistory.index(user) + result[:query].length.should eq(1) + result[:next].should eq(nil) + + sale2 = Sale.create_jam_track_sale(user2) + shopping_cart = ShoppingCart.create(user2, jam_track) + sale_line_item2 = SaleLineItem.create_from_shopping_cart(sale2, shopping_cart, nil, 'some_adjustment_uuid', nil) + + result = PaymentHistory.index(user) + result[:query].length.should eq(1) + result[:next].should eq(nil) + end + end +end diff --git a/ruby/spec/jam_ruby/models/recording_spec.rb b/ruby/spec/jam_ruby/models/recording_spec.rb index 7b64b2144..3de7d09e1 100644 --- a/ruby/spec/jam_ruby/models/recording_spec.rb +++ b/ruby/spec/jam_ruby/models/recording_spec.rb @@ -15,6 +15,29 @@ describe Recording do @track = FactoryGirl.create(:track, :connection => @connection, :instrument => @instrument) end + describe "popular_recordings" do + it "empty" do + Recording.popular_recordings.length.should eq(0) + end + + it "one public recording" do + claim = FactoryGirl.create(:claimed_recording) + + claim.recording.is_done = true + claim.recording.save! + recordings = Recording.popular_recordings + recordings.length.should eq(1) + recordings[0].id.should eq(claim.recording.id) + end + + it "one private recording" do + claim = FactoryGirl.create(:claimed_recording, is_public: true) + + recordings = Recording.popular_recordings + recordings.length.should eq(0) + end + end + describe "cleanup_excessive_storage" do sample_audio='sample.file' diff --git a/ruby/spec/jam_ruby/models/rsvp_request_spec.rb b/ruby/spec/jam_ruby/models/rsvp_request_spec.rb index 0f8aa5874..f93501647 100644 --- a/ruby/spec/jam_ruby/models/rsvp_request_spec.rb +++ b/ruby/spec/jam_ruby/models/rsvp_request_spec.rb @@ -104,7 +104,7 @@ describe RsvpRequest do it "should not allow non-invitee to RSVP to session with closed RSVPs" do @music_session.open_rsvps = false @music_session.save! - expect {RsvpRequest.create({:session_id => @music_session.id, :rsvp_slots => [@slot1.id, @slot2.id]}, @non_session_invitee)}.to raise_error(JamRuby::PermissionError) + expect {RsvpRequest.create({:session_id => @music_session.id, :rsvp_slots => [@slot1.id, @slot2.id]}, @non_session_invitee)}.to raise_error(JamRuby::JamPermissionError) end it "should allow RSVP creation with autoapprove option for open RSVP sessions" do @@ -216,7 +216,7 @@ describe RsvpRequest do # attempt to approve with non-organizer rs1 = RsvpRequestRsvpSlot.find_by_rsvp_slot_id(@slot1.id) rs2 = RsvpRequestRsvpSlot.find_by_rsvp_slot_id(@slot2.id) - expect {RsvpRequest.update({:id => rsvp.id, :session_id => @music_session.id, :rsvp_responses => [{:request_slot_id => rs1.id, :accept => true}, {:request_slot_id => rs2.id, :accept => true}]}, @session_invitee)}.to raise_error(PermissionError) + expect {RsvpRequest.update({:id => rsvp.id, :session_id => @music_session.id, :rsvp_responses => [{:request_slot_id => rs1.id, :accept => true}, {:request_slot_id => rs2.id, :accept => true}]}, @session_invitee)}.to raise_error(JamPermissionError) # approve with organizer rs1 = RsvpRequestRsvpSlot.find_by_rsvp_slot_id(@slot1.id) @@ -402,7 +402,7 @@ describe RsvpRequest do comment.comment.should == "Let's Jam!" # cancel - expect {RsvpRequest.cancel({:id => rsvp.id, :session_id => @music_session.id, :cancelled => "all", :message => "I'm gonna cancel all your RSVPs"}, user)}.to raise_error(PermissionError) + expect {RsvpRequest.cancel({:id => rsvp.id, :session_id => @music_session.id, :cancelled => "all", :message => "I'm gonna cancel all your RSVPs"}, user)}.to raise_error(JamPermissionError) end end diff --git a/ruby/spec/jam_ruby/models/signup_hint_spec.rb b/ruby/spec/jam_ruby/models/signup_hint_spec.rb new file mode 100644 index 000000000..1ec79efef --- /dev/null +++ b/ruby/spec/jam_ruby/models/signup_hint_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe SignupHint do + + let(:user) {AnonymousUser.new(SecureRandom.uuid)} + + describe "refresh_by_anoymous_user" do + it "creates" do + hint = SignupHint.refresh_by_anoymous_user(user, {redirect_location: 'abc'}) + hint.errors.any?.should be_false + hint.redirect_location.should eq('abc') + hint.want_jamblaster.should be_false + end + + it "updated" do + SignupHint.refresh_by_anoymous_user(user, {redirect_location: 'abc'}) + + hint = SignupHint.refresh_by_anoymous_user(user, {redirect_location: nil, want_jamblaster: true}) + hint.errors.any?.should be_false + hint.redirect_location.should be_nil + hint.want_jamblaster.should be_true + end + end +end diff --git a/ruby/spec/support/utilities.rb b/ruby/spec/support/utilities.rb index 93222cf6c..c033de858 100644 --- a/ruby/spec/support/utilities.rb +++ b/ruby/spec/support/utilities.rb @@ -3,10 +3,18 @@ JAMKAZAM_TESTING_BUCKET = 'jamkazam-testing' # cuz i'm not comfortable using aws def app_config klass = Class.new do + def admin_root_url + 'http://localhost:3333' + end + def email_alerts_alias 'alerts@jamkazam.com' end + def email_recurly_notice + 'recurly-alerts@jamkazam.com' + end + def email_generic_from 'nobody@jamkazam.com' end @@ -166,8 +174,8 @@ def app_config false end - def signing_job_run_max_time - 60 # 1 minute + def signing_step_max_time + 40 # 40 seconds end def signing_job_queue_max_time @@ -182,6 +190,9 @@ def app_config 'foobar' end + def unsubscribe_token + 'blah' + end private diff --git a/web/.gitignore b/web/.gitignore index 48289754a..e305498b8 100644 --- a/web/.gitignore +++ b/web/.gitignore @@ -23,6 +23,7 @@ doc/ .idea *.iml +jt_metadata.json artifacts diff --git a/web/Gemfile b/web/Gemfile index 73bbc90f9..e53c64492 100644 --- a/web/Gemfile +++ b/web/Gemfile @@ -87,6 +87,7 @@ gem 'recurly' gem 'guard', '2.7.3' gem 'influxdb', '0.1.8' gem 'influxdb-rails', '0.1.10' +gem 'sitemap_generator' group :development, :test do gem 'rspec-rails', '2.14.2' diff --git a/web/Rakefile b/web/Rakefile index f4019acfc..882005f42 100644 --- a/web/Rakefile +++ b/web/Rakefile @@ -6,6 +6,7 @@ #require 'resque/scheduler/tasks' require 'resque/tasks' require 'resque/scheduler/tasks' +require 'sitemap_generator/tasks' require File.expand_path('../config/application', __FILE__) SampleApp::Application.load_tasks diff --git a/web/app/assets/images/content/icon-pause-24-black.png b/web/app/assets/images/content/icon-pause-24-black.png new file mode 100644 index 000000000..2f719e15d Binary files /dev/null and b/web/app/assets/images/content/icon-pause-24-black.png differ diff --git a/web/app/assets/images/content/icon-pause-24-gray.png b/web/app/assets/images/content/icon-pause-24-gray.png new file mode 100644 index 000000000..38237b3ad Binary files /dev/null and b/web/app/assets/images/content/icon-pause-24-gray.png differ diff --git a/web/app/assets/images/content/icon-play-24-black.png b/web/app/assets/images/content/icon-play-24-black.png new file mode 100644 index 000000000..e21b8b5f9 Binary files /dev/null and b/web/app/assets/images/content/icon-play-24-black.png differ diff --git a/web/app/assets/images/content/icon-play-24-gray.png b/web/app/assets/images/content/icon-play-24-gray.png new file mode 100644 index 000000000..88c9c10ef Binary files /dev/null and b/web/app/assets/images/content/icon-play-24-gray.png differ diff --git a/web/app/assets/images/content/icon_cam.png b/web/app/assets/images/content/icon_cam.png new file mode 100644 index 000000000..e32bede67 Binary files /dev/null and b/web/app/assets/images/content/icon_cam.png differ diff --git a/web/app/assets/images/content/icon_stopbutton.png b/web/app/assets/images/content/icon_stopbutton.png new file mode 100644 index 000000000..37d19e686 Binary files /dev/null and b/web/app/assets/images/content/icon_stopbutton.png differ diff --git a/web/app/assets/images/web/generic_button_cta.png b/web/app/assets/images/web/generic_button_cta.png new file mode 100644 index 000000000..c0691f343 Binary files /dev/null and b/web/app/assets/images/web/generic_button_cta.png differ diff --git a/web/app/assets/javascripts/accounts.js b/web/app/assets/javascripts/accounts.js index b7af8f033..91454c046 100644 --- a/web/app/assets/javascripts/accounts.js +++ b/web/app/assets/javascripts/accounts.js @@ -8,8 +8,10 @@ var rest = context.JK.Rest(); var userId; var user = {}; + function beforeShow(data) { + console.log("beforeShow", data) userId = data.id; } @@ -43,6 +45,19 @@ var invalidProfiles = prettyPrintAudioProfiles(context.JK.getBadConfigMap()); var sessionSummary = summarizeSession(userDetail); + if(gon.global.video_available && gon.global.video_available!="none" ) { + var webcamName; + var webcam = context.jamClient.FTUECurrentSelectedVideoDevice() + if (webcam == null || typeof(webcam) == "undefined" || Object.keys(webcam).length == 0) { + webcamName = "None Configured" + } else { + webcamName = _.values(webcam)[0] + } + } + else { + webcamName = 'video unavailable' + } + var $template = $(context._.template($('#template-account-main').html(), { email: userDetail.email, name: userDetail.name, @@ -56,6 +71,7 @@ invalidProfiles : invalidProfiles, isNativeClient: gon.isNativeClient, musician: context.JK.currentUserMusician, + webcamName: webcamName, sales_count: userDetail.sales_count } , { variable: 'data' })); @@ -65,8 +81,9 @@ $('#account-scheduled-sessions-link').show(); } else { $('#account-scheduled-sessions-link').hide(); - } - } + } + + }// function function prettyPrintAudioProfiles(profileMap) { var profiles = ""; @@ -110,6 +127,7 @@ $('#account-content-scroller').on('click', '#account-edit-subscriptions-link', function(evt) { evt.stopPropagation(); navToEditSubscriptions(); return false; } ); $('#account-content-scroller').on('click', '#account-edit-payments-link', function(evt) { evt.stopPropagation(); navToEditPayments(); return false; } ); $('#account-content-scroller').on('click', '#account-edit-audio-link', function(evt) { evt.stopPropagation(); navToEditAudio(); return false; } ); + $('#account-content-scroller').on('click', '#account-edit-video-link', function(evt) { evt.stopPropagation(); navToEditVideo(); return false; } ); $('#account-content-scroller').on('avatar_changed', '#profile-avatar', function(evt, newAvatarUrl) { evt.stopPropagation(); updateAvatar(newAvatarUrl); return false; }) // License dialog: @@ -158,6 +176,11 @@ window.location = "/client#/account/audio" } + function navToEditVideo() { + resetForm() + window.location = "/client#/account/video" + } + function navToPaymentHistory() { window.location = '/client#/account/paymentHistory' } @@ -178,6 +201,7 @@ } function initialize() { + var screenBindings = { 'beforeShow': beforeShow, 'afterShow': afterShow diff --git a/web/app/assets/javascripts/accounts_audio_profile.js b/web/app/assets/javascripts/accounts_audio_profile.js index 1e646a4cf..915383acc 100644 --- a/web/app/assets/javascripts/accounts_audio_profile.js +++ b/web/app/assets/javascripts/accounts_audio_profile.js @@ -163,6 +163,13 @@ function handleConfigureAudioProfile(audioProfileId) { + if(!gearUtils.canBeConfigured(audioProfileId)) { + + context.JK.Banner.showAlert("The System Default (Playback Only) profile can not currently be configured."); + return; + } + + if(audioProfileId == gearUtils.GearUtil) if(audioProfileId != context.jamClient.FTUEGetMusicProfileName()) { logger.debug("activating " + audioProfileId); var result = context.jamClient.FTUELoadAudioConfiguration(audioProfileId); diff --git a/web/app/assets/javascripts/accounts_payment_history_screen.js.coffee b/web/app/assets/javascripts/accounts_payment_history_screen.js.coffee index 2f20246cf..82dbe73eb 100644 --- a/web/app/assets/javascripts/accounts_payment_history_screen.js.coffee +++ b/web/app/assets/javascripts/accounts_payment_history_screen.js.coffee @@ -51,41 +51,33 @@ context.JK.AccountPaymentHistoryScreen = class AccountPaymentHistoryScreen renderPayments:(response) => if response.entries? && response.entries.length > 0 - for sale in response.entries - amt = sale.recurly_total_in_cents - amt = 0 if !amt? - - original_total = sale.state.original_total - refund_total = sale.state.refund_total - - refund_state = null - if original_total != 0 # the enclosed logic does not work for free purchases - if refund_total == original_total - refund_state = 'refunded' - else if refund_total != 0 and refund_total < original_total - refund_state = 'partial refund' - - - displayAmount = (amt/100).toFixed(2) - status = 'paid' - - if sale.state.voided - status = 'voided' - displayAmount = (0).toFixed(2) - else if refund_state? - status = refund_state - displayAmount = (amt/100).toFixed(2) + " (refunded: #{(refund_total/100).toFixed(2)})" - - description = [] - for line_item in sale.line_items - description.push(line_item.product_info?.name) + for paymentHistory in response.entries + if paymentHistory.sale? + # this is a sale + sale = paymentHistory.sale + amt = sale.recurly_total_in_cents + status = 'paid' + displayAmount = ' $' + (amt/100).toFixed(2) + date = context.JK.formatDate(sale.created_at, true) + items = [] + for line_item in sale.line_items + items.push(line_item.product_info?.name) + description = items.join(', ') + else + # this is a recurly webhook + transaction = paymentHistory.transaction + amt = transaction.amount_in_cents + status = transaction.transaction_type + displayAmount = '($' + (amt/100).toFixed(2) + ')' + date = context.JK.formatDate(transaction.transaction_at, true) + description = transaction.admin_description payment = { - date: context.JK.formatDate(sale.created_at, true) + date: date amount: displayAmount status: status - payment_method: 'Credit Card', - description: description.join(', ') + payment_method: 'Credit Card' + description: description } tr = $(context._.template(@rowTemplate, payment, { variable: 'data' })); @@ -98,10 +90,9 @@ context.JK.AccountPaymentHistoryScreen = class AccountPaymentHistoryScreen # Turn in to HTML rows and append: #@tbody.html("") - console.log("response.next", response) - @next = response.next_page + @next = response.next @renderPayments(response) - if response.next_page == null + if response.next == null # if we less results than asked for, end searching @scroller.infinitescroll 'pause' @logger.debug("end of history") @@ -147,7 +138,7 @@ context.JK.AccountPaymentHistoryScreen = class AccountPaymentHistoryScreen msg: $('
Loading ...
') img: '/assets/shared/spinner.gif' path: (page) => - '/api/sales?' + $.param(that.buildQuery()) + '/api/payment_histories?' + $.param(that.buildQuery()) }, (json, opts) => this.salesHistoryDone(json) diff --git a/web/app/assets/javascripts/accounts_video_profile.js b/web/app/assets/javascripts/accounts_video_profile.js new file mode 100644 index 000000000..fbb1a6d53 --- /dev/null +++ b/web/app/assets/javascripts/accounts_video_profile.js @@ -0,0 +1,32 @@ +(function (context, $) { + + "use strict"; + + context.JK = context.JK || {}; + context.JK.AccountVideoProfile = function (app) { + var $webcamViewer = new context.JK.WebcamViewer() + function initialize() { + var screenBindings = { + 'beforeShow': beforeShow, + 'beforeHide':beforeHide + }; + app.bindScreen('account/video', screenBindings); + + $webcamViewer.init($(".webcam-container")) + } + + function beforeShow() { + $webcamViewer.beforeShow() + } + + function beforeHide() { + $webcamViewer.setVideoOff() + } + + this.beforeShow = beforeShow + this.beforeHide = beforeHide + this.initialize = initialize + return this; + }; + +})(window, jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/application.js b/web/app/assets/javascripts/application.js index bc065cf22..030fd6e88 100644 --- a/web/app/assets/javascripts/application.js +++ b/web/app/assets/javascripts/application.js @@ -44,6 +44,7 @@ //= require globals //= require AAB_message_factory //= require jam_rest +//= require ga //= require utils //= require subscription_utils //= require custom_controls diff --git a/web/app/assets/javascripts/checkout_order.js b/web/app/assets/javascripts/checkout_order.js index fbe41c665..523c02691 100644 --- a/web/app/assets/javascripts/checkout_order.js +++ b/web/app/assets/javascripts/checkout_order.js @@ -30,15 +30,16 @@ var $orderPrompt = null; var $emptyCartPrompt = null; var $noAccountInfoPrompt = null; + var $downloadApplicationLink = null; function beforeShow() { - beforeShowOrder(); + } function afterShow(data) { - + beforeShowOrder(); } @@ -56,11 +57,13 @@ } function beforeShowOrder() { + $purchasedJamTracks.empty() $orderPrompt.addClass('hidden') $emptyCartPrompt.addClass('hidden') $noAccountInfoPrompt.addClass('hidden') $orderPanel.removeClass("hidden") $thanksPanel.addClass("hidden") + $purchasedJamTrackHeader.attr('status', 'in-progress') $screen.find(".place-order").addClass('disabled').off('click', placeOrder) $("#order_error").text('').addClass("hidden") step = 3; @@ -225,6 +228,7 @@ $orderPanel.addClass("hidden") $thanksPanel.removeClass("hidden") jamTrackUtils.checkShoppingCart() + app.refreshUser() handleJamTracksPurchased(purchaseResponse.jam_tracks) } @@ -237,6 +241,11 @@ } else { $jamTrackInBrowser.removeClass('hidden'); + app.user().done(function(user) { + if(!user.first_downloaded_client_at) { + $downloadApplicationLink.removeClass('hidden') + } + }) } } } @@ -297,7 +306,7 @@ } 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.') + $purchasedJamTrackHeader.attr('status', 'done') } } @@ -349,6 +358,7 @@ $emptyCartPrompt = $screen.find('.empty-cart-prompt'); $noAccountInfoPrompt = $screen.find('.no-account-info-prompt'); $orderContent = $orderPanel.find(".order-content"); + $downloadApplicationLink = $screen.find('.download-jamkazam-wrapper'); if ($screen.length == 0) throw "$screen must be specified"; if ($navigation.length == 0) throw "$navigation must be specified"; diff --git a/web/app/assets/javascripts/checkout_signin.js b/web/app/assets/javascripts/checkout_signin.js index 22ee88060..2416375c4 100644 --- a/web/app/assets/javascripts/checkout_signin.js +++ b/web/app/assets/javascripts/checkout_signin.js @@ -5,6 +5,7 @@ context.JK.CheckoutSignInScreen = function(app) { var logger = context.JK.logger; + var rest = context.JK.Rest(); var $screen = null; var $navigation = null; @@ -17,6 +18,7 @@ var $inputElements = null; var $contentHolder = null; var $btnNext = null; + var $btnFacebook = null; function beforeShow(data) { renderNavigation(); @@ -44,6 +46,7 @@ $signinForm.on('submit', login); $signinBtn.on('click', login); $btnNext.on('click', moveNext); + $btnFacebook.on('click', facebookSignup); } function reset() { @@ -55,6 +58,31 @@ return false; } + + function facebookSignup() { + + var $btn = $(this); + + if($btn.is('.disabled')) { + logger.debug("ignoring fast attempt at facebook signup") + return false; + } + + $btn.addClass('disabled') + + rest.createSignupHint({redirect_location: '/client#/checkoutPayment'}) + .done(function() { + // send the user on to facebook signin + window.location = $btn.attr('href'); + }) + .fail(function() { + app.notify({text:"Facebook Signup is not working properly"}); + }) + .always(function() { + $btn.removeClass('disabled') + }) + return false; + } function login() { if($signinBtn.is('.disabled')) { return false; @@ -117,6 +145,7 @@ $inputElements = $signinForm.find('.input-elements'); $contentHolder = $screen.find('.content-holder'); $btnNext = $screen.find('.btnNext'); + $btnFacebook = $screen.find('.signin-facebook') if($screen.length == 0) throw "$screen must be specified"; if($navigation.length == 0) throw "$navigation must be specified"; diff --git a/web/app/assets/javascripts/checkout_utils.js.coffee b/web/app/assets/javascripts/checkout_utils.js.coffee index ec6f8bb98..4505725ad 100644 --- a/web/app/assets/javascripts/checkout_utils.js.coffee +++ b/web/app/assets/javascripts/checkout_utils.js.coffee @@ -16,7 +16,7 @@ class CheckoutUtils setPreserveBillingInfo:() => date = new Date(); - minutes = 1; + minutes = 10; date.setTime(date.getTime() + (minutes * 60 * 1000)) $.removeCookie(@cookie_name, { path: '/' }) $.cookie(@cookie_name, "jam", { expires: date, path: '/' }) diff --git a/web/app/assets/javascripts/corp/corporate.js b/web/app/assets/javascripts/corp/corporate.js index 60cef8df2..989e7bb85 100644 --- a/web/app/assets/javascripts/corp/corporate.js +++ b/web/app/assets/javascripts/corp/corporate.js @@ -5,7 +5,7 @@ //= require globals //= require jamkazam //= require utils -//= require ga //= require jam_rest +//= require ga //= require corp/init //= require_directory ../corp \ No newline at end of file diff --git a/web/app/assets/javascripts/dialog/banner.js b/web/app/assets/javascripts/dialog/banner.js index 2ced92f0c..7e0fc0f12 100644 --- a/web/app/assets/javascripts/dialog/banner.js +++ b/web/app/assets/javascripts/dialog/banner.js @@ -135,7 +135,11 @@ if(!button.name) throw "button.name must be specified"; if(!button.click) throw "button.click must be specified"; - var buttonStyle = options.buttons.length == i + 1 ? 'button-orange' : 'button-grey'; + var buttonStyle = button.buttonStyle; + if(!buttonStyle) { + buttonStyle = options.buttons.length == i + 1 ? 'button-orange' : 'button-grey'; + } + var $btn = $('' + button.name + ''); $btn.click(function() { diff --git a/web/app/assets/javascripts/dialog/clientPreferencesDialog.js b/web/app/assets/javascripts/dialog/clientPreferencesDialog.js index 308006d3e..9b4f0fa72 100644 --- a/web/app/assets/javascripts/dialog/clientPreferencesDialog.js +++ b/web/app/assets/javascripts/dialog/clientPreferencesDialog.js @@ -21,6 +21,7 @@ var autostart = $autoStartField.find('.icheckbox_minimal').is('.checked'); context.jamClient.SetAutoStart(autostart); app.layout.closeDialog('client-preferences-dialog') + context.jamClient.SaveSettings(); return false; }) } @@ -54,4 +55,4 @@ this.initialize = initialize; } -})(window, jQuery); \ No newline at end of file +})(window, jQuery); diff --git a/web/app/assets/javascripts/dialog/gettingStartedDialog.js b/web/app/assets/javascripts/dialog/gettingStartedDialog.js index db80c15ad..0faa28699 100644 --- a/web/app/assets/javascripts/dialog/gettingStartedDialog.js +++ b/web/app/assets/javascripts/dialog/gettingStartedDialog.js @@ -50,7 +50,7 @@ $browserJamTrackBtn.click(function() { app.layout.closeDialog('getting-started') - window.location = '/client#/jamtrack' + window.location = '/client#/jamtrackBrowse' return false; }) diff --git a/web/app/assets/javascripts/dialog/openBackingTrackDialog.js b/web/app/assets/javascripts/dialog/openBackingTrackDialog.js index 87150456f..bd0d136aa 100644 --- a/web/app/assets/javascripts/dialog/openBackingTrackDialog.js +++ b/web/app/assets/javascripts/dialog/openBackingTrackDialog.js @@ -49,7 +49,6 @@ function getBackingTracks(page) { var result = context.jamClient.getBackingTrackList(); - console.log("result", result) var backingTracks = result.backing_tracks; if (!backingTracks || backingTracks.length == 0) { @@ -89,8 +88,7 @@ rest.openBackingTrack({id: context.JK.CurrentSessionModel.id(), backing_track_path: backingTrack.name}) .done(function(response) { var result = context.jamClient.SessionOpenBackingTrackFile(backingTrack.name, false); - console.log("BackingTrackPlay response: %o", result); - + // TODO: Possibly actually check the result. Investigate // what real client returns: // // if(result) { diff --git a/web/app/assets/javascripts/dialog/recordingFinishedDialog.js b/web/app/assets/javascripts/dialog/recordingFinishedDialog.js index f78022877..866e460a1 100644 --- a/web/app/assets/javascripts/dialog/recordingFinishedDialog.js +++ b/web/app/assets/javascripts/dialog/recordingFinishedDialog.js @@ -10,10 +10,10 @@ var $dialog = null; function resetForm() { - // remove all display errors $('#recording-finished-dialog form .error-text').remove() $('#recording-finished-dialog form .error').removeClass("error") + removeGoogleLoginErrors() } function beforeShow() { @@ -130,11 +130,47 @@ return false; } + function startGoogleLogin(e) { + e.preventDefault() + logger.debug("Starting google login") + window._oauth_win = window.open("/auth/google_login", "Log In to Google", "height=500,width=500,menubar=no,resizable=no,status=no"); + + window._oauth_callback = function() { + window._oauth_win.close() + setGoogleAuthState() + } + return false; + } + function claimRecording(e) { - resetForm(); - registerClaimRecordingHandlers(false); + registerClaimRecordingHandlers(false) + var upload_to_youtube = $('#recording-finished-dialog form input[name=upload_to_youtube]').is(':checked') + + if (upload_to_youtube) { + $.ajax({ + type: "GET", + dataType: "json", + url: "/auth/has_google_auth" + }).success(function(data) { + if(data.has_google_auth) { + performClaim() + } else { + var error_ul = $('') + $('#recording-finished-dialog form [purpose=upload_to_youtube]').addClass('error').append(error_ul) + } + }).always(function () { + registerClaimRecordingHandlers(true); + }) + } else { + performClaim() + } + + return false; + } + + function performClaim() { var name = $('#recording-finished-dialog form input[name=name]').val(); var description = $('#recording-finished-dialog form textarea[name=description]').val(); var genre = $('#recording-finished-dialog form select[name=genre]').val(); @@ -151,59 +187,53 @@ save_video: save_video, upload_to_youtube: upload_to_youtube }) - .done(function () { - $dialog.data('result', {keep:true}); - app.layout.closeDialog('recordingFinished'); - context.JK.GA.trackMakeRecording(); - }) - .fail(function (jqXHR) { - if (jqXHR.status == 422) { - var errors = JSON.parse(jqXHR.responseText); + .done(function () { + $dialog.data('result', {keep:true}); + app.layout.closeDialog('recordingFinished'); + context.JK.GA.trackMakeRecording(); + }) + .fail(function (jqXHR) { + if (jqXHR.status == 422) { + var errors = JSON.parse(jqXHR.responseText); - var $name_errors = context.JK.format_errors('name', errors); - if ($name_errors) $('#recording-finished-dialog form input[name=name]').closest('div.field').addClass('error').end().after($name_errors); + var $name_errors = context.JK.format_errors('name', errors); + if ($name_errors) $('#recording-finished-dialog form input[name=name]').closest('div.field').addClass('error').end().after($name_errors); - var $description_errors = context.JK.format_errors('description', errors); - if ($description_errors) $('#recording-finished-dialog form input[name=description]').closest('div.field').addClass('error').end().after($description_errors); + var $description_errors = context.JK.format_errors('description', errors); + if ($description_errors) $('#recording-finished-dialog form input[name=description]').closest('div.field').addClass('error').end().after($description_errors); - var $genre_errors = context.JK.format_errors('genre', errors); - if ($genre_errors) $('#recording-finished-dialog form select[name=genre]').closest('div.field').addClass('error').end().after($genre_errors); + var $genre_errors = context.JK.format_errors('genre', errors); + if ($genre_errors) $('#recording-finished-dialog form select[name=genre]').closest('div.field').addClass('error').end().after($genre_errors); - var $is_public_errors = context.JK.format_errors('is_public', errors); - if ($is_public_errors) $('#recording-finished-dialog form input[name=is_public]').closest('div.field').addClass('error').end().after($is_public_errors); + var $is_public_errors = context.JK.format_errors('is_public', errors); + if ($is_public_errors) $('#recording-finished-dialog form input[name=is_public]').closest('div.field').addClass('error').end().after($is_public_errors); - var $save_video_errors = context.JK.format_errors('save_video', errors); - if ($save_video_errors) $('#recording-finished-dialog form input[name=save_video]').closest('div.field').addClass('error').end().after($save_video_errors); + var $save_video_errors = context.JK.format_errors('save_video', errors); + if ($save_video_errors) $('#recording-finished-dialog form input[name=save_video]').closest('div.field').addClass('error').end().after($save_video_errors); + + var recording_error = context.JK.get_first_error('recording_id', errors); - var $upload_to_youtube_errors = context.JK.format_errors('upload_to_youtube', errors); - if ($upload_to_youtube_errors) $('#recording-finished-dialog form input[name=upload_to_youtube]').closest('div.field').addClass('error').end().after($upload_to_youtube_errors); + if (recording_error) context.JK.showErrorDialog(app, "Unable to claim recording.", recording_error); + } + else { + logger.error("unable to claim recording %o", arguments); - var recording_error = context.JK.get_first_error('recording_id', errors); - - if (recording_error) context.JK.showErrorDialog(app, "Unable to claim recording.", recording_error); - } - else { - logger.error("unable to claim recording %o", arguments); - - context.JK.showErrorDialog(app, "Unable to claim recording.", jqXHR.responseText); - } - }) - .always(function () { - registerClaimRecordingHandlers(true); - }); - return false; + context.JK.showErrorDialog(app, "Unable to claim recording.", jqXHR.responseText); + } + }) + .always(function () { + registerClaimRecordingHandlers(true); + }); } function registerClaimRecordingHandlers(onOff) { + $('#keep-session-recording').off('click', claimRecording) + $('#recording-finished-dialog form').off('submit', claimRecording); + if (onOff) { $('#keep-session-recording').on('click', claimRecording); $('#recording-finished-dialog form').on('submit', claimRecording); } - else { - $('#keep-session-recording').off('click', claimRecording) - $('#recording-finished-dialog form').off('submit', claimRecording); - } - } function registerDiscardRecordingHandlers(onOff) { @@ -216,6 +246,11 @@ } function onPause() { + logger.debug("calling jamClient.SessionPausePlay"); + context.jamClient.SessionPausePlay(); + } + + function onStop() { logger.debug("calling jamClient.SessionStopPlay"); context.jamClient.SessionStopPlay(); } @@ -234,9 +269,39 @@ registerClaimRecordingHandlers(true); registerDiscardRecordingHandlers(true); $(playbackControls) + .on('stop', onStop) .on('pause', onPause) .on('play', onPlay) .on('change-position', onChangePlayPosition); + $dialog.find(".google_login_button").on('click', startGoogleLogin); + + // Check for google authorization using AJAX and show/hide the + // google login button / "signed in" label as appropriate: + $(window).on('focus', function() { + setGoogleAuthState(); + }); + } + + function setGoogleAuthState() { + $.ajax({ + type: "GET", + dataType: "json", + url: "/auth/has_google_auth" + }).success(function(data) { + if(data.has_google_auth) { + $("input.google_login_button").addClass("hidden") + $("span.signed_in_to_google").removeClass("hidden") + removeGoogleLoginErrors() + } else { + $("span.signed_in_to_google").addClass("hidden") + $("input.google_login_button").removeClass("hidden") + } + }) + } + + function removeGoogleLoginErrors() { + $("ul.error-text.upload_to_youtube").remove() + $('#recording-finished-dialog form div[purpose=upload_to_youtube]').removeClass('error') } function setRecording(recordingData) { @@ -267,7 +332,6 @@ playbackControls = new context.JK.PlaybackControls($('#recording-finished-dialog .recording-controls')); registerStaticEvents(); - initializeButtons(); }; diff --git a/web/app/assets/javascripts/dialog/singlePlayerProfileGuard.js.coffee b/web/app/assets/javascripts/dialog/singlePlayerProfileGuard.js.coffee index d94a21d6a..4c54d1a06 100644 --- a/web/app/assets/javascripts/dialog/singlePlayerProfileGuard.js.coffee +++ b/web/app/assets/javascripts/dialog/singlePlayerProfileGuard.js.coffee @@ -42,7 +42,7 @@ context.JK.SinglePlayerProfileGuardDialog = class SinglePlayerProfileGuardDialog latency = '?' if canPlayWithOthers.audioLatency? - latency = canPlayWithOthers.audioLatency + latency = canPlayWithOthers.audioLatency.toFixed(2) @audioLatency.text("#{latency} milliseconds.") diff --git a/web/app/assets/javascripts/download_jamtrack.js.coffee b/web/app/assets/javascripts/download_jamtrack.js.coffee index 8442fec02..634d1b9ae 100644 --- a/web/app/assets/javascripts/download_jamtrack.js.coffee +++ b/web/app/assets/javascripts/download_jamtrack.js.coffee @@ -48,6 +48,8 @@ context.JK.DownloadJamTrack = class DownloadJamTrack @tracked = false @ajaxEnqueueAborted = false @ajaxGetJamTrackRightAborted = false + @currentPackagingStep = null + @totalSteps = null throw "no JamTrack specified" unless @jamTrack? throw "invalid size" if @size != 'large' && @size != 'small' @@ -174,6 +176,7 @@ context.JK.DownloadJamTrack = class DownloadJamTrack if @state == @states.errored data.result = 'error' data.detail = @errorReason + @rest.createAlert("JamTrack Sync failed for #{context.JK.currentUserName}", data) else data.result = 'success' @@ -187,7 +190,10 @@ context.JK.DownloadJamTrack = class DownloadJamTrack showDownloading: () => @logger.debug("showing #{@state.name}") # while downloading, we don't run the transition timer, because the download API is guaranteed to call success, or failure, eventually - context.jamClient.JamTrackDownload(@jamTrack.id, this.makeDownloadSuccessCallback(), this.makeDownloadFailureCallback()) + context.jamClient.JamTrackDownload(@jamTrack.id, context.JK.currentUserId, + this.makeDownloadProgressCallback(), + this.makeDownloadSuccessCallback(), + this.makeDownloadFailureCallback()) showKeying: () => @logger.debug("showing #{@state.name}") @@ -374,8 +380,22 @@ context.JK.DownloadJamTrack = class DownloadJamTrack .done(this.processJamTrackRight) .fail(this.processJamTrackRightFail) + # update progress indicator for packaging step + updateSteps: () => + if @currentPackagingStep? and @totalSteps? + progress = "#{Math.round(@currentPackagingStep/@totalSteps * 100)}%" + else + progress = '...' + + @root.find('.state-packaging .progress').text(progress) + + processSigningState: (jamTrackRight) => + signingState = jamTrackRight.signing_state + @totalSteps = jamTrackRight.packaging_steps + @currentPackagingStep = jamTrackRight.current_packaging_step + + @updateSteps() - processSigningState: (signingState) => @logger.debug("DownloadJamTrack: processSigningState: " + signingState) switch signingState @@ -435,8 +455,9 @@ context.JK.DownloadJamTrack = class DownloadJamTrack processJamTrackRight: (myJamTrack) => + @logger.debug("processJamTrackRight", myJamTrack) unless @ajaxGetJamTrackRightAborted - this.processSigningState(myJamTrack.signing_state) + this.processSigningState(myJamTrack) else @logger.debug("DownloadJamTrack: ignoring processJamTrackRight response") @@ -459,16 +480,26 @@ context.JK.DownloadJamTrack = class DownloadJamTrack @logger.debug("DownloadJamTrack: ignoring processEnqueueJamTrackFail response") onJamTrackRightEvent: (e, data) => - @logger.debug("DownloadJamTrack: subscription notification received: type:" + data.type) + @logger.debug("DownloadJamTrack: subscription notification received: type:" + data.type, data) this.expectTransition() - this.processSigningState(data.body.signing_state) + this.processSigningState(data.body) - downloadProgressCallback: (bytesReceived, bytesTotal, downloadSpeedMegSec, timeRemaining) => - bytesReceived = Number(bytesReceived) - bytesTotal = Number(bytesTotal) - # bytesTotal from Qt is not trust worthy; trust server's answer instead - #progressWidth = ((bytesReceived / updateSize) * 100).toString() + "%"; - # $('#progress-bar').width(progressWidth) + updateDownloadProgress: () => + + if @bytesReceived? and @bytesTotal? + progress = "#{Math.round(@bytesReceived/@bytesTotal * 100)}%" + else + progress = '0%' + + @root.find('.state-downloading .progress').text(progress) + + downloadProgressCallback: (bytesReceived, bytesTotal) => + @logger.debug("download #{bytesReceived}/#{bytesTotal}") + + @bytesReceived = Number(bytesReceived) + @bytesTotal = Number(bytesTotal) + + setTimeout(this.updateDownloadProgress, 100) downloadSuccessCallback: (updateLocation) => # is the package loadable yet? diff --git a/web/app/assets/javascripts/everywhere/everywhere.js b/web/app/assets/javascripts/everywhere/everywhere.js index 2c010bac2..741f70b3b 100644 --- a/web/app/assets/javascripts/everywhere/everywhere.js +++ b/web/app/assets/javascripts/everywhere/everywhere.js @@ -20,6 +20,7 @@ var stun = null; var rest = context.JK.Rest(); + $(document).on('JAMKAZAM_CONSTRUCTED', function(e, data) { var app = data.app; @@ -31,6 +32,8 @@ updateScoringIntervals(); initializeInfluxDB(); + + trackNewUser(); }) $(document).on('JAMKAZAM_READY', function() { @@ -204,9 +207,9 @@ var user = app.user() if(user) { user.done(function(userProfile) { - if (userProfile.show_whats_next && userProfile.show_whats_next_count < 10 && + if (!userProfile.show_jamtrack_guide && userProfile.show_whats_next && userProfile.show_whats_next_count < 10 && window.location.pathname.indexOf(gon.client_path) == 0 && - window.location.pathname.indexOf('/checkout') == -1 && + window.location.hash.indexOf('/checkout') == -1 && !app.layout.isDialogShowing('getting-started')) { app.layout.showDialog('getting-started'); @@ -219,4 +222,26 @@ context.JK.JamTrackUtils.checkShoppingCart(); } + function trackNewUser() { + var cookie = $.cookie('new_user') + + if(cookie) { + try { + cookie = JSON.parse(cookie) + + context.JK.signup = {} + context.JK.signup = cookie + + $(function() { + // ga() object isn't ready until the page is loaded + $.removeCookie('new_user') + context.JK.GA.trackRegister(cookie.musician, cookie.registrationType); + }); + } + catch(e) { + logger.error("unable to deserialize new_user cookie") + } + } + } + })(window, jQuery); diff --git a/web/app/assets/javascripts/fakeJamClient.js b/web/app/assets/javascripts/fakeJamClient.js index 732547d19..98468defb 100644 --- a/web/app/assets/javascripts/fakeJamClient.js +++ b/web/app/assets/javascripts/fakeJamClient.js @@ -21,6 +21,7 @@ var frameSize = 2.5; var fakeJamClientRecordings = null; var p2pCallbacks = null; + var videoShared = false; var metronomeActive=false; var metronomeBPM=false; var metronomeSound=false; @@ -59,6 +60,45 @@ function FTUESetMusicProfileName() { } + + function FTUESelectVideoCaptureDevice(device, settings) { + + } + function FTUESetVideoEncodeResolution(resolution) { + + } + function FTUEGetVideoCaptureDeviceNames() { + return ["Built-in Webcam HD"] + } + function FTUECurrentSelectedVideoDevice() { + return {"xy323ss": "Built-in Webcam HD"} + } + function FTUEGetAvailableEncodeVideoResolutions() { + return { + 1: "1024x768", + 2: "800x600" + } + } + + function FTUEGetVideoCaptureDeviceCapabilities() { + return {} + } + + function isSessVideoShared() { + return videoShared; + } + + function SessStopVideoSharing() { + videoShared=false; + } + + function SessStartVideoSharing(bitrate) { + if (!bitrate || typeof(bitrate)=="undefined") { + bitrate = 0 + } + videoShared=true; + } + function FTUEGetInputLatency() { dbg("FTUEGetInputLatency"); return 2; @@ -403,6 +443,14 @@ } // Session Functions + + function SessionCurrrentJamTrackPlayPosMs() { + return 0; + } + + function SessionGetJamTracksPlayDurationMs() { + return 60000; + } function SessionAddTrack() {} function SessionAudioResync() { @@ -931,6 +979,7 @@ function LeaveSessionAndMinimize() {} function SetAutoStart() {} function GetAutoStart() { return true; } + function SaveSettings() {} // Javascript Bridge seems to camel-case // Set the instance functions: @@ -1031,6 +1080,8 @@ // Session this.SessionAddTrack = SessionAddTrack; + this.SessionCurrrentJamTrackPlayPosMs = SessionCurrrentJamTrackPlayPosMs; + this.SessionGetJamTracksPlayDurationMs = SessionGetJamTracksPlayDurationMs; this.SessionGetControlState = SessionGetControlState; this.SessionGetAllControlState = SessionGetAllControlState; this.SessionSetUserName = SessionSetUserName; @@ -1140,6 +1191,19 @@ this.ClosePreviewRecording = ClosePreviewRecording; this.OnDownloadAvailable = OnDownloadAvailable; + // Video functionality: + this.FTUESelectVideoCaptureDevice = FTUESelectVideoCaptureDevice + this.FTUESetVideoEncodeResolution = FTUESetVideoEncodeResolution; + this.FTUEGetVideoCaptureDeviceNames = FTUEGetVideoCaptureDeviceNames; + this.FTUECurrentSelectedVideoDevice = FTUECurrentSelectedVideoDevice; + this.FTUEGetAvailableEncodeVideoResolutions = FTUEGetAvailableEncodeVideoResolutions; + this.FTUEGetVideoCaptureDeviceCapabilities = FTUEGetVideoCaptureDeviceCapabilities; + + + this.isSessVideoShared = isSessVideoShared; + this.SessStopVideoSharing = SessStopVideoSharing; + this.SessStartVideoSharing = SessStartVideoSharing; + // Clipboard this.SaveToClipboard = SaveToClipboard; @@ -1169,4 +1233,4 @@ this.clientID = "devtester"; }; - })(window,jQuery); \ No newline at end of file + })(window,jQuery); diff --git a/web/app/assets/javascripts/ga.js b/web/app/assets/javascripts/ga.js index 4fe5e565d..8e51a081e 100644 --- a/web/app/assets/javascripts/ga.js +++ b/web/app/assets/javascripts/ga.js @@ -6,6 +6,7 @@ "use strict"; context.JK = context.JK || {}; + var rest = context.JK.Rest(); var logger = context.JK.logger; // types @@ -130,6 +131,20 @@ jkComment : 'jkComment' }; + // JamTrack categories and actions: + var jamTrackAvailabilityTypes = { + worldwide: 'JamTrackGlobal', + usa: 'JamTrackUSA' + } + var jamTrackActions = { + isPublic: 'PublicPerformance', + isPrivate: 'PrivateUse' + } + var jamTrackSessionLabels = { + nonSession: 'NonSession', + inSession: 'InSession' + } + function translatePlatformForGA(platform) { assertOneOf(platform, context.JK.OS); @@ -239,6 +254,36 @@ context.ga('send', 'event', categories.findSession, sessionCountRollup, numSessionsFound); } + function trackJamTrackPlaySession(sessionId, inSession) { + rest.getSession(sessionId).done(function(session) { + if (session && session.jam_track ) { + rest.getJamTracks({id:session.jam_track.id}).done(function(response) { + if (response.jamtracks && response.jamtracks.length!=0) { + var jamtrack = response.jamtracks[0] + trackJamTrackPlay( + jamtrack.sales_region!=context.JK.AVAILABILITY_US, + session.participants.length > 1, + inSession); + }// if + })// rest.getJamTracks + }// if + })// rest.getSession + } + + function trackJamTrackPlay(isGlobal, isPublic, inSession) { + assertBoolean(isGlobal) + assertBoolean(isPublic) + assertBoolean(inSession) + context.ga( + 'send', + 'event', + (isGlobal) ? jamTrackAvailabilityTypes.worldwide : jamTrackAvailabilityTypes.usa, + (isPublic) ? jamTrackActions.isPublic : jamTrackActions.isPrivate, + (inSession) ? jamTrackSessionLabels.inSession : jamTrackSessionLabels.nonSession + ) + logger.debug("Tracked Jam Track Play") + } + // if you want to pass in no title, either omit it from the arg list when u invoke virtualPageView, or pass in undefined, NOT null function virtualPageView(page, title) { @@ -271,7 +316,8 @@ } // when someone plays a recording - function trackRecordingPlay(recordingAction) { + function trackRecordingPlay(recording, recordingAction) { + if (!recordingAction) { recordingAction = _defaultPlayAction(); } @@ -279,10 +325,20 @@ var label = JK.currentUserId ? userLabels.registeredUser : userLabels.visitor; context.ga('send', 'event', categories.recordingPlay, recordingAction, label); + + if (recording.jam_track) { + rest.getJamTracks({id:recording.jam_track_id}).done(function(response) { + if (response.jamtracks && response.jamtracks.length==1) { + var jamtrack = response.jamtracks[0] + trackJamTrackPlay(jamtrack.sales_region!=context.JK.AVAILABILITY_US, recording.fan_access, false); + } + }) + } } // when someone plays a live session broadcast - function trackSessionPlay(recordingAction) { + function trackSessionPlay(session, recordingAction) { + logger.debug("Tracking session play: ", session) if (!recordingAction) { recordingAction = _defaultPlayAction(); } @@ -379,7 +435,8 @@ GA.trackSessionQuality = trackSessionQuality; GA.trackServiceInvitations = trackServiceInvitations; GA.trackFindSessions = trackFindSessions; - GA.virtualPageView = virtualPageView; + GA.trackJamTrackPlay = trackJamTrackPlay; + GA.trackJamTrackPlaySession = trackJamTrackPlaySession; GA.trackFriendConnect = trackFriendConnect; GA.trackMakeRecording = trackMakeRecording; GA.trackShareRecording = trackShareRecording; @@ -387,8 +444,8 @@ GA.trackSessionPlay = trackSessionPlay; GA.trackBand = trackBand; GA.trackJKSocial = trackJKSocial; - - + GA.virtualPageView = virtualPageView; + context.JK.GA = GA; })(window,jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/helpBubbleHelper.js b/web/app/assets/javascripts/helpBubbleHelper.js index 9c9a05bd5..eb68a4a6c 100644 --- a/web/app/assets/javascripts/helpBubbleHelper.js +++ b/web/app/assets/javascripts/helpBubbleHelper.js @@ -10,6 +10,7 @@ var rest = new context.JK.Rest(); context.JK.HelpBubbleHelper = helpBubble; var logger = context.JK.logger; + var jamTrackGuideTimeout = null; var defaultScoreBreakDownOptions = {positions: ['right', 'top', 'bottom', 'left'], width:'600px', closeWhenOthersOpen: true }; helpBubble.scoreBreakdown = function($element, isCurrentUser, full_score, myAudioLatency, otherAudioLatency, internetScore, options) { @@ -26,4 +27,59 @@ } } + function bigHelpOptions(options) { + return {positions: options.positions, offsetParent: options.offsetParent, + spikeGirth: 15, + spikeLength: 20, + fill: 'white', + cornerRadius:8, + strokeWidth: 2, + cssStyles: { + fontWeight:'bold', + fontSize: '20px', + backgroundColor:'transparent', + color:'#ed3618'}} + } + + function clearJamTrackGuideTimeout() { + if(jamTrackGuideTimeout) { + clearTimeout(jamTrackGuideTimeout); + jamTrackGuideTimeout = null; + } + } + + function jamTrackGuide(callback) { + if(gon.isNativeClient) { + context.JK.app.user().done(function(user) { + if(user.show_jamtrack_guide) { + clearJamTrackGuideTimeout(); + jamTrackGuideTimeout = setTimeout(function() { + callback() + }, 1000) + + } + }) + } + } + + helpBubble.clearJamTrackGuide = clearJamTrackGuideTimeout; + + helpBubble.jamtrackGuideTile = function ($element, $offsetParent) { + jamTrackGuide(function() { + context.JK.prodBubble($element, 'jamtrack-guide-tile', {}, bigHelpOptions({positions:['top'], offsetParent: $offsetParent})) + }) + } + + helpBubble.jamtrackGuidePrivate = function ($element, $offsetParent) { + jamTrackGuide(function() { + context.JK.prodBubble($element, 'jamtrack-guide-private', {}, bigHelpOptions({positions:['right'], offsetParent: $offsetParent})) + }) + } + + helpBubble.jamtrackGuideSession = function ($element, $offsetParent) { + jamTrackGuide(function() { + context.JK.prodBubble($element, 'jamtrack-guide-session', {}, bigHelpOptions({positions:['left'], offsetParent: $offsetParent})) + }) + } + })(window, jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/homeScreen.js b/web/app/assets/javascripts/homeScreen.js index ec7b86d50..a96075b47 100644 --- a/web/app/assets/javascripts/homeScreen.js +++ b/web/app/assets/javascripts/homeScreen.js @@ -11,6 +11,14 @@ function beforeShow(data) { } + function afterShow(data) { + context.JK.HelpBubbleHelper.jamtrackGuideTile($('.homecard.createsession'), $screen.find('.createsession')); + } + + function beforeHide() { + context.JK.HelpBubbleHelper.clearJamTrackGuide(); + } + function mouseenterTile() { $(this).addClass('hover'); } @@ -84,7 +92,7 @@ } this.initialize = function() { - var screenBindings = { 'beforeShow': beforeShow }; + var screenBindings = { 'beforeShow': beforeShow, 'afterShow': afterShow, 'beforeHide' : beforeHide }; app.bindScreen('home', screenBindings); events(); $screen = $('.screen[layout-id="home"]') diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js index 82afb1fc5..6c3cce4f0 100644 --- a/web/app/assets/javascripts/jam_rest.js +++ b/web/app/assets/javascripts/jam_rest.js @@ -287,11 +287,6 @@ } function addPlayablePlay(playableId, playableType, claimedRecordingId, userId) { - if (playableType == 'JamRuby::Recording') { - context.JK.GA.trackRecordingPlay(); - } else if (playableType == 'JamRuby::MusicSession') { - context.JK.GA.trackSessionPlay(); - } return $.ajax({ url: '/api/users/' + playableId + "/plays", type: "POST", @@ -1466,7 +1461,7 @@ }); } - function getJamtracks(options) { + function getJamTracks(options) { return $.ajax({ type: "GET", url: '/api/jamtracks?' + $.param(options), @@ -1475,6 +1470,15 @@ }); } + function getJamTrackArtists(options) { + return $.ajax({ + type: "GET", + url: '/api/jamtracks/artists?' + $.param(options), + dataType: "json", + contentType: 'application/json' + }); + } + function getJamTrackRight(options) { var jamTrackId = options['id']; @@ -1518,7 +1522,7 @@ function getSalesHistory(options) { return $.ajax({ type: "GET", - url: '/api/sales?' + $.param(options), + url: '/api/payment_histories?' + $.param(options), dataType: "json", contentType: 'application/json' }); @@ -1675,7 +1679,30 @@ }); } - function initialize() { + function createSignupHint(data) { + return $.ajax({ + type: "POST", + url: '/api/signup_hints', + dataType: "json", + contentType: 'application/json', + data: JSON.stringify(data), + }); + } + + function createAlert(subject, data) { + var message = {subject:subject}; + $.extend(message, data); + console.log("message", message) + return $.ajax({ + type: "POST", + url: '/api/alerts', + dataType: "json", + contentType: 'application/json', + data: JSON.stringify(message), + }); + } + + function initialize() { return self; } @@ -1799,7 +1826,8 @@ this.updateAudioLatency = updateAudioLatency; this.getJamTrack = getJamTrack; this.getJamTrackWithArtistInfo = getJamTrackWithArtistInfo; - this.getJamtracks = getJamtracks; + this.getJamTracks = getJamTracks; + this.getJamTrackArtists = getJamTrackArtists; this.getPurchasedJamTracks = getPurchasedJamTracks; this.getPaymentHistory = getPaymentHistory; this.getSalesHistory = getSalesHistory; @@ -1825,6 +1853,8 @@ this.getMusicianSearchFilter = getMusicianSearchFilter; this.postMusicianSearchFilter = postMusicianSearchFilter; this.playJamTrack = playJamTrack; + this.createSignupHint = createSignupHint; + this.createAlert = createAlert; return this; }; diff --git a/web/app/assets/javascripts/jam_track_preview.js.coffee b/web/app/assets/javascripts/jam_track_preview.js.coffee index 926a8d824..78a3d97dd 100644 --- a/web/app/assets/javascripts/jam_track_preview.js.coffee +++ b/web/app/assets/javascripts/jam_track_preview.js.coffee @@ -9,7 +9,7 @@ context.JK.JamTrackPreview = class JamTrackPreview @EVENTS = context.JK.EVENTS @rest = context.JK.Rest() @logger = context.JK.logger - @options = options || {master_shows_duration: false} + @options = options || {master_shows_duration: false, color:'gray'} @app = app @jamTrack = jamTrack @jamTrackTrack = jamTrackTrack @@ -23,7 +23,7 @@ context.JK.JamTrackPreview = class JamTrackPreview template = $('#template-jam-track-preview') throw "no jam track preview template" if not template.exists() - @root.html($(template.html())) + @root.html(context._.template(template.html(), @options, {variable:'data'})) @playButton = @root.find('.play-button') @stopButton = @root.find('.stop-button') @instrumentIcon = @root.find('.instrument-icon') diff --git a/web/app/assets/javascripts/jam_track_screen.js.coffee b/web/app/assets/javascripts/jam_track_screen.js.coffee index 6cea8e994..8247fbaeb 100644 --- a/web/app/assets/javascripts/jam_track_screen.js.coffee +++ b/web/app/assets/javascripts/jam_track_screen.js.coffee @@ -50,7 +50,7 @@ context.JK.JamTrackScreen=class JamTrackScreen @instrument.val(parms.instrument) if(parms.availability?) @availability.val(parms.availability) - window.history.replaceState({}, "", "/client#/jamtrack") + window.history.replaceState({}, "", "/client#/jamtrackBrowse") getParams:() => params = {} @@ -63,16 +63,31 @@ context.JK.JamTrackScreen=class JamTrackScreen 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 - rest.getJamtracks(@currentQuery).done((response) => - that.clearResults() + this.setFilterState(false) + rest.getJamTracks(@currentQuery).done((response) => that.handleJamtrackResponse(response) - ).fail (jqXHR) => + ).fail( (jqXHR) => that.clearResults() that.noMoreJamtracks.show() - that.app.notifyServerError jqXHR, 'Jamtrack Unavailable' + that.app.notifyServerError jqXHR, 'Jamtrack Unavailable' + ).always () => + that.setFilterState(true) search:() => this.refresh() @@ -117,7 +132,7 @@ context.JK.JamTrackScreen=class JamTrackScreen # if we less results than asked for, end searching @scroller.infinitescroll 'pause' if @currentPage == 0 and response.jamtracks.length == 0 - @content.append '
No JamTracks found.
' + @content.append '') + for track in jamTrack.tracks + trackElement.find("[jamtrack-track-id='#{track.id}']").removeClass('hidden') + else + trackElement.find('.extra').addClass('hidden') + detailArrow.html('preview all tracks ') + count = 0 + for track in jamTrack.tracks + if count < 2 + 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 - @screen.find('.play-button').on 'click', this.playJamtrack - @screen.find('.jamtrack-add-cart').on 'click', this.addToCartJamtrack - @screen.find('.license-us-why').on 'click', this.licenseUSWhy - @screen.find('.jamtrack-detail-btn').on 'click', this.toggleExpanded + 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}) @@ -188,37 +228,41 @@ context.JK.JamTrackScreen=class JamTrackScreen trackRow.track_cnt = jamtrack.tracks.length trackRow.tracks = [] for track in jamtrack.tracks - if track.track_type != 'Master' - 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 + ')' + 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 + ')' options = jamtrack: trackRow - expanded: jamtrackExpanded + expanded: false @jamtrackItem = $(context._.template($('#template-jamtrack').html(), options, variable: 'data')) that.renderJamtrack(@jamtrackItem, jamtrack) - - this.registerEvents() + 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") - new JK.JamTrackPreview(@app, previewElement, jamTrack, track, {master_shows_duration: false}) + #if this.expanded==jamTrack.id + for track in jamTrack.tracks + trackRow = jamtrackElement.find("[jamtrack-track-id='#{track.id}']") + previewElement = trackRow.find(".jamtrack-preview") + new JK.JamTrackPreview(@app, previewElement, jamTrack, track, {master_shows_duration: true, color:'gray'}) + + this.handleExpanded(jamtrackElement, false) showJamtrackDescription:(e) => e.preventDefault() @@ -232,17 +276,14 @@ context.JK.JamTrackScreen=class JamTrackScreen e.preventDefault() jamtrackRecord = $(e.target).parents('.jamtrack-record') jamTrackId = jamtrackRecord.attr("jamtrack-id") - if this.expanded==jamTrackId - this.expanded = null - else - this.expanded = jamTrackId - this.rerenderJamtracks() + + this.handleExpanded(jamtrackRecord) initialize:() => screenBindings = 'beforeShow': this.beforeShow 'afterShow': this.afterShow - @app.bindScreen 'jamtrack', screenBindings + @app.bindScreen 'jamtrackBrowse', screenBindings @screen = $('#jamtrack-find-form') @scroller = @screen.find('.content-body-scroller') @content = @screen.find('.jamtrack-content') diff --git a/web/app/assets/javascripts/jamkazam.js b/web/app/assets/javascripts/jamkazam.js index 2bc31b957..d1b41d451 100644 --- a/web/app/assets/javascripts/jamkazam.js +++ b/web/app/assets/javascripts/jamkazam.js @@ -344,6 +344,18 @@ return userData; } + this.refreshUser = function() { + userDeferred = rest.getUserDetail(); + if (userDeferred) { + userDeferred.done(this.updateUserCache) + } + else { + userDeferred = new $.Deferred(); + userDeferred.reject('not_logged_in'); + } + return userDeferred; + } + this.activeElementEvent = function(evtName, data) { return this.layout.activeElementEvent(evtName, data); } diff --git a/web/app/assets/javascripts/jamtrack_landing.js.coffee b/web/app/assets/javascripts/jamtrack_landing.js.coffee index 760744e9f..2fb470923 100644 --- a/web/app/assets/javascripts/jamtrack_landing.js.coffee +++ b/web/app/assets/javascripts/jamtrack_landing.js.coffee @@ -8,45 +8,70 @@ context.JK.JamTrackLanding = class JamTrackLanding @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') - + @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.getJamtracks({group_artist: true}) - .done(this.buildArtistLinks) - .fail(this.handleFailure) + @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() - afterShow:() => buildArtistLinks:(response) => - # Get artist names and build links - jamtracks = response.jamtracks + # Get artist names and build links + @logger.debug("buildArtest links response", response) + + artists = response.artists $("#band_list>li:not('#no_bands_found')").remove() - if jamtracks.length==0 - $("#no_bands_found").removeClass("hidden") + if artists.length==0 + @noBandsFound.removeClass("hidden") else - $("#no_bands_found").addClass("hidden") + @noBandsFound.addClass("hidden") # client#/jamtrack - for jamtrack in jamtracks - artistLink = "#{jamtrack.original_artist}" - $("#band_list").append("
  • #{artistLink}
  • ") + for artist in artists + artistLink = "#{artist.original_artist} (#{artist.song_count})" + @bandList.append("
  • #{artistLink}
  • ") # We don't want to do a full page load if this is clicked on here: bindArtistLinks:() => - band_list=$("ul#band_list") that=this - band_list.on "click", "a.artist-link", (event)-> - context.location="client#/jamtrack" + @bandList.on "click", "a.artist-link", (event)-> + context.location="client#/jamtrackBrowse" window.history.replaceState({}, "", this.href) event.preventDefault() diff --git a/web/app/assets/javascripts/jquery.listenRecording.js b/web/app/assets/javascripts/jquery.listenRecording.js index 84a7628f7..1303f50f7 100644 --- a/web/app/assets/javascripts/jquery.listenRecording.js +++ b/web/app/assets/javascripts/jquery.listenRecording.js @@ -78,6 +78,10 @@ audioDomElement.play(); isPlaying = true; rest.addPlayablePlay(recordingId, 'JamRuby::Recording', claimedRecordingId, context.JK.currentUserId); + rest.getRecording({id: recordingId}) + .done(function(recording) { + context.JK.GA.trackRecordingPlay(recording); + }) }) } diff --git a/web/app/assets/javascripts/jquery.listenbroadcast.js b/web/app/assets/javascripts/jquery.listenbroadcast.js index f6964cd60..89c5184dd 100644 --- a/web/app/assets/javascripts/jquery.listenbroadcast.js +++ b/web/app/assets/javascripts/jquery.listenbroadcast.js @@ -112,7 +112,8 @@ waitForBufferingTimeout = setTimeout(noBuffer, WAIT_FOR_BUFFER_TIMEOUT); logger.debug("setting buffering timeout"); rest.addPlayablePlay(musicSessionId, 'JamRuby::MusicSession', null, context.JK.currentUserId); - + context.JK.GA.trackJamTrackPlaySession(musicSessionId, false) + if(needsCanPlayGuard()) { $audio.bind('canplay', function() { audioDomElement.play(); diff --git a/web/app/assets/javascripts/landing/landing.js b/web/app/assets/javascripts/landing/landing.js index 0afe196f0..19034e53c 100644 --- a/web/app/assets/javascripts/landing/landing.js +++ b/web/app/assets/javascripts/landing/landing.js @@ -32,8 +32,8 @@ //= require jamkazam //= require utils //= require ui_helper -//= require ga //= require jam_rest +//= require ga //= require web/signup_helper //= require web/signin_helper //= require web/signin diff --git a/web/app/assets/javascripts/playbackControls.js b/web/app/assets/javascripts/playbackControls.js index 1a90712a6..fb82ab0c6 100644 --- a/web/app/assets/javascripts/playbackControls.js +++ b/web/app/assets/javascripts/playbackControls.js @@ -29,6 +29,7 @@ var $playButton = $('.play-button img.playbutton', $parentElement); var $pauseButton = $('.play-button img.pausebutton', $parentElement); + var $stopButton = $('.stop-button img.stopbutton', $parentElement); var $currentTime = $('.recording-current', $parentElement); var $duration = $('.duration-time', $parentElement); var $sliderBar = $('.recording-playback', $parentElement); @@ -42,6 +43,7 @@ var playbackDurationMs = 0; var playbackPositionMs = 0; var durationChanged = false; + var seenActivity = false; var endReached = false; var dragging = false; @@ -53,16 +55,28 @@ var playbackMonitorMode = PLAYBACK_MONITOR_MODE.MEDIA_FILE; function startPlay() { + seenActivity = false; updateIsPlaying(true); if(endReached) { update(0, playbackDurationMs, playbackPlaying); } $self.triggerHandler('play', {playbackMode: playbackMode, playbackMonitorMode: playbackMonitorMode}); + + + if(playbackMonitorMode == PLAYBACK_MONITOR_MODE.JAMTRACK) { + var sessionModel = context.JK.CurrentSessionModel || null; + context.JK.GA.trackJamTrackPlaySession(sessionModel.id(), true) + } } function stopPlay(endReached) { updateIsPlaying(false); - $self.triggerHandler('pause', {playbackMode: playbackMode, playbackMonitorMode: playbackMonitorMode, endReached : endReached}); + $self.triggerHandler('stop', {playbackMode: playbackMode, playbackMonitorMode: playbackMonitorMode, endReached : endReached}); + } + + function pausePlay(endReached) { + updateIsPlaying(false); + $self.triggerHandler('pause', {playbackMode: playbackMode, playbackMonitorMode: playbackMonitorMode, endReached : endReached}); } function updateOffsetBasedOnPosition(offsetLeft) { @@ -121,6 +135,17 @@ // return false; //} + pausePlay(); + return false; + }); + + $stopButton.on('click', function(e) { + var sessionModel = context.JK.CurrentSessionModel || null; + //if(sessionModel && sessionModel.areControlsLockedForJamTrackRecording() && $parentElement.closest('.session-track').data('track_data').type == 'jam_track') { + // context.JK.prodBubble($pauseButton, 'jamtrack-controls-disabled', {}, {positions:['top'], offsetParent: $pauseButton}) + // return false; + //} + stopPlay(); return false; }); @@ -182,8 +207,7 @@ var positionMs = context.jamClient.SessionCurrrentJamTrackPlayPosMs(); var duration = context.jamClient.SessionGetJamTracksPlayDurationMs(); var durationMs = duration.media_len; - var start = duration.start; // needed to understand start offset, and prevent slider from moving in tapins - //console.log("JamTrack start: " + start) + var start = duration.start; // needed to understand start offset, and prevent slider from moving in tapins } else { var positionMs = context.jamClient.SessionCurrrentPlayPosMs(); @@ -197,18 +221,11 @@ positionMs = 0; } - if(playbackMonitorMode = PLAYBACK_MONITOR_MODE.JAMTRACK) { - - if(isPlaying) { - $jamTrackGetReady.attr('data-current-time', positionMs) - } - else { - // this is so the jamtrack 'Get Ready!' stays hidden when it's not playing - $jamTrackGetReady.attr('data-current-time', -1) - } - + if(positionMs > 0) { + seenActivity = true; } + if(playbackMonitorMode == PLAYBACK_MONITOR_MODE.METRONOME) { updateIsPlaying(isPlaying); } @@ -216,6 +233,17 @@ update(positionMs, durationMs, isPlaying); } + if(playbackMonitorMode == PLAYBACK_MONITOR_MODE.JAMTRACK) { + + if(playbackPlaying) { + $jamTrackGetReady.attr('data-current-time', positionMs) + } + else { + // this is so the jamtrack 'Get Ready!' stays hidden when it's not playing + $jamTrackGetReady.attr('data-current-time', -1) + } + + } monitorPlaybackTimeout = setTimeout(monitorRecordingPlayback, 500); } @@ -227,9 +255,9 @@ } // at the end of the play, the duration sets to 0, as does currentTime. but isPlaying does not reset to - logger.debug("currentTimeMs, durationTimeMs", currentTimeMs, durationTimeMs); - if(currentTimeMs == 0 && durationTimeMs == 0) { - if(isPlaying) { + //logger.debug("currentTimeMs, durationTimeMs, mode", currentTimeMs, durationTimeMs, playbackMonitorMode); + if(currentTimeMs == 0 && seenActivity) { + if(playbackPlaying) { isPlaying = false; durationTimeMs = playbackDurationMs; currentTimeMs = playbackDurationMs; @@ -293,6 +321,7 @@ $pauseButton.hide(); } + logger.debug("updating is playing: " + isPlaying) playbackPlaying = isPlaying; } } diff --git a/web/app/assets/javascripts/scheduled_session.js.erb b/web/app/assets/javascripts/scheduled_session.js.erb index a9ab74fa2..f2c07b7bc 100644 --- a/web/app/assets/javascripts/scheduled_session.js.erb +++ b/web/app/assets/javascripts/scheduled_session.js.erb @@ -922,6 +922,19 @@ if(!context.JK.guardAgainstBrowser(app)) { return false; } + + if(createSessionSettings.createType == '<%= MusicSession::CREATE_TYPE_QUICK_START %>') { + // short-cut added for private sessions; just get it going + beforeMoveStep1(); // this will populate the createSessionSettings structure + startSessionClicked(); // and this will create the session + return false; + } + } + + if(optionRequiresMultiplayerProfile()) { + if(context.JK.guardAgainstSinglePlayerProfile(app).canPlay == false) { + return false; + } } if(optionRequiresMultiplayerProfile()) { @@ -986,7 +999,11 @@ } function afterShow() { + context.JK.HelpBubbleHelper.jamtrackGuidePrivate($screen.find('li[create-type="quick-start"] label'), $screen.find('.content-body-scroller')) + } + function beforeHide() { + context.JK.HelpBubbleHelper.clearJamTrackGuide(); } function toggleDate(dontRebuildDropdowns) { @@ -1293,7 +1310,7 @@ instrumentSelector = instrumentSelectorInstance; instrumentRSVP = instrumentRSVPSelectorInstance; - var screenBindings = {'beforeShow': beforeShow, 'afterShow': afterShow}; + var screenBindings = {'beforeShow': beforeShow, 'afterShow': afterShow, 'beforeHide' : beforeHide}; app.bindScreen('createSession', screenBindings); $wizardSteps = $screen.find('.create-session-wizard'); diff --git a/web/app/assets/javascripts/session.js b/web/app/assets/javascripts/session.js index dfa76dc65..d7246dd5d 100644 --- a/web/app/assets/javascripts/session.js +++ b/web/app/assets/javascripts/session.js @@ -13,7 +13,8 @@ var modUtils = context.JK.ModUtils; var logger = context.JK.logger; var self = this; - + var webcamViewer = new context.JK.WebcamViewer() + var defaultParticipant = { tracks: [{ instrument_id: "unknown" @@ -133,16 +134,19 @@ var RENDER_SESSION_DELAY = 750; // When I need to render a session, I have to wait a bit for the mixers to be there. function beforeShow(data) { - sessionId = data.id; - if(!sessionId) { - window.location = '/client#/home'; - } - promptLeave = true; - $myTracksContainer.empty(); - displayDoneRecording(); // assumption is that you can't join a recording session, so this should be safe + sessionId = data.id; + if(!sessionId) { + window.location = '/client#/home'; + } + promptLeave = true; + $myTracksContainer.empty(); + displayDoneRecording(); // assumption is that you can't join a recording session, so this should be safe - var shareDialog = new JK.ShareDialog(context.JK.app, sessionId, "session"); - shareDialog.initialize(context.JK.FacebookHelperInstance); + var shareDialog = new JK.ShareDialog(context.JK.app, sessionId, "session"); + shareDialog.initialize(context.JK.FacebookHelperInstance); + if(gon.global.video_available && gon.global.video_available!="none") { + webcamViewer.beforeShow() + } } function beforeDisconnect() { @@ -168,10 +172,13 @@ } } checkForCurrentUser(); + + context.JK.HelpBubbleHelper.jamtrackGuideSession($screen.find('li.open-a-jamtrack'), $screen) } function afterShow(data) { + $fluidTracks.addClass('showing'); $openBackingTrack.removeClass('disabled'); if(!context.JK.JamServer.connected) { @@ -207,7 +214,6 @@ var singlePlayerCheckOK = true; // to know whether we are allowed to be in this session, we have to check if we are the creator when checking against single player functionality if(musicSession.user_id != context.JK.currentUserId) { - var canPlay = context.JK.guardAgainstSinglePlayerProfile(app, function () { promptLeave = false; }); @@ -413,7 +419,7 @@ } else { displayDoneRecording(); - promptUserToSave(data.recordingId); + promptUserToSave(data.recordingId, timeline); } }) @@ -501,6 +507,13 @@ function beforeHide(data) { + context.JK.HelpBubbleHelper.clearJamTrackGuide(); + + if(gon.global.video_available && gon.global.video_available!="none") { + webcamViewer.setVideoOff() + } + + $fluidTracks.removeClass('showing'); if(screenActive) { // this path is possible if FTUE is invoked on session page, and they cancel sessionModel.leaveCurrentSession() @@ -532,6 +545,7 @@ var metronomeMasterMixers = getMetronomeMasterMixers(); if (metronomeMixer == null && metronomeMasterMixers.length > 0) { + logger.debug("monitoring metronome") playbackControls.startMonitor(context.JK.PLAYBACK_MONITOR_MODE.METRONOME) } else if (metronomeMixer != null && metronomeMasterMixers.length == 0) { @@ -542,10 +556,14 @@ function checkJamTrackTransition(currentSession) { // handle jam tracks - if (jamTrack == null && (currentSession && currentSession.jam_track != null)) { + + // if we have a recording open, then don't go into JamTrack monitor mode even if a JamTrack is open + if (jamTrack == null && (currentSession && currentSession.jam_track != null && currentSession.claimed_recording == null)) { + logger.debug("monitoring jamtrack") playbackControls.startMonitor(context.JK.PLAYBACK_MONITOR_MODE.JAMTRACK); } - else if (jamTrack && (currentSession == null || currentSession.jam_track == null)) { + else if (jamTrack && (currentSession == null || (currentSession.jam_track == null && currentSession.claimed_recording == null))) { + logger.debug("stop monitoring jamtrack") playbackControls.stopMonitor(); } jamTrack = currentSession == null ? null : currentSession.jam_track; @@ -554,9 +572,11 @@ function checkBackingTrackTransition(currentSession) { // handle backing tracks if (backing_track_path == null && (currentSession && currentSession.backing_track_path != null)) { + logger.debug("monitoring backing track") playbackControls.startMonitor(); } else if (backing_track_path && (currentSession == null || currentSession.backing_track_path == null)) { + logger.debug("stop monitoring backing track") playbackControls.stopMonitor(); } backing_track_path = currentSession == null ? null : currentSession.backing_track_path; @@ -568,9 +588,11 @@ if (claimedRecording == null && (currentSession && currentSession.claimed_recording != null)) { // this is a 'started with a claimed_recording' transition. // we need to start a timer to watch for the state of the play session + logger.debug("monitoring recording") playbackControls.startMonitor(); } else if (claimedRecording && (currentSession == null || currentSession.claimed_recording == null)) { + logger.debug("stop monitoring recording") playbackControls.stopMonitor(); } claimedRecording = currentSession == null ? null : currentSession.claimed_recording; @@ -709,7 +731,7 @@ function _initDialogs() { configureTrackDialog.initialize(); - addNewGearDialog.initialize(); + addNewGearDialog.initialize(); } // Get the latest list of underlying audio mixer channels, and populates: @@ -1278,13 +1300,15 @@ $('.session-recording-name').text(jamTrackName); var noCorrespondingTracks = false; - $.each(jamTrackMixers, function(index, mixer) { + $.each(jamTracks, function(index, jamTrack) { + var mixer = null; var preMasteredClass = ""; // find the track or tracks that correspond to the mixer var correspondingTracks = [] - $.each(jamTracks, function(i, jamTrack) { - if(mixer.id == jamTrack.id) { + $.each(jamTrackMixers, function(i, matchMixer) { + if(matchMixer.id == jamTrack.id) { correspondingTracks.push(jamTrack); + mixer = matchMixer; } }); @@ -1692,9 +1716,11 @@ // special case; if it's me and I have no tracks, show info about this sort of use of the app if (myTrack && participant.tracks.length == 0) { $tracksHolder.addClass('no-local-tracks') + $liveTracksContainer.addClass('no-local-tracks') } else { $tracksHolder.removeClass('no-local-tracks') + $liveTracksContainer.removeClass('no-local-tracks') } // loop through all tracks for each participant @@ -2003,16 +2029,13 @@ var otherAudioWidthPct = Math.floor(100 * otherAudioWidth/totalWidth); var liveTrackWidthPct = Math.ceil(100 * liveTrackWidth/totalWidth); - logger.debug("resizeFluid: ", minimumLiveTrackWidth, otherAudioWidth, otherAudioWidthPct, liveTrackWidthPct, liveTrackWidthPct) - + //logger.debug("resizeFluid: ", minimumLiveTrackWidth, otherAudioWidth, otherAudioWidthPct, liveTrackWidthPct, liveTrackWidthPct) $audioTracks.css('width', otherAudioWidthPct + '%'); $liveTracks.css('width', liveTrackWidthPct + '%'); } function _addRecordingTrack(trackData, mixer, oppositeMixer) { - otherAudioFilled(); - $('.session-recordings .recording-controls').show(); var parentSelector = '#session-recordedtracks-container'; @@ -2472,6 +2495,18 @@ return false; } + function sessionWebCam(e) { + e.preventDefault(); + if(webcamViewer.isVideoShared()) { + $('#session-webcam').removeClass("selected") + } else { + $('#session-webcam').addClass("selected") + } + + webcamViewer.toggleWebcam() + return false; + } + // http://stackoverflow.com/questions/2604450/how-to-create-a-jquery-clock-timer function updateRecordingTimer() { @@ -2511,11 +2546,12 @@ } function displayStartedRecording() { + // the commented out code reflects dropping the counter as your recording to save space startTimeDate = new Date; - $recordingTimer = $("(0:00)"); - var $recordingStatus = $('').append("Stop Recording").append($recordingTimer); + //$recordingTimer = $("(0:00)"); + var $recordingStatus = $('').append("Stop Recording")//.append($recordingTimer); $('#recording-status').html( $recordingStatus ); - recordingTimerInterval = setInterval(updateRecordingTimer, 1000); + //recordingTimerInterval = setInterval(updateRecordingTimer, 1000); } function displayStoppingRecording(data) { @@ -2542,7 +2578,7 @@ $recordingTimer = null; $('#recording-start-stop').removeClass('currently-recording'); - $('#recording-status').text("Make a Recording"); + $('#recording-status').text("Make Recording"); } function lockControlsforJamTrackRecording() { @@ -2573,9 +2609,12 @@ } } - function promptUserToSave(recordingId) { + function promptUserToSave(recordingId, timeline) { rest.getRecording( {id: recordingId} ) .done(function(recording) { + if(timeline) { + recording.timeline = timeline.global + } recordingFinishedDialog.setRecording(recording); app.layout.showDialog('recordingFinished').one(EVENTS.DIALOG_CLOSED, function(e, data) { if(data.result && data.result.keep){ @@ -2587,7 +2626,7 @@ } function checkPendingMetronome() { - logger.debug("checkPendingMetronome", sessionModel.isMetronomeOpen(), getMetronomeMasterMixers().length) + //logger.debug("checkPendingMetronome", sessionModel.isMetronomeOpen(), getMetronomeMasterMixers().length) if(sessionModel.isMetronomeOpen() && getMetronomeMasterMixers().length == 0) { var pendingMetronome = $($templatePendingMetronome.html()) @@ -2695,7 +2734,7 @@ text: "Unable to open your JamTrack. Please contact support@jamkazam.com" }, null, true); } else { - rest.playJamTrack(jamTrack.id); + playJamTrack(jamTrack.id); } } }) @@ -2714,8 +2753,21 @@ return false; } - function openBackingTrackFile(e) { + function playJamTrack(jamTrackId) { + var participantCnt=sessionModel.participants().length + rest.playJamTrack(jamTrackId) + .done(function() { + app.refreshUser(); + }) + context.stats.write('web.jamtrack.open', { + value: 1, + session_size: participantCnt, + user_id: context.JK.currentUserId, + user_name: context.JK.currentUserName + }) + }// function + function openBackingTrackFile(e) { // just ignore the click if they are currently recording for now if(sessionModel.recordingModel.isRecording()) { app.notify({ @@ -2726,6 +2778,12 @@ return false; } else { context.jamClient.openBackingTrackFile(sessionModel.backing_track) + context.stats.write('web.backingtrack.open', { + value: 1, + session_size: participantCnt, + user_id: context.JK.currentUserId, + user_name: context.JK.currentUserName + }) //context.JK.CurrentSessionModel.refreshCurrentSession(true); } return false; @@ -2852,6 +2910,12 @@ } function closeBackingTrack() { + + if (sessionModel.recordingModel.isRecording()) { + logger.debug("can't close backing track while recording") + return false; + } + rest.closeBackingTrack({id: sessionModel.id()}) .done(function() { //sessionModel.refreshCurrentSession(true); @@ -2872,9 +2936,13 @@ } function closeJamTrack() { - logger.debug("closing recording"); + if (sessionModel.recordingModel.isRecording()) { + logger.debug("can't close jamtrack while recording") + return false; + } + if(downloadJamTrack) { logger.debug("closing DownloadJamTrack widget") downloadJamTrack.root.remove(); @@ -2942,13 +3010,31 @@ } function onPause(e, data) { - logger.debug("calling jamClient.SessionStopPlay. endReached:", data.endReached); + logger.debug("calling jamClient.SessionPausePlay. endReached:", data.endReached); + + // if a JamTrack is open, and the user hits 'pause' or 'stop', we need to automatically stop the recording + if(sessionModel.jamTracks() && sessionModel.recordingModel.isRecording()) { + startStopRecording(); + } if(!data.endReached) { - context.jamClient.SessionStopPlay(); + context.jamClient.SessionPausePlay(); } } + function onStop(e, data) { + logger.debug("calling jamClient.SessionStopPlay. endReached:", data.endReached); + + // if a JamTrack is open, and the user hits 'pause' or 'stop', we need to automatically stop the recording + if(sessionModel.jamTracks() && sessionModel.recordingModel.isRecording()) { + startStopRecording(); + } + + if(!data.endReached) { + context.jamClient.SessionStopPlay(); + } + } + function onPlay(e, data) { logger.debug("calling jamClient.SessionStartPlay"); context.jamClient.SessionStartPlay(data.playbackMode); @@ -3045,7 +3131,6 @@ var mode = data.playbackMode; // will be either 'self' or 'cricket' logger.debug("setting metronome playback mode: ", mode) - var isCricket = mode == 'cricket'; context.jamClient.setMetronomeCricketTestState(isCricket); } @@ -3078,6 +3163,7 @@ function events() { $('#session-leave').on('click', sessionLeave); $('#session-resync').on('click', sessionResync); + $('#session-webcam').on('click', sessionWebCam); $('#session-contents').on("click", '[action="delete"]', deleteSession); $tracksHolder.on('click', 'div[control="mute"]', toggleMute); $('#recording-start-stop').on('click', startStopRecording); @@ -3109,6 +3195,7 @@ $closePlaybackRecording.on('click', closeOpenMedia); $(playbackControls) .on('pause', onPause) + .on('stop', onStop) .on('play', onPlay) .on('change-position', onChangePlayPosition); $(friendInput).focus(function() { $(this).val(''); }) @@ -3161,7 +3248,10 @@ $fluidTracks = $screen.find('.session-fluidtracks'); $voiceChat = $screen.find('#voice-chat'); $tracksHolder = $screen.find('#tracks') - + if(gon.global.video_available && gon.global.video_available!="none") { + webcamViewer.init($(".webcam-container")) + webcamViewer.setVideoOff() + } events(); diff --git a/web/app/assets/javascripts/sessionModel.js b/web/app/assets/javascripts/sessionModel.js index 3c0afa636..ceb4b70b6 100644 --- a/web/app/assets/javascripts/sessionModel.js +++ b/web/app/assets/javascripts/sessionModel.js @@ -215,6 +215,7 @@ // see if we already have tracks; if so, we need to run with these var inputTracks = context.JK.TrackHelpers.getUserTracks(context.jamClient); + logger.debug("isNoInputProfile", gearUtils.isNoInputProfile()) if(inputTracks.length > 0 || gearUtils.isNoInputProfile() ) { logger.debug("on page enter, tracks are already available") sessionPageEnterDeferred.resolve(inputTracks); diff --git a/web/app/assets/javascripts/shopping_cart.js b/web/app/assets/javascripts/shopping_cart.js index 86157b96d..ac115f175 100644 --- a/web/app/assets/javascripts/shopping_cart.js +++ b/web/app/assets/javascripts/shopping_cart.js @@ -11,10 +11,11 @@ var $content = null; function beforeShow(data) { - loadShoppingCarts(); + } function afterShow(data) { + loadShoppingCarts(); } function afterHide() { @@ -29,18 +30,22 @@ function proceedCheckout(e) { e.preventDefault(); + logger.debug("proceedCheckout") if (!context.JK.currentUserId) { + logger.debug("proceeding to signin screen because there is no user") window.location = '/client#/checkoutSignin'; } else { - app.user().done(function(user) { - if(user.has_recurly_account && user.reuse_card) { - window.location = '/client#/checkoutOrder'; - } - else { - window.location = '/client#/checkoutPayment'; - } - }) + var user = app.currentUser(); + + if(user.has_recurly_account && user.reuse_card) { + logger.debug("proceeding to checkout order screen because we have card info already") + window.location = '/client#/checkoutOrder'; + } + else { + logger.debug("proceeding to checkout payment screen because we do not have card info") + window.location = '/client#/checkoutPayment'; + } } } @@ -55,6 +60,7 @@ .fail(app.ajaxError); } + function clearContent() { $content.empty(); } @@ -69,11 +75,24 @@ function renderShoppingCarts(carts) { var data = {}; - var latest_cart = carts[carts.length - 1]; + + if(carts.length > 0) { + var latest_cart = carts[0]; + } var $latestCartHtml = ""; + var any_in_us = false + context._.each(carts, function(cart) { + if(cart.product_info.sales_region == 'United States') { + any_in_us = true + } + }) + if (latest_cart) { + + latest_cart.any_in_us = any_in_us + $latestCartHtml = $( context._.template( $('#template-shopping-cart-header').html(), @@ -85,9 +104,9 @@ var sub_total = 0; $.each(carts, function(index, cart) { - sub_total += parseFloat(cart.product_info.price) * parseFloat(cart.quantity); + sub_total += parseFloat(cart.product_info.real_price); }); - data.sub_total = sub_total.toFixed(2); + data.sub_total = sub_total; data.carts = carts; var $cartsHtml = $( diff --git a/web/app/assets/javascripts/web/congratulations.js b/web/app/assets/javascripts/web/congratulations.js index 576759f17..31ddf40d4 100644 --- a/web/app/assets/javascripts/web/congratulations.js +++ b/web/app/assets/javascripts/web/congratulations.js @@ -8,13 +8,6 @@ if(musician) { context.JK.Downloads.listClients(true); } - - if(registrationType) { - $(function() { - // ga() object isn't ready until the page is loaded - context.JK.GA.trackRegister(musician, registrationType); - }); - } } context.congratulations = congratulations; diff --git a/web/app/assets/javascripts/web/individual_jamtrack.js b/web/app/assets/javascripts/web/individual_jamtrack.js index bbfd05896..2d33e9c5d 100644 --- a/web/app/assets/javascripts/web/individual_jamtrack.js +++ b/web/app/assets/javascripts/web/individual_jamtrack.js @@ -26,7 +26,7 @@ else { $individualizedHeader.removeClass('hidden') $jamtrack_name.text(jam_track.name); - $jamTracksButton.attr('href', '/client?artist=' + jam_track.original_artist + '#/jamtrack') + $jamTracksButton.attr('href', '/client?artist=' + jam_track.original_artist + '#/jamtrackBrowse') } } @@ -36,7 +36,7 @@ $previews.append($element); - new context.JK.JamTrackPreview(app, $element, jam_track, track, {master_shows_duration: false}) + new context.JK.JamTrackPreview(app, $element, jam_track, track, {master_shows_duration: false, color:'black'}) }) $previews.append('
    ') diff --git a/web/app/assets/javascripts/web/individual_jamtrack_band.js b/web/app/assets/javascripts/web/individual_jamtrack_band.js index 0de60a2e7..df08517c9 100644 --- a/web/app/assets/javascripts/web/individual_jamtrack_band.js +++ b/web/app/assets/javascripts/web/individual_jamtrack_band.js @@ -21,7 +21,7 @@ logger.debug("jam_track", jam_track) $jamTrackBandInfo.text(jam_track.band_jam_track_count + ' ' + jam_track.original_artist); - $jamTracksButton.attr('href', '/client?artist=' + jam_track.original_artist + '#/jamtrack') + $jamTracksButton.attr('href', '/client?artist=' + jam_track.original_artist + '#/jamtrackBrowse') if(jam_track.band_jam_track_count == 1) { $jamTrackNoun.text('JamTrack') @@ -33,7 +33,7 @@ $previews.append($element); - new context.JK.JamTrackPreview(app, $element, jam_track, track, {master_shows_duration: false}) + new context.JK.JamTrackPreview(app, $element, jam_track, track, {master_shows_duration: false, color:'black'}) }) $previews.append('
    ') diff --git a/web/app/assets/javascripts/web/web.js b/web/app/assets/javascripts/web/web.js index 7f128d092..b4e835dbe 100644 --- a/web/app/assets/javascripts/web/web.js +++ b/web/app/assets/javascripts/web/web.js @@ -47,8 +47,8 @@ //= require subscription_utils //= require ui_helper //= require custom_controls -//= require ga //= require jam_rest +//= require ga //= require session_utils //= require recording_utils //= require helpBubbleHelper diff --git a/web/app/assets/javascripts/webcam_viewer.js.coffee b/web/app/assets/javascripts/webcam_viewer.js.coffee new file mode 100644 index 000000000..ed2b1d772 --- /dev/null +++ b/web/app/assets/javascripts/webcam_viewer.js.coffee @@ -0,0 +1,115 @@ +$ = jQuery +context = window +context.JK ||= {}; + +context.JK.WebcamViewer = class WebcamViewer + constructor: (@root) -> + @client = context.jamClient + @logger = context.JK.logger + @initialScan = false + @toggleBtn = null + @webcamSelect = null + @resolutionSelect = null + @videoShared=false + @resolution=null + + init: (root) => + @root = root + @toggleBtn = @root.find(".webcam-test-btn") + @webcamSelect = @root.find(".webcam-select-container select") + @resolutionSelect = @root.find(".webcam-resolution-select-container select") + @webcamSelect.on("change", this.selectWebcam) + @toggleBtn.on 'click', @toggleWebcam + + beforeShow:() => + this.loadWebCams() + this.selectWebcam() + this.loadResolutions() + this.selectResolution() + @initialScan = true + @client.SessStopVideoSharing() + #client.SessSetInsetPosition(5) + #client.SessSetInsetSize(1) + #client.FTUESetAutoSelectVideoLayout(false) + #client.SessSelectVideoDisplayLayoutGroup(1) + + + selectWebcam:(e, data) => + device = @webcamSelect.val() + if device? + caps = @client.FTUEGetVideoCaptureDeviceCapabilities(device) + @logger.debug("Got capabilities from device", caps, device) + @client.FTUESelectVideoCaptureDevice(device, caps) + + selectResolution:() => + @logger.debug 'Selecting res control: ', @resolutionSelect + @resolution = @resolutionSelect.val() + if @resolution? + @logger.debug 'Selecting res: ', @resolution + @client.FTUESetVideoEncodeResolution @resolution + # if @isVideoShared + # this.setVideoOff() + # this.toggleWebcam() + + setVideoOff:() => + if this.isVideoShared() + @client.SessStopVideoSharing() + + isVideoShared:() => + @videoShared + + setToggleState:() => + available = @webcamSelect.find('option').size() > 0 + shared = this.isVideoShared() + @toggleBtn.prop 'disabled', true + @toggleBtn.prop 'disabled', !available + + toggleWebcam:() => + @logger.debug 'Toggling webcam from: ', this.isVideoShared() + if this.isVideoShared() + @toggleBtn.removeClass("selected") + @client.SessStopVideoSharing() + @videoShared = false + else + @toggleBtn.addClass("selected") + @client.SessStartVideoSharing 0 + @videoShared = true + + selectedDeviceName:() => + webcamName="None Configured" + webcam = @client.FTUECurrentSelectedVideoDevice() + if (webcam? && Object.keys(webcam).length>0) + webcamName = _.values(webcam)[0] + + webcamName + + loadWebCams:() => + devices = @client.FTUEGetVideoCaptureDeviceNames() + selectedDevice = this.selectedDeviceName() + selectControl = @webcamSelect + context._.each devices, (device) -> + selected = device == selectedDevice + option = $('