jam-cloud/web/app/assets/javascripts/sync_viewer.js.coffee

1024 lines
44 KiB
CoffeeScript

$ = jQuery
context = window
context.JK ||= {};
context.JK.SyncViewer = class SyncViewer
constructor: (@app) ->
@EVENTS = context.JK.EVENTS
@rest = context.JK.Rest()
@logger = context.JK.logger
@recordingUtils = context.JK.RecordingUtils
@since = 0
@limit = 20
@showing = false
@downloadCommandId = null
@downloadMetadata = null
@uploadCommandId = null
@uploadMetadata = null
@cleanupCommandId = null
@cleanupMetadata = null
init: () =>
@root = $($('#template-sync-viewer').html())
@inProgress = @root.find('.in-progress')
@downloadProgress = @inProgress.find('.download-progress')
@uploadProgress = @inProgress.find('.upload-progress')
@list = @root.find('.list')
@logList = @root.find('.log-list')
@templateRecordedTrack = $('#template-sync-viewer-recorded-track')
@templateStreamMix = $('#template-sync-viewer-stream-mix')
@templateMix = $('#template-sync-viewer-mix')
@templateNoSyncs = $('#template-sync-viewer-no-syncs')
@templateRecordingWrapperDetails = $('#template-sync-viewer-recording-wrapper-details')
@templateHoverRecordedTrack = $('#template-sync-viewer-hover-recorded-track')
@templateHoverMix = $('#template-sync-viewer-hover-mix')
@templateDownloadReset = $('#template-sync-viewer-download-progress-reset')
@templateUploadReset = $('#template-sync-viewer-upload-progress-reset')
@templateGenericCommand = $('#template-sync-viewer-generic-command')
@templateRecordedTrackCommand = $('#template-sync-viewer-recorded-track-command')
@templateLogItem = $('#template-sync-viewer-log-item')
@tabSelectors = @root.find('.dialog-tabs .tab')
@tabs = @root.find('.tab-content')
@logBadge = @tabSelectors.find('.badge')
@paginator = @root.find('.paginator-holder')
@uploadStates = {
unknown: 'unknown',
too_many_upload_failures: 'too-many-upload-failures',
me_upload_soon: 'me-upload-soon',
them_upload_soon: 'them-upload-soon'
missing: 'missing',
me_uploaded: 'me-uploaded',
them_uploaded: 'them-uploaded'
}
@clientStates = {
unknown: 'unknown',
too_many_uploads: 'too-many-downloads',
hq: 'hq',
sq: 'sq',
missing: 'missing',
discarded: 'discarded'
}
throw "no sync-viewer" if not @root.exists()
throw "no in-progress" if not @inProgress.exists()
throw "no list" if not @list.exists()
throw "no recorded track template" if not @templateRecordedTrack.exists()
throw "no stream mix template" if not @templateStreamMix.exists()
throw "no empty syncs template" if not @templateNoSyncs.exists()
$(document).on(@EVENTS.FILE_MANAGER_CMD_START, this.fileManagerCmdStart)
$(document).on(@EVENTS.FILE_MANAGER_CMD_STOP, this.fileManagerCmdStop)
$(document).on(@EVENTS.FILE_MANAGER_CMD_PROGRESS, this.fileManagerCmdProgress)
$(document).on(@EVENTS.FILE_MANAGER_CMD_ASAP_UPDATE, this.fileManagerAsapCommandStatus)
@tabSelectors.click((e) =>
$tabSelected = $(e.target).closest('a.tab')
purpose = $tabSelected.attr('purpose')
$otherTabSelectors = @tabSelectors.not('[purpose="' + purpose + '"]')
$otherTabs = @tabs.not('[purpose="' + purpose + '"]')
$tab = @tabs.filter('[purpose="' + purpose + '"]')
$otherTabs.hide();
$tab.show()
$otherTabSelectors.removeClass('selected');
$tabSelected.addClass('selected');
if purpose == 'log'
@logBadge.hide()
)
onShow: () =>
@showing = true
this.load()
onHide: () =>
@showing = false
#$(document).off(@EVENTS.FILE_MANAGER_CMD_START, this.fileManagerCmdStart)
#$(document).off(@EVENTS.FILE_MANAGER_CMD_STOP, this.fileManagerCmdStop)
#$(document).off(@EVENTS.FILE_MANAGER_CMD_PROGRESS, this.fileManagerCmdProgress)
getUserSyncs: (page) =>
@rest.getUserSyncs({since: page * @limit, limit: @limit})
.done(this.processUserSyncs)
load: () =>
@list.empty()
@since = 0
this.renderHeader()
this.getUserSyncs(0)
.done((response) =>
$paginator = context.JK.Paginator.create(response.total_entries, @limit, 0, this.getUserSyncs)
@paginator.empty().append($paginator);
)
renderHeader: () =>
recordingManagerState = context.jamClient.GetRecordingManagerState()
if recordingManagerState.running
@uploadProgress.removeClass('quiet paused busy')
@downloadProgress.removeClass('quiet paused busy')
if recordingManagerState.current_download
@downloadProgress.addClass('busy')
else
@downloadProgress.addClass('quiet')
@downloadProgress.find('.busy').empty()
if recordingManagerState.current_upload
@uploadProgress.addClass('busy')
else
@uploadProgress.addClass('quiet')
@uploadProgress.find('.busy').empty()
else
@downloadProgress.removeClass('quiet paused busy').addClass('paused')
@uploadProgress.removeClass('quiet paused busy').addClass('paused')
@downloadProgress.find('.busy').empty()
@uploadProgress.find('.busy').empty()
updateMixState: ($mix) =>
serverInfo = $mix.data('server-info')
mixInfo = @recordingUtils.createMixInfo(serverInfo)
$mixState = $mix.find('.mix-state')
$mixStateMsg = $mixState.find('.msg')
$mixStateProgress = $mixState.find('.progress')
$mixState.removeClass('still-uploading discarded unknown mixed mixing waiting-to-mix error stream-mix').addClass(mixInfo.mixStateClass).attr('data-state', mixInfo.mixState).data('mix-state', mixInfo)
$mixStateMsg.text(mixInfo.mixStateMsg)
$mixStateProgress.css('width', '0')
updateStreamMixState: ($streamMix) =>
clientInfo = $streamMix.data('client-info')
serverInfo = $streamMix.data('server-info')
# determine client state
clientStateMsg = 'UNKNOWN'
clientStateClass = 'unknown'
clientState = @clientStates.unknown
if clientInfo?
if clientInfo.local_state == 'COMPRESSED'
clientStateMsg = 'STREAM QUALITY'
clientStateClass = 'sq'
clientState = @clientStates.sq
else if clientInfo.local_state == 'UNCOMPRESSED'
clientStateMsg = 'STREAM QUALITY'
clientStateClass = 'sq'
clientState = @clientStates.sq
else if clientInfo.local_state == 'MISSING'
clientStateMsg = 'MISSING'
clientStateClass = 'missing'
clientState = @clientStates.missing
# determine upload state
uploadStateMsg = 'UNKNOWN'
uploadStateClass = 'unknown'
uploadState = @uploadStates.unknown
if !serverInfo.fully_uploaded
if serverInfo.upload.too_many_upload_failures
uploadStateMsg = 'UPLOAD FAILURE'
uploadStateClass = 'error'
uploadState = @uploadStates.too_many_upload_failures
else
if clientInfo?
if clientInfo.local_state == 'UNCOMPRESSED' or clientInfo.local_state == 'COMPRESSED'
uploadStateMsg = 'PENDING UPLOAD'
uploadStateClass = 'upload-soon'
uploadState = @uploadStates.me_upload_soon
else
uploadStateMsg = 'MISSING'
uploadStateClass = 'missing'
uploadState = @uploadStates.missing
else
uploadStateMsg = 'MISSING'
uploadStateClass = 'missing'
uploadState = @uploadStates.missing
else
uploadStateMsg = 'UPLOADED'
uploadStateClass = 'uploaded'
uploadState = @uploadStates.me_uploaded
$clientState = $streamMix.find('.client-state')
$clientStateMsg = $clientState.find('.msg')
$clientStateProgress = $clientState.find('.progress')
$uploadState = $streamMix.find('.upload-state')
$uploadStateMsg = $uploadState.find('.msg')
$uploadStateProgress = $uploadState.find('.progress')
$clientState.removeClass('discarded missing sq hq unknown error').addClass(clientStateClass).attr('data-state', clientState).data('custom-class', clientStateClass)
$clientStateMsg.text(clientStateMsg)
$clientStateProgress.css('width', '0')
$uploadState.removeClass('upload-soon error unknown missing uploaded').addClass(uploadStateClass).attr('data-state', uploadState).data('custom-class', uploadStateClass)
$uploadStateMsg.text(uploadStateMsg)
$uploadStateProgress.css('width', '0')
updateTrackState: ($track) =>
clientInfo = $track.data('client-info')
serverInfo = $track.data('server-info')
myTrack = serverInfo.user.id == context.JK.currentUserId
# determine client state
clientStateMsg = 'UNKNOWN'
clientStateClass = 'unknown'
clientState = @clientStates.unknown
if serverInfo.download.should_download
if serverInfo.download.too_many_downloads
clientStateMsg = 'EXCESS DOWNLOADS'
clientStateClass = 'error'
clientState = @clientStates.too_many_uploads
else
if clientInfo?
if clientInfo.local_state == 'HQ'
clientStateMsg = 'HIGHEST QUALITY'
clientStateClass = 'hq'
clientState = @clientStates.hq
else
clientStateMsg = 'STREAM QUALITY'
clientStateClass = 'sq'
clientState = @clientStates.sq
else
clientStateMsg = 'MISSING'
clientStateClass = 'missing'
clientState = @clientStates.missing
else
clientStateMsg = 'DISCARDED'
clientStateClass = 'discarded'
clientState = @clientStates.discarded
# determine upload state
uploadStateMsg = 'UNKNOWN'
uploadStateClass = 'unknown'
uploadState = @uploadStates.unknown
if !serverInfo.fully_uploaded
if serverInfo.upload.too_many_upload_failures
uploadStateMsg = 'UPLOAD FAILURE'
uploadStateClass = 'error'
uploadState = @uploadStates.too_many_upload_failures
else
if myTrack
if clientInfo?
if clientInfo.local_state == 'HQ'
uploadStateMsg = 'PENDING UPLOAD'
uploadStateClass = 'upload-soon'
uploadState = @uploadStates.me_upload_soon
else
uploadStateMsg = 'MISSING'
uploadStateClass = 'missing'
uploadState = @uploadStates.missing
else
uploadStateMsg = 'MISSING'
uploadStateClass = 'missing'
uploadState = @uploadStates.missing
else
uploadStateMsg = 'PENDING UPLOAD'
uploadStateClass = 'upload-soon'
uploadState = @uploadStates.them_upload_soon
else
uploadStateMsg = 'UPLOADED'
uploadStateClass = 'uploaded'
if myTrack
uploadState = @uploadStates.me_uploaded
else
uploadState = @uploadStates.them_uploaded
$clientState = $track.find('.client-state')
$clientStateMsg = $clientState.find('.msg')
$clientStateProgress = $clientState.find('.progress')
$uploadState = $track.find('.upload-state')
$uploadStateMsg = $uploadState.find('.msg')
$uploadStateProgress = $uploadState.find('.progress')
$clientState.removeClass('discarded missing sq hq unknown error').addClass(clientStateClass).attr('data-state', clientState).data('custom-class', clientStateClass)
$clientStateMsg.text(clientStateMsg)
$clientStateProgress.css('width', '0')
$uploadState.removeClass('upload-soon error unknown missing uploaded').addClass(uploadStateClass).attr('data-state', uploadState).data('custom-class', uploadStateClass)
$uploadStateMsg.text(uploadStateMsg)
$uploadStateProgress.css('width', '0')
# this allows us to make styling decisions based on the combination of both client and upload state.
$track.addClass("clientState-#{clientStateClass}").addClass("uploadState-#{uploadStateClass}")
$clientRetry = $clientState.find('.retry')
$uploadRetry = $uploadState.find('.retry')
if gon.isNativeClient
# handle client state
# only show RETRY button if you have a SQ or if it's missing, and it's been uploaded already
if (clientState == @clientStates.sq or clientState == @clientStates.missing) and (uploadState == @uploadStates.me_uploaded or uploadState == @uploadStates.them_uploaded)
$clientRetry.show()
else
$clientRetry.hide()
# only show RETRY button if you have the HQ track, it's your track, and the server doesn't yet have it
if myTrack and @clientStates.hq and (uploadState == @uploadStates.error or uploadState == @uploadStates.me_upload_soon)
$uploadRetry.show()
else
$uploadRetry.hide()
else
$clientRetry.hide()
$uploadRetry.hide()
associateClientInfo: (recording) =>
for clientInfo in recording.local_tracks
$track = @list.find(".recorded-track[data-recording-id='#{recording.recording_id}'][data-client-track-id='#{clientInfo.client_track_id}']")
$track.data('client-info', clientInfo)
$track.data('total-size', recording.size)
$track = @list.find(".mix[data-recording-id='#{recording.recording_id}']")
$track.data('client-info', recording.mix)
$track.data('total-size', recording.size)
$track = @list.find(".stream-mix[data-recording-id='#{recording.recording_id}']")
$track.data('client-info', recording.stream_mix)
$track.data('total-size', recording.size)
displayStreamMixHover: ($streamMix) =>
$clientState = $streamMix.find('.client-state')
$clientStateMsg = $clientState.find('.msg')
clientStateClass = $clientState.data('custom-class')
clientState = $clientState.attr('data-state')
clientInfo = $streamMix.data('client-info')
$uploadState = $streamMix.find('.upload-state')
$uploadStateMsg = $uploadState.find('.msg')
uploadStateClass = $uploadState.data('custom-class')
uploadState = $uploadState.attr('data-state')
serverInfo = $streamMix.data('server-info')
# decide on special case strings first
summary = ''
if clientState == @clientStates.sq && uploadState == @uploadStates.me_upload_soon
summary = "We will attempt to upload your stream mix so that others can hear the recording on the JamKazam site, even before the final mix is done. It will upload shortly."
else if clientState == @clientStates.sq && uploadState == @uploadStates.me_uploaded
# we have the SQ version, and the other user has uploaded the HQ version... it's coming soon!
summary = "We already uploaded your stream mix so that others can hear the recording on the JamKazam site, even before the final mix is done. Since it's uploaded, there is nothing else left to do with the stream mix... you're all done!"
else if clientState == @clientStates.missing
summary = "You do not have the stream mix on your computer anymore. This can happen if you change the computer that you run JamKazam on. It's important to note that once a final mix for the recording is available, there is no value in the stream mix."
clientStateDefinition = switch clientState
when @clientStates.sq then "The stream mix is always STREAM QUALITY, because it's the version of the recording that you heard in your earphones as you made the recording."
when @clientStates.missing then "MISSING means you do not have the stream mix anymore."
else 'There is no help for this state'
uploadStateDefinition = switch uploadState
when @uploadStates.too_many_upload_failures then "Failed attempts at uploading this stream mix has happened an unusually large times. No more uploads will be attempted."
when @uploadStates.me_upload_soon then "PENDING UPLOAD means your JamKazam application will upload this stream mix soon."
when @uploadStates.me_uploaded then "UPLOADED means you have already uploaded this stream mix."
when @uploadStates.missing then "MISSING means your JamKazam application does not have this stream mix, and the server does not either."
context._.template(@templateHoverRecordedTrack.html(),
{summary: summary,
clientStateDefinition: clientStateDefinition,
uploadStateDefinition: uploadStateDefinition,
clientStateMsg: $clientStateMsg.text(),
uploadStateMsg: $uploadStateMsg.text(),
clientStateClass: clientStateClass,
uploadStateClass: uploadStateClass}
{variable: 'data'})
displayTrackHover: ($recordedTrack) =>
$clientState = $recordedTrack.find('.client-state')
$clientStateMsg = $clientState.find('.msg')
clientStateClass = $clientState.data('custom-class')
clientState = $clientState.attr('data-state')
clientInfo = $recordedTrack.data('client-info')
$uploadState = $recordedTrack.find('.upload-state')
$uploadStateMsg = $uploadState.find('.msg')
uploadStateClass = $uploadState.data('custom-class')
uploadState = $uploadState.attr('data-state')
serverInfo = $recordedTrack.data('server-info')
# decide on special case strings first
summary = ''
if clientState == @clientStates.sq && uploadState == @uploadStates.them_upload_soon
# we have the SQ version, and the other user hasn't uploaded it yet
summary = "#{serverInfo.user.name} has not yet uploaded the high-quality version of this track. Once he or she does, JamKazam will download it and replace your stream-quality version."
else if clientState == @clientStates.missing && uploadState == @uploadStates.them_upload_soon
# we don't have any version of the track at all, and the other user hasn't uploaded it yet
summary = "#{serverInfo.user.name} has not yet uploaded the high-quality version of this track. Once he or she does, JamKazam will download it and this track will no longer be missing."
else if clientState == @clientStates.sq && uploadState == @uploadStates.them_uploaded
# we have the SQ version, and the other user has uploaded the HQ version... it's coming soon!
summary = "#{serverInfo.user.name} has uploaded the high-quality version of this track. JamKazam will soon download it and replace your stream-quality version."
else if clientState == @clientStates.missing && uploadState == @uploadStates.them_uploaded
# we have no version of the track at all, and the other user has uploaded the HQ version... it's coming soon!
summary = "#{serverInfo.user.name} has uploaded the high-quality version of this track. JamKazam will soon restore it and then this track will no longer be missing."
else if clientState == @clientStates.sq && uploadState == @uploadStates.me_uploaded
# we have the SQ version, and the other user has uploaded the HQ version... it's coming soon!
summary = "You have previously uploaded the high-quality version of this track. JamKazam will soon restore it and replace your stream-quality version."
else if clientState == @clientStates.missing && uploadState == @uploadStates.me_uploaded
# we have no version of the track at all, and the other user has uploaded the HQ version... it's coming soon!
summary = "You have previously uploaded the high-quality version of this track. JamKazam will soon restore it and then this track will no longer be missing."
else if clientState == @clientStates.discarded && (uploadState == @uploadStates.me_uploaded or uploadState == @uploadStates.them_uploaded)
# we decided not to keep the recording... so it's important to clarify why they are seeing it at all
summary = "When this recording was made, you elected to not keep it. JamKazam already uploaded your high-quality tracks for the recording, because at least one other person decided to keep the recording and needs your tracks to make a high-quality mix."
else if clientState == @clientStates.discarded
# we decided not to keep the recording... so it's important to clarify why they are seeing it at all
summary = "When this recording was made, you elected to not keep it. JamKazam will still try to upload your high-quality tracks for the recording, because at least one other person decided to keep the recording and needs your tracks to make a high-quality mix."
else if clientState == @clientStates.hq and ( uploadState == @uploadStates.them_uploaded or uploadState == @uploadStates.me_uploaded )
summary = "Both you and the JamKazam server have the high-quality version of this track. Once all the other tracks for this recording are also synchronized, then the final mix can be made."
clientStateDefinition = switch clientState
when @clientStates.too_many_downloads then "This track has been downloaded an unusually large number of times. No more downloads are allowed."
when @clientStates.hq then "HIGHEST QUALITY means you have the original version of this track, as recorded by the user that made it."
when @clientStates.sq then "STREAM QUALITY means you have the version of the track that you received over the internet in real-time."
when @clientStates.missing then "MISSING means you do not have this track anymore."
when @clientStates.discarded then "DISCARDED means you chose to not keep this recording when the recording was over."
else 'There is no help for this state'
uploadStateDefinition = switch uploadState
when @uploadStates.too_many_upload_failures then "Failed attempts at uploading this track has happened an unusually large times. No more uploads will be attempted."
when @uploadStates.me_upload_soon then "PENDING UPLOAD means your JamKazam application will upload this track soon."
when @uploadStates.them_up_soon then "PENDING UPLOAD means #{serverInfo.user.name} will upload this track soon."
when @uploadStates.me_uploaded then "UPLOADED means you have already uploaded this track."
when @uploadStates.them_uploaded then "UPLOADED means #{serverInfo.user.name} has already uploaded this track."
when @uploadStates.missing then "MISSING means your JamKazam application does not have this track, and the server does not either."
context._.template(@templateHoverRecordedTrack.html(),
{summary: summary,
clientStateDefinition: clientStateDefinition,
uploadStateDefinition: uploadStateDefinition,
clientStateMsg: $clientStateMsg.text(),
uploadStateMsg: $uploadStateMsg.text(),
clientStateClass: clientStateClass,
uploadStateClass: uploadStateClass}
{variable: 'data'})
onHoverOfStateIndicator: () ->
$recordedTrack = $(this).closest('.recorded-track.sync')
self = $recordedTrack.data('sync-viewer')
self.displayTrackHover($recordedTrack)
onStreamMixHover: () ->
$streamMix = $(this).closest('.stream-mix.sync')
self = $streamMix.data('sync-viewer')
self.displayStreamMixHover($streamMix)
resetDownloadProgress: () =>
@downloadProgress
resetUploadProgress: () =>
@uploadProgress
sendCommand: ($retry, cmd) =>
if context.JK.CurrentSessionModel and context.JK.CurrentSessionModel.inSession()
context.JK.ackBubble($retry, 'sync-viewer-paused', {}, {offsetParent: $retry.closest('.dialog')})
else
context.jamClient.OnTrySyncCommand(cmd)
context.JK.ackBubble($retry, 'sync-viewer-retry', {}, {offsetParent: $retry.closest('.dialog')})
retryDownloadRecordedTrack: (e) =>
$retry = $(e.target)
$track = $retry.closest('.recorded-track')
serverInfo = $track.data('server-info')
this.sendCommand($retry, {
type: 'recorded_track',
action: 'download'
queue: 'download',
recording_id: serverInfo.recording_id
track_id: serverInfo.client_track_id
})
return false
retryUploadRecordedTrack: (e) =>
$retry = $(e.target)
$track = $retry.closest('.recorded-track')
serverInfo = $track.data('server-info')
this.sendCommand($retry, {
type: 'recorded_track',
action: 'upload'
queue: 'upload',
recording_id: serverInfo.recording_id
track_id: serverInfo.client_track_id
})
return false
createMix: (userSync) =>
recordingInfo = null
if userSync == 'fake'
recordingInfo = arguments[1]
# sift through the recorded_tracks in here; if they are marked discarded, then we can also mark this one discarded too
discarded = true
for claim in recordingInfo.claimed_recordings
if claim.user_id == context.JK.currentUserId
discarded = false
break
userSync = { recording_id: recordingInfo.id, duration: recordingInfo.duration, fake:true, discarded: discarded }
$mix = $(context._.template(@templateMix.html(), userSync, {variable: 'data'}))
else
$mix = $(context._.template(@templateMix.html(), userSync, {variable: 'data'}))
$mix.data('server-info', userSync)
$mix.data('sync-viewer', this)
$mix.data('view-context', 'sync')
$mixState = $mix.find('.mix-state')
this.updateMixState($mix)
context.JK.hoverBubble($mixState, @recordingUtils.onMixHover, {width:'450px', closeWhenOthersOpen: true, positions:['left'], trigger:['hoverIntent', 'none']})
$mix
createTrack: (userSync) =>
$track = $(context._.template(@templateRecordedTrack.html(), userSync, {variable: 'data'}))
$track.data('server-info', userSync)
$track.data('sync-viewer', this)
$clientState = $track.find('.client-state')
$uploadState = $track.find('.upload-state')
$clientStateRetry = $clientState.find('.retry')
$clientStateRetry.click(this.retryDownloadRecordedTrack)
$uploadStateRetry = $uploadState.find('.retry')
$uploadStateRetry.click(this.retryUploadRecordedTrack)
context.JK.bindHoverEvents($track)
context.JK.bindInstrumentHover($track, {positions:['top'], shrinkToFit: true});
context.JK.hoverBubble($clientState, this.onHoverOfStateIndicator, {width:'450px', closeWhenOthersOpen: true, positions:['left']})
context.JK.hoverBubble($uploadState, this.onHoverOfStateIndicator, {width:'450px', closeWhenOthersOpen: true, positions:['right']})
$clientState.addClass('is-native-client') if gon.isNativeClient
$uploadState.addClass('is-native-client') if gon.isNativeClient
$track
createStreamMix: (userSync) =>
$track = $(context._.template(@templateStreamMix.html(), userSync, {variable: 'data'}))
$track.data('server-info', userSync)
$track.data('sync-viewer', this)
$clientState = $track.find('.client-state')
$uploadState = $track.find('.upload-state')
$uploadState.find('.retry').click(this.retryUploadRecordedTrack)
context.JK.hoverBubble($clientState, this.onStreamMixHover, {width:'450px', closeWhenOthersOpen: true, positions:['left']})
context.JK.hoverBubble($uploadState, this.onStreamMixHover, {width:'450px', closeWhenOthersOpen: true, positions:['right']})
$clientState.addClass('is-native-client') if gon.isNativeClient
$uploadState.addClass('is-native-client') if gon.isNativeClient
$track
exportRecording: (e) =>
$export = $(e.target)
if context.JK.CurrentSessionModel and context.JK.CurrentSessionModel.inSession()
context.JK.ackBubble($export, 'sync-viewer-paused', {}, {offsetParent: $export.closest('.dialog')})
return
recordingId = $export.closest('.details').attr('data-recording-id')
if !recordingId? or recordingId == ""
throw "exportRecording can't find data-recording-id"
cmd =
{ type: 'export_recording',
action: 'export',
queue: 'upload',
recording_id: recordingId}
logger.debug("enqueueing export")
context.jamClient.OnTrySyncCommand(cmd)
return false;
deleteRecording: (e) =>
$delete = $(e.target)
if context.JK.CurrentSessionModel and context.JK.CurrentSessionModel.inSession()
context.JK.ackBubble($delete, 'sync-viewer-paused', {}, {offsetParent: $delete.closest('.dialog')})
return
$details = $delete.closest('.details')
recordingId = $details.attr('data-recording-id')
if !recordingId? or recordingId == ""
throw "deleteRecording can't find data-recording-id"
context.JK.Banner.showYesNo({
title: "Confirm Deletion",
html: "Are you sure you want to delete this recording?",
yes: =>
@rest.deleteRecordingClaim(recordingId).done((response)=>
cmd =
{ type: 'recording_directory',
action: 'delete',
queue: 'cleanup',
recording_id: recordingId}
# now check if the sync is gone entirely, allowing us to delete it from the UI
@rest.getUserSync({user_sync_id: recordingId}).done((userSync) =>
# the user sync is still here. tell user it'll be done as soon as they've uploaded their files
context.JK.ackBubble($delete, 'file-sync-delayed-deletion', {}, {offsetParent: $delete.closest('.dialog')})
)
.fail((xhr) =>
if xhr.status == 404
# the userSync is gone; remove from file manager dynamically
$recordingHolder = $details.closest('.recording-holder')
$recordingHolder.slideUp()
else
@app.ajaxError(arguments)
)
context.jamClient.OnTrySyncCommand(cmd)
)
.fail(@app.ajaxError)
})
return false;
displaySize: (size) =>
# size is in bytes. divide by million, anxosd round to one decimal place
megs = Math.round(size * 10 / (1024 * 1024) ) / 10
"#{megs}M"
createRecordingWrapper: ($toWrap, recordingInfo) =>
totalSize = $($toWrap.get(0)).data('total-size')
recordingInfo.recording_landing_url = "/recordings/#{recordingInfo.id}"
recordingInfo.totalSize = this.displaySize(totalSize)
recordingInfo.claimedRecordingId = recordingInfo.my?.id
$wrapperDetails = $(context._.template(@templateRecordingWrapperDetails.html(), recordingInfo, {variable: 'data'}))
$wrapper = $('<div class="recording-holder"></div>')
$toWrap.wrapAll($wrapper)
$wrapper = $toWrap.closest('.recording-holder')
$wrapperDetails.prependTo($wrapper)
$mix = $wrapper.find('.mix.sync')
if $mix.length == 0
# create a virtual mix so that the UI is consistent
$wrapper.append(this.createMix('fake', recordingInfo))
$wrapper.find('a.export').click(this.exportRecording)
$wrapper.find('a.delete').click(this.deleteRecording)
separateByRecording: () =>
$recordedTracks = @list.find('.sync')
currentRecordingId = null;
queue = $([]);
for recordedTrack in $recordedTracks
$recordedTrack = $(recordedTrack)
recordingId = $recordedTrack.attr('data-recording-id')
if recordingId != currentRecordingId
if queue.length > 0
this.createRecordingWrapper(queue, $(queue.get(0)).data('server-info').recording)
queue = $([])
currentRecordingId = recordingId
queue = queue.add(recordedTrack)
if queue.length > 0
this.createRecordingWrapper(queue, $(queue.get(0)).data('server-info').recording)
processUserSyncs: (response) =>
@list.empty()
# check if no entries
if @since == 0 and response.entries.length == 0
@list.append(context._.template(@templateNoSyncs.html(), {}, {variable: 'data'}))
else
recordings = {} # collect all unique recording
for userSync in response.entries
if userSync.type == 'recorded_track'
@list.append(this.createTrack(userSync))
else if userSync.type == 'mix'
@list.append(this.createMix(userSync))
else if userSync.type == 'stream_mix'
@list.append(this.createStreamMix(userSync))
recordings[userSync.recording_id] = userSync.recording
recordingsToResolve = []
# resolve each track against backend data:
for recording_id, recording of recordings
recordingsToResolve.push(recording)
clientRecordings = context.jamClient.GetLocalRecordingState(recordings: recordingsToResolve)
if clientRecordings.error?
alert(clientRecordings.error)
else
this.associateClientInfo(turp) for turp in clientRecordings.recordings
for track in @list.find('.recorded-track.sync')
this.updateTrackState($(track))
for streamMix in @list.find('.stream-mix.sync')
this.updateStreamMixState($(streamMix))
this.separateByRecording()
@since = response.next
resolveTrack: (commandMetadata) =>
recordingId = commandMetadata['recording_id']
clientTrackId = commandMetadata['track_id']
matchingTrack = @list.find(".recorded-track[data-recording-id='#{recordingId}'][data-client-track-id='#{clientTrackId}']")
if matchingTrack.length == 0
return @rest.getRecordedTrack({recording_id: recordingId, track_id: clientTrackId})
else
deferred = $.Deferred();
deferred.resolve(matchingTrack.data('server-info'))
return deferred
renderFullUploadRecordedTrack: (serverInfo) =>
$track = $(context._.template(@templateRecordedTrackCommand.html(), $.extend(serverInfo, {action:'UPLOADING'}), {variable: 'data'}))
$busy = @uploadProgress.find('.busy')
$busy.empty().append($track)
@uploadProgress.find('.progress').css('width', '0%')
renderFullDownloadRecordedTrack: (serverInfo) =>
$track = $(context._.template(@templateRecordedTrackCommand.html(), $.extend(serverInfo, {action:'DOWNLOADING'}), {variable: 'data'}))
$busy = @downloadProgress.find('.busy')
$busy.empty().append($track)
@downloadProgress.find('.progress').css('width', '0%')
# this will either show a generic placeholder, or immediately show the whole track
renderDownloadRecordedTrack: (commandId, commandMetadata) =>
# try to find the info in the list; if we can't find it, then resolve it
deferred = this.resolveTrack(commandMetadata)
if deferred.state() == 'pending'
this.renderGeneric(commandId, 'download', commandMetadata)
deferred.done(this.renderFullDownloadRecordedTrack).fail(()=> @logger.error("unable to fetch recorded_track info") )
renderUploadRecordedTrack: (commandId, commandMetadata) =>
# try to find the info in the list; if we can't find it, then resolve it
deferred = this.resolveTrack(commandMetadata)
if deferred.state() == 'pending'
this.renderGeneric(commandId, 'upload', commandMetadata)
deferred.done(this.renderFullUploadRecordedTrack).fail(()=> @logger.error("unable to fetch recorded_track info") )
renderGeneric: (commandId, category, commandMetadata) =>
commandMetadata.displayType = this.displayName(commandMetadata)
$generic = $(context._.template(@templateGenericCommand.html(), commandMetadata, {variable: 'data'}))
if category == 'download'
$busy = @downloadProgress.find('.busy')
$busy.empty().append($generic)
else if category == 'upload'
$busy = @uploadProgress.find('.busy')
$busy.empty().append($generic)
else
@logger.error("unknown category #{category}")
renderStartCommand: (commandId, commandType, commandMetadata) =>
#console.log("renderStartCommand", arguments)
unless commandMetadata?
managerState = context.jamClient.GetRecordingManagerState()
if commandType == 'download'
commandMetadata = managerState.current_download
else if commandType == 'upload'
commandMetadata = managerState.current_upload
else
@logger.error("unknown commandType #{commandType}")
unless commandMetadata?
# we still have no metadata. we have to give up
@logger.error("no metadata found for current command #{commandId} #{commandType}. bailing out")
return
if commandMetadata.queue == 'download'
@downloadCommandId = commandId
@downloadMetadata = commandMetadata
@downloadProgress.removeClass('quiet paused busy')
@downloadProgress.addClass('busy')
if commandMetadata.type == 'recorded_track' and commandMetadata.action == 'download'
this.renderDownloadRecordedTrack(commandId, commandMetadata)
else
this.renderGeneric(commandId, 'download', commandMetadata)
else if commandMetadata.queue == 'upload'
@uploadCommandId = commandId
@uploadMetadata = commandMetadata
@uploadProgress.removeClass('quiet paused busy')
@uploadProgress.addClass('busy')
if commandMetadata.type == 'recorded_track' and commandMetadata.action == 'upload'
this.renderUploadRecordedTrack(commandId, commandMetadata)
else
this.renderGeneric(commandId, 'upload', commandMetadata)
else if commandMetadata.queue == 'cleanup'
@cleanupCommandId = commandId
@cleanupMetadata = commandMetadata
renderSingleRecording: (userSyncs) =>
return if userSyncs.entries.length == 0
clientRecordings = context.jamClient.GetLocalRecordingState(recordings: [userSyncs.entries[0].recording])
for userSync in userSyncs.entries
if userSync.type == 'recorded_track'
$track = @list.find(".sync[data-id='#{userSync.id}']")
continue if $track.length == 0
$track.data('server-info', userSync)
this.associateClientInfo(clientRecordings.recordings[0])
this.updateTrackState($track)
else if userSync.type == 'mix'
# check if there is a virtual mix 1st; if so, update it
$mix = @list.find(".mix.virtual[data-recording-id='#{userSync.recording.id}']")
if $mix.length == 0
$mix = @list.find(".sync[data-id='#{userSync.id}']")
continue if $mix.length == 0
$newMix = this.createMix(userSync)
this.associateClientInfo(clientRecordings.recordings[0])
$mix.replaceWith($newMix)
else if userSync.type == 'stream_mix'
$streamMix = @list.find(".sync[data-id='#{userSync.id}']")
continue if $streamMix.length == 0
$streamMix.data('server-info', userSync)
this.associateClientInfo(clientRecordings.recordings[0])
this.updateStreamMixState($streamMix)
updateSingleRecording: (recording_id) =>
@rest.getUserSyncs({recording_id: recording_id}).done(this.renderSingleRecording)
updateSingleRecordedTrack: ($track) =>
serverInfo = $track.data('server-info')
@rest.getUserSync({user_sync_id: serverInfo.id})
.done((userSync) =>
# associate new server-info with this track
$track.data('server-info', userSync)
# associate new client-info with this track
clientRecordings = context.jamClient.GetLocalRecordingState(recordings: [userSync.recording])
this.associateClientInfo(clientRecordings.recordings[0])
this.updateTrackState($track)
)
.fail(@app.ajaxError)
updateProgressOnSync: ($track, queue, percentage) =>
state = if queue == 'upload' then '.upload-state' else '.client-state'
$progress = $track.find("#{state} .progress")
$progress.css('width', percentage + '%')
renderFinishCommand: (commandId, data) =>
reason = data.commandReason
success = data.commandSuccess
if commandId == @downloadCommandId
this.logResult(@downloadMetadata, success, reason, false)
recordingId = @downloadMetadata['recording_id']
this.updateSingleRecording(recordingId) if recordingId?
else if commandId == @uploadCommandId
this.logResult(@uploadMetadata, success, reason, false)
recordingId = @uploadMetadata['recording_id']
this.updateSingleRecording(recordingId) if recordingId?
else if commandId == @cleanupCommandId
this.logResult(@cleanupMetadata, success, reason, false)
else
@logger.error("unknown commandId in renderFinishCommand")
# refresh the header when done. we need to leave this callback to let the command fully switch to off
#setTimeout(this.renderHeader, 1)
this.renderHeader()
renderPercentage: (commandId, commandType, percentage) =>
if commandId == @downloadCommandId
$progress = @downloadProgress.find('.progress')
$progress.css('width', percentage + '%')
if @downloadMetadata.type == 'recorded_track'
clientTrackId = @downloadMetadata['track_id']
recordingId = @downloadMetadata['recording_id']
$matchingTrack = @list.find(".recorded-track.sync[data-recording-id='#{recordingId}'][data-client-track-id='#{clientTrackId}']")
if $matchingTrack.length > 0
this.updateProgressOnSync($matchingTrack, 'download', percentage)
else if commandId == @uploadCommandId
$progress = @uploadProgress.find('.progress')
$progress.css('width', percentage + '%')
if @uploadMetadata.type == 'recorded_track' and @uploadMetadata.action == 'upload'
clientTrackId = @uploadMetadata['track_id']
recordingId = @uploadMetadata['recording_id']
$matchingTrack = @list.find(".recorded-track.sync[data-recording-id='#{recordingId}'][data-client-track-id='#{clientTrackId}']")
if $matchingTrack.length > 0
this.updateProgressOnSync($matchingTrack, 'upload', percentage)
else if @uploadMetadata.type == 'stream_mix' and @uploadMetadata.action == 'upload'
recordingId = @uploadMetadata['recording_id']
$matchingStreamMix = @list.find(".stream-mix.sync[data-recording-id='#{recordingId}']")
if $matchingStreamMix.length > 0
this.updateProgressOnSync($matchingStreamMix, 'upload', percentage)
else if commandId == @cleanupCommandId
# ignore
else
@logger.error("unknown commandId in renderFinishCommand")
fileManagerCmdStart: (e, data) =>
#console.log("fileManagerCmdStart", data)
commandId = data['commandId']
commandType = data['commandType']
commandMetadata = data['commandMetadata']
category = commandMetadata.queue
if category == 'download' && (@downloadCommandId != null && @downloadCommandId != commandId)
@logger.warn("received command-start for download but previous command did not send stop")
this.renderFinishCommand(commandId, category)
else if @uploadCommandId != null && @uploadCommandId != commandId
@logger.warn("received command-start for upload but previous command did not send stop")
this.renderFinishCommand(commandId, category)
this.renderStartCommand(commandId, commandType, commandMetadata)
fileManagerCmdStop: (e, data) =>
#console.log("fileManagerCmdStop", data)
commandId = data['commandId']
if commandId == @downloadCommandId
this.renderFinishCommand(commandId, data)
@downloadCommandId = null
@downloadMetadata = null;
else if commandId == @uploadCommandId
this.renderFinishCommand(commandId, data)
@uploadCommandId = null
@uploadMetadata = null
else if commandId == @cleanupCommandId
this.renderFinishCommand(commandId, data)
@cleanupCommandId = null
@cleanupMetadata = null
else
@logger.warn("received command-stop for unknown command: #{commandId} #{@downloadCommandId} #{@uploadCommandId} #{@cleanupCommandId}" )
fileManagerCmdProgress: (e, data) =>
#console.log("fileManagerCmdProgress", data)
commandId = data['commandId']
if commandId == @downloadCommandId
category = 'download'
this.renderPercentage(commandId, category, data.percentage)
else if commandId == @uploadCommandId
category = 'upload'
this.renderPercentage(commandId, category, data.percentage)
else if commandId == @cleanupCommandId
# do nothing
else
@logger.warn("received command-percentage for unknown command")
fileManagerAsapCommandStatus: (e, data) =>
this.logResult(data.commandMetadata, false, data.commandReason, true)
displayName: (metadata) =>
if metadata.type == 'recorded_track' && metadata.action == 'download'
return 'DOWNLOADING TRACK'
else if metadata.type == 'recorded_track' && metadata.action == 'upload'
return 'UPLOADING TRACK'
else if metadata.type == 'mix' && metadata.action == 'download'
return 'DOWNLOADING MIX'
else if metadata.type == 'recorded_track' && metadata.action == 'convert'
return 'COMPRESSING TRACK'
else if metadata.type == 'recorded_track' && metadata.action == 'delete'
return 'CLEANUP TRACK'
else if metadata.type == 'stream_mix' && metadata.action == 'upload'
return 'UPLOADING STREAM MIX'
else if metadata.type == 'recording_directory' && metadata.action == 'delete'
return 'DELETE RECORDING'
else
return "#{metadata.action} #{metadata.type}".toUpperCase()
shownTab: () =>
@tabSelectors.filter('.selected')
# create a log in the Log tab
logResult: (metadata, success, reason, isAsap) =>
# if an error comes in, and the log tab is not already showing, increment the badge
if not success and (!@showing || this.shownTab().attr('purpose') != 'log')
@logBadge.css('display', 'inline-block')
if @showing # don't do animation unless user can see it
@logBadge.pulse({'background-color' : '#868686'}, {pulses: 2}, () => @logBadge.css('background-color', '#980006'))
displayReason = switch reason
when 'no-match-in-queue' then 'restart JamKazam'
when 'already-done' then 'ignored, already done'
when 'failed-convert' then 'failed previously'
when 'minimum-protection-time' then 'too soon to delete'
else reason
displaySuccess = if success then 'yes' else 'no'
$log = context._.template(@templateLogItem.html(), {isAsap: isAsap, command: this.displayName(metadata), success: success, displaySuccess: displaySuccess, detail: displayReason, when: new Date()}, {variable: 'data'})
@logList.prepend($log)