Merge branch 'develop' of bitbucket.org:jamkazam/jam-cloud into develop

This commit is contained in:
Jonathan Kolyer 2014-10-31 05:05:05 +00:00
commit e7b8d05f6d
77 changed files with 683 additions and 230 deletions

View File

@ -126,6 +126,8 @@ module JamAdmin
config.twitter_app_secret = ENV['TWITTER_APP_SECRET'] || 'Azcy3QqfzYzn2fsojFPYXcn72yfwa0vG6wWDrZ3KT8'
config.ffmpeg_path = ENV['FFMPEG_PATH'] || (File.exist?('/usr/local/bin/ffmpeg') ? '/usr/local/bin/ffmpeg' : '/usr/bin/ffmpeg')
config.normalize_ogg_path = ENV['NORMALIZE_OGG_PATH'] || (File.exist?('/usr/local/bin/normalize-ogg') ? '/usr/local/bin/normalize-ogg' : '/usr/bin/normalize-ogg')
config.normalize_mp3_path = ENV['NORMALIZE_MP3_PATH'] || (File.exist?('/usr/local/bin/normalize-mp3') ? '/usr/local/bin/normalize-mp3' : '/usr/bin/normalize-mp3')
config.max_audio_downloads = 100

View File

@ -481,6 +481,7 @@ message RecordingMasterMixComplete {
optional string msg = 3;
optional string notification_id = 4;
optional string created_at = 5;
optional string claimed_recording_id = 6;
}
message DownloadAvailable {

View File

@ -27,6 +27,7 @@ require "jam_ruby/constants/validation_messages"
require "jam_ruby/errors/permission_error"
require "jam_ruby/errors/state_error"
require "jam_ruby/errors/jam_argument_error"
require "jam_ruby/errors/conflict_error"
require "jam_ruby/lib/app_config"
require "jam_ruby/lib/s3_manager_mixin"
require "jam_ruby/lib/module_overrides"

View File

@ -8,7 +8,7 @@
<br/>
<br/>
<p>
This email was received because someone left feedback at <a style="color: #588C98;" href="http://www.jamkazam.com/corp/contact">http://www.jamkazam.com/corp/contact</a>
This email was received because someone left feedback at <a href="http://www.jamkazam.com/corp/contact">http://www.jamkazam.com/corp/contact</a>
</p>
</body>
</html>

View File

@ -1,7 +1,7 @@
<% provide(:title, "You've been invited to JamKazam by #{@sender.name}!") %>
<% provide(:photo_url, @sender.resolved_photo_url) %>
To signup, please go to the <a style="color: #588C98;" href="<%= @signup_url %>">create account</a> page.
To signup, please go to the <a href="<%= @signup_url %>">create account</a> page.
<% content_for :note do %>
<% if @note %>

View File

@ -1,3 +1,3 @@
<% provide(:title, 'Welcome to the JamKazam Beta!') %>
To signup, please go to the <a style="color: #588C98;" href="<%= @signup_url %>">create account</a> page.
To signup, please go to the <a href="<%= @signup_url %>">create account</a> page.

View File

@ -7,7 +7,7 @@
</p>
<p>
<a style="color: #588C98;" href="https://jamkazam.desk.com/">https://jamkazam.desk.com</a>
<a href="https://jamkazam.desk.com/">https://jamkazam.desk.com</a>
</p>
<p>-- Team JamKazam

View File

@ -7,11 +7,11 @@
</p>
<p>
<a style="color: #588C98;" href="http://www.jamkazam.com/downloads">Go to Download Page</a>
<a href="http://www.jamkazam.com/downloads">Go to Download Page</a>
</p>
<p>
<a style="color: #588C98;" href="https://jamkazam.desk.com">Go to Support Center</a>
<a href="https://jamkazam.desk.com">Go to Support Center</a>
</p>
<p>-- Team JamKazam

View File

@ -6,13 +6,13 @@
<p>We noticed that you have not yet successfully set up your audio gear and passed the JamKazam latency and input/output audio gear tests. This means that you cannot yet play in online sessions with other musicians. If you are having trouble with this step, please click the link below for a knowledge base article that can help you get past this hurdle. If the test says your audio gear is not fast enough, or if your audio quality sounds poor, or if you are just confused, its very likely the tips in this article will help you get things set up and optimized so you can start playing online.
</p>
<p><a style="color: #588C98;" href="http://bit.ly/1i4Uul4">http://bit.ly/1i4Uul4</a>
<p><a href="http://bit.ly/1i4Uul4">http://bit.ly/1i4Uul4</a>
</p>
<p>And if this knowledge base article does not get you fixed up, please visit our JamKazam support center at the link below, and post a request for assistance so that we can help you get up and running:
</p>
<p><a style="color: #588C98;" href="https://jamkazam.desk.com">https://jamkazam.desk.com</a>
<p><a href="https://jamkazam.desk.com">https://jamkazam.desk.com</a>
</p>
<p>-- Team JamKazam

View File

@ -10,7 +10,7 @@
Its still very early in our companys development, so we dont have zillions of users online on our service yet. If you click Find Session, you will often not find a good session to join, both due to the number of musicians online at any given time, and also because you wont see private sessions where groups of musicians dont want to be interrupted in their sessions.
</p>
<p>If you are having trouble getting into sessions, wed suggest you click the Musicians tile on the home screen of the app or the website: <a style="color: #588C98;" href="http://www.jamkazam.com/client#/musicians">Go To Musicians Page</a>
<p>If you are having trouble getting into sessions, wed suggest you click the Musicians tile on the home screen of the app or the website: <a href="http://www.jamkazam.com/client#/musicians">Go To Musicians Page</a>
</p>
<p>This will display the JamKazam musicians sorted by latency to you - in other words, you can see which musicians have good network connections to you. Any musicians with green and yellow latency scores have good enough connections to support a play session with you. We recommend that read the profiles of these musicians to find others with shared musical interests and good network connections to you, and then use the Message button to say hi and see if they are interested in playing with you. If they are, use the Connect button to “friend” them on JamKazam, and use the Message button to set up a time to meet online for a session.
@ -24,7 +24,7 @@ One of the best ways to connect and play with others is to invite your friends f
If you are having audio quality problems or other issues when you get into a session, please click the link below to visit our support center, and check the knowledge base articles under the Troubleshooting header to find solutions. And if that doesnt work, please post a request for assistance in the support center so that we can help you get up and running:
</p>
<p><a style="color: #588C98;" href="https://jamkazam.desk.com">https://jamkazam.desk.com</a>
<p><a href="https://jamkazam.desk.com">https://jamkazam.desk.com</a>
</p>
<p>-- Team JamKazam

View File

@ -7,7 +7,7 @@
</p>
<p>Find Other Musicians on JamKazam<br />
To find and connect with other musicians who are already on JamKazam, wed suggest you click the Musicians tile on the home screen of the app or the website: <a style="color: #588C98;" href="http://www.jamkazam.com/client#/musicians">Go To Musicians Page</a>
To find and connect with other musicians who are already on JamKazam, wed suggest you click the Musicians tile on the home screen of the app or the website: <a href="http://www.jamkazam.com/client#/musicians">Go To Musicians Page</a>
</p>
<p>This will display the JamKazam musicians sorted by latency to you - in other words, you can see which musicians have good network connections to you. Any musicians with green and yellow latency scores have good enough connections to support a play session with you. We recommend that you read the profiles of these musicians to find others with shared musical interests and good network connections to you, and then use the Message button to say hi and see if they are interested in playing with you. If they are, use the Connect button to “friend” them on JamKazam, and use the Message button to set up a time to meet online for a session.
@ -20,7 +20,7 @@ One of the best ways to connect and play with others is to invite your friends f
<p>If you have any trouble, please visit our support center at the link below any time to get help:
</p>
<p><a style="color: #588C98;" href="https://jamkazam.desk.com/">https://jamkazam.desk.com</a>
<p><a href="https://jamkazam.desk.com/">https://jamkazam.desk.com</a>
</p>
<p>-- Team JamKazam

View File

@ -9,7 +9,7 @@
<p>If you have any trouble, please visit our support center at the link below any time to get help:
</p>
<p><a style="color: #588C98;" href="https://jamkazam.desk.com">https://jamkazam.desk.com</a>
<p><a href="https://jamkazam.desk.com">https://jamkazam.desk.com</a>
</p>
<p>-- Team JamKazam

View File

@ -9,7 +9,7 @@
<p>If you are having audio quality problems or other issues when you get into a session, please click the link below to visit our support center, and check the knowledge base articles under the Troubleshooting header to find solutions. And if that doesnt work, please post a request for assistance in the support center so that we can help you get up and running:
</p>
<p><a style="color: #588C98;" href="https://jamkazam.desk.com">https://jamkazam.desk.com</a>
<p><a href="https://jamkazam.desk.com">https://jamkazam.desk.com</a>
</p>
<p>We really want you to be successful and have fun with this new way of playing music with others, so please reach out and let us help you!

View File

@ -1,3 +1,3 @@
<% provide(:title, 'New Band Session') %>
<p><%= @body %>&nbsp;<a style="color: #588C98;" href="<%= @session_url %>">Listen in.</a></p>
<p><%= @body %>&nbsp;<a href="<%= @session_url %>">Listen in.</a></p>

View File

@ -2,4 +2,4 @@
<p>Welcome to JamKazam, <%= @user.first_name %>!</p>
<p>To confirm this email address, please go to the <a style="color: #588C98;" href="<%= @signup_confirm_url %>">signup confirmation page</a>.</p>
<p>To confirm this email address, please go to the <a href="<%= @signup_confirm_url %>">signup confirmation page</a>.</p>

View File

@ -2,4 +2,4 @@
<p><%= @body %></p>
<p>To accept this friend request, <a style="color: #588C98;" href="<%= @url %>">click here</a>.</p>
<p>To accept this friend request, <a href="<%= @url %>">click here</a>.</p>

View File

@ -1,3 +1,3 @@
<% provide(:title, 'Musician in Session') %>
<p><%= @body %>&nbsp;<a style="color: #588C98;" href="<%= @session_url %>">Listen in.</a></p>
<p><%= @body %>&nbsp;<a href="<%= @session_url %>">Listen in.</a></p>

View File

@ -24,7 +24,7 @@ Hi <%= @user.first_name %>,
<% end %>
</table>
</p>
<p>There are currently <%= @new_musicians.size%> musicians on JamKazam with low enough latency Internet connections to you to support a good online session. To see ALL the JamKazam musicians with whom you may want to connect and play, view our Musicians page at: <a style="color: #588C98;" href="http://www.jamkazam.com/client#/musicians">http://www.jamkazam.com/client#/musicians</a>.
<p>There are currently <%= @new_musicians.size%> musicians on JamKazam with low enough latency Internet connections to you to support a good online session. To see ALL the JamKazam musicians with whom you may want to connect and play, view our Musicians page at: <a href="http://www.jamkazam.com/client#/musicians">http://www.jamkazam.com/client#/musicians</a>.
</p>
<p>Best Regards,</p>

View File

@ -1,3 +1,3 @@
<% provide(:title, 'JamKazam Password Reset') %>
Visit this link so that you can change your JamKazam password: <a style="color: #588C98;" href="<%= @password_reset_url %>">reset password</a>.
Visit this link so that you can change your JamKazam password: <a href="<%= @password_reset_url %>">reset password</a>.

View File

@ -2,4 +2,4 @@
<p><%= @body %></p>
<p><a style="color: #588C98;" href="<%= @session_url %>">View Session Details</a></p>
<p><a href="<%= @session_url %>">View Session Details</a></p>

View File

@ -7,5 +7,5 @@
<p><%= @session_name %></p>
<p><%= @session_date %></p>
<p><a style="color: #588C98;" href="<%= @session_url %>">View Session Details</a></p>
<p><a href="<%= @session_url %>">View Session Details</a></p>
<% end %>

View File

@ -68,7 +68,7 @@
<td><%= sess.genre.description %></td>
<td>
<%= sess.name %><br/>
<a style="color: #588C98;" href="<%= "http://www.jamkazam.com/sessions/#{sess.id}/details" %>">Details</a>
<a href="<%= "http://www.jamkazam.com/sessions/#{sess.id}/details" %>">Details</a>
</td>
<td><%= sess.description %></td>
<td style="text-align:center">
@ -86,7 +86,7 @@
</tbody>
</table>
<p>To see ALL the scheduled sessions that you might be interested in joining, view our <a style="color: #588C98;" href="http://www.jamkazam.com/client#/findSession">Find Session page</a>.</p>
<p>To see ALL the scheduled sessions that you might be interested in joining, view our <a href="http://www.jamkazam.com/client#/findSession">Find Session page</a>.</p>
<p>Best Regards,</p>

View File

@ -7,4 +7,4 @@
<%= @session_date %>
</p>
<p><a style="color: #588C98;" href="<%= @session_url %>">View Session Details</a></p>
<p><a href="<%= @session_url %>">View Session Details</a></p>

View File

@ -7,4 +7,4 @@
<%= @session_date %>
</p>
<p><a style="color: #588C98;" href="<%= @session_url %>">View Session Details</a></p>
<p><a href="<%= @session_url %>">View Session Details</a></p>

View File

@ -7,4 +7,4 @@
<%= @session_date %>
</p>
<p><a style="color: #588C98;" href="<%= @session_url %>">View Session Details</a></p>
<p><a href="<%= @session_url %>">View Session Details</a></p>

View File

@ -7,4 +7,4 @@
<%= @session_date %>
</p>
<p><a style="color: #588C98;" href="<%= @session_url %>">View Session Details</a></p>
<p><a href="<%= @session_url %>">View Session Details</a></p>

View File

@ -7,4 +7,4 @@
<%= @session_date %>
</p>
<p><a style="color: #588C98;" href="<%= @session_url %>">View Session Details</a></p>
<p><a href="<%= @session_url %>">View Session Details</a></p>

View File

@ -7,4 +7,4 @@
<%= @session_date %>
</p>
<p><a style="color: #588C98;" href="<%= @session_url %>">View Session Details</a></p>
<p><a href="<%= @session_url %>">View Session Details</a></p>

View File

@ -7,4 +7,4 @@
<%= @session_date %>
</p>
<p><a style="color: #588C98;" href="<%= @session_url %>">View Session Details</a></p>
<p><a href="<%= @session_url %>">View Session Details</a></p>

View File

@ -4,5 +4,5 @@
<% content_for :note do %>
<%= @note %>
<p>To reply to this message, <a style="color: #588C98;" href="<%= @url %>">click here</a>.</p>
<p>To reply to this message, <a href="<%= @url %>">click here</a>.</p>
<% end %>

View File

@ -1,3 +1,3 @@
<% provide(:title, 'Please Confirm New JamKazam Email') %>
Please click the following link to confirm your change in email: <a style="color: #588C98;" href="<%= @user.update_email_confirmation_url %>">confirm email</a>.
Please click the following link to confirm your change in email: <a href="<%= @user.update_email_confirmation_url %>">confirm email</a>.

View File

@ -11,31 +11,31 @@
<p>
Getting Started Video<br/>
We recommend watching this video before you jump into the service just to get oriented. It will really help you hit the ground running:
<a style="color: #588C98;" href="https://www.youtube.com/watch?v=VexH4834o9I">https://www.youtube.com/watch?v=VexH4834o9I</a>
<a href="https://www.youtube.com/watch?v=VexH4834o9I">https://www.youtube.com/watch?v=VexH4834o9I</a>
</p>
<p>
Other Great Tutorial Videos<br />
There are several other very great videos that will help you understand how to find and connect with other musicians on the service, create your own sessions or find and join other musicians sessions, play in sessions, record and share your performances, and even live broadcast your sessions to family, friends, and fans. Check these helpful videos out here:
<a style="color: #588C98;" href="https://jamkazam.desk.com/customer/portal/articles/1304097-tutorial-videos">https://jamkazam.desk.com/customer/portal/articles/1304097-tutorial-videos</a>
<a href="https://jamkazam.desk.com/customer/portal/articles/1304097-tutorial-videos">https://jamkazam.desk.com/customer/portal/articles/1304097-tutorial-videos</a>
</p>
<p>
Knowledge Base Articles<br />
You can find Getting Started knowledge base articles on things like frequently asked questions (FAQ), minimum system requirements for your Windows or Mac computer, how to troubleshoot audio problems in sessions, and more here:
<a style="color: #588C98;" href="https://jamkazam.desk.com/customer/portal/topics/564807-getting-started/articles">https://jamkazam.desk.com/customer/portal/topics/564807-getting-started/articles</a>
<a href="https://jamkazam.desk.com/customer/portal/topics/564807-getting-started/articles">https://jamkazam.desk.com/customer/portal/topics/564807-getting-started/articles</a>
</p>
<p>
JamKazam Support Portal<br />
If you run into trouble and need help, please reach out to us. We will be glad to do everything we can to get you up and running. You can find our support portal here:
<a style="color: #588C98;" href="https://jamkazam.desk.com/">https://jamkazam.desk.com/</a>
<a href="https://jamkazam.desk.com/">https://jamkazam.desk.com/</a>
</p>
<p>
JamKazam Community Forum<br />
And if you just want to chat, share tips and war stories, and hang out with fellow JamKazamers, you can visit our community forum here:
<a style="color: #588C98;" href="http://forums.jamkazam.com/">http://forums.jamkazam.com/</a>
<a href="http://forums.jamkazam.com/">http://forums.jamkazam.com/</a>
</p>
<p>

View File

@ -9,6 +9,9 @@
margin-bottom:0px;
line-height:140%;
}
a {
color: #ffcc00 !important;
}
</style>
</head>
@ -48,7 +51,7 @@
<!-- CALL OUT BOX -->
</font></p>
<p style="margin-top:0px"><font size="2" color="#7FACBA" face="Arial, Helvetica, sans-serif">This email was sent to you because you have an account at <a style="color: #588C98;" href="http://www.jamkazam.com">JamKazam</a>.
<p style="margin-top:0px"><font size="2" color="#7FACBA" face="Arial, Helvetica, sans-serif">This email was sent to you because you have an account at <a href="http://www.jamkazam.com">JamKazam</a>.
</td></tr></table>
</td>

