diff --git a/ruby/lib/jam_ruby/lib/subscription_message.rb b/ruby/lib/jam_ruby/lib/subscription_message.rb index 62409b6aa..8f4320249 100644 --- a/ruby/lib/jam_ruby/lib/subscription_message.rb +++ b/ruby/lib/jam_ruby/lib/subscription_message.rb @@ -14,11 +14,11 @@ module JamRuby end def self.mount_source_up_requested(mount) - Notification.send_subscription_message('mount', mount.id, {type: 'source_up_requested'}.to_json ) + Notification.send_subscription_message('mount', mount.id, {change_type: IcecastSourceChange::CHANGE_TYPE_MOUNT_UP_REQUEST}.to_json ) end def self.mount_source_down_requested(mount) - Notification.send_subscription_message('mount', mount.id, {type: 'source_down_requested'}.to_json ) + Notification.send_subscription_message('mount', mount.id, {change_type: IcecastSourceChange::CHANGE_TYPE_MOUNT_DOWN_REQUEST}.to_json ) end end end diff --git a/ruby/lib/jam_ruby/models/icecast_mount.rb b/ruby/lib/jam_ruby/models/icecast_mount.rb index 29f3a6888..be1cfb382 100644 --- a/ruby/lib/jam_ruby/models/icecast_mount.rb +++ b/ruby/lib/jam_ruby/models/icecast_mount.rb @@ -69,7 +69,7 @@ module JamRuby #IcecastSourceChange.delete_all(["icecast_mount_id = ?", self.id]) if source_direction # and tell anyone listening that the direction has changed - SubscriptionMessage.mount_source_direction(self) + # SubscriptionMessage.mount_source_direction(self) end # Note: @@ -139,7 +139,7 @@ module JamRuby first = source_changes.first # don't check source_changes if actual source state mirrors desired source state... just say we are good, and pass down relevant sourcing user ID if present - result = success_state('source_' + (source_direction ? 'up' : 'down'), first.nil? ? nil : first.user_id) + result = success_state('source_' + (sourced ? 'up' : 'down'), first.nil? ? nil : first.user_id) elsif source_changes.count > 0 # if the desired source direction is up, but we haven't sourced yet... let's try and find out why @@ -200,7 +200,7 @@ module JamRuby def source_up with_lock do self.sourced = true - self.sourced_needs_changing_at = nil + self.sourced_needs_changing_at = Time.now self.no_config_changed = true save(validate: false) end @@ -209,7 +209,7 @@ module JamRuby def source_down with_lock do self.sourced = false - self.sourced_needs_changing_at = nil + self.sourced_needs_changing_at = Time.now self.no_config_changed = true save(validate: false) end diff --git a/ruby/spec/jam_ruby/models/icecast_mount_spec.rb b/ruby/spec/jam_ruby/models/icecast_mount_spec.rb index 847e9697c..553b5a34d 100644 --- a/ruby/spec/jam_ruby/models/icecast_mount_spec.rb +++ b/ruby/spec/jam_ruby/models/icecast_mount_spec.rb @@ -296,7 +296,7 @@ describe IcecastMount do let(:mount_needing_source) {FactoryGirl.create(:iceast_mount_with_music_session, sourced: false, sourced_needs_changing_at: Time.now, listeners: 1)} it "happy sourced mount" do - sourced_mount.state.should == success_state('source_down') + sourced_mount.state.should == success_state('source_up') end it "just transitioned" do @@ -317,7 +317,7 @@ describe IcecastMount do it "happy sourced mount" do mount_needing_source.sourced = true mount_needing_source.save! - mount_needing_source.state.should == success_state('source_down', change1.user.id) + mount_needing_source.state.should == success_state('source_up', change1.user.id) end it "succeeded recently in correct transition" do diff --git a/web/app/assets/images/content/join-icon.jpg b/web/app/assets/images/content/join-icon.jpg new file mode 100644 index 000000000..19342ce3e Binary files /dev/null and b/web/app/assets/images/content/join-icon.jpg differ diff --git a/web/app/assets/images/content/listen-icon.jpg b/web/app/assets/images/content/listen-icon.jpg new file mode 100644 index 000000000..1f7416525 Binary files /dev/null and b/web/app/assets/images/content/listen-icon.jpg differ diff --git a/web/app/assets/images/content/pause-icon.jpg b/web/app/assets/images/content/pause-icon.jpg new file mode 100644 index 000000000..085139efc Binary files /dev/null and b/web/app/assets/images/content/pause-icon.jpg differ diff --git a/web/app/assets/images/content/rsvp-icon.jpg b/web/app/assets/images/content/rsvp-icon.jpg new file mode 100644 index 000000000..b9f0797c8 Binary files /dev/null and b/web/app/assets/images/content/rsvp-icon.jpg differ diff --git a/web/app/assets/javascripts/globals.js b/web/app/assets/javascripts/globals.js index 4f68a3125..584eba17b 100644 --- a/web/app/assets/javascripts/globals.js +++ b/web/app/assets/javascripts/globals.js @@ -44,7 +44,8 @@ MUTE_SELECTED: 'mute_selected', SUBSCRIBE_NOTIFICATION: 'subscribe_notification', CONNECTION_UP: 'connection_up', - CONNECTION_DOWN: 'connection_down' + CONNECTION_DOWN: 'connection_down', + SCREEN_CHANGED: 'screen_changed' }; context.JK.ALERT_NAMES = { diff --git a/web/app/assets/javascripts/jquery.listenbroadcast.js b/web/app/assets/javascripts/jquery.listenbroadcast.js index d8dba3e4a..349305b57 100644 --- a/web/app/assets/javascripts/jquery.listenbroadcast.js +++ b/web/app/assets/javascripts/jquery.listenbroadcast.js @@ -35,7 +35,6 @@ var WAIT_FOR_BUFFER_TIMEOUT = 5000; var RETRY_ATTEMPTS = 5; // we try 4 times, so the user will wait up until RETRY_ATTEMPTS * WAIT_FOR_BUFFER_TIMEOUTS - var logger = context.JK.logger; var rest = context.JK.Rest(); var $parent = $parentElement; @@ -44,10 +43,15 @@ var musicSessionId = null; var waitForBufferingTimeout = null; var fanAccess = null; + var audioSrc = null; + var audioType = null; var retryAttempts = 0; var self = this; var mountInfo = null; var $mountState = null; + var lazyAudioInit = options && options.lazyAudioInit; + var hoverOptions = (options && options.hoverOptions) ? options.hoverOptions : {} + var $detailHelper = options && options.detailHelper; var destroyed = false; @@ -73,6 +77,18 @@ e.stopPropagation(); } + if(lazyAudioInit) { + if($audio.length == 0) { + $audio = + $('' + + '' + + '') + $parent.append($audio) + audioDomElement = $audio.get(0); + audioBind(); + } + } + if(destroyed) return; if(!audioDomElement) throw "no audio element supplied; the user should not be able to attempt a play" @@ -117,7 +133,7 @@ if(destroyed) return; - if(!audioDomElement) throw "no audio element supplied; the user should not be able to attempt a pause" + if(!lazyAudioInit && !audioDomElement) throw "no audio element supplied; the user should not be able to attempt a pause" transition(PlayStateNone); @@ -133,6 +149,12 @@ } } + function onScreenChanged(e, data) { + if(context.JK.ListenBroadcastCurrentlyPlaying) { + context.JK.ListenBroadcastCurrentlyPlaying.pause(); + } + } + // this is the only way to make audio stop buffering after the user hits pause function recreateAudioElement() { // jeez: http://stackoverflow.com/questions/4071872/html5-video-force-abort-of-buffering/13302599#13302599 @@ -408,6 +430,8 @@ logger.error("unknown state: " + playState) } + $parent.data('listenbroadcast-playstate', playState); + $parent.triggerHandler('statechange.listenBroadcast', { element: $parent, @@ -509,7 +533,9 @@ logger.debug("subscription notification received: type:" + data.type) - updateMountInfo(data.body.mount); + if(data.body.mount) { + updateMountInfo(data.body.mount); + } var id = data.id var type = data.type @@ -519,7 +545,7 @@ var $detailHolder = $mountState.find('.detail-items') var msgType = data.body.type; - var sourceChange = data.body.source_change; + var sourceChange = data.body; if(sourceChange) { var $detail = addSourceChange($detailHolder, sourceChange) if ($detail) { @@ -549,8 +575,8 @@ $detailText.text('Start Broadcast Requested') } else if(sourceChange.change_type == 'source_down_request') { - $who.text('Server') - $detailText.text('Start Broadcast Requested') + $who.html('Server') + $detailText.text('Stop Broadcast Requested') } else { @@ -597,43 +623,61 @@ return $detail; } + function cleanupDetails() { + var mountId = $parent.data('mount-id') + if(mountId) { + context.JK.SubscriptionUtils.unsubscribe('mount', mountId) + } + $mountState = null; + mountInfo = null; + } + + function openBubble() { + checkServer().done(function(response) { + var mountId = response.mount ? response.mount.id : null + + if(mountId) { + rest.getMount({id: mountId}) + .done(function (mount) { + mountInfo = mount + $parent.data('mount-id', mountId) + context.JK.SubscriptionUtils.subscribe('mount', mountId).on(context.JK.EVENTS.SUBSCRIBE_NOTIFICATION, onDetailEvent) + $parent.btOn() + }) + .fail(context.JK.app.ajaxError) + } + else { + mountInfo = null; + context.JK.app.layout.notify('This session can not currently broadcast') + } + }) + .fail(function() { + logger.debug("session is over") + }) + } + function bindHoverDetail() { var stateHolderHtml = context._.template($('#template-listen-broadcast-state').html(), {}, {variable: 'data'}); - context.JK.hoverBubble($parent, stateHolderHtml, {trigger: 'none', preShow: broadcastDetailPreShow, positions:['bottom'], width:'300px', height:'250px'}) + var btOptions = $.extend({}, {trigger: 'none', preShow: broadcastDetailPreShow, postHide: cleanupDetails, closeWhenOthersOpen: true, positions:['bottom', 'right', 'top', 'left'], width:'300px', height:'250px'}, hoverOptions) + context.JK.hoverBubble($parent, stateHolderHtml, btOptions) - $parent.hoverIntent({ - over: function() { - checkServer().done(function(response) { - var mountId = response.mount ? response.mount.id : null + // in case the bt is destroyed + $parent.on('remove', cleanupDetails); - if(mountId) { - rest.getMount({id: mountId}) - .done(function (mount) { - mountInfo = mount - $parent.data('mount-id', mountId) - context.JK.SubscriptionUtils.subscribe('mount', mountId).on(context.JK.EVENTS.SUBSCRIBE_NOTIFICATION, onDetailEvent) - $parent.btOn() - }) - .fail(context.JK.app.ajaxError) - } - else { - mountInfo = null; - context.JK.app.layout.notify('This session can not currently broadcast') - } - }) - .fail(function() { - logger.debug("session is over") - }) - }, - out: function() { - var mountId = $parent.data('mount-id') - context.JK.SubscriptionUtils.unsubscribe('mount', mountId) - $parent.btOff(); - $mountState = null; - mountInfo = null; - } - }) + if($detailHelper) { + $detailHelper.click(function() { openBubble(); return false; }) + } + else { + $parent.hoverIntent({ + over: function() { + openBubble(); + }, + out: function() { + + } + }) + } } function initialize() { @@ -645,23 +689,35 @@ if(fanAccess === null) throw 'fan-access must be specified in $parentElement'; fanAccess = $parent.attr('fan-access') === 'true' // coerce to boolean + if(lazyAudioInit) { + audioSrc = $parent.attr('data-audio-src'); + if(audioSrc === null) throw 'data-audio-src must be specified in $parentElement'; + audioType = $parent.attr('data-audio-type'); + if(audioType === null) throw 'data-audio-type must be specified in $parentElement'; + } + bindHoverDetail(); $audio = $('audio', $parent); - if($audio.length == 0) { + if($audio.length == 0 && !lazyAudioInit) { return; } + if($audio.length > 1) { throw "more than one element found"; } - audioDomElement = $audio.get(0); - audioBind(); + if(!lazyAudioInit) { + audioDomElement = $audio.get(0); + audioBind(); + } $parent.bind('play.listenBroadcast', play); $parent.bind('pause.listenBroadcast', pause); $parent.bind('destroy.listenBroadcast', destroy); + + $(document).on(context.JK.EVENTS.SCREEN_CHANGED, onScreenChanged); } initialize(); diff --git a/web/app/assets/javascripts/layout.js b/web/app/assets/javascripts/layout.js index c37301b38..723e75650 100644 --- a/web/app/assets/javascripts/layout.js +++ b/web/app/assets/javascripts/layout.js @@ -542,6 +542,8 @@ logger.debug("Changing screen to " + currentScreen); + $(document).triggerHandler(EVENTS.SCREEN_CHANGED, {previousScreen: previousScreen, newScreen: currentScreen}) + screenEvent(currentScreen, 'beforeShow', data); // For now -- it seems we want it open always. diff --git a/web/app/assets/javascripts/sessionList.js b/web/app/assets/javascripts/sessionList.js index 3f3ee478d..a1ee1c3d4 100644 --- a/web/app/assets/javascripts/sessionList.js +++ b/web/app/assets/javascripts/sessionList.js @@ -18,11 +18,63 @@ var $latencyTemplate = $('#template-latency'); var $musicianTemplate = $('#template-musician-info'); var showJoinLink = true; + var showListenLink = true; var showRsvpLink = true; - function renderActiveSession(session, tbGroup, myAudioLatency) { + // related to listen + function stateChange(e, data) { + var $listenLink = e.element; + var $listenText = $('.listen-link-text', $listenLink); + var $listenDetails = $('.listen-link-details', $listenLink); - $('#actionHeader', tbGroup).html('JOIN'); + if(data.displayText) + { + if(data.state == 'playing') { + $listenText.text('Stop Listening') + } + else { + $listenText.text(context.JK.toTitleCase(data.displayText)) + $listenDetails.addClass('statusing') + } + } + + if(data.isEnd) { + $listenText.text('Listen').removeClass('statusing') + stopPlay(); + } + + if(data.isSessionOver) { + $listenLink.removeClass('inprogress').addClass('ended') + } + } + + function startPlay($listenLink) { + $listenLink.find('img').attr('src', '/assets/content/pause-icon.jpg'); + $listenLink.trigger('play.listenBroadcast'); + } + + function stopPlay($listenLink) { + $listenLink.find('img').attr('src', '/assets/content/listen-icon.jpg'); + $listenLink.trigger('pause.listenBroadcast'); + } + + function togglePlay() { + var $listenLink = $(this) + var $listenText = $('.listen-link-text', $listenLink); + var $listenDetails = $('.listen-link-details', $listenLink); + if($listenLink.data('listenbroadcast-playstate') == 'playing') { + $listenText.text('Listen') + $listenDetails.removeClass('statusing') + stopPlay($listenLink); + } + else { + startPlay($listenLink); + } + return false; + } + + + function renderActiveSession(session, tbGroup, myAudioLatency) { var i = 0; var inSessionUsersHtml = '', rsvpFirst3UsersHtml = '', rsvpRemainingUsersHtml = '', openSlotsFirst3Html = '', openSlotsRemainingHtml = '', latencyInSessionHtml = '', latencyFirst3Html = '', latencyRemainingHtml = '', notationFileHtml = ''; @@ -32,6 +84,8 @@ var inSessionUsers = []; showJoinLink = session.musician_access; + console.log('session', session) + showListenLink = session.fan_access && session.active_music_session && session.active_music_session.mount; // render musicians who are already in the session if (session.active_music_session && "participants" in session.active_music_session && session.active_music_session.participants.length > 0) { @@ -100,6 +154,7 @@ var sessionVals = buildSessionObject(session, notationFileHtml, rsvpFirst3UsersHtml, rsvpRemainingUsersHtml, openSlotsFirst3Html, openSlotsRemainingHtml, latencyFirst3Html, latencyRemainingHtml, latencyInSessionHtml); sessionVals.in_session_musicians = inSessionUsersHtml.length > 0 ? inSessionUsersHtml : 'N/A'; sessionVals.join_link_display_style = showJoinLink ? "block" : "none"; + sessionVals.listen_link_display_style = showListenLink ? "inline-block" : "none"; var $row = $(context.JK.fillTemplate($activeSessionTemplate.html(), sessionVals)); var $offsetParent = $(tbGroup).closest('.content'); @@ -134,6 +189,26 @@ }); }); + // enable listen button + + if(showListenLink) { + var $listenLink = $('.listen-link', $parentRow) + var $listenText = $('.listen-link-text', $parentRow) + var $listenDetailHover = $('.listen-detail-hover', $parentRow) + $listenLink.parent().hoverIntent({ + over: function() { + $listenDetailHover.show(); + }, + out: function() { + $listenDetailHover.hide(); + } + }) + $listenLink.attr('data-music-session', session.id).attr('fan-access', session.fan_access).attr('data-audio-src', session.active_music_session.mount.url).attr('data-audio-type', session.active_music_session.mount.mime_type) + $listenLink.listenBroadcast({lazyAudioInit:true, hoverOptions: {offsetParent: $offsetParent}, detailHelper: $listenDetailHover}); + $listenLink.bind('statechange.listenBroadcast', stateChange); + $listenLink.click(togglePlay); + } + if (showJoinLink) { // wire up the Join Link to the T&Cs dialog $('.join-link', $parentRow).click(function(evt) { @@ -171,8 +246,6 @@ var hasPendingOrDeclinedRsvp = false; var openRsvps = session.open_rsvps; - $('#actionHeader', tbGroup).html('RSVP'); - var i = 0; var rsvpFirst3UsersHtml = '', rsvpRemainingUsersHtml = '', openSlotsFirst3Html = '', openSlotsRemainingHtml = '', latencyFirst3Html = '', latencyRemainingHtml = '', notationFileHtml = ''; @@ -285,7 +358,7 @@ if (approvedRsvpId) { showRsvpLink = false; - noLinkText = $('You have been confirmed for this session. Cancel'); + noLinkText = $('You have been confirmed for this session. Cancel'); noLinkText.find('a').click(function() { ui.launchRsvpCancelDialog(session.id, approvedRsvpId) .one(EVENTS.RSVP_CANCELED, function() { @@ -302,7 +375,7 @@ } else if (pendingRsvpId) { showRsvpLink = false; - noLinkText = $('You have RSVP\'ed to this session. Cancel'); + noLinkText = $('You have RSVP\'ed to this session. Cancel'); noLinkText.find('a').click(function() { ui.launchRsvpCancelDialog(session.id, pendingRsvpId) .one(EVENTS.RSVP_CANCELED, function() { diff --git a/web/app/assets/javascripts/utils.js b/web/app/assets/javascripts/utils.js index 7e45b7c46..a0b549958 100644 --- a/web/app/assets/javascripts/utils.js +++ b/web/app/assets/javascripts/utils.js @@ -213,6 +213,10 @@ return; } + $element.on('remove', function() { + $element.btOff(); + }) + if ($element instanceof jQuery) { if ($element.length == 0) { logger.error("hoverBubble: no element specified with text %o", text); @@ -449,6 +453,7 @@ }); } + // http://stackoverflow.com/questions/4878756/javascript-how-to-capitalize-first-letter-of-each-word-like-a-2-word-city context.JK.toTitleCase = function(str) { return str.replace(/\w\S*/g, function(txt){ return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); diff --git a/web/app/assets/stylesheets/client/findSession.css.scss b/web/app/assets/stylesheets/client/findSession.css.scss index 7c2ca99c7..a3aee3ce2 100644 --- a/web/app/assets/stylesheets/client/findSession.css.scss +++ b/web/app/assets/stylesheets/client/findSession.css.scss @@ -85,4 +85,42 @@ .latency-value { @include border-radius(2px); } + + .action-links { + a { margin-top:10px; } + } + + .listen-link-details { + position:relative; + left:6px; + + &.statusing { + left:0; + } + } + + .listen-detail-hover { + margin-left:5px; + margin-top:0 !important; + color: #fc0; + display:none; + } + + .join-link { + position:relative; + left:-3px; + } + .join-link-text { + position:relative; + left:6px; + } + + .rsvp-link { + position:relative; + left:0px; + } + .rsvp-link-text { + position:relative; + left:6px; + } } \ No newline at end of file diff --git a/web/app/assets/stylesheets/client/listenBroadcast.css.scss b/web/app/assets/stylesheets/client/listenBroadcast.css.scss index e4d9495fb..03751c9e1 100644 --- a/web/app/assets/stylesheets/client/listenBroadcast.css.scss +++ b/web/app/assets/stylesheets/client/listenBroadcast.css.scss @@ -43,12 +43,12 @@ &[data-status=source_up] {.source_up { display:inline-block; }} &[data-status=source_down] {.source_down { display:inline-block; }} - &[data-status=source_wrong_up] {.source_up { display:inline-block; }} - &[data-status=source_wrong_down] {.source_up { display:inline-block; }} - &[data-status=transition_up] {.source_up { display:inline-block; }} - &[data-status=transition_down] {.source_up { display:inline-block; }} - &[data-status=transition_timeout_up] {.source_up { display:inline-block; }} - &[data-status=transition_timeout_down] {.source_up { display:inline-block; }} + &[data-status=source_wrong_up] {.source_wrong_up { display:inline-block; }} + &[data-status=source_wrong_down] {.source_wrong_down { display:inline-block; }} + &[data-status=transition_up] {.transition_up { display:inline-block; }} + &[data-status=transition_down] {.transition_down { display:inline-block; }} + &[data-status=transition_timeout_up] {.transition_timeout_up { display:inline-block; }} + &[data-status=transition_timeout_down] {.transition_timeout_down { display:inline-block; }} &[data-status=unknown] {.unknown { display:inline-block; }} &[data-status=loading] {.loading { display:inline-block; }} &[data-status=db_error] {.db_error{ display:inline-block; }} @@ -82,7 +82,7 @@ .detail-items { - height:85px; + height:80px; overflow:auto; } diff --git a/web/app/assets/stylesheets/dialogs/shareDialog.css.scss b/web/app/assets/stylesheets/dialogs/shareDialog.css.scss index 6aaf1b7cd..60bff5e51 100644 --- a/web/app/assets/stylesheets/dialogs/shareDialog.css.scss +++ b/web/app/assets/stylesheets/dialogs/shareDialog.css.scss @@ -278,6 +278,12 @@ .link-contents { margin-bottom:20px; + float:left; + } + + #btn-share-copy { + top:-4px; + position:relative; } } diff --git a/web/app/views/api_icecast/source_change_notification.rabl b/web/app/views/api_icecast/source_change_notification.rabl index 012ae9ebf..5c1f87744 100644 --- a/web/app/views/api_icecast/source_change_notification.rabl +++ b/web/app/views/api_icecast/source_change_notification.rabl @@ -4,7 +4,7 @@ node do |source_change| partial("api_icecast/show_source_change", :object => source_change) end -child(:mount) { |mount| +child(:mount => :mount) { |mount| attributes :id, :listeners, :source_direction, :sourced node :state do |mount| diff --git a/web/app/views/clients/_findSession.html.erb b/web/app/views/clients/_findSession.html.erb index 094794387..69c5c97c7 100644 --- a/web/app/views/clients/_findSession.html.erb +++ b/web/app/views/clients/_findSession.html.erb @@ -91,6 +91,21 @@ Notation Files: {notation_files} + + + + <%= image_tag "content/listen-icon.jpg", :size => "40x40" %> + + + Listen? + + + + <%= image_tag "content/join-icon.jpg", :size => "37x40" %> + + Join + + @@ -156,11 +171,6 @@ Legal Policy:{legal_policy} - - - <%= image_tag "content/icon_join.png", :size => "19x22" %> - - @@ -183,6 +193,18 @@ {scheduled_start} + + + + + You cannot RSVP to this session. + + <%= image_tag "content/rsvp-icon.jpg", :size => "40x40" %> + + + RSVP + + @@ -235,12 +257,6 @@ Legal Policy:{legal_policy} - - You cannot RSVP to this session. - - <%= image_tag "content/icon_join.png", :size => "19x22" %> - - diff --git a/web/app/views/clients/_sessionList.html.erb b/web/app/views/clients/_sessionList.html.erb index d5d34f228..e326b8e8d 100644 --- a/web/app/views/clients/_sessionList.html.erb +++ b/web/app/views/clients/_sessionList.html.erb @@ -7,8 +7,7 @@ SESSION MUSICIANS LATENCY - POLICIES - + POLICIES diff --git a/web/config/scheduler.yml b/web/config/scheduler.yml index cf5c1e80a..59cae771c 100644 --- a/web/config/scheduler.yml +++ b/web/config/scheduler.yml @@ -26,12 +26,12 @@ UnusedMusicNotationCleaner: description: "Remove unused music notations" UserProgressEmailer: - cron: "30 21 * * *" +# cron: "30 21 * * *" class: "JamRuby::UserProgressEmailer" description: "Sends periodic user progress emails" DailySessionEmailer: - cron: "0 6 * * *" +# cron: "0 6 * * *" class: "JamRuby::DailySessionEmailer" description: "Sends daily scheduled session emails" @@ -41,7 +41,7 @@ ScheduledMusicSessionCleaner: description: "Purges old, forgotten sessions that have not been started for >4 weeks" NewMusicianEmailer: - cron: "0 1 * * 1" +# cron: "0 1 * * 1" class: "JamRuby::NewMusicianEmailer" description: "Sends weekly emails of new users with good latency"