diff --git a/admin/config/application.rb b/admin/config/application.rb index 573e946f0..08b11d58c 100644 --- a/admin/config/application.rb +++ b/admin/config/application.rb @@ -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 diff --git a/pb/src/client_container.proto b/pb/src/client_container.proto index b9a7aeba0..5515757d9 100644 --- a/pb/src/client_container.proto +++ b/pb/src/client_container.proto @@ -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 { diff --git a/ruby/lib/jam_ruby.rb b/ruby/lib/jam_ruby.rb index 98b93a4c1..649dc40e6 100755 --- a/ruby/lib/jam_ruby.rb +++ b/ruby/lib/jam_ruby.rb @@ -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" diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/corp_mailer/feedback.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/corp_mailer/feedback.html.erb index 611a9891b..7daab2af6 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/corp_mailer/feedback.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/corp_mailer/feedback.html.erb @@ -8,7 +8,7 @@

-This email was received because someone left feedback at http://www.jamkazam.com/corp/contact +This email was received because someone left feedback at http://www.jamkazam.com/corp/contact

diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/invited_user_mailer/friend_invitation.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/invited_user_mailer/friend_invitation.html.erb index 7667ae9fd..11c4bffd7 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/invited_user_mailer/friend_invitation.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/invited_user_mailer/friend_invitation.html.erb @@ -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 create account page. +To signup, please go to the create account page. <% content_for :note do %> <% if @note %> diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/invited_user_mailer/welcome_betauser.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/invited_user_mailer/welcome_betauser.html.erb index 8cc412c4f..c1aafeaee 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/invited_user_mailer/welcome_betauser.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/invited_user_mailer/welcome_betauser.html.erb @@ -1,3 +1,3 @@ <% provide(:title, 'Welcome to the JamKazam Beta!') %> -To signup, please go to the create account page. \ No newline at end of file +To signup, please go to the create account page. \ No newline at end of file diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/progress_mailer/client_dl_notrun.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/progress_mailer/client_dl_notrun.html.erb index 50719e908..88d388512 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/progress_mailer/client_dl_notrun.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/progress_mailer/client_dl_notrun.html.erb @@ -7,7 +7,7 @@

-https://jamkazam.desk.com +https://jamkazam.desk.com

-- Team JamKazam diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/progress_mailer/client_notdl.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/progress_mailer/client_notdl.html.erb index cb221e5c9..500479365 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/progress_mailer/client_notdl.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/progress_mailer/client_notdl.html.erb @@ -7,11 +7,11 @@

-Go to Download Page +Go to Download Page

-Go to Support Center +Go to Support Center

-- Team JamKazam diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/progress_mailer/client_run_notgear.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/progress_mailer/client_run_notgear.html.erb index c561dd267..a06990e41 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/progress_mailer/client_run_notgear.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/progress_mailer/client_run_notgear.html.erb @@ -6,13 +6,13 @@

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, it’s very likely the tips in this article will help you get things set up and optimized so you can start playing online.

-

http://bit.ly/1i4Uul4 +

http://bit.ly/1i4Uul4

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:

-

https://jamkazam.desk.com +

https://jamkazam.desk.com

-- Team JamKazam diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/progress_mailer/gear_notsess.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/progress_mailer/gear_notsess.html.erb index 75b82aa15..3d96ac749 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/progress_mailer/gear_notsess.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/progress_mailer/gear_notsess.html.erb @@ -10,7 +10,7 @@ It’s still very early in our company’s development, so we don’t 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 won’t see private sessions where groups of musicians don’t want to be interrupted in their sessions.

-

If you are having trouble getting into sessions, we’d suggest you click the Musicians tile on the home screen of the app or the website: Go To Musicians Page +

If you are having trouble getting into sessions, we’d suggest you click the Musicians tile on the home screen of the app or the website: Go To Musicians Page

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 doesn’t work, please post a request for assistance in the support center so that we can help you get up and running:

-

https://jamkazam.desk.com +

https://jamkazam.desk.com

-- Team JamKazam diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/progress_mailer/reg_notconnect.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/progress_mailer/reg_notconnect.html.erb index 59254f3ab..0751b7388 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/progress_mailer/reg_notconnect.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/progress_mailer/reg_notconnect.html.erb @@ -7,7 +7,7 @@

Find Other Musicians on JamKazam
-To find and connect with other musicians who are already on JamKazam, we’d suggest you click the Musicians tile on the home screen of the app or the website: Go To Musicians Page +To find and connect with other musicians who are already on JamKazam, we’d suggest you click the Musicians tile on the home screen of the app or the website: Go To Musicians Page

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

If you have any trouble, please visit our support center at the link below any time to get help:

-

https://jamkazam.desk.com +

https://jamkazam.desk.com

-- Team JamKazam diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/progress_mailer/reg_notinvite.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/progress_mailer/reg_notinvite.html.erb index ef9bba2bc..0794830df 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/progress_mailer/reg_notinvite.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/progress_mailer/reg_notinvite.html.erb @@ -9,7 +9,7 @@

If you have any trouble, please visit our support center at the link below any time to get help:

-

https://jamkazam.desk.com +

https://jamkazam.desk.com

-- Team JamKazam diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/progress_mailer/sess_notgood.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/progress_mailer/sess_notgood.html.erb index 60ed88c69..adc2dc5c1 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/progress_mailer/sess_notgood.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/progress_mailer/sess_notgood.html.erb @@ -9,7 +9,7 @@

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 doesn’t work, please post a request for assistance in the support center so that we can help you get up and running:

-

https://jamkazam.desk.com +

https://jamkazam.desk.com

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! diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/band_session_join.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/band_session_join.html.erb index 3106f63df..276ae4fcc 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/band_session_join.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/band_session_join.html.erb @@ -1,3 +1,3 @@ <% provide(:title, 'New Band Session') %> -

<%= @body %> Listen in.

\ No newline at end of file +

<%= @body %> Listen in.

\ No newline at end of file diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/confirm_email.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/confirm_email.html.erb index 7c85b9df4..651d1c236 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/confirm_email.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/confirm_email.html.erb @@ -2,4 +2,4 @@

Welcome to JamKazam, <%= @user.first_name %>!

-

To confirm this email address, please go to the signup confirmation page.

+

To confirm this email address, please go to the signup confirmation page.

diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/friend_request.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/friend_request.html.erb index 56b9c3623..cf9df2fe2 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/friend_request.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/friend_request.html.erb @@ -2,4 +2,4 @@

<%= @body %>

-

To accept this friend request, click here.

+

To accept this friend request, click here.

diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/musician_session_join.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/musician_session_join.html.erb index 97a709a97..de2b7e43c 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/musician_session_join.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/musician_session_join.html.erb @@ -1,3 +1,3 @@ <% provide(:title, 'Musician in Session') %> -

<%= @body %> Listen in.

\ No newline at end of file +

<%= @body %> Listen in.

\ No newline at end of file diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/new_musicians.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/new_musicians.html.erb index bf5f12c0e..8551c23df 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/new_musicians.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/new_musicians.html.erb @@ -24,7 +24,7 @@ Hi <%= @user.first_name %>, <% end %>

-

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: http://www.jamkazam.com/client#/musicians. +

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: http://www.jamkazam.com/client#/musicians.

Best Regards,

diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/password_reset.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/password_reset.html.erb index 49a0a90bd..2ac4845c6 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/password_reset.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/password_reset.html.erb @@ -1,3 +1,3 @@ <% provide(:title, 'JamKazam Password Reset') %> -Visit this link so that you can change your JamKazam password: reset password. \ No newline at end of file +Visit this link so that you can change your JamKazam password: reset password. \ No newline at end of file diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_cancelled.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_cancelled.html.erb index d0ff59dc2..4fa8eb0f9 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_cancelled.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_cancelled.html.erb @@ -2,4 +2,4 @@

<%= @body %>

-

View Session Details

\ No newline at end of file +

View Session Details

\ No newline at end of file diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_comment.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_comment.html.erb index a367a7444..af6bac519 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_comment.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_comment.html.erb @@ -7,5 +7,5 @@

<%= @session_name %>

<%= @session_date %>

-

View Session Details

+

View Session Details

<% end %> \ No newline at end of file diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_daily.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_daily.html.erb index 5fb602b24..221c06839 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_daily.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_daily.html.erb @@ -68,7 +68,7 @@ <%= sess.genre.description %> <%= sess.name %>
- ">Details + ">Details <%= sess.description %> @@ -86,7 +86,7 @@ -

To see ALL the scheduled sessions that you might be interested in joining, view our Find Session page.

+

To see ALL the scheduled sessions that you might be interested in joining, view our Find Session page.

Best Regards,

diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_invitation.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_invitation.html.erb index 7e62e683a..a3e774395 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_invitation.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_invitation.html.erb @@ -7,4 +7,4 @@ <%= @session_date %>

-

View Session Details

\ No newline at end of file +

View Session Details

\ No newline at end of file diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_reminder.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_reminder.html.erb index 1521bd74a..c8514b939 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_reminder.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_reminder.html.erb @@ -7,4 +7,4 @@ <%= @session_date %>

-

View Session Details

\ No newline at end of file +

View Session Details

\ No newline at end of file diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_rescheduled.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_rescheduled.html.erb index e0e534018..9331036ca 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_rescheduled.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_rescheduled.html.erb @@ -7,4 +7,4 @@ <%= @session_date %>

-

View Session Details

\ No newline at end of file +

View Session Details

\ No newline at end of file diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_rsvp.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_rsvp.html.erb index c615903a8..046f1847f 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_rsvp.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_rsvp.html.erb @@ -7,4 +7,4 @@ <%= @session_date %>

-

View Session Details

\ No newline at end of file +

View Session Details

\ No newline at end of file diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_rsvp_approved.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_rsvp_approved.html.erb index 03305ce72..e12cab821 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_rsvp_approved.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_rsvp_approved.html.erb @@ -7,4 +7,4 @@ <%= @session_date %>

-

View Session Details

\ No newline at end of file +

View Session Details

\ No newline at end of file diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_rsvp_cancelled.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_rsvp_cancelled.html.erb index b7740ea1a..0c996aa5d 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_rsvp_cancelled.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_rsvp_cancelled.html.erb @@ -7,4 +7,4 @@ <%= @session_date %>

-

View Session Details

\ No newline at end of file +

View Session Details

\ No newline at end of file diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_rsvp_cancelled_org.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_rsvp_cancelled_org.html.erb index 3cfa830d0..05ce13a7e 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_rsvp_cancelled_org.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_rsvp_cancelled_org.html.erb @@ -7,4 +7,4 @@ <%= @session_date %>

-

View Session Details

\ No newline at end of file +

View Session Details

\ No newline at end of file diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/text_message.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/text_message.html.erb index cb083ca1b..879c27c2c 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/text_message.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/text_message.html.erb @@ -4,5 +4,5 @@ <% content_for :note do %> <%= @note %> -

To reply to this message, click here.

+

To reply to this message, click here.

<% end %> diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/updating_email.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/updating_email.html.erb index 0617f81ae..b0792e217 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/updating_email.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/updating_email.html.erb @@ -1,3 +1,3 @@ <% provide(:title, 'Please Confirm New JamKazam Email') %> -Please click the following link to confirm your change in email: confirm email. \ No newline at end of file +Please click the following link to confirm your change in email: confirm email. \ No newline at end of file diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/welcome_message.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/welcome_message.html.erb index b74876e92..5b663b7ee 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/welcome_message.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/welcome_message.html.erb @@ -11,31 +11,31 @@

Getting Started Video
We recommend watching this video before you jump into the service just to get oriented. It will really help you hit the ground running: -https://www.youtube.com/watch?v=VexH4834o9I +https://www.youtube.com/watch?v=VexH4834o9I

Other Great Tutorial Videos
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: -https://jamkazam.desk.com/customer/portal/articles/1304097-tutorial-videos +https://jamkazam.desk.com/customer/portal/articles/1304097-tutorial-videos

Knowledge Base Articles
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: -https://jamkazam.desk.com/customer/portal/topics/564807-getting-started/articles +https://jamkazam.desk.com/customer/portal/topics/564807-getting-started/articles

JamKazam Support Portal
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: -https://jamkazam.desk.com/ +https://jamkazam.desk.com/

JamKazam Community Forum
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: -http://forums.jamkazam.com/ +http://forums.jamkazam.com/

diff --git a/ruby/lib/jam_ruby/app/views/layouts/from_user_mailer.html.erb b/ruby/lib/jam_ruby/app/views/layouts/from_user_mailer.html.erb index 8ce59bc6b..aa1d520f8 100644 --- a/ruby/lib/jam_ruby/app/views/layouts/from_user_mailer.html.erb +++ b/ruby/lib/jam_ruby/app/views/layouts/from_user_mailer.html.erb @@ -9,6 +9,9 @@ margin-bottom:0px; line-height:140%; } + a { + color: #ffcc00 !important; + } @@ -48,7 +51,7 @@

-

This email was sent to you because you have an account at JamKazam. +

This email was sent to you because you have an account at JamKazam. diff --git a/ruby/lib/jam_ruby/app/views/layouts/user_mailer.html.erb b/ruby/lib/jam_ruby/app/views/layouts/user_mailer.html.erb index 3509aa0bc..7e411bbc9 100644 --- a/ruby/lib/jam_ruby/app/views/layouts/user_mailer.html.erb +++ b/ruby/lib/jam_ruby/app/views/layouts/user_mailer.html.erb @@ -9,7 +9,9 @@ margin-bottom:0px; line-height:140%; } - + a { + color: #ffcc00 !important; + } @@ -37,7 +39,7 @@ -

This email was sent to you because you have an account at JamKazam.  Click here to unsubscribe and update your profile settings. +

This email was sent to you because you have an account at JamKazam.  Click here to unsubscribe and update your profile settings.

diff --git a/ruby/lib/jam_ruby/errors/conflict_error.rb b/ruby/lib/jam_ruby/errors/conflict_error.rb new file mode 100644 index 000000000..5fe2e2491 --- /dev/null +++ b/ruby/lib/jam_ruby/errors/conflict_error.rb @@ -0,0 +1,5 @@ +module JamRuby + class ConflictError < Exception + + end +end \ No newline at end of file diff --git a/ruby/lib/jam_ruby/lib/nav.rb b/ruby/lib/jam_ruby/lib/nav.rb index 7f58dad61..bfc788471 100644 --- a/ruby/lib/jam_ruby/lib/nav.rb +++ b/ruby/lib/jam_ruby/lib/nav.rb @@ -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 diff --git a/ruby/lib/jam_ruby/message_factory.rb b/ruby/lib/jam_ruby/message_factory.rb index 7ece76e28..f5666089a 100644 --- a/ruby/lib/jam_ruby/message_factory.rb +++ b/ruby/lib/jam_ruby/message_factory.rb @@ -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, diff --git a/ruby/lib/jam_ruby/models/music_session.rb b/ruby/lib/jam_ruby/models/music_session.rb index 376a104e9..750ddf462 100644 --- a/ruby/lib/jam_ruby/models/music_session.rb +++ b/ruby/lib/jam_ruby/models/music_session.rb @@ -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? diff --git a/ruby/lib/jam_ruby/models/notification.rb b/ruby/lib/jam_ruby/models/notification.rb index 77b2c2394..adb481c81 100644 --- a/ruby/lib/jam_ruby/models/notification.rb +++ b/ruby/lib/jam_ruby/models/notification.rb @@ -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, diff --git a/ruby/lib/jam_ruby/models/rsvp_request.rb b/ruby/lib/jam_ruby/models/rsvp_request.rb index f30851efe..9f233b973 100644 --- a/ruby/lib/jam_ruby/models/rsvp_request.rb +++ b/ruby/lib/jam_ruby/models/rsvp_request.rb @@ -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] diff --git a/ruby/lib/jam_ruby/resque/audiomixer.rb b/ruby/lib/jam_ruby/resque/audiomixer.rb index 66973b12a..da3038622 100644 --- a/ruby/lib/jam_ruby/resque/audiomixer.rb +++ b/ruby/lib/jam_ruby/resque/audiomixer.rb @@ -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) diff --git a/ruby/spec/jam_ruby/models/rsvp_request_spec.rb b/ruby/spec/jam_ruby/models/rsvp_request_spec.rb index 26ddd1c22..0f8aa5874 100644 --- a/ruby/spec/jam_ruby/models/rsvp_request_spec.rb +++ b/ruby/spec/jam_ruby/models/rsvp_request_spec.rb @@ -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 diff --git a/ruby/spec/support/utilities.rb b/ruby/spec/support/utilities.rb index 7c625ed78..0ed42f4d9 100644 --- a/ruby/spec/support/utilities.rb +++ b/ruby/spec/support/utilities.rb @@ -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 diff --git a/web/app/assets/javascripts/accounts_session_detail.js b/web/app/assets/javascripts/accounts_session_detail.js index 3ff0981be..53eb195f7 100644 --- a/web/app/assets/javascripts/accounts_session_detail.js +++ b/web/app/assets/javascripts/accounts_session_detail.js @@ -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 += ' '; + 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'} ); diff --git a/web/app/assets/javascripts/accounts_session_properties.js b/web/app/assets/javascripts/accounts_session_properties.js index d86698bcb..0a0f1ca34 100644 --- a/web/app/assets/javascripts/accounts_session_properties.js +++ b/web/app/assets/javascripts/accounts_session_properties.js @@ -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 = $('').text(notation.file_name); - var $text = $('').text($link); + var $link = notation.viewable ? $('').html(notation.file_name) : '#'; + var $text = $('').html($link); var $file = $('
  • ').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"); diff --git a/web/app/assets/javascripts/dialog/rsvpCancelDialog.js b/web/app/assets/javascripts/dialog/rsvpCancelDialog.js index c2efb6822..bd0079bf3 100644 --- a/web/app/assets/javascripts/dialog/rsvpCancelDialog.js +++ b/web/app/assets/javascripts/dialog/rsvpCancelDialog.js @@ -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 diff --git a/web/app/assets/javascripts/dialog/rsvpCreateSlotDialog.js b/web/app/assets/javascripts/dialog/rsvpCreateSlotDialog.js new file mode 100644 index 000000000..dcf5fd500 --- /dev/null +++ b/web/app/assets/javascripts/dialog/rsvpCreateSlotDialog.js @@ -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("
    "); + + $('.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); \ No newline at end of file diff --git a/web/app/assets/javascripts/dialog/rsvpSubmitDialog.js b/web/app/assets/javascripts/dialog/rsvpSubmitDialog.js index be8bf3afa..03ccb6a5f 100644 --- a/web/app/assets/javascripts/dialog/rsvpSubmitDialog.js +++ b/web/app/assets/javascripts/dialog/rsvpSubmitDialog.js @@ -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('Play Any Instrument You Like
    '); + $('.rsvp-instruments', $dialog).append('Play Any Instrument You Like
    '); } if (hasOpenSlots) { $.each(response.open_slots, function(index, val) { - var instrument = val.instrument_id; - - $('.rsvp-instruments', $dialog).append('' + val.description + " (" + val.proficiency_desc + ")
    "); + $('.rsvp-instruments', $dialog).append('' + val.description + " (" + val.proficiency_desc + ")
    "); }); } } @@ -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(); }); } diff --git a/web/app/assets/javascripts/dialog/sessionSettingsDialog.js b/web/app/assets/javascripts/dialog/sessionSettingsDialog.js index 3aa5c6f3c..ea18fcd01 100644 --- a/web/app/assets/javascripts/dialog/sessionSettingsDialog.js +++ b/web/app/assets/javascripts/dialog/sessionSettingsDialog.js @@ -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 = $('').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 = $('').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() { diff --git a/web/app/assets/javascripts/hoverMusician.js b/web/app/assets/javascripts/hoverMusician.js index f5f69440a..815cd2f8b 100644 --- a/web/app/assets/javascripts/hoverMusician.js +++ b/web/app/assets/javascripts/hoverMusician.js @@ -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 diff --git a/web/app/assets/javascripts/notificationPanel.js b/web/app/assets/javascripts/notificationPanel.js index 1e56483f3..ab293afb4 100644 --- a/web/app/assets/javascripts/notificationPanel.js +++ b/web/app/assets/javascripts/notificationPanel.js @@ -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'); diff --git a/web/app/assets/javascripts/recording_utils.js.coffee b/web/app/assets/javascripts/recording_utils.js.coffee index 3a6dbd002..925029c7d 100644 --- a/web/app/assets/javascripts/recording_utils.js.coffee +++ b/web/app/assets/javascripts/recording_utils.js.coffee @@ -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' diff --git a/web/app/assets/javascripts/scheduled_session.js.erb b/web/app/assets/javascripts/scheduled_session.js.erb index e45059c72..e113e4b86 100644 --- a/web/app/assets/javascripts/scheduled_session.js.erb +++ b/web/app/assets/javascripts/scheduled_session.js.erb @@ -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'); diff --git a/web/app/assets/javascripts/session.js b/web/app/assets/javascripts/session.js index a49148209..c3dbad3b9 100644 --- a/web/app/assets/javascripts/session.js +++ b/web/app/assets/javascripts/session.js @@ -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", $('You will need to reconfigure your audio device.')); } + 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; diff --git a/web/app/assets/javascripts/sessionModel.js b/web/app/assets/javascripts/sessionModel.js index 9bfb3409d..60d8862b8 100644 --- a/web/app/assets/javascripts/sessionModel.js +++ b/web/app/assets/javascripts/sessionModel.js @@ -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 diff --git a/web/app/assets/javascripts/session_utils.js b/web/app/assets/javascripts/session_utils.js index 482aaeab1..0c22b0108 100644 --- a/web/app/assets/javascripts/session_utils.js +++ b/web/app/assets/javascripts/session_utils.js @@ -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 diff --git a/web/app/assets/javascripts/sync_viewer.js.coffee b/web/app/assets/javascripts/sync_viewer.js.coffee index 02fd55c1d..4be93ba13 100644 --- a/web/app/assets/javascripts/sync_viewer.js.coffee +++ b/web/app/assets/javascripts/sync_viewer.js.coffee @@ -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) => diff --git a/web/app/assets/javascripts/ui_helper.js b/web/app/assets/javascripts/ui_helper.js index 0ed51e8d0..167855b30 100644 --- a/web/app/assets/javascripts/ui_helper.js +++ b/web/app/assets/javascripts/ui_helper.js @@ -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; }; diff --git a/web/app/assets/javascripts/web/sessions.js b/web/app/assets/javascripts/web/sessions.js index e6727fb91..95e64ca71 100644 --- a/web/app/assets/javascripts/web/sessions.js +++ b/web/app/assets/javascripts/web/sessions.js @@ -88,7 +88,7 @@ function togglePlay() { if(playing) { - $status.text('SESSION IN PROGRESS'); + $status.text('LIVE SESSION IN PROGRESS'); stopPlay(); } else { diff --git a/web/app/assets/stylesheets/client/account.css.scss b/web/app/assets/stylesheets/client/account.css.scss index 74183442c..c34722147 100644 --- a/web/app/assets/stylesheets/client/account.css.scss +++ b/web/app/assets/stylesheets/client/account.css.scss @@ -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 */ diff --git a/web/app/assets/stylesheets/client/common.css.scss b/web/app/assets/stylesheets/client/common.css.scss index 856ad5f21..e2f174fe0 100644 --- a/web/app/assets/stylesheets/client/common.css.scss +++ b/web/app/assets/stylesheets/client/common.css.scss @@ -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; } diff --git a/web/app/controllers/api_controller.rb b/web/app/controllers/api_controller.rb index b27e41e9b..d05413842 100644 --- a/web/app/controllers/api_controller.rb +++ b/web/app/controllers/api_controller.rb @@ -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 diff --git a/web/app/controllers/api_music_sessions_controller.rb b/web/app/controllers/api_music_sessions_controller.rb index c10e79020..c8457d959 100644 --- a/web/app/controllers/api_music_sessions_controller.rb +++ b/web/app/controllers/api_music_sessions_controller.rb @@ -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 diff --git a/web/app/helpers/feeds_helper.rb b/web/app/helpers/feeds_helper.rb index 38abfff85..d86ee3253 100644 --- a/web/app/helpers/feeds_helper.rb +++ b/web/app/helpers/feeds_helper.rb @@ -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) diff --git a/web/app/views/clients/_account_session_detail.html.haml b/web/app/views/clients/_account_session_detail.html.haml index 28fc61218..0b62b7217 100644 --- a/web/app/views/clients/_account_session_detail.html.haml +++ b/web/app/views/clients/_account_session_detail.html.haml @@ -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 diff --git a/web/app/views/clients/_account_session_properties.html.haml b/web/app/views/clients/_account_session_properties.html.haml index 69a6364d5..f50398b23 100644 --- a/web/app/views/clients/_account_session_properties.html.haml +++ b/web/app/views/clients/_account_session_properties.html.haml @@ -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 diff --git a/web/app/views/clients/_sessionSettings.html.haml b/web/app/views/clients/_sessionSettings.html.haml index 5a3523c13..3a115260a 100644 --- a/web/app/views/clients/_sessionSettings.html.haml +++ b/web/app/views/clients/_sessionSettings.html.haml @@ -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 diff --git a/web/app/views/dialogs/_dialogs.html.haml b/web/app/views/dialogs/_dialogs.html.haml index 69c332a5c..7ab177b1d 100644 --- a/web/app/views/dialogs/_dialogs.html.haml +++ b/web/app/views/dialogs/_dialogs.html.haml @@ -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' diff --git a/web/app/views/dialogs/_rsvpCreateSlotDialog.html.slim b/web/app/views/dialogs/_rsvpCreateSlotDialog.html.slim new file mode 100644 index 000000000..47bbfeec8 --- /dev/null +++ b/web/app/views/dialogs/_rsvpCreateSlotDialog.html.slim @@ -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 \ No newline at end of file diff --git a/web/app/views/dialogs/_rsvpSubmitDialog.html.haml b/web/app/views/dialogs/_rsvpSubmitDialog.html.haml index b27837203..1135fde57 100644 --- a/web/app/views/dialogs/_rsvpSubmitDialog.html.haml +++ b/web/app/views/dialogs/_rsvpSubmitDialog.html.haml @@ -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): diff --git a/web/app/views/errors/conflict_error.rabl b/web/app/views/errors/conflict_error.rabl new file mode 100644 index 000000000..fd3151a24 --- /dev/null +++ b/web/app/views/errors/conflict_error.rabl @@ -0,0 +1,7 @@ +object @exception + +attributes :message + +node "type" do + "ConflictError" +end \ No newline at end of file diff --git a/web/app/views/recordings/show.html.erb b/web/app/views/recordings/show.html.erb index 747f6c383..b8e2bf74a 100644 --- a/web/app/views/recordings/show.html.erb +++ b/web/app/views/recordings/show.html.erb @@ -18,7 +18,7 @@ <% end %>
    -<% if @claimed_recording.is_public %> +<% if @claimed_recording.is_public || @claimed_recording.recording.has_access?(current_user) %>
    <% unless @claimed_recording.recording.band.blank? %>
    @@ -97,7 +97,7 @@ <% end %>
    -<% 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} %> diff --git a/web/config/application.rb b/web/config/application.rb index ed7798fbf..b15e13426 100644 --- a/web/config/application.rb +++ b/web/config/application.rb @@ -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}\'`")}") diff --git a/web/spec/features/music_sessions_spec.rb b/web/spec/features/music_sessions_spec.rb index ee22f4158..4f7fba61e 100644 --- a/web/spec/features/music_sessions_spec.rb +++ b/web/spec/features/music_sessions_spec.rb @@ -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 diff --git a/web/spec/features/recording_landing_spec.rb b/web/spec/features/recording_landing_spec.rb index 45875b53b..6bf9f7beb 100644 --- a/web/spec/features/recording_landing_spec.rb +++ b/web/spec/features/recording_landing_spec.rb @@ -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 \ No newline at end of file diff --git a/web/spec/support/utilities.rb b/web/spec/support/utilities.rb index e57f78fc7..e459ac8b0 100644 --- a/web/spec/support/utilities.rb +++ b/web/spec/support/utilities.rb @@ -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 diff --git a/websocket-gateway/lib/jam_websockets/server.rb b/websocket-gateway/lib/jam_websockets/server.rb index f03ecf11e..9d27ea040 100644 --- a/websocket-gateway/lib/jam_websockets/server.rb +++ b/websocket-gateway/lib/jam_websockets/server.rb @@ -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