From 5ac69fc6469709edd7a1ebd925b9582b0037cd97 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Fri, 9 Jan 2015 16:11:04 -0600 Subject: [PATCH 01/23] * wip --- .../javascripts/download_jamtrack.js.coffee | 47 ++++++++++++++++++- web/app/assets/javascripts/fakeJamClient.js | 5 ++ web/app/assets/javascripts/jam_rest.js | 23 +++++++++ web/config/routes.rb | 2 +- .../download_jamtrack_spec.js.coffee | 5 +- 5 files changed, 76 insertions(+), 6 deletions(-) diff --git a/web/app/assets/javascripts/download_jamtrack.js.coffee b/web/app/assets/javascripts/download_jamtrack.js.coffee index 75e684bc6..937574403 100644 --- a/web/app/assets/javascripts/download_jamtrack.js.coffee +++ b/web/app/assets/javascripts/download_jamtrack.js.coffee @@ -2,10 +2,53 @@ $ = jQuery context = window context.JK ||= {}; -context.JK.DownloadJamTrack = class SyncViewer +# 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.DownloadJamTrack = class DownloadJamTrack constructor: (@app) -> @EVENTS = context.JK.EVENTS @rest = context.JK.Rest() + @jamTrackId = null + @states = { + synchronized: 'synchronized', + packaging: 'packaging', + downloading: 'downloading', + keying: 'keying', + initial: 'initial' + } + @state = @states.initial - init: () => + init: (jamTrackId) => + @jamTrackId = jamTrackId @root = $($('#template-download-jamtrack').html()) + + # after you've created the DownloadJamTrack widget, call synchronize which will begin ensuring that the jamtrack + # is downloaded and ready to open + synchronize: () => + this.checkState() + + transition: (newState) => + + + checkState: () => + isPlayable = context.jamClient.JamTrackIsPlayable() + + if isPlayable + this.transition(@states.synchronized) + else + + + @rest.getJamTrackRight({id: @jamTrackId}) + .done(this.processJamTrackRight) + .fail(@app.ajaxError) + + processJamTrackRight: (myJamTrack) => + if response.signing_state + + + diff --git a/web/app/assets/javascripts/fakeJamClient.js b/web/app/assets/javascripts/fakeJamClient.js index e9bae710f..caba3c900 100644 --- a/web/app/assets/javascripts/fakeJamClient.js +++ b/web/app/assets/javascripts/fakeJamClient.js @@ -679,6 +679,9 @@ function TrackSetInstrument(track, instrumentId) {} + function JamTrackIsPlayable() { + return true; + } // Method which sets volume function UpdateMixer(mixerId) {} @@ -973,6 +976,8 @@ this.TrackGetChatUsesMusic = TrackGetChatUsesMusic; this.TrackSetChatUsesMusic = TrackSetChatUsesMusic; + this.JamTrackIsPlayable = JamTrackIsPlayable; + // Scoring Knobs this.GetScoreWorkTimingInterval = GetScoreWorkTimingInterval; this.SetScoreWorkTimingInterval = SetScoreWorkTimingInterval; diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js index 230e6d23b..be4a7c5c4 100644 --- a/web/app/assets/javascripts/jam_rest.js +++ b/web/app/assets/javascripts/jam_rest.js @@ -1347,6 +1347,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", @@ -1583,6 +1605,7 @@ this.updateAudioLatency = updateAudioLatency; this.getJamtracks = getJamtracks; this.getPurchasedJamTracks = getPurchasedJamTracks; + this.getJamTrackRight = getJamTrackRight; this.addJamtrackToShoppingCart = addJamtrackToShoppingCart; this.getShoppingCarts = getShoppingCarts; this.removeShoppingCart = removeShoppingCart; diff --git a/web/config/routes.rb b/web/config/routes.rb index ecbec6909..49b2b98a6 100644 --- a/web/config/routes.rb +++ b/web/config/routes.rb @@ -197,7 +197,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 diff --git a/web/spec/javascripts/download_jamtrack_spec.js.coffee b/web/spec/javascripts/download_jamtrack_spec.js.coffee index b98150477..6b97f8771 100644 --- a/web/spec/javascripts/download_jamtrack_spec.js.coffee +++ b/web/spec/javascripts/download_jamtrack_spec.js.coffee @@ -1,7 +1,7 @@ describe "DownloadJamTrack", -> beforeEach -> - this.fixtures = fixture.load("downoadJamTrack.html", "user_sync_track1.json"); # append these fixtures which were already cached + this.fixtures = fixture.load("downoadJamTrack.html"); # append these fixtures which were already cached this.server = sinon.fakeServer.create(); window.jamClient = sinon.stub() this.downloadJamTrack = new JK.DownloadJamTrack() @@ -12,5 +12,4 @@ describe "DownloadJamTrack", -> this.server.restore(); it "display state correctly", -> - $track = this.syncViewer.createTrack(this.track1) - this.syncViewer.updateTrackState($track) + //$track = this.downloadJamTrack.createTrack() From 4d8a7a9bc1e29681244db15a6cb283e7e4feeff7 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Sun, 11 Jan 2015 15:42:12 -0600 Subject: [PATCH 02/23] * wip --- ruby/lib/jam_ruby/models/jam_track_right.rb | 4 +- .../javascripts/download_jamtrack.js.coffee | 233 ++++++++++++++++-- web/app/assets/javascripts/utils.js | 4 + web/app/controllers/spikes_controller.rb | 2 + .../_download_jamtrack_templates.html.slim | 28 +++ 5 files changed, 249 insertions(+), 22 deletions(-) diff --git a/ruby/lib/jam_ruby/models/jam_track_right.rb b/ruby/lib/jam_ruby/models/jam_track_right.rb index affee58a5..3e82185ed 100644 --- a/ruby/lib/jam_ruby/models/jam_track_right.rb +++ b/ruby/lib/jam_ruby/models/jam_track_right.rb @@ -123,8 +123,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' @@ -137,6 +135,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/web/app/assets/javascripts/download_jamtrack.js.coffee b/web/app/assets/javascripts/download_jamtrack.js.coffee index 937574403..f8605475e 100644 --- a/web/app/assets/javascripts/download_jamtrack.js.coffee +++ b/web/app/assets/javascripts/download_jamtrack.js.coffee @@ -9,46 +9,239 @@ context.JK ||= {}; # 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) -> + constructor: (@app, jamTrackId, jamTrackRightId) -> @EVENTS = context.JK.EVENTS @rest = context.JK.Rest() - @jamTrackId = null + @logger = context.JK.logger + @jamTrackId = jamTrackId + @jamTrackRightId = jamTrackRightId + @attemptedEnqueue = false + @errorMessage = null + @transitionTimer = null + @downloadTimer = null + @trackDetail = null + @errorSupportMsg = "Press RETRY, or if you have already retried, please contact support@jamkazam.com." @states = { - synchronized: 'synchronized', - packaging: 'packaging', - downloading: 'downloading', - keying: 'keying', - initial: 'initial' + no_client: { name: 'no-client', show: @showNoClient }, + synchronized: { name: 'synchronized', show: @showSynchronized }, + packaging: { name: 'packaging', show: @showPackaging }, + downloading: { name: 'downloading', show: @showDownloading }, + keying: { name: 'keying', show: @showKeying }, + initial: { name: 'initial', show: @showInitial }, + errored: { name: 'errored', show: @showError } } @state = @states.initial - init: (jamTrackId) => - @jamTrackId = jamTrackId - @root = $($('#template-download-jamtrack').html()) + context.JK.DownloadJamTracks[@jamTrackId] = this + # after you've created the DownloadJamTrack widget, call synchronize which will begin ensuring that the jamtrack # is downloaded and ready to open - synchronize: () => + init: () => + @root = $($('#template-download-jamtrack').html()) + + # populate in template and visual transition functions + for state, data of @states + data.template = $("#template-download-jamtrack-state-#{data.name}") + + # check if we are in a browser or client + if !gon.isNativeClient + @states.no_client.show() + else + @states.initial.show() + this.synchronize() + @attemptedEnqueue = false + this.checkState() + + + showPackaging: () => + @logger.debug("showing #{@states.name}") + + showDownloading: () => + @logger.debug("showing #{@states.name}") + # while downloading, we don't run the transition timer, because the download API is guaranteed to call success, or failure, eventually + this.clearTransitionTimer() + context.jamClient.StartFileDownload(context.JK.makeAbsolute('/api/jamtracks/download/' + @jamTrackId), this.makeDownloadProgressCallback(), this.makeDownloadSuccessCallback(), this.makeDownloadFailureCallback(), true) + + showKeying: () => + @logger.debug("showing #{@states.name}") + this.clearTransitionTimer() + context.jamClient.requestJamTrackKeys() + this.expectKeyed() + + showInitial: () => + @logger.debug("showing #{@states.name}") + context.JK.SubscriptionUtils.subscribe('jam_track_right', @jamTrackRightId).on(context.JK.EVENTS.SUBSCRIBE_NOTIFICATION, this.onJamTrackRightEvent) + + showError: () => + @logger.debug("showing #{@states.name}") + clearTransitionTimer() + context.JK.SubscriptionUtils.unsubscribe('jam_track_right', @jamTrackRightId) + + showSynchronized: () => + @logger.debug("showing #{@states.name}") + clearTransitionTimer() + context.JK.SubscriptionUtils.unsubscribe('jam_track_right', @jamTrackRightId) + + showNoClient: () => + @logger.debug("showing #{@states.name}") + + downloadCheck: () => + @logger.debug "downloadcheck" + + # sets an interval timer for every 3 seconds, waiting for the status to change + expectKeyed: () => + + expectDownload: () => + unless @downloadTimer? + return + + # 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 + + + 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 @transitionTimer? + return + + # 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) => - + if newState == @state + @logger.debug("DownloadJamTrack: ignoring state change #{@state}") + return + + @logger.debug("DownloadJamTrack: state change: #{@state} => #{newState}") + @state = newState + + @states[newState].show() checkState: () => - isPlayable = context.jamClient.JamTrackIsPlayable() + # 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(@jamTrackId) - if isPlayable - this.transition(@states.synchronized) - else + switch @trackDetail.key_state + when 'pending' + this.transition(@states.keying) + when 'not authorized' + this.transition(@states.keying) + when 'ready' + this.transition(@states.synchronized) + when 'unknown' + @rest.getJamTrackRight({id: @jamTrackId}) + .done(this.processJamTrackRight) + .fail(this.processJamTrackRightFail) - @rest.getJamTrackRight({id: @jamTrackId}) - .done(this.processJamTrackRight) - .fail(@app.ajaxError) + processSigningState: (signingState) => + switch signingState + when 'QUIET' + if @attemptedEnqueue + # this means we've already tried to poke the server. something is wrong + @errorMessage = "The server has not begun building your JamTrack. #{@errorSupportMsg}" + this.transition(@states.errored) + else + + this.expectTransition() + + @rest.enqueueJamTrack({id: @jamTrackId}) + .done(this.processEnqueueJamTrack) + .fail(this.processEnqueueJamTrackFail) + when 'QUEUED' + # when it's queued, there is nothing to do except wait. + this.expectTransition() + this.transition(@states.packaging) + when 'QUEUED_TIMEOUT' + @errorMessage = "The server took too long to begin processing your JamTrack. #{@errorSupportMsg}" + this.transition(@states.errored) + when 'SIGNING' + this.expectTransition() + when 'SIGNING_TIMEOUT' + @errorMessage = "The server took too long to create your JamTrack. #{@errorSupportMsg}" + this.transition(@states.errored) + when 'SIGNED' + this.transition(@states.downloading) + when 'ERROR' + if @attemptedEnqueue + # this means we've already tried to poke the server. something is wrong + @errorMessage = "The server failed to create your package. #{@errorSupportMsg}" + this.transition(@states.errored) + else + this.expectTransition() + + @rest.enqueueJamTrack({id: @jamTrackId}) + .done(this.processEnqueueJamTrack) + .fail(this.processEnqueueJamTrackFail) + + + + processJamTrackRightFail: () => + @errorMessage = "Unable to check with the server on the status of your JamTrack. #{@errorSupportMsg}" + this.transition(@states.errored) + + processEnqueueJamTrack: (enqueueResponse) => + this.expectTransition() # the act of enqueuing should send down events to the client. we wait... + + processEnqueueJamTrackFail: () => + @errorMessage = "Unable to ask the server to build your JamTrack. #{@errorSupportMsg}" + this.transition(@states.errored) processJamTrackRight: (myJamTrack) => - if response.signing_state + this.processSigningState(myJamTrack.signing_state) + 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? + this.transition(@states.keying) + + downloadFailureCallback: (errorMsg) => + @errorMessage = errorMessage + this.transition(@states.errored) + + # makes a function name for the backend + makeDownloadProgressCallback: () => + "JK.DownloadJamTracks['#{@jamTrackId}'].downloadProgressCallback" + + # makes a function name for the backend + makeDownloadSuccessCallback: () => + "JK.DownloadJamTracks['#{@jamTrackId}'].downloadSuccessCallback" + + # makes a function name for the backend + makeDownloadFailureCallback: () => + "JK.DownloadJamTracks['#{@jamTrackId}'].downloadFailureCallback" diff --git a/web/app/assets/javascripts/utils.js b/web/app/assets/javascripts/utils.js index 052b7d20e..f4ac9cee0 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/controllers/spikes_controller.rb b/web/app/controllers/spikes_controller.rb index b07987b4d..e0e43129f 100644 --- a/web/app/controllers/spikes_controller.rb +++ b/web/app/controllers/spikes_controller.rb @@ -33,6 +33,8 @@ 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' diff --git a/web/app/views/clients/_download_jamtrack_templates.html.slim b/web/app/views/clients/_download_jamtrack_templates.html.slim index bf5831cb4..abbbaf955 100644 --- a/web/app/views/clients/_download_jamtrack_templates.html.slim +++ b/web/app/views/clients/_download_jamtrack_templates.html.slim @@ -1,2 +1,30 @@ script type="text/template" id='template-download-jamtrack' .download-jamtrack + + +script type="text/template" id="template-download-jamtrack-state-no-client" + .state-no-client + | To download this JamTrack, launch the JamKazam application and open it while in a session. + +script type="text/template" id="template-download-jamtrack-state-synchronized" + .state-success + | This JamTrack is on your system and ready to play. + +script type="text/template" id="template-download-jamtrack-state-packaging" + .state-packaging + | Your JamTrack is currently being created on the JamKazam server. + +script type="text/template" id="template-download-jamtrack-state-downloading" + .state-downloading + | Your JamTrack is currently being downloaded. + +script type="text/template" id="template-download-jamtrack-state-keying" + .state-keying + | Your JamTrack is being authenticated. + +script type="text/template" id="template-download-jamtrack-state-initial" + .state-initial + | Initializing... +script type="text/template" id="template-download-jamtrack-state-errored" + .state-errored + .msg \ No newline at end of file From 698f4ba648e243d56a20b44317f2c729529c0cc6 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Wed, 21 Jan 2015 15:22:31 -0600 Subject: [PATCH 03/23] * wip --- .../javascripts/download_jamtrack.js.coffee | 213 +++++++++---- web/app/assets/javascripts/fakeJamClient.js | 4 + web/app/assets/javascripts/jam_rest.js | 1 + web/app/controllers/spikes_controller.rb | 12 + .../_download_jamtrack_templates.html.slim | 27 +- web/app/views/clients/index.html.erb | 1 + .../views/spikes/download_jam_track.html.slim | 28 ++ web/config/routes.rb | 1 + .../download_jamtrack_spec.js.coffee | 286 +++++++++++++++++- 9 files changed, 507 insertions(+), 66 deletions(-) create mode 100644 web/app/views/spikes/download_jam_track.html.slim diff --git a/web/app/assets/javascripts/download_jamtrack.js.coffee b/web/app/assets/javascripts/download_jamtrack.js.coffee index f8605475e..52ef822b3 100644 --- a/web/app/assets/javascripts/download_jamtrack.js.coffee +++ b/web/app/assets/javascripts/download_jamtrack.js.coffee @@ -2,6 +2,24 @@ $ = jQuery context = window context.JK ||= {}; +# 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 @jamTrackRightId +# +# 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: @@ -18,21 +36,25 @@ context.JK.DownloadJamTrack = class DownloadJamTrack @jamTrackId = jamTrackId @jamTrackRightId = jamTrackRightId @attemptedEnqueue = false + @errorReason = null @errorMessage = null @transitionTimer = null @downloadTimer = null @trackDetail = null + @stateHolder = null + @active = false + @startTime = null + @path = [] @errorSupportMsg = "Press RETRY, or if you have already retried, please contact support@jamkazam.com." @states = { - no_client: { name: 'no-client', show: @showNoClient }, - synchronized: { name: 'synchronized', show: @showSynchronized }, + 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 }, + keying: { name: 'keying', show: @showKeying, max_time: 10000 }, initial: { name: 'initial', show: @showInitial }, - errored: { name: 'errored', show: @showError } + errored: { name: 'errored', show: @showError, leaf: true} } - @state = @states.initial context.JK.DownloadJamTracks[@jamTrackId] = this @@ -40,7 +62,14 @@ context.JK.DownloadJamTrack = class DownloadJamTrack # 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 + this.reset() + + downloadJamTrackTemplate = $('#template-download-jamtrack') + throw "no download jamtrack template" if not downloadJamTrackTemplate.exists() + + @root = $(downloadJamTrackTemplate.html()) + @stateHolder = @root.find('.state') # populate in template and visual transition functions for state, data of @states @@ -48,55 +77,104 @@ context.JK.DownloadJamTrack = class DownloadJamTrack # check if we are in a browser or client if !gon.isNativeClient - @states.no_client.show() + this.transition(@states.no_client) else @states.initial.show() - this.synchronize() - @attemptedEnqueue = false - this.checkState() + + # when done with the widget, call destroy; this ensures it's not still active + destroy: () => + @active = false + this.reset() + + reset: () => + @path = [] + @startTime = new Date() + @state = @states.initial + # reset attemptedEnqueue to false, to allow one attempt to enqueue + @attemptedEnqueue = false + this.clearDownloadTimer() + this.clearTransitionTimer() + for state, data of @states + if data.timer? + clearInterval(data.timer) + data.timer = null showPackaging: () => - @logger.debug("showing #{@states.name}") + @logger.debug("showing #{@state.name}") + this.expectTransition() showDownloading: () => - @logger.debug("showing #{@states.name}") + @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 - this.clearTransitionTimer() context.jamClient.StartFileDownload(context.JK.makeAbsolute('/api/jamtracks/download/' + @jamTrackId), this.makeDownloadProgressCallback(), this.makeDownloadSuccessCallback(), this.makeDownloadFailureCallback(), true) showKeying: () => - @logger.debug("showing #{@states.name}") - this.clearTransitionTimer() + @logger.debug("showing #{@state.name}") context.jamClient.requestJamTrackKeys() - this.expectKeyed() + this.waitForState() showInitial: () => - @logger.debug("showing #{@states.name}") + @logger.debug("showing #{@state.name}") + this.expectTransition() context.JK.SubscriptionUtils.subscribe('jam_track_right', @jamTrackRightId).on(context.JK.EVENTS.SUBSCRIBE_NOTIFICATION, this.onJamTrackRightEvent) + this.checkState() showError: () => - @logger.debug("showing #{@states.name}") - clearTransitionTimer() + @logger.debug("showing #{@state.name}") context.JK.SubscriptionUtils.unsubscribe('jam_track_right', @jamTrackRightId) + @stateHolder.find('.msg').text(@errorMessage) + @stateHolder.find('.retry-button').click(this.retry) showSynchronized: () => - @logger.debug("showing #{@states.name}") - clearTransitionTimer() + @logger.debug("showing #{@state.name}") context.JK.SubscriptionUtils.unsubscribe('jam_track_right', @jamTrackRightId) showNoClient: () => - @logger.debug("showing #{@states.name}") + @logger.debug("showing #{@state.name}") downloadCheck: () => - @logger.debug "downloadcheck" + @logger.debug "downloadCheck" - # sets an interval timer for every 3 seconds, waiting for the status to change - expectKeyed: () => + 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 @downloadTimer? - return + 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() @@ -107,6 +185,10 @@ context.JK.DownloadJamTrack = class DownloadJamTrack clearTimeout(@downloadTimer) @downloadTimer = null + transitionError: (reasonCode, errorMessage) => + @errorReason = reasonCode + @errorMessage = errorMessage + this.transition(@states.errored) transitionCheck: () => this.checkState() @@ -117,8 +199,8 @@ context.JK.DownloadJamTrack = class DownloadJamTrack # 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 @transitionTimer? - return + 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() @@ -130,23 +212,54 @@ context.JK.DownloadJamTrack = class DownloadJamTrack @transitionTimer = null transition: (newState) => + unless @active + @logger.error("DownloadJamTrack: ignoring state change because we are not active") + if newState == @state - @logger.debug("DownloadJamTrack: ignoring state change #{@state}") + @logger.debug("DownloadJamTrack: ignoring state change #{@state.name}") return - @logger.debug("DownloadJamTrack: state change: #{@state} => #{newState}") + @logger.debug("DownloadJamTrack: state change: #{@state.name} => #{newState.name}") + + # make sure there is no timer running on the old state + this.clearTransitionTimer() + this.clearStateTimer() + @state = newState - @states[newState].show() + # track which states were taken + @path.push(@state.name) + + if @state.leaf + # report a stat now that we've reached the end of this widget's journey + flattened_path = @path.join('-') + data = {value: 1, path: flattened_path, duration: (new Date().getTime() - @startTime.getTime()) / 1000} + if @state == @states.errored + data.result = 'error' + data.detail = @errorDetail + else + data.result = 'success' + + context.JK.stats.write('web.jamtrack.downloader', data) + + @state.stateStartTime = new Date(); + + @stateHolder.children().remove() + @stateHolder.append(@state.template.html()) + + @state.show() 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(@jamTrackId) + @trackDetail = context.jamClient.JamTrackGetTrackDetail (@jamTrackId) + + @logger.debug("DownloadJamTrack: JamTrackGetTrackDetail.key_state: " + @trackDetail.key_state) 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) @@ -157,57 +270,54 @@ context.JK.DownloadJamTrack = class DownloadJamTrack 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 - @errorMessage = "The server has not begun building your JamTrack. #{@errorSupportMsg}" - this.transition(@states.errored) + this.transitionError("enqueue-timeout", "The server has not begun building your JamTrack.") else - this.expectTransition() + @attemptedEnqueue = true @rest.enqueueJamTrack({id: @jamTrackId}) .done(this.processEnqueueJamTrack) .fail(this.processEnqueueJamTrackFail) when 'QUEUED' # when it's queued, there is nothing to do except wait. - this.expectTransition() this.transition(@states.packaging) when 'QUEUED_TIMEOUT' - @errorMessage = "The server took too long to begin processing your JamTrack. #{@errorSupportMsg}" - this.transition(@states.errored) + this.transitionError("queued-timeout", "The server took too long to begin processing your JamTrack.") when 'SIGNING' - this.expectTransition() + this.transition(@states.packaging) when 'SIGNING_TIMEOUT' - @errorMessage = "The server took too long to create your JamTrack. #{@errorSupportMsg}" - this.transition(@states.errored) + 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 - @errorMessage = "The server failed to create your package. #{@errorSupportMsg}" - this.transition(@states.errored) + this.transitionError("package-error", "The server failed to create your package.") else this.expectTransition() + @attemptedEnqueue = true @rest.enqueueJamTrack({id: @jamTrackId}) .done(this.processEnqueueJamTrack) .fail(this.processEnqueueJamTrackFail) - - + else + @logger.error("unknown state: " + signingState) + this.transitionError("unknown-state-#{signingState}", "The server sent an unknown state message: " + signingState) processJamTrackRightFail: () => - @errorMessage = "Unable to check with the server on the status of your JamTrack. #{@errorSupportMsg}" - this.transition(@states.errored) + this.transitionError("status-check-error", "Unable to check with the server on the status of your JamTrack.") processEnqueueJamTrack: (enqueueResponse) => this.expectTransition() # the act of enqueuing should send down events to the client. we wait... processEnqueueJamTrackFail: () => - @errorMessage = "Unable to ask the server to build your JamTrack. #{@errorSupportMsg}" - this.transition(@states.errored) + this.transitionError("enqueue-error", "Unable to ask the server to build your JamTrack.") processJamTrackRight: (myJamTrack) => this.processSigningState(myJamTrack.signing_state) @@ -230,8 +340,7 @@ context.JK.DownloadJamTrack = class DownloadJamTrack this.transition(@states.keying) downloadFailureCallback: (errorMsg) => - @errorMessage = errorMessage - this.transition(@states.errored) + this.transitionError("download-error", errorMsg) # makes a function name for the backend makeDownloadProgressCallback: () => diff --git a/web/app/assets/javascripts/fakeJamClient.js b/web/app/assets/javascripts/fakeJamClient.js index caba3c900..7e98a64f5 100644 --- a/web/app/assets/javascripts/fakeJamClient.js +++ b/web/app/assets/javascripts/fakeJamClient.js @@ -682,6 +682,9 @@ function JamTrackIsPlayable() { return true; } + function JamTrackGetTrackDetail() { + return {key_state: 'ready'} + } // Method which sets volume function UpdateMixer(mixerId) {} @@ -977,6 +980,7 @@ this.TrackSetChatUsesMusic = TrackSetChatUsesMusic; this.JamTrackIsPlayable = JamTrackIsPlayable; + this.JamTrackGetTrackDetail = JamTrackGetTrackDetail; // Scoring Knobs this.GetScoreWorkTimingInterval = GetScoreWorkTimingInterval; diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js index be4a7c5c4..c2897195b 100644 --- a/web/app/assets/javascripts/jam_rest.js +++ b/web/app/assets/javascripts/jam_rest.js @@ -1606,6 +1606,7 @@ this.getJamtracks = getJamtracks; this.getPurchasedJamTracks = getPurchasedJamTracks; this.getJamTrackRight = getJamTrackRight; + this.enqueueJamTrack = enqueueJamTrack; this.addJamtrackToShoppingCart = addJamtrackToShoppingCart; this.getShoppingCarts = getShoppingCarts; this.removeShoppingCart = removeShoppingCart; diff --git a/web/app/controllers/spikes_controller.rb b/web/app/controllers/spikes_controller.rb index e0e43129f..910ff88b3 100644 --- a/web/app/controllers/spikes_controller.rb +++ b/web/app/controllers/spikes_controller.rb @@ -39,4 +39,16 @@ class SpikesController < ApplicationController 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.switchState = params[:state] + + render :layout => 'web' + end end diff --git a/web/app/views/clients/_download_jamtrack_templates.html.slim b/web/app/views/clients/_download_jamtrack_templates.html.slim index abbbaf955..014d2e118 100644 --- a/web/app/views/clients/_download_jamtrack_templates.html.slim +++ b/web/app/views/clients/_download_jamtrack_templates.html.slim @@ -1,30 +1,41 @@ 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 - | To download this JamTrack, launch the JamKazam application and open it while in a session. + | To play this JamTrack, launch the JamKazam application and open it while in a session. script type="text/template" id="template-download-jamtrack-state-synchronized" - .state-success + .state-synchronized | This JamTrack is on your system and ready to play. script type="text/template" id="template-download-jamtrack-state-packaging" .state-packaging - | Your JamTrack is currently being created on the JamKazam server. + .msg + | Your JamTrack is currently being created on the JamKazam server. + .spinner-small script type="text/template" id="template-download-jamtrack-state-downloading" .state-downloading - | Your JamTrack is currently being downloaded. + .msg + | Your JamTrack is currently being downloaded. + .spinner-small script type="text/template" id="template-download-jamtrack-state-keying" .state-keying - | Your JamTrack is being authenticated. + .msg + | Your JamTrack is being authenticated. + .spinner-small script type="text/template" id="template-download-jamtrack-state-initial" .state-initial - | Initializing... + .msg + | Initializing... + script type="text/template" id="template-download-jamtrack-state-errored" .state-errored - .msg \ No newline at end of file + .msg + .retry + | Press RETRY, or if you have already retried, please contact support@jamkazam.com. + .a.button-orange.retry-button RETRY \ No newline at end of file diff --git a/web/app/views/clients/index.html.erb b/web/app/views/clients/index.html.erb index 92d50feda..650f1b96b 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/spikes/download_jam_track.html.slim b/web/app/views/spikes/download_jam_track.html.slim new file mode 100644 index 000000000..3f56517f4 --- /dev/null +++ b/web/app/views/spikes/download_jam_track.html.slim @@ -0,0 +1,28 @@ += javascript_include_tag "download_jamtrack" += render "clients/download_jamtrack_templates" + +- provide(:title, 'Download Jam Track Widget') + +.content-wrapper + h2 Jam Track State Widget + ul + li synchronized + li no_client + li packaging + li downloading + li keying + li initial + li errored + #widget + +javascript: + $(document).on('JAMKAZAM_READY', function() { + window.downloadJamTrack = new JK.DownloadJamTrack(gon.jamTrackId, gon.jamTrackRightId) + downloadJamTrack.init() + $('#widget').append(window.downloadJamTrack.root) + + console.log("gon.switchstate", gon.switchState) + if(gon.switchState) { + downloadJamTrack.transition(downloadJamTrack.states[gon.switchState]); + } + }) \ No newline at end of file diff --git a/web/config/routes.rb b/web/config/routes.rb index 49b2b98a6..3fb0a8c7f 100644 --- a/web/config/routes.rb +++ b/web/config/routes.rb @@ -92,6 +92,7 @@ SampleApp::Application.routes.draw do 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' # junk pages match '/help', to: 'static_pages#help' diff --git a/web/spec/javascripts/download_jamtrack_spec.js.coffee b/web/spec/javascripts/download_jamtrack_spec.js.coffee index 6b97f8771..339bc6faf 100644 --- a/web/spec/javascripts/download_jamtrack_spec.js.coffee +++ b/web/spec/javascripts/download_jamtrack_spec.js.coffee @@ -1,15 +1,289 @@ describe "DownloadJamTrack", -> beforeEach -> - this.fixtures = fixture.load("downoadJamTrack.html"); # append these fixtures which were already cached + this.fixtures = fixture.load("downloadJamTrack.html"); # append these fixtures which were already cached this.server = sinon.fakeServer.create(); window.jamClient = sinon.stub() - this.downloadJamTrack = new JK.DownloadJamTrack() - this.downloadJamTrack.init() - $('body').append(this.downloadJamTrack.root) + this.app = sinon.stub() + this.jamTrackId = '1' + window.gon = {} + window.JK.JamServer = {} + window.JK.stats = {} + @statsSpy = window.JK.stats.write = sinon.spy() + window.JK.JamServer.send = sinon.stub(); # attempts to subscribe to the socket will need this afterEach -> this.server.restore(); + window.JK.stats.write.reset() + + describe "normal browser", -> + + beforeEach -> + window.gon.isNativeClient = false + @showNoClientSpy = sinon.spy(JK.DownloadJamTrack.prototype, 'showNoClient') + this.downloadJamTrack = new JK.DownloadJamTrack(@app, @jamTrackId, '1') + $('body').append(this.downloadJamTrack.root) + + afterEach -> + @showNoClientSpy.restore() + + it "switches to 'no client' correctly", -> + window.jamClient.JamTrackGetTrackDetail = 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, @jamTrackId, '1') + $('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'}) + + @downloadJamTrack.init(); + + expect(window.jamClient.JamTrackGetTrackDetail.callCount).toBe(1) + 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.requestJamTrackKeys = 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, @jamTrackId, '1') + @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'}) + + @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) + + it "shows synchronized", -> + window.jamClient.JamTrackGetTrackDetail = sinon.stub() + window.jamClient.JamTrackGetTrackDetail.returns({'key_state' : 'pending'}) + + @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'}) + + # 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.requestJamTrackKeys = 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, @jamTrackId, '1') + @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.JamTrackGetTrackDetail = sinon.stub() + window.jamClient.JamTrackGetTrackDetail.returns({'key_state' : 'unknown'}) + + spyOn(@downloadJamTrack.rest, 'getJamTrackRight').andCallFake((data) => + d = $.Deferred(); + d.resolve({signing_state: 'SIGNED'}); + d.promise(); + ) + + window.jamClient.StartFileDownload = sinon.stub() + + @downloadJamTrack.init() + + expect(window.jamClient.JamTrackGetTrackDetail.callCount).toBe(1) + expect(window.jamClient.StartFileDownload.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'}) + + # 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'}) + + 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.StartFileDownload = 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'}) + + 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.StartFileDownload = 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.downloadJamTrack.createTrack() From f43061b8ac79327133151737724ddb4c44bdd705 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Wed, 21 Jan 2015 15:24:42 -0600 Subject: [PATCH 04/23] * remove unused stuff --- web/spec/javascripts/download_jamtrack_spec.js.coffee | 2 -- 1 file changed, 2 deletions(-) diff --git a/web/spec/javascripts/download_jamtrack_spec.js.coffee b/web/spec/javascripts/download_jamtrack_spec.js.coffee index 339bc6faf..29acc49e7 100644 --- a/web/spec/javascripts/download_jamtrack_spec.js.coffee +++ b/web/spec/javascripts/download_jamtrack_spec.js.coffee @@ -2,7 +2,6 @@ describe "DownloadJamTrack", -> beforeEach -> this.fixtures = fixture.load("downloadJamTrack.html"); # append these fixtures which were already cached - this.server = sinon.fakeServer.create(); window.jamClient = sinon.stub() this.app = sinon.stub() this.jamTrackId = '1' @@ -13,7 +12,6 @@ describe "DownloadJamTrack", -> window.JK.JamServer.send = sinon.stub(); # attempts to subscribe to the socket will need this afterEach -> - this.server.restore(); window.JK.stats.write.reset() describe "normal browser", -> From 372444ebd1a299c354b4c66588ecb6710f631aae Mon Sep 17 00:00:00 2001 From: Seth Call Date: Fri, 23 Jan 2015 10:50:58 -0600 Subject: [PATCH 05/23] * wip --- web/app/assets/javascripts/JamServer.js | 9 ++- .../javascripts/download_jamtrack.js.coffee | 63 ++++++++++++++----- web/app/assets/javascripts/fakeJamClient.js | 3 + web/app/assets/javascripts/globals.js | 3 +- web/app/assets/javascripts/jam_rest.js | 2 +- .../javascripts/subscription_utils.js.coffee | 2 + .../client/downloadJamTrack.css.scss | 17 +++++ .../controllers/api_jam_tracks_controller.rb | 17 +++-- web/app/views/api_jam_tracks/list_keys.rabl | 2 +- .../_download_jamtrack_templates.html.slim | 11 ++-- .../views/spikes/download_jam_track.html.slim | 32 +++++++--- web/config/application.rb | 2 + web/config/routes.rb | 6 +- .../download_jamtrack_spec.js.coffee | 29 ++++++--- 14 files changed, 148 insertions(+), 50 deletions(-) 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/download_jamtrack.js.coffee b/web/app/assets/javascripts/download_jamtrack.js.coffee index 52ef822b3..f42ed5793 100644 --- a/web/app/assets/javascripts/download_jamtrack.js.coffee +++ b/web/app/assets/javascripts/download_jamtrack.js.coffee @@ -44,8 +44,9 @@ context.JK.DownloadJamTrack = class DownloadJamTrack @stateHolder = null @active = false @startTime = null + @attempts = 0 + @tracked = false @path = [] - @errorSupportMsg = "Press RETRY, or if you have already retried, please contact support@jamkazam.com." @states = { no_client: { name: 'no-client', show: @showNoClient, leaf: true }, synchronized: { name: 'synchronized', show: @showSynchronized, leaf: true}, @@ -81,13 +82,17 @@ context.JK.DownloadJamTrack = class DownloadJamTrack else @states.initial.show() - # when done with the widget, call destroy; this ensures it's not still active + # when done with the widget, call destroy; this ensures it's not still active, and tracks final metrics destroy: () => @active = false + 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() @state = @states.initial # reset attemptedEnqueue to false, to allow one attempt to enqueue @@ -99,6 +104,34 @@ context.JK.DownloadJamTrack = class DownloadJamTrack clearInterval(data.timer) data.timer = null + # report a stat now that we've reached the end of this widget's journey + trackProgress: () => + + # do not double-report + if @tracked + 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}") @@ -107,15 +140,16 @@ context.JK.DownloadJamTrack = class DownloadJamTrack showDownloading: () => @logger.debug("showing #{@state.name}") # while downloading, we don't run the transition timer, because the download API is guaranteed to call success, or failure, eventually - context.jamClient.StartFileDownload(context.JK.makeAbsolute('/api/jamtracks/download/' + @jamTrackId), this.makeDownloadProgressCallback(), this.makeDownloadSuccessCallback(), this.makeDownloadFailureCallback(), true) + context.jamClient.JamTrackDownload(@jamTrackId, this.makeDownloadSuccessCallback(), this.makeDownloadFailureCallback()) showKeying: () => @logger.debug("showing #{@state.name}") - context.jamClient.requestJamTrackKeys() + context.jamClient.JamTrackKeysRequest() this.waitForState() showInitial: () => @logger.debug("showing #{@state.name}") + @attempts = @attempts + 1 this.expectTransition() context.JK.SubscriptionUtils.subscribe('jam_track_right', @jamTrackRightId).on(context.JK.EVENTS.SUBSCRIBE_NOTIFICATION, this.onJamTrackRightEvent) this.checkState() @@ -125,6 +159,11 @@ context.JK.DownloadJamTrack = class DownloadJamTrack context.JK.SubscriptionUtils.unsubscribe('jam_track_right', @jamTrackRightId) @stateHolder.find('.msg').text(@errorMessage) @stateHolder.find('.retry-button').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}") @@ -137,6 +176,8 @@ context.JK.DownloadJamTrack = class DownloadJamTrack @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 @@ -231,16 +272,7 @@ context.JK.DownloadJamTrack = class DownloadJamTrack @path.push(@state.name) if @state.leaf - # report a stat now that we've reached the end of this widget's journey - flattened_path = @path.join('-') - data = {value: 1, path: flattened_path, duration: (new Date().getTime() - @startTime.getTime()) / 1000} - if @state == @states.errored - data.result = 'error' - data.detail = @errorDetail - else - data.result = 'success' - - context.JK.stats.write('web.jamtrack.downloader', data) + this.trackProgress() @state.stateStartTime = new Date(); @@ -249,6 +281,8 @@ context.JK.DownloadJamTrack = class DownloadJamTrack @state.show() + $(this).triggerHandler(@EVENTS.JAMTRACK_DOWNLOADER_STATE_CHANGED, {state: @state.name}) + 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 (@jamTrackId) @@ -340,6 +374,7 @@ context.JK.DownloadJamTrack = class DownloadJamTrack this.transition(@states.keying) downloadFailureCallback: (errorMsg) => + this.transitionError("download-error", errorMsg) # makes a function name for the backend diff --git a/web/app/assets/javascripts/fakeJamClient.js b/web/app/assets/javascripts/fakeJamClient.js index 7e98a64f5..cd3776d8a 100644 --- a/web/app/assets/javascripts/fakeJamClient.js +++ b/web/app/assets/javascripts/fakeJamClient.js @@ -685,6 +685,8 @@ function JamTrackGetTrackDetail() { return {key_state: 'ready'} } + function JamTrackKeysRequest() {} + // Method which sets volume function UpdateMixer(mixerId) {} @@ -981,6 +983,7 @@ this.JamTrackIsPlayable = JamTrackIsPlayable; this.JamTrackGetTrackDetail = JamTrackGetTrackDetail; + this.JamTrackKeysRequest = JamTrackKeysRequest; // Scoring Knobs this.GetScoreWorkTimingInterval = GetScoreWorkTimingInterval; 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 c2897195b..2278fd333 100644 --- a/web/app/assets/javascripts/jam_rest.js +++ b/web/app/assets/javascripts/jam_rest.js @@ -1352,7 +1352,7 @@ return $.ajax({ type: "GET", - url: '/api/jamtracks/rights' + jamTrackId + '?' + $.param(options), + url: '/api/jamtracks/rights/' + jamTrackId + '?' + $.param(options), dataType: "json", contentType: 'application/json' }) 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/stylesheets/client/downloadJamTrack.css.scss b/web/app/assets/stylesheets/client/downloadJamTrack.css.scss index 1cfe42004..97313ab65 100644 --- a/web/app/assets/stylesheets/client/downloadJamTrack.css.scss +++ b/web/app/assets/stylesheets/client/downloadJamTrack.css.scss @@ -1,5 +1,22 @@ @import "client/common"; .download-jamtrack { + display:inline-block; + + .state { + text-align:center; + } + .retry-button { + margin-top:20px; + } + + .retry { + margin-top:10px; + } + + .spinner-large { + margin:20px auto 0; + text-align:center; + } } \ No newline at end of file diff --git a/web/app/controllers/api_jam_tracks_controller.rb b/web/app/controllers/api_jam_tracks_controller.rb index 1cb0182d7..e2d7dffe7 100644 --- a/web/app/controllers/api_jam_tracks_controller.rb +++ b/web/app/controllers/api_jam_tracks_controller.rb @@ -34,7 +34,7 @@ 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?) - JamTrackRight.where(:id => self.id).update_all(:last_downloaded_at => Time.now) + JamTrackRight.where(:id => @jam_track_right.id).update_all(:last_downloaded_at => Time.now) @jam_track_right.update_download_count @jam_track_right.save! redirect_to @jam_track_right.sign_url @@ -58,14 +58,21 @@ class ApiJamTracksController < ApiController end def keys - jamtrack_ids = params[:jamtracks] + jamtrack_holder = params[:jamtracks] - unless jamtrack_ids.kind_of?(Array) - render :json => {message: 'jamtracks parameter must be an array'}, :status => 200 + 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/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/clients/_download_jamtrack_templates.html.slim b/web/app/views/clients/_download_jamtrack_templates.html.slim index 014d2e118..f87cf24fd 100644 --- a/web/app/views/clients/_download_jamtrack_templates.html.slim +++ b/web/app/views/clients/_download_jamtrack_templates.html.slim @@ -4,29 +4,29 @@ script type="text/template" id='template-download-jamtrack' script type="text/template" id="template-download-jamtrack-state-no-client" .state-no-client - | To play this JamTrack, launch the JamKazam application and open it while in a session. + | To play your JamTrack, launch the JamKazam application and open it while in a session. script type="text/template" id="template-download-jamtrack-state-synchronized" .state-synchronized - | This JamTrack is on your system and ready to play. + | Your JamTrack is on your system and ready to play. script type="text/template" id="template-download-jamtrack-state-packaging" .state-packaging .msg | Your JamTrack is currently being created on the JamKazam server. - .spinner-small + .spinner-large script type="text/template" id="template-download-jamtrack-state-downloading" .state-downloading .msg | Your JamTrack is currently being downloaded. - .spinner-small + .spinner-large script type="text/template" id="template-download-jamtrack-state-keying" .state-keying .msg | Your JamTrack is being authenticated. - .spinner-small + .spinner-large script type="text/template" id="template-download-jamtrack-state-initial" .state-initial @@ -37,5 +37,4 @@ script type="text/template" id="template-download-jamtrack-state-errored" .state-errored .msg .retry - | Press RETRY, or if you have already retried, please contact support@jamkazam.com. .a.button-orange.retry-button RETRY \ No newline at end of file diff --git a/web/app/views/spikes/download_jam_track.html.slim b/web/app/views/spikes/download_jam_track.html.slim index 3f56517f4..183ee9bb6 100644 --- a/web/app/views/spikes/download_jam_track.html.slim +++ b/web/app/views/spikes/download_jam_track.html.slim @@ -1,10 +1,13 @@ = 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 @@ -16,13 +19,26 @@ #widget javascript: - $(document).on('JAMKAZAM_READY', function() { - window.downloadJamTrack = new JK.DownloadJamTrack(gon.jamTrackId, gon.jamTrackRightId) - downloadJamTrack.init() - $('#widget').append(window.downloadJamTrack.root) + 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 - console.log("gon.switchstate", gon.switchState) - if(gon.switchState) { - downloadJamTrack.transition(downloadJamTrack.states[gon.switchState]); - } + setTimeout(function() { + window.downloadJamTrack = new JK.DownloadJamTrack(data.app, gon.jamTrackId, gon.jamTrackRightId) + 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 a396cb57f..2435d6026 100644 --- a/web/config/application.rb +++ b/web/config/application.rb @@ -309,5 +309,7 @@ if defined?(Bundler) config.influxdb_hosts = ["localhost"] config.influxdb_port = 8086 config.influxdb_ignored_environments = ENV["INFLUXDB_ENABLED"] == '1' ? ['test', 'cucumber'] : ['test', 'cucumber', 'development'] + + config.allow_spikes = false end end diff --git a/web/config/routes.rb b/web/config/routes.rb index 3fb0a8c7f..4b81b4224 100644 --- a/web/config/routes.rb +++ b/web/config/routes.rb @@ -77,17 +77,15 @@ 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' diff --git a/web/spec/javascripts/download_jamtrack_spec.js.coffee b/web/spec/javascripts/download_jamtrack_spec.js.coffee index 29acc49e7..683693abf 100644 --- a/web/spec/javascripts/download_jamtrack_spec.js.coffee +++ b/web/spec/javascripts/download_jamtrack_spec.js.coffee @@ -7,12 +7,12 @@ describe "DownloadJamTrack", -> this.jamTrackId = '1' window.gon = {} window.JK.JamServer = {} - window.JK.stats = {} - @statsSpy = window.JK.stats.write = sinon.spy() + window.stats = {} + @statsSpy = window.stats.write = sinon.spy() window.JK.JamServer.send = sinon.stub(); # attempts to subscribe to the socket will need this afterEach -> - window.JK.stats.write.reset() + window.stats.write.reset() describe "normal browser", -> @@ -67,7 +67,7 @@ describe "DownloadJamTrack", -> describe "pending", -> beforeEach -> - window.jamClient.requestJamTrackKeys = sinon.stub() + 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') @@ -100,6 +100,17 @@ describe "DownloadJamTrack", -> 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() @@ -128,7 +139,7 @@ describe "DownloadJamTrack", -> describe "JamTrack needs downloading", -> beforeEach -> - window.jamClient.requestJamTrackKeys = sinon.stub() + 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') @@ -156,12 +167,12 @@ describe "DownloadJamTrack", -> d.promise(); ) - window.jamClient.StartFileDownload = sinon.stub() + window.jamClient.JamTrackDownload = sinon.stub() @downloadJamTrack.init() expect(window.jamClient.JamTrackGetTrackDetail.callCount).toBe(1) - expect(window.jamClient.StartFileDownload.callCount).toBe(1) + expect(window.jamClient.JamTrackDownload.callCount).toBe(1) expect(@showDownloadingSpy.calledOnce).toBe(true) expect(@downloadJamTrack.stateHolder.find('.state-downloading')).toHaveLength(1) @@ -201,7 +212,7 @@ describe "DownloadJamTrack", -> d.promise(); ) - window.jamClient.StartFileDownload = sinon.stub() + window.jamClient.JamTrackDownload = sinon.stub() @downloadJamTrack.init() @@ -252,7 +263,7 @@ describe "DownloadJamTrack", -> d.promise(); ) - window.jamClient.StartFileDownload = sinon.stub() + window.jamClient.JamTrackDownload = sinon.stub() @downloadJamTrack.init() From 7844f2ffad926d86749ac96ecef84db6a5a89ecc Mon Sep 17 00:00:00 2001 From: Seth Call Date: Fri, 23 Jan 2015 13:55:15 -0600 Subject: [PATCH 06/23] * wip --- web/app/assets/javascripts/dialog/openJamTrackDialog.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/app/assets/javascripts/dialog/openJamTrackDialog.js b/web/app/assets/javascripts/dialog/openJamTrackDialog.js index b3d485876..522b1a5e6 100644 --- a/web/app/assets/javascripts/dialog/openJamTrackDialog.js +++ b/web/app/assets/javascripts/dialog/openJamTrackDialog.js @@ -77,8 +77,9 @@ // 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('t'); + var result = context.jamClient.JamTrackPlay(jamTrack.id); logger.debug("JamTrackPlay response: %o", result); From ac204782b3cc2fff6ffee15f7cd2ae7fcf95dd18 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Fri, 23 Jan 2015 14:30:52 -0600 Subject: [PATCH 07/23] * fixing tests --- .../api_jam_tracks_controller_spec.rb | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/web/spec/controllers/api_jam_tracks_controller_spec.rb b/web/spec/controllers/api_jam_tracks_controller_spec.rb index 31101592c..62bd151e9 100644 --- a/web/spec/controllers/api_jam_tracks_controller_spec.rb +++ b/web/spec/controllers/api_jam_tracks_controller_spec.rb @@ -157,18 +157,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 @@ -177,32 +177,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 From c86aa6a5d75b49687ada1b02f1fe21853ed1c890 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Fri, 23 Jan 2015 17:11:49 -0600 Subject: [PATCH 08/23] * fake to bounce jenkins --- web/README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/web/README.md b/web/README.md index ae45c560b..efb2ab112 100644 --- a/web/README.md +++ b/web/README.md @@ -11,5 +11,3 @@ $ bundle $ rake jasmine Open browser to localhost:8888 - - From f53c5c9612424eaf72c92d9490d0b835bddb3afe Mon Sep 17 00:00:00 2001 From: Seth Call Date: Mon, 26 Jan 2015 21:22:44 -0600 Subject: [PATCH 09/23] *wip --- build | 2 +- .../javascripts/dialog/openJamTrackDialog.js | 15 +-- .../javascripts/download_jamtrack.js.coffee | 9 +- web/app/assets/javascripts/fakeJamClient.js | 4 + web/app/assets/javascripts/session.js | 94 +++++++++++++++---- web/app/assets/stylesheets/client/client.css | 1 + .../client/downloadJamTrack.css.scss | 4 + .../stylesheets/client/session.css.scss | 4 + .../views/api_jam_tracks/show_for_client.rabl | 4 + .../_download_jamtrack_templates.html.slim | 5 +- web/lib/tasks/sample_data.rake | 2 +- .../api_jam_tracks_controller_spec.rb | 1 + 12 files changed, 107 insertions(+), 38 deletions(-) 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/web/app/assets/javascripts/dialog/openJamTrackDialog.js b/web/app/assets/javascripts/dialog/openJamTrackDialog.js index 522b1a5e6..0c6d2e712 100644 --- a/web/app/assets/javascripts/dialog/openJamTrackDialog.js +++ b/web/app/assets/javascripts/dialog/openJamTrackDialog.js @@ -77,19 +77,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 f42ed5793..ff9669530 100644 --- a/web/app/assets/javascripts/download_jamtrack.js.coffee +++ b/web/app/assets/javascripts/download_jamtrack.js.coffee @@ -58,7 +58,10 @@ context.JK.DownloadJamTrack = class DownloadJamTrack } context.JK.DownloadJamTracks[@jamTrackId] = this + downloadJamTrackTemplate = $('#template-download-jamtrack') + throw "no download jamtrack template" if not downloadJamTrackTemplate.exists() + @root = $(downloadJamTrackTemplate.html()) # after you've created the DownloadJamTrack widget, call synchronize which will begin ensuring that the jamtrack # is downloaded and ready to open @@ -66,10 +69,8 @@ context.JK.DownloadJamTrack = class DownloadJamTrack @active = true this.reset() - downloadJamTrackTemplate = $('#template-download-jamtrack') - throw "no download jamtrack template" if not downloadJamTrackTemplate.exists() - @root = $(downloadJamTrackTemplate.html()) + @root.on('remove', this.destroy) # automatically destroy self when removed from DOM @stateHolder = @root.find('.state') # populate in template and visual transition functions @@ -281,7 +282,7 @@ context.JK.DownloadJamTrack = class DownloadJamTrack @state.show() - $(this).triggerHandler(@EVENTS.JAMTRACK_DOWNLOADER_STATE_CHANGED, {state: @state.name}) + $(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 diff --git a/web/app/assets/javascripts/fakeJamClient.js b/web/app/assets/javascripts/fakeJamClient.js index cd3776d8a..37a0b65e6 100644 --- a/web/app/assets/javascripts/fakeJamClient.js +++ b/web/app/assets/javascripts/fakeJamClient.js @@ -679,6 +679,8 @@ function TrackSetInstrument(track, instrumentId) {} + function JamTrackStopPlay() {} + function JamTrackPlay(){return true; } function JamTrackIsPlayable() { return true; } @@ -981,6 +983,8 @@ this.TrackGetChatUsesMusic = TrackGetChatUsesMusic; this.TrackSetChatUsesMusic = TrackSetChatUsesMusic; + this.JamTrackStopPlay = JamTrackStopPlay; + this.JamTrackPlay = JamTrackPlay; this.JamTrackIsPlayable = JamTrackIsPlayable; this.JamTrackGetTrackDetail = JamTrackGetTrackDetail; this.JamTrackKeysRequest = JamTrackKeysRequest; diff --git a/web/app/assets/javascripts/session.js b/web/app/assets/javascripts/session.js index 96d1dc55a..d5a6e0c31 100644 --- a/web/app/assets/javascripts/session.js +++ b/web/app/assets/javascripts/session.js @@ -47,6 +47,10 @@ var $screen = null; var $mixModeDropdown = null; var $templateMixerModeChange = null; + var $otherAudioContainer = null; + var $myTracksContainer = null; + var $liveTracksContainer = null; + var downloadJamTrack = null; var rest = context.JK.Rest(); var RENDER_SESSION_DELAY = 750; // When I need to render a session, I have to wait a bit for the mixers to be there. @@ -98,7 +102,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"); @@ -500,7 +504,7 @@ } function renderSession() { - $('#session-mytracks-container').empty(); + $myTracksContainer.empty(); $('.session-track').remove(); // Remove previous tracks var $voiceChat = $('#voice-chat'); $voiceChat.hide(); @@ -514,7 +518,7 @@ if ($('.session-livetracks .track').length === 0) { $('.session-livetracks .when-empty').show(); } - if ($('.session-recordings .track').length === 0) { + 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(); @@ -884,7 +888,7 @@ } function renderJamTracks(jamTrackMixers) { - log.debug("rendering jam tracks") + logger.debug("rendering jam tracks") var jamTracks = sessionModel.jamTracks(); // pluck the 1st mixer, and assume that all other mixers in this group are of the same type (between JamTrack vs Peer) @@ -1433,11 +1437,9 @@ 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(); @@ -1451,7 +1453,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); @@ -1467,24 +1469,27 @@ 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) { - var parentSelector = '#session-recordedtracks-container'; - var $destination = $(parentSelector); - $('.session-recordings .when-empty').hide(); - $('.session-recording-name-wrapper').show(); + + otherAudioFilled(); + $('.session-recordings .recording-controls').show(); var template = $('#template-session-track').html(); var newTrack = $(context.JK.fillTemplate(template, trackData)); - $destination.append(newTrack); + $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') @@ -1921,7 +1926,59 @@ 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; + + logger.debug("JamTrack INFO", jamTrack.id, jamTrack.jam_track_right_id) + + // 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.id, jamTrack.jam_track_right_id); + + // 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; + + context.jamClient.JamTrackStopPlay(); + 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; } @@ -2093,6 +2150,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'); events(); 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 97313ab65..762a4103b 100644 --- a/web/app/assets/stylesheets/client/downloadJamTrack.css.scss +++ b/web/app/assets/stylesheets/client/downloadJamTrack.css.scss @@ -14,6 +14,10 @@ margin-top:10px; } + .msg { + white-space: normal; + } + .spinner-large { margin:20px auto 0; text-align:center; diff --git a/web/app/assets/stylesheets/client/session.css.scss b/web/app/assets/stylesheets/client/session.css.scss index 8c37af233..53cbfd929 100644 --- a/web/app/assets/stylesheets/client/session.css.scss +++ b/web/app/assets/stylesheets/client/session.css.scss @@ -133,6 +133,10 @@ margin-bottom:8px; font-size:16px; } + + .download-jamtrack { + margin-top:50px; + } } 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..1b2db2fe6 100644 --- a/web/app/views/api_jam_tracks/show_for_client.rabl +++ b/web/app/views/api_jam_tracks/show_for_client.rabl @@ -2,6 +2,10 @@ object @jam_track attributes :id, :name, :description, :initial_play_silence, :original_artist +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/clients/_download_jamtrack_templates.html.slim b/web/app/views/clients/_download_jamtrack_templates.html.slim index f87cf24fd..38aba682b 100644 --- a/web/app/views/clients/_download_jamtrack_templates.html.slim +++ b/web/app/views/clients/_download_jamtrack_templates.html.slim @@ -31,10 +31,11 @@ script type="text/template" id="template-download-jamtrack-state-keying" script type="text/template" id="template-download-jamtrack-state-initial" .state-initial .msg - | Initializing... + | Initializing JamTrack... + .spinner-large script type="text/template" id="template-download-jamtrack-state-errored" .state-errored .msg .retry - .a.button-orange.retry-button RETRY \ No newline at end of file + a.button-orange.retry-button RETRY \ No newline at end of file diff --git a/web/lib/tasks/sample_data.rake b/web/lib/tasks/sample_data.rake index 853194455..ff107ac00 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 62bd151e9..f22fb23f9 100644 --- a/web/spec/controllers/api_jam_tracks_controller_spec.rb +++ b/web/spec/controllers/api_jam_tracks_controller_spec.rb @@ -101,6 +101,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 From 43b9fd055941bfbd448f53b6ff8f056f2087e101 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Tue, 27 Jan 2015 07:25:19 -0600 Subject: [PATCH 10/23] * fix test --- web/spec/javascripts/download_jamtrack_spec.js.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/web/spec/javascripts/download_jamtrack_spec.js.coffee b/web/spec/javascripts/download_jamtrack_spec.js.coffee index 683693abf..58fbe3427 100644 --- a/web/spec/javascripts/download_jamtrack_spec.js.coffee +++ b/web/spec/javascripts/download_jamtrack_spec.js.coffee @@ -158,6 +158,7 @@ describe "DownloadJamTrack", -> @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'}) From 3496aa999f34b9b3411c9b2437c3dc087a6ed575 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Thu, 29 Jan 2015 14:17:55 -0600 Subject: [PATCH 11/23] * wip --- .../javascripts/dialog/openJamTrackDialog.js | 1 + .../javascripts/download_jamtrack.js.coffee | 181 ++++++++++++------ web/app/assets/javascripts/fakeJamClient.js | 5 +- web/app/assets/javascripts/order.js | 142 ++++++++++++-- web/app/assets/javascripts/session.js | 45 +++-- .../stylesheets/client/checkout.css.scss | 34 ++++ .../client/downloadJamTrack.css.scss | 57 +++++- web/app/controllers/api_recurly_controller.rb | 20 +- web/app/controllers/spikes_controller.rb | 1 + .../_download_jamtrack_templates.html.slim | 79 ++++++-- web/app/views/clients/_order.html.slim | 16 +- web/app/views/clients/_session.html.erb | 2 +- .../dialogs/_openJamTrackDialog.html.slim | 2 +- .../views/spikes/download_jam_track.html.slim | 2 +- web/lib/recurly_client.rb | 3 +- .../download_jamtrack_spec.js.coffee | 9 +- 16 files changed, 481 insertions(+), 118 deletions(-) diff --git a/web/app/assets/javascripts/dialog/openJamTrackDialog.js b/web/app/assets/javascripts/dialog/openJamTrackDialog.js index 0c6d2e712..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; diff --git a/web/app/assets/javascripts/download_jamtrack.js.coffee b/web/app/assets/javascripts/download_jamtrack.js.coffee index ff9669530..77fa5aa34 100644 --- a/web/app/assets/javascripts/download_jamtrack.js.coffee +++ b/web/app/assets/javascripts/download_jamtrack.js.coffee @@ -13,7 +13,7 @@ context.JK ||= {}; # * packaging # * downloading # -# errored state can be entered from @jamTrackRightId +# 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 # @@ -29,12 +29,12 @@ context.JK ||= {}; context.JK.DownloadJamTracks = {} context.JK.DownloadJamTrack = class DownloadJamTrack - constructor: (@app, jamTrackId, jamTrackRightId) -> + constructor: (@app, jamTrack, size = 'large') -> @EVENTS = context.JK.EVENTS @rest = context.JK.Rest() @logger = context.JK.logger - @jamTrackId = jamTrackId - @jamTrackRightId = jamTrackRightId + @jamTrack = jamTrack + @size = size @attemptedEnqueue = false @errorReason = null @errorMessage = null @@ -46,6 +46,12 @@ context.JK.DownloadJamTrack = class DownloadJamTrack @startTime = null @attempts = 0 @tracked = false + @ajaxEnqueueAborted = false + @ajaxGetJamTrackRightAborted = false + + throw "no JamTrack specified" unless @jamTrack? + throw "invalid size" if @size != 'large' && @size != 'small' + @path = [] @states = { no_client: { name: 'no-client', show: @showNoClient, leaf: true }, @@ -54,38 +60,46 @@ context.JK.DownloadJamTrack = class DownloadJamTrack 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[@jamTrackId] = this + context.JK.DownloadJamTracks[@jamTrack.id] = this downloadJamTrackTemplate = $('#template-download-jamtrack') throw "no download jamtrack template" if not downloadJamTrackTemplate.exists() @root = $(downloadJamTrackTemplate.html()) - - # after you've created the DownloadJamTrack widget, call synchronize which will begin ensuring that the jamtrack - # is downloaded and ready to open - init: () => - @active = true - this.reset() - - - @root.on('remove', this.destroy) # automatically destroy self when removed from DOM @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: () => + @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 - @states.initial.show() + 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() @@ -95,16 +109,40 @@ context.JK.DownloadJamTrack = class DownloadJamTrack @attempts = 0 @tracked = false @startTime = new Date() - @state = @states.initial # 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: () => @@ -112,6 +150,9 @@ context.JK.DownloadJamTrack = class DownloadJamTrack 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') @@ -141,25 +182,35 @@ context.JK.DownloadJamTrack = class DownloadJamTrack showDownloading: () => @logger.debug("showing #{@state.name}") # while downloading, we don't run the transition timer, because the download API is guaranteed to call success, or failure, eventually - context.jamClient.JamTrackDownload(@jamTrackId, this.makeDownloadSuccessCallback(), this.makeDownloadFailureCallback()) + 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', @jamTrackRightId).on(context.JK.EVENTS.SUBSCRIBE_NOTIFICATION, this.onJamTrackRightEvent) + 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', @jamTrackRightId) - @stateHolder.find('.msg').text(@errorMessage) - @stateHolder.find('.retry-button').click(this.retry) + 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' @@ -168,7 +219,7 @@ context.JK.DownloadJamTrack = class DownloadJamTrack showSynchronized: () => @logger.debug("showing #{@state.name}") - context.JK.SubscriptionUtils.unsubscribe('jam_track_right', @jamTrackRightId) + context.JK.SubscriptionUtils.unsubscribe('jam_track_right', @jamTrack.jam_track_right_id) showNoClient: () => @logger.debug("showing #{@state.name}") @@ -256,16 +307,22 @@ context.JK.DownloadJamTrack = class DownloadJamTrack transition: (newState) => unless @active @logger.error("DownloadJamTrack: ignoring state change because we are not active") - - if newState == @state - @logger.debug("DownloadJamTrack: ignoring state change #{@state.name}") return - @logger.debug("DownloadJamTrack: state change: #{@state.name} => #{newState.name}") + if newState == @state + @logger.debug("DownloadJamTrack: ignoring state change #{@state.name} #{newState}") + return - # make sure there is no timer running on the old state - this.clearTransitionTimer() - this.clearStateTimer() + 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 @@ -275,18 +332,13 @@ context.JK.DownloadJamTrack = class DownloadJamTrack if @state.leaf this.trackProgress() - @state.stateStartTime = new Date(); - - @stateHolder.children().remove() - @stateHolder.append(@state.template.html()) - - @state.show() + 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 (@jamTrackId) + @trackDetail = context.jamClient.JamTrackGetTrackDetail (@jamTrack.id) @logger.debug("DownloadJamTrack: JamTrackGetTrackDetail.key_state: " + @trackDetail.key_state) @@ -299,7 +351,8 @@ context.JK.DownloadJamTrack = class DownloadJamTrack when 'ready' this.transition(@states.synchronized) when 'unknown' - @rest.getJamTrackRight({id: @jamTrackId}) + @ajaxGetJamTrackRightAborted = false + @rest.getJamTrackRight({id: @jamTrack.id}) .done(this.processJamTrackRight) .fail(this.processJamTrackRightFail) @@ -315,10 +368,7 @@ context.JK.DownloadJamTrack = class DownloadJamTrack else this.expectTransition() - @attemptedEnqueue = true - @rest.enqueueJamTrack({id: @jamTrackId}) - .done(this.processEnqueueJamTrack) - .fail(this.processEnqueueJamTrackFail) + this.attemptToEnqueue() when 'QUEUED' # when it's queued, there is nothing to do except wait. this.transition(@states.packaging) @@ -337,29 +387,45 @@ context.JK.DownloadJamTrack = class DownloadJamTrack else this.expectTransition() - @attemptedEnqueue = true - @rest.enqueueJamTrack({id: @jamTrackId}) - .done(this.processEnqueueJamTrack) - .fail(this.processEnqueueJamTrackFail) + this.attemptToEnqueue() else @logger.error("unknown state: " + signingState) this.transitionError("unknown-state-#{signingState}", "The server sent an unknown state message: " + signingState) - processJamTrackRightFail: () => - this.transitionError("status-check-error", "Unable to check with the server on the status of your JamTrack.") + attemptToEnqueue: () => + @attemptedEnqueue = true + @ajaxEnqueueAborted = false + @rest.enqueueJamTrack({id: @jamTrack.id}) + .done(this.processEnqueueJamTrack) + .fail(this.processEnqueueJamTrackFail) - processEnqueueJamTrack: (enqueueResponse) => - this.expectTransition() # the act of enqueuing should send down events to the client. we wait... - - processEnqueueJamTrackFail: () => - this.transitionError("enqueue-error", "Unable to ask the server to build your JamTrack.") processJamTrackRight: (myJamTrack) => - this.processSigningState(myJamTrack.signing_state) + 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) @@ -372,6 +438,7 @@ context.JK.DownloadJamTrack = class DownloadJamTrack downloadSuccessCallback: (updateLocation) => # is the package loadable yet? + @logger.debug("DownloadJamTrack: download complete - on to keying") this.transition(@states.keying) downloadFailureCallback: (errorMsg) => @@ -380,13 +447,13 @@ context.JK.DownloadJamTrack = class DownloadJamTrack # makes a function name for the backend makeDownloadProgressCallback: () => - "JK.DownloadJamTracks['#{@jamTrackId}'].downloadProgressCallback" + "JK.DownloadJamTracks['#{@jamTrack.id}'].downloadProgressCallback" # makes a function name for the backend makeDownloadSuccessCallback: () => - "JK.DownloadJamTracks['#{@jamTrackId}'].downloadSuccessCallback" + "JK.DownloadJamTracks['#{@jamTrack.id}'].downloadSuccessCallback" # makes a function name for the backend makeDownloadFailureCallback: () => - "JK.DownloadJamTracks['#{@jamTrackId}'].downloadFailureCallback" + "JK.DownloadJamTracks['#{@jamTrack.id}'].downloadFailureCallback" diff --git a/web/app/assets/javascripts/fakeJamClient.js b/web/app/assets/javascripts/fakeJamClient.js index 37a0b65e6..e8bdfcaaf 100644 --- a/web/app/assets/javascripts/fakeJamClient.js +++ b/web/app/assets/javascripts/fakeJamClient.js @@ -685,10 +685,10 @@ return true; } function JamTrackGetTrackDetail() { - return {key_state: 'ready'} + return {key_state: 'unknown'} } function JamTrackKeysRequest() {} - + function JamTrackDownload() {} // Method which sets volume function UpdateMixer(mixerId) {} @@ -988,6 +988,7 @@ this.JamTrackIsPlayable = JamTrackIsPlayable; this.JamTrackGetTrackDetail = JamTrackGetTrackDetail; this.JamTrackKeysRequest = JamTrackKeysRequest; + this.JamTrackDownload = JamTrackDownload; // Scoring Knobs this.GetScoreWorkTimingInterval = GetScoreWorkTimingInterval; 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/session.js b/web/app/assets/javascripts/session.js index d5a6e0c31..051143f73 100644 --- a/web/app/assets/javascripts/session.js +++ b/web/app/assets/javascripts/session.js @@ -503,6 +503,15 @@ } } + 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 renderSession() { $myTracksContainer.empty(); $('.session-track').remove(); // Remove previous tracks @@ -518,11 +527,7 @@ if ($('.session-livetracks .track').length === 0) { $('.session-livetracks .when-empty').show(); } - 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(); - } + resetOtherAudioContent(); } function _initDialogs() { @@ -1933,8 +1938,6 @@ var jamTrack = data.result.jamTrack; - logger.debug("JamTrack INFO", jamTrack.id, jamTrack.jam_track_right_id) - // hide 'other audio' placeholder otherAudioFilled(); @@ -1946,7 +1949,7 @@ downloadJamTrack = null } - downloadJamTrack = new context.JK.DownloadJamTrack(app, jamTrack.id, jamTrack.jam_track_right_id); + downloadJamTrack = new context.JK.DownloadJamTrack(app, jamTrack.id, '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) { @@ -1957,7 +1960,10 @@ downloadJamTrack.destroy(); downloadJamTrack = null; + // XXX: test with this removed; it should be unnecessary context.jamClient.JamTrackStopPlay(); + + // JamTrackPlay means 'load' var result = context.jamClient.JamTrackPlay(jamTrack.id); if(!result) { @@ -1972,7 +1978,7 @@ // show it on the page $otherAudioContainer.append(downloadJamTrack.root) - // kick off the download jamtrack process + // kick off the download JamTrack process downloadJamTrack.init() } else { @@ -2005,15 +2011,30 @@ if(sessionModel.recordedTracks()) { closeRecording(); } - else if(sessionModel.jamTracks()) { + else if(sessionModel.jamTracks() || downloadJamTrack) { closeJamTrack(); } else { - logger.error("don't know how to close open media (backing track?)"); + logger.error("don't know how to close open media (backing track maybe?)"); } + return false; } 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(); @@ -2032,6 +2053,8 @@ } function closeRecording() { + logger.debug("closing recording"); + rest.stopPlayClaimedRecording({id: sessionModel.id(), claimed_recording_id: sessionModel.getCurrentSession().claimed_recording.id}) .done(function() { sessionModel.refreshCurrentSession(); 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/downloadJamTrack.css.scss b/web/app/assets/stylesheets/client/downloadJamTrack.css.scss index 762a4103b..27eb232a0 100644 --- a/web/app/assets/stylesheets/client/downloadJamTrack.css.scss +++ b/web/app/assets/stylesheets/client/downloadJamTrack.css.scss @@ -2,10 +2,8 @@ .download-jamtrack { display:inline-block; + width:100%; - .state { - text-align:center; - } .retry-button { margin-top:20px; } @@ -23,4 +21,57 @@ 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/controllers/api_recurly_controller.rb b/web/app/controllers/api_recurly_controller.rb index 575366d55..c00f85254 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 910ff88b3..36b0596a4 100644 --- a/web/app/controllers/spikes_controller.rb +++ b/web/app/controllers/spikes_controller.rb @@ -47,6 +47,7 @@ class SpikesController < ApplicationController gon.jamTrackId = jamTrack.id gon.jamTrackRightId = jamTrackRight.id + gon.size = params[:size] ? params[:size] : 'large' gon.switchState = params[:state] render :layout => 'web' diff --git a/web/app/views/clients/_download_jamtrack_templates.html.slim b/web/app/views/clients/_download_jamtrack_templates.html.slim index 38aba682b..fef7f2cd8 100644 --- a/web/app/views/clients/_download_jamtrack_templates.html.slim +++ b/web/app/views/clients/_download_jamtrack_templates.html.slim @@ -4,38 +4,83 @@ script type="text/template" id='template-download-jamtrack' script type="text/template" id="template-download-jamtrack-state-no-client" .state-no-client - | To play your JamTrack, launch the JamKazam application and open it while in a session. + .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 - | Your JamTrack is on your system and ready to play. + .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 - .msg - | Your JamTrack is currently being created on the JamKazam server. - .spinner-large + .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 - .msg - | Your JamTrack is currently being downloaded. - .spinner-large + .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 - .msg - | Your JamTrack is being authenticated. - .spinner-large + .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 - .msg - | Initializing JamTrack... - .spinner-large + .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 - .msg - .retry - a.button-orange.retry-button RETRY \ No newline at end of file + .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/_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.erb b/web/app/views/clients/_session.html.erb index 377ec043d..45403f7c9 100644 --- a/web/app/views/clients/_session.html.erb +++ b/web/app/views/clients/_session.html.erb @@ -105,7 +105,7 @@

