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()