From 1de7c86e29db17c5c558d38065a9f9e582f1f6bf Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Wed, 25 Feb 2015 09:41:03 -0600 Subject: [PATCH 01/82] VRFS-2785 : Fields for new purchased jam track. --- .../views/admin/jam_tracks/_jam_track_right_fields.html.slim | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 admin/app/views/admin/jam_tracks/_jam_track_right_fields.html.slim diff --git a/admin/app/views/admin/jam_tracks/_jam_track_right_fields.html.slim b/admin/app/views/admin/jam_tracks/_jam_track_right_fields.html.slim new file mode 100644 index 000000000..9bf130e56 --- /dev/null +++ b/admin/app/views/admin/jam_tracks/_jam_track_right_fields.html.slim @@ -0,0 +1,5 @@ += f.inputs name: 'Jam Track Right fields' do + + ol.nested-fields + = f.input :jam_track, :required=>true, collection: JamTrack.all, include_blank: false + = f.input :user, :required=>true, collection: User.all, include_blank: false From 9c9c01b2fd9eb2b893407969990e0ae272691ec6 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Wed, 25 Feb 2015 09:41:51 -0600 Subject: [PATCH 02/82] VRFS-2785 : Make id fields accessible for admin UI. --- ruby/lib/jam_ruby/models/jam_track_right.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/ruby/lib/jam_ruby/models/jam_track_right.rb b/ruby/lib/jam_ruby/models/jam_track_right.rb index affee58a5..b8bf19047 100644 --- a/ruby/lib/jam_ruby/models/jam_track_right.rb +++ b/ruby/lib/jam_ruby/models/jam_track_right.rb @@ -4,6 +4,7 @@ module JamRuby class JamTrackRight < ActiveRecord::Base include JamRuby::S3ManagerMixin attr_accessible :user, :jam_track, :user_id, :jam_track_id, :url, :md5, :length, :download_count + attr_accessible :user_id, :jam_track_id, as: :admin belongs_to :user, class_name: "JamRuby::User" # the owner, or purchaser of the jam_track belongs_to :jam_track, class_name: "JamRuby::JamTrack" From 7f851c66cb0206fbe9b44a98c94dbea11e93cefe Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Tue, 24 Feb 2015 10:12:32 -0600 Subject: [PATCH 03/82] VRFS-2797 : New shopping cart icon. Also a transparent small version to use as icon. --- .../images/content/icon_shopping_cart.png | Bin 1316 -> 4324 bytes .../assets/images/content/shopping-cart.png | Bin 7355 -> 2274 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/web/app/assets/images/content/icon_shopping_cart.png b/web/app/assets/images/content/icon_shopping_cart.png index 24bf9b09f7d0189a9a44c495614a7fe68708bfc2..511fe7c777160a42ba017f105b1d5ae447e1421d 100644 GIT binary patch literal 4324 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 Qk6s;G>bi&$Rja^%0U+0;zyJUM delta 1298 zcmV+t1?~FeA*2eBBYyw{XF*Lt006O$eEU(80000WV@Og>004R>004l5008;`004mK z004C`008P>0026e000+ooVrmw00006VoOIv0RI600RN!9r;`8x1Qtm|K~y-)os>&# zRAm&$|L40i-<`Q_XF5|#ANZg=gg`=cAxZ-wy3hp+Dei#SLcYP5vU>OL|> z2fvM1a9k_6R|@z@D@3!i=r;*zq40C$*0_%A!iYCri+{Ft`H2K5+DdblM$onxc5}#X zx{Vyk><|!mL{3a`J-LXScz~cajkz{JiI6YP1OJ|x0b7YFG__X(UoNV8{*)$^9ioZ` zrai)}1SAo>hDOx~rX8RXGM8+o8B4Nk_!ylBhJg=L${E$to`+$rNSgju5n@>)0%0h0 z3j>`(!+!!nSq6Ne(4H{RNg{(h9MPk^PFQ7wz%+pzpP4|_JE zp2JgKjT;S{OPuOMG_j#OfZQMuto|7hyzX<7Lv;4lv~HZ2S*qSfLDhugW~Z_^QyH4d*x#rRN!ua zCVxQObTe?n-F!1^00uv+wb1_Z)EAVWoHsv&5Ku~C&z?OmKC<-Too$wtW#$D9|0|eT zRA$TH^}X`iw{(Xl_V3^SNmR=G>C>k_wk(T4L|~?Sfr!9bGZT>@qPid&qklz$)*1&6 z9N3ahr{B!wa!I9B2ehh7GnND0%?tv+1Am}qAw)q4%2cb>w->IM8Klz*2~l5 z=Z|ryy}!IbO$2^`doCLQnvh8}{30#fqwMJF+?a@)o62_mWT{X% zM?}FtDu^Dl9(wg70000bbVXQnWPf3FWjbSWWnpw>05UK!G%YYUEiy4wF)%tZI65>m zD=;!TFfbYlyz>A603~!qSaf7zbY(hiZ)9m^c>ppnF*GeOH!U(TR536*GB`RkG%GMN zIxsM6R|HQ0000?uMObuGZ)S9NVRB^vL1b@YWgtmyVP|DhWnpA_ami&o01E&B07*qo IM6N<$g8UjrF8}}l diff --git a/web/app/assets/images/content/shopping-cart.png b/web/app/assets/images/content/shopping-cart.png index eef1a4c69deeb80b91d380a40acbaed8241ebfaa..62e6cc2fbc705c6badbb81048e452fb2eb9b960c 100644 GIT binary patch literal 2274 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 literal 7355 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 Date: Wed, 25 Feb 2015 17:19:03 -0600 Subject: [PATCH 04/82] VRFS-2782 : Support multiple bit rates for jam tracks: migration, model and test updates. --- db/manifest | 3 ++- db/up/add_jam_track_bitrates.sql | 14 ++++++++++++++ ruby/lib/jam_ruby/jam_tracks_manager.rb | 2 +- ruby/lib/jam_ruby/models/jam_track.rb | 6 +++--- ruby/lib/jam_ruby/models/jam_track_right.rb | 13 ++++++++----- ruby/lib/jam_ruby/models/jam_track_track.rb | 13 ++++++++----- ruby/lib/jam_ruby/resque/jam_tracks_builder.rb | 2 +- ruby/spec/jam_ruby/models/jam_track_right_spec.rb | 10 +++++----- ruby/spec/jam_ruby/models/jam_track_track_spec.rb | 6 +++--- .../jam_ruby/resque/jam_tracks_cleaner_spec.rb | 8 ++++---- web/app/controllers/api_jam_tracks_controller.rb | 6 +++--- 11 files changed, 52 insertions(+), 31 deletions(-) create mode 100644 db/up/add_jam_track_bitrates.sql diff --git a/db/manifest b/db/manifest index 55f1373b5..7a7bc5cff 100755 --- a/db/manifest +++ b/db/manifest @@ -252,4 +252,5 @@ metronome.sql recorded_backing_tracks.sql recorded_backing_tracks_add_filename.sql user_syncs_include_backing_tracks.sql -remove_bpm_from_jamtracks.sql \ No newline at end of file +remove_bpm_from_jamtracks.sql +add_jam_track_bitrates.sql \ No newline at end of file diff --git a/db/up/add_jam_track_bitrates.sql b/db/up/add_jam_track_bitrates.sql new file mode 100644 index 000000000..aadfd2ead --- /dev/null +++ b/db/up/add_jam_track_bitrates.sql @@ -0,0 +1,14 @@ +ALTER TABLE jam_track_tracks RENAME COLUMN url TO url_48; +ALTER TABLE jam_track_tracks RENAME COLUMN md5 TO md5_48; +ALTER TABLE jam_track_tracks RENAME COLUMN length TO length_48; +ALTER TABLE jam_track_tracks ADD COLUMN url_44 VARCHAR; +ALTER TABLE jam_track_tracks ADD COLUMN md5_44 VARCHAR; +ALTER TABLE jam_track_tracks ADD COLUMN length_44 BIGINT; + +ALTER TABLE jam_track_rights RENAME COLUMN url TO url_48; +ALTER TABLE jam_track_rights RENAME COLUMN md5 TO md5_48; +ALTER TABLE jam_track_rights RENAME COLUMN length TO length_48; +ALTER TABLE jam_track_rights ADD COLUMN url_44 VARCHAR; +ALTER TABLE jam_track_rights ADD COLUMN md5_44 VARCHAR; +ALTER TABLE jam_track_rights ADD COLUMN length_44 BIGINT; + diff --git a/ruby/lib/jam_ruby/jam_tracks_manager.rb b/ruby/lib/jam_ruby/jam_tracks_manager.rb index a5c04cd61..81a1b7b4e 100644 --- a/ruby/lib/jam_ruby/jam_tracks_manager.rb +++ b/ruby/lib/jam_ruby/jam_tracks_manager.rb @@ -58,7 +58,7 @@ module JamRuby #raise ArgumentError, "output_jkz is empty #{output_jkz}" unless File.exists?(output_jkz) - jam_track_right.url.store!(File.open(output_jkz, "rb")) + jam_track_right.url_48.store!(File.open(output_jkz, "rb")) jam_track_right.signed=true jam_track_right.downloaded_since_sign=false jam_track_right.private_key=File.read("#{tmp_dir}/skey.pem") diff --git a/ruby/lib/jam_ruby/models/jam_track.rb b/ruby/lib/jam_ruby/models/jam_track.rb index 3d884ca3a..3fb7fabdd 100644 --- a/ruby/lib/jam_ruby/models/jam_track.rb +++ b/ruby/lib/jam_ruby/models/jam_track.rb @@ -126,9 +126,9 @@ module JamRuby :type => "jam_track", :id => jam_track_right.id.to_s, :jam_track_id => jam_track_right.jam_track_id, - :length => jam_track_right.length, - :md5 => jam_track_right.md5, - :url => jam_track_right.url, + :length => jam_track_right.length_48, + :md5 => jam_track_right.md5_48, + :url => jam_track_right.url_48, :created_at => jam_track_right.created_at, :next => jam_track_right.id } diff --git a/ruby/lib/jam_ruby/models/jam_track_right.rb b/ruby/lib/jam_ruby/models/jam_track_right.rb index b8bf19047..e113670b5 100644 --- a/ruby/lib/jam_ruby/models/jam_track_right.rb +++ b/ruby/lib/jam_ruby/models/jam_track_right.rb @@ -3,8 +3,9 @@ module JamRuby # describes what users have rights to which tracks class JamTrackRight < ActiveRecord::Base include JamRuby::S3ManagerMixin - attr_accessible :user, :jam_track, :user_id, :jam_track_id, :url, :md5, :length, :download_count + attr_accessible :user, :jam_track, :user_id, :jam_track_id, :download_count attr_accessible :user_id, :jam_track_id, as: :admin + attr_accessible :url_48, :md5_48, :length_48, :url_44, :md5_44, :length_44 belongs_to :user, class_name: "JamRuby::User" # the owner, or purchaser of the jam_track belongs_to :jam_track, class_name: "JamRuby::JamTrack" @@ -16,7 +17,8 @@ module JamRuby validates_uniqueness_of :user_id, scope: :jam_track_id # Uploads the JKZ: - mount_uploader :url, JamTrackRightUploader + mount_uploader :url_48, JamTrackRightUploader + mount_uploader :url_44, JamTrackRightUploader before_destroy :delete_s3_files MAX_JAM_TRACK_DOWNLOADS = 1000 @@ -82,12 +84,13 @@ module JamRuby # the idea is that this is used when a user who has the rights to this tries to download this JamTrack # we would verify their rights (can_download?), and generates a URL in response to the click so that they can download # but the url is short lived enough so that it wouldn't be easily shared - def sign_url(expiration_time = 120) - s3_manager.sign_url(self[:url], {:expires => expiration_time, :secure => false}) + def sign_url(expiration_time = 120, bitrate=48) + field_name = (bitrate==48) ? "url_48" : "url_44" + s3_manager.sign_url(self[field_name], {:expires => expiration_time, :secure => false}) end def delete_s3_files - remove_url! + remove_url_48! end def enqueue diff --git a/ruby/lib/jam_ruby/models/jam_track_track.rb b/ruby/lib/jam_ruby/models/jam_track_track.rb index 978ed29ab..65dc1816c 100644 --- a/ruby/lib/jam_ruby/models/jam_track_track.rb +++ b/ruby/lib/jam_ruby/models/jam_track_track.rb @@ -7,9 +7,11 @@ module JamRuby # there should only be one Master per JamTrack, but there can be N Track per JamTrack TRACK_TYPE = %w{Master Track} - mount_uploader :url, JamTrackTrackUploader + mount_uploader :url_48, JamTrackTrackUploader + mount_uploader :url_44, JamTrackTrackUploader - attr_accessible :jam_track_id, :track_type, :instrument, :instrument_id, :position, :part, :url, as: :admin + attr_accessible :jam_track_id, :track_type, :instrument, :instrument_id, :position, :part, as: :admin + attr_accessible :url_44, :url_48, :md5_44, :md5_48, :length_44, :length_48, as: :admin validates :position, presence: true, numericality: {only_integer: true}, length: {in: 1..1000} validates :part, length: {maximum: 20} @@ -35,10 +37,11 @@ module JamRuby # the idea is that this is used when a user who has the rights to this tries to download this JamTrack # we would verify their rights (can_download?), and generates a URL in response to the click so that they can download # but the url is short lived enough so that it wouldn't be easily shared - def sign_url(expiration_time = 120) - s3_manager.sign_url(self[:url], {:expires => expiration_time, :response_content_type => 'audio/ogg', :secure => false}) + def sign_url(expiration_time = 120, bitrate=48) + field_name = (bitrate==48) ? "url_48" : "url_44" + s3_manager.sign_url(self[field_name], {:expires => expiration_time, :response_content_type => 'audio/ogg', :secure => false}) end - + def can_download?(user) # I think we have to make a special case for 'previews', but maybe that's just up to the controller to not check can_download? jam_track.owners.include?(user) diff --git a/ruby/lib/jam_ruby/resque/jam_tracks_builder.rb b/ruby/lib/jam_ruby/resque/jam_tracks_builder.rb index 19d836462..6d352dece 100644 --- a/ruby/lib/jam_ruby/resque/jam_tracks_builder.rb +++ b/ruby/lib/jam_ruby/resque/jam_tracks_builder.rb @@ -40,7 +40,7 @@ module JamRuby JamRuby::JamTracksManager.save_jam_track_right_jkz(@jam_track_right) - length = @jam_track_right.url.size() + length = @jam_track_right.url_48.size() md5 = Digest::MD5.new @jam_track_right.finish_sign(length, md5.to_s) 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 b92bfa04b..4d81c68c4 100644 --- a/ruby/spec/jam_ruby/models/jam_track_right_spec.rb +++ b/ruby/spec/jam_ruby/models/jam_track_right_spec.rb @@ -71,22 +71,22 @@ describe JamTrackRight do uploader.store!(File.open(ogg_path, 'rb')) jam_track_track.save! - jam_track_track[:url].should == jam_track_track.store_dir + '/' + jam_track_track.filename + jam_track_track[:url_48].should == jam_track_track.store_dir + '/' + jam_track_track.filename # verify it's on S3 s3 = S3Manager.new(APP_CONFIG.aws_bucket, APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key) - s3.exists?(jam_track_track[:url]).should be_true - s3.length(jam_track_track[:url]).should == File.size?(ogg_path) + s3.exists?(jam_track_track[:url_48]).should be_true + s3.length(jam_track_track[:url_48]).should == File.size?(ogg_path) jam_track_right = JamTrackRight.create(:user=>user, :jam_track=>jam_track) #expect { JamRuby::JamTracksManager.save_jam_track_jkz(user, jam_track) #}.to_not raise_error(ArgumentError) jam_track_right.reload - jam_track_right[:url].should == jam_track_right.store_dir + '/' + jam_track_right.filename + jam_track_right[:url_48].should == jam_track_right.store_dir + '/' + jam_track_right.filename # verify it's on S3 - url = jam_track_right[:url] + url = jam_track_right[:url_48] s3 = S3Manager.new(APP_CONFIG.aws_bucket, APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key) s3.exists?(url).should be_true s3.length(url).should > File.size?(ogg_path) diff --git a/ruby/spec/jam_ruby/models/jam_track_track_spec.rb b/ruby/spec/jam_ruby/models/jam_track_track_spec.rb index 6cc5c3773..7ffa09edd 100644 --- a/ruby/spec/jam_ruby/models/jam_track_track_spec.rb +++ b/ruby/spec/jam_ruby/models/jam_track_track_spec.rb @@ -52,12 +52,12 @@ describe JamTrackTrack do jam_track_track.save! # verify that the uploader stores the correct path - jam_track_track[:url].should == jam_track_track.store_dir + '/' + jam_track_track.filename + jam_track_track[:url_48].should == jam_track_track.store_dir + '/' + jam_track_track.filename # verify it's on S3 s3 = S3Manager.new(APP_CONFIG.aws_bucket, APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key) - s3.exists?(jam_track_track[:url]).should be_true - s3.length(jam_track_track[:url]).should == 'abc'.length + s3.exists?(jam_track_track[:url_48]).should be_true + s3.length(jam_track_track[:url_48]).should == 'abc'.length # download it via signed URL, and check contents url = jam_track_track.sign_url diff --git a/ruby/spec/jam_ruby/resque/jam_tracks_cleaner_spec.rb b/ruby/spec/jam_ruby/resque/jam_tracks_cleaner_spec.rb index 36cb5a067..1c900bc52 100644 --- a/ruby/spec/jam_ruby/resque/jam_tracks_cleaner_spec.rb +++ b/ruby/spec/jam_ruby/resque/jam_tracks_cleaner_spec.rb @@ -26,20 +26,20 @@ describe JamTracksCleaner do jam_track_right.signed=true jam_track_right - jam_track_right.url.store!(File.open(RIGHT_NAME)) + jam_track_right.url_48.store!(File.open(RIGHT_NAME)) jam_track_right.downloaded_since_sign=true jam_track_right.save! - jam_track_right[:url].should == jam_track_right.store_dir + '/' + jam_track_right.filename + jam_track_right[:url_48].should == jam_track_right.store_dir + '/' + jam_track_right.filename jam_track_right.reload # Should exist after uploading: - url = jam_track_right[:url] + url = jam_track_right[:url_48] s3 = S3Manager.new(APP_CONFIG.aws_bucket, APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key) url.should_not be_nil s3 = S3Manager.new(APP_CONFIG.aws_bucket, APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key) - s3.exists?(jam_track_right[:url]).should be_true + s3.exists?(jam_track_right[:url_48]).should be_true JamRuby::JamTracksCleaner.perform s3.exists?(url).should be_true diff --git a/web/app/controllers/api_jam_tracks_controller.rb b/web/app/controllers/api_jam_tracks_controller.rb index cb257be16..0bc81cbc7 100644 --- a/web/app/controllers/api_jam_tracks_controller.rb +++ b/web/app/controllers/api_jam_tracks_controller.rb @@ -25,7 +25,7 @@ class ApiJamTracksController < ApiController def downloads begin - render :json => JamTrack.list_downloads(current_user, params[:limit], params[:since]), :status => 200 + render :json => JamTrack.list_downloads(current_user, params[:limit], params[:since], params[:bitrate]), :status => 200 rescue render :json => { :message => "could not produce list of files" }, :status => 403 end @@ -33,11 +33,11 @@ class ApiJamTracksController < ApiController def download if @jam_track_right.valid? - if (@jam_track_right && @jam_track_right.signed && @jam_track_right.url.present? &&@jam_track_right.url.file.exists?) + if (@jam_track_right && @jam_track_right.signed && @jam_track_right.url_48.present? &&@jam_track_right.url_48.file.exists?) @jam_track_right.update_download_count @jam_track_right.last_downloaded_at = Time.now @jam_track_right.save! - redirect_to @jam_track_right.sign_url + redirect_to @jam_track_right.sign_url(120, params[:bitrate]) else @jam_track_right.enqueue_if_needed render :json => { :message => "not available, digitally signing Jam Track offline." }, :status => 202 From bd08b40e71f425cdc6859e92cc2215b49ab6ac9b Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Thu, 26 Feb 2015 17:00:01 -0600 Subject: [PATCH 05/82] VRFS-2782 : Support multiple bitrates throughout models, managers, up loaders and builders. --- .../app/uploaders/jam_track_track_uploader.rb | 8 ++++- ruby/lib/jam_ruby/jam_tracks_manager.rb | 19 ++++++----- ruby/lib/jam_ruby/models/jam_track.rb | 17 +++++++--- ruby/lib/jam_ruby/models/jam_track_right.rb | 34 ++++++++++++++----- .../lib/jam_ruby/resque/jam_tracks_builder.rb | 12 +++---- .../jam_ruby/models/jam_track_right_spec.rb | 2 +- .../jam_ruby/models/jam_track_track_spec.rb | 2 +- 7 files changed, 63 insertions(+), 31 deletions(-) diff --git a/ruby/lib/jam_ruby/app/uploaders/jam_track_track_uploader.rb b/ruby/lib/jam_ruby/app/uploaders/jam_track_track_uploader.rb index 9defe1099..d2a80ce3d 100644 --- a/ruby/lib/jam_ruby/app/uploaders/jam_track_track_uploader.rb +++ b/ruby/lib/jam_ruby/app/uploaders/jam_track_track_uploader.rb @@ -23,6 +23,12 @@ class JamTrackTrackUploader < CarrierWave::Uploader::Base end def filename - "#{model.store_dir}/#{model.filename}" if model.id + if model.id + if mounted_as==:url_48 + "#{model.store_dir}/#{model.filename}" + else + "#{model.store_dir}/#{mounted_as}/#{model.filename}" + end + end end end diff --git a/ruby/lib/jam_ruby/jam_tracks_manager.rb b/ruby/lib/jam_ruby/jam_tracks_manager.rb index 81a1b7b4e..cf7ed79c7 100644 --- a/ruby/lib/jam_ruby/jam_tracks_manager.rb +++ b/ruby/lib/jam_ruby/jam_tracks_manager.rb @@ -12,13 +12,13 @@ module JamRuby @@log = Logging.logger[JamTracksManager] class << self - def save_jam_track_jkz(user, jam_track) + def save_jam_track_jkz(user, jam_track, bitrate=48) jam_track_right = jam_track.right_for_user(user) raise ArgumentError if jam_track_right.nil? - save_jam_track_right_jkz(jam_track_right) + save_jam_track_right_jkz(jam_track_right, bitrate) end - def save_jam_track_right_jkz(jam_track_right) + def save_jam_track_right_jkz(jam_track_right, bitrate=48) jam_track = jam_track_right.jam_track #py_root = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "..", "jamtracks")) py_root = APP_CONFIG.jamtracks_dir @@ -31,7 +31,7 @@ module JamRuby # use the jam_track_track ID as the filename.ogg/.wav, because it's important metadata nm = jam_track_track.id + File.extname(jam_track_track.filename) track_filename = File.join(tmp_dir, nm) - track_url = jam_track_track.sign_url + track_url = jam_track_track.sign_url(120, bitrate) copy_url_to_file(track_url, track_filename) copy_url_to_file(track_url, File.join(".", nm)) jam_file_opts << " -i '#{track_filename}+#{jam_track_track.part}'" @@ -54,11 +54,14 @@ module JamRuby #puts "stdout: #{out}, stderr: #{err}" raise ArgumentError, "Error calling python script: #{err}" if err.present? raise ArgumentError, "Error calling python script: #{out}" if out && (out.index("No track files specified") || out.index("Cannot find file")) - jam_track_right[:url] - + #raise ArgumentError, "output_jkz is empty #{output_jkz}" unless File.exists?(output_jkz) - - jam_track_right.url_48.store!(File.open(output_jkz, "rb")) + if bitrate==48 + jam_track_right.url_48.store!(File.open(output_jkz, "rb")) + else + jam_track_right.url_44.store!(File.open(output_jkz, "rb")) + end + jam_track_right.signed=true jam_track_right.downloaded_since_sign=false jam_track_right.private_key=File.read("#{tmp_dir}/skey.pem") diff --git a/ruby/lib/jam_ruby/models/jam_track.rb b/ruby/lib/jam_ruby/models/jam_track.rb index 3fb7fabdd..557316541 100644 --- a/ruby/lib/jam_ruby/models/jam_track.rb +++ b/ruby/lib/jam_ruby/models/jam_track.rb @@ -114,7 +114,7 @@ module JamRuby jam_track_rights.where("user_id=?", user).first end - def self.list_downloads(user, limit = 100, since = 0) + def self.list_downloads(user, limit = 100, since = 0, bitrate = 48) since = 0 unless since || since == '' # guard against nil downloads = [] @@ -122,16 +122,23 @@ module JamRuby .limit(limit) .where('jam_track_rights.id > ?', since) .each do |jam_track_right| - downloads << { + download = { :type => "jam_track", :id => jam_track_right.id.to_s, :jam_track_id => jam_track_right.jam_track_id, - :length => jam_track_right.length_48, - :md5 => jam_track_right.md5_48, - :url => jam_track_right.url_48, :created_at => jam_track_right.created_at, :next => jam_track_right.id } + if(bitrate==48) + download[:length] = jam_track_right.length_48 + download[:md5] = jam_track_right.md5_48 + download[:url] = jam_track_right.url_48 + else + download[:length] = jam_track_right.length_44 + download[:md5] = jam_track_right.md5_44 + download[:url] = jam_track_right.url_44 + end + downloads << download end next_id = downloads[-1][:next] if downloads.length > 0 diff --git a/ruby/lib/jam_ruby/models/jam_track_right.rb b/ruby/lib/jam_ruby/models/jam_track_right.rb index e113670b5..094339e27 100644 --- a/ruby/lib/jam_ruby/models/jam_track_right.rb +++ b/ruby/lib/jam_ruby/models/jam_track_right.rb @@ -62,12 +62,17 @@ module JamRuby else raise "Error sending notification #{self.errors}" end - end - def finish_sign(length, md5) + + def finish_sign(length, md5, bitrate) self.last_signed_at = Time.now - self.length = length - self.md5 = md5 + if bitrate==48 + self.length_48 = length + self.md5_48 = md5 + else + self.length_44 = length + self.md5_44 = md5 + end self.signed = true self.error_count = 0 self.error_reason = nil @@ -91,30 +96,41 @@ module JamRuby def delete_s3_files remove_url_48! + remove_url_44! end - def enqueue + def enqueue(bitrate=48) begin JamTrackRight.where(:id => self.id).update_all(:signing_queued_at => Time.now, :signing_started_at => nil, :last_signed_at => nil) - Resque.enqueue(JamTracksBuilder, self.id) + Resque.enqueue(JamTracksBuilder, self.id, bitrate: bitrate) true rescue Exception => e + puts "e: #{e}" # implies redis is down. we don't update started_at by bailing out here false end end # if the job is already signed, just queued up for signing, or currently signing, then don't enqueue... otherwise fire it off - def enqueue_if_needed + def enqueue_if_needed(bitrate=48) state = signing_state - if state == 'SIGNED' || state == 'SIGNING' || state == 'QUEUED' false else - enqueue + enqueue(bitrate) true end end + + # @return true if signed && file exists for the bitrate specifed: + def ready?(bitrate=48) + if bitrate==48 + self.signed && self.url_48.present? && self.url_48.file.exists? + else + self.signed && self.url_44.present? && self.url_44.file.exists? + end + end + # returns easy to digest state field # SIGNED - the package is ready to be downloaded # ERROR - the package was built unsuccessfully diff --git a/ruby/lib/jam_ruby/resque/jam_tracks_builder.rb b/ruby/lib/jam_ruby/resque/jam_tracks_builder.rb index 6d352dece..f60d1f26b 100644 --- a/ruby/lib/jam_ruby/resque/jam_tracks_builder.rb +++ b/ruby/lib/jam_ruby/resque/jam_tracks_builder.rb @@ -7,7 +7,7 @@ require 'digest/md5' module JamRuby class JamTracksBuilder extend JamRuby::ResqueStats - + attr_accessor :bitrate @queue = :jam_tracks_builder def log @@ -16,9 +16,10 @@ module JamRuby attr_accessor :jam_track_right_id - def self.perform(jam_track_right_id) + def self.perform(jam_track_right_id, bitrate) jam_track_builder = JamTracksBuilder.new() jam_track_builder.jam_track_right_id = jam_track_right_id + jam_track_builder.bitrate=bitrate jam_track_builder.run end @@ -38,12 +39,11 @@ module JamRuby # 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) - JamRuby::JamTracksManager.save_jam_track_right_jkz(@jam_track_right) + JamRuby::JamTracksManager.save_jam_track_right_jkz(@jam_track_right, bitrate) - length = @jam_track_right.url_48.size() + length = bitrate==48 ? @jam_track_right.url_48.size() : @jam_track_right.url_44.size() md5 = Digest::MD5.new - - @jam_track_right.finish_sign(length, md5.to_s) + @jam_track_right.finish_sign(length, md5.to_s, bitrate) log.info "Signed jamtrack to #{@jam_track_right[:url]}" 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 4d81c68c4..e06e212b2 100644 --- a/ruby/spec/jam_ruby/models/jam_track_right_spec.rb +++ b/ruby/spec/jam_ruby/models/jam_track_right_spec.rb @@ -67,7 +67,7 @@ describe JamTrackRight do jam_track_track = FactoryGirl.create(:jam_track_track) jam_track = jam_track_track.jam_track - uploader = JamTrackTrackUploader.new(jam_track_track, :url) + uploader = JamTrackTrackUploader.new(jam_track_track, :url_48) uploader.store!(File.open(ogg_path, 'rb')) jam_track_track.save! diff --git a/ruby/spec/jam_ruby/models/jam_track_track_spec.rb b/ruby/spec/jam_ruby/models/jam_track_track_spec.rb index 7ffa09edd..ed128a097 100644 --- a/ruby/spec/jam_ruby/models/jam_track_track_spec.rb +++ b/ruby/spec/jam_ruby/models/jam_track_track_spec.rb @@ -47,7 +47,7 @@ describe JamTrackTrack do it "uploads to s3 with correct name, and then downloads via signed URL" do jam_track_track = FactoryGirl.create(:jam_track_track) - uploader = JamTrackTrackUploader.new(jam_track_track, :url) + uploader = JamTrackTrackUploader.new(jam_track_track, :url_48) uploader.store!(File.open(TRACK_NAME)) # uploads file jam_track_track.save! From a2f681d0b4a1ff83920b51193ce58cb81855e421 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Thu, 26 Feb 2015 17:00:53 -0600 Subject: [PATCH 06/82] VRFS-2782 : Multiple bitrates in controllers --- web/app/controllers/api_jam_tracks_controller.rb | 13 ++++++------- web/app/controllers/api_recordings_controller.rb | 2 +- .../controllers/api_jam_tracks_controller_spec.rb | 2 +- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/web/app/controllers/api_jam_tracks_controller.rb b/web/app/controllers/api_jam_tracks_controller.rb index 0bc81cbc7..567d0b7ad 100644 --- a/web/app/controllers/api_jam_tracks_controller.rb +++ b/web/app/controllers/api_jam_tracks_controller.rb @@ -33,13 +33,14 @@ class ApiJamTracksController < ApiController def download if @jam_track_right.valid? - if (@jam_track_right && @jam_track_right.signed && @jam_track_right.url_48.present? &&@jam_track_right.url_48.file.exists?) + bitrate = params[:bitrate] + if (@jam_track_right && @jam_track_right.ready?(bitrate)) @jam_track_right.update_download_count @jam_track_right.last_downloaded_at = Time.now @jam_track_right.save! - redirect_to @jam_track_right.sign_url(120, params[:bitrate]) + redirect_to @jam_track_right.sign_url(120, bitrate) else - @jam_track_right.enqueue_if_needed + @jam_track_right.enqueue_if_needed(bitrate) render :json => { :message => "not available, digitally signing Jam Track offline." }, :status => 202 end else @@ -48,8 +49,7 @@ class ApiJamTracksController < ApiController end def enqueue - @jam_track_right.enqueue_if_needed - + @jam_track_right.enqueue_if_needed(params[:bitrate]) render :json => { :message => "enqueued" }, :status => 200 end @@ -59,14 +59,13 @@ class ApiJamTracksController < ApiController def keys jamtrack_ids = params[:jamtracks] - + unless jamtrack_ids.kind_of?(Array) render :json => {message: 'jamtracks parameter must be an array'}, :status => 200 return end @jam_tracks = JamTrackRight.list_keys(current_user, params[:jamtracks]) - render "api_jam_tracks/list_keys", :layout => nil end diff --git a/web/app/controllers/api_recordings_controller.rb b/web/app/controllers/api_recordings_controller.rb index 5caa975a8..97ac23a22 100644 --- a/web/app/controllers/api_recordings_controller.rb +++ b/web/app/controllers/api_recordings_controller.rb @@ -57,7 +57,7 @@ class ApiRecordingsController < ApiController @recorded_track.valid? if !@recorded_track.errors.any? @recorded_track.save! - redirect_to @recorded_track.sign_url + redirect_to @recorded_track.sign_url() else render :json => { :message => "download limit surpassed" }, :status => 404 end diff --git a/web/spec/controllers/api_jam_tracks_controller_spec.rb b/web/spec/controllers/api_jam_tracks_controller_spec.rb index 31101592c..85773b25e 100644 --- a/web/spec/controllers/api_jam_tracks_controller_spec.rb +++ b/web/spec/controllers/api_jam_tracks_controller_spec.rb @@ -114,7 +114,7 @@ describe ApiJamTracksController do @jam_track = FactoryGirl.create(:jam_track) #jam_track_track.jam_track jam_track_track = @jam_track.jam_track_tracks.first - uploader = JamTrackTrackUploader.new(jam_track_track, :url) + uploader = JamTrackTrackUploader.new(jam_track_track, :url_48) uploader.store!(File.open(@ogg_path, 'rb')) #jam_track_track.url.store!(File.open(ogg_path, "rb")) jam_track_track.save! From e6c49a18b2192ccbff9355e5e68b3d355abab214 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Thu, 26 Feb 2015 17:01:44 -0600 Subject: [PATCH 07/82] VRFS-2782 : Add a jam tracks builder spec to more directly exercise building, in addition to specific tests for 48kHz vs 44kHz --- .../resque/jam_tracks_builder_spec.rb | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 ruby/spec/jam_ruby/resque/jam_tracks_builder_spec.rb diff --git a/ruby/spec/jam_ruby/resque/jam_tracks_builder_spec.rb b/ruby/spec/jam_ruby/resque/jam_tracks_builder_spec.rb new file mode 100644 index 000000000..bf72bd00d --- /dev/null +++ b/ruby/spec/jam_ruby/resque/jam_tracks_builder_spec.rb @@ -0,0 +1,83 @@ +require 'spec_helper' + +describe JamTracksBuilder do + include UsesTempFiles + include CarrierWave::Test::Matchers + + before (:all) do + @user = FactoryGirl.create(:user) + @jam_track = FactoryGirl.create(:jam_track) + original_storage = JamTrackTrackUploader.storage = :fog + original_storage = JamTrackRightUploader.storage = :fog + @s3 = S3Manager.new(APP_CONFIG.aws_bucket, APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key) + end + + after(:all) do + JamTrackRightUploader.storage = @original_storage + JamTrackTrackUploader.storage = @original_storage + end + + before(:each) do + @s3.delete_folder('jam_tracks') + end + + it "should build" do + ogg_path = File.join('spec', 'files', 'on.ogg') + user = FactoryGirl.create(:user) + jam_track_track = FactoryGirl.create(:jam_track_track) + jam_track = jam_track_track.jam_track + + #uploader = JamTrackTrackUploader.new(jam_track_track, :url_48) + jam_track_track.url_48.store!(File.open(ogg_path, 'rb')) + jam_track_track.save! + + jam_track_track[:url_48].should == jam_track_track.store_dir + '/' + jam_track_track.filename + + # verify it's on S3 + @s3.exists?(jam_track_track[:url_48]).should be_true + @s3.length(jam_track_track[:url_48]).should == File.size?(ogg_path) + @s3.exists?(jam_track_track[:url_44]).should be_false + + # Check right + jam_track_right = JamTrackRight.create(:user=>user, :jam_track=>jam_track) + jam_track_right[:url_48].should be_nil + jam_track_right[:url_44].should be_nil + JamTracksBuilder.perform(jam_track_right.id, 48) + jam_track_right.reload + jam_track_right[:url_48].should == jam_track_right.store_dir + '/' + jam_track_right.filename + @s3.exists?(jam_track_track[:url_44]).should be_false + end + + describe "with bitrate 44" do + it "should build" do + ogg_path = File.join('spec', 'files', 'on.ogg') + user = FactoryGirl.create(:user) + + # Should build bitrate 44 and only bitrate 44: + jam_track_track = FactoryGirl.create(:jam_track_track) + jam_track = jam_track_track.jam_track + + # uploader = JamTrackTrackUploader.new(jam_track_track, :url_44) + # uploader.store!(File.open(ogg_path, 'rb')) + jam_track_track.url_44.store!(File.open(ogg_path, 'rb')) + jam_track_track.save! + + jam_track_track[:url_44].should == jam_track_track.store_dir + '/url_44/' + jam_track_track.filename + + # verify it's on S3 + @s3.exists?(jam_track_track[:url_44]).should be_true + @s3.length(jam_track_track[:url_44]).should == File.size?(ogg_path) + @s3.exists?(jam_track_track[:url_48]).should be_false + + # Check right + jam_track_right = JamTrackRight.create(:user=>user, :jam_track=>jam_track) + jam_track_right[:url_44].should be_nil + jam_track_right[:url_48].should be_nil + JamTracksBuilder.perform(jam_track_right.id, 44) + jam_track_right.reload + jam_track_right[:url_44].should == jam_track_right.store_dir + '/' + jam_track_right.filename + jam_track_right.url_44.should_not be_nil + @s3.exists?(jam_track_track[:url_48]).should be_false + end + end +end \ No newline at end of file From 81f288f4f775b3ef313ff7485d1c2ef0bff12f74 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Thu, 26 Feb 2015 17:01:53 -0600 Subject: [PATCH 08/82] VRFS-2782 : Multiple bitrates in admin UI. --- .../admin/jam_tracks/_jam_track_track_fields.html.slim | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/admin/app/views/admin/jam_tracks/_jam_track_track_fields.html.slim b/admin/app/views/admin/jam_tracks/_jam_track_track_fields.html.slim index 0503f4b54..b949247ff 100644 --- a/admin/app/views/admin/jam_tracks/_jam_track_track_fields.html.slim +++ b/admin/app/views/admin/jam_tracks/_jam_track_track_fields.html.slim @@ -12,10 +12,16 @@ i | before you can upload, you must select 'Update JamTrack' - else - = f.input :url, :as => :file, :label => 'Track file' + = f.input :url_48, :as => :file, :label => 'Track file (48kHz)' - unless f.object.nil? || f.object[:url].nil? .current_file_holder style='margin-bottom:10px' a href=f.object.sign_url(3600) style='padding:0 0 0 20px' | Download + = f.input :url_44, :as => :file, :label => 'Track file (44kHz)' + - unless f.object.nil? || f.object[:url].nil? + .current_file_holder style='margin-bottom:10px' + a href=f.object.sign_url(3600, 44) style='padding:0 0 0 20px' + | Download + = link_to_remove_association "Delete Track", f, class: 'button', style: 'margin-left:10px' \ No newline at end of file From 030fa1a351f6d20766c5dfa0ec10afe3a412879c Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Thu, 26 Feb 2015 17:43:22 -0600 Subject: [PATCH 09/82] VRFS-2782 : Add api controller test for multiple bitrates. Fix bug found as a result. --- ruby/lib/jam_ruby/models/jam_track_right.rb | 2 +- ruby/spec/files/off.ogg | Bin 0 -> 4303 bytes .../api_jam_tracks_controller_spec.rb | 63 ++++++++++++++---- web/spec/files/off.ogg | Bin 0 -> 4303 bytes 4 files changed, 52 insertions(+), 13 deletions(-) create mode 100644 ruby/spec/files/off.ogg create mode 100644 web/spec/files/off.ogg diff --git a/ruby/lib/jam_ruby/models/jam_track_right.rb b/ruby/lib/jam_ruby/models/jam_track_right.rb index 094339e27..1ae19ae9e 100644 --- a/ruby/lib/jam_ruby/models/jam_track_right.rb +++ b/ruby/lib/jam_ruby/models/jam_track_right.rb @@ -102,7 +102,7 @@ module JamRuby def enqueue(bitrate=48) begin JamTrackRight.where(:id => self.id).update_all(:signing_queued_at => Time.now, :signing_started_at => nil, :last_signed_at => nil) - Resque.enqueue(JamTracksBuilder, self.id, bitrate: bitrate) + Resque.enqueue(JamTracksBuilder, self.id, bitrate) true rescue Exception => e puts "e: #{e}" diff --git a/ruby/spec/files/off.ogg b/ruby/spec/files/off.ogg new file mode 100644 index 0000000000000000000000000000000000000000..743d6e3aa6ca187978d95bb768ef449e0ccc6071 GIT binary patch literal 4303 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 literal 0 HcmV?d00001 diff --git a/web/spec/controllers/api_jam_tracks_controller_spec.rb b/web/spec/controllers/api_jam_tracks_controller_spec.rb index 85773b25e..1ff6058df 100644 --- a/web/spec/controllers/api_jam_tracks_controller_spec.rb +++ b/web/spec/controllers/api_jam_tracks_controller_spec.rb @@ -6,6 +6,7 @@ describe ApiJamTracksController do before(:all) do @original_storage = JamTrackTrackUploader.storage = :fog @original_storage_right = JamTrackRightUploader.storage = :fog + @s3 = S3Manager.new(APP_CONFIG.aws_bucket, APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key) end after(:all) do @@ -76,7 +77,7 @@ describe ApiJamTracksController do it "finds a download" do #get "/download/#{right.id}/" right = JamTrackRight.create(:user=>@user, :jam_track=>@jam_track) - get :download, { :format => 'json', :id => @jam_track.id } + get :download, { :format=>'json', :id=>@jam_track.id } response.should be_success response.status.should == 202 @@ -114,43 +115,81 @@ describe ApiJamTracksController do @jam_track = FactoryGirl.create(:jam_track) #jam_track_track.jam_track jam_track_track = @jam_track.jam_track_tracks.first + # 48 kHz: uploader = JamTrackTrackUploader.new(jam_track_track, :url_48) uploader.store!(File.open(@ogg_path, 'rb')) + + # 44 kHz: + uploader = JamTrackTrackUploader.new(jam_track_track, :url_44) + uploader.store!(File.open(File.join('spec', 'files', 'off.ogg'), 'rb')) + #jam_track_track.url.store!(File.open(ogg_path, "rb")) jam_track_track.save! jam_track_track.reload ResqueSpec.reset! end - it "download depends on rights" do - s3 = S3Manager.new(APP_CONFIG.aws_bucket, APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key) - get :download, :id => @jam_track.id + it "download depends on rights" do + get :download, :id=>@jam_track.id response.status.should == 403 right = JamTrackRight.create(:user=>@user, :jam_track=>@jam_track) - get :download, :id => @jam_track.id + get :download, :id=>@jam_track.id response.status.should == 202 right.download_count.should eq(0) right.private_key.should be_nil - JamTracksBuilder.should have_queued(right.id).in(:jam_tracks_builder) - qname = "#{ResqueSpec.queue_name(JamRuby::JamTracksBuilder)}" + #puts "ResqueSpec.peek(qname)#{ResqueSpec.peek(qname)}" + JamTracksBuilder.should have_queued(right.id,nil).in(:jam_tracks_builder) + expect(ResqueSpec.peek(qname).present?).to eq(true) ResqueSpec.perform_next(qname) - JamTracksBuilder.should_not have_queued(right.id).in(:jam_tracks_builder) + JamTracksBuilder.should_not have_queued(right.id,nil).in(:jam_tracks_builder) right.reload right.private_key.should_not be_nil right.download_count.should eq(0) - get :download, :id => @jam_track.id + get :download, :id=>@jam_track.id response.status.should == 302 response.location.should =~ /.*#{Regexp.escape(right.filename)}.*/ right.reload right.download_count.should eq(1) - notifications = Notification.where(:jam_track_right_id => right.id) + notifications = Notification.where(:jam_track_right_id=>right.id) + notifications.count.should == 1 + end + + it "supports multiple bitrates" do + get :download, :id=>@jam_track.id, :bitrate=>44 + response.status.should == 403 + + right = JamTrackRight.create(:user=>@user, :jam_track=>@jam_track) + get :download, :id=>@jam_track.id, :bitrate=>44 + response.status.should == 202 + right.download_count.should eq(0) + right.private_key.should be_nil + + qname = "#{ResqueSpec.queue_name(JamRuby::JamTracksBuilder)}" + #puts "ResqueSpec.peek(qname)#{ResqueSpec.peek(qname)}" + JamTracksBuilder.should have_queued(right.id,"44").in(:jam_tracks_builder) + + expect(ResqueSpec.peek(qname).present?).to eq(true) + ResqueSpec.perform_next(qname) + + JamTracksBuilder.should_not have_queued(right.id,"44").in(:jam_tracks_builder) + right.reload + right.private_key.should_not be_nil + right.download_count.should eq(0) + + get :download, :id=>@jam_track.id, :bitrate=>44 + response.status.should == 302 + response.location.should =~ /.*#{Regexp.escape(right.filename)}.*/ + right.reload + right.download_count.should eq(1) + + notifications = Notification.where(:jam_track_right_id=>right.id) notifications.count.should == 1 end end @@ -212,7 +251,7 @@ describe ApiJamTracksController do it "success" do right = FactoryGirl.create(:jam_track_right, user: @user, signed: false) right.signing_queued_at.should be_nil - post :enqueue, {:format => 'json', :id => right.jam_track.id} + post :enqueue, {:format=>'json', :id=>right.jam_track.id} response.should be_success right.reload @@ -224,7 +263,7 @@ describe ApiJamTracksController do it "success" do right = FactoryGirl.create(:jam_track_right, user: @user) - get :show_jam_track_right, {:id => right.jam_track.id} + get :show_jam_track_right, {:id=>right.jam_track.id} response.should be_success json = JSON.parse(response.body) json['signing_state'].should eq('QUIET') diff --git a/web/spec/files/off.ogg b/web/spec/files/off.ogg new file mode 100644 index 0000000000000000000000000000000000000000..743d6e3aa6ca187978d95bb768ef449e0ccc6071 GIT binary patch literal 4303 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 literal 0 HcmV?d00001 From 718e43df5dbc9ea837713331b15ee008d7a66512 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Thu, 26 Feb 2015 17:43:30 -0600 Subject: [PATCH 10/82] Cleanup --- ruby/lib/jam_ruby/resque/jam_tracks_builder.rb | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/ruby/lib/jam_ruby/resque/jam_tracks_builder.rb b/ruby/lib/jam_ruby/resque/jam_tracks_builder.rb index f60d1f26b..04a508602 100644 --- a/ruby/lib/jam_ruby/resque/jam_tracks_builder.rb +++ b/ruby/lib/jam_ruby/resque/jam_tracks_builder.rb @@ -7,16 +7,14 @@ require 'digest/md5' module JamRuby class JamTracksBuilder extend JamRuby::ResqueStats - attr_accessor :bitrate + attr_accessor :jam_track_right_id, :bitrate @queue = :jam_tracks_builder def log @log || Logging.logger[JamTracksBuilder] end - attr_accessor :jam_track_right_id - - def self.perform(jam_track_right_id, bitrate) + def self.perform(jam_track_right_id, bitrate=48) jam_track_builder = JamTracksBuilder.new() jam_track_builder.jam_track_right_id = jam_track_right_id jam_track_builder.bitrate=bitrate @@ -24,9 +22,9 @@ module JamRuby end def run + bitrate ||= 48 begin - log.info("jam_track_builder job starting. jam_track_right_id #{jam_track_right_id}") - + log.info("jam_track_builder job starting. jam_track_right_id #{jam_track_right_id}, bitrate: #{bitrate}") begin @jam_track_right = JamTrackRight.find(jam_track_right_id) @@ -38,15 +36,14 @@ module JamRuby # 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) - JamRuby::JamTracksManager.save_jam_track_right_jkz(@jam_track_right, bitrate) - length = bitrate==48 ? @jam_track_right.url_48.size() : @jam_track_right.url_44.size() + # If bitrate is 48 (the default), use that URL. Otherwise, use 44kHz: + length = (bitrate==48) ? @jam_track_right.url_48.size() : @jam_track_right.url_44.size() md5 = Digest::MD5.new @jam_track_right.finish_sign(length, md5.to_s, bitrate) - log.info "Signed jamtrack to #{@jam_track_right[:url]}" - + log.info "Signed #{bitrate}kHz jamtrack to #{@jam_track_right[:url]}" rescue Exception => e # record the error in the database post_error(e) From d314c3327cc820bdd2f638a8210de6709b49d194 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Fri, 27 Feb 2015 11:38:40 -0600 Subject: [PATCH 11/82] VRFS-2782 : Fix error found by test. --- ruby/lib/jam_ruby/jam_tracks_manager.rb | 2 +- ruby/lib/jam_ruby/resque/jam_tracks_builder.rb | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ruby/lib/jam_ruby/jam_tracks_manager.rb b/ruby/lib/jam_ruby/jam_tracks_manager.rb index cf7ed79c7..0cccbbeb1 100644 --- a/ruby/lib/jam_ruby/jam_tracks_manager.rb +++ b/ruby/lib/jam_ruby/jam_tracks_manager.rb @@ -79,7 +79,7 @@ module JamRuby http.request request do |response| response_code = response.code.to_i unless response_code >= 200 && response_code <= 299 - puts "Response from server was #{response_code} / #{response.message}" + @@log.info "Response from server was #{response_code} / #{response.message}" raise "bad status code: #{response_code}. body: #{response.body}" end response.read_body do |chunk| diff --git a/ruby/lib/jam_ruby/resque/jam_tracks_builder.rb b/ruby/lib/jam_ruby/resque/jam_tracks_builder.rb index 04a508602..395d12084 100644 --- a/ruby/lib/jam_ruby/resque/jam_tracks_builder.rb +++ b/ruby/lib/jam_ruby/resque/jam_tracks_builder.rb @@ -14,7 +14,7 @@ module JamRuby @log || Logging.logger[JamTracksBuilder] end - def self.perform(jam_track_right_id, bitrate=48) + def self.perform(jam_track_right_id, bitrate=48) jam_track_builder = JamTracksBuilder.new() jam_track_builder.jam_track_right_id = jam_track_right_id jam_track_builder.bitrate=bitrate @@ -22,9 +22,9 @@ module JamRuby end def run - bitrate ||= 48 + self.bitrate ||= 48 begin - log.info("jam_track_builder job starting. jam_track_right_id #{jam_track_right_id}, bitrate: #{bitrate}") + log.info("jam_track_builder job starting. jam_track_right_id #{jam_track_right_id}, bitrate: #{self.bitrate}") begin @jam_track_right = JamTrackRight.find(jam_track_right_id) @@ -36,14 +36,14 @@ module JamRuby # 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) - JamRuby::JamTracksManager.save_jam_track_right_jkz(@jam_track_right, bitrate) + JamRuby::JamTracksManager.save_jam_track_right_jkz(@jam_track_right, self.bitrate) # If bitrate is 48 (the default), use that URL. Otherwise, use 44kHz: - length = (bitrate==48) ? @jam_track_right.url_48.size() : @jam_track_right.url_44.size() + length = (self.bitrate==48) ? @jam_track_right.url_48.size() : @jam_track_right.url_44.size() md5 = Digest::MD5.new - @jam_track_right.finish_sign(length, md5.to_s, bitrate) + @jam_track_right.finish_sign(length, md5.to_s, self.bitrate) - log.info "Signed #{bitrate}kHz jamtrack to #{@jam_track_right[:url]}" + log.info "Signed #{self.bitrate}kHz jamtrack to #{@jam_track_right[:url]}" rescue Exception => e # record the error in the database post_error(e) From 5396e66c679d0ec807047ee4cb53103547a538ef Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Fri, 27 Feb 2015 11:51:45 -0600 Subject: [PATCH 12/82] Merge with develop --- admin/app/admin/jam_tracks.rb | 1 + .../views/admin/jam_tracks/_form.html.slim | 7 +- admin/config/application.rb | 3 + admin/config/initializers/jam_tracks.rb | 25 + build | 2 +- db/manifest | 3 + db/up/jam_track_jmep_data.sql | 2 + db/up/jam_track_version.sql | 1 + db/up/recorded_jam_track_tracks.sql | 10 +- ruby/lib/jam_ruby.rb | 2 + ruby/lib/jam_ruby/jam_tracks_manager.rb | 4 +- ruby/lib/jam_ruby/jmep_manager.rb | 55 +++ ruby/lib/jam_ruby/models/jam_track.rb | 6 +- ruby/lib/jam_ruby/models/jam_track_right.rb | 4 +- ruby/lib/jam_ruby/models/jam_track_track.rb | 2 + ruby/lib/jam_ruby/models/mix.rb | 27 +- .../models/recorded_jam_track_track.rb | 10 +- ruby/lib/jam_ruby/models/recording.rb | 31 +- ruby/lib/jam_ruby/models/user.rb | 4 + ruby/lib/jam_ruby/recurly_client.rb | 3 +- ruby/spec/factories.rb | 7 +- ruby/spec/jam_ruby/models/recording_spec.rb | 27 + web/app/assets/javascripts/JamServer.js | 9 +- .../javascripts/dialog/openJamTrackDialog.js | 15 +- .../javascripts/download_jamtrack.js.coffee | 467 +++++++++++++++++- web/app/assets/javascripts/faderHelpers.js | 22 +- web/app/assets/javascripts/fakeJamClient.js | 17 + web/app/assets/javascripts/globals.js | 3 +- web/app/assets/javascripts/jam_rest.js | 45 +- web/app/assets/javascripts/order.js | 142 +++++- .../assets/javascripts/playbackControls.js | 49 +- web/app/assets/javascripts/recordingModel.js | 1 - web/app/assets/javascripts/session.js | 207 +++++++- web/app/assets/javascripts/sessionModel.js | 23 + .../javascripts/subscription_utils.js.coffee | 2 + web/app/assets/javascripts/utils.js | 4 + .../stylesheets/client/checkout.css.scss | 34 ++ web/app/assets/stylesheets/client/client.css | 1 + .../client/downloadJamTrack.css.scss | 72 +++ .../stylesheets/client/session.css.scss | 11 + .../controllers/api_jam_tracks_controller.rb | 18 +- .../controllers/api_recordings_controller.rb | 9 +- web/app/controllers/api_recurly_controller.rb | 20 +- web/app/controllers/spikes_controller.rb | 16 +- .../views/api_claimed_recordings/show.rabl | 21 +- web/app/views/api_jam_tracks/list_keys.rabl | 2 +- web/app/views/api_jam_tracks/show.rabl | 2 +- .../views/api_jam_tracks/show_for_client.rabl | 10 +- web/app/views/api_music_sessions/show.rabl | 2 +- web/app/views/api_recordings/show.rabl | 18 +- .../_download_jamtrack_templates.html.slim | 84 ++++ web/app/views/clients/_help.html.slim | 3 + web/app/views/clients/_order.html.slim | 16 +- web/app/views/clients/_session.html.slim | 2 +- web/app/views/clients/index.html.erb | 1 + .../dialogs/_openJamTrackDialog.html.slim | 2 +- .../views/spikes/download_jam_track.html.slim | 44 ++ web/config/application.rb | 3 + web/config/routes.rb | 10 +- web/lib/tasks/sample_data.rake | 2 +- .../api_jam_tracks_controller_spec.rb | 19 +- .../api_recordings_controller_spec.rb | 432 ++++++++-------- .../download_jamtrack_spec.js.coffee | 312 +++++++++++- 63 files changed, 2070 insertions(+), 338 deletions(-) create mode 100644 admin/config/initializers/jam_tracks.rb create mode 100644 db/up/jam_track_jmep_data.sql create mode 100644 db/up/jam_track_version.sql create mode 100644 ruby/lib/jam_ruby/jmep_manager.rb create mode 100644 web/app/views/spikes/download_jam_track.html.slim diff --git a/admin/app/admin/jam_tracks.rb b/admin/app/admin/jam_tracks.rb index 8e62bbc85..6cfb6c1a3 100644 --- a/admin/app/admin/jam_tracks.rb +++ b/admin/app/admin/jam_tracks.rb @@ -26,6 +26,7 @@ ActiveAdmin.register JamRuby::JamTrack, :as => 'JamTracks' do column :id column :name column :description + column :version column :initial_play_silence column :time_signature column :status diff --git a/admin/app/views/admin/jam_tracks/_form.html.slim b/admin/app/views/admin/jam_tracks/_form.html.slim index 16e53837d..9ec25ffcf 100644 --- a/admin/app/views/admin/jam_tracks/_form.html.slim +++ b/admin/app/views/admin/jam_tracks/_form.html.slim @@ -7,7 +7,9 @@ | JamTrack should only be made available (to end users) if all its sub-component are in place: = f.input :available, as: :boolean = f.input :description, :input_html => { :rows=>5, :maxlength=>1000 } - = f.input :initial_play_silence, :label => 'Initial Play Silence (seconds)' + = f.input :plan_code, :label=>'Recurly Plan Code', :required=>true, :hint => 'Must match plan code in Recurly' + = f.input :version, :label => 'Version', :hint => 'Increment this value whenever you invalidate (update) the definition of this JamTrack' + //= f.input :initial_play_silence, :label => 'Initial Play Silence (seconds)' = f.input :time_signature, collection: JamRuby::JamTrack::TIME_SIGNATURES, include_blank: false = f.input :status, collection: JamRuby::JamTrack::STATUS, include_blank: false = f.input :recording_type, collection: JamRuby::JamTrack::RECORDING_TYPE, include_blank: false @@ -24,8 +26,9 @@ = f.input :reproduction_royalty_amount, :required=>true, :input_html=>{type:'numeric'} = f.input :licensor_royalty_amount, :required=>true, :input_html=>{type:'numeric'} = f.input :pro_royalty_amount, :required=>true, :input_html=>{type:'numeric'} - = f.input :plan_code, :label=>'Recurly Plan Code', :required=>true = f.input :url, :as => :file, :label => 'Audio File' + = f.input :jmep_text, :as => :text, :label => "JMEP Text", :input_html => {:rows => 5 } + = f.input :jmep_json, :as => :text, :label => "JMEP Json", :input_html => {:rows => 5, :readonly=>true }, :hint => 'readonly' = f.semantic_fields_for :jam_track_tracks do |track| = render 'jam_track_track_fields', f: track diff --git a/admin/config/application.rb b/admin/config/application.rb index 684f30d7e..102a03176 100644 --- a/admin/config/application.rb +++ b/admin/config/application.rb @@ -147,5 +147,8 @@ module JamAdmin config.influxdb_hosts = ["localhost"] config.influxdb_port = 8086 config.influxdb_ignored_environments = ENV["INFLUXDB_ENABLED"] == '1' ? ['test', 'cucumber'] : ['test', 'cucumber', 'development'] + + config.jamtracks_dir = ENV['JAMTRACKS_DIR'] || File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "jamtracks")) + config.jmep_dir = ENV['JMEP_DIR'] || File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "jmep")) end end diff --git a/admin/config/initializers/jam_tracks.rb b/admin/config/initializers/jam_tracks.rb new file mode 100644 index 000000000..c88fc6a2c --- /dev/null +++ b/admin/config/initializers/jam_tracks.rb @@ -0,0 +1,25 @@ +class JamRuby::JamTrack + + # add a custom validation + + before_save :jmep_json_generate + validate :jmep_text_validate + + def jmep_text_validate + begin + JmepManager.execute(self.jmep_text) + rescue ArgumentError => err + errors.add(:jmep_text, err.to_s) + end + end + + def jmep_json_generate + begin + self[:jmep_json] = JmepManager.execute(self.jmep_text) + rescue ArgumentError => err + #errors.add(:jmep_text, err.to_s) + end + end + + +end diff --git a/build b/build index 6d408c180..db8ab33a4 100755 --- a/build +++ b/build @@ -66,7 +66,7 @@ DEB_SERVER=http://localhost:9010/apt-`uname -p` GEM_SERVER=http://localhost:9000/gems # if still going, then push all debs up - if [[ "$GIT_BRANCH" == *develop* || "$GIT_BRANCH" == *master* || "$GIT_BRANCH" == *release* ]]; then + if [[ "$GIT_BRANCH" == *develop* || "$GIT_BRANCH" == *master* || "$GIT_BRANCH" == *release* || "$GIT_BRANCH" == *feature* || "$GIT_BRANCH" == *hotfix* ]]; then echo "" echo "PUSHING DB ARTIFACTS" diff --git a/db/manifest b/db/manifest index cf4efdfb2..f5ffa9cc3 100755 --- a/db/manifest +++ b/db/manifest @@ -255,3 +255,6 @@ user_syncs_include_backing_tracks.sql remove_bpm_from_jamtracks.sql add_jam_track_bitrates.sql widen_user_authorization_token.sql +jam_track_version.sql +recorded_jam_track_tracks.sql +jam_track_jmep_data.sql \ No newline at end of file diff --git a/db/up/jam_track_jmep_data.sql b/db/up/jam_track_jmep_data.sql new file mode 100644 index 000000000..46c6e67b5 --- /dev/null +++ b/db/up/jam_track_jmep_data.sql @@ -0,0 +1,2 @@ +ALTER TABLE jam_tracks ADD COLUMN jmep_text VARCHAR; +ALTER TABLE jam_tracks ADD COLUMN jmep_json JSON; \ No newline at end of file diff --git a/db/up/jam_track_version.sql b/db/up/jam_track_version.sql new file mode 100644 index 000000000..7c1efff86 --- /dev/null +++ b/db/up/jam_track_version.sql @@ -0,0 +1 @@ +ALTER TABLE jam_tracks ADD COLUMN version VARCHAR NOT NULL DEFAULT 0; diff --git a/db/up/recorded_jam_track_tracks.sql b/db/up/recorded_jam_track_tracks.sql index 246b76425..08a485643 100644 --- a/db/up/recorded_jam_track_tracks.sql +++ b/db/up/recorded_jam_track_tracks.sql @@ -1,15 +1,15 @@ -ALTER TABLE recordings ADD COLUMN jam_track_id VARCHAR(64) REFERENCES jam_tracks(id); +ALTER TABLE recordings ADD COLUMN jam_track_id BIGINT REFERENCES jam_tracks(id); ALTER TABLE recordings ADD COLUMN jam_track_initiator_id VARCHAR(64) REFERENCES users(id); CREATE TABLE recorded_jam_track_tracks ( id BIGINT PRIMARY KEY, - user_id VARCHAR(64) REFERENCES users(id) ON DELETE CASCADE, - jam_track_track_id VARCHAR(64), - recording_id VARCHAR(64) NOT NULL, + user_id VARCHAR(64) NOT NULL REFERENCES users(id) ON DELETE CASCADE, + jam_track_track_id VARCHAR(64) REFERENCES jam_track_tracks(id) NOT NULL, + recording_id VARCHAR(64) REFERENCES recordings(id) NOT NULL, discard BOOLEAN, timeline JSON, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); -ALTER TABLE recorded_jam_tracks ALTER COLUMN id SET DEFAULT nextval('tracks_next_tracker_seq'); \ No newline at end of file +ALTER TABLE recorded_jam_track_tracks ALTER COLUMN id SET DEFAULT nextval('tracks_next_tracker_seq'); diff --git a/ruby/lib/jam_ruby.rb b/ruby/lib/jam_ruby.rb index e147eadc6..67f81933b 100755 --- a/ruby/lib/jam_ruby.rb +++ b/ruby/lib/jam_ruby.rb @@ -139,6 +139,7 @@ require "jam_ruby/models/recorded_backing_track_observer" require "jam_ruby/models/recorded_track" require "jam_ruby/models/recorded_track_observer" require "jam_ruby/models/recorded_video" +require "jam_ruby/models/recorded_jam_track_track" require "jam_ruby/models/quick_mix" require "jam_ruby/models/quick_mix_observer" require "jam_ruby/models/share_token" @@ -204,6 +205,7 @@ require "jam_ruby/models/user_sync" require "jam_ruby/models/video_source" require "jam_ruby/models/text_message" require "jam_ruby/jam_tracks_manager" +require "jam_ruby/jmep_manager" include Jampb diff --git a/ruby/lib/jam_ruby/jam_tracks_manager.rb b/ruby/lib/jam_ruby/jam_tracks_manager.rb index 0cccbbeb1..fc5dd5df3 100644 --- a/ruby/lib/jam_ruby/jam_tracks_manager.rb +++ b/ruby/lib/jam_ruby/jam_tracks_manager.rb @@ -20,7 +20,6 @@ module JamRuby def save_jam_track_right_jkz(jam_track_right, bitrate=48) jam_track = jam_track_right.jam_track - #py_root = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "..", "jamtracks")) py_root = APP_CONFIG.jamtracks_dir Dir.mktmpdir do |tmp_dir| jam_file_opts="" @@ -42,10 +41,11 @@ module JamRuby title=jam_track.name output_jkz=File.join(tmp_dir, "#{title.parameterize}.jkz") py_file = File.join(py_root, "jkcreate.py") + version = jam_track.version @@log.info "Executing python source in #{py_file}, outputting to #{tmp_dir} (#{output_jkz})" # From http://stackoverflow.com/questions/690151/getting-output-of-system-calls-in-ruby/5970819#5970819: - cli = "python #{py_file} -D -k #{sku} -p #{tmp_dir}/pkey.pem -s #{tmp_dir}/skey.pem #{jam_file_opts} -o #{output_jkz} -t '#{title}'" + cli = "python #{py_file} -D -k #{sku} -p #{tmp_dir}/pkey.pem -s #{tmp_dir}/skey.pem #{jam_file_opts} -o #{output_jkz} -t '#{title}' -V '#{version}'" Open3.popen3(cli) do |stdin, stdout, stderr, wait_thr| pid = wait_thr.pid exit_status = wait_thr.value diff --git a/ruby/lib/jam_ruby/jmep_manager.rb b/ruby/lib/jam_ruby/jmep_manager.rb new file mode 100644 index 000000000..a4d0215bc --- /dev/null +++ b/ruby/lib/jam_ruby/jmep_manager.rb @@ -0,0 +1,55 @@ +require 'json' +require 'tempfile' +require 'open3' +require 'fileutils' +require 'open-uri' + +module JamRuby + + # Interact with external python tools to create jmep json + class JmepManager + + @@log = Logging.logger[JmepManager] + + class << self + + def execute(jmep_text) + + json = nil + + if jmep_text.blank? + return nil + end + + py_root = APP_CONFIG.jmep_dir + Dir.mktmpdir do |tmp_dir| + + output_json = File.join(tmp_dir, "jmep.json") + input_text = File.join(tmp_dir, "jmep.txt") + + # put JMEP text into input file + File.open(input_text, 'w') { |file| file.write(jmep_text) } + + py_file = File.join(py_root, "jmepgen.py") + @@log.info "Executing python source in #{py_file}, outputting to #{output_json})" + + # From http://stackoverflow.com/questions/690151/getting-output-of-system-calls-in-ruby/5970819#5970819: + cli = "python #{py_file} -i '#{input_text}' -o '#{output_json}'" + Open3.popen3(cli) do |stdin, stdout, stderr, wait_thr| + pid = wait_thr.pid + exit_status = wait_thr.value + err = stderr.read(1000) + out = stdout.read(1000) + + raise ArgumentError, "#{out} #{err}" if exit_status != 0 + + json = File.read(output_json) + end + end + + json + end + + end + end +end diff --git a/ruby/lib/jam_ruby/models/jam_track.rb b/ruby/lib/jam_ruby/models/jam_track.rb index 557316541..f6513ac66 100644 --- a/ruby/lib/jam_ruby/models/jam_track.rb +++ b/ruby/lib/jam_ruby/models/jam_track.rb @@ -16,7 +16,7 @@ module JamRuby :original_artist, :songwriter, :publisher, :licensor, :licensor_id, :pro, :genre, :genre_id, :sales_region, :price, :reproduction_royalty, :public_performance_royalty, :reproduction_royalty_amount, :licensor_royalty_amount, :pro_royalty_amount, :plan_code, :initial_play_silence, :jam_track_tracks_attributes, - :jam_track_tap_ins_attributes, :available, as: :admin + :jam_track_tap_ins_attributes, :available, :version, :jmep_json, :jmep_text, as: :admin validates :name, presence: true, uniqueness: true, length: {maximum: 200} validates :description, length: {maximum: 1000} @@ -30,6 +30,7 @@ module JamRuby validates :sales_region, inclusion: {in: [nil] + SALES_REGION} validates_format_of :price, with: /^\d+\.*\d{0,2}$/ validates :initial_play_silence, numericality: true, :allow_nil => true + validates :version, presence: true validates :reproduction_royalty, inclusion: {in: [nil, true, false]} validates :public_performance_royalty, inclusion: {in: [nil, true, false]} @@ -50,6 +51,8 @@ module JamRuby has_many :playing_sessions, :class_name => "JamRuby::ActiveMusicSession" + has_many :recordings, :class_name => "JamRuby::Recording" + accepts_nested_attributes_for :jam_track_tracks, allow_destroy: true accepts_nested_attributes_for :jam_track_tap_ins, allow_destroy: true @@ -156,6 +159,7 @@ module JamRuby def sanitize_active_admin self.genre_id = nil if self.genre_id == '' self.licensor_id = nil if self.licensor_id == '' + self.jmep_json = nil if self.jmep_json = '' end end end diff --git a/ruby/lib/jam_ruby/models/jam_track_right.rb b/ruby/lib/jam_ruby/models/jam_track_right.rb index 1ae19ae9e..f7f1f7ddf 100644 --- a/ruby/lib/jam_ruby/models/jam_track_right.rb +++ b/ruby/lib/jam_ruby/models/jam_track_right.rb @@ -143,8 +143,6 @@ module JamRuby state = nil if signed state = 'SIGNED' - elsif error_count > 0 - state = 'ERROR' elsif signing_started_at if Time.now - signing_started_at > APP_CONFIG.signing_job_run_max_time state = 'SIGNING_TIMEOUT' @@ -157,6 +155,8 @@ module JamRuby else state = 'QUEUED' end + elsif error_count > 0 + state = 'ERROR' else state = 'QUIET' # needs to be poked to go build end diff --git a/ruby/lib/jam_ruby/models/jam_track_track.rb b/ruby/lib/jam_ruby/models/jam_track_track.rb index 65dc1816c..c608cb004 100644 --- a/ruby/lib/jam_ruby/models/jam_track_track.rb +++ b/ruby/lib/jam_ruby/models/jam_track_track.rb @@ -23,6 +23,8 @@ module JamRuby belongs_to :instrument, class_name: "JamRuby::Instrument" belongs_to :jam_track, class_name: "JamRuby::JamTrack" + has_many :recorded_jam_track_tracks, :class_name => "JamRuby::RecordedJamTrackTrack", :foreign_key => :jam_track_track_id, :dependent => :destroy + # create storage directory that will house this jam_track, as well as def store_dir "#{jam_track.store_dir}/tracks" diff --git a/ruby/lib/jam_ruby/models/mix.rb b/ruby/lib/jam_ruby/models/mix.rb index 83557f4f7..f441a441f 100644 --- a/ruby/lib/jam_ruby/models/mix.rb +++ b/ruby/lib/jam_ruby/models/mix.rb @@ -141,12 +141,35 @@ module JamRuby recording.recorded_tracks.each do |recorded_track| manifest["files"] << { "filename" => recorded_track.sign_url(one_day), "codec" => "vorbis", "offset" => 0 } - mix_params << { "level" => 100, "balance" => 0 } + mix_params << { "level" => 1.0, "balance" => 0 } end recording.recorded_backing_tracks.each do |recorded_backing_track| manifest["files"] << { "filename" => recorded_backing_track.sign_url(one_day), "codec" => "vorbis", "offset" => 0 } - mix_params << { "level" => 100, "balance" => 0 } + mix_params << { "level" => 1.0, "balance" => 0 } + end + + recording.recorded_jam_track_tracks.each do |recorded_jam_track_track| + manifest["files"] << { "filename" => recorded_jam_track_track.jam_track_track.sign_url(one_day), "codec" => "vorbis", "offset" => 0 } + # let's look for level info from the client + level = 1.0 # default value - means no effect + if recorded_jam_track_track.timeline + + timeline_data = JSON.parse(recorded_jam_track_track.timeline) + + # always take the 1st entry for now + first = timeline_data[0] + + if first["mute"] + # mute equates to no noise + level = 0.0 + else + # otherwise grab the left channel... + level = first["vol_l"] + end + end + + mix_params << { "level" => level, "balance" => 0 } end manifest["timeline"] << { "timestamp" => 0, "mix" => mix_params } diff --git a/ruby/lib/jam_ruby/models/recorded_jam_track_track.rb b/ruby/lib/jam_ruby/models/recorded_jam_track_track.rb index 19907eb1c..f77615beb 100644 --- a/ruby/lib/jam_ruby/models/recorded_jam_track_track.rb +++ b/ruby/lib/jam_ruby/models/recorded_jam_track_track.rb @@ -10,11 +10,11 @@ module JamRuby validates :jam_track_track, presence:true def self.create_from_jam_track_track(jam_track_track, recording) - recorded_backing_track = self.new - recorded_backing_track.recording = recording - recorded_backing_track.jam_track_track = jam_track_track - recorded_backing_track.save - recorded_backing_track + recorded_jam_track_track = self.new + recorded_jam_track_track.recording = recording + recorded_jam_track_track.jam_track_track = jam_track_track + recorded_jam_track_track.save + recorded_jam_track_track end end diff --git a/ruby/lib/jam_ruby/models/recording.rb b/ruby/lib/jam_ruby/models/recording.rb index 69d690962..c4537c94d 100644 --- a/ruby/lib/jam_ruby/models/recording.rb +++ b/ruby/lib/jam_ruby/models/recording.rb @@ -12,6 +12,7 @@ module JamRuby has_many :recorded_tracks, :class_name => "JamRuby::RecordedTrack", :foreign_key => :recording_id, :dependent => :destroy has_many :recorded_videos, :class_name => "JamRuby::RecordedVideo", :foreign_key => :recording_id, :dependent => :destroy has_many :recorded_backing_tracks, :class_name => "JamRuby::RecordedBackingTrack", :foreign_key => :recording_id, :dependent => :destroy + has_many :recorded_jam_track_tracks, :class_name => "JamRuby::RecordedJamTrackTrack", :foreign_key => :recording_id, :dependent => :destroy has_many :comments, :class_name => "JamRuby::RecordingComment", :foreign_key => "recording_id", :dependent => :destroy has_many :likes, :class_name => "JamRuby::RecordingLiker", :foreign_key => "recording_id", :dependent => :destroy has_many :plays, :class_name => "JamRuby::PlayablePlay", :as => :playable, :dependent => :destroy @@ -20,6 +21,8 @@ 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 :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' accepts_nested_attributes_for :recorded_tracks, :mixes, :claimed_recordings, allow_destroy: true @@ -223,6 +226,14 @@ module JamRuby recording.recorded_backing_tracks << RecordedBackingTrack.create_from_backing_track(backing_track, recording) end end + + if music_session.jam_track + music_session.jam_track.jam_track_tracks.each do |jam_track_track| + recording.recorded_jam_track_tracks << RecordedJamTrackTrack.create_from_jam_track_track(jam_track_track, recording) + end + recording.jam_track = music_session.jam_track + recording.jam_track_initiator = music_session.jam_track_initiator + end end end @@ -556,7 +567,7 @@ module JamRuby :recording_id => recorded_item.recording_id, :client_track_id => recorded_item.client_track_id, :next => recorded_item.id - }) + }) else end @@ -678,6 +689,24 @@ module JamRuby self.save(:validate => false) end + def add_timeline(timeline) + tracks = timeline["tracks"] + + raise JamArgumentError, "tracks must be specified" unless tracks + + jam_tracks = tracks.select {|track| track["type"] == "jam_track"} + jam_tracks.each do |client_jam_track| + recorded_jam_track_track = RecordedJamTrackTrack.find_by_jam_track_track_id(client_jam_track["id"]) + if recorded_jam_track_track + recorded_jam_track_track.timeline = client_jam_track["timeline"].to_json + recorded_jam_track_track.save! + else + @@log.error("unable to find JamTrackTrack with id #{recorded_jam_track_track.id}") + end + end + end + + private def self.validate_user_is_band_member(user, band) unless band.users.exists? user diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb index 3bb4c270a..a469ab69a 100644 --- a/ruby/lib/jam_ruby/models/user.rb +++ b/ruby/lib/jam_ruby/models/user.rb @@ -124,6 +124,10 @@ module JamRuby has_many :recorded_videos, :foreign_key => "user_id", :class_name => "JamRuby::RecordedVideo", :inverse_of => :user has_many :recorded_backing_tracks, :foreign_key => "user_id", :class_name => "JamRuby::RecordedBackingTrack", :inverse_of => :user has_many :quick_mixes, :foreign_key => "user_id", :class_name => "JamRuby::QuickMix", :inverse_of => :user + has_many :recorded_jam_track_tracks, :foreign_key => "user_id", :class_name => "JamRuby::RecordedJamTrackTrack", :inverse_of => :user + + # jam track recordings started + has_many :initiated_jam_track_recordings, :foreign_key => 'jam_track_initiator_id', :class_name => "JamRuby::Recording", :inverse_of => :jam_track_initiator # invited users has_many :invited_users, :foreign_key => "sender_id", :class_name => "JamRuby::InvitedUser" diff --git a/ruby/lib/jam_ruby/recurly_client.rb b/ruby/lib/jam_ruby/recurly_client.rb index eb70824c9..3bf68e991 100644 --- a/ruby/lib/jam_ruby/recurly_client.rb +++ b/ruby/lib/jam_ruby/recurly_client.rb @@ -111,6 +111,7 @@ module JamRuby end def place_order(current_user, jam_track) + jam_track_right = nil account = get_account(current_user) if (account.present?) begin @@ -136,7 +137,7 @@ module JamRuby else raise RecurlyClientError, "Could not find account to place order." end - account + jam_track_right end def find_or_create_account(current_user, billing_info=nil) diff --git a/ruby/spec/factories.rb b/ruby/spec/factories.rb index 8c940bd0b..07008dda7 100644 --- a/ruby/spec/factories.rb +++ b/ruby/spec/factories.rb @@ -268,7 +268,6 @@ FactoryGirl.define do association :recording, factory: :recording end - factory :recorded_video, :class => JamRuby::RecordedVideo do sequence(:client_video_source_id) { |n| "client_video_source_id-#{n}"} fully_uploaded true @@ -277,6 +276,12 @@ FactoryGirl.define do association :recording, factory: :recording end + factory :recorded_jam_track_track, :class => JamRuby::RecordedJamTrackTrack do + association :user, factory: :user + association :recording, factory: :recording + association :jam_track_track, factory: :jam_track_track + end + factory :instrument, :class => JamRuby::Instrument do description { |n| "Instrument #{n}" } end diff --git a/ruby/spec/jam_ruby/models/recording_spec.rb b/ruby/spec/jam_ruby/models/recording_spec.rb index d2fd7c3b0..3d8515f29 100644 --- a/ruby/spec/jam_ruby/models/recording_spec.rb +++ b/ruby/spec/jam_ruby/models/recording_spec.rb @@ -1073,6 +1073,33 @@ describe Recording do RecordedVideo.find_by_id(video.id).should_not be_nil end end + + describe "add_timeline" do + + let!(:recorded_jam_track_track) {FactoryGirl.create(:recorded_jam_track_track)} + let(:recording) {recorded_jam_track_track.recording} + let(:timeline_data) {{"sample" => "data"}} + let(:good_timeline) { { + "tracks" => [ + { + "id" => recorded_jam_track_track.jam_track_track.id, + "timeline" => timeline_data, + "type" => "jam_track" + } + ] + } + } + + it "applies timeline data correctly" do + recording.add_timeline good_timeline + recorded_jam_track_track.reload + JSON.parse(recorded_jam_track_track.timeline).should eq(timeline_data) + end + + it "fails if no tracks data" do + expect { recording.add_timeline({}) }.to raise_error(JamRuby::JamArgumentError) + end + end end diff --git a/web/app/assets/javascripts/JamServer.js b/web/app/assets/javascripts/JamServer.js index 73997c0a7..61b889eb2 100644 --- a/web/app/assets/javascripts/JamServer.js +++ b/web/app/assets/javascripts/JamServer.js @@ -529,10 +529,12 @@ if(server.connecting) { logger.error("server.connect should never be called if we are already connecting. cancelling.") + // XXX should return connectDeferred, but needs to be tested/vetted return; } if(server.connected) { logger.error("server.connect should never be called if we are already connected. cancelling.") + // XXX should return connectDeferred, but needs to be tested/vetted return; } @@ -678,7 +680,12 @@ logger.info("server.send(" + jsMessage + ")"); } if (server !== undefined && server.socket !== undefined && server.socket.send !== undefined) { - server.socket.send(jsMessage); + try { + server.socket.send(jsMessage); + } + catch(err) { + logger.warn("error when sending on websocket: " + err) + } } else { logger.warn("Dropped message because server connection is closed."); } diff --git a/web/app/assets/javascripts/dialog/openJamTrackDialog.js b/web/app/assets/javascripts/dialog/openJamTrackDialog.js index 21432a68e..349742ad7 100644 --- a/web/app/assets/javascripts/dialog/openJamTrackDialog.js +++ b/web/app/assets/javascripts/dialog/openJamTrackDialog.js @@ -24,6 +24,7 @@ } function beforeShow() { + $dialog.data('result', null) emptyList(); resetPagination(); showing = true; @@ -77,18 +78,8 @@ // tell the server we are about to start a recording rest.openJamTrack({id: context.JK.CurrentSessionModel.id(), jam_track_id: jamTrack.id}) .done(function(response) { - context.jamClient.JamTrackStopPlay(); - var result = context.jamClient.JamTrackPlay(jamTrack.id); - - logger.debug("JamTrackPlay response: %o", result); - - if(result) { - app.layout.closeDialog('open-jam-track-dialog'); - } - else { - logger.error("unable to open jam track") - } - + $dialog.data('result', {success:true, jamTrack: jamTrack}) + app.layout.closeDialog('open-jam-track-dialog'); }) .fail(function(jqXHR) { app.notifyServerError(jqXHR, "Unable to Open JamTrack For Playback"); diff --git a/web/app/assets/javascripts/download_jamtrack.js.coffee b/web/app/assets/javascripts/download_jamtrack.js.coffee index 75e684bc6..6e09d8bc5 100644 --- a/web/app/assets/javascripts/download_jamtrack.js.coffee +++ b/web/app/assets/javascripts/download_jamtrack.js.coffee @@ -2,10 +2,471 @@ $ = jQuery context = window context.JK ||= {}; -context.JK.DownloadJamTrack = class SyncViewer - constructor: (@app) -> +# This is the sequence of how this widget works: +# checkState() is the heart of the state machine; it is called to get things going, and is called whenevr a state ends +# checkState() checks first against what the client thinks about the state of the JamTrack; +# if it on the disk then the state machine may enter one of: +# * synchronized +# * keying +# +# if it's still on the server, then the state machine may be: +# * packaging +# * downloading +# +# errored state can be entered from @jamTrack.jam_track_right_id +# +# other state; you augment the error to the user by suppling @errorMessage before transitioning +# +# no-client is the way the widget behaves when you are in a normal browser (i.e., nothing happens other than tell the user to use the client) +# +# Discussion of the different states: +# There are different states that a JamTrack can be in. +# The final success state is that the JamTrack is on disk and loadable. (show synchronized state) +# But there are others until you get there: +# The JamTrack does not exist on the server, so we will create it (packaging state) +# The JamTrack exists on the server, but not on disk, so we will download it (downloading state) +# The JamTrack is on the disk, but does not yet have keys, so we will fetch them (keying) + +context.JK.DownloadJamTracks = {} +context.JK.DownloadJamTrack = class DownloadJamTrack + constructor: (@app, jamTrack, size = 'large') -> @EVENTS = context.JK.EVENTS @rest = context.JK.Rest() + @logger = context.JK.logger + @jamTrack = jamTrack + @size = size + @attemptedEnqueue = false + @errorReason = null + @errorMessage = null + @transitionTimer = null + @downloadTimer = null + @trackDetail = null + @stateHolder = null + @active = false + @startTime = null + @attempts = 0 + @tracked = false + @ajaxEnqueueAborted = false + @ajaxGetJamTrackRightAborted = false + throw "no JamTrack specified" unless @jamTrack? + throw "invalid size" if @size != 'large' && @size != 'small' + throw "no JamTrack version" unless @jamTrack.version? + + @path = [] + @states = { + no_client: { name: 'no-client', show: @showNoClient, leaf: true }, + synchronized: { name: 'synchronized', show: @showSynchronized, leaf: true}, + packaging: { name: 'packaging', show: @showPackaging }, + downloading: { name: 'downloading', show: @showDownloading }, + keying: { name: 'keying', show: @showKeying, max_time: 10000 }, + initial: { name: 'initial', show: @showInitial }, + quiet: { name: 'quiet', show: @showQuiet }, + errored: { name: 'errored', show: @showError, leaf: true} + } + + context.JK.DownloadJamTracks[@jamTrack.id] = this + downloadJamTrackTemplate = $('#template-download-jamtrack') + throw "no download jamtrack template" if not downloadJamTrackTemplate.exists() + + @root = $(downloadJamTrackTemplate.html()) + @stateHolder = @root.find('.state') + @root.on('remove', this.destroy) # automatically destroy self when removed from DOM + + # populate in template and visual transition functions + for state, data of @states + data.template = $("#template-download-jamtrack-state-#{data.name}") + + # start off in quiet state, but don't do it through transition system. The transition system expects a change, not initial state + @state = @states.quiet + + this.showState() + + + # after you've created the DownloadJamTrack widget, call synchronize which will begin ensuring that the jamtrack + # is downloaded and ready to open init: () => - @root = $($('#template-download-jamtrack').html()) + @active = true + @root.addClass('active') + this.reset() + + # check if we are in a browser or client + if !gon.isNativeClient + this.transition(@states.no_client) + else + this.transition(@states.initial) + + # when done with the widget, call destroy; this ensures it's not still active, and tracks final metrics + destroy: () => + $(this).off() + @active = false + @root.removeClass('active') + this.trackProgress() + # since we are not in a leave node, we need to report a state since this is effectively our end state + this.reset() + + reset: () => + @path = [] + @attempts = 0 + @tracked = false + @startTime = new Date() + # reset attemptedEnqueue to false, to allow one attempt to enqueue + @attemptedEnqueue = false + this.clearDownloadTimer() + this.clearTransitionTimer() + this.abortEnqueue() + this.abortGetJamTrackRight() + for state, data of @states + if data.timer? + clearInterval(data.timer) + data.timer = null + + abortEnqueue: () => + if @ajaxEnqueueAborted + @logger.debug("DownloadJamTrack: aborting ajax enqueue") + # we need to clear out @ajaxEnqueue *before* calling abort(), because the .fail callback fires inline + ajax = @ajaxEnqueueAborted + @ajaxEnqueueAborted = true + ajax.abort() + + abortGetJamTrackRight: () => + if @ajaxGetJamTrackRightAborted + @logger.debug("DownloadJamTrack: aborting ajax GetJamTrackRight") + # we need to clear out @ajaxEnqueue *before* calling abort(), because the .fail callback fires inline + ajax = @ajaxGetJamTrackRightAborted + @ajaxGetJamTrackRightAborted = true + ajax.abort() + + showState: () => + @state.stateStartTime = new Date(); + @stateHolder.children().remove() + @stateHolder.append(context._.template(@state.template.html(), @jamTrack, { variable: 'data' })) + @stateHolder.find('.' + @size).removeClass('hidden') + @state.show() + + # report a stat now that we've reached the end of this widget's journey + trackProgress: () => + + # do not double-report + if @tracked + return + + if @path.length == 0 + return + + unless @state.leaf + # we've been asked to report at a non-leaf node, meaning the user must have cancelled + @path.push('user-cancelled') + + flattened_path = @path.join('-') + + data = { + value: 1, + path: flattened_path, + duration: (new Date().getTime() - @startTime.getTime()) / 1000, + attempts: @attempts, + user_id: context.JK.currentUserId, + user_name: context.JK.currentUserName} + if @state == @states.errored + data.result = 'error' + data.detail = @errorReason + else + data.result = 'success' + + context.stats.write('web.jamtrack.downloader', data) + @tracked = true + + showPackaging: () => + @logger.debug("showing #{@state.name}") + this.expectTransition() + + 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()) + + showKeying: () => + @logger.debug("showing #{@state.name}") + context.jamClient.JamTrackKeysRequest() + this.waitForState() + + showQuiet: () => + @logger.debug("showing #{@state.name}") + + showInitial: () => + @logger.debug("showing #{@state.name}") + @attempts = @attempts + 1 + this.expectTransition() + context.JK.SubscriptionUtils.subscribe('jam_track_right', @jamTrack.jam_track_right_id).on(context.JK.EVENTS.SUBSCRIBE_NOTIFICATION, this.onJamTrackRightEvent) + this.checkState() + + showError: () => + @logger.debug("showing #{@state.name}") + context.JK.SubscriptionUtils.unsubscribe('jam_track_right', @jamTrack.jam_track_right_id) + + if @size == 'large' + @stateHolder.find('.msg').text(@errorMessage) + @stateHolder.find('.retry-button').click(this.retry) + else + @stateHolder.find('.msg').text(@jamTrack.name + ' (error)') + @stateHolder.find('.errormsg').text(@errorMessage) + @stateHolder.find('.retry-button').on('click', this.retry) + + retryMsg = '' + if @attempts > 1 + retryMsg = 'Continue retrying or contact support@jamkazam.com' + + @stateHolder.find('.retry').text(retryMsg) + + showSynchronized: () => + @logger.debug("showing #{@state.name}") + context.JK.SubscriptionUtils.unsubscribe('jam_track_right', @jamTrack.jam_track_right_id) + + showNoClient: () => + @logger.debug("showing #{@state.name}") + + downloadCheck: () => + @logger.debug "downloadCheck" + + retry: () => + @path = [] + @path.push('retry') + # just switch to the initial state again, causing the loop to start again + this.transition(@states.initial) + return false + + clearStateTimer: () => + if @state.timer? + clearInterval(@state.timer) + @state.timer = null + + stateIntervalCheck: () => + this.checkState() + + # if the timer is null now, then it must have been whacked due to a state change + # if not, then let's see if we have timed out + if @state.timer? + if (new Date()).getTime() - @state.stateStartTime.getTime() > @state.max_time + @logger.debug("The current step (#{@state.name}) took too long") + + if @state == @states.keying + # specific message + this.transitionError("#{@state.name}-timeout", "It took too long for the JamTrack to be keyed.") + else + # generic message + this.transitionError("#{@state.name}-timeout", "The current step (#{@state.name}) took too long") + + + # sets an interval timer for every second, waiting for the status to change + waitForState: () => + unless @active + @logger.error("DownloadJamTrack: ignoring waitForState because we are not active") + + @state.timer = setInterval(this.stateIntervalCheck, 1000) + + + # unused atm; the backend is good about always signalling. we still should though + expectDownload: () => + unless @active + @logger.error("DownloadJamTrack: ignoring expectDownload because we are not active") + + # every 10 seconds, wake up and check the server and see if we missed a state transition + this.clearDownloadTimer() + @downloadTimer = setTimeout(this.downloadCheck, 10000) + + clearDownloadTimer: () => + if @downloadTimer? + clearTimeout(@downloadTimer) + @downloadTimer = null + + transitionError: (reasonCode, errorMessage) => + @errorReason = reasonCode + @errorMessage = errorMessage + this.transition(@states.errored) + + transitionCheck: () => + this.checkState() + + # this should be called every time something changes statefully, to restart a 12 second timer to hit the server for update. + # if everything is moving snappily, we won't have to query the server much, because we are also getting subscription events + # about any changes to the status of the jam track. But, we could miss a message or there could be a path in the server where + # we don't get an event, so that's why, after 12 seconds, we'll still go to the server and check. + # exception: this should not be runngi + expectTransition: () => + unless @active + @logger.error("DownloadJamTrack: ignoring expectTransition because we are not active") + + # every 12 seconds, wake up and check the server and see if we missed a state transition + this.clearTransitionTimer() + @transitionTimer = setTimeout(this.transitionCheck, 12000) + + clearTransitionTimer: () => + if @transitionTimer? + clearTimeout(@transitionTimer) + @transitionTimer = null + + transition: (newState) => + unless @active + @logger.error("DownloadJamTrack: ignoring state change because we are not active") + return + + if newState == @state + @logger.debug("DownloadJamTrack: ignoring state change #{@state.name} #{newState}") + return + + if @state? + @logger.debug("DownloadJamTrack: state change: #{@state.name} => #{newState.name}") + # make sure there is no timer running on the old state + this.clearTransitionTimer() + this.clearStateTimer() + this.abortEnqueue() + @logger.debug("aborting getJamTrack right on state change") + this.abortGetJamTrackRight() + else + @logger.debug("DownloadJamTrack: initial state: #{newState.name}") + + @state = newState + + # track which states were taken + @path.push(@state.name) + + if @state.leaf + this.trackProgress() + + this.showState() + + $(this).triggerHandler(@EVENTS.JAMTRACK_DOWNLOADER_STATE_CHANGED, {state: @state}) + + checkState: () => + # check for the success state against the local state of the client... if it's playable, then we should be OK + @trackDetail = context.jamClient.JamTrackGetTrackDetail (@jamTrack.id) + + @logger.debug("DownloadJamTrack: JamTrackGetTrackDetail.key_state: " + @trackDetail.key_state) + + # first check if the version is not the same; if so, invalidate. + + if @trackDetail.version? + if @jamTrack.version != @trackDetail.version + @logger.info("DownloadJamTrack: JamTrack on disk is different version (stored: #{@trackDetail.version}, server: #{@jamTrack.version}. Invalidating") + context.jamClient.InvalidateJamTrack(@jamTrack.id) + @trackDetail = context.jamClient.JamTrackGetTrackDetail (@jamTrack.id) + + if @trackDetail.version? + @logger.error("after invalidating package, the version is still wrong!") + throw "after invalidating package, the version is still wrong!" + + switch @trackDetail.key_state + when 'pending' + this.transition(@states.keying) + when 'not authorized' + # TODO: if not authorized, do we need to re-initiate a keying attempt? + this.transition(@states.keying) + when 'ready' + this.transition(@states.synchronized) + when 'unknown' + @ajaxGetJamTrackRightAborted = false + @rest.getJamTrackRight({id: @jamTrack.id}) + .done(this.processJamTrackRight) + .fail(this.processJamTrackRightFail) + + + processSigningState: (signingState) => + @logger.debug("DownloadJamTrack: processSigningState: " + signingState) + + switch signingState + when 'QUIET' + if @attemptedEnqueue + # this means we've already tried to poke the server. something is wrong + this.transitionError("enqueue-timeout", "The server has not begun building your JamTrack.") + else + this.expectTransition() + + this.attemptToEnqueue() + when 'QUEUED' + # when it's queued, there is nothing to do except wait. + this.transition(@states.packaging) + when 'QUEUED_TIMEOUT' + this.transitionError("queued-timeout", "The server took too long to begin processing your JamTrack.") + when 'SIGNING' + this.transition(@states.packaging) + when 'SIGNING_TIMEOUT' + this.transitionError("signing-timeout", "The server took too long to create your JamTrack.") + when 'SIGNED' + this.transition(@states.downloading) + when 'ERROR' + if @attemptedEnqueue + # this means we've already tried to poke the server. something is wrong + this.transitionError("package-error", "The server failed to create your package.") + else + this.expectTransition() + + this.attemptToEnqueue() + else + @logger.error("unknown state: " + signingState) + this.transitionError("unknown-state-#{signingState}", "The server sent an unknown state message: " + signingState) + + attemptToEnqueue: () => + @attemptedEnqueue = true + @ajaxEnqueueAborted = false + @rest.enqueueJamTrack({id: @jamTrack.id}) + .done(this.processEnqueueJamTrack) + .fail(this.processEnqueueJamTrackFail) + + + processJamTrackRight: (myJamTrack) => + unless @ajaxGetJamTrackRightAborted + this.processSigningState(myJamTrack.signing_state) + else + @logger.debug("DownloadJamTrack: ignoring processJamTrackRight response") + + processJamTrackRightFail: () => + unless @ajaxGetJamTrackRightAborted? + this.transitionError("status-check-error", "Unable to check with the server on the status of your JamTrack.") + else + @logger.debug("DownloadJamTrack: ignoring processJamTrackRightFail response") + + processEnqueueJamTrack: (enqueueResponse) => + unless @ajaxEnqueueAborted + this.expectTransition() # the act of enqueuing should send down events to the client. we wait... + else + @logger.debug("DownloadJamTrack: ignoring processEnqueueJamTrack response") + + processEnqueueJamTrackFail: () => + unless @ajaxEnqueueAborted + this.transitionError("enqueue-error", "Unable to ask the server to build your JamTrack.") + else + @logger.debug("DownloadJamTrack: ignoring processEnqueueJamTrackFail response") + + onJamTrackRightEvent: (e, data) => + @logger.debug("DownloadJamTrack: subscription notification received: type:" + data.type) + this.expectTransition() + this.processSigningState(data.body.signing_state) + + 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) + + downloadSuccessCallback: (updateLocation) => + # is the package loadable yet? + @logger.debug("DownloadJamTrack: download complete - on to keying") + this.transition(@states.keying) + + downloadFailureCallback: (errorMsg) => + + this.transitionError("download-error", errorMsg) + + # makes a function name for the backend + makeDownloadProgressCallback: () => + "JK.DownloadJamTracks['#{@jamTrack.id}'].downloadProgressCallback" + + # makes a function name for the backend + makeDownloadSuccessCallback: () => + "JK.DownloadJamTracks['#{@jamTrack.id}'].downloadSuccessCallback" + + # makes a function name for the backend + makeDownloadFailureCallback: () => + "JK.DownloadJamTracks['#{@jamTrack.id}'].downloadFailureCallback" + diff --git a/web/app/assets/javascripts/faderHelpers.js b/web/app/assets/javascripts/faderHelpers.js index b4cb3823d..ba66a44ca 100644 --- a/web/app/assets/javascripts/faderHelpers.js +++ b/web/app/assets/javascripts/faderHelpers.js @@ -20,7 +20,8 @@ e.stopPropagation(); var $fader = $(this); - + var sessionModel = window.JK.CurrentSessionModel || null; + var mediaControlsDisabled = $fader.data('media-controls-disabled'); if(mediaControlsDisabled) { var mediaTrackOpener = $fader.data('media-track-opener'); @@ -28,11 +29,16 @@ return false; } + if(sessionModel && sessionModel.areControlsLockedForJamTrackRecording() && $control.closest('.session-track').data('track_data').type == 'jam_track') { + window.JK.prodBubble($fader, 'jamtrack-controls-disabled', {}, {positions:['top'], offsetParent: $fader.closest('.screen')}) + return false; + } + if($fader.data('showHelpAboutMediaMixers')) { - if(window.JK.CurrentSessionModel) { - if(!window.JK.CurrentSessionModel.hasShownAudioMediaMixerHelp()) { + if(sessionModel) { + if(!sessionModel.hasShownAudioMediaMixerHelp()) { window.JK.prodBubble($fader, 'volume-media-mixers', {}, {positions:['top'], offsetParent: $fader.closest('.screen')}) - window.JK.CurrentSessionModel.markShownAudioMediaMixerHelp() + sessionModel.markShownAudioMediaMixerHelp() } } } @@ -138,10 +144,16 @@ var mediaControlsDisabled = $draggingFaderHandle.data('media-controls-disabled'); var mediaTrackOpener = $draggingFaderHandle.data('media-track-opener'); - + var sessionModel = window.JK.CurrentSessionModel || null; + if(mediaControlsDisabled) { return false; } + + if(sessionModel && sessionModel.areControlsLockedForJamTrackRecording() && $draggingFaderHandle.closest('.session-track').data('track_data').type == 'jam_track') { + return false; + } + return true; } diff --git a/web/app/assets/javascripts/fakeJamClient.js b/web/app/assets/javascripts/fakeJamClient.js index 4db550fcb..7a15ffbb1 100644 --- a/web/app/assets/javascripts/fakeJamClient.js +++ b/web/app/assets/javascripts/fakeJamClient.js @@ -707,6 +707,16 @@ function TrackSetInstrument(track, instrumentId) {} + function JamTrackStopPlay() {} + function JamTrackPlay(){return true; } + function JamTrackIsPlayable() { + return true; + } + function JamTrackGetTrackDetail() { + return {key_state: 'unknown'} + } + function JamTrackKeysRequest() {} + function JamTrackDownload() {} // Method which sets volume function UpdateMixer(mixerId) {} @@ -1062,6 +1072,13 @@ this.TrackGetChatUsesMusic = TrackGetChatUsesMusic; this.TrackSetChatUsesMusic = TrackSetChatUsesMusic; + this.JamTrackStopPlay = JamTrackStopPlay; + this.JamTrackPlay = JamTrackPlay; + this.JamTrackIsPlayable = JamTrackIsPlayable; + this.JamTrackGetTrackDetail = JamTrackGetTrackDetail; + this.JamTrackKeysRequest = JamTrackKeysRequest; + this.JamTrackDownload = JamTrackDownload; + // Scoring Knobs this.GetScoreWorkTimingInterval = GetScoreWorkTimingInterval; this.SetScoreWorkTimingInterval = SetScoreWorkTimingInterval; diff --git a/web/app/assets/javascripts/globals.js b/web/app/assets/javascripts/globals.js index 584eba17b..01c640719 100644 --- a/web/app/assets/javascripts/globals.js +++ b/web/app/assets/javascripts/globals.js @@ -45,7 +45,8 @@ SUBSCRIBE_NOTIFICATION: 'subscribe_notification', CONNECTION_UP: 'connection_up', CONNECTION_DOWN: 'connection_down', - SCREEN_CHANGED: 'screen_changed' + SCREEN_CHANGED: 'screen_changed', + JAMTRACK_DOWNLOADER_STATE_CHANGED: 'jamtrack_downloader_state' }; context.JK.ALERT_NAMES = { diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js index f0500ae65..dca216937 100644 --- a/web/app/assets/javascripts/jam_rest.js +++ b/web/app/assets/javascripts/jam_rest.js @@ -1434,6 +1434,28 @@ }); } + function getJamTrackRight(options) { + var jamTrackId = options['id']; + + return $.ajax({ + type: "GET", + url: '/api/jamtracks/rights/' + jamTrackId + '?' + $.param(options), + dataType: "json", + contentType: 'application/json' + }) + } + + function enqueueJamTrack(options) { + var jamTrackId = options['id']; + + return $.ajax({ + type: "POST", + url: '/api/jamtracks/enqueue?' + jamTrackId + '?' + $.param(options), + dataType: "json", + contentType: 'applications/json' + }); + } + function getPurchasedJamTracks(options) { return $.ajax({ type: "GET", @@ -1563,13 +1585,23 @@ }); } - function validateUrlSite(url, sitetype) { + function validateUrlSite(url, sitetype) { + return $.ajax({ + type: "GET", + url: '/api/data_validation?sitetype='+sitetype+'&data=' + encodeURIComponent(url), + contentType: 'application/json' + }); + } + + function addRecordingTimeline(recordingId, data) { return $.ajax({ - type: "GET", - url: '/api/data_validation?sitetype='+sitetype+'&data=' + encodeURIComponent(url), - contentType: 'application/json' + type: "POST", + url: '/api/recordings/' + recordingId + '/timeline', + dataType: "json", + contentType: 'application/json', + data: JSON.stringify(data), }); - } + } function initialize() { return self; @@ -1694,6 +1726,8 @@ this.updateAudioLatency = updateAudioLatency; this.getJamtracks = getJamtracks; this.getPurchasedJamTracks = getPurchasedJamTracks; + this.getJamTrackRight = getJamTrackRight; + this.enqueueJamTrack = enqueueJamTrack; this.getBackingTracks = getBackingTracks; this.addJamtrackToShoppingCart = addJamtrackToShoppingCart; this.getShoppingCarts = getShoppingCarts; @@ -1710,6 +1744,7 @@ this.createSourceChange = createSourceChange; this.validateUrlSite = validateUrlSite; this.markRecordedBackingTrackSilent = markRecordedBackingTrackSilent; + this.addRecordingTimeline = addRecordingTimeline; return this; }; diff --git a/web/app/assets/javascripts/order.js b/web/app/assets/javascripts/order.js index fb741ad31..c5eb34e65 100644 --- a/web/app/assets/javascripts/order.js +++ b/web/app/assets/javascripts/order.js @@ -4,9 +4,12 @@ context.JK = context.JK || {}; context.JK.OrderScreen = function(app) { + var EVENTS = context.JK.EVENTS; var logger = context.JK.logger; var $screen = null; + var $templateOrderContent = null; + var $templatePurchasedJamTrack = null; var $navigation = null; var $billingInfo = null; var $shippingInfo = null; @@ -16,15 +19,23 @@ var $paymentInfoPanel = null; var $orderPanel = null; var $thanksPanel = null; + var $jamTrackInBrowser = null; + var $purchasedJamTrack = null; + var $purchasedJamTrackHeader = null; + var $purchasedJamTracks = null; var $orderContent = null; var userDetail = null; var step = null; var billing_info = null; var shipping_info = null; var shipping_as_billing = null; + var downloadJamTracks = []; + var purchasedJamTracks = null; + var purchasedJamTrackIterator = 0; function beforeShow() { - beforeShowPaymentInfo(); + beforeShowPaymentInfo(); + resetJamTrackDownloadInfo(); } function beforeShowPaymentInfo() { @@ -33,6 +44,12 @@ renderAccountInfo(); } + function resetJamTrackDownloadInfo() { + $purchasedJamTrack.addClass('hidden'); + $purchasedJamTracks.children().remove() + $jamTrackInBrowser.hide('hidden'); + } + function renderAccountInfo() { rest.getUserDetail() .done(populateAccountInfo) @@ -81,6 +98,21 @@ } function afterShow(data) { + // XXX : style-test code + // moveToThanks({jam_tracks: [{id: 14, jam_track_right_id: 11, name: 'Back in Black'}, {id: 15, jam_track_right_id: 11, name: 'In Bloom'}, {id: 16, jam_track_right_id: 11, name: 'Love Bird Supreme'}]}); + } + + function beforeHide() { + if(downloadJamTracks) { + context._.each(downloadJamTracks, function(downloadJamTrack) { + downloadJamTrack.destroy(); + downloadJamTrack.root.remove(); + }) + + downloadJamTracks = []; + } + purchasedJamTracks = null; + purchasedJamTrackIterator = 0; } function next(e) { @@ -412,7 +444,7 @@ data.shipping_as_billing = shipping_as_billing var orderContentHtml = $( context._.template( - $('#template-order-content').html(), + $templateOrderContent.html(), data, {variable: 'data'} ) @@ -430,13 +462,86 @@ beforeShowOrder(); } - function moveToThanks() { + function moveToThanks(purchaseResponse) { $("#order_error").addClass("hidden") $paymentInfoPanel.addClass("hidden") $orderPanel.addClass("hidden") $thanksPanel.removeClass("hidden") rest.clearShoppingCart() beforeShowOrder() + handleJamTracksPurchased(purchaseResponse.jam_tracks) + } + + function handleJamTracksPurchased(jamTracks) { + // were any JamTracks purchased? + var jamTracksPurchased = jamTracks && jamTracks.length > 0; + if(jamTracksPurchased) { + if(gon.isNativeClient) { + startDownloadJamTracks(jamTracks) + } + else { + $jamTrackInBrowser.removeClass('hidden'); + } + } + } + function startDownloadJamTracks(jamTracks) { + // there can be multiple purchased JamTracks, so we cycle through them + + purchasedJamTracks = jamTracks; + + // populate list of jamtracks purchased, that we will iterate through graphically + context._.each(jamTracks, function(jamTrack) { + var downloadJamTrack = new context.JK.DownloadJamTrack(app, jamTrack, 'small'); + var $purchasedJamTrack = $(context._.template( + $templatePurchasedJamTrack.html(), + jamTrack, + {variable: 'data'} + )); + + $purchasedJamTracks.append($purchasedJamTrack) + + // show it on the page + $purchasedJamTrack.append(downloadJamTrack.root) + + downloadJamTracks.push(downloadJamTrack) + }) + + iteratePurchasedJamTracks(); + } + + function iteratePurchasedJamTracks() { + if(purchasedJamTrackIterator < purchasedJamTracks.length ) { + var downloadJamTrack = downloadJamTracks[purchasedJamTrackIterator++]; + + // make sure the 'purchasing JamTrack' section can be seen + $purchasedJamTrack.removeClass('hidden'); + + // the widget indicates when it gets to any transition; we can hide it once it reaches completion + $(downloadJamTrack).on(EVENTS.JAMTRACK_DOWNLOADER_STATE_CHANGED, function(e, data) { + + if(data.state == downloadJamTrack.states.synchronized) { + logger.debug("jamtrack " + downloadJamTrack.jamTrack.name + " synchronized;") + //downloadJamTrack.root.remove(); + downloadJamTrack.destroy(); + + // go to the next JamTrack + iteratePurchasedJamTracks() + } + }) + + logger.debug("jamtrack " + downloadJamTrack.jamTrack.name + " downloader initializing") + + // kick off the download JamTrack process + downloadJamTrack.init() + + // XXX style-test code + // downloadJamTrack.transitionError("package-error", "The server failed to create your package.") + + } + else { + logger.debug("done iterating over purchased JamTracks") + $purchasedJamTrackHeader.text('All purchased JamTracks have been downloaded successfully! You can now play them in a session.') + } } function moveToPaymentInfo(e) { @@ -506,21 +611,28 @@ function initialize() { var screenBindings = { 'beforeShow': beforeShow, - 'afterShow': afterShow + 'afterShow': afterShow, + 'beforeHide' : beforeHide }; app.bindScreen('order', screenBindings); - $screen = $("#orderScreen"); - $paymentInfoPanel = $screen.find(".checkout-payment-info"); - $orderPanel = $screen.find(".order-panel"); - $thanksPanel = $screen.find(".thanks-panel"); - $navigation = $screen.find(".checkout-navigation-bar"); - $billingInfo = $paymentInfoPanel.find(".billing-address"); - $shippingInfo = $paymentInfoPanel.find(".shipping-address"); - $paymentMethod = $paymentInfoPanel.find(".payment-method"); - $shippingAddress = $paymentInfoPanel.find(".shipping-address-detail"); - $shippingAsBilling = $paymentInfoPanel.find("#shipping-as-billing"); - $orderContent = $orderPanel.find(".order-content"); + $screen = $("#orderScreen"); + $templateOrderContent = $("#template-order-content"); + $templatePurchasedJamTrack = $('#template-purchased-jam-track'); + $paymentInfoPanel = $screen.find(".checkout-payment-info"); + $orderPanel = $screen.find(".order-panel"); + $thanksPanel = $screen.find(".thanks-panel"); + $jamTrackInBrowser = $screen.find(".thanks-detail.jam-tracks-in-browser"); + $purchasedJamTrack = $thanksPanel.find(".thanks-detail.purchased-jam-track"); + $purchasedJamTrackHeader = $purchasedJamTrack.find(".purchased-jam-track-header"); + $purchasedJamTracks = $purchasedJamTrack.find(".purchased-list") + $navigation = $screen.find(".checkout-navigation-bar"); + $billingInfo = $paymentInfoPanel.find(".billing-address"); + $shippingInfo = $paymentInfoPanel.find(".shipping-address"); + $paymentMethod = $paymentInfoPanel.find(".payment-method"); + $shippingAddress = $paymentInfoPanel.find(".shipping-address-detail"); + $shippingAsBilling = $paymentInfoPanel.find("#shipping-as-billing"); + $orderContent = $orderPanel.find(".order-content"); if($screen.length == 0) throw "$screen must be specified"; if($navigation.length == 0) throw "$navigation must be specified"; diff --git a/web/app/assets/javascripts/playbackControls.js b/web/app/assets/javascripts/playbackControls.js index e05c185ae..3930635e6 100644 --- a/web/app/assets/javascripts/playbackControls.js +++ b/web/app/assets/javascripts/playbackControls.js @@ -47,6 +47,7 @@ var canUpdateBackend = false; var playbackMode = PlaybackMode.EveryWhere; var monitorPlaybackTimeout = null; + var jamTrackMode = false; // if true, we use different APIs to determine playback info function startPlay() { updateIsPlaying(true); @@ -67,7 +68,7 @@ playbackPositionMs = parseInt((offsetLeft / sliderBarWidth) * playbackDurationMs); updateCurrentTimeText(playbackPositionMs); if(canUpdateBackend) { - $self.triggerHandler('change-position', {positionMs: playbackPositionMs}); + $self.triggerHandler('change-position', {positionMs: playbackPositionMs, jamTrackMode: jamTrackMode}); canUpdateBackend = false; } } @@ -100,13 +101,25 @@ } $playButton.on('click', function(e) { - startPlay(); + var sessionModel = context.JK.CurrentSessionModel || null; + if(sessionModel && sessionModel.areControlsLockedForJamTrackRecording() && $parentElement.closest('.session-track').data('track_data').type == 'jam_track') { + context.JK.prodBubble($fader, 'jamtrack-controls-disabled', {}, {positions:['top'], offsetParent: $playButton}) return false; + } + + startPlay(); + return false; }); $pauseButton.on('click', function(e) { - stopPlay(); + 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; }); $sliderBar.on('click', function(e) { @@ -144,16 +157,31 @@ }); function monitorRecordingPlayback() { + if(jamTrackMode) { + 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) + } + else { + var positionMs = context.jamClient.SessionCurrrentPlayPosMs(); + var durationMs = context.jamClient.SessionGetTracksPlayDurationMs(); + } + var isPlaying = context.jamClient.isSessionTrackPlaying(); - var positionMs = context.jamClient.SessionCurrrentPlayPosMs(); - var durationMs = context.jamClient.SessionGetTracksPlayDurationMs(); + + if(positionMs < 0) { + // bug in backend? + positionMs = 0; + } update(positionMs, durationMs, isPlaying); monitorPlaybackTimeout = setTimeout(monitorRecordingPlayback, 500); } - function update(currentTimeMs, durationTimeMs, isPlaying) { + function update(currentTimeMs, durationTimeMs, isPlaying, offsetStart) { if(dragging) { return; @@ -175,6 +203,10 @@ } } + + if(currentTimeMs < offsetStart) { + currentTimeMs = 0; // this is to squelch movement during tap-in period + } updateDurationTime(durationTimeMs); updateCurrentTime(currentTimeMs); updateIsPlaying(isPlaying); @@ -247,7 +279,10 @@ } } - function startMonitor() { + function startMonitor(_jamTrackMode) { + + jamTrackMode = !!_jamTrackMode; + monitorRecordingPlayback(); } diff --git a/web/app/assets/javascripts/recordingModel.js b/web/app/assets/javascripts/recordingModel.js index 078a41c4d..a0e275f48 100644 --- a/web/app/assets/javascripts/recordingModel.js +++ b/web/app/assets/javascripts/recordingModel.js @@ -132,7 +132,6 @@ $self.triggerHandler('stoppedRecording', {'recordingId': recording.id, 'reason' : 'rest', 'details' : arguments}); } }); - }); return true; } diff --git a/web/app/assets/javascripts/session.js b/web/app/assets/javascripts/session.js index b47946641..114c12c8a 100644 --- a/web/app/assets/javascripts/session.js +++ b/web/app/assets/javascripts/session.js @@ -82,6 +82,7 @@ var startingRecording = false; // double-click guard var claimedRecording = null; var backing_track_path = null; + var jamTrack = null; var playbackControls = null; var promptLeave = false; var rateSessionDialog = null; @@ -93,6 +94,10 @@ var $screen = null; var $mixModeDropdown = null; var $templateMixerModeChange = null; + var $otherAudioContainer = null; + var $myTracksContainer = null; + var $liveTracksContainer = null; + var downloadJamTrack = null; var $closePlaybackRecording = null; var $openBackingTrack = null; var mediaTrackGroups = [ChannelGroupIds.MediaTrackGroup, ChannelGroupIds.JamTrackGroup, ChannelGroupIds.MetronomeGroup]; @@ -106,7 +111,7 @@ window.location = '/client#/home'; } promptLeave = true; - $('#session-mytracks-container').empty(); + $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"); @@ -247,6 +252,7 @@ $(sessionModel.recordingModel) .on('startingRecording', function(e, data) { displayStartingRecording(); + lockControlsforJamTrackRecording(); }) .on('startedRecording', function(e, data) { if(data.reason) { @@ -292,12 +298,30 @@ { displayStartedRecording(); displayWhoCreated(data.clientId); + lockControlsforJamTrackRecording(); } }) .on('stoppingRecording', function(e, data) { displayStoppingRecording(data); + unlockControlsforJamTrackRecording(); }) .on('stoppedRecording', function(e, data) { + + unlockControlsforJamTrackRecording(); + if(sessionModel.selfOpenedJamTracks()) { + + var timeline = context.jamClient.GetJamTrackTimeline(); + + rest.addRecordingTimeline(data.recordingId, timeline) + .fail(function(){ + app.notify( + { title: "Unable to Add JamTrack Volume Data", + text: "The volume of the JamTrack will not be correct in the recorded mix." }, + null, + true); + }) + } + if(data.reason) { logger.warn("Recording Discarded: ", data); var reason = data.reason; @@ -461,6 +485,14 @@ playbackControls.stopMonitor(); } backing_track_path = currentSession == null ? null : currentSession.backing_track_path; + + if(jamTrack == null && (currentSession && currentSession.jam_track != null)) { + playbackControls.startMonitor(true); + } + else if(jamTrack && (currentSession == null || currentSession.jam_track == null)) { + playbackControls.stopMonitor(); + } + jamTrack = currentSession == null ? null : currentSession.jam_track; } function sessionChanged() { @@ -518,6 +550,14 @@ } } + function resetOtherAudioContent() { + if ($('.session-recordings .track').length === 0 && $('.session-recordings .download-jamtrack').length === 0) { + $('.session-recordings .when-empty').show(); + $('.session-recording-name-wrapper').hide(); + $('.session-recordings .recording-controls').hide(); + $('.session-recordings .session-recording-name').text('(No audio loaded)') + } + } function didSelfOpenMedia() { var localMediaMixers = _mixersForGroupIds([ChannelGroupIds.MediaTrackGroup, ChannelGroupIds.JamTrackGroup, ChannelGroupIds.MetronomeGroup], MIX_MODES.MASTER); @@ -530,7 +570,7 @@ } function renderSession() { - $('#session-mytracks-container').empty(); + $myTracksContainer.empty(); $('.session-track').remove(); // Remove previous tracks var $voiceChat = $('#voice-chat'); $voiceChat.hide(); @@ -545,7 +585,9 @@ if ($('.session-livetracks .track').length === 0) { $('.session-livetracks .when-empty').show(); } - + resetOtherAudioContent(); + + /** if ($('.session-recordings .track').length === 0) { $('.session-recordings .when-empty').show(); $('.session-recording-name-wrapper').hide(); @@ -558,6 +600,7 @@ $('.session-recordings .recording-controls').show(); checkShowCloseControl(); } + */ // Handle long labels: $(".track-label").dotdotdot() @@ -1047,6 +1090,7 @@ // Default trackData to participant + no Mixer state. var trackData = { + type: 'backing_track', trackId: backingTrack.id, clientId: backingTrack.client_id, name: 'Backing', @@ -1101,19 +1145,14 @@ // find the track or tracks that correspond to the mixer var correspondingTracks = [] $.each(jamTracks, function(i, jamTrack) { - if(mixer.id.indexOf("L") == 0) { - if(mixer.id.substring(1) == jamTrack.id) { + if(mixer.id == jamTrack.id) { correspondingTracks.push(jamTrack); } - else { - // this should not be possible - alert("Invalid state: the backing track had neither persisted_track_id or persisted_client_id"); - } - } }); if(correspondingTracks.length == 0) { noCorrespondingTracks = true; + logger.error("could not correlate jam tracks", jamTrackMixers, jamTracks) app.notify({ title: "Unable to Open JamTrack", text: "Could not correlate server and client tracks", @@ -1145,6 +1184,7 @@ // Default trackData to participant + no Mixer state. var trackData = { + type: 'jam_track', trackId: oneOfTheTracks.id, clientId: oneOfTheTracks.client_id, name: name, @@ -1254,6 +1294,7 @@ // Default trackData to participant + no Mixer state. var trackData = { + type: 'metronome', trackId: "MS" + oneOfTheTracks.id, clientId: oneOfTheTracks.client_id, name: "Metronome", @@ -1361,6 +1402,7 @@ // Default trackData to participant + no Mixer state. var trackData = { + type: 'recorded_track', trackId: oneOfTheTracks.id, clientId: oneOfTheTracks.client_id, name: name, @@ -1771,15 +1813,14 @@ function _addTrack(allowDelete, trackData, mixer, oppositeMixer) { - var parentSelector = '#session-mytracks-container'; - var $destination = $(parentSelector); + var $destination = $myTracksContainer; if (trackData.clientId !== app.clientId) { - parentSelector = '#session-livetracks-container'; - $destination = $(parentSelector); + $destination = $liveTracksContainer $('.session-livetracks .when-empty').hide(); } var template = $('#template-session-track').html(); var newTrack = $(context.JK.fillTemplate(template, trackData)); + newTrack.data('track_data', trackData) var audioOverlay = $('.disabled-track-overlay', newTrack); var $trackIconMute = newTrack.find('.track-icon-mute') $trackIconMute.muteSelector().on(EVENTS.MUTE_SELECTED, trackMuteSelected) @@ -1789,7 +1830,7 @@ $destination.append(newTrack); // Render VU meters and gain fader - var trackSelector = parentSelector + ' .session-track[track-id="' + trackData.trackId + '"]'; + var trackSelector = $destination.selector + ' .session-track[track-id="' + trackData.trackId + '"]'; var gainPercent = trackData.gainPercent || 0; connectTrackToMixer(trackSelector, trackData, trackData.mixerId, gainPercent, trackData.group_id); @@ -1805,21 +1846,31 @@ tracks[trackData.trackId] = new context.JK.SessionTrack(trackData.clientId); } - + // something is being shown now in the other audio area + function otherAudioFilled() { + $('.session-recordings .when-empty').hide(); + $('.session-recording-name-wrapper').show(); + } function _addRecordingTrack(trackData, mixer, oppositeMixer) { + + otherAudioFilled(); + + $('.session-recordings .recording-controls').show(); + var parentSelector = '#session-recordedtracks-container'; var $destination = $(parentSelector); - + var template = $('#template-session-track').html(); var newTrack = $(context.JK.fillTemplate(template, trackData)); - $destination.append(newTrack); + newTrack.data('track_data', trackData); + $otherAudioContainer.append(newTrack); if(trackData.preMasteredClass) { context.JK.helpBubble($('.track-instrument', newTrack), 'pre-processed-track', {}, {offsetParent: newTrack.closest('.content-body')}); } // Render VU meters and gain fader - var trackSelector = parentSelector + ' .session-track[track-id="' + trackData.trackId + '"]'; + var trackSelector = $otherAudioContainer.selector + ' .session-track[track-id="' + trackData.trackId + '"]'; var gainPercent = trackData.gainPercent || 0; var $track = connectTrackToMixer(trackSelector, trackData, trackData.mixerId, gainPercent, null); var $trackIconMute = $track.find('.track-icon-mute') @@ -2072,6 +2123,11 @@ return false; } + if(sessionModel.areControlsLockedForJamTrackRecording() && $control.closest('.session-track').data('track_data').type == 'jam_track') { + context.JK.prodBubble($control, 'jamtrack-controls-disabled', {}, {positions:['bottom'], offsetParent: $control.closest('.screen')}) + return false; + } + if($control.data('showHelpAboutMediaMixers')) { if(!sessionModel.hasShownAudioMediaMixerHelp()) { context.JK.prodBubble($control, 'volume-media-mixers', {}, {positions:['bottom'], offsetParent: $control.closest('.screen')}) @@ -2079,6 +2135,8 @@ } } + + $.each(mixerIds, function(i,v) { var mixerId = v; // behavior: if this is the user's track in personal mode, then we mute the track globally @@ -2316,6 +2374,14 @@ $('#recording-status').text("Make a Recording"); } + function lockControlsforJamTrackRecording() { + sessionModel.lockControlsforJamTrackRecording(); + } + + function unlockControlsforJamTrackRecording() { + sessionModel.unlockControlsforJamTrackRecording(); + } + function displayWhoCreated(clientId) { if(app.clientId != clientId) { // don't show to creator sessionModel.findUserBy({clientId: clientId}) @@ -2382,7 +2448,69 @@ return false; } - app.layout.showDialog('open-jam-track-dialog'); + app.layout.showDialog('open-jam-track-dialog').one(EVENTS.DIALOG_CLOSED, function(e, data) { + + // once the dialog is closed, see if the user has a jamtrack selected + if(!data.canceled && data.result.jamTrack) { + + var jamTrack = data.result.jamTrack; + + // hide 'other audio' placeholder + otherAudioFilled(); + + if(downloadJamTrack) { + // if there was one showing before somehow, destroy it. + logger.warn("destroying existing JamTrack") + downloadJamTrack.root.remove(); + downloadJamTrack.destroy(); + downloadJamTrack = null + } + + downloadJamTrack = new context.JK.DownloadJamTrack(app, jamTrack, 'large'); + + // the widget indicates when it gets to any transition; we can hide it once it reaches completion + $(downloadJamTrack).on(EVENTS.JAMTRACK_DOWNLOADER_STATE_CHANGED, function(e, data) { + + if(data.state == downloadJamTrack.states.synchronized) { + logger.debug("jamtrack synchronized; hide widget and show tracks") + downloadJamTrack.root.remove(); + downloadJamTrack.destroy(); + downloadJamTrack = null; + + // XXX: test with this removed; it should be unnecessary + context.jamClient.JamTrackStopPlay(); + + if(jamTrack.jmep) + { + logger.debug("setting jmep data") + context.jamClient.JamTrackLoadJmep(jamTrack.id, jamTrack.jmep) + } + else { + logger.debug("no jmep data for jamtrack") + } + + // JamTrackPlay means 'load' + var result = context.jamClient.JamTrackPlay(jamTrack.id); + + if(!result) { + app.notify( + { title: "JamTrack Can Not Open", + text: "Unable to open your JamTrack. Please contact support@jamkazam.com" + }, null, true); + } + } + }) + + // show it on the page + $otherAudioContainer.append(downloadJamTrack.root) + + // kick off the download JamTrack process + downloadJamTrack.init() + } + else { + logger.debug("OpenJamTrack dialog closed with no selection; ignoring", data) + } + }) return false; } @@ -2497,7 +2625,7 @@ if(sessionModel.recordedTracks()) { closeRecording(); } - else if(sessionModel.jamTracks()) { + else if(sessionModel.jamTracks() || downloadJamTrack) { closeJamTrack(); } else if(sessionModel.backingTrack() && sessionModel.backingTrack().path) { @@ -2533,6 +2661,20 @@ } function closeJamTrack() { + + logger.debug("closing recording"); + + if(downloadJamTrack) { + logger.debug("closing DownloadJamTrack widget") + downloadJamTrack.root.remove(); + downloadJamTrack.destroy(); + downloadJamTrack = null; + + // this is necessary because a syncing widget means no jamtracks are loaded; + // so removing the widget will not cause a backend media change event (and so renderSession will not be called, ultimately) + resetOtherAudioContent(); + } + rest.closeJamTrack({id: sessionModel.id()}) .done(function() { sessionModel.refreshCurrentSession(true); @@ -2567,6 +2709,8 @@ } function closeRecording() { + logger.debug("closing recording"); + rest.stopPlayClaimedRecording({id: sessionModel.id(), claimed_recording_id: sessionModel.getCurrentSession().claimed_recording.id}) .done(function(response) { //sessionModel.refreshCurrentSession(true); @@ -2598,10 +2742,28 @@ function onChangePlayPosition(e, data){ logger.debug("calling jamClient.SessionTrackSeekMs(" + data.positionMs + ")"); + + if(data.jamTrackMode) { + context.jamClient.SessionJamTrackSeekMs(data.positionMs); + } + else { context.jamClient.SessionTrackSeekMs(data.positionMs); + } } function startStopRecording() { + + // check first if a jamtrack is loaded, and playing; if so, tell user to stop the play + if(sessionModel.jamTracks() && context.jamClient.isSessionTrackPlaying()) { + app.notify( + { title: "Can't Recording a Play JamTrack", + text: "Stop the JamTrack before trying to recording." }, + null, + true); + + return; + } + if(sessionModel.recordingModel.isRecording()) { sessionModel.recordingModel.stopRecording(); } @@ -2720,6 +2882,9 @@ $screen = $('#session-screen'); $mixModeDropdown = $screen.find('select.monitor-mode') $templateMixerModeChange = $('#template-mixer-mode-change'); + $otherAudioContainer = $('#session-recordedtracks-container'); + $myTracksContainer = $('#session-mytracks-container') + $liveTracksContainer = $('#session-livetracks-container'); $closePlaybackRecording = $('#close-playback-recording') $openBackingTrack = $('#open-a-backingtrack'); events(); diff --git a/web/app/assets/javascripts/sessionModel.js b/web/app/assets/javascripts/sessionModel.js index 7487dec2d..f1d7988ec 100644 --- a/web/app/assets/javascripts/sessionModel.js +++ b/web/app/assets/javascripts/sessionModel.js @@ -37,6 +37,7 @@ var previousBackingTracks = []; var openBackingTrack = null; var shownAudioMediaMixerHelp = false; + var controlsLockedForJamTrackRecording = false; var mixerMode = MIX_MODES.PERSONAL; @@ -113,6 +114,11 @@ } } + // did I open up the current JamTrack? + function selfOpenedJamTracks() { + return currentSession && (currentSession.jam_track_initiator_id == context.JK.currentUserId) + } + function backingTrack() { if(currentSession) { // TODO: objectize this for VRFS-2665, VRFS-2666, VRFS-2667, VRFS-2668 @@ -152,6 +158,18 @@ return inSession; } + function lockControlsforJamTrackRecording() { + controlsLockedForJamTrackRecording = true; + } + + function unlockControlsforJamTrackRecording() { + controlsLockedForJamTrackRecording = false; + } + + function areControlsLockedForJamTrackRecording() { + return controlsLockedForJamTrackRecording; + } + function onMixerModeChanged(newMixerMode) { mixerMode = newMixerMode; @@ -352,6 +370,7 @@ previousBackingTracks = [] openBackingTrack = null shownAudioMediaMixerHelp = false + controlsLockedForJamTrackRecording = false; } // you should only update currentSession with this function @@ -830,6 +849,10 @@ this.isMasterMixMode = isMasterMixMode; this.isPersonalMixMode = isPersonalMixMode; this.getMixMode = getMixMode; + this.selfOpenedJamTracks = selfOpenedJamTracks; + this.areControlsLockedForJamTrackRecording = areControlsLockedForJamTrackRecording; + this.lockControlsforJamTrackRecording = lockControlsforJamTrackRecording; + this.unlockControlsforJamTrackRecording = unlockControlsforJamTrackRecording; // ALERT HANDLERS this.onBackendMixerChanged = onBackendMixerChanged; diff --git a/web/app/assets/javascripts/subscription_utils.js.coffee b/web/app/assets/javascripts/subscription_utils.js.coffee index afd7852ab..b9b3e7823 100644 --- a/web/app/assets/javascripts/subscription_utils.js.coffee +++ b/web/app/assets/javascripts/subscription_utils.js.coffee @@ -84,6 +84,7 @@ class SubscriptionUtils # call subscribe, and use the returned object to listen for events of name context.JK.EVENTS.SUBSCRIBE_NOTIFICATION subscribe: (type, id) => + id = id.toString() key = this.genKey(type, id) @logger.debug("subscribing for any notifications for #{key}") @@ -104,6 +105,7 @@ class SubscriptionUtils # TODO: this should not send a unsubscribe message to the server it's the last listener for the specific type/id combo unsubscribe: (type, id) => + id = id.toString() key = this.genKey(type, id) @logger.debug("unsubscribing for any notifications for #{key}") diff --git a/web/app/assets/javascripts/utils.js b/web/app/assets/javascripts/utils.js index 1b6c1a575..fef57ccf5 100644 --- a/web/app/assets/javascripts/utils.js +++ b/web/app/assets/javascripts/utils.js @@ -910,6 +910,10 @@ return null; } + context.JK.makeAbsolute = function(path) { + return window.location.protocol + '//' + window.location.host + path; + } + context.JK.popExternalLinks = function ($parent) { if(!$parent) $parent = $('body'); diff --git a/web/app/assets/stylesheets/client/checkout.css.scss b/web/app/assets/stylesheets/client/checkout.css.scss index 6f9bc6a40..efe918e22 100644 --- a/web/app/assets/stylesheets/client/checkout.css.scss +++ b/web/app/assets/stylesheets/client/checkout.css.scss @@ -168,6 +168,40 @@ .thanks-panel { padding: 30px; + + span.notice { + font-style:italic; + font-size:12px; + } + + br.purchase-downloads { + clear:both; + margin-bottom:20px; + } + + .thanks-detail.purchased-jam-track { + + margin-top:20px; + + .purchased-jam-track-header { + font-size: 15px; + margin-bottom:10px; + } + + ul.purchased-list { + float:left; + margin:20px 100px 0 20px; + + li { + margin:0; + } + } + + .download-jamtrack { + width:auto; + vertical-align: middle; // to make bullets mid-align when error shows + } + } } .order-panel { diff --git a/web/app/assets/stylesheets/client/client.css b/web/app/assets/stylesheets/client/client.css index 8403cf75a..9e1899ce3 100644 --- a/web/app/assets/stylesheets/client/client.css +++ b/web/app/assets/stylesheets/client/client.css @@ -66,4 +66,5 @@ *= require jquery.Jcrop *= require icheck/minimal/minimal *= require users/syncViewer + *= require ./downloadJamTrack */ \ No newline at end of file diff --git a/web/app/assets/stylesheets/client/downloadJamTrack.css.scss b/web/app/assets/stylesheets/client/downloadJamTrack.css.scss index 1cfe42004..27eb232a0 100644 --- a/web/app/assets/stylesheets/client/downloadJamTrack.css.scss +++ b/web/app/assets/stylesheets/client/downloadJamTrack.css.scss @@ -1,5 +1,77 @@ @import "client/common"; .download-jamtrack { + display:inline-block; + width:100%; + .retry-button { + margin-top:20px; + } + + .retry { + margin-top:10px; + } + + .msg { + white-space: normal; + } + + .spinner-large { + margin:20px auto 0; + text-align:center; + } + + .small { + .state { + text-align:left; + } + font-size:inherit; + .msg { + line-height: 32px; + height: 32px; + display: inline-block; + vertical-align: middle; + } + .spinner-small { + display:inline-block; + vertical-align:middle; + } + } + + .large { + .state { + text-align:center; + } + } + + &.active { + + .small { + margin-bottom:5px; + + .msg { + font-weight:bold; + color:white; + display:inline; + } + .errormsg { + display:block; + font-size:14px; + } + .retry { + display:block; + margin:3px 0 0 0; + font-size:14px; + } + .retry-button { + float:right; + margin:5px 0 5px 20px; + } + + .msg-holder { + display:block; + } + } + + } } \ No newline at end of file diff --git a/web/app/assets/stylesheets/client/session.css.scss b/web/app/assets/stylesheets/client/session.css.scss index fc6f8ad17..7f38661c6 100644 --- a/web/app/assets/stylesheets/client/session.css.scss +++ b/web/app/assets/stylesheets/client/session.css.scss @@ -148,6 +148,17 @@ } } + .session-recording-name { + width:60%; + overflow:hidden; + margin-top:9px; + margin-bottom:8px; + font-size:16px; + } + + .download-jamtrack { + margin-top:50px; + } } diff --git a/web/app/controllers/api_jam_tracks_controller.rb b/web/app/controllers/api_jam_tracks_controller.rb index 567d0b7ad..03a80a51a 100644 --- a/web/app/controllers/api_jam_tracks_controller.rb +++ b/web/app/controllers/api_jam_tracks_controller.rb @@ -58,14 +58,22 @@ class ApiJamTracksController < ApiController end def keys - jamtrack_ids = params[:jamtracks] - - unless jamtrack_ids.kind_of?(Array) - render :json => {message: 'jamtracks parameter must be an array'}, :status => 200 + jamtrack_holder = params[:jamtracks] + + unless jamtrack_holder.kind_of?(Hash) + render :json => {message: 'jamtracks parameter must be an hash'}, :status => 422 return end - @jam_tracks = JamTrackRight.list_keys(current_user, params[:jamtracks]) + jamtrack_ids = jamtrack_holder[:tracks] + + unless jamtrack_ids.kind_of?(Array) + render :json => {message: 'jamtracks:tracks parameter must be an array'}, :status => 422 + return + end + + @jam_tracks = JamTrackRight.list_keys(current_user, jamtrack_ids) + render "api_jam_tracks/list_keys", :layout => nil end diff --git a/web/app/controllers/api_recordings_controller.rb b/web/app/controllers/api_recordings_controller.rb index 97ac23a22..b6f246e72 100644 --- a/web/app/controllers/api_recordings_controller.rb +++ b/web/app/controllers/api_recordings_controller.rb @@ -1,7 +1,7 @@ class ApiRecordingsController < ApiController before_filter :api_signed_in_user, :except => [ :add_like ] - before_filter :lookup_recording, :only => [ :show, :stop, :claim, :discard, :keep, :delete_claim ] + before_filter :lookup_recording, :only => [ :show, :stop, :claim, :discard, :keep, :delete_claim, :add_timeline_data ] before_filter :lookup_recorded_track, :only => [ :download, :upload_next_part, :upload_sign, :upload_part_complete, :upload_complete ] before_filter :lookup_recorded_backing_track, :only => [ :backing_track_download, :backing_track_upload_next_part, :backing_track_upload_sign, :backing_track_upload_part_complete, :backing_track_upload_complete ] before_filter :lookup_recorded_video, :only => [ :video_upload_sign, :video_upload_start, :video_upload_complete ] @@ -377,6 +377,13 @@ class ApiRecordingsController < ApiController end end + # metadata + def add_timeline + @recording.add_timeline(params[:metadata]) + + render :json => {}, :status => 200 + end + private def lookup_recording diff --git a/web/app/controllers/api_recurly_controller.rb b/web/app/controllers/api_recurly_controller.rb index cd0aa4b55..28d16933b 100644 --- a/web/app/controllers/api_recurly_controller.rb +++ b/web/app/controllers/api_recurly_controller.rb @@ -57,20 +57,36 @@ class ApiRecurlyController < ApiController def place_order error=nil puts "PLACING ORDER #{params.inspect}" + response = {jam_tracks:[]} + + # 1st confirm that all specified JamTracks exist + jam_tracks = [] + params[:jam_tracks].each do |jam_track_id| jam_track = JamTrack.where("id=?", jam_track_id).first if jam_track - @client.place_order(current_user, jam_track) + jam_tracks << jam_track else error="JamTrack not found for '#{jam_track_id}'" break end end + # then buy each + unless error + jam_tracks.each do |jam_track| + jam_track_right = @client.place_order(current_user, jam_track) + # build up the response object with JamTracks that were purchased. + # if this gets more complicated, we should switch to RABL + response[:jam_tracks] << {name: jam_track.name, id: jam_track.id, jam_track_right_id: jam_track_right.id} + end + end + + if error render json: { errors: {message:error}}, :status => 404 else - render :json=>{}, :status=>200 + render :json=>response, :status=>200 end rescue RecurlyClientError => x render json: { message: x.inspect, errors: x.errors}, :status => 404 diff --git a/web/app/controllers/spikes_controller.rb b/web/app/controllers/spikes_controller.rb index 4bfc90e54..ab6da676d 100644 --- a/web/app/controllers/spikes_controller.rb +++ b/web/app/controllers/spikes_controller.rb @@ -33,11 +33,26 @@ class SpikesController < ApplicationController def subscription + #Notification.send_reload(MessageFactory::ALL_NATIVE_CLIENTS) + Notification.send_subscription_message('test', '1', '{"msg": "oh hai 1"}') Notification.send_subscription_message('test', '2', '{"msg": "oh hai 2"}') render text: 'oh hai' end + def download_jam_track + + jamTrack = JamTrack.find(params[:jam_track_id]) + jamTrackRight = jamTrack.right_for_user(current_user) + + gon.jamTrackId = jamTrack.id + gon.jamTrackRightId = jamTrackRight.id + gon.size = params[:size] ? params[:size] : 'large' + gon.switchState = params[:state] + + render :layout => 'web' + end + def site_validate render :layout => 'web' end @@ -45,5 +60,4 @@ class SpikesController < ApplicationController def recording_source render :layout => 'web' end - end diff --git a/web/app/views/api_claimed_recordings/show.rabl b/web/app/views/api_claimed_recordings/show.rabl index aa7de2609..91174f87b 100644 --- a/web/app/views/api_claimed_recordings/show.rabl +++ b/web/app/views/api_claimed_recordings/show.rabl @@ -20,8 +20,16 @@ node :mix do |claimed_recording| end child(:recording => :recording) { - attributes :id, :created_at, :duration, :comment_count, :like_count, :play_count - + attributes :id, :created_at, :duration, :comment_count, :like_count, :play_count, :jam_track_id, :jam_track_initiator_id + + child(:jam_track => :jam_track) { + attributes :id + + node :jmep do |jam_track| + jam_track.jmep_json ? JSON.parse(jam_track.jmep_json) : nil + end + } + child(:band => :band) { attributes :id, :name, :location, :photo_url } @@ -50,6 +58,15 @@ child(:recording => :recording) { end } + child(:recorded_jam_track_tracks => :recorded_jam_track_tracks) { + node do |recorded_jam_track_track| + { + id: recorded_jam_track_track.jam_track_track.id, + timeline: recorded_jam_track_track.timeline ? JSON.parse(recorded_jam_track_track.timeline) : [] + } + end + } + child(:comments => :comments) { attributes :comment, :created_at diff --git a/web/app/views/api_jam_tracks/list_keys.rabl b/web/app/views/api_jam_tracks/list_keys.rabl index 43a33b24e..f82aa5650 100644 --- a/web/app/views/api_jam_tracks/list_keys.rabl +++ b/web/app/views/api_jam_tracks/list_keys.rabl @@ -2,7 +2,7 @@ object @jam_tracks node do |jam_track| { - id: jam_track['id'], + id: jam_track['id'].to_s, private: jam_track['private_key'], error: jam_track['private_key'] ? nil : ( jam_track['jam_track_right_id'] ? 'no_key' : 'not_purchased' ) } diff --git a/web/app/views/api_jam_tracks/show.rabl b/web/app/views/api_jam_tracks/show.rabl index f9ea856db..cab264aa5 100644 --- a/web/app/views/api_jam_tracks/show.rabl +++ b/web/app/views/api_jam_tracks/show.rabl @@ -1,6 +1,6 @@ object @jam_track -attributes :id, :name, :description, :recording_type, :original_artist, :songwriter, :publisher, :sales_region, :price +attributes :id, :name, :description, :recording_type, :original_artist, :songwriter, :publisher, :sales_region, :price, :version node :genres do |item| [item.genre.description] # XXX: need to return single genre; not array diff --git a/web/app/views/api_jam_tracks/show_for_client.rabl b/web/app/views/api_jam_tracks/show_for_client.rabl index 4e708a4ac..364b13031 100644 --- a/web/app/views/api_jam_tracks/show_for_client.rabl +++ b/web/app/views/api_jam_tracks/show_for_client.rabl @@ -1,6 +1,14 @@ object @jam_track -attributes :id, :name, :description, :initial_play_silence, :original_artist +attributes :id, :name, :description, :initial_play_silence, :original_artist, :version + +node :jmep do |jam_track| + jam_track.jmep_json ? JSON.parse(jam_track.jmep_json) : nil +end + +node :jam_track_right_id do |jam_track| + jam_track.right_for_user(current_user).id +end child(:jam_track_tracks => :tracks) { attributes :id, :part, :instrument diff --git a/web/app/views/api_music_sessions/show.rabl b/web/app/views/api_music_sessions/show.rabl index cd277c3fe..7c1b0c8de 100644 --- a/web/app/views/api_music_sessions/show.rabl +++ b/web/app/views/api_music_sessions/show.rabl @@ -13,7 +13,7 @@ if !current_user } else - attributes :id, :name, :description, :musician_access, :approval_required, :fan_access, :fan_chat, :band_id, :user_id, :claimed_recording_initiator_id, :track_changes_counter, :max_score, :backing_track_path, :metronome_active + attributes :id, :name, :description, :musician_access, :approval_required, :fan_access, :fan_chat, :band_id, :user_id, :claimed_recording_initiator_id, :track_changes_counter, :max_score, :backing_track_path, :metronome_active, :jam_track_initiator_id node :can_join do |session| session.can_join?(current_user, true) diff --git a/web/app/views/api_recordings/show.rabl b/web/app/views/api_recordings/show.rabl index f3c817b07..a717a6bfb 100644 --- a/web/app/views/api_recordings/show.rabl +++ b/web/app/views/api_recordings/show.rabl @@ -1,6 +1,6 @@ object @recording -attributes :id, :band, :created_at, :duration, :comment_count, :like_count, :play_count, :when_will_be_discarded? +attributes :id, :band, :created_at, :duration, :comment_count, :like_count, :play_count, :when_will_be_discarded?, :jam_track_id, :jam_track_initiator_id node :mix do |recording| if recording.mix @@ -12,6 +12,13 @@ node :mix do |recording| end end +child(:jam_track => :jam_track) { + attributes :id + + node :jmep do |jam_track| + jam_track.jmep_json ? JSON.parse(jam_track.jmep_json) : nil + end +} child(:band => :band) { attributes :id, :name, :location, :photo_url @@ -33,6 +40,15 @@ child(:recorded_backing_tracks => :recorded_backing_tracks) { end } +child(:recorded_jam_track_tracks => :recorded_jam_track_tracks) { + node do |recorded_jam_track_track| + { + id: recorded_jam_track_track.jam_track_track.id, + timeline: recorded_jam_track_track.timeline ? JSON.parse(recorded_jam_track_track.timeline) : [] + } + end +} + child(:comments => :comments) { attributes :comment, :created_at diff --git a/web/app/views/clients/_download_jamtrack_templates.html.slim b/web/app/views/clients/_download_jamtrack_templates.html.slim index bf5831cb4..fef7f2cd8 100644 --- a/web/app/views/clients/_download_jamtrack_templates.html.slim +++ b/web/app/views/clients/_download_jamtrack_templates.html.slim @@ -1,2 +1,86 @@ script type="text/template" id='template-download-jamtrack' .download-jamtrack + .state + +script type="text/template" id="template-download-jamtrack-state-no-client" + .state-no-client + .large.hidden + .msg + | To play your JamTrack, launch the JamKazam application and open the JamTrack while in a session. + .small.hidden + .msg + | {{data.name}} (launch client) + +script type="text/template" id="template-download-jamtrack-state-synchronized" + .state-synchronized + .large.hidden + .msg + | Your JamTrack is on your system and ready to play. + .small.hidden + .msg + | {{data.name}} (done) + +script type="text/template" id="template-download-jamtrack-state-packaging" + .state-packaging + .large.hidden + .msg + | Your JamTrack is currently being created on the JamKazam server. + .spinner-large + .small.hidden + .msg + | {{data.name}} (packaging) + .spinner-small + +script type="text/template" id="template-download-jamtrack-state-downloading" + .state-downloading + .large.hidden + .msg + | Your JamTrack is currently being downloaded. + .spinner-large + .small.hidden + .msg + | {{data.name}} (downloading) + .spinner-small + +script type="text/template" id="template-download-jamtrack-state-keying" + .state-keying + .large.hidden + .msg + | Your JamTrack is being authenticated. + .spinner-large + .small.hidden + .msg + | {{data.name}} (keying) + .spinner-small + +script type="text/template" id="template-download-jamtrack-state-initial" + .state-initial + .large.hidden + .msg + | Initializing JamTrack... + .spinner-large + .small.hidden + .msg + | {{data.name}} (initializing) + .spinner-small + +script type="text/template" id="template-download-jamtrack-state-quiet" + .state-quiet + .large.hidden + .msg + .small.hidden + .msg + | {{data.name}} (pending) + +script type="text/template" id="template-download-jamtrack-state-errored" + .state-errored + .large.hidden + .msg + .retry + a.button-orange.retry-button RETRY + .small.hidden + .msg-holder + .msg + a.button-orange.retry-button RETRY + .errormsg + .retry diff --git a/web/app/views/clients/_help.html.slim b/web/app/views/clients/_help.html.slim index 7fe11020b..60646b6ec 100644 --- a/web/app/views/clients/_help.html.slim +++ b/web/app/views/clients/_help.html.slim @@ -199,6 +199,9 @@ script type="text/template" id="template-help-media-controls-disabled" | Only the person who opened the recording can control the volume levels. | {% } %} +script type="text/template" id="template-help-jamtrack-controls-disabled" + | During a recording, volume and mute controls for JamTracks are disabled. So, get the session volume levels right before starting the recording. + script type="text/template" id="template-help-volume-media-mixers" | Audio files only expose both master and personal mix controls, so any change here will also affect everyone in the session. diff --git a/web/app/views/clients/_order.html.slim b/web/app/views/clients/_order.html.slim index 241af7217..ffab91d7a 100644 --- a/web/app/views/clients/_order.html.slim +++ b/web/app/views/clients/_order.html.slim @@ -178,8 +178,15 @@ div layout="screen" layout-id="order" id="orderScreen" class="screen secondary" br .thanks-detail We'll send you an email confirming your order shortly. br - .thanks-detail If you purchased any JamTracks, the next time you run the JamKazam application, your JamTracks will automatically be downloaded to the app, and you will receive a notification when the download is complete. - + .thanks-detail.jam-tracks-in-browser.hidden + | To play your purchased JamTrack, launch the JamKazam application and open the JamTrack while in a session. + .thanks-detail.purchased-jam-track.hidden + h2.purchased-jam-track-header Downloading Your Purchased JamTracks + span Each JamTrack will be downloaded sequentially. + br + span.notice Note that you do not have to wait for this to complete in order to use your JamTrack later. + br.clear + ul.purchased-list @@ -276,4 +283,7 @@ script type='text/template' id='template-order-content' span and ' a href="http://www.jamkazam.com/corp/returns" returns policy - span . \ No newline at end of file + span . + +script type='text/template' id='template-purchased-jam-track' + li data-jam-track-id="{{data.jam_track_id}}" \ No newline at end of file diff --git a/web/app/views/clients/_session.html.slim b/web/app/views/clients/_session.html.slim index 7a79b81fd..49a5b2cb0 100644 --- a/web/app/views/clients/_session.html.slim +++ b/web/app/views/clients/_session.html.slim @@ -71,7 +71,7 @@ | other audio .session-recording-name-wrapper .session-recording-name.left - | (No recording loaded) + | (No audio loaded) .session-add.right a#close-playback-recording[href="#"] = image_tag "content/icon_close.png", {:width => 18, :height => 20, :align => "texttop"} diff --git a/web/app/views/clients/index.html.erb b/web/app/views/clients/index.html.erb index 94af76504..7586d0e9b 100644 --- a/web/app/views/clients/index.html.erb +++ b/web/app/views/clients/index.html.erb @@ -64,6 +64,7 @@ <%= render "overlay_small" %> <%= render "listenBroadcast" %> <%= render "sync_viewer_templates" %> +<%= render "download_jamtrack_templates" %> <%= render "help" %> <%= render 'dialogs/dialogs' %>
diff --git a/web/app/views/dialogs/_openJamTrackDialog.html.slim b/web/app/views/dialogs/_openJamTrackDialog.html.slim index 18ca215a7..2e5ec5f7c 100644 --- a/web/app/views/dialogs/_openJamTrackDialog.html.slim +++ b/web/app/views/dialogs/_openJamTrackDialog.html.slim @@ -31,7 +31,7 @@ a href='/client#/jamtrack' rel="external" | Shop for JamTracks .right - a href="#" class="button-grey" layout-action="close" + a href="#" class="button-grey" layout-action="cancel" | CANCEL diff --git a/web/app/views/spikes/download_jam_track.html.slim b/web/app/views/spikes/download_jam_track.html.slim new file mode 100644 index 000000000..ade3744b5 --- /dev/null +++ b/web/app/views/spikes/download_jam_track.html.slim @@ -0,0 +1,44 @@ += javascript_include_tag "download_jamtrack" += render "clients/download_jamtrack_templates" += stylesheet_link_tag "client/downloadJamTrack" + +- provide(:title, 'Download Jam Track Widget') + +.content-wrapper + h2 Jam Track State Widget + + h3 Possible States + ul + li synchronized + li no_client + li packaging + li downloading + li keying + li initial + li errored + #widget + +javascript: + var initialized = false; + $(document).on('JAMKAZAM_READY', function(e, data) { + window.JK.JamServer.get$Server().on(window.JK.EVENTS.CONNECTION_UP, function() { + if(initialized) { + return; + } + initialized = true + + setTimeout(function() { + window.downloadJamTrack = new JK.DownloadJamTrack(data.app, {id: gon.jamTrackId, jam_track_right_id: gon.jamTrackRightId, name: 'Back in Black'}, gon.size) + downloadJamTrack.init() + $('#widget').append(window.downloadJamTrack.root) + + if (gon.switchState == 'errored') { + downloadJamTrack.transitionError("package-error", "The server failed to create your package.") + } + else if (gon.switchState) { + downloadJamTrack.transition(downloadJamTrack.states[gon.switchState]); + } + }, 1) + + }) + }) \ No newline at end of file diff --git a/web/config/application.rb b/web/config/application.rb index 687b93ff4..2ec535647 100644 --- a/web/config/application.rb +++ b/web/config/application.rb @@ -209,6 +209,8 @@ if defined?(Bundler) # Location of jamtracks python tool: config.jamtracks_dir = ENV['JAMTRACKS_DIR'] || File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "jamtracks")) + config.jmep_dir = ENV['JMEP_DIR'] || File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "jmep")) + # amount of time before we think packaging job is broken config.signing_job_run_max_time = 60 # 1 minute # amount of time before we think the queue is stuck @@ -310,6 +312,7 @@ if defined?(Bundler) config.influxdb_port = 8086 config.influxdb_ignored_environments = ENV["INFLUXDB_ENABLED"] == '1' ? ['test', 'cucumber'] : ['test', 'cucumber', 'development'] + config.allow_spikes = false config.show_jamblaster_notice = true config.show_jamblaster_kickstarter_link = true config.metronome_available = true diff --git a/web/config/routes.rb b/web/config/routes.rb index cfefcc260..00fd25f38 100644 --- a/web/config/routes.rb +++ b/web/config/routes.rb @@ -81,21 +81,20 @@ SampleApp::Application.routes.draw do match '/endorse/:id/:service', to: 'users#endorse', :as => 'endorse' - # temporarily allow for debugging--only allows admini n - match '/listen_in', to: 'spikes#listen_in' - # embed resque-web if this is development mode - if Rails.env == "development" + if Rails.env == "development" || Rails.application.config.allow_spikes require 'resque/server' require 'resque-retry' require 'resque-retry/server' mount Resque::Server.new, :at => "/resque" if Rails.env == "development" # route to spike controller (proof-of-concepts) + match '/listen_in', to: 'spikes#listen_in' match '/facebook_invite', to: 'spikes#facebook_invite' match '/launch_app', to: 'spikes#launch_app' match '/websocket', to: 'spikes#websocket' match '/test_subscription', to: 'spikes#subscription' + match '/widgets/download_jam_track', to: 'spikes#download_jam_track' match '/site_validate', to: 'spikes#site_validate' match '/recording_source', to: 'spikes#recording_source' @@ -210,7 +209,7 @@ SampleApp::Application.routes.draw do match '/jamtracks/downloads' => 'api_jam_tracks#downloads', :via => :get, :as => 'api_jam_tracks_downloads' match '/jamtracks/download/:id' => 'api_jam_tracks#download', :via => :get, :as => 'api_jam_tracks_download' match '/jamtracks/enqueue/:id' => 'api_jam_tracks#enqueue', :via => :post, :as => 'api_jam_tracks_enqueue' - match '/jamtracks/show/:id' => 'api_jam_tracks#show_jam_track_right', :via => :get, :as => 'api_jam_tracks_show_right' + match '/jamtracks/rights/:id' => 'api_jam_tracks#show_jam_track_right', :via => :get, :as => 'api_jam_tracks_show_right' match '/jamtracks/keys' => 'api_jam_tracks#keys', :via => :post, :as => 'api_jam_tracks_keys' # Shopping carts @@ -452,6 +451,7 @@ SampleApp::Application.routes.draw do match '/recordings/:id/comments' => 'api_recordings#add_comment', :via => :post, :as => 'api_recordings_add_comment' match '/recordings/:id/likes' => 'api_recordings#add_like', :via => :post, :as => 'api_recordings_add_like' match '/recordings/:id/discard' => 'api_recordings#discard', :via => :post, :as => 'api_recordings_discard' + match '/recordings/:id/timeline' => 'api_recordings#add_timeline', :via => :post, :as => 'api_recordings_timeline' # Recordings - recorded_tracks match '/recordings/:id/tracks/:track_id' => 'api_recordings#show_recorded_track', :via => :get, :as => 'api_recordings_show_recorded_track' diff --git a/web/lib/tasks/sample_data.rake b/web/lib/tasks/sample_data.rake index 853194455..c19d46dea 100644 --- a/web/lib/tasks/sample_data.rake +++ b/web/lib/tasks/sample_data.rake @@ -1,6 +1,6 @@ require 'factory_girl' require 'open-uri' - +#require './spec/factories.rb' # useful when run on a server namespace :db do desc "Add a simple one track recording to the database" diff --git a/web/spec/controllers/api_jam_tracks_controller_spec.rb b/web/spec/controllers/api_jam_tracks_controller_spec.rb index 1ff6058df..5ec41f318 100644 --- a/web/spec/controllers/api_jam_tracks_controller_spec.rb +++ b/web/spec/controllers/api_jam_tracks_controller_spec.rb @@ -102,6 +102,7 @@ describe ApiJamTracksController do response.should be_success json = JSON.parse(response.body) json['jamtracks'].length.should eq(1) + json['jamtracks'][0]['jam_track_right_id'].should eq(right.id) json['next'].should be_nil end end @@ -196,18 +197,18 @@ describe ApiJamTracksController do describe "keys" do it "empty" do - get :keys, jamtracks: [] + get :keys, jamtracks: {tracks: []} response.status.should == 200 json = JSON.parse(response.body) json.length == 0 end it "track with no rights" do - get :keys, jamtracks: [@jam_track.id] + get :keys, jamtracks: { tracks: [@jam_track.id] } response.status.should == 200 json = JSON.parse(response.body) json.length.should == 1 - json[0]['id'].should == @jam_track.id + json[0]['id'].should == @jam_track.id.to_s json[0]['private'].should be_nil json[0]['error'].should == 'not_purchased' end @@ -216,32 +217,32 @@ describe ApiJamTracksController do right = FactoryGirl.create(:jam_track_right, user: @user, private_key: nil, jam_track: @jam_track) - get :keys, jamtracks: [@jam_track.id] + get :keys, jamtracks: { tracks: [@jam_track.id] } response.status.should == 200 json = JSON.parse(response.body) json.length.should == 1 - json[0]['id'].should == @jam_track.id + json[0]['id'].should == @jam_track.id.to_s json[0]['private'].should be_nil json[0]['error'].should == 'no_key' end it "track with key" do right = FactoryGirl.create(:jam_track_right, user: @user, private_key: 'abc', jam_track: @jam_track) - get :keys, jamtracks: [@jam_track.id] + get :keys, jamtracks: { tracks: [@jam_track.id] } response.status.should == 200 json = JSON.parse(response.body) json.length.should == 1 - json[0]['id'].should == @jam_track.id + json[0]['id'].should == @jam_track.id.to_s json[0]['private'].should eq('abc') json[0]['error'].should be_nil end it "non-owning user asking for a real track" do right = FactoryGirl.create(:jam_track_right, user: FactoryGirl.create(:user), private_key: 'abc', jam_track: @jam_track) - get :keys, jamtracks: [@jam_track.id] + get :keys, jamtracks: { tracks: [@jam_track.id] } response.status.should == 200 json = JSON.parse(response.body) - json[0]['id'].should == @jam_track.id + json[0]['id'].should == @jam_track.id.to_s json[0]['private'].should be_nil json[0]['error'].should == 'not_purchased' end diff --git a/web/spec/controllers/api_recordings_controller_spec.rb b/web/spec/controllers/api_recordings_controller_spec.rb index f09a6130d..ac54f6272 100644 --- a/web/spec/controllers/api_recordings_controller_spec.rb +++ b/web/spec/controllers/api_recordings_controller_spec.rb @@ -3,217 +3,251 @@ require 'spec_helper' describe ApiRecordingsController do render_views + describe "recording with backing track" do - before(:each) do - @user = FactoryGirl.create(:user) - @instrument = FactoryGirl.create(:instrument, :description => 'a great instrument') - @music_session = FactoryGirl.create(:active_music_session, :creator => @user, :musician_access => true) - @connection = FactoryGirl.create(:connection, :user => @user, :music_session => @music_session) - @track = FactoryGirl.create(:track, :connection => @connection, :instrument => @instrument) - @backing_track = FactoryGirl.create(:backing_track, :connection => @connection) - controller.current_user = @user - end - - describe "start" do - it "should work" do - post :start, { :format => 'json', :music_session_id => @music_session.id } - response.should be_success - response_body = JSON.parse(response.body) - response_body['id'].should_not be_nil - recording = Recording.find(response_body['id']) + before(:each) do + @user = FactoryGirl.create(:user) + @instrument = FactoryGirl.create(:instrument, :description => 'a great instrument') + @music_session = FactoryGirl.create(:active_music_session, :creator => @user, :musician_access => true) + @connection = FactoryGirl.create(:connection, :user => @user, :music_session => @music_session) + @track = FactoryGirl.create(:track, :connection => @connection, :instrument => @instrument) + @backing_track = FactoryGirl.create(:backing_track, :connection => @connection) + controller.current_user = @user end - it "should not allow multiple starts" do - post :start, { :format => 'json', :music_session_id => @music_session.id } - post :start, { :format => 'json', :music_session_id => @music_session.id } - response.status.should == 422 - response_body = JSON.parse(response.body) - response_body["errors"]["music_session"][0].should == ValidationMessages::ALREADY_BEING_RECORDED + describe "start" do + it "should work" do + post :start, {:format => 'json', :music_session_id => @music_session.id} + response.should be_success + response_body = JSON.parse(response.body) + response_body['id'].should_not be_nil + recording = Recording.find(response_body['id']) + end + + it "should not allow multiple starts" do + post :start, {:format => 'json', :music_session_id => @music_session.id} + post :start, {:format => 'json', :music_session_id => @music_session.id} + response.status.should == 422 + response_body = JSON.parse(response.body) + response_body["errors"]["music_session"][0].should == ValidationMessages::ALREADY_BEING_RECORDED + end + + it "should not allow start while playback ongoing" do + recording = Recording.start(@music_session, @user) + recording.stop + recording.reload + claimed_recording = recording.claim(@user, "name", "description", Genre.first, true) + @music_session.claimed_recording_start(@user, claimed_recording) + @music_session.errors.any?.should be_false + post :start, {:format => 'json', :music_session_id => @music_session.id} + response.status.should == 422 + response_body = JSON.parse(response.body) + response_body["errors"]["music_session"][0].should == ValidationMessages::ALREADY_PLAYBACK_RECORDING + end + + it "should not allow start by somebody not in the music session" do + user2 = FactoryGirl.create(:user) + controller.current_user = user2 + post :start, {:format => 'json', :music_session_id => @music_session.id} + response.status.should == 403 + end end - it "should not allow start while playback ongoing" do - recording = Recording.start(@music_session, @user) - recording.stop - recording.reload - claimed_recording = recording.claim(@user, "name", "description", Genre.first, true) - @music_session.claimed_recording_start(@user, claimed_recording) - @music_session.errors.any?.should be_false - post :start, { :format => 'json', :music_session_id => @music_session.id } - response.status.should == 422 - response_body = JSON.parse(response.body) - response_body["errors"]["music_session"][0].should == ValidationMessages::ALREADY_PLAYBACK_RECORDING + describe "get" do + it "should work" do + post :start, {:format => 'json', :music_session_id => @music_session.id} + response.should be_success + response_body = JSON.parse(response.body) + response_body['id'].should_not be_nil + recordingId = response_body['id'] + get :show, {:format => 'json', :id => recordingId} + response.should be_success + response_body = JSON.parse(response.body) + response_body['id'].should == recordingId + end + end - it "should not allow start by somebody not in the music session" do - user2 = FactoryGirl.create(:user) - controller.current_user = user2 - post :start, { :format => 'json', :music_session_id => @music_session.id } - response.status.should == 403 + describe "stop" do + it "should work" do + post :start, {:format => 'json', :music_session_id => @music_session.id} + response_body = JSON.parse(response.body) + recording = Recording.find(response_body['id']) + post :stop, {:format => 'json', :id => recording.id} + response.should be_success + response_body = JSON.parse(response.body) + response_body['id'].should_not be_nil + Recording.find(response_body['id']).id.should == recording.id + end + + it "should not allow stop on a session not being recorded" do + post :start, {:format => 'json', :music_session_id => @music_session.id} + response_body = JSON.parse(response.body) + recording = Recording.find(response_body['id']) + post :stop, {:format => 'json', :id => recording.id} + post :stop, {:format => 'json', :id => recording.id} + response.status.should == 422 + response_body = JSON.parse(response.body) + end + + it "should not allow stop on a session requested by a different member" do + + post :start, {:format => 'json', :music_session_id => @music_session.id} + response_body = JSON.parse(response.body) + recording = Recording.find(response_body['id']) + user2 = FactoryGirl.create(:user) + controller.current_user = user2 + post :stop, {:format => 'json', :id => recording.id} + response.status.should == 403 + end + end + + describe "download track" do + let(:mix) { FactoryGirl.create(:mix) } + + it "should only allow a user to download a track if they have claimed the recording" do + post :start, {:format => 'json', :music_session_id => @music_session.id} + response_body = JSON.parse(response.body) + recording = Recording.find(response_body['id']) + post :stop, {:format => 'json', :id => recording.id} + response.should be_success + end + + + it "is possible" do + mix.touch + recorded_track = mix.recording.recorded_tracks[0] + controller.current_user = mix.recording.owner + get :download, {id: recorded_track.recording.id, track_id: recorded_track.client_track_id} + response.status.should == 302 + + recorded_track.reload + recorded_track.download_count.should == 1 + + get :download, {id: recorded_track.recording.id, track_id: recorded_track.client_track_id} + response.status.should == 302 + + recorded_track.reload + recorded_track.download_count.should == 2 + end + + + it "prevents download after limit is reached" do + mix.touch + recorded_track = mix.recording.recorded_tracks[0] + recorded_track.download_count = APP_CONFIG.max_audio_downloads + recorded_track.save! + controller.current_user = recorded_track.user + get :download, {format: 'json', id: recorded_track.recording.id, track_id: recorded_track.client_track_id} + response.status.should == 404 + JSON.parse(response.body, symbolize_names: true)[:message].should == "download limit surpassed" + end + + + it "lets admins surpass limit" do + mix.touch + recorded_track = mix.recording.recorded_tracks[0] + recorded_track.download_count = APP_CONFIG.max_audio_downloads + recorded_track.save! + recorded_track.user.admin = true + recorded_track.user.save! + + controller.current_user = recorded_track.user + get :download, {format: 'json', id: recorded_track.recording.id, track_id: recorded_track.client_track_id} + response.status.should == 302 + recorded_track.reload + recorded_track.download_count.should == 101 + end + end + + describe "download backing track" do + let(:mix) { FactoryGirl.create(:mix) } + + it "should only allow a user to download a track if they have claimed the recording" do + post :start, {:format => 'json', :music_session_id => @music_session.id} + response_body = JSON.parse(response.body) + recording = Recording.find(response_body['id']) + post :stop, {:format => 'json', :id => recording.id} + response.should be_success + end + + + it "is possible" do + mix.touch + recorded_track = mix.recording.recorded_tracks[0] + controller.current_user = mix.recording.owner + get :download, {id: recorded_track.recording.id, track_id: recorded_track.client_track_id} + response.status.should == 302 + + recorded_track.reload + recorded_track.download_count.should == 1 + + get :download, {id: recorded_track.recording.id, track_id: recorded_track.client_track_id} + response.status.should == 302 + + recorded_track.reload + recorded_track.download_count.should == 2 + end + + + it "prevents download after limit is reached" do + mix.touch + recorded_track = mix.recording.recorded_tracks[0] + recorded_track.download_count = APP_CONFIG.max_audio_downloads + recorded_track.save! + controller.current_user = recorded_track.user + get :download, {format: 'json', id: recorded_track.recording.id, track_id: recorded_track.client_track_id} + response.status.should == 404 + JSON.parse(response.body, symbolize_names: true)[:message].should == "download limit surpassed" + end + + + it "lets admins surpass limit" do + mix.touch + recorded_track = mix.recording.recorded_tracks[0] + recorded_track.download_count = APP_CONFIG.max_audio_downloads + recorded_track.save! + recorded_track.user.admin = true + recorded_track.user.save! + + controller.current_user = recorded_track.user + get :download, {format: 'json', id: recorded_track.recording.id, track_id: recorded_track.client_track_id} + response.status.should == 302 + recorded_track.reload + recorded_track.download_count.should == 101 + end end end - describe "get" do - it "should work" do - post :start, { :format => 'json', :music_session_id => @music_session.id } - response.should be_success - response_body = JSON.parse(response.body) - response_body['id'].should_not be_nil - recordingId = response_body['id'] - get :show, {:format => 'json', :id => recordingId} - response.should be_success - response_body = JSON.parse(response.body) - response_body['id'].should == recordingId + describe "recording with jam track" do + + before(:each) do + @user = FactoryGirl.create(:user) + @instrument = FactoryGirl.create(:instrument, :description => 'a great instrument') + @music_session = FactoryGirl.create(:active_music_session, :creator => @user, :musician_access => true) + @connection = FactoryGirl.create(:connection, :user => @user, :music_session => @music_session) + @track = FactoryGirl.create(:track, :connection => @connection, :instrument => @instrument) + @jam_track = FactoryGirl.create(:jam_track) + + # make sure the jam track is opened + @music_session.jam_track = @jam_track + @music_session.jam_track_initiator = @user + @music_session.save! + controller.current_user = @user end - end - - describe "stop" do - it "should work" do - post :start, { :format => 'json', :music_session_id => @music_session.id } - response_body = JSON.parse(response.body) - recording = Recording.find(response_body['id']) - post :stop, { :format => 'json', :id => recording.id } - response.should be_success - response_body = JSON.parse(response.body) - response_body['id'].should_not be_nil - Recording.find(response_body['id']).id.should == recording.id - end - - it "should not allow stop on a session not being recorded" do - post :start, { :format => 'json', :music_session_id => @music_session.id } - response_body = JSON.parse(response.body) - recording = Recording.find(response_body['id']) - post :stop, { :format => 'json', :id => recording.id } - post :stop, { :format => 'json', :id => recording.id } - response.status.should == 422 - response_body = JSON.parse(response.body) - end - - it "should not allow stop on a session requested by a different member" do - - post :start, { :format => 'json', :music_session_id => @music_session.id } - response_body = JSON.parse(response.body) - recording = Recording.find(response_body['id']) - user2 = FactoryGirl.create(:user) - controller.current_user = user2 - post :stop, { :format => 'json', :id => recording.id } - response.status.should == 403 - end - end - - describe "download track" do - let(:mix) { FactoryGirl.create(:mix) } - - it "should only allow a user to download a track if they have claimed the recording" do - post :start, { :format => 'json', :music_session_id => @music_session.id } - response_body = JSON.parse(response.body) - recording = Recording.find(response_body['id']) - post :stop, { :format => 'json', :id => recording.id } - response.should be_success - end - - - it "is possible" do - mix.touch - recorded_track = mix.recording.recorded_tracks[0] - controller.current_user = mix.recording.owner - get :download, {id: recorded_track.recording.id, track_id: recorded_track.client_track_id} - response.status.should == 302 - - recorded_track.reload - recorded_track.download_count.should == 1 - - get :download, {id: recorded_track.recording.id, track_id: recorded_track.client_track_id} - response.status.should == 302 - - recorded_track.reload - recorded_track.download_count.should == 2 - end - - - it "prevents download after limit is reached" do - mix.touch - recorded_track = mix.recording.recorded_tracks[0] - recorded_track.download_count = APP_CONFIG.max_audio_downloads - recorded_track.save! - controller.current_user = recorded_track.user - get :download, {format:'json', id: recorded_track.recording.id, track_id: recorded_track.client_track_id} - response.status.should == 404 - JSON.parse(response.body, symbolize_names: true)[:message].should == "download limit surpassed" - end - - - it "lets admins surpass limit" do - mix.touch - recorded_track = mix.recording.recorded_tracks[0] - recorded_track.download_count = APP_CONFIG.max_audio_downloads - recorded_track.save! - recorded_track.user.admin = true - recorded_track.user.save! - - controller.current_user = recorded_track.user - get :download, {format:'json', id: recorded_track.recording.id, track_id: recorded_track.client_track_id} - response.status.should == 302 - recorded_track.reload - recorded_track.download_count.should == 101 - end - end - - describe "download backing track" do - let(:mix) { FactoryGirl.create(:mix) } - - it "should only allow a user to download a track if they have claimed the recording" do - post :start, { :format => 'json', :music_session_id => @music_session.id } - response_body = JSON.parse(response.body) - recording = Recording.find(response_body['id']) - post :stop, { :format => 'json', :id => recording.id } - response.should be_success - end - - - it "is possible" do - mix.touch - recorded_track = mix.recording.recorded_tracks[0] - controller.current_user = mix.recording.owner - get :download, {id: recorded_track.recording.id, track_id: recorded_track.client_track_id} - response.status.should == 302 - - recorded_track.reload - recorded_track.download_count.should == 1 - - get :download, {id: recorded_track.recording.id, track_id: recorded_track.client_track_id} - response.status.should == 302 - - recorded_track.reload - recorded_track.download_count.should == 2 - end - - - it "prevents download after limit is reached" do - mix.touch - recorded_track = mix.recording.recorded_tracks[0] - recorded_track.download_count = APP_CONFIG.max_audio_downloads - recorded_track.save! - controller.current_user = recorded_track.user - get :download, {format:'json', id: recorded_track.recording.id, track_id: recorded_track.client_track_id} - response.status.should == 404 - JSON.parse(response.body, symbolize_names: true)[:message].should == "download limit surpassed" - end - - - it "lets admins surpass limit" do - mix.touch - recorded_track = mix.recording.recorded_tracks[0] - recorded_track.download_count = APP_CONFIG.max_audio_downloads - recorded_track.save! - recorded_track.user.admin = true - recorded_track.user.save! - - controller.current_user = recorded_track.user - get :download, {format:'json', id: recorded_track.recording.id, track_id: recorded_track.client_track_id} - response.status.should == 302 - recorded_track.reload - recorded_track.download_count.should == 101 + describe "start" do + it "should work" do + post :start, {:format => 'json', :music_session_id => @music_session.id} + response.should be_success + response_body = JSON.parse(response.body) + response_body['id'].should_not be_nil + recording = Recording.find(response_body['id']) + recorded_jam_track_track = response_body["recorded_jam_track_tracks"][0] + recorded_jam_track_track["id"].should eq(@jam_track.jam_track_tracks[0].id) + recorded_jam_track_track["timeline"].should eq([]) + end end end end + diff --git a/web/spec/javascripts/download_jamtrack_spec.js.coffee b/web/spec/javascripts/download_jamtrack_spec.js.coffee index b98150477..4c358b324 100644 --- a/web/spec/javascripts/download_jamtrack_spec.js.coffee +++ b/web/spec/javascripts/download_jamtrack_spec.js.coffee @@ -1,16 +1,310 @@ describe "DownloadJamTrack", -> beforeEach -> - this.fixtures = fixture.load("downoadJamTrack.html", "user_sync_track1.json"); # append these fixtures which were already cached - this.server = sinon.fakeServer.create(); + this.fixtures = fixture.load("downloadJamTrack.html"); # append these fixtures which were already cached window.jamClient = sinon.stub() - this.downloadJamTrack = new JK.DownloadJamTrack() - this.downloadJamTrack.init() - $('body').append(this.downloadJamTrack.root) + this.app = sinon.stub() + this.jamTrackId = '1' + this.jamTrack = {id: this.jamTrackId, jam_track_right_id: '1', name: 'Back in Black', version:'1'} + window.gon = {} + window.JK.JamServer = {} + window.stats = {} + @statsSpy = window.stats.write = sinon.spy() + window.JK.JamServer.send = sinon.stub(); # attempts to subscribe to the socket will need this afterEach -> - this.server.restore(); + window.stats.write.reset() + + describe "normal browser", -> + + beforeEach -> + window.gon.isNativeClient = false + @showNoClientSpy = sinon.spy(JK.DownloadJamTrack.prototype, 'showNoClient') + @downloadJamTrack = new JK.DownloadJamTrack(@app, @jamTrack) + $('body').append(this.downloadJamTrack.root) + + afterEach -> + @showNoClientSpy.restore() + + it "switches to 'no client' correctly", -> + window.jamClient.JamTrackGetTrackDetail = sinon.stub() + window.jamClient.InvalidateJamTrack = sinon.stub() + #window.jamClient.JamTrackGetTrackDetail.returns({'key_state' : 'ready'}) + + @downloadJamTrack.init(); + + expect(window.jamClient.JamTrackGetTrackDetail.callCount).toBe(0) + expect(@showNoClientSpy.calledOnce).toBe(true) + expect(@downloadJamTrack.stateHolder.find('.state-no-client')).toHaveLength(1) + expect(@statsSpy.calledOnce).toBe(true) + + describe "client", -> + + beforeEach -> + window.gon.isNativeClient = true + + describe "already synchronized", -> + beforeEach -> + @showSynchronizedSpy = sinon.spy(JK.DownloadJamTrack.prototype, 'showSynchronized') + @downloadJamTrack = new JK.DownloadJamTrack(@app, @jamTrack) + $('body').append(@downloadJamTrack.root) + + afterEach -> + @showSynchronizedSpy.restore() + @downloadJamTrack.destroy() + + it "shows synchronized state", -> + window.jamClient.JamTrackGetTrackDetail = sinon.stub() + window.jamClient.JamTrackGetTrackDetail.returns({'key_state' : 'ready', 'version' : '1'}) + window.jamClient.InvalidateJamTrack = sinon.stub() + + @downloadJamTrack.init(); + + expect(window.jamClient.JamTrackGetTrackDetail.callCount).toBe(1) + expect(window.jamClient.InvalidateJamTrack.callCount).toBe(0) + expect(@showSynchronizedSpy.calledOnce).toBe(true) + expect(@downloadJamTrack.transitionTimer).toBe(null) + expect(@downloadJamTrack.downloadTimer).toBe(null) + expect(@statsSpy.calledOnce).toBe(true) + + expect(@downloadJamTrack.stateHolder.find('.state-synchronized')).toHaveLength(1) + + describe "pending", -> + beforeEach -> + window.jamClient.JamTrackKeysRequest = sinon.stub() + @showSynchronizedSpy = sinon.spy(JK.DownloadJamTrack.prototype, 'showSynchronized') + @showErrorSpy = sinon.spy(JK.DownloadJamTrack.prototype, 'showError') + @showKeyingSpy = sinon.spy(JK.DownloadJamTrack.prototype, 'showKeying') + @downloadJamTrack = new JK.DownloadJamTrack(@app, @jamTrack) + @downloadJamTrack.states.keying.max_time = -1 # hurry up the test, instead of waiting 10 seconds + $('body').append(@downloadJamTrack.root) + + afterEach -> + @showSynchronizedSpy.restore() + @showErrorSpy.restore() + @showKeyingSpy.restore() + @downloadJamTrack.destroy() + + it "shows errored state due to timeout", -> + window.jamClient.JamTrackGetTrackDetail = sinon.stub() + window.jamClient.JamTrackGetTrackDetail.returns({'key_state' : 'pending', 'version' : '1'}) + window.jamClient.InvalidateJamTrack = sinon.stub() + + @downloadJamTrack.init(); + + expect(window.jamClient.JamTrackGetTrackDetail.callCount).toBe(1) + expect(@showKeyingSpy.calledOnce).toBe(true) + expect(@downloadJamTrack.states.keying.timer).toNotBe(null) + + @downloadJamTrack.stateIntervalCheck() + + expect(window.jamClient.JamTrackGetTrackDetail.callCount).toBe(2) + + expect(@showErrorSpy.calledOnce).toBe(true) + expect(@downloadJamTrack.states.keying.timer).toBe(null) + expect(@downloadJamTrack.stateHolder.find('.state-errored')).toHaveLength(1) + expect(@downloadJamTrack.stateHolder.find('.state-errored .msg')).toContainText('It took too long for the JamTrack to be keyed.') + expect(@statsSpy.calledOnce).toBe(true) + expect(@downloadJamTrack.attempts).toBe(1) + + # now simulate a retry attempt + @downloadJamTrack.stateHolder.find('.retry-button').trigger('click') + + # and verify that we are beginning a re-attempt + expect(window.jamClient.JamTrackGetTrackDetail.callCount).toBe(3) + expect(@showKeyingSpy.calledTwice).toBe(true) + expect(@downloadJamTrack.states.keying.timer).toNotBe(null) + expect(@downloadJamTrack.attempts).toBe(2) + + + it "shows synchronized", -> + window.jamClient.JamTrackGetTrackDetail = sinon.stub() + window.jamClient.JamTrackGetTrackDetail.returns({'key_state' : 'pending', 'version' : '1'}) + window.jamClient.InvalidateJamTrack = sinon.stub() + + @downloadJamTrack.init() + + expect(window.jamClient.JamTrackGetTrackDetail.callCount).toBe(1) + expect(@showKeyingSpy.calledOnce).toBe(true) + + # keying timer should be firing + expect(@downloadJamTrack.states.keying.timer).toNotBe(null) + + # say the keys have been fetched + window.jamClient.JamTrackGetTrackDetail.returns({'key_state' : 'ready', 'version' : '1'}) + window.jamClient.InvalidateJamTrack = sinon.stub() + + # then do a check + @downloadJamTrack.stateIntervalCheck() + + expect(@showSynchronizedSpy.calledOnce).toBe(true) + expect(@downloadJamTrack.states.keying.timer).toBe(null) + expect(@downloadJamTrack.stateHolder.find('.state-synchronized')).toHaveLength(1) + expect(@statsSpy.calledOnce).toBe(true) + + + + describe "JamTrack needs downloading", -> + beforeEach -> + window.jamClient.JamTrackDownload = sinon.stub() + @showSynchronizedSpy = sinon.spy(JK.DownloadJamTrack.prototype, 'showSynchronized') + @showErrorSpy = sinon.spy(JK.DownloadJamTrack.prototype, 'showError') + @showKeyingSpy = sinon.spy(JK.DownloadJamTrack.prototype, 'showKeying') + @showDownloadingSpy = sinon.spy(JK.DownloadJamTrack.prototype, 'showDownloading') + @showPackagingSpy = sinon.spy(JK.DownloadJamTrack.prototype, 'showPackaging') + @downloadJamTrack = new JK.DownloadJamTrack(@app, @jamTrack) + @downloadJamTrack.states.keying.max_time = -1 # hurry up the test, instead of waiting 10 seconds + $('body').append(@downloadJamTrack.root) + + afterEach -> + @showSynchronizedSpy.restore() + @showErrorSpy.restore() + @showKeyingSpy.restore() + @showDownloadingSpy.restore() + @showPackagingSpy.restore() + @downloadJamTrack.destroy() + + it "shows downloading for signed package", -> + window.jamClient.JamTrackKeysRequest = sinon.stub() + window.jamClient.JamTrackGetTrackDetail = sinon.stub() + window.jamClient.JamTrackGetTrackDetail.returns({'key_state' : 'unknown', 'version' : '1'}) + window.jamClient.InvalidateJamTrack = sinon.stub() + + spyOn(@downloadJamTrack.rest, 'getJamTrackRight').andCallFake((data) => + d = $.Deferred(); + d.resolve({signing_state: 'SIGNED'}); + d.promise(); + ) + + window.jamClient.JamTrackDownload = sinon.stub() + + @downloadJamTrack.init() + + expect(window.jamClient.JamTrackGetTrackDetail.callCount).toBe(1) + expect(window.jamClient.JamTrackDownload.callCount).toBe(1) + expect(@showDownloadingSpy.calledOnce).toBe(true) + expect(@downloadJamTrack.stateHolder.find('.state-downloading')).toHaveLength(1) + + eval(@downloadJamTrack.makeDownloadSuccessCallback() + '()') + + expect(@showKeyingSpy.calledOnce).toBe(true) + expect(@downloadJamTrack.stateHolder.find('.state-keying')).toHaveLength(1) + + # keying timer should be firing + expect(@downloadJamTrack.states.keying.timer).toNotBe(null) + + # say the keys have been fetched + window.jamClient.JamTrackGetTrackDetail.returns({'key_state' : 'ready', 'version' : '1'}) + window.jamClient.InvalidateJamTrack = sinon.stub() + + # check state again + @downloadJamTrack.stateIntervalCheck() + + # we should now be synchronized + expect(@showSynchronizedSpy.calledOnce).toBe(true) + expect(@downloadJamTrack.states.keying.timer).toBe(null) + expect(@downloadJamTrack.stateHolder.find('.state-synchronized')).toHaveLength(1) + expect(@statsSpy.calledOnce).toBe(true) + + it "is not yet packaged", -> + window.jamClient.JamTrackGetTrackDetail = sinon.stub() + window.jamClient.JamTrackGetTrackDetail.returns({'key_state' : 'unknown', 'version' : '1'}) + window.jamClient.InvalidateJamTrack = sinon.stub() + + spyOn(@downloadJamTrack.rest, 'getJamTrackRight').andCallFake((data) => + d = $.Deferred(); + d.resolve({signing_state: 'QUIET'}); + d.promise(); + ) + + spyOn(@downloadJamTrack.rest, 'enqueueJamTrack').andCallFake((data) => + d = $.Deferred(); + d.resolve({}); + d.promise(); + ) + + window.jamClient.JamTrackDownload = sinon.stub() + + @downloadJamTrack.init() + + expect(@downloadJamTrack.attemptedEnqueue).toBe(true) + expect(@downloadJamTrack.transitionTimer?).toBe(true) + + # simulate poke from server saying the track has been queued + @downloadJamTrack.onJamTrackRightEvent(null, {body: {signing_state: 'QUEUED'}}) + + # the frontend should be saying that it's packaging now + expect(@downloadJamTrack.transitionTimer?).toBe(true) + expect(@showPackagingSpy.calledOnce).toBe(true) + expect(@downloadJamTrack.stateHolder.find('.state-packaging')).toHaveLength(1) + + # simulate poke from server saying the track is currently signing + @downloadJamTrack.onJamTrackRightEvent(null, {body: {signing_state: 'SIGNING'}}) + + # the frontend still be saying it's packaging + expect(@downloadJamTrack.transitionTimer?).toBe(true) + expect(@showPackagingSpy.calledOnce).toBe(true) + + # simulate poke from server saying the track is signed + @downloadJamTrack.onJamTrackRightEvent(null, {body: {signing_state: 'SIGNED'}}) + + expect(@downloadJamTrack.transitionTimer?).toBe(false) + + # downloading has started; other test covers this, so we stop testing + expect(@showDownloadingSpy.calledOnce).toBe(true) + expect(@downloadJamTrack.stateHolder.find('.state-downloading')).toHaveLength(1) + + # since we haven't yet made it to a leave node, make sure we haven't reported a stat + expect(@statsSpy.callCount).toBe(0) + + it "queue time out when packaging", -> + window.jamClient.JamTrackGetTrackDetail = sinon.stub() + window.jamClient.JamTrackGetTrackDetail.returns({'key_state' : 'unknown', 'version' : '1'}) + window.jamClient.InvalidateJamTrack = sinon.stub() + + getJamTrackRightSpy = spyOn(@downloadJamTrack.rest, 'getJamTrackRight') + getJamTrackRightSpy.andCallFake((data) => + d = $.Deferred(); + d.resolve({signing_state: 'QUIET'}); + d.promise(); + ) + + spyOn(@downloadJamTrack.rest, 'enqueueJamTrack').andCallFake((data) => + d = $.Deferred(); + d.resolve({}); + d.promise(); + ) + + window.jamClient.JamTrackDownload = sinon.stub() + + @downloadJamTrack.init() + + expect(@downloadJamTrack.attemptedEnqueue).toBe(true) + expect(@downloadJamTrack.transitionTimer?).toBe(true) + + getJamTrackRightSpy.reset() + + # simulate timer running out, and server check resulting in QUEUED_TIMEOUT + getJamTrackRightSpy.andCallFake((data) => + d = $.Deferred(); + d.resolve({signing_state: 'QUEUED_TIMEOUT'}); + d.promise(); + ) + + @downloadJamTrack.transitionCheck() + + # the frontend should be saying that it's packaging now + expect(@downloadJamTrack.transitionTimer?).toBe(false) + expect(@showErrorSpy.calledOnce).toBe(true) + expect(@downloadJamTrack.stateHolder.find('.state-errored')).toHaveLength(1) + expect(@downloadJamTrack.stateHolder.find('.state-errored .msg')).toContainText('The server took too long to begin processing your JamTrack.') + + expect(@statsSpy.calledOnce).toBe(true) + + + + + + + - it "display state correctly", -> - $track = this.syncViewer.createTrack(this.track1) - this.syncViewer.updateTrackState($track) From c8ad1d38568a87d4a1bf3c7a3ad1e67772c2ff75 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Mon, 2 Mar 2015 13:30:45 -0600 Subject: [PATCH 13/82] VRFS-2805 : Use jquery.payment to make the credit card and verification more user-friendly. Also use jquery.payment for validation. --- web/Gemfile | 1 + web/app/assets/javascripts/application.js | 1 + web/app/assets/javascripts/order.js | 38 +++++++++++++++++------ web/app/views/clients/_order.html.slim | 17 +++++----- 4 files changed, 40 insertions(+), 17 deletions(-) diff --git a/web/Gemfile b/web/Gemfile index 0405c352f..c7cb8debf 100644 --- a/web/Gemfile +++ b/web/Gemfile @@ -56,6 +56,7 @@ gem 'aasm', '3.0.16' gem 'carrierwave', '0.9.0' gem 'carrierwave_direct' gem 'fog' +gem 'jquery-payment-rails' gem 'haml-rails' gem 'unf' #optional fog dependency gem 'devise', '3.3.0' #3.4.0 causes uninitialized constant ActionController::Metal (NameError) diff --git a/web/app/assets/javascripts/application.js b/web/app/assets/javascripts/application.js index 037c2c1b7..73a57daad 100644 --- a/web/app/assets/javascripts/application.js +++ b/web/app/assets/javascripts/application.js @@ -35,6 +35,7 @@ //= require jquery.browser //= require jquery.custom-protocol //= require jquery.exists +//= require jquery.payment //= require jstz //= require class //= require AAC_underscore diff --git a/web/app/assets/javascripts/order.js b/web/app/assets/javascripts/order.js index c5eb34e65..600c36d7a 100644 --- a/web/app/assets/javascripts/order.js +++ b/web/app/assets/javascripts/order.js @@ -36,6 +36,7 @@ function beforeShow() { beforeShowPaymentInfo(); resetJamTrackDownloadInfo(); + } function beforeShowPaymentInfo() { @@ -115,6 +116,7 @@ purchasedJamTrackIterator = 0; } + // TODO: Refactor: this function is long and fraught with many return points. function next(e) { e.preventDefault(); $("#order_error").addClass("hidden") @@ -308,10 +310,8 @@ $paymentMethod.find('#divCardName .error-text').remove(); $paymentMethod.find('#divCardName').addClass("error"); $paymentMethod.find('#card-name').after("
  • Card Name is required