other audio

-
(No recording loaded)
+
(No audio loaded)
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 index 183ee9bb6..ade3744b5 100644 --- a/web/app/views/spikes/download_jam_track.html.slim +++ b/web/app/views/spikes/download_jam_track.html.slim @@ -28,7 +28,7 @@ javascript: initialized = true setTimeout(function() { - window.downloadJamTrack = new JK.DownloadJamTrack(data.app, gon.jamTrackId, gon.jamTrackRightId) + 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) diff --git a/web/lib/recurly_client.rb b/web/lib/recurly_client.rb index 4992814cb..e10fa81d6 100644 --- a/web/lib/recurly_client.rb +++ b/web/lib/recurly_client.rb @@ -69,6 +69,7 @@ module JamRuby end def place_order(current_user, jam_track) + jam_track_right = nil account = get_account(current_user) if (account.present?) begin @@ -94,7 +95,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/web/spec/javascripts/download_jamtrack_spec.js.coffee b/web/spec/javascripts/download_jamtrack_spec.js.coffee index 58fbe3427..5eec4ebe9 100644 --- a/web/spec/javascripts/download_jamtrack_spec.js.coffee +++ b/web/spec/javascripts/download_jamtrack_spec.js.coffee @@ -5,6 +5,7 @@ describe "DownloadJamTrack", -> window.jamClient = sinon.stub() this.app = sinon.stub() this.jamTrackId = '1' + this.jamTrack = {id: this.jamTrackId, jam_track_right_id: '1', name: 'Back in Black'} window.gon = {} window.JK.JamServer = {} window.stats = {} @@ -19,7 +20,7 @@ describe "DownloadJamTrack", -> beforeEach -> window.gon.isNativeClient = false @showNoClientSpy = sinon.spy(JK.DownloadJamTrack.prototype, 'showNoClient') - this.downloadJamTrack = new JK.DownloadJamTrack(@app, @jamTrackId, '1') + this.downloadJamTrack = new JK.DownloadJamTrack(@app, @jamTrack) $('body').append(this.downloadJamTrack.root) afterEach -> @@ -44,7 +45,7 @@ describe "DownloadJamTrack", -> describe "already synchronized", -> beforeEach -> @showSynchronizedSpy = sinon.spy(JK.DownloadJamTrack.prototype, 'showSynchronized') - @downloadJamTrack = new JK.DownloadJamTrack(@app, @jamTrackId, '1') + @downloadJamTrack = new JK.DownloadJamTrack(@app, @jamTrack) $('body').append(@downloadJamTrack.root) afterEach -> @@ -71,7 +72,7 @@ describe "DownloadJamTrack", -> @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, @jamTrackId, '1') + @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) @@ -145,7 +146,7 @@ describe "DownloadJamTrack", -> @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, @jamTrackId, '1') + @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) From 38849a1c443691b7cf9d9e795b8ce4f89adcdd3d Mon Sep 17 00:00:00 2001 From: Seth Call Date: Thu, 19 Feb 2015 15:56:42 -0600 Subject: [PATCH 12/23] VRFS-2804 - fix code to find the jamtrack ID correctly based on recent client changes --- web/app/assets/javascripts/session.js | 11 +++-------- web/lib/tasks/sample_data.rake | 2 +- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/web/app/assets/javascripts/session.js b/web/app/assets/javascripts/session.js index 3a759149c..364f9ce9e 100644 --- a/web/app/assets/javascripts/session.js +++ b/web/app/assets/javascripts/session.js @@ -1100,19 +1100,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", @@ -2357,7 +2352,7 @@ downloadJamTrack = null } - downloadJamTrack = new context.JK.DownloadJamTrack(app, jamTrack.id, 'large'); + 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) { diff --git a/web/lib/tasks/sample_data.rake b/web/lib/tasks/sample_data.rake index ff107ac00..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 +#require './spec/factories.rb' # useful when run on a server namespace :db do desc "Add a simple one track recording to the database" From 0472d6bc79b727d65a8b7d37df30e59be5688647 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Fri, 20 Feb 2015 11:55:49 -0600 Subject: [PATCH 13/23] * VRFS-2686 - jamtrack sliders work --- .../assets/javascripts/playbackControls.js | 32 ++++++++++++++++--- web/app/assets/javascripts/session.js | 15 +++++++++ 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/web/app/assets/javascripts/playbackControls.js b/web/app/assets/javascripts/playbackControls.js index e05c185ae..f54e31669 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; } } @@ -144,16 +145,30 @@ }); 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 + } + 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 +190,10 @@ } } + + if(currentTimeMs < offsetStart) { + currentTimeMs = 0; // this is to squelch movement during tap-in period + } updateDurationTime(durationTimeMs); updateCurrentTime(currentTimeMs); updateIsPlaying(isPlaying); @@ -247,7 +266,10 @@ } } - function startMonitor() { + function startMonitor(_jamTrackMode) { + + jamTrackMode = !!_jamTrackMode; + monitorRecordingPlayback(); } diff --git a/web/app/assets/javascripts/session.js b/web/app/assets/javascripts/session.js index 364f9ce9e..937c83bdb 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; @@ -462,6 +463,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() { @@ -2619,7 +2628,13 @@ 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() { From 9dbf1a3e56671f71e46efab04df0c915da94d7e5 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Mon, 23 Feb 2015 09:12:34 -0600 Subject: [PATCH 14/23] * VRFS-2634 adding version to jamtrack --- admin/app/admin/jam_tracks.rb | 1 + admin/app/views/admin/jam_tracks/_form.html.slim | 3 ++- db/manifest | 3 ++- db/up/jam_track_version.sql | 1 + ruby/lib/jam_ruby/jam_tracks_manager.rb | 3 ++- ruby/lib/jam_ruby/models/jam_track.rb | 3 ++- web/app/assets/javascripts/download_jamtrack.js.coffee | 8 ++++++++ web/app/views/api_jam_tracks/show.rabl | 2 +- web/app/views/api_jam_tracks/show_for_client.rabl | 2 +- 9 files changed, 20 insertions(+), 6 deletions(-) create mode 100644 db/up/jam_track_version.sql 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..3950ee86f 100644 --- a/admin/app/views/admin/jam_tracks/_form.html.slim +++ b/admin/app/views/admin/jam_tracks/_form.html.slim @@ -7,6 +7,8 @@ | 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 :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 @@ -24,7 +26,6 @@ = 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.semantic_fields_for :jam_track_tracks do |track| diff --git a/db/manifest b/db/manifest index 55f1373b5..9bb10cf65 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 +jam_track_version.sql \ 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/ruby/lib/jam_ruby/jam_tracks_manager.rb b/ruby/lib/jam_ruby/jam_tracks_manager.rb index a5c04cd61..9618ab3c4 100644 --- a/ruby/lib/jam_ruby/jam_tracks_manager.rb +++ b/ruby/lib/jam_ruby/jam_tracks_manager.rb @@ -42,10 +42,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/models/jam_track.rb b/ruby/lib/jam_ruby/models/jam_track.rb index 3d884ca3a..552b4238b 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, 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]} diff --git a/web/app/assets/javascripts/download_jamtrack.js.coffee b/web/app/assets/javascripts/download_jamtrack.js.coffee index 77fa5aa34..162a1772a 100644 --- a/web/app/assets/javascripts/download_jamtrack.js.coffee +++ b/web/app/assets/javascripts/download_jamtrack.js.coffee @@ -51,6 +51,7 @@ context.JK.DownloadJamTrack = class DownloadJamTrack throw "no JamTrack specified" unless @jamTrack? throw "invalid size" if @size != 'large' && @size != 'small' + throw "no JamTrack version" unless @jamTrack.version? @path = [] @states = { @@ -342,6 +343,13 @@ context.JK.DownloadJamTrack = class DownloadJamTrack @logger.debug("DownloadJamTrack: JamTrackGetTrackDetail.key_state: " + @trackDetail.key_state) + # first check if the version is not the same; if so, invalidate. + + if @jamTrack.version != @trackDetail.version + @logger.info("DownloadJamTrack: JamTrack on disk is different version. Invalidating") + context.jamClient.InvalidateJamTrack(@jamTrack.id) + @trackDetail = context.jamClient.JamTrackGetTrackDetail (@jamTrack.id) + switch @trackDetail.key_state when 'pending' this.transition(@states.keying) 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 1b2db2fe6..7d2b38851 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,6 @@ object @jam_track -attributes :id, :name, :description, :initial_play_silence, :original_artist +attributes :id, :name, :description, :initial_play_silence, :original_artist, :version node :jam_track_right_id do |jam_track| jam_track.right_for_user(current_user).id From c8752fd8303b829ceb50d06ad8b76e8409abd96d Mon Sep 17 00:00:00 2001 From: Seth Call Date: Mon, 23 Feb 2015 16:55:19 -0600 Subject: [PATCH 15/23] * VRFS-2634 - add version check to jamtrack packages --- .../javascripts/download_jamtrack.js.coffee | 2 +- .../download_jamtrack_spec.js.coffee | 30 ++++++++++++------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/web/app/assets/javascripts/download_jamtrack.js.coffee b/web/app/assets/javascripts/download_jamtrack.js.coffee index 162a1772a..f98598fba 100644 --- a/web/app/assets/javascripts/download_jamtrack.js.coffee +++ b/web/app/assets/javascripts/download_jamtrack.js.coffee @@ -346,7 +346,7 @@ context.JK.DownloadJamTrack = class DownloadJamTrack # first check if the version is not the same; if so, invalidate. if @jamTrack.version != @trackDetail.version - @logger.info("DownloadJamTrack: JamTrack on disk is different version. Invalidating") + @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) diff --git a/web/spec/javascripts/download_jamtrack_spec.js.coffee b/web/spec/javascripts/download_jamtrack_spec.js.coffee index 5eec4ebe9..4c358b324 100644 --- a/web/spec/javascripts/download_jamtrack_spec.js.coffee +++ b/web/spec/javascripts/download_jamtrack_spec.js.coffee @@ -5,7 +5,7 @@ describe "DownloadJamTrack", -> window.jamClient = sinon.stub() this.app = sinon.stub() this.jamTrackId = '1' - this.jamTrack = {id: this.jamTrackId, jam_track_right_id: '1', name: 'Back in Black'} + this.jamTrack = {id: this.jamTrackId, jam_track_right_id: '1', name: 'Back in Black', version:'1'} window.gon = {} window.JK.JamServer = {} window.stats = {} @@ -20,7 +20,7 @@ describe "DownloadJamTrack", -> beforeEach -> window.gon.isNativeClient = false @showNoClientSpy = sinon.spy(JK.DownloadJamTrack.prototype, 'showNoClient') - this.downloadJamTrack = new JK.DownloadJamTrack(@app, @jamTrack) + @downloadJamTrack = new JK.DownloadJamTrack(@app, @jamTrack) $('body').append(this.downloadJamTrack.root) afterEach -> @@ -28,6 +28,7 @@ describe "DownloadJamTrack", -> 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(); @@ -54,11 +55,13 @@ describe "DownloadJamTrack", -> it "shows synchronized state", -> window.jamClient.JamTrackGetTrackDetail = sinon.stub() - window.jamClient.JamTrackGetTrackDetail.returns({'key_state' : 'ready'}) + 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) @@ -84,7 +87,8 @@ describe "DownloadJamTrack", -> it "shows errored state due to timeout", -> window.jamClient.JamTrackGetTrackDetail = sinon.stub() - window.jamClient.JamTrackGetTrackDetail.returns({'key_state' : 'pending'}) + window.jamClient.JamTrackGetTrackDetail.returns({'key_state' : 'pending', 'version' : '1'}) + window.jamClient.InvalidateJamTrack = sinon.stub() @downloadJamTrack.init(); @@ -115,7 +119,8 @@ describe "DownloadJamTrack", -> it "shows synchronized", -> window.jamClient.JamTrackGetTrackDetail = sinon.stub() - window.jamClient.JamTrackGetTrackDetail.returns({'key_state' : 'pending'}) + window.jamClient.JamTrackGetTrackDetail.returns({'key_state' : 'pending', 'version' : '1'}) + window.jamClient.InvalidateJamTrack = sinon.stub() @downloadJamTrack.init() @@ -126,7 +131,8 @@ describe "DownloadJamTrack", -> expect(@downloadJamTrack.states.keying.timer).toNotBe(null) # say the keys have been fetched - window.jamClient.JamTrackGetTrackDetail.returns({'key_state' : 'ready'}) + window.jamClient.JamTrackGetTrackDetail.returns({'key_state' : 'ready', 'version' : '1'}) + window.jamClient.InvalidateJamTrack = sinon.stub() # then do a check @downloadJamTrack.stateIntervalCheck() @@ -161,7 +167,8 @@ describe "DownloadJamTrack", -> it "shows downloading for signed package", -> window.jamClient.JamTrackKeysRequest = sinon.stub() window.jamClient.JamTrackGetTrackDetail = sinon.stub() - window.jamClient.JamTrackGetTrackDetail.returns({'key_state' : 'unknown'}) + window.jamClient.JamTrackGetTrackDetail.returns({'key_state' : 'unknown', 'version' : '1'}) + window.jamClient.InvalidateJamTrack = sinon.stub() spyOn(@downloadJamTrack.rest, 'getJamTrackRight').andCallFake((data) => d = $.Deferred(); @@ -187,7 +194,8 @@ describe "DownloadJamTrack", -> expect(@downloadJamTrack.states.keying.timer).toNotBe(null) # say the keys have been fetched - window.jamClient.JamTrackGetTrackDetail.returns({'key_state' : 'ready'}) + window.jamClient.JamTrackGetTrackDetail.returns({'key_state' : 'ready', 'version' : '1'}) + window.jamClient.InvalidateJamTrack = sinon.stub() # check state again @downloadJamTrack.stateIntervalCheck() @@ -200,7 +208,8 @@ describe "DownloadJamTrack", -> it "is not yet packaged", -> window.jamClient.JamTrackGetTrackDetail = sinon.stub() - window.jamClient.JamTrackGetTrackDetail.returns({'key_state' : 'unknown'}) + window.jamClient.JamTrackGetTrackDetail.returns({'key_state' : 'unknown', 'version' : '1'}) + window.jamClient.InvalidateJamTrack = sinon.stub() spyOn(@downloadJamTrack.rest, 'getJamTrackRight').andCallFake((data) => d = $.Deferred(); @@ -250,7 +259,8 @@ describe "DownloadJamTrack", -> it "queue time out when packaging", -> window.jamClient.JamTrackGetTrackDetail = sinon.stub() - window.jamClient.JamTrackGetTrackDetail.returns({'key_state' : 'unknown'}) + window.jamClient.JamTrackGetTrackDetail.returns({'key_state' : 'unknown', 'version' : '1'}) + window.jamClient.InvalidateJamTrack = sinon.stub() getJamTrackRightSpy = spyOn(@downloadJamTrack.rest, 'getJamTrackRight') getJamTrackRightSpy.andCallFake((data) => From 4841e56dcefd7b1eb15f0b2dceb9eedd8c0fc2e3 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Mon, 23 Feb 2015 16:56:21 -0600 Subject: [PATCH 16/23] * VRFS-2634 - add version check to jamtrack packages --- web/app/assets/javascripts/download_jamtrack.js.coffee | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/web/app/assets/javascripts/download_jamtrack.js.coffee b/web/app/assets/javascripts/download_jamtrack.js.coffee index f98598fba..244466b83 100644 --- a/web/app/assets/javascripts/download_jamtrack.js.coffee +++ b/web/app/assets/javascripts/download_jamtrack.js.coffee @@ -350,6 +350,10 @@ context.JK.DownloadJamTrack = class DownloadJamTrack context.jamClient.InvalidateJamTrack(@jamTrack.id) @trackDetail = context.jamClient.JamTrackGetTrackDetail (@jamTrack.id) + if @jamTrack.version != @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) From dbaeb8d9968d014b4478ddb2abcadd6356ecc24a Mon Sep 17 00:00:00 2001 From: Seth Call Date: Wed, 25 Feb 2015 10:43:21 -0600 Subject: [PATCH 17/23] merged --- db/manifest | 3 +- db/up/recorded_jam_track_tracks.sql | 15 +++++++++ ruby/lib/jam_ruby.rb | 1 + ruby/lib/jam_ruby/models/jam_track.rb | 2 ++ ruby/lib/jam_ruby/models/jam_track_track.rb | 2 ++ .../models/recorded_jam_track_track.rb | 21 +++++++++++++ ruby/lib/jam_ruby/models/recording.rb | 31 ++++++++++++++++++- ruby/lib/jam_ruby/models/user.rb | 4 +++ ruby/spec/factories.rb | 7 ++++- ruby/spec/jam_ruby/models/recording_spec.rb | 27 ++++++++++++++++ web/app/assets/javascripts/jam_rest.js | 21 ++++++++++--- web/app/assets/javascripts/recordingModel.js | 1 - web/app/assets/javascripts/session.js | 19 ++++++++++++ web/app/assets/javascripts/sessionModel.js | 6 ++++ .../controllers/api_recordings_controller.rb | 9 +++++- web/app/views/api_music_sessions/show.rabl | 2 +- web/config/routes.rb | 1 + 17 files changed, 161 insertions(+), 11 deletions(-) create mode 100644 db/up/recorded_jam_track_tracks.sql create mode 100644 ruby/lib/jam_ruby/models/recorded_jam_track_track.rb diff --git a/db/manifest b/db/manifest index 9bb10cf65..fbdb8d856 100755 --- a/db/manifest +++ b/db/manifest @@ -253,4 +253,5 @@ recorded_backing_tracks.sql recorded_backing_tracks_add_filename.sql user_syncs_include_backing_tracks.sql remove_bpm_from_jamtracks.sql -jam_track_version.sql \ No newline at end of file +jam_track_version.sql +recorded_jam_track_tracks.sql \ No newline at end of file diff --git a/db/up/recorded_jam_track_tracks.sql b/db/up/recorded_jam_track_tracks.sql new file mode 100644 index 000000000..504c5e6ad --- /dev/null +++ b/db/up/recorded_jam_track_tracks.sql @@ -0,0 +1,15 @@ +ALTER TABLE recordings ADD COLUMN jam_track_id BIGINT REFERENCES jam_tracks(id); +ALTER TABLE recordings ADD COLUMN jam_track_initiator_id VARCHAR(64) REFERENCES users(id); + +CREATE TABLE recorded_jam_track_tracks ( + id BIGINT PRIMARY KEY, + user_id VARCHAR(64) NOT NULL REFERENCES users(id) ON DELETE CASCADE, + jam_track_track_id VARCHAR(64) REFERENCES jam_track_tracks(id) NOT NULL, + recording_id VARCHAR(64) REFERENCES recordings(id) NOT NULL, + discard BOOLEAN, + timeline JSON, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +ALTER TABLE recorded_jam_track_tracks ALTER COLUMN id SET DEFAULT nextval('tracks_next_tracker_seq'); \ No newline at end of file diff --git a/ruby/lib/jam_ruby.rb b/ruby/lib/jam_ruby.rb index b94d7d9a2..f878bac21 100755 --- a/ruby/lib/jam_ruby.rb +++ b/ruby/lib/jam_ruby.rb @@ -138,6 +138,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" diff --git a/ruby/lib/jam_ruby/models/jam_track.rb b/ruby/lib/jam_ruby/models/jam_track.rb index 552b4238b..18f8e77a5 100644 --- a/ruby/lib/jam_ruby/models/jam_track.rb +++ b/ruby/lib/jam_ruby/models/jam_track.rb @@ -51,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 diff --git a/ruby/lib/jam_ruby/models/jam_track_track.rb b/ruby/lib/jam_ruby/models/jam_track_track.rb index 978ed29ab..c8d7c523f 100644 --- a/ruby/lib/jam_ruby/models/jam_track_track.rb +++ b/ruby/lib/jam_ruby/models/jam_track_track.rb @@ -21,6 +21,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/recorded_jam_track_track.rb b/ruby/lib/jam_ruby/models/recorded_jam_track_track.rb new file mode 100644 index 000000000..f77615beb --- /dev/null +++ b/ruby/lib/jam_ruby/models/recorded_jam_track_track.rb @@ -0,0 +1,21 @@ +module JamRuby + # BackingTrack analog to JamRuby::RecordedTrack + class RecordedJamTrackTrack < ActiveRecord::Base + + belongs_to :user, :class_name => "JamRuby::User", :inverse_of => :recorded_jam_track_tracks + belongs_to :recording, :class_name => "JamRuby::Recording", :inverse_of => :recorded_jam_track_tracks + belongs_to :jam_track_track, :class_name => "JamRuby::JamTrackTrack", :inverse_of => :recorded_jam_track_tracks + + validates :user, presence: true + validates :jam_track_track, presence:true + + def self.create_from_jam_track_track(jam_track_track, recording) + 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 +end diff --git a/ruby/lib/jam_ruby/models/recording.rb b/ruby/lib/jam_ruby/models/recording.rb index 69d690962..bee8a2f95 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 => :recorded_jam_tracks, :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_tracks << RecordedJamTrack.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/spec/factories.rb b/ruby/spec/factories.rb index 42418a699..0da938fc1 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/jam_rest.js b/web/app/assets/javascripts/jam_rest.js index df18a8070..dca216937 100644 --- a/web/app/assets/javascripts/jam_rest.js +++ b/web/app/assets/javascripts/jam_rest.js @@ -1585,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; @@ -1734,6 +1744,7 @@ this.createSourceChange = createSourceChange; this.validateUrlSite = validateUrlSite; this.markRecordedBackingTrackSilent = markRecordedBackingTrackSilent; + this.addRecordingTimeline = addRecordingTimeline; return this; }; 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 937c83bdb..0bcb6d5f6 100644 --- a/web/app/assets/javascripts/session.js +++ b/web/app/assets/javascripts/session.js @@ -300,6 +300,21 @@ displayStoppingRecording(data); }) .on('stoppedRecording', function(e, data) { + + 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; @@ -1056,6 +1071,7 @@ // Default trackData to participant + no Mixer state. var trackData = { + type: 'backing_track', trackId: backingTrack.id, clientId: backingTrack.client_id, name: 'Backing', @@ -1140,6 +1156,7 @@ // Default trackData to participant + no Mixer state. var trackData = { + type: 'jam_track', trackId: oneOfTheTracks.id, clientId: oneOfTheTracks.client_id, name: name, @@ -1241,6 +1258,7 @@ // Default trackData to participant + no Mixer state. var trackData = { + type: 'metronome', trackId: "MS" + oneOfTheTracks.id, clientId: oneOfTheTracks.client_id, name: "Metronome", @@ -1340,6 +1358,7 @@ // Default trackData to participant + no Mixer state. var trackData = { + type: 'recorded_track', trackId: oneOfTheTracks.id, clientId: oneOfTheTracks.client_id, name: name, diff --git a/web/app/assets/javascripts/sessionModel.js b/web/app/assets/javascripts/sessionModel.js index 7487dec2d..b9fd3ea35 100644 --- a/web/app/assets/javascripts/sessionModel.js +++ b/web/app/assets/javascripts/sessionModel.js @@ -113,6 +113,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 @@ -830,6 +835,7 @@ this.isMasterMixMode = isMasterMixMode; this.isPersonalMixMode = isPersonalMixMode; this.getMixMode = getMixMode; + this.selfOpenedJamTracks = selfOpenedJamTracks; // ALERT HANDLERS this.onBackendMixerChanged = onBackendMixerChanged; diff --git a/web/app/controllers/api_recordings_controller.rb b/web/app/controllers/api_recordings_controller.rb index 5caa975a8..89923b89d 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/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/config/routes.rb b/web/config/routes.rb index 1613bf522..00fd25f38 100644 --- a/web/config/routes.rb +++ b/web/config/routes.rb @@ -451,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' From ca9e3706c7b2bacfa7e321da569a946009e55e47 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Wed, 25 Feb 2015 16:13:04 -0600 Subject: [PATCH 18/23] * wip --- ruby/lib/jam_ruby/models/mix.rb | 27 ++++++++++++++++-- web/app/assets/javascripts/faderHelpers.js | 22 +++++++++++---- web/app/assets/javascripts/session.js | 33 ++++++++++++++++++++++ web/app/assets/javascripts/sessionModel.js | 17 +++++++++++ web/app/views/clients/_help.html.slim | 3 ++ 5 files changed, 95 insertions(+), 7 deletions(-) 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/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/session.js b/web/app/assets/javascripts/session.js index 0bcb6d5f6..b4195a9ce 100644 --- a/web/app/assets/javascripts/session.js +++ b/web/app/assets/javascripts/session.js @@ -249,6 +249,7 @@ $(sessionModel.recordingModel) .on('startingRecording', function(e, data) { displayStartingRecording(); + lockControlsforJamTrackRecording(); }) .on('startedRecording', function(e, data) { if(data.reason) { @@ -294,13 +295,16 @@ { 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(); @@ -1776,6 +1780,7 @@ } 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) @@ -1818,6 +1823,7 @@ var template = $('#template-session-track').html(); var newTrack = $(context.JK.fillTemplate(template, trackData)); + 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')}); @@ -2059,6 +2065,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')}) @@ -2066,6 +2077,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 @@ -2297,6 +2310,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}) @@ -2657,6 +2678,18 @@ } 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(); } diff --git a/web/app/assets/javascripts/sessionModel.js b/web/app/assets/javascripts/sessionModel.js index b9fd3ea35..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; @@ -157,6 +158,18 @@ return inSession; } + function lockControlsforJamTrackRecording() { + controlsLockedForJamTrackRecording = true; + } + + function unlockControlsforJamTrackRecording() { + controlsLockedForJamTrackRecording = false; + } + + function areControlsLockedForJamTrackRecording() { + return controlsLockedForJamTrackRecording; + } + function onMixerModeChanged(newMixerMode) { mixerMode = newMixerMode; @@ -357,6 +370,7 @@ previousBackingTracks = [] openBackingTrack = null shownAudioMediaMixerHelp = false + controlsLockedForJamTrackRecording = false; } // you should only update currentSession with this function @@ -836,6 +850,9 @@ 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/views/clients/_help.html.slim b/web/app/views/clients/_help.html.slim index 4fda8bda3..b82a98fbf 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 master mix controls, so any change here will also affect everyone in the session. From 4a7d6925da74569a50b95004a7f7cb90715fa929 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Wed, 25 Feb 2015 19:56:26 -0600 Subject: [PATCH 19/23] * wip --- ruby/lib/jam_ruby/models/recording.rb | 4 +- .../assets/javascripts/playbackControls.js | 16 +- .../views/api_claimed_recordings/show.rabl | 11 +- web/app/views/api_recordings/show.rabl | 11 +- .../api_recordings_controller_spec.rb | 432 ++++++++++-------- 5 files changed, 269 insertions(+), 205 deletions(-) diff --git a/ruby/lib/jam_ruby/models/recording.rb b/ruby/lib/jam_ruby/models/recording.rb index bee8a2f95..c4537c94d 100644 --- a/ruby/lib/jam_ruby/models/recording.rb +++ b/ruby/lib/jam_ruby/models/recording.rb @@ -22,7 +22,7 @@ module JamRuby 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 => :recorded_jam_tracks, :foreign_key => 'jam_track_initiator_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 @@ -229,7 +229,7 @@ module JamRuby if music_session.jam_track music_session.jam_track.jam_track_tracks.each do |jam_track_track| - recording.recorded_jam_tracks << RecordedJamTrack.create_from_jam_track_track(jam_track_track, recording) + 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 diff --git a/web/app/assets/javascripts/playbackControls.js b/web/app/assets/javascripts/playbackControls.js index f54e31669..f88d541c6 100644 --- a/web/app/assets/javascripts/playbackControls.js +++ b/web/app/assets/javascripts/playbackControls.js @@ -101,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) { diff --git a/web/app/views/api_claimed_recordings/show.rabl b/web/app/views/api_claimed_recordings/show.rabl index aa7de2609..5803d4da4 100644 --- a/web/app/views/api_claimed_recordings/show.rabl +++ b/web/app/views/api_claimed_recordings/show.rabl @@ -20,7 +20,7 @@ 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(:band => :band) { attributes :id, :name, :location, :photo_url @@ -50,6 +50,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_recordings/show.rabl b/web/app/views/api_recordings/show.rabl index f3c817b07..cda9e1251 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 @@ -33,6 +33,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/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 + From a02f7c2dfb53e87445ca50237d39a9ed9a35bf52 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Wed, 25 Feb 2015 22:04:58 -0600 Subject: [PATCH 20/23] * VRFS-2839 - add check to admin --- admin/app/controllers/checks_controller.rb | 19 +++++++++++++++++++ admin/config/routes.rb | 1 + 2 files changed, 20 insertions(+) create mode 100644 admin/app/controllers/checks_controller.rb diff --git a/admin/app/controllers/checks_controller.rb b/admin/app/controllers/checks_controller.rb new file mode 100644 index 000000000..52330c2aa --- /dev/null +++ b/admin/app/controllers/checks_controller.rb @@ -0,0 +1,19 @@ +class ChecksController < ApplicationController + + #respond_to :json + + # create or update a client_artifact row + def check_latency_tester + + latency_tester_name = params[:name] + + exists = Connection.where(client_type: Connection::TYPE_LATENCY_TESTER).where(client_id: latency_tester_name).first + + if exists + render :text => "", :status => :ok + else + render :text => "", :status => 404 + end + end + +end \ No newline at end of file diff --git a/admin/config/routes.rb b/admin/config/routes.rb index 05aed06bc..bae57c044 100644 --- a/admin/config/routes.rb +++ b/admin/config/routes.rb @@ -32,6 +32,7 @@ JamAdmin::Application.routes.draw do match '/api/artifacts' => 'artifacts#update_artifacts', :via => :post match '/api/mix/:id/enqueue' => 'admin/mixes#mix_again', :via => :post + match '/api/checks/latency_tester' => 'checks#check_latency_tester', :via => :get mount Resque::Server.new, :at => "/resque" From 8205b5ea58f62c23fa032f9bf3cb81d2a7a2d655 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Fri, 27 Feb 2015 08:37:12 -0600 Subject: [PATCH 21/23] * VRFS-2841 - load jmep into the backend --- .../views/admin/jam_tracks/_form.html.slim | 4 +- admin/config/application.rb | 3 + admin/config/initializers/jam_tracks.rb | 25 +++++++++ db/manifest | 3 +- db/up/jam_track_jmep_data.sql | 2 + ruby/lib/jam_ruby.rb | 1 + ruby/lib/jam_ruby/jam_tracks_manager.rb | 1 - ruby/lib/jam_ruby/jmep_manager.rb | 55 +++++++++++++++++++ ruby/lib/jam_ruby/models/jam_track.rb | 3 +- .../javascripts/download_jamtrack.js.coffee | 15 ++--- .../assets/javascripts/playbackControls.js | 1 + web/app/assets/javascripts/session.js | 11 +++- .../views/api_claimed_recordings/show.rabl | 10 +++- .../views/api_jam_tracks/show_for_client.rabl | 4 ++ web/app/views/api_recordings/show.rabl | 7 +++ web/config/application.rb | 2 + 16 files changed, 134 insertions(+), 13 deletions(-) create mode 100644 admin/config/initializers/jam_tracks.rb create mode 100644 db/up/jam_track_jmep_data.sql create mode 100644 ruby/lib/jam_ruby/jmep_manager.rb diff --git a/admin/app/views/admin/jam_tracks/_form.html.slim b/admin/app/views/admin/jam_tracks/_form.html.slim index 3950ee86f..9ec25ffcf 100644 --- a/admin/app/views/admin/jam_tracks/_form.html.slim +++ b/admin/app/views/admin/jam_tracks/_form.html.slim @@ -9,7 +9,7 @@ = f.input :description, :input_html => { :rows=>5, :maxlength=>1000 } = 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 :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 @@ -27,6 +27,8 @@ = f.input :licensor_royalty_amount, :required=>true, :input_html=>{type:'numeric'} = f.input :pro_royalty_amount, :required=>true, :input_html=>{type:'numeric'} = 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/db/manifest b/db/manifest index fbdb8d856..2cafa6501 100755 --- a/db/manifest +++ b/db/manifest @@ -254,4 +254,5 @@ recorded_backing_tracks_add_filename.sql user_syncs_include_backing_tracks.sql remove_bpm_from_jamtracks.sql jam_track_version.sql -recorded_jam_track_tracks.sql \ No newline at end of file +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/ruby/lib/jam_ruby.rb b/ruby/lib/jam_ruby.rb index f878bac21..a2dc1f4e7 100755 --- a/ruby/lib/jam_ruby.rb +++ b/ruby/lib/jam_ruby.rb @@ -204,6 +204,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 9618ab3c4..37003d115 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) 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="" 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 18f8e77a5..fa57b623f 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, :version, 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} @@ -152,6 +152,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/web/app/assets/javascripts/download_jamtrack.js.coffee b/web/app/assets/javascripts/download_jamtrack.js.coffee index 244466b83..6e09d8bc5 100644 --- a/web/app/assets/javascripts/download_jamtrack.js.coffee +++ b/web/app/assets/javascripts/download_jamtrack.js.coffee @@ -345,14 +345,15 @@ context.JK.DownloadJamTrack = class DownloadJamTrack # first check if the version is not the same; if so, invalidate. - 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? if @jamTrack.version != @trackDetail.version - @logger.error("after invalidating package, the version is still wrong!") - throw "after invalidating package, the version is still wrong!" + @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' diff --git a/web/app/assets/javascripts/playbackControls.js b/web/app/assets/javascripts/playbackControls.js index f88d541c6..4489f0c71 100644 --- a/web/app/assets/javascripts/playbackControls.js +++ b/web/app/assets/javascripts/playbackControls.js @@ -162,6 +162,7 @@ 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(); diff --git a/web/app/assets/javascripts/session.js b/web/app/assets/javascripts/session.js index b4195a9ce..75d427266 100644 --- a/web/app/assets/javascripts/session.js +++ b/web/app/assets/javascripts/session.js @@ -307,7 +307,7 @@ unlockControlsforJamTrackRecording(); if(sessionModel.selfOpenedJamTracks()) { - var timeline = context.jamClient.getJamTrackTimeline(); + var timeline = context.jamClient.GetJamTrackTimeline(); rest.addRecordingTimeline(data.recordingId, timeline) .fail(function(){ @@ -2415,6 +2415,15 @@ // 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); diff --git a/web/app/views/api_claimed_recordings/show.rabl b/web/app/views/api_claimed_recordings/show.rabl index 5803d4da4..91174f87b 100644 --- a/web/app/views/api_claimed_recordings/show.rabl +++ b/web/app/views/api_claimed_recordings/show.rabl @@ -21,7 +21,15 @@ end child(:recording => :recording) { 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 } 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 7d2b38851..364b13031 100644 --- a/web/app/views/api_jam_tracks/show_for_client.rabl +++ b/web/app/views/api_jam_tracks/show_for_client.rabl @@ -2,6 +2,10 @@ object @jam_track 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 diff --git a/web/app/views/api_recordings/show.rabl b/web/app/views/api_recordings/show.rabl index cda9e1251..a717a6bfb 100644 --- a/web/app/views/api_recordings/show.rabl +++ b/web/app/views/api_recordings/show.rabl @@ -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 diff --git a/web/config/application.rb b/web/config/application.rb index b2fd02ae1..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 From a2e28cfe75d4939c6ef640496264bcd1fb4c42f9 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Sat, 28 Feb 2015 02:21:38 -0500 Subject: [PATCH 22/23] VRFS-2701 display online presences on about tab --- web/app/assets/javascripts/profile.js | 70 ++++++++++++++++++--- web/app/assets/javascripts/profile_utils.js | 2 +- web/app/views/clients/_profile.html.erb | 24 ++++--- 3 files changed, 78 insertions(+), 18 deletions(-) diff --git a/web/app/assets/javascripts/profile.js b/web/app/assets/javascripts/profile.js index 20efadbf6..e59fb8f9d 100644 --- a/web/app/assets/javascripts/profile.js +++ b/web/app/assets/javascripts/profile.js @@ -39,9 +39,9 @@ var $reverbNationPresence = $screen.find('#reverbnation-presence'); var $bandCampPresence = $screen.find('#bandcamp-presence'); var $fandalismPresence = $screen.find('#fandalism-presence'); - var $soundCloudPresence = $screen.find('#youtube-presence'); - var $soundCloudPresence = $screen.find('#facebook-presence'); - var $youTubePresence = $screen.find('#twitter-presence'); + var $youTubePresence = $screen.find('#youtube-presence'); + var $facebookPresence = $screen.find('#facebook-presence'); + var $twitterPresence = $screen.find('#twitter-presence'); // current interests var $noInterests = $screen.find('#no-interests'); @@ -519,15 +519,67 @@ $noOnlinePresence.show(); } else { - $noOnlinePresence.hide(); + $noOnlinePresence.hide(); - if (user.website) { - $userWebsite.append(""); - } + if (user.website) { + $userWebsite.find('a').attr('href', user.website); + } - $.each(onlinePresences, function(index, presence) { + var soundCloudPresences = profileUtils.soundCloudPresences(onlinePresences); + if (soundCloudPresences && soundCloudPresences.length > 0) { + $soundCloudPresence.find('a').attr('href', 'http://www.soundcloud.com/' + soundCloudPresences[0].username); + } + else { + $soundCloudPresence.hide(); + } - }); + var reverbNationPresences = profileUtils.reverbNationPresences(onlinePresences); + if (reverbNationPresences && reverbNationPresences.length > 0) { + $reverbNationPresence.find('a').attr('href', 'http://www.reverbnation.com/' + reverbNationPresences[0].username); + } + else { + $reverbNationPresence.hide(); + } + + var bandCampPresences = profileUtils.bandCampPresences(onlinePresences); + if (bandCampPresences && bandCampPresences.length > 0) { + $bandCampPresence.find('a').attr('href', 'http://' + bandCampPresences[0].username + '.bandcamp.com/'); + } + else { + $bandCampPresence.hide(); + } + + var fandalismPresences = profileUtils.fandalismPresences(onlinePresences); + if (fandalismPresences && fandalismPresences.length > 0) { + $fandalismPresence.find('a').attr('href', 'http://www.fandalism.com/' + fandalismPresences[0].username); + } + else { + $fandalismPresence.hide(); + } + + var youTubePresences = profileUtils.youTubePresences(onlinePresences); + if (youTubePresences && youTubePresences.length > 0) { + $youTubePresence.find('a').attr('href', 'http://www.youtube.com/' + youTubePresences[0].username); + } + else { + $youTubePresence.hide(); + } + + var facebookPresences = profileUtils.facebookPresences(onlinePresences); + if (facebookPresences && facebookPresences.length > 0) { + $facebookPresence.find('a').attr('href', 'http://www.facebook.com/' + facebookPresences[0].username); + } + else { + $facebookPresence.hide(); + } + + var twitterPresences = profileUtils.twitterPresences(onlinePresences); + if (twitterPresences && twitterPresences.length > 0) { + $twitterPresence.find('a').attr('href', 'http://www.twitter.com/' + twitterPresences[0].username); + } + else { + $twitterPresence.hide(); + } } // current interests diff --git a/web/app/assets/javascripts/profile_utils.js b/web/app/assets/javascripts/profile_utils.js index 32b5ce542..57b51448f 100644 --- a/web/app/assets/javascripts/profile_utils.js +++ b/web/app/assets/javascripts/profile_utils.js @@ -232,7 +232,7 @@ profileUtils.twitterPresences = function(presences) { var matches = $.grep(presences, function(p) { - return s.service_type === ONLINE_PRESENCE_TYPES.TWITTER.description; + return p.service_type === ONLINE_PRESENCE_TYPES.TWITTER.description; }); return matches; diff --git a/web/app/views/clients/_profile.html.erb b/web/app/views/clients/_profile.html.erb index e95e22c45..a51f9fedb 100644 --- a/web/app/views/clients/_profile.html.erb +++ b/web/app/views/clients/_profile.html.erb @@ -137,28 +137,36 @@
None specified
-
+
+
-
+
+
-
+
+
-
+
+
-
+
+
-
+
+
-
+
+
-
+
+