View File

@ -9,7 +9,9 @@
margin-bottom:0px;
line-height:140%;
}
a {
color: #ffcc00 !important;
}
</style>
</head>
@ -37,7 +39,7 @@
<td align="left">
<!-- CALL OUT BOX -->
<p style="margin-top:0px"><font size="2" color="#7FACBA" face="Arial, Helvetica, sans-serif">This email was sent to you because you have an account at <a style="color: #588C98;" href="http://www.jamkazam.com">JamKazam</a>.&nbsp;&nbsp;Click <a style="color: #588C98;" href="http://www.jamkazam.com/client#/account/profile">here to unsubscribe</a> and update your profile settings.
<p style="margin-top:0px"><font size="2" color="#7FACBA" face="Arial, Helvetica, sans-serif">This email was sent to you because you have an account at <a href="http://www.jamkazam.com">JamKazam</a>.&nbsp;&nbsp;Click <a href="http://www.jamkazam.com/client#/account/profile">here to unsubscribe</a> and update your profile settings.
</font></p>
</td></tr></table>

View File

@ -0,0 +1,5 @@
module JamRuby
class ConflictError < Exception
end
end

View File

@ -27,6 +27,10 @@ module JamRuby
"#{base_url}/findSession"
end
def self.session(session)
"#{base_url}/session/#{session.id}"
end
private
def self.base_url