"); - return false; - } - else { + } else { $paymentMethod.find('#divCardName').removeClass("error"); } @@ -319,21 +319,37 @@ $paymentMethod.find('#divCardNumber .error-text').remove(); $paymentMethod.find('#divCardNumber').addClass("error"); $paymentMethod.find('#card-number').after("
  • Card Number is required
"); - return false; - } - else { + } else if (!$.payment.validateCardNumber(card_number)) { + $paymentMethod.find('#divCardNumber .error-text').remove(); + $paymentMethod.find('#divCardNumber').addClass("error"); + $paymentMethod.find('#card-number').after("
  • Card Number is not valid
"); + return false; + } else { $paymentMethod.find('#divCardNumber').removeClass("error"); } + if (!$.payment.validateCardExpiry(card_month, card_year)) { + $paymentMethod.find('#divCardExpiry .error-text').remove(); + $paymentMethod.find('#divCardExpiry').addClass("error"); + $paymentMethod.find('#card-expiry').after("
  • Card Number is not valid
"); + } else { + $paymentMethod.find('#divCardExpiry').removeClass("error"); + } + if (!card_verify) { $paymentMethod.find('#divCardVerify .error-text').remove(); $paymentMethod.find('#divCardVerify').addClass("error"); - $paymentMethod.find('#card_verify').after("
  • Card Verification Value is required