From fa7ec522c505da119b8806512ae9786e47e2333d Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Sat, 28 Feb 2015 02:52:03 -0500 Subject: [PATCH 23/23] VRFS-2701 refactor / added action links to each section --- web/app/assets/javascripts/profile.js | 235 ++++++++++++++---------- web/app/views/clients/_profile.html.erb | 4 +- 2 files changed, 140 insertions(+), 99 deletions(-) diff --git a/web/app/assets/javascripts/profile.js b/web/app/assets/javascripts/profile.js index e59fb8f9d..ad663cbc5 100644 --- a/web/app/assets/javascripts/profile.js +++ b/web/app/assets/javascripts/profile.js @@ -96,6 +96,9 @@ var $btnAddFriend = $screen.find('#btn-add-friend'); var $btnFollowUser = $screen.find('#btn-follow-user'); var $btnMessageUser = $screen.find('#btn-message-user'); + var $btnAddRecordings = $screen.find('.add-recordings'); + var $btnAddSites = $screen.find('.add-sites'); + var $btnAddInterests = $screen.find('.add-interests'); // social var $socialLeft = $screen.find('.profile-social-left'); @@ -426,15 +429,121 @@ function bindAbout() { - $instruments.empty(); + if (!isCurrentUser()) { + $btnAddRecordings.hide(); + $btnAddSites.hide(); + $btnAddInterests.hide(); + } + renderNameLocationStats(); + renderBio(); + renderMusicalExperience(); + renderPerformanceSamples(); + renderOnlinePresence(); + renderInterests(); + } + + function renderNameLocationStats() { // name $userName.html(user.name); // avatar $avatar.attr('src', context.JK.resolveAvatarUrl(user.photo_url)); - // instruments + // location + $location.html(user.location); + + $age.html(user.age ? user.age + " years old" : ""); + + // stats + var text = user.friend_count > 1 || user.friend_count === 0 ? " Friends" : " Friend"; + $friendStats.html('' + user.friend_count + '' + text); + + text = user.follower_count > 1 || user.follower_count === 0 ? " Followers" : " Follower"; + $followerStats.html('' + user.follower_count + '' + text); + + if (isMusician()) { + text = user.session_count > 1 || user.session_count === 0 ? " Sessions" : " Session"; + $sessionStats.html(user.session_count + text); + + text = user.recording_count > 1 || user.recording_count === 0 ? " Recordings" : " Recording"; + $recordingStats.html(user.recording_count + text); + } + else { + text = " Following"; + $followingStats.html(user.following_count + text); + text = user.favorite_count > 1 || user.favorite_count === 0 ? " Favorites" : " Favorite"; + $favoriteStats.html(user.favorite_count + text); + } + } + + function renderBio() { + + function initializeBioVisibility() { + + $showBio.hide(); + $noBio.hide(); + $biographyEditor.hide(); + + $bioTextArea.val(user.biography); + + if(user.biography) { + + $showBio.show(); + $biographyText.text(user.biography).show(); + } + else { + if(isCurrentUser()) { + $noBio.show(); + } + } + } + + initializeBioVisibility(); + + // $addBiographyButton.unbind('click').click(function() { + // $biographyEditor.val(user.biography).show(); + // return false; + // }); + + $submitBiographyButton.unbind('click').click(function() { + var bio = $bioTextArea.val(); + $bioTextArea.closest('div.field').removeClass('error'); + $('.error-text', $bioTextArea.closest('div.field')).remove(); + userDefer = rest.updateUser({ + biography: bio + }) + .done(function(response) { + user = response; + initializeBioVisibility(); + }) + .fail(function(jqXHR) { + if(jqXHR.status == 422) { + var errors = JSON.parse(jqXHR.responseText) + var biography = context.JK.format_errors("biography", errors); + if(biography != null) { + $bioTextArea.closest('div.field').addClass('error').end().after(biography); + } + else { + app.notifyServerError(jqXHR, "Unable to update biography") + } + } + else { + app.notifyServerError(jqXHR, "Unable to update biography") + } + }) + return false; + }) + + $cancelBiographyButton.unbind('click').click(function() { + initializeBioVisibility(); + return false; + }) + } + + function renderMusicalExperience() { + $instruments.empty(); + if (user.instruments) { for (var i = 0; i < user.instruments.length; i++) { var instrument = user.instruments[i]; @@ -471,28 +580,20 @@ // studio gigs var studioGigCount = user.studio_session_count; $studioCount.html(studioGigCount > 0 ? 'Has played ' + profileUtils.gigMap[studioGigCount] + ' studio session gigs' : NOT_SPECIFIED_TEXT); + } - // location - $location.html(user.location); - - $age.html(user.age ? user.age + " years old" : ""); - - // stats - var text = user.friend_count > 1 || user.friend_count === 0 ? " Friends" : " Friend"; - $friendStats.html('' + user.friend_count + '' + text); - - text = user.follower_count > 1 || user.follower_count === 0 ? " Followers" : " Follower"; - $followerStats.html('' + user.follower_count + '' + text); - - // text = user.following_count > 1 || user.following_count === 0 ? " Followings" : " Following"; - // $('#profile-following-stats').html('' + user.following_count + '' + text); - + function renderPerformanceSamples() { // performance samples var performanceSamples = user.performance_samples; if (!performanceSamples || performanceSamples.length === 0) { $noSamples.show(); + + if (isCurrentUser()) { + $btnAddRecordings.show(); + } } else { + $btnAddRecordings.hide(); $noSamples.hide(); // show samples section @@ -512,13 +613,23 @@ $youTubeSamples.append("" + sample.service_id + ""); }); } + } + function renderOnlinePresence() { // online presences var onlinePresences = user.online_presences; if ((!onlinePresences || onlinePresences.length === 0) && !user.website) { $noOnlinePresence.show(); + + if (isCurrentUser()) { + $btnAddSites.show(); + } + else { + $btnAddSites.hide(); + } } else { + $btnAddSites.hide(); $noOnlinePresence.hide(); if (user.website) { @@ -581,7 +692,9 @@ $twitterPresence.hide(); } } + } + function renderInterests() { // current interests var noInterests = !user.paid_sessions && !user.free_sessions && !user.cowriting && !user.virtual_band && !user.traditional_band; if (noInterests) { @@ -591,9 +704,16 @@ $cowritingSection.hide(); $traditionalBandSection.hide(); $virtualBandSection.hide(); + + if (isCurrentUser()) { + $btnAddInterests.show(); + } } else { + $btnAddInterests.hide(); + $noInterests.hide(); + // paid sessions if (user.paid_sessions) { $paidGigSection.show(); @@ -662,87 +782,6 @@ $virtualBandSection.hide(); } } - - if (isMusician()) { - text = user.session_count > 1 || user.session_count === 0 ? " Sessions" : " Session"; - $sessionStats.html(user.session_count + text); - - text = user.recording_count > 1 || user.recording_count === 0 ? " Recordings" : " Recording"; - $recordingStats.html(user.recording_count + text); - } else { - text = " Following"; - $followingStats.html(user.following_count + text); - text = user.favorite_count > 1 || user.favorite_count === 0 ? " Favorites" : " Favorite"; - $favoriteStats.html(user.favorite_count + text); - } - - renderBio(); - } - - /** The biography show/edit functionality */ - function renderBio() { - - - function initializeBioVisibility() { - - $showBio.hide(); - $noBio.hide(); - $biographyEditor.hide(); - - $bioTextArea.val(user.biography); - - if(user.biography) { - - $showBio.show(); - $biographyText.text(user.biography).show(); - } - else { - if(isCurrentUser()) { - $noBio.show(); - } - } - } - - initializeBioVisibility(); - - // $addBiographyButton.unbind('click').click(function() { - // $biographyEditor.val(user.biography).show(); - // return false; - // }); - - $submitBiographyButton.unbind('click').click(function() { - var bio = $bioTextArea.val(); - $bioTextArea.closest('div.field').removeClass('error'); - $('.error-text', $bioTextArea.closest('div.field')).remove(); - userDefer = rest.updateUser({ - biography: bio - }) - .done(function(response) { - user = response; - initializeBioVisibility(); - }) - .fail(function(jqXHR) { - if(jqXHR.status == 422) { - var errors = JSON.parse(jqXHR.responseText) - var biography = context.JK.format_errors("biography", errors); - if(biography != null) { - $bioTextArea.closest('div.field').addClass('error').end().after(biography); - } - else { - app.notifyServerError(jqXHR, "Unable to update biography") - } - } - else { - app.notifyServerError(jqXHR, "Unable to update biography") - } - }) - return false; - }) - - $cancelBiographyButton.unbind('click').click(function() { - initializeBioVisibility(); - return false; - }) } /****************** SOCIAL TAB *****************/ diff --git a/web/app/views/clients/_profile.html.erb b/web/app/views/clients/_profile.html.erb index a51f9fedb..1a41e2c86 100644 --- a/web/app/views/clients/_profile.html.erb +++ b/web/app/views/clients/_profile.html.erb @@ -131,6 +131,7 @@

+
Online Presence
@@ -170,6 +171,7 @@

+
Current Interests
@@ -227,7 +229,7 @@
-
+
I'm interested in forming virtual band(s)