View File

@ -712,9 +712,10 @@ module JamRuby
)
end
def recording_master_mix_complete(receiver_id, recording_id, band_id, msg, notification_id, created_at)
def recording_master_mix_complete(receiver_id, recording_id, claimed_recording_id, band_id, msg, notification_id, created_at)
recording_master_mix_complete = Jampb::RecordingMasterMixComplete.new(
:recording_id => recording_id,
:claimed_recording_id => claimed_recording_id,
:band_id => band_id,
:msg => msg,
:notification_id => notification_id,

View File

@ -724,7 +724,7 @@ module JamRuby
query = query.offset(offset)
query = query.limit(limit)
query = query.where("music_sessions.create_type IS NULL OR music_sessions.create_type != ?", MusicSession::CREATE_TYPE_QUICK_START)
query = query.where("music_sessions.create_type IS NULL OR (music_sessions.create_type != ? AND music_sessions.create_type != ?)", MusicSession::CREATE_TYPE_QUICK_START, MusicSession::CREATE_TYPE_IMMEDIATE)
query = query.where("music_sessions.genre_id = ?", genre) unless genre.blank?
query = query.where('music_sessions.language = ?', lang) unless lang.blank?
query = query.where("(description_tsv @@ to_tsquery('jamenglish', ?))", ActiveRecord::Base.connection.quote(keyword) + ':*') unless keyword.blank?

View File

@ -1147,6 +1147,7 @@ module JamRuby
msg = @@message_factory.recording_master_mix_complete(
claimed_recording.user_id,
recording.id,
claimed_recording.id,
notification.band_id,
notification_msg,
notification.id,

View File

@ -154,7 +154,7 @@ module JamRuby
# authorize the user attempting to respond to the RSVP request
if music_session.creator.id != user.id
raise PermissionError, "Only the session organizer can accept or decline and RSVP request."
raise PermissionError, "Only the session organizer can accept or decline an RSVP request."
end
rsvp_request = RsvpRequest.find_by_id(rsvp_request_id)
@ -181,8 +181,35 @@ module JamRuby
raise StateError, "Slot does not exist"
end
# slot has already been accepted
if rsvp_slot.chosen && r[:accept]
raise StateError, "All RSVP slots for the #{rsvp_slot.instrument_id} have been already approved."
# get the instrument and skill level for this slot and see if there are others available
# and auto-reassign the request_slot to the open rsvp_slot
open_slots = music_session.open_slots
# don't worry about matching proficiency if the user RSVPed to "Any Skill Level"
if rsvp_slot.proficiency_level == 0
open_slots = open_slots.select { |slot| slot.instrument_id == rsvp_slot.instrument_id }
else
open_slots = open_slots.select { |slot| slot.instrument_id == rsvp_slot.instrument_id && slot.proficiency_level == rsvp_slot.proficiency_level }
end
# (1) another available slot exists matching this instrument and proficiency
unless open_slots.blank?
rsvp_slot = open_slots.first
request_slot.rsvp_slot_id = rsvp_slot.id # this links the RsvpRequestRsvpSlot to the available open slot matching this instrument and proficiency level
# (2) no identical slots available => create a new one on the fly
else
new_slot = RsvpSlot.new
new_slot.instrument_id = rsvp_slot.instrument_id
new_slot.proficiency_level = rsvp_slot.proficiency_level
new_slot.music_session_id = rsvp_slot.music_session_id
new_slot.is_unstructured_rsvp = rsvp_slot.is_unstructured_rsvp
new_slot.save!
request_slot.rsvp_slot_id = new_slot.id # saved below on line 218
end
end
if r[:accept]

View File

@ -322,7 +322,7 @@ module JamRuby
raise "no output ogg file after mix" unless File.exist? @output_ogg_filename
ffmpeg_cmd = "#{APP_CONFIG.ffmpeg_path} -i \"#{@output_ogg_filename}\" -ab 128k -metadata JamRecordingId=#{@manifest[:recording_id]} -metadata JamMixId=#{@mix_id} -metadata JamType=Mix \"#{@output_mp3_filename}\""
ffmpeg_cmd = "#{APP_CONFIG.ffmpeg_path} -i \"#{@output_ogg_filename}\" -ab 192k -metadata JamRecordingId=#{@manifest[:recording_id]} -metadata JamMixId=#{@mix_id} -metadata JamType=Mix \"#{@output_mp3_filename}\""
system(ffmpeg_cmd)
@ -335,6 +335,30 @@ module JamRuby
end
raise "no output mp3 file after conversion" unless File.exist? @output_mp3_filename
# time to normalize both mp3 and ogg files
normalize_ogg_cmd = "#{APP_CONFIG.normalize_ogg_path} --bitrate 128 -i \"#{@output_ogg_filename}\""
system(normalize_ogg_cmd)
unless $? == 0
@error_reason = 'normalize-ogg-failed'
@error_detail = $?.to_s
error_msg = "normalize-ogg failed status=#{$?} error_reason=#{@error_reason} error_detail=#{@error_detail}"
@@log.info(error_msg)
raise error_msg
end
raise "no output ogg file after normalization" unless File.exist? @output_ogg_filename
normalize_mp3_cmd = "#{APP_CONFIG.normalize_mp3_path} --bitrate 128 -i \"#{@output_mp3_filename}\""
system(normalize_mp3_cmd)
unless $? == 0
@error_reason = 'normalize-mp3-failed'
@error_detail = $?.to_s
error_msg = "normalize-mp3 failed status=#{$?} error_reason=#{@error_reason} error_detail=#{@error_detail}"
@@log.info(error_msg)
raise error_msg
end
raise "no output mp3 file after conversion" unless File.exist? @output_mp3_filename
end
def symbolize_keys(obj)

View File

@ -228,7 +228,47 @@ describe RsvpRequest do
n.description.should == NotificationTypes::SCHEDULED_SESSION_RSVP_APPROVED
end
it "should not allow approval of RSVP for a slot that has already been approved" do
it "should allow approval of multiple RSVPs to same slot ID when multiple slots exist for same instrument and proficiency level" do
@music_session.open_rsvps = true
@music_session.save!
@slot2.delete
slot2 = FactoryGirl.build(:rsvp_slot, :music_session => @music_session, :instrument => JamRuby::Instrument.find('electric guitar'))
slot2.save!
rsvp1 = RsvpRequest.create({:session_id => @music_session.id, :rsvp_slots => [@slot1.id], :message => "Let's Jam!"}, @session_invitee)
rsvp2 = RsvpRequest.create({:session_id => @music_session.id, :rsvp_slots => [@slot1.id], :message => "Let's Jam!"}, @non_session_invitee)
rs1 = RsvpRequestRsvpSlot.find_by_rsvp_request_id(rsvp1.id)
rs1.rsvp_slot_id.should == @slot1.id
# approve RSVP 1
RsvpRequest.update({:id => rsvp1.id, :session_id => @music_session.id, :rsvp_responses => [{:request_slot_id => rs1.id, :accept => true}]}, @session_creator)
# ensure slot ID didn't change
rs1 = RsvpRequestRsvpSlot.find_by_rsvp_request_id(rsvp1.id)
rs1.rsvp_slot_id.should == @slot1.id
# check before approving
rs2 = RsvpRequestRsvpSlot.find_by_rsvp_request_id(rsvp2.id)
rs2.rsvp_slot_id.should == @slot1.id
# approve RSVP 2 (same slot ID, but slot2 is same instrument + proficiency level)
RsvpRequest.update({:id => rsvp2.id, :session_id => @music_session.id, :rsvp_responses => [{:request_slot_id => rs2.id, :accept => true}]}, @session_creator)
# should have linked the RsvpRequestRsvpSlot record to the second slot
rs2 = RsvpRequestRsvpSlot.find_by_rsvp_request_id(rsvp2.id)
rs2.rsvp_slot_id.should == slot2.id
slots = RsvpSlot.where("music_session_id = ?", @music_session.id)
slots.count.should == 2
request_slots = RsvpRequestRsvpSlot.where("chosen=true")
request_slots.count.should == 2
end
it "should create a new slot for an RSVP for a slot that has already been approved" do
rsvp = RsvpRequest.create({:session_id => @music_session.id, :rsvp_slots => [@slot1.id, @slot2.id], :message => "Let's Jam!"}, @session_invitee)
# approve
@ -239,7 +279,10 @@ describe RsvpRequest do
# approve again
rs1 = RsvpRequestRsvpSlot.find_by_rsvp_slot_id(@slot1.id)
rs2 = RsvpRequestRsvpSlot.find_by_rsvp_slot_id(@slot2.id)
expect {RsvpRequest.update({:id => rsvp.id, :session_id => @music_session.id, :rsvp_responses => [{:request_slot_id => rs1.id, :accept => true}, {:request_slot_id => rs2.id, :accept => true}]}, @session_creator)}.to raise_error(StateError)
RsvpRequest.update({:id => rsvp.id, :session_id => @music_session.id, :rsvp_responses => [{:request_slot_id => rs1.id, :accept => true}, {:request_slot_id => rs2.id, :accept => true}]}, @session_creator)
slots = RsvpSlot.where("music_session_id = ?", @music_session.id)
slots.count.should == 4
end
end

View File

@ -38,6 +38,14 @@ def app_config
ENV['FFMPEG_PATH'] || '/usr/local/bin/ffmpeg'
end
def normalize_ogg_path
ENV['NORMALIZE_OGG_PATH'] || '/usr/local/bin/normalize-ogg'
end
def normalize_mp3_path
ENV['NORMALIZE_MP3_PATH'] || '/usr/local/bin/normalize-mp3'
end
def icecast_reload_cmd
'true' # as in, /bin/true
end

View File

@ -31,7 +31,6 @@
function beforeShow(data) {
sessionId = data.id;
console.log("sessionId=%o", sessionId);
loadSessionData();
}
@ -41,8 +40,7 @@
function inviteMusicians(e) {
e.preventDefault();
friendInput = inviteMusiciansUtil.inviteSessionUpdate('#update-session-invite-musicians',
sessionId);
friendInput = inviteMusiciansUtil.inviteSessionUpdate('#update-session-invite-musicians', sessionId);
inviteMusiciansUtil.loadFriends();
$(friendInput).show();
// invitationDialog.showEmailDialog();
@ -85,12 +83,55 @@
e.preventDefault();
var rsvpId = $(e.target).attr('request-id');
var userName = $(e.target).attr('user-name');
var instrumentIds = $(e.target).attr('data-instrument-text');
var params = buildRsvpRequestActionParams(rsvpId, true);
// first check if any open slots exist for these instruments
rest.getOpenSessionSlots(sessionData.id, true)
.done(function(openSlots) {
if (openSlots) {
if (openSlots.length === 0) {
ui.launchRsvpCreateSlotDialog(sessionData.id, instrumentIds.split('|'), userName);
}
else {
var arrInstrumentIds = instrumentIds.split('|');
var openSlotInstrumentIds = [];
var unavailableSlotInstrumentIds = [];
// ensure each instrument in the user's list is available in the open slots list
$.each(openSlots, function(index, slot) {
openSlotInstrumentIds.push(slot.instrument_id);
});
// build list of instrument IDs in the RSVP request for which there are no open slots
for (var i=0; i < arrInstrumentIds.length; i++) {
if ($.inArray(arrInstrumentIds[i], openSlotInstrumentIds) === -1) {
unavailableSlotInstrumentIds.push(arrInstrumentIds[i]);
}
}
if (unavailableSlotInstrumentIds.length > 0) {
ui.launchRsvpCreateSlotDialog(sessionData.id, unavailableSlotInstrumentIds, userName, function() {
approve(rsvpId, params);
});
}
else {
approve(rsvpId, params);
}
}
}
else {
ui.launchRsvpCreateSlotDialog(sessionData.id, instrumentIds.split('|'), userName);
}
});
}
function approve(rsvpId, params) {
rest.updateRsvpRequest(rsvpId, params)
.done(refreshSessionDetail)
.fail(function(jqXHR, textStatus, errorMessage) {
if (jqXHR.status === 400) {
if (jqXHR.status === 409) {
app.notify(
{
title: "Unable to Approve RSVP",
@ -284,11 +325,14 @@
var latencyHtml = "";
$.each(sessionData.pending_rsvp_requests, function(index, pending_rsvp_request) {
if (pending_rsvp_request.user_id != context.JK.currentUserId) {
var instrumentDesc = [];
if ("instrument_list" in pending_rsvp_request && pending_rsvp_request.instrument_list != null) {
$.each(pending_rsvp_request.instrument_list, function (index, instrument) {
var instrumentId = instrument == null ? null : instrument.id;
var inst = context.JK.getInstrumentIcon24(instrumentId);
instrumentLogoHtml += '<img title="' + instrumentId + '" hoveraction="instrument" data-instrument-id="' + instrumentId + '" src="' + inst + '" width="24" height="24" />&nbsp;';
instrumentDesc.push(instrumentId);
})
}
@ -304,7 +348,7 @@
$templateAccountPendingRsvp.html(),
{user_id: pending_rsvp_request.user_id, avatar_url: avatar_url,
user_name: pending_rsvp_request.user.name, instruments: instrumentLogoHtml,
latency: latencyHtml, request_id: pending_rsvp_request.id},
latency: latencyHtml, request_id: pending_rsvp_request.id, instrument_text: instrumentDesc.join('|')},
{variable: 'data'}
);

View File

@ -9,7 +9,9 @@
var rest = context.JK.Rest();
var sessionId = null;
var sessionData = null;
var $screen = null;
var $screen = $("#account-session-properties");
var $inputFiles = $screen.find('#session-select-files');
var $selectedFilenames = $screen.find('#selected-filenames');
var $propertiesBody = null;
var $languageList = null;
var $recurringModeList = null;
@ -62,10 +64,11 @@
function events() {
$startTimeList.on('change', toggleStartTime);
$btnSelectFiles.on('click', toggleSelectFiles);
$dateTimeTBD.on('ifChanged', toggleDateTBD);
$screen.find("#session-update").on('click', updateSessionProperties);
$screen.find('#session-prop-select-files').on('change', changeSelectedFiles);
$inputFiles.on('change', changeSelectedFiles);
$btnSelectFiles.on('click', toggleSelectFiles);
}
function toggleStartTime() {
@ -252,7 +255,8 @@
// }
}
data.recurring_mode = $recurringModeList.val();
data.music_notations = sessionData.music_notations;
data.music_notations = JSON.stringify(sessionData.music_notations);
console.log("data.music_notations=%o", data.music_notations);
data.timezone = $timezoneList.val();
data.open_rsvps = $screen.find("#open-rsvp")[0].checked;
@ -306,6 +310,7 @@
function uploadNotations(notations) {
var formData = new FormData();
var maxExceeded = false;
$.each(notations, function(i, file) {
var max = 10 * 1024 * 1024;
if(file.size > max) {
@ -326,6 +331,7 @@
}
formData.append('client_id', app.clientId);
//formData.append('session_id', sessionId);
$btnSelectFiles.text('UPLOADING...').data('uploading', true)
$uploadSpinner.show();
return rest.uploadMusicNotations(formData)
@ -362,7 +368,6 @@
}
function changeSelectedFiles() {
var $inputFiles = $screen.find('#session-prop-select-files');
var fileNames = [];
var files = $inputFiles.get(0).files;
var error = false;
@ -400,7 +405,7 @@
}
event.preventDefault();
$('#session-select-files').trigger('click');
$inputFiles.trigger('click');
return false;
}
@ -457,9 +462,10 @@
$fansAccess.val(sessionData.fans_access_value);
$screen.find('#session-policy').val(sessionData.legal_policy);
$selectedFilenames.empty();
context._.each(sessionData.music_notations, function(notation) {
var $link = $('<a href="/music_notations/' + notation.id + '">').text(notation.file_name);
var $text = $('<span>').text($link);
var $link = notation.viewable ? $('<a href="' + notation.file_url + '" rel="external">').html(notation.file_name) : '#';
var $text = $('<span>').html($link);
var $file = $('<li>').append($text);
$musicNotations.append($file);
})
@ -473,7 +479,6 @@
app.bindScreen('account/sessionProperties', screenBindings);
$screen = $(".account-session-properties");
$propertiesBody = $screen.find("#account-session-properties-div");
$recurringModeList = $screen.find("#recurring-mode-prop-list");
$languageList = $screen.find("#session-prop-lang-list");
@ -485,7 +490,7 @@
$musicianAccess = $screen.find("#session-prop-musician-access");
$fansAccess = $screen.find("#session-prop-fans-access");
$musicNotations = $screen.find("#selected-filenames");
$btnSelectFiles = $screen.find("#select-notation-files");
$btnSelectFiles = $screen.find(".btn-select-files");
$uploadSpinner = $screen.find(".upload-spinner");
$dateTimeTBD = $screen.find("#date_time_tbd");

View File

@ -35,7 +35,7 @@
}
function showDialog() {
return app.layout.showDialog('rsvp-cancel-dialog');
return app.layout.showDialog(dialogId);
}
function events() {
@ -99,7 +99,7 @@
$dialog = $('[layout-id="' + dialogId + '"]');
$("#rsvp-cancel-dialog").iCheck({
$dialog.iCheck({
checkboxClass: 'icheckbox_minimal',
radioClass: 'iradio_minimal',
inheritClass: true

View File

@ -0,0 +1,64 @@
(function(context,$) {
"use strict";
context.JK = context.JK || {};
context.JK.RsvpCreateSlotDialog = function(app, sessionId, instrumentIds, rsvpRequesterName, createSlotsCallback) {
var EVENTS = context.JK.EVENTS;
var logger = context.JK.logger;
var rest = context.JK.Rest();
var $dialog = null;
var dialogId = 'rsvp-create-slot-dialog';
var $btnSave = null;
function beforeShow(data) {
}
function afterShow(data) {
var instructions = "All RSVP slots for the instruments below have already been filled. Would you like to open up a new RSVP slot for " + rsvpRequesterName + " so that they can join your session too?";
var instruments = instrumentIds.join("<br/>");
$('.instructions', $dialog).html(instructions);
$('.instruments', $dialog).html(instruments);
}
function afterHide() {
}
function showDialog() {
return app.layout.showDialog(dialogId);
}
function events() {
$btnSave.unbind('click');
$btnSave.click(function(e) {
e.preventDefault();
if (createSlotsCallback) {
createSlotsCallback();
app.layout.closeDialog(dialogId);
}
});
}
function initialize() {
var dialogBindings = {
'beforeShow' : beforeShow,
'afterShow' : afterShow,
'afterHide': afterHide
};
app.bindDialog(dialogId, dialogBindings);
$dialog = $('[layout-id="' + dialogId + '"]');
$btnSave = $dialog.find('.btnSave');
events();
}
this.initialize = initialize;
this.showDialog = showDialog;
}
return this;
})(window,jQuery);

View File

@ -30,17 +30,14 @@
var hasOpenSlots = response.open_slots && response.open_slots.length > 0;
if (response['is_unstructured_rsvp?']) {
var checkedState = hasOpenSlots ? '' : 'checked="checked"'
$('.rsvp-instruments', $dialog).append('<input type="checkbox" ' + checkedState + ' value="unstructured"/>Play Any Instrument You Like<br/>');
$('.rsvp-instruments', $dialog).append('<input type="checkbox" ' + checkedState + ' value="unstructured"/><span>Play Any Instrument You Like</span><br/>');
}
if (hasOpenSlots) {
$.each(response.open_slots, function(index, val) {
var instrument = val.instrument_id;
$('.rsvp-instruments', $dialog).append('<input type="checkbox" value="' + val.id + '"/>' + val.description + " (" + val.proficiency_desc + ")<br/>");
$('.rsvp-instruments', $dialog).append('<input type="checkbox" data-instrument-id="' + val.instrument_id + '" value="' + val.id + '"/><span>' + val.description + " (" + val.proficiency_desc + ")</span><br/>");
});
}
}
@ -60,18 +57,28 @@
$btnSubmit.click(function(e) {
e.preventDefault();
var error = false;
var slotIds = [];
var selectedSlots = [];
$("input:checked", '.rsvp-instruments').each(function(index) {
var selection = $(this).attr('data-instrument-id');
if ($.inArray(selection, selectedSlots) > -1) {
$('.error', $dialog).html('You have selected the same instrument twice.').show();
error = true;
return;
}
selectedSlots.push(selection);
slotIds.push($(this).val());
});
if (error) return;
if (slotIds.length === 0) {
$('.error', $dialog).show();
$('.error', $dialog).html('You must select at least 1 instrument.').show();
return;
}
var error = false;
// TODO: show spinner??
rest.submitRsvpRequest(sessionId, slotIds)
.done(function(response) {
@ -83,8 +90,7 @@
})
.fail(function(xhr, textStatus, errorMessage) {
error = true;
$('.error', $dialog).html("Unexpected error occurred while saving message (" + xhr.status + ")");
$('.error', $dialog).show();
$('.error', $dialog).html("Unexpected error occurred while saving message (" + xhr.status + ")").show();
});
}

View File

@ -8,7 +8,7 @@
var $selectedFilenames = $screen.find('#selected-filenames');
var $uploadSpinner = $screen.find('.upload-spinner');
var $selectedFilenames = $('#settings-selected-filenames');
var $inputFiles = $('#settings-select-files');
var $inputFiles = $screen.find('#session-select-files');
var $btnSelectFiles = $screen.find('.btn-select-files');
var rest = new JK.Rest();
var sessionId;
@ -116,37 +116,7 @@
rest.updateSession($('#session-settings-id').val(), data).done(settingsSaved);
}
function changeSettingsSelectedFiles() {
var fileNames = [];
var files = $inputFiles.get(0).files;
var error = false;
for (var i = 0; i < files.length; ++i) {
var name = files.item(i).name;
var ext = name.split('.').pop().toLowerCase();
if ($.inArray(ext, ["pdf", "png", "jpg", "jpeg", "gif", "xml", "mxl", "txt"]) == -1) {
error = true;
break;
}
fileNames.push(name);
}
if (error) {
app.notifyAlert("Error", "We're sorry, but you can only upload images (.png .jpg .jpeg .gif), text (.txt), PDFs (.pdf), and XML files (.xml .mxl).");
$inputFiles.replaceWith($inputFiles.clone(true));
}
else {
// upload as soon as user picks their files.
uploadSettingsNotations($inputFiles.get(0).files)
.done(function() {
context._.each(fileNames, function(fileName) {
var $text = $('<span>').text(fileName);
$selectedFilenames.append($text);
})
})
}
}
function uploadSettingsNotations(notations) {
function uploadNotations(notations) {
var formData = new FormData();
var maxExceeded = false;
@ -205,14 +175,44 @@
});
}
function toggleSettingsSelectFiles(event) {
function changeSelectedFiles() {
var fileNames = [];
var files = $inputFiles.get(0).files;
var error = false;
for (var i = 0; i < files.length; ++i) {
var name = files.item(i).name;
var ext = name.split('.').pop().toLowerCase();
if ($.inArray(ext, ["pdf", "png", "jpg", "jpeg", "gif", "xml", "mxl", "txt"]) == -1) {
error = true;
break;
}
fileNames.push(name);
}
if (error) {
app.notifyAlert("Error", "We're sorry, but you can only upload images (.png .jpg .jpeg .gif), text (.txt), PDFs (.pdf), and XML files (.xml .mxl).");
$inputFiles.replaceWith($inputFiles.clone(true));
}
else {
// upload as soon as user picks their files.
uploadNotations($inputFiles.get(0).files)
.done(function() {
context._.each(fileNames, function(fileName) {
var $text = $('<span>').text(fileName);
$selectedFilenames.append($text);
})
})
}
}
function toggleSelectFiles(event) {
if($btnSelectFiles.data('uploading')) {
logger.debug("ignoring click of SELECT FILES... while uploading")
return false;
}
event.preventDefault();
$('#settings-select-files').trigger('click');
$inputFiles.trigger('click');
return false;
}
@ -225,8 +225,8 @@
function events() {
$('#session-settings-dialog-submit').on('click', saveSettings);
$inputFiles.on('change', changeSettingsSelectedFiles);
$btnSelectFiles.on('click', toggleSettingsSelectFiles);
$inputFiles.on('change', changeSelectedFiles);
$btnSelectFiles.on('click', toggleSelectFiles);
}
this.initialize = function() {

View File

@ -88,8 +88,11 @@
var fullScore = null;
if (response.internet_score) {
fullScore = response.last_jam_audio_latency + calculateAudioLatency(response.my_audio_latency) + calculateAudioLatency(response.internet_score);
if (response.internet_score && response.internet_score.length > 0) {
if (response.internet_score[0].score && !isNaN(response.internet_score[0].score)) {
var internetScore = parseInt(response.internet_score[0].score);
fullScore = (response.internet_score + calculateAudioLatency(response.my_audio_latency) + calculateAudioLatency(response.last_jam_audio_latency)) / 2;
}
}
// latency badge template needs these 2 properties

View File

@ -25,6 +25,7 @@
var notificationBatchSize = 20;
var currentNotificationPage = 0;
var didLoadAllNotifications = false, isLoading = false;
var ui = new context.JK.UIHelper(JK.app);
function isNotificationsPanelVisible() {
return $contents.is(':visible')
@ -422,7 +423,7 @@
var $action_btn = $notification.find($btnNotificationAction);
$action_btn.text('SESSION DETAILS');
$action_btn.click(function() {
context.JK.popExternalLink('/sessions/' + payload.session_id + '/details');
openSessionInfoWebPage({"session_id": payload.session_id});
});
}
@ -843,10 +844,12 @@
}, [{
id: "btn-more-info",
text: "MORE INFO",
"layout-action": "close",
href: JK.root_url + "/sessions/" + payload.session_id + "/details",
rel: "external",
"class": "button-orange"
"class": "button-orange",
callback: openSessionInfoWebPage,
callback_args: {
"session_id": payload.session_id
}
}]
);
});
@ -865,10 +868,12 @@
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
}, [{
id: "btn-manage-rsvp",
text: "Manage RSVP",
"layout-action": "close",
href: "/client#/account/sessionDetail/" + payload.session_id,
"class": "button-orange"
text: "MANAGE RSVP",
"class": "button-orange",
callback: navigateToSessionDetails,
callback_args: {
"session_id": payload.session_id
}
}]
);
});
@ -886,11 +891,14 @@
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
}, [{
id: "btn-session-details",
text: "Session Details",
text: "SESSION DETAILS",
"layout-action": "close",
href: JK.root_url + "/sessions/" + payload.session_id + "/details",
rel: "external",
"class": "button-orange"
"class": "button-orange",
callback: openSessionInfoWebPage,
callback_args: {
"session_id": payload.session_id
}
}]
);
});
@ -908,11 +916,13 @@
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
}, [{
id: "btn-session-details",
text: "Session Details",
"layout-action": "close",
href: JK.root_url + "/sessions/" + payload.session_id + "/details",
text: "SESSION DETAILS",
rel: "external",
"class": "button-orange"
"class": "button-orange",
callback: openSessionInfoWebPage,
callback_args: {
"session_id": payload.session_id
}
}]
);
});
@ -930,11 +940,13 @@
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
}, [{
id: "btn-session-details",
text: "Session Details",
"layout-action": "close",
href: JK.root_url + "/sessions/" + payload.session_id + "/details",
text: "SESSION DETAILS",
rel: "external",
"class": "button-orange"
"class": "button-orange",
callback: openSessionInfoWebPage,
callback_args: {
"session_id": payload.session_id
}
}]
);
});
@ -952,11 +964,13 @@
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
}, [{
id: "btn-session-details",
text: "Session Details",
"layout-action": "close",
href: JK.root_url + "/sessions/" + payload.session_id + "/details",
text: "SESSION DETAILS",
rel: "external",
"class": "button-orange"
"class": "button-orange",
callback: openSessionInfoWebPage,
callback_args: {
"session_id": payload.session_id
}
}]
);
});
@ -974,11 +988,13 @@
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
}, [{
id: "btn-session-details",
text: "Session Details",
"layout-action": "close",
href: JK.root_url + "/sessions/" + payload.session_id + "/details",
text: "SESSION DETAILS",
rel: "external",
"class": "button-orange"
"class": "button-orange",
callback: openSessionInfoWebPage,
callback_args: {
"session_id": payload.session_id
}
}]
);
});
@ -996,11 +1012,13 @@
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
}, [{
id: "btn-session-details",
text: "Session Details",
"layout-action": "close",
href: JK.root_url + "/sessions/" + payload.session_id + "/details",
text: "SESSION DETAILS",
rel: "external",
"class": "button-orange"
"class": "button-orange",
callback: openSessionInfoWebPage,
callback_args: {
"session_id": payload.session_id
}
}]
);
});
@ -1019,11 +1037,13 @@
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
}, [{
id: "btn-session-details",
text: "Session Details",
"layout-action": "close",
href: JK.root_url + "/sessions/" + payload.session_id + "/details",
text: "SESSION DETAILS",
rel: "external",
"class": "button-orange"
"class": "button-orange",
callback: openSessionInfoWebPage,
callback_args: {
"session_id": payload.session_id
}
}]
);
});
@ -1101,7 +1121,7 @@
"class": "button-orange",
callback: shareRecording,
callback_args: {
"recording_id": payload.recording_id
"claimed_recording_id": payload.claimed_recording_id
}
}]
);
@ -1109,7 +1129,9 @@
}
function shareRecording(args) {
var recordingId = args.recording_id;
var claimedRecordingId = args.claimed_recording_id;
ui.launchShareDialog(claimedRecordingId, 'recording');
}
function registerBandInvitation() {
@ -1231,6 +1253,14 @@
context.JK.popExternalLink('/recordings/' + args.recording_id);
}
function navigateToSessionDetails(args) {
context.location = '/client#/account/sessionDetail/' + args.session_id;
}
function openSessionInfoWebPage(args) {
context.JK.popExternalLink('/sessions/' + args.session_id + '/details');
}
function deleteNotificationHandler(evt) {
evt.stopPropagation();
var notificationId = $(this).attr('notification-id');

View File

@ -84,17 +84,25 @@ class RecordingUtils
mixStateClass = 'discarded'
mixState = 'discarded'
else
mixStateMsg = 'STILL UPLOADING'
mixStateClass = 'still-uploading'
mixState = 'still-uploading'
return {
if mix.fake and mix.discarded
mixStateMsg = 'DISCARDED'
mixStateClass = 'discarded'
mixState = 'discarded'
else
mixStateMsg = 'STILL UPLOADING'
mixStateClass = 'still-uploading'
mixState = 'still-uploading'
result = {
mixStateMsg: mixStateMsg,
mixStateClass: mixStateClass,
mixState: mixState,
isError: mixState == 'error'
}
result
onMixHover: () ->
$mix = $(this).closest('.mix')
mixStateInfo = $mix.data('mix-state')
@ -114,7 +122,10 @@ class RecordingUtils
serverInfo = $mix.data('server-info')
# lie if this is a virtualized mix (i.e., mix is created after recording is made)
mixState = 'still-uploading' if !serverInfo? or serverInfo.fake
if !serverInfo?
mixState = 'still-uploading'
else if serverInfo.fake
mixState = if serverInfo.discarded then 'discarded' else 'still-uploading'
summary = ''
if mixState == 'still-uploading'

View File

@ -28,7 +28,7 @@
var friendInput = null;
// Main layout
var $screen = null;
var $screen = $('#create-session-layout');
var $wizardSteps = null;
var $currentWizardStep = null;
var step = 0;
@ -42,6 +42,7 @@
var $languageList = null;
var $sessionPlusMusiciansLabel = null;
var $editScheduledSessions = null;
var $inputFiles = $screen.find('#session-select-files');
var $btnSelectFiles = null;
var $selectedFilenames = null;
var $uploadSpinner = null;
@ -1032,7 +1033,6 @@
}
function changeSelectedFiles() {
var $inputFiles = $('#session-step-2 #session-select-files');
var fileNames = [];
var files = $inputFiles.get(0).files;
var error = false;
@ -1070,7 +1070,7 @@
}
event.preventDefault();
$('#session-select-files').trigger('click');
$inputFiles.trigger('click');
return false;
}
@ -1186,8 +1186,6 @@
function events() {
$createTypes.on("ifChanged", toggleCreateType);
$startTimeList.on('change', function() { toggleStartTime(); });
$btnSelectFiles.on('click', toggleSelectFiles);
$('#session-step-2 #session-select-files').on('change', changeSelectedFiles);
$policyTypes.on("ifChanged", togglePolicyTypeChanged);
$('#session-step-4 #session-musician-access').on('change', toggleMusicianAccessTypes);
$('#session-step-4 #session-fans-access').on('change', toggleFanAccessTypes);
@ -1205,6 +1203,9 @@
});
$(friendInput).focus(function() { $(this).val(''); })
$inputFiles.on('change', changeSelectedFiles);
$btnSelectFiles.on('click', toggleSelectFiles);
}
function initialize(invitationDialogInstance, friendSelectorDialog, instrumentSelectorInstance, instrumentRSVPSelectorInstance) {
@ -1221,7 +1222,6 @@
var screenBindings = {'beforeShow': beforeShow, 'afterShow': afterShow};
app.bindScreen('createSession', screenBindings);
$screen = $('#create-session-layout');
$wizardSteps = $screen.find('.create-session-wizard');
$templateSteps = $('#template-session-steps');
$templateButtons = $('#template-session-buttons');

View File

@ -371,6 +371,14 @@
if(response["errors"] && response["errors"]["tracks"] && response["errors"]["tracks"][0] == "Please select at least one track") {
app.notifyAlert("No Inputs Configured", $('<span>You will need to reconfigure your audio device.</span>'));
}
else if(response["errors"] && response["errors"]["music_session"] && response["errors"]["music_session"][0] == ["is currently recording"]) {
promptLeave = false;
context.window.location = "/client#/findSession";
app.notify( { title: "Unable to Join Session", text: "The session is currently recording." }, null, true);
}
else {
app.notifyServerError(xhr, 'Unable to Join Session');
}
}
else {
app.notifyServerError(xhr, 'Unable to Join Session');
@ -396,7 +404,12 @@
if(screenActive) {
// this path is possible if FTUE is invoked on session page, and they cancel
sessionModel.leaveCurrentSession()
.fail(app.ajaxError);
.fail(function(jqXHR) {
if(jqXHR.status != 404) {
logger.debug("leave session failed");
app.ajaxError(arguments)
}
});
}
screenActive = false;

View File

@ -184,10 +184,10 @@
leaveSessionRest(currentSessionId)
.done(function() {
sessionChanged();
deferred.resolve(arguments);
deferred.resolve(arguments[0], arguments[1], arguments[2]);
})
.fail(function() {
deferred.reject(arguments);
deferred.reject(arguments[0], arguments[1], arguments[2]);
});
// 'unregister' for callbacks

View File

@ -133,6 +133,11 @@
rest.getSession(sessionId)
.done(function(response) {
session = response;
if(session && session.recording) {
context.JK.app.notify( { title: "Unable to Join Session", text: "The session is currently recording." }, null, true);
return;
}
if ("invitations" in session) {
var invitation;
// user has invitations for this session

View File

@ -220,6 +220,7 @@ context.JK.SyncViewer = class SyncViewer
updateTrackState: ($track) =>
clientInfo = $track.data('client-info')
serverInfo = $track.data('server-info')
myTrack = serverInfo.user.id == context.JK.currentUserId
# determine client state
clientStateMsg = 'UNKNOWN'
@ -246,7 +247,7 @@ context.JK.SyncViewer = class SyncViewer
clientStateClass = 'missing'
clientState = @clientStates.missing
else
clientStateClass = 'DISCARDED'
clientStateMsg = 'DISCARDED'
clientStateClass = 'discarded'
clientState = @clientStates.discarded
@ -261,7 +262,7 @@ context.JK.SyncViewer = class SyncViewer
uploadStateClass = 'error'
uploadState = @uploadStates.too_many_upload_failures
else
if serverInfo.user.id == context.JK.currentUserId
if myTrack
if clientInfo?
if clientInfo.local_state == 'HQ'
uploadStateMsg = 'PENDING UPLOAD'
@ -282,7 +283,7 @@ context.JK.SyncViewer = class SyncViewer
else
uploadStateMsg = 'UPLOADED'
uploadStateClass = 'uploaded'
if serverInfo.user.id == context.JK.currentUserId
if myTrack
uploadState = @uploadStates.me_uploaded
else
uploadState = @uploadStates.them_uploaded
@ -301,6 +302,30 @@ context.JK.SyncViewer = class SyncViewer
$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}']")
@ -485,7 +510,14 @@ context.JK.SyncViewer = class SyncViewer
recordingInfo = null
if userSync == 'fake'
recordingInfo = arguments[1]
userSync = { recording_id: recordingInfo.id, duration: recordingInfo.duration, fake:true }
# 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'}))
@ -504,14 +536,16 @@ context.JK.SyncViewer = class SyncViewer
$track.data('sync-viewer', this)
$clientState = $track.find('.client-state')
$uploadState = $track.find('.upload-state')
$clientState.find('.retry').click(this.retryDownloadRecordedTrack)
$uploadState.find('.retry').click(this.retryUploadRecordedTrack)
$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 context.jamClient.IsNativeClient()
$uploadState.addClass('is-native-client') if context.jamClient.IsNativeClient()
$clientState.addClass('is-native-client') if gon.isNativeClient
$uploadState.addClass('is-native-client') if gon.isNativeClient
$track
createStreamMix: (userSync) =>
@ -523,8 +557,8 @@ context.JK.SyncViewer = class SyncViewer
$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 context.jamClient.IsNativeClient()
$uploadState.addClass('is-native-client') if context.jamClient.IsNativeClient()
$clientState.addClass('is-native-client') if gon.isNativeClient
$uploadState.addClass('is-native-client') if gon.isNativeClient
$track
exportRecording: (e) =>

View File

@ -47,12 +47,19 @@
return rsvpDialog.showDialog();
}
function launchRsvpCreateSlotDialog(sessionId, instrumentIds, rsvpRequesterName, createSlotsCallback) {
var rsvpDialog = new JK.RsvpCreateSlotDialog(JK.app, sessionId, instrumentIds, rsvpRequesterName, createSlotsCallback);
rsvpDialog.initialize();
return rsvpDialog.showDialog();
}
this.addSessionLike = addSessionLike;
this.addRecordingLike = addRecordingLike;
this.launchCommentDialog = launchCommentDialog;
this.launchShareDialog = launchShareDialog;
this.launchRsvpSubmitDialog = launchRsvpSubmitDialog;
this.launchRsvpCancelDialog = launchRsvpCancelDialog;
this.launchRsvpCreateSlotDialog = launchRsvpCreateSlotDialog;
return this;
};

View File

@ -88,7 +88,7 @@
function togglePlay() {
if(playing) {
$status.text('SESSION IN PROGRESS');
$status.text('LIVE SESSION IN PROGRESS');
stopPlay();
}
else {

View File

@ -452,6 +452,25 @@
.action-bar {
margin-top: 20px;
}
#selected-filenames {
margin-top:10px;
font-size:12px;
margin-left: 5px;
li {
margin-bottom:1px;
white-space:nowrap;
line-height:14px;
}
li span {
white-space:nowrap;
text-overflow:ellipsis;
overflow:hidden;
display:block;
}
}
}
/** account sessions */

View File

@ -180,6 +180,7 @@ $fair: #cc9900;
@mixin client-state-box {
.client-state {
position:relative;
text-align:center;
@ -193,7 +194,6 @@ $fair: #cc9900;
&.error {
background-color: $error;
&.is-native-client { .retry { display:inline-block; } }
}
&.hq {
@ -202,12 +202,10 @@ $fair: #cc9900;
&.sq {
background-color: $good;
&.is-native-client { .retry { display:inline-block; } }
}
&.missing {
background-color: $error;
&.is-native-client { .retry { display:inline-block; } }
}
&.discarded {
@ -240,7 +238,6 @@ $fair: #cc9900;
&.error {
background-color: $error;
&.is-native-client { .retry { display:inline-block; } }
}
&.missing {
@ -249,7 +246,6 @@ $fair: #cc9900;
&.upload-soon {
background-color: $fair;
&.is-native-client { .retry { display:inline-block; } }
}
&.uploaded {
@ -278,7 +274,7 @@ $fair: #cc9900;
color:white;
&.still-uploading { background-color: $fair; }
&.discard {background-color: $unknown; }
&.discarded {background-color: $unknown; }
&.unknown { background-color: $unknown; }
&.error { background-color: $error; }
&.mixed { background-color: $good; }

View File

@ -16,6 +16,10 @@ class ApiController < ApplicationController
@exception = exception
render "errors/permission_error", :status => 403
end
rescue_from 'JamRuby::ConflictError' do |exception|
@exception = exception
render "errors/conflict_error", :status => 409
end
rescue_from 'ActiveRecord::RecordNotFound' do |exception|
@@log.debug(exception)
render :json => { :errors => { :resource => ["record not found"] } }, :status => 404

View File

@ -212,13 +212,16 @@ class ApiMusicSessionsController < ApiController
@music_session.band = (params[:band] ? Band.find(params[:band]) : nil) if params.include? :band
@music_session.save
params[:music_notations].each do |notation_id|
notation = MusicNotation.find(notation_id)
notation.music_session = ms
notation.save
if params.include? :music_notations
notations = JSON.parse(params[:music_notations])
notations.each do |n|
notation = MusicNotation.find(n["id"])
notation.music_session = @music_session
notation.save
ms.music_notations << notation
end if params.include? :music_notations
@music_session.music_notations << notation
end
end
if @music_session.errors.any?
response.status = :unprocessable_entity

View File

@ -75,11 +75,11 @@ module FeedsHelper
end
def recording_artist_name(recording)
(recording.band.nil? ? nil : recording.band.name) || recording.candidate_claimed_recording.user.name
(recording.band.nil? ? nil : recording.band.name) || recording.owner.name
end
def recording_artist_id(recording)
(recording.band.nil? ? nil : recording.band.id) || recording.candidate_claimed_recording.user.id
(recording.band.nil? ? nil : recording.band.id) || recording.owner.id
end
def recording_artist_hoveraction(recording)

View File

@ -95,7 +95,7 @@
{{data.latency}}
%td.rsvp-buttons
%a{href: "/client#/profile/{{data.user_id}}", class: 'button-orange left', 'user-id' => "{{data.user_id}}"} PROFILE
%a{href: "#", class: 'button-orange left approveRsvpRequest', 'user-id' => "{{data.user_id}}", 'request-id' => "{{data.request_id}}"} APPROVE
%a{href: "#", class: 'button-orange left approveRsvpRequest', 'data-instrument-text' => "{{data.instrument_text}}", 'user-id' => "{{data.user_id}}", 'request-id' => "{{data.request_id}}", 'user-name' => "{{data.user_name}}"} APPROVE
%a{href: "#", class: 'button-orange left declineRsvpRequest', 'user-id' => "{{data.user_id}}", 'request-id' => "{{data.request_id}}"} DECLINE
.clearall
.clearall

View File

@ -98,12 +98,12 @@
Notation Files:
.right-column
.spinner-small.upload-spinner
.selected-files-section
%ul#selected-filenames
.select-files-section
%a{href: "#", class: "button-orange btn-select-files", id: "select-notation-files"} SELECT FILES...
%input{type: "file", class: "hidden", id: "session-prop-select-files", value: "Select Files...",
%a.button-orange.btn-select-files SELECT FILES...
%input{type: "file", class: "hidden", id: "session-select-files", value: "Select Files...",
accept: ".pdf, .png, .jpg, .jpeg, .gif, .xml, .mxl, .txt", multiple: "true"}
.selected-files-section
%ul#selected-filenames
.clearall
.clearall
.clearall

View File

@ -59,7 +59,7 @@
%div{:id => "settings-selected-filenames"}
.right.ib.mb10
%a.button-orange.btn-select-files SELECT FILES...
%input.hidden{:type => "file", :id => "settings-select-files", :value => "Select Files...", :accept => ".pdf, .png, .jpg, .jpeg, .gif, .xml, .mxl, .txt"}
%input.hidden{:type => "file", :id => "session-select-files", :value => "Select Files...", :accept => ".pdf, .png, .jpg, .jpeg, .gif, .xml, .mxl, .txt"}
.spinner-small.upload-spinner
.clearall.right.mt10

View File

@ -17,6 +17,7 @@
= render 'dialogs/commentDialog'
= render 'dialogs/rsvpSubmitDialog'
= render 'dialogs/rsvpCancelDialog'
= render 'dialogs/rsvpCreateSlotDialog'
= render 'dialogs/sessionCancelDialog'
= render 'dialogs/signinDialog'
= render 'dialogs/signupDialog'

View File

@ -0,0 +1,13 @@
.dialog.dialog-overlay-sm layout='dialog' layout-id='rsvp-create-slot-dialog' id='rsvp-create-slot-dialog'
.content-head
= image_tag "content/icon_alert.png", {:width => 24, :height => 24, :class => 'content-icon' }
h1 RSVP Slot Already Filled
.dialog-inner
div.instructions
br
div.instruments
.buttons
.right
a.button-grey class='btnCancel' layout-action='cancel' NO
a.button-orange class='btnSave' YES

View File

@ -9,7 +9,7 @@
.schedule-recurrence
.part
.slot-instructions Check the box(es) next to the track(s) you want to play in the session:
.error{:style => 'display:none'} You must select at least 1 instrument.
.error{:style => 'display:none'}
.rsvp-instruments
.comment-instructions Enter a message to the other musicians in the session (optional):

View File

@ -0,0 +1,7 @@
object @exception
attributes :message
node "type" do
"ConflictError"
end

View File

@ -18,7 +18,7 @@
<% end %>
<div class="recordings-page">
<% if @claimed_recording.is_public %>
<% if @claimed_recording.is_public || @claimed_recording.recording.has_access?(current_user) %>
<div class="landing-band">
<% unless @claimed_recording.recording.band.blank? %>
<div class="landing-avatar">
@ -97,7 +97,7 @@
<% end %>
</div>
<% if @claimed_recording.is_public %>
<% if @claimed_recording.is_public || @claimed_recording.recording.has_access?(current_user) %>
<% if signed_in? %>
<% unless @claimed_recording.recording.band.nil? %>
<%= render :partial => "shared/landing_sidebar", :locals => {:user => @claimed_recording.recording.band, :recent_history => @claimed_recording.recording.band.recent_history} %>

View File

@ -178,6 +178,8 @@ if defined?(Bundler)
config.audiomixer_path = "/var/lib/audiomixer/audiomixer/audiomixerapp"
config.ffmpeg_path = ENV['FFMPEG_PATH'] || (File.exist?('/usr/local/bin/ffmpeg') ? '/usr/local/bin/ffmpeg' : '/usr/bin/ffmpeg')
config.normalize_ogg_path = ENV['NORMALIZE_OGG_PATH'] || (File.exist?('/usr/local/bin/normalize-ogg') ? '/usr/local/bin/normalize-ogg' : '/usr/bin/normalize-ogg')
config.normalize_mp3_path = ENV['NORMALIZE_MP3_PATH'] || (File.exist?('/usr/local/bin/normalize-mp3') ? '/usr/local/bin/normalize-mp3' : '/usr/bin/normalize-mp3')
# if it looks like linux, use init.d script; otherwise use kill
config.icecast_reload_cmd = ENV['ICECAST_RELOAD_CMD'] || (File.exist?('/usr/local/bin/icecast2') ? "bash -l -c #{Shellwords.escape("sudo /etc/init.d/icecast2 reload")}" : "bash -l -c #{Shellwords.escape("kill -1 `ps -f | grep /usr/local/bin/icecast | grep -v grep | awk \'{print $2}\'`")}")

View File

@ -11,6 +11,26 @@ describe "Music Session", :js => true, :type => :feature, :capybara_feature => t
subject { page }
describe "recorded session" do
before(:each) do
ActiveMusicSession.delete_all
MusicSession.delete_all
end
let(:searcher) { FactoryGirl.create(:user) }
let(:creator) { FactoryGirl.create(:user) }
let(:conn) { FactoryGirl.create(:connection, :user => creator) }
let(:description) {'hot recordings in here'}
let(:session) {FactoryGirl.create(:active_music_session, creator:creator, description: description)}
let(:recording) {FactoryGirl.create(:recording, music_session: session, owner: creator)}
it "won't let user join" do
recording.touch
join_session(searcher, description:'hot recordings in here', no_verify:true)
find('#notification p').text('The session is currently recording.')
end
end
context "last person" do
before(:each) do

View File

@ -1,6 +1,6 @@
require 'spec_helper'
describe "Landing", :js => true, :type => :feature, :capybara_feature => true do
describe "Landing" do
let (:user) { FactoryGirl.create(:user) }
@ -10,41 +10,70 @@ describe "Landing", :js => true, :type => :feature, :capybara_feature => true do
Recording.delete_all
end
before(:each) do
MusicSession.delete_all
sign_in_poltergeist(user)
end
let (:claimed_recording) { FactoryGirl.create(:claimed_recording) }
let (:recording) { claimed_recording.recording }
it "should render comments" do
describe "no js required" do
recording = ClaimedRecording.first
comment = "test comment"
timestamp = "less than a minute ago"
url = "/recordings/#{claimed_recording.id}"
visit url
it "shows private recording to someone who was in the session" do
# make the session hidden
claimed_recording.is_public = false
claimed_recording.save!
fill_in "txtRecordingComment", with: comment
find('#btnPostComment').trigger(:click)
visit "/recordings/#{claimed_recording.id}"
# (1) Test a user creating a comment and ensure it displays.
# and verify that after we visit the page, we can see the name of it
find('strong', text: 'RECORDING NOT FOUND')
# comment body
find('div.comment-text', text: comment)
# log in the user who was a part of the session
sign_in(claimed_recording.user)
# timestamp
find('div.comment-timestamp', text: timestamp)
visit "/recordings/#{claimed_recording.id}"
# (2) Test a user visiting a landing page with an existing comment.
# and verify that after we visit the page, we can see the name of it
find('h2', text: claimed_recording.name)
end
end
# re-visit page to reload from database
visit url
# comment body
find('div.comment-text', text: comment)
describe "js required", :js => true, :type => :feature, :capybara_feature => true do
# timestamp
find('div.comment-timestamp', text: timestamp)
before(:each) do
MusicSession.delete_all
sign_in_poltergeist(user)
end
it "should render comments" do
recording = ClaimedRecording.first
comment = "test comment"
timestamp = "less than a minute ago"
url = "/recordings/#{claimed_recording.id}"
visit url
fill_in "txtRecordingComment", with: comment
find('#btnPostComment').trigger(:click)
# (1) Test a user creating a comment and ensure it displays.
# comment body
find('div.comment-text', text: comment)
# timestamp
find('div.comment-timestamp', text: timestamp)
# (2) Test a user visiting a landing page with an existing comment.
# re-visit page to reload from database
visit url
# comment body
find('div.comment-text', text: comment)
# timestamp
find('div.comment-timestamp', text: timestamp)
end
end
end

View File

@ -417,9 +417,11 @@ def join_session(joiner, options)
# verify the session description is seen by second client
expect(page).to have_text(description)
find('.join-link').trigger(:click)
find('#btn-accept-terms').trigger(:click)
expect(page).to have_selector('h2', text: 'my tracks')
find('#session-screen .session-mytracks .session-track')
unless options[:no_verify]
find('#btn-accept-terms').trigger(:click)
expect(page).to have_selector('h2', text: 'my tracks')
find('#session-screen .session-mytracks .session-track')
end
end
end

View File

@ -80,7 +80,7 @@ module JamWebsockets
@router.periodical_check_connections
EventMachine::PeriodicTimer.new(2) do
sane_logging { @router.periodical_check_connections }
safety_net { sane_logging { @router.periodical_check_connections } }
end
end
@ -89,7 +89,7 @@ module JamWebsockets
@router.periodical_check_clients
EventMachine::PeriodicTimer.new(30) do
sane_logging { @router.periodical_check_clients }
safety_net { sane_logging { @router.periodical_check_clients } }
end
end
@ -98,16 +98,29 @@ module JamWebsockets
@router.periodical_flag_connections
EventMachine::PeriodicTimer.new(2) do
sane_logging { @router.periodical_flag_connections }
safety_net { sane_logging { @router.periodical_flag_connections } }
end
end
def start_stats_dump
EventMachine::PeriodicTimer.new(60) do
@router.periodical_stats_dump
safety_net { @router.periodical_stats_dump }
end
end
# this was added for this reason: https://jamkazam.atlassian.net/browse/VRFS-2425
# if an unhandled exception occurs in PeriodicTimer, it just kills all future timers; doesn't kill the app.
# not really what you want.
# so, we signal to Bugsnag, so we know really bad stuff is happening, but we also move
def safety_net(&blk)
begin
blk.call
rescue => e
Bugsnag.notify(e)
@log.error("unhandled exception in EM Timer #{e}")
end
end
def sane_logging(&blk)
# used around repeated transactions that cause too much ActiveRecord::Base logging
# example is handling heartbeats