"); + $paymentMethod.find('#card-verify').after("
  • Card Verification Value is required
"); return false; - } - else { + } else if(!$.payment.validateCardCVC(card_verify)) { + $paymentMethod.find('#divCardVerify .error-text').remove(); + $paymentMethod.find('#divCardVerify').addClass("error"); + $paymentMethod.find('#card-verify').after("
  • Card Verification Value is not valid.
"); + + return false; + } else { $paymentMethod.find('#divCardVerify').removeClass("error"); } @@ -606,6 +622,10 @@ radioClass: 'iradio_minimal', inheritClass: true }); + + // Use jquery.payment to limit characters and length: + $paymentMethod.find("#card-number").payment('formatCardNumber'); + $paymentMethod.find("#card-verify").payment('formatCardCVC'); } function initialize() { diff --git a/web/app/views/clients/_order.html.slim b/web/app/views/clients/_order.html.slim index ffab91d7a..f5ea0cc91 100644 --- a/web/app/views/clients/_order.html.slim +++ b/web/app/views/clients/_order.html.slim @@ -71,19 +71,20 @@ div layout="screen" layout-id="order" id="orderScreen" class="screen secondary" input type="text" id="card-name" .clearall #divCardNumber - .card-label + .card-label.mt10 label for="card-number" Card Number: - .card-value + .card-value.mt10 input type="text" id="card-number" .clearall - .card-label Expiration Date: - .card-value - = date_select("card", "expire-date", use_two_digit_numbers: true, discard_day: true, :start_year => Time.now.year, :end_year => Time.now.year + 18, :order => [:month, :day, :year], :default => -25.years.from_now, :html=>{:class => "account-profile-birthdate"} ) - .clearall + #divCardExpiry + .card-label.mt10 Expiration Date: + .card-value.mt10 + =date_select("card", "expire-date", use_two_digit_numbers: true, discard_day: true, :start_year => Time.now.year, :end_year => Time.now.year + 18, :order => [:month, :day, :year], :default => -25.years.from_now, :html=>{:class => "account-profile-birthdate", :id=>"card-expiry"} ) + .clearall #divCardVerify - .card-label + .card-label.mt10 label for="card-verify" Verification Value: - .card-value + .card-value.mt10 input type="text" id="card-verify" .clearall .card-label.mt15 From c8059e9c009d47d6031cfcf01d9fcae64463e170 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Tue, 3 Mar 2015 23:14:24 -0600 Subject: [PATCH 14/82] VRFS-2822 : Create a jam track landing page, a coffee script to control it, and styles. Link from main client view. Link TO current (browse) page. --- .../javascripts/jamtrack_landing.js.coffee | 93 +++++++++++++++++++ .../stylesheets/client/jamtrack.css.scss | 26 ++++++ web/app/views/clients/_home.html.slim | 2 +- .../views/clients/_jamtrack_landing.html.slim | 18 ++++ web/app/views/clients/index.html.erb | 4 + 5 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 web/app/assets/javascripts/jamtrack_landing.js.coffee create mode 100644 web/app/views/clients/_jamtrack_landing.html.slim diff --git a/web/app/assets/javascripts/jamtrack_landing.js.coffee b/web/app/assets/javascripts/jamtrack_landing.js.coffee new file mode 100644 index 000000000..32ded4f14 --- /dev/null +++ b/web/app/assets/javascripts/jamtrack_landing.js.coffee @@ -0,0 +1,93 @@ +$ = jQuery +context = window +context.JK ||= {}; + +context.JK.JamTrackLanding = class JamTrackLanding + constructor: (@app) -> + console.log "CONSTRUCTING", @app + @client = context.jamClient + @logger = context.JK.logger + @screen = null + + initialize:() => + screenBindings = + 'beforeShow': @beforeShow + 'afterShow': @afterShow + @app.bindScreen('jamtrackLanding', screenBindings); + @screen = $('#jamtrackLanding') + console.log "INITIALIZING", @screen + + beforeShow:() => + console.log "beforeShow" + + afterShow:() => + console.log "afterShow" + + # selectWebcam:(e, data) => + # console.log 'Selecting control: ', @webcamSelect + # device = @webcamSelect.val() + # if device != null and device != '' + # console.log 'Selecting webcam: ', device + + # selectResolution:() => + # console.log 'Selecting res control: ', @resolutionSelect + # @resolution = @resolutionSelect.val() + # if @resolution != null and @resolution != '' + # console.log 'Selecting res: ', @resolution + # @client.FTUESetVideoEncodeResolution @resolution + + # setVideoOff:() => + # if this.isVideoShared() + # @client.SessStopVideoSharing() + + # isVideoShared:() => + # @videoShared + + # setToggleState:() => + # available = @webcamSelect.find('option').size() > 0 + # shared = this.isVideoShared() + # console.log 'Setting toggle from : ', shared + # @toggleBtn.prop 'disabled', true + # @toggleBtn.prop 'disabled', !available + + # toggleWebcam:() => + # console.log 'Toggling webcam from: ', this.isVideoShared() + # if this.isVideoShared() + # @client.SessStopVideoSharing() + # @videoShared = false + # else + # @client.SessStartVideoSharing 0 + # @videoShared = true + + # loadWebCams:() => + # devices = @client.FTUEGetVideoCaptureDeviceNames() + # selectedDevice = @client.FTUECurrentSelectedVideoDevice() + # selectControl = @webcamSelect + # context._.each devices, (device) -> + # selected = device == selectedDevice + # option = $('