diff --git a/admin/Gemfile b/admin/Gemfile
index 716543c5e..21e2c819a 100644
--- a/admin/Gemfile
+++ b/admin/Gemfile
@@ -117,4 +117,4 @@ end
gem 'pry'
gem 'pry-remote'
gem 'pry-stack_explorer'
-gem 'pry-debugger'
+#gem 'pry-debugger'
diff --git a/admin/app/admin/connections.rb b/admin/app/admin/connections.rb
index b97e71164..92cfb668e 100644
--- a/admin/app/admin/connections.rb
+++ b/admin/app/admin/connections.rb
@@ -91,6 +91,7 @@ ActiveAdmin.register JamRuby::Connection, :as => 'Connection' do
row :locidispid
row :aasm_state
row :udp_reachable
+ row :is_network_testing
row :scoring_failures
row :scoring_timeout_occurrences
row :scoring_failures_offset
diff --git a/admin/app/admin/scoring_load.rb b/admin/app/admin/scoring_load.rb
index e020a4316..f6f3e3b2c 100644
--- a/admin/app/admin/scoring_load.rb
+++ b/admin/app/admin/scoring_load.rb
@@ -5,7 +5,7 @@ ActiveAdmin.register_page "Current Scoring Load" do
table_for GetWork.summary do
column "Work", :work_count
column "Who", Proc.new { |connection| "#{connection.first_name} #{connection.last_name} - #{connection.email}" }
- column "Errors", Proc.new { |connection| "#{connection.udp_reachable != false ? "" : "No STUN,"} #{connection.in_timeout != 'f' ? "Timeout," : ""} #{connection.in_session != 'f' ? "In-Session," : ""}" }
+ column "Errors", Proc.new { |connection| "#{connection.udp_reachable != false ? "" : "No STUN,"} #{connection.is_network_testing != false ? "NETWORK TESTING" : ""} #{connection.in_timeout != 'f' ? "Timeout," : ""} #{connection.in_session != 'f' ? "In-Session," : ""}" }
column "Total Timeouts", :scoring_timeout_occurrences
column "Current Scoring Failures", :scoring_failures
column "Offset", :scoring_failures_offset
diff --git a/db/manifest b/db/manifest
index ae595ece9..5b66bc20d 100755
--- a/db/manifest
+++ b/db/manifest
@@ -216,4 +216,7 @@ fix_find_session_sorting_2216c.sql
entabulate_current_network_scores.sql
discard_scores_changed.sql
emails_from_update.sql
-
+add_active_feed.sql
+connection_network_testing.sql
+video_sources.sql
+recorded_videos.sql
diff --git a/db/up/add_active_feed.sql b/db/up/add_active_feed.sql
new file mode 100644
index 000000000..725b36ec6
--- /dev/null
+++ b/db/up/add_active_feed.sql
@@ -0,0 +1 @@
+alter table feeds add column active BOOLEAN DEFAULT FALSE;
\ No newline at end of file
diff --git a/db/up/connection_network_testing.sql b/db/up/connection_network_testing.sql
new file mode 100644
index 000000000..3cd9e78df
--- /dev/null
+++ b/db/up/connection_network_testing.sql
@@ -0,0 +1,44 @@
+-- let the server know if the client is network testing. If so, then also remove them from work
+ALTER TABLE connections ADD COLUMN is_network_testing BOOLEAN DEFAULT FALSE NOT NULL;
+
+DROP FUNCTION IF EXISTS get_work (my_client_id VARCHAR(64), mylocidispid BIGINT, myaddr BIGINT, return_rows INT, stale_score INTERVAL);
+CREATE FUNCTION get_work (my_client_id VARCHAR(64), mylocidispid BIGINT, myaddr BIGINT, return_rows INT, stale_score INTERVAL) RETURNS TABLE (client_id VARCHAR(64)) VOLATILE AS $$
+BEGIN
+ RETURN QUERY WITH
+ scorable_locations AS (
+ SELECT DISTINCT locidispid FROM connections WHERE client_type = 'client' AND connections.client_id != my_client_id AND addr != myaddr AND udp_reachable AND is_network_testing = FALSE AND NOW() > scoring_timeout AND connections.music_session_id IS NULL AND
+ locidispid NOT IN (SELECT DISTINCT blocidispid FROM most_recent_scores WHERE alocidispid = mylocidispid AND (current_timestamp - score_dt) < stale_score) AND
+ locidispid/1000000 IN (SELECT locid FROM geoiplocations WHERE geog && st_buffer((SELECT geog FROM geoiplocations WHERE locid = mylocidispid/1000000), 4023360))
+ )
+
+ SELECT tmp.client_id FROM (SELECT connections.client_id, random() AS r, row_number() OVER (PARTITION BY connections.locidispid) AS rownum FROM connections, scorable_locations
+ WHERE connections.locidispid = scorable_locations.locidispid AND client_type = 'client' AND connections.client_id != my_client_id AND addr != myaddr AND udp_reachable AND NOW() > scoring_timeout AND connections.music_session_id IS NULL ) tmp WHERE rownum <= 1 ORDER BY r LIMIT return_rows;
+
+ RETURN;
+
+END;
+$$ LANGUAGE plpgsql;
+
+
+DROP FUNCTION IF EXISTS get_work_summary (stale_score INTERVAL);
+CREATE FUNCTION get_work_summary (stale_score INTERVAL) RETURNS TABLE (work_count BIGINT, client_id VARCHAR(64), email VARCHAR, first_name VARCHAR, last_name VARCHAR, user_id VARCHAR(64), udp_reachable BOOLEAN, in_timeout BOOLEAN, in_session BOOLEAN, scoring_failures INT, scoring_failures_offset INT, scoring_timeout_occurrences INT, is_network_testing BOOLEAN) VOLATILE AS $$
+BEGIN
+ RETURN QUERY
+ SELECT SUM(CASE WHEN tmp.test_client_id IS NULL OR tmp.in_session OR tmp.in_timeout OR tmp.udp_reachable = FALSE OR tmp.is_network_testing = TRUE THEN 0 ELSE 1 END) AS work_count, tmp.client_id AS client_id, users.email, users.first_name, users.last_name, users.id AS user_id, tmp.udp_reachable, tmp.in_timeout, tmp.in_session, tmp.scoring_failures, tmp.scoring_failures_offset, tmp.scoring_timeout_occurrences, tmp.is_network_testing FROM
+ (SELECT connections.client_type, scorable_locations.client_id AS test_client_id, connections.client_id AS client_id, connections.user_id AS user_id, connections.udp_reachable, connections.scoring_timeout > NOW() as in_timeout, connections.music_session_id IS NOT NULL AS in_session, connections.scoring_failures, connections.scoring_failures_offset, connections.scoring_timeout_occurrences, connections.is_network_testing, scorable_locations.client_id IS NULL AS same_client, row_number() OVER (PARTITION BY connections.locidispid) AS rownum FROM connections LEFT OUTER JOIN scorable_locations(connections.client_id, connections.locidispid, connections.addr, stale_score)
+ ON connections.locidispid != scorable_locations.locidispid) tmp INNER JOIN users ON tmp.user_id = users.id WHERE tmp.client_type = 'client' GROUP BY tmp.client_id, users.email, users.first_name, users.last_name, users.id, tmp.same_client, tmp.udp_reachable, tmp.in_timeout, tmp.in_session, tmp.scoring_failures, tmp.scoring_failures_offset, tmp.scoring_timeout_occurrences, tmp.is_network_testing ORDER BY work_count DESC;
+ RETURN;
+END;
+$$ LANGUAGE plpgsql;
+
+DROP FUNCTION IF EXISTS get_work_summary_no_agg(stale_score INTERVAL);
+-- useful for debugging get_work_summary
+CREATE FUNCTION get_work_summary_no_agg (stale_score INTERVAL) RETURNS TABLE (client_id VARCHAR(64), test_client_id VARCHAR(64), email VARCHAR, first_name VARCHAR, last_name VARCHAR, user_id VARCHAR(64), udp_reachable BOOLEAN, in_timeout BOOLEAN, scoring_failures INT, scoring_failures_offset INT, scoring_timeout_occurrences INT, is_network_testing BOOLEAN) VOLATILE AS $$
+BEGIN
+ RETURN QUERY
+ SELECT tmp.client_id AS client_id, tmp.test_client_id, users.email, users.first_name, users.last_name, users.id AS user_id, tmp.udp_reachable, tmp.in_timeout, tmp.scoring_failures, tmp.scoring_failures_offset, tmp.scoring_timeout_occurrences, tmp.is_network_testing FROM
+ (SELECT scorable_locations.client_id AS test_client_id, connections.client_id AS client_id, connections.user_id AS user_id, connections.udp_reachable, connections.scoring_timeout > NOW() as in_timeout, connections.scoring_failures, connections.scoring_failures_offset, connections.scoring_timeout_occurrences, connections.is_network_testing, scorable_locations.client_id IS NULL AS same_client, row_number() OVER (PARTITION BY connections.locidispid) AS rownum FROM connections LEFT OUTER JOIN scorable_locations(connections.client_id, connections.locidispid, connections.addr, stale_score)
+ ON connections.locidispid != scorable_locations.locidispid AND connections.client_type = 'client') tmp INNER JOIN users ON tmp.user_id = users.id ORDER BY tmp.client_id;
+ RETURN;
+END;
+$$ LANGUAGE plpgsql;
\ No newline at end of file
diff --git a/db/up/recorded_videos.sql b/db/up/recorded_videos.sql
new file mode 100644
index 000000000..fc8e22814
--- /dev/null
+++ b/db/up/recorded_videos.sql
@@ -0,0 +1,17 @@
+CREATE TABLE recorded_videos (
+ id BIGINT PRIMARY KEY,
+ user_id VARCHAR(64) REFERENCES users(id) ON DELETE CASCADE,
+ fully_uploaded BOOLEAN NOT NULL DEFAULT FALSE,
+ recording_id VARCHAR(64) NOT NULL,
+ length BIGINT,
+ client_video_source_id VARCHAR(64) NOT NULL,
+ url VARCHAR(1024),
+ file_offset BIGINT,
+ upload_failures INTEGER NOT NULL DEFAULT 0,
+ discard BOOLEAN,
+
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+ALTER TABLE recorded_videos ALTER COLUMN id SET DEFAULT nextval('tracks_next_tracker_seq');
diff --git a/db/up/video_sources.sql b/db/up/video_sources.sql
new file mode 100644
index 000000000..cab646c94
--- /dev/null
+++ b/db/up/video_sources.sql
@@ -0,0 +1,9 @@
+CREATE TABLE video_sources (
+ id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
+ connection_id VARCHAR(64) NOT NULL,
+ client_video_source_id VARCHAR(64) NOT NULL,
+
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
diff --git a/ruby/Gemfile b/ruby/Gemfile
index 8352f613f..8457d4649 100644
--- a/ruby/Gemfile
+++ b/ruby/Gemfile
@@ -31,7 +31,7 @@ gem 'sendgrid', '1.2.0'
gem 'aws-sdk' #, '1.29.1'
gem 'carrierwave', '0.9.0'
gem 'aasm', '3.0.16'
-gem 'devise', '>= 1.1.2'
+gem 'devise', '3.3.0' # 3.4.0 causes: uninitialized constant ActionController::Metal (NameError)
gem 'postgres-copy'
gem 'geokit'
gem 'geokit-rails'
diff --git a/ruby/lib/jam_ruby.rb b/ruby/lib/jam_ruby.rb
index eb8fc345e..9daaa1e8d 100755
--- a/ruby/lib/jam_ruby.rb
+++ b/ruby/lib/jam_ruby.rb
@@ -173,6 +173,8 @@ require "jam_ruby/models/chat_message"
require "jam_ruby/models/generic_state"
require "jam_ruby/models/score_history"
require "jam_ruby/models/jam_company"
+require "jam_ruby/models/video_source"
+require "jam_ruby/models/recorded_video"
include Jampb
diff --git a/ruby/lib/jam_ruby/app/mailers/user_mailer.rb b/ruby/lib/jam_ruby/app/mailers/user_mailer.rb
index ccc0cf52c..46275db4a 100644
--- a/ruby/lib/jam_ruby/app/mailers/user_mailer.rb
+++ b/ruby/lib/jam_ruby/app/mailers/user_mailer.rb
@@ -412,26 +412,28 @@
end
end
- def scheduled_session_comment(user, msg, comment, session)
- return if !user.subscribe_email
+ def scheduled_session_comment(target_user, sender, msg, comment, session)
+ return if !target_user.subscribe_email
- email = user.email
+ email = target_user.email
subject = "New Session Comment"
unique_args = {:type => "scheduled_session_comment"}
@body = msg
@session_name = session.name
@session_date = session.pretty_scheduled_start(true)
@comment = comment
+ @sender = sender
+ @suppress_user_has_account_footer = true
@session_url = "#{APP_CONFIG.external_root_url}/sessions/#{session.id}/details"
sendgrid_category "Notification"
sendgrid_unique_args :type => unique_args[:type]
sendgrid_recipients([email])
- sendgrid_substitute('@USERID', [user.id])
+ sendgrid_substitute('@USERID', [target_user.id])
mail(:to => email, :subject => subject) do |format|
format.text
- format.html
+ format.html { render :layout => "from_user_mailer" }
end
end
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 7ebe56c8f..a367a7444 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
@@ -1,7 +1,11 @@
-<% provide(:title, 'Scheduled Session Comment') %>
+<% provide(:title, "Scheduled Session Comment from #{@sender.name}") %>
+<% provide(:photo_url, @sender.resolved_photo_url) %>
-
<%= @body %>
+<% content_for :note do %>
+ <%= @comment %>
-<%= @comment %>
+ <%= @session_name %>
+ <%= @session_date %>
-View Session Details
\ No newline at end of file
+ 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_comment.text.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_comment.text.erb
index 6a7dc5f59..5bb7377a2 100644
--- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_comment.text.erb
+++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_comment.text.erb
@@ -5,4 +5,6 @@
<%= @comment %>
+- <%= @sender.name %>
+
See session details at <%= @session_url %>.
\ No newline at end of file
diff --git a/ruby/lib/jam_ruby/connection_manager.rb b/ruby/lib/jam_ruby/connection_manager.rb
index e8349c97d..c2f22df30 100644
--- a/ruby/lib/jam_ruby/connection_manager.rb
+++ b/ruby/lib/jam_ruby/connection_manager.rb
@@ -89,7 +89,7 @@ module JamRuby
udp_reachable_value = udp_reachable.nil? ? 'udp_reachable' : udp_reachable
sql =< "JamRuby::ActiveMusicSession", foreign_key: :music_session_id
has_one :latency_tester, class_name: 'JamRuby::LatencyTester', foreign_key: :client_id, primary_key: :client_id
has_many :tracks, :class_name => "JamRuby::Track", :inverse_of => :connection, :foreign_key => 'connection_id', :dependent => :delete_all
+ has_many :video_sources, :class_name => "JamRuby::VideoSource", :inverse_of => :connection, :foreign_key => 'connection_id', :dependent => :delete_all
- validates :as_musician, :inclusion => {:in => [true, false]}
+ validates :as_musician, :inclusion => {:in => [true, false, nil]}
validates :client_type, :inclusion => {:in => [TYPE_CLIENT, TYPE_BROWSER, TYPE_LATENCY_TESTER]}
validates_numericality_of :last_jam_audio_latency, greater_than:0, :allow_nil => true
validate :can_join_music_session, :if => :joining_session?
@@ -162,12 +163,13 @@ module JamRuby
true
end
- def join_the_session(music_session, as_musician, tracks, user, audio_latency)
+ def join_the_session(music_session, as_musician, tracks, user, audio_latency, videos=nil)
self.music_session_id = music_session.id
self.as_musician = as_musician
self.joining_session = true
self.joined_session_at = Time.now
associate_tracks(tracks) unless tracks.nil?
+ associate_videos(videos) unless videos.nil?
self.save
# if user joins the session as a musician, update their addr and location
@@ -192,6 +194,19 @@ module JamRuby
end
end
+ def associate_videos(videos)
+ unless videos.nil?
+ self.video_sources.clear()
+ videos.each do |video|
+ v = VideoSource.new
+ v.connection = self
+ v.client_video_source_id = video["client_video_source_id"]
+ v.save # todo what if it fails?
+ self.video_sources << v
+ end
+ end
+ end
+
def self.update_locidispids(use_copied = true)
# using addr, we can rebuild locidispid
diff --git a/ruby/lib/jam_ruby/models/feed.rb b/ruby/lib/jam_ruby/models/feed.rb
index cd68b8bd4..c2448e511 100644
--- a/ruby/lib/jam_ruby/models/feed.rb
+++ b/ruby/lib/jam_ruby/models/feed.rb
@@ -47,13 +47,13 @@ module JamRuby
# handle sort
if sort == 'date'
query = query.where("feeds.id < #{start}")
- query = query.order('feeds.id DESC')
+ query = query.order('feeds.active DESC, feeds.id DESC')
elsif sort == 'plays'
query = query.offset(start)
- query = query.order("COALESCE(recordings.play_count, music_sessions.play_count) DESC ")
+ query = query.order("feeds.active DESC, COALESCE(recordings.play_count, music_sessions.play_count) DESC")
elsif sort == 'likes'
query = query.offset(start)
- query = query.order("COALESCE(recordings.like_count, music_sessions.like_count) DESC ")
+ query = query.order("feeds.active DESC, COALESCE(recordings.like_count, music_sessions.like_count) DESC")
else
raise "sort not implemented: #{sort}"
end
diff --git a/ruby/lib/jam_ruby/models/get_work.rb b/ruby/lib/jam_ruby/models/get_work.rb
index 8946770fd..df5003925 100644
--- a/ruby/lib/jam_ruby/models/get_work.rb
+++ b/ruby/lib/jam_ruby/models/get_work.rb
@@ -12,6 +12,7 @@ module JamRuby
def self.get_work_list(connection, rows = 25, staleness_hours = 120)
+ return [] if connection.is_network_testing # short-circuit 0 results if is_network_testing
return [] unless connection.udp_reachable # short-circuit 0 results if udp_reachable
return [] if connection.scoring_timeout > Time.now # short-circuit 0 results if in scoring timeout
return [] if connection.in_session?
@@ -25,7 +26,7 @@ module JamRuby
end
def self.summary(staleness_hours = 120)
- r = GetWork.select([:work_count, :client_id, :email, :first_name, :last_name, :user_id, :udp_reachable, :in_timeout, :in_session, :scoring_failures, :scoring_failures_offset, :scoring_timeout_occurrences]).find_by_sql("select work_count, client_id, email, first_name, last_name, user_id, udp_reachable, in_timeout, in_session, scoring_failures, scoring_failures_offset, scoring_timeout_occurrences FROM get_work_summary(INTERVAL '#{staleness_hours} hours')" )
+ r = GetWork.select([:work_count, :client_id, :email, :first_name, :last_name, :user_id, :udp_reachable, :in_timeout, :in_session, :scoring_failures, :scoring_failures_offset, :scoring_timeout_occurrences, :is_network_testing]).find_by_sql("select work_count, client_id, email, first_name, last_name, user_id, udp_reachable, in_timeout, in_session, scoring_failures, scoring_failures_offset, scoring_timeout_occurrences, is_network_testing FROM get_work_summary(INTERVAL '#{staleness_hours} hours')" )
end
end
end
diff --git a/ruby/lib/jam_ruby/models/music_session.rb b/ruby/lib/jam_ruby/models/music_session.rb
index 67e3eebe2..0095eb652 100644
--- a/ruby/lib/jam_ruby/models/music_session.rb
+++ b/ruby/lib/jam_ruby/models/music_session.rb
@@ -74,6 +74,7 @@ module JamRuby
def add_to_feed
feed = Feed.new
feed.music_session = self
+ feed.active = true
end
@@ -604,6 +605,12 @@ module JamRuby
hist.end_history if hist
+ feed = Feed.find_by_music_session_id(session_id)
+ unless feed.nil?
+ feed.active = false
+ feed.save
+ end
+
Notification.send_session_ended(session_id)
end
diff --git a/ruby/lib/jam_ruby/models/notification.rb b/ruby/lib/jam_ruby/models/notification.rb
index 217205337..777f4853c 100644
--- a/ruby/lib/jam_ruby/models/notification.rb
+++ b/ruby/lib/jam_ruby/models/notification.rb
@@ -930,14 +930,13 @@ module JamRuby
rsvp_requests = RsvpRequest.index(music_session)
target_users = send_to_cancelled ? rsvp_requests.map { |r| r.user } : rsvp_requests.where(:canceled => false).map { |r| r.user }
target_users = target_users.concat([music_session.creator])
+ source_user = creator
pending_invites = music_session.pending_invitations
# remove the creator from the array
target_users = target_users.concat(pending_invites).uniq - [creator]
target_users.each do |target_user|
- source_user = creator
-
notification = Notification.new
notification.description = NotificationTypes::SCHEDULED_SESSION_COMMENT
notification.source_user_id = source_user.id
@@ -964,7 +963,7 @@ module JamRuby
end
begin
- UserMailer.scheduled_session_comment(target_user, notification_msg, comment, music_session).deliver
+ UserMailer.scheduled_session_comment(target_user, source_user, notification_msg, comment, music_session).deliver
rescue => e
@@log.error("Unable to send SCHEDULED_SESSION_COMMENT email to user #{target_user.email} #{e}")
end
diff --git a/ruby/lib/jam_ruby/models/recorded_video.rb b/ruby/lib/jam_ruby/models/recorded_video.rb
new file mode 100644
index 000000000..63837c53a
--- /dev/null
+++ b/ruby/lib/jam_ruby/models/recorded_video.rb
@@ -0,0 +1,18 @@
+module JamRuby
+ # Video analog to JamRuby::RecordedTrack
+ class RecordedVideo < ActiveRecord::Base
+ belongs_to :user, :class_name => "JamRuby::User", :inverse_of => :recorded_videos
+ belongs_to :recording, :class_name => "JamRuby::Recording", :inverse_of => :recorded_videos
+
+ validates :client_video_source_id, :presence => true
+
+ def self.create_from_video_source(video_source, recording)
+ recorded_video_source = self.new
+ recorded_video_source.recording = recording
+ recorded_video_source.client_video_source_id = video_source.id
+ recorded_video_source.user = video_source.connection.user
+ recorded_video_source.save
+ recorded_video_source
+ end
+ end
+end
diff --git a/ruby/lib/jam_ruby/models/recording.rb b/ruby/lib/jam_ruby/models/recording.rb
index edd2a4d1a..4368c3234 100644
--- a/ruby/lib/jam_ruby/models/recording.rb
+++ b/ruby/lib/jam_ruby/models/recording.rb
@@ -9,6 +9,7 @@ module JamRuby
has_many :claimed_recordings, :class_name => "JamRuby::ClaimedRecording", :inverse_of => :recording, :foreign_key => 'recording_id', :dependent => :destroy
has_many :mixes, :class_name => "JamRuby::Mix", :inverse_of => :recording, :foreign_key => 'recording_id', :dependent => :destroy
has_many :recorded_tracks, :class_name => "JamRuby::RecordedTrack", :foreign_key => :recording_id, :dependent => :destroy
+ has_many :recorded_videos, :class_name => "JamRuby::RecordedVideo", :foreign_key => :recording_id, :dependent => :destroy
has_many :comments, :class_name => "JamRuby::RecordingComment", :foreign_key => "recording_id", :dependent => :destroy
has_many :likes, :class_name => "JamRuby::RecordingLiker", :foreign_key => "recording_id", :dependent => :destroy
has_many :plays, :class_name => "JamRuby::PlayablePlay", :as => :playable, :dependent => :destroy
@@ -152,6 +153,10 @@ module JamRuby
connection.tracks.each do |track|
recording.recorded_tracks << RecordedTrack.create_from_track(track, recording)
end
+
+ connection.video_sources.each do |video|
+ recording.recorded_videos << RecordedVideo.create_from_video_source(video, recording)
+ end
end
end
end
@@ -298,22 +303,82 @@ module JamRuby
since = 0 unless since || since == '' # guard against nil
uploads = []
- RecordedTrack
- .joins(:recording)
- .where(:user_id => user.id)
- .where(:fully_uploaded => false)
- .where('recorded_tracks.id > ?', since)
- .where("upload_failures <= #{APP_CONFIG.max_track_upload_failures}")
- .where("duration IS NOT NULL")
- .where('all_discarded = false')
- .order('recorded_tracks.id')
- .limit(limit).each do |recorded_track|
- uploads.push({
- :type => "recorded_track",
- :client_track_id => recorded_track.client_track_id,
- :recording_id => recorded_track.recording_id,
- :next => recorded_track.id
- })
+
+ # Uploads now include videos in addition to the tracks.
+ # This is accomplished using a SQL union via arel, as follows:
+
+ # Select fields from track. Note the reorder, which removes
+ # the default scope sort as it b0rks the union. Also note the
+ # alias so that we can differentiate tracks and videos when
+ # processing the results:
+ track_arel = RecordedTrack.select([
+ :id,
+ :recording_id,
+ :url,
+ :fully_uploaded,
+ :upload_failures,
+ :client_track_id,
+ Arel::Nodes::As.new('track', Arel.sql('item_type'))
+ ]).reorder("")
+
+ # Select fields for video. Note that it must include
+ # the same number of fields as the track in order for
+ # the union to work:
+ vid_arel = RecordedVideo.select([
+ :id,
+ :recording_id,
+ :url,
+ :fully_uploaded,
+ :upload_failures,
+ :client_video_source_id,
+ Arel::Nodes::As.new('video', Arel.sql('item_type'))
+ ]).reorder("")
+
+ # Glue them together:
+ union = track_arel.union(vid_arel)
+
+ # Create a table alias from the union so we can get back to arel:
+ utable = RecordedTrack.arel_table.create_table_alias(union, :recorded_items)
+ arel = track_arel.from(utable)
+ arel = arel.select([
+ :id,
+ :recording_id,
+ :url,
+ :fully_uploaded,
+ :upload_failures,
+ :client_track_id,
+ :item_type
+ ])
+
+ # Further joining and criteria for the unioned object:
+ arel = arel.joins("INNER JOIN (SELECT id as rec_id, all_discarded, duration FROM recordings) recs ON rec_id=recorded_items.recording_id") \
+ .where('recorded_items.fully_uploaded =?', false) \
+ .where('recorded_items.id > ?', since) \
+ .where("upload_failures <= #{APP_CONFIG.max_track_upload_failures}") \
+ .where("duration IS NOT NULL") \
+ .where('all_discarded = false') \
+ .order('recorded_items.id') \
+ .limit(limit)
+
+ # Load into array:
+ arel.each do |recorded_item|
+ if(recorded_item.item_type=='video')
+ # A video:
+ uploads << ({
+ :type => "recorded_video",
+ :client_video_source_id => recorded_item.client_track_id,
+ :recording_id => recorded_item.recording_id,
+ :next => recorded_item.id
+ })
+ else
+ # A track:
+ uploads << ({
+ :type => "recorded_track",
+ :client_track_id => recorded_item.client_track_id,
+ :recording_id => recorded_item.recording_id,
+ :next => recorded_item.id
+ })
+ end
end
next_value = uploads.length > 0 ? uploads[-1][:next].to_s : nil
@@ -321,7 +386,6 @@ module JamRuby
next_value = since # echo back to the client the same value they passed in, if there are no results
end
-
{
"uploads" => uploads,
"next" => next_value.to_s
diff --git a/ruby/lib/jam_ruby/models/rsvp_request.rb b/ruby/lib/jam_ruby/models/rsvp_request.rb
index 689aad72e..f30851efe 100644
--- a/ruby/lib/jam_ruby/models/rsvp_request.rb
+++ b/ruby/lib/jam_ruby/models/rsvp_request.rb
@@ -182,7 +182,7 @@ module JamRuby
end
if rsvp_slot.chosen && r[:accept]
- raise StateError, "The #{rsvp_slot.instrument_id} slot has already been approved for another user."
+ raise StateError, "All RSVP slots for the #{rsvp_slot.instrument_id} have been already approved."
end
if r[:accept]
diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb
index 9cdc68a1c..b33a1d2c0 100644
--- a/ruby/lib/jam_ruby/models/user.rb
+++ b/ruby/lib/jam_ruby/models/user.rb
@@ -103,6 +103,7 @@ module JamRuby
# saved tracks
has_many :recorded_tracks, :foreign_key => "user_id", :class_name => "JamRuby::RecordedTrack", :inverse_of => :user
+ has_many :recorded_videos, :foreign_key => "user_id", :class_name => "JamRuby::RecordedVideo", :inverse_of => :user
# invited users
has_many :invited_users, :foreign_key => "sender_id", :class_name => "JamRuby::InvitedUser"
@@ -1271,8 +1272,7 @@ module JamRuby
if audio_latency > 2
# updating the connection is best effort
if connection
- connection.last_jam_audio_latency = audio_latency
- connection.save
+ Connection.where(:id => connection.id).update_all(:last_jam_audio_latency => audio_latency)
end
self.last_jam_audio_latency = audio_latency
diff --git a/ruby/lib/jam_ruby/models/video_source.rb b/ruby/lib/jam_ruby/models/video_source.rb
new file mode 100644
index 000000000..4517bf4bf
--- /dev/null
+++ b/ruby/lib/jam_ruby/models/video_source.rb
@@ -0,0 +1,10 @@
+module JamRuby
+ # Video analog to JamRuby::Track
+ class VideoSource < ActiveRecord::Base
+ self.table_name = "video_sources"
+ self.primary_key = 'id'
+ default_scope order('created_at ASC')
+ belongs_to :connection, :class_name => "JamRuby::Connection", :inverse_of => :video_sources, :foreign_key => 'connection_id'
+ validates :connection, presence: true
+ end
+end
\ No newline at end of file
diff --git a/ruby/lib/jam_ruby/resque/scheduled/active_music_session_cleaner.rb b/ruby/lib/jam_ruby/resque/scheduled/active_music_session_cleaner.rb
index 18722217d..e700a0427 100644
--- a/ruby/lib/jam_ruby/resque/scheduled/active_music_session_cleaner.rb
+++ b/ruby/lib/jam_ruby/resque/scheduled/active_music_session_cleaner.rb
@@ -38,6 +38,7 @@ module JamRuby
stale_sessions.each do |s|
if s.connections.count == 0
+ s.before_destroy
s.delete
end
end
diff --git a/ruby/spec/factories.rb b/ruby/spec/factories.rb
index 249cd8247..2d030b752 100644
--- a/ruby/spec/factories.rb
+++ b/ruby/spec/factories.rb
@@ -231,6 +231,10 @@ FactoryGirl.define do
sequence(:client_track_id) { |n| "client_track_id#{n}"}
end
+ factory :video_source, :class => JamRuby::VideoSource do
+ client_video_source_id "test_source_id"
+ end
+
factory :recorded_track, :class => JamRuby::RecordedTrack do
instrument JamRuby::Instrument.first
sound 'stereo'
@@ -244,6 +248,12 @@ FactoryGirl.define do
association :recording, factory: :recording
end
+ factory :recorded_video, :class => JamRuby::RecordedVideo do
+ sequence(:client_id) { |n| "client_id-#{n}"}
+ sequence(:track_id) { |n| "track_id-#{n}"}
+ sequence(:client_track_id) { |n| "client_track_id-#{n}"}
+ end
+
factory :instrument, :class => JamRuby::Instrument do
description { |n| "Instrument #{n}" }
end
diff --git a/ruby/spec/jam_ruby/models/active_music_session_spec.rb b/ruby/spec/jam_ruby/models/active_music_session_spec.rb
index 28ac362cc..ac0288f23 100644
--- a/ruby/spec/jam_ruby/models/active_music_session_spec.rb
+++ b/ruby/spec/jam_ruby/models/active_music_session_spec.rb
@@ -711,5 +711,38 @@ describe ActiveMusicSession do
@music_session.get_connection_ids(exclude_client_id: @connection2.client_id, as_musician: true).should == [@connection1.client_id]
end
end
+
+ describe "join_the_session" do
+ let(:creator_1) { FactoryGirl.create(:user, last_jam_locidispid: 4, last_jam_audio_latency: 8) }
+ let(:creator_conn_1) { FactoryGirl.create(:connection, user: creator_1, ip_address: '4.4.4.4', locidispid: 4, addr:4) }
+ let!(:music_session_1) { FactoryGirl.create(:active_music_session, :creator => creator_1, genre: Genre.find('african'), language: 'eng', description: "Bunny Jumps" ) }
+ let(:tracks) { [{'sound' => 'mono', 'client_track_id' => 'abc', 'instrument_id' => 'piano'}] }
+ let(:videos) { [{'client_video_source_id' => 'abc'}] }
+
+ it "joins the session with no video" do
+ creator_conn_1.join_the_session(music_session_1.music_session, true, tracks, creator_1, 10)
+ creator_conn_1.errors.any?.should be_false
+
+ music_sessions = ActiveMusicSession.index(creator_1)
+ music_sessions.should_not be_nil
+ music_sessions.length.should == 1
+ music_sessions[0].connections.should have(1).items
+ music_sessions[0].connections.should have(1).items
+ music_sessions[0].connections[0].tracks.should have(1).items
+ music_sessions[0].connections[0].video_sources.should have(0).items
+ end
+
+ it "joins the session with video" do
+ creator_conn_1.join_the_session(music_session_1.music_session, true, tracks, creator_1, 10, videos)
+ creator_conn_1.errors.any?.should be_false
+ music_sessions = ActiveMusicSession.index(creator_1)
+ music_sessions.should_not be_nil
+ music_sessions.length.should == 1
+ creator_conn_1.video_sources.should have(1).items
+ music_sessions[0].connections.should have(1).items
+ music_sessions[0].connections[0].video_sources.should have(1).items
+ music_sessions[0].connections[0].tracks.should have(1).items
+ end
+ end
end
diff --git a/ruby/spec/jam_ruby/models/connection_spec.rb b/ruby/spec/jam_ruby/models/connection_spec.rb
index c391c7f29..dc7d8cdba 100644
--- a/ruby/spec/jam_ruby/models/connection_spec.rb
+++ b/ruby/spec/jam_ruby/models/connection_spec.rb
@@ -9,6 +9,8 @@ describe JamRuby::Connection do
:ip_address => "1.1.1.1",
:client_id => "1") }
+ let(:tracks) { [{'sound' => 'mono', 'client_track_id' => 'abc', 'instrument_id' => 'piano'}] }
+
it 'starts in the correct state' do
connection = FactoryGirl.create(:connection,
:user => user,
@@ -100,4 +102,5 @@ describe JamRuby::Connection do
conn.locidispid.should == 0
end
end
+
end
diff --git a/ruby/spec/jam_ruby/models/feed_spec.rb b/ruby/spec/jam_ruby/models/feed_spec.rb
index dc790f13c..f63d03116 100644
--- a/ruby/spec/jam_ruby/models/feed_spec.rb
+++ b/ruby/spec/jam_ruby/models/feed_spec.rb
@@ -54,16 +54,16 @@ describe Feed do
end
describe "sorting" do
- it "sorts by index (date) DESC" do
+ it "sorts by active flag / index (date) DESC" do
claimed_recording = FactoryGirl.create(:claimed_recording)
feeds, start = Feed.index(user1)
feeds.length.should == 2
- feeds[0].recording.should == claimed_recording.recording
- feeds[1].music_session.should == claimed_recording.recording.music_session.music_session
+ feeds[1].recording.should == claimed_recording.recording
+ feeds[0].music_session.should == claimed_recording.recording.music_session.music_session
end
- it "sort by plays DESC" do
+ it "sort by active flag / plays DESC" do
claimed_recording1 = FactoryGirl.create(:claimed_recording)
claimed_recording2 = FactoryGirl.create(:claimed_recording)
@@ -77,8 +77,8 @@ describe Feed do
feeds, start = Feed.index(user1, :sort => 'plays')
feeds.length.should == 4
- feeds[0].recording.should == claimed_recording2.recording
- feeds[1].recording.should == claimed_recording1.recording
+ feeds[2].recording.should == claimed_recording2.recording
+ feeds[3].recording.should == claimed_recording1.recording
FactoryGirl.create(:playable_play, playable: claimed_recording1.recording.music_session.music_session, user: user1)
FactoryGirl.create(:playable_play, playable: claimed_recording1.recording.music_session.music_session, user: user2)
@@ -88,11 +88,11 @@ describe Feed do
feeds, start = Feed.index(user1, :sort => 'plays')
feeds.length.should == 4
feeds[0].music_session.should == claimed_recording1.recording.music_session.music_session
- feeds[1].recording.should == claimed_recording2.recording
- feeds[2].recording.should == claimed_recording1.recording
+ feeds[2].recording.should == claimed_recording2.recording
+ feeds[3].recording.should == claimed_recording1.recording
end
- it "sort by likes DESC" do
+ it "sort by active flag / likes DESC" do
claimed_recording1 = FactoryGirl.create(:claimed_recording)
claimed_recording2 = FactoryGirl.create(:claimed_recording)
@@ -106,6 +106,7 @@ describe Feed do
feeds, start = Feed.index(user1, :sort => 'likes')
feeds.length.should == 4
+ feeds = feeds.where("feeds.music_session_id is null")
feeds[0].recording.should == claimed_recording2.recording
feeds[1].recording.should == claimed_recording1.recording
@@ -116,8 +117,8 @@ describe Feed do
feeds, start = Feed.index(user1, :sort => 'likes')
feeds.length.should == 4
feeds[0].music_session.should == claimed_recording1.recording.music_session.music_session
- feeds[1].recording.should == claimed_recording2.recording
- feeds[2].recording.should == claimed_recording1.recording
+ feeds[2].recording.should == claimed_recording2.recording
+ feeds[3].recording.should == claimed_recording1.recording
end
end
@@ -195,6 +196,9 @@ describe Feed do
it "supports date pagination" do
claimed_recording = FactoryGirl.create(:claimed_recording)
+ ams = ActiveMusicSession.find(claimed_recording.recording.music_session.music_session.id)
+ ams.before_destroy
+
options = {limit: 1}
feeds, start = Feed.index(user1, options)
feeds.length.should == 1
diff --git a/ruby/spec/jam_ruby/models/get_work_spec.rb b/ruby/spec/jam_ruby/models/get_work_spec.rb
index b47b7041d..e05c6cb95 100644
--- a/ruby/spec/jam_ruby/models/get_work_spec.rb
+++ b/ruby/spec/jam_ruby/models/get_work_spec.rb
@@ -176,6 +176,16 @@ describe GetWork do
GetWork.get_work_list(other_connection2).should == [my_connection.client_id]
end
+ it "excludes network testing clients" do
+ my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
+ other_connection = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2, is_network_testing: true)
+ other_connection2 = FactoryGirl.create(:connection, locidispid: houston_geoip[:locidispid], addr: 3)
+
+ GetWork.get_work_list(my_connection).should == [other_connection2.client_id]
+ GetWork.get_work_list(other_connection).should == []
+ GetWork.get_work_list(other_connection2).should == [my_connection.client_id]
+ end
+
it "excludes scoring_timeout clients (1)" do
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2, scoring_timeout: 1.days.from_now)
diff --git a/ruby/spec/jam_ruby/models/recorded_video_spec.rb b/ruby/spec/jam_ruby/models/recorded_video_spec.rb
new file mode 100644
index 000000000..e8043f24a
--- /dev/null
+++ b/ruby/spec/jam_ruby/models/recorded_video_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+require 'rest-client'
+
+describe RecordedVideo do
+ include UsesTempFiles
+ let (:user) {FactoryGirl.create(:user)}
+ let (:connection) {FactoryGirl.create(:connection, :user => user)}
+ let (:music_session){FactoryGirl.create(:active_music_session, :creator => user, :musician_access => true)}
+ let (:recording) {FactoryGirl.create(:recording, :music_session => music_session, :owner => user)}
+ let (:video_source) {FactoryGirl.create(:video_source, :connection => connection)}
+
+ it "should create from a video source" do
+ recorded_video_source = RecordedVideo.create_from_video_source(video_source, recording)
+ recorded_video_source.should_not be_nil
+ recorded_video_source.user.id.should == video_source.connection.user.id
+ recorded_video_source.fully_uploaded.should == false
+ recorded_video_source.client_video_source_id.should == video_source.id
+ end
+
+end
+
diff --git a/ruby/spec/jam_ruby/models/recording_spec.rb b/ruby/spec/jam_ruby/models/recording_spec.rb
index 0bc9702e0..d7a5cf709 100644
--- a/ruby/spec/jam_ruby/models/recording_spec.rb
+++ b/ruby/spec/jam_ruby/models/recording_spec.rb
@@ -7,10 +7,9 @@ describe Recording do
@instrument = FactoryGirl.create(:instrument, :description => 'a great instrument')
@music_session = FactoryGirl.create(:active_music_session, :creator => @user, :musician_access => true)
@connection = FactoryGirl.create(:connection, :user => @user, :music_session => @music_session)
- @track = FactoryGirl.create(:track, :connection => @connection, :instrument => @instrument)
+ @track = FactoryGirl.create(:track, :connection => @connection, :instrument => @instrument)
end
-
-
+
it "should allow finding of recorded tracks" do
user2 = FactoryGirl.create(:user)
connection2 = FactoryGirl.create(:connection, :user => user2, :music_session => @music_session)
@@ -39,7 +38,7 @@ describe Recording do
@recorded_tracks.length.should == 1
@recorded_tracks.first.instrument_id == @track.instrument_id
@recorded_tracks.first.user_id == @track.connection.user_id
- end
+ end
it "should not start a recording if the session is already being recorded" do
Recording.start(@music_session, @user).errors.any?.should be_false
@@ -286,6 +285,64 @@ describe Recording do
@recording2.errors.any?.should be_false
end
end
+
+
+ it "set video from source" do
+ @video_source = FactoryGirl.create(:video_source, :connection => @connection)
+
+ @music_session.is_recording?.should be_false
+ @recording = Recording.start(@music_session, @user)
+ @music_session.reload
+ @music_session.recordings[0].should == @recording
+ @recording.owner_id.should == @user.id
+
+ @recorded_videos = RecordedVideo.where(:recording_id => @recording.id)
+ @recorded_videos.should have(1).items
+ @recorded_videos[0].client_video_source_id.should eq(@video_source.id)
+ end
+
+ it "should include video when listing uploads" do
+ @video_source = FactoryGirl.create(:video_source, :connection => @connection)
+ @recording = Recording.start(@music_session, @user)
+ @recording.stop
+ @recording.reload
+ @genre = FactoryGirl.create(:genre)
+ @recording.claim(@user, "Recording", "Recording Description", @genre, true)
+
+ # We should have 2 items; a track and a video:
+ uploads = Recording.list_uploads(@user)
+ uploads["uploads"].should have(2).items
+ uploads["uploads"][0][:type].should eq("recorded_track")
+ uploads["uploads"][1][:type].should eq("recorded_video")
+ uploads["uploads"][0].should include(:client_track_id)
+ uploads["uploads"][1].should include(:client_video_source_id)
+
+ # Next page should have nothing:
+ uploads = Recording.list_uploads(@user, 10, uploads["next"])
+ uploads["uploads"].should have(0).items
+ end
+
+ it "should paginate with video" do
+ @video_source = FactoryGirl.create(:video_source, :connection => @connection)
+ @recording = Recording.start(@music_session, @user)
+ @recording.stop
+ @recording.reload
+ @genre = FactoryGirl.create(:genre)
+ @recording.claim(@user, "Recording", "Recording Description", @genre, true)
+
+ # Limit to 1, so we can test pagination:
+ uploads = Recording.list_uploads(@user, 1)
+ uploads["uploads"].should have(1).items # First page
+
+ # Second page:
+ uploads = Recording.list_uploads(@user, 1, uploads["next"])
+ uploads["uploads"].should have(1).items
+
+ # Last page (should be empty):
+ uploads = Recording.list_uploads(@user, 10, uploads["next"])
+ uploads["uploads"].should have(0).items
+ end
+
end
diff --git a/ruby/spec/jam_ruby/models/video_source_spec.rb b/ruby/spec/jam_ruby/models/video_source_spec.rb
new file mode 100644
index 000000000..166443c22
--- /dev/null
+++ b/ruby/spec/jam_ruby/models/video_source_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe VideoSource do
+
+ let (:user) {FactoryGirl.create(:user) }
+ let (:music_session) { FactoryGirl.create(:active_music_session, :creator => user)}
+ let (:connection) { FactoryGirl.create(:connection, :user => user, :music_session => music_session) }
+ let (:msuh) {FactoryGirl.create(:music_session_user_history, :history => music_session.music_session, :user => user, :client_id => connection.client_id) }
+
+
+ before(:each) do
+ msuh.touch
+ end
+
+ describe "simple create" do
+ it "create a video source" do
+ video_source = FactoryGirl.create(:video_source, :connection => connection)
+ video_source.should_not be_nil
+ end
+ end
+end
\ No newline at end of file
diff --git a/web/Gemfile b/web/Gemfile
index ad080e413..c97941bd1 100644
--- a/web/Gemfile
+++ b/web/Gemfile
@@ -18,7 +18,7 @@ else
gem 'jam_websockets', "0.1.#{ENV["BUILD_NUMBER"]}"
ENV['NOKOGIRI_USE_SYSTEM_LIBRARIES'] ||= "true"
end
-gem 'oj'
+gem 'oj', '2.10.2'
gem 'builder'
gem 'rails', '~>3.2.11'
gem 'railties', '~>3.2.11'
@@ -55,7 +55,7 @@ gem 'carrierwave_direct'
gem 'fog'
gem 'haml-rails'
gem 'unf' #optional fog dependency
-gem 'devise', '>= 1.1.2'
+gem 'devise', '3.3.0' #3.4.0 causes uninitialized constant ActionController::Metal (NameError)
gem 'postgres-copy'
#group :libv8 do
# gem 'libv8', "~> 3.11.8"
diff --git a/web/app/assets/javascripts/accounts_session_detail.js b/web/app/assets/javascripts/accounts_session_detail.js
index c43a8b537..a93d0b1c7 100644
--- a/web/app/assets/javascripts/accounts_session_detail.js
+++ b/web/app/assets/javascripts/accounts_session_detail.js
@@ -101,7 +101,18 @@
rest.updateRsvpRequest(rsvpId, params)
.done(refreshSessionDetail)
- .fail(app.ajaxError);
+ .fail(function(jqXHR, textStatus, errorMessage) {
+ if (jqXHR.status === 400) {
+ app.notify(
+ {
+ title: "Unable to Approve RSVP",
+ text: jqXHR.responseJSON.message
+ });
+ }
+ else {
+ app.ajaxError(jqXHR, textStatus, errorMessage);
+ }
+ });
}
function declineRsvpRequest(e) {
@@ -132,6 +143,9 @@
});
context.JK.bindHoverEvents();
+ // context.JK.bindInstrumentHover($('#pendingRSVPs'));
+ // context.JK.bindInstrumentHover($('#session-rsvps'));
+ // context.JK.bindInstrumentHover($('#still-needed'));
}
function loadSessionData() {
@@ -243,7 +257,7 @@
$.each(pending_rsvp_request.instrument_list, function (index, instrument) {
var instrumentId = instrument == null ? null : instrument.id;
var inst = context.JK.getInstrumentIcon24(instrumentId);
- instrumentLogoHtml += '
';
+ instrumentLogoHtml += '
';
})
}
@@ -281,7 +295,7 @@
$.each(approved_rsvp.instrument_list, function(index, instrument) {
var instrumentId = instrument == null ? null : instrument.id;
var inst = context.JK.getInstrumentIcon24(instrumentId);
- instrumentLogoHtml += '
';
+ instrumentLogoHtml += '
';
});
}
@@ -312,7 +326,7 @@
rsvpHtml = context._.template(
$("#template-account-session-rsvp").html(),
- {id: approved_rsvp.id, user_id: approved_rsvp.user_id, avatar_url: avatar_url,
+ {id: approved_rsvp.id, avatar_url: avatar_url,
user_name: approved_rsvp.name, instruments: instrumentLogoHtml,
latency: latencyHtml, is_owner: sessionData.isOwner, request_id: request_id},
{variable: 'data'}
@@ -346,7 +360,7 @@
resultHtml += context._.template(
$("#template-account-invited").html(),
- {avatar_url: avatar_url, user_id: invitation.reciever_id},
+ {avatar_url: avatar_url, user_id: invitation.receiver_id},
{variable: 'data'}
);
});
diff --git a/web/app/assets/javascripts/dialog/networkTestDialog.js b/web/app/assets/javascripts/dialog/networkTestDialog.js
index 5d64ceb64..da0d7130e 100644
--- a/web/app/assets/javascripts/dialog/networkTestDialog.js
+++ b/web/app/assets/javascripts/dialog/networkTestDialog.js
@@ -54,13 +54,14 @@
}
function beforeShow() {
+ networkTest.haltScoring();
if(!networkTest.isScoring()) {
networkTest.reset();
}
}
function afterHide() {
-
+ networkTest.resumeScoring();
}
function initialize() {
diff --git a/web/app/assets/javascripts/everywhere/everywhere.js b/web/app/assets/javascripts/everywhere/everywhere.js
index e90049966..71fd1169a 100644
--- a/web/app/assets/javascripts/everywhere/everywhere.js
+++ b/web/app/assets/javascripts/everywhere/everywhere.js
@@ -140,6 +140,9 @@
}
function updateScoringIntervals() {
+ // make sure latency testing is still going on, in case a refresh occurred during network test
+ context.jamClient.SetLatencyTestBlocked(false)
+
// set scoring intervals
if(context.jamClient.SetScoreWorkTimingInterval){
var success = context.jamClient.SetScoreWorkTimingInterval(
diff --git a/web/app/assets/javascripts/fakeJamClient.js b/web/app/assets/javascripts/fakeJamClient.js
index f6af7789f..393501e1b 100644
--- a/web/app/assets/javascripts/fakeJamClient.js
+++ b/web/app/assets/javascripts/fakeJamClient.js
@@ -362,6 +362,18 @@
return 8;
}
+ function SetLatencyTestBlocked(blocked) {
+
+ }
+
+ function isLatencyTestBlocked() {
+ return false;
+ }
+
+ function GetLastLatencyTestTimes() {
+ return { initiated: 10000, requested: 10000}
+ }
+
function GetASIODevices() {
var response =[{"device_id":0,"device_name":"Realtek High Definition Audio","device_type": 0,"interfaces":[{"interface_id":0,"interface_name":"Realtek HDA SPDIF Out","pins":[{"is_input":false,"pin_id":0,"pin_name":"PC Speaker"}]},{"interface_id":1,"interface_name":"Realtek HD Audio rear output","pins":[{"is_input":false,"pin_id":0,"pin_name":"PC Speaker"}]},{"interface_id":2,"interface_name":"Realtek HD Audio Mic input","pins":[{"is_input":true,"pin_id":0,"pin_name":"Recording Control"}]},{"interface_id":3,"interface_name":"Realtek HD Audio Line input","pins":[{"is_input":true,"pin_id":0,"pin_name":"Recording Control"}]},{"interface_id":4,"interface_name":"Realtek HD Digital input","pins":[{"is_input":true,"pin_id":0,"pin_name":"Capture"}]},{"interface_id":5,"interface_name":"Realtek HD Audio Stereo input","pins":[{"is_input":true,"pin_id":0,"pin_name":"Recording Control"}]}],"wavert_supported":false},{"device_id":1,"device_name":"M-Audio FW Audiophile","device_type": 1,"interfaces":[{"interface_id":0,"interface_name":"FW AP Multi","pins":[{"is_input":false,"pin_id":0,"pin_name":"Output"},{"is_input":true,"pin_id":1,"pin_name":"Input"}]},{"interface_id":1,"interface_name":"FW AP 1/2","pins":[{"is_input":false,"pin_id":0,"pin_name":"Output"},{"is_input":true,"pin_id":1,"pin_name":"Input"}]},{"interface_id":2,"interface_name":"FW AP SPDIF","pins":[{"is_input":false,"pin_id":0,"pin_name":"Output"},{"is_input":true,"pin_id":1,"pin_name":"Input"}]},{"interface_id":3,"interface_name":"FW AP 3/4","pins":[{"is_input":false,"pin_id":0,"pin_name":"Output"}]}],"wavert_supported":false},{"device_id":2,"device_name":"Virtual Audio Cable","device_type": 2,"interfaces":[{"interface_id":0,"interface_name":"Virtual Cable 2","pins":[{"is_input":true,"pin_id":0,"pin_name":"Capture"},{"is_input":false,"pin_id":1,"pin_name":"Output"}]},{"interface_id":1,"interface_name":"Virtual Cable 1","pins":[{"is_input":true,"pin_id":0,"pin_name":"Capture"},{"is_input":false,"pin_id":1,"pin_name":"Output"}]}],"wavert_supported":false},{"device_id":3,"device_name":"WebCamDV WDM Audio Capture","device_type": 3,"interfaces":[{"interface_id":0,"interface_name":"WebCamDV Audio","pins":[{"is_input":true,"pin_id":0,"pin_name":"Recording Control"},{"is_input":false,"pin_id":1,"pin_name":"Volume Control"}]}],"wavert_supported":false}];
return response;
@@ -823,6 +835,9 @@
this.IsMyNetworkWireless = IsMyNetworkWireless;
this.SetNetworkTestScore = SetNetworkTestScore;
this.GetNetworkTestScore = GetNetworkTestScore;
+ this.SetLatencyTestBlocked = SetLatencyTestBlocked;
+ this.isLatencyTestBlocked = isLatencyTestBlocked;
+ this.GetLastLatencyTestTimes = GetLastLatencyTestTimes;
this.RegisterQuitCallback = RegisterQuitCallback;
this.LeaveSessionAndMinimize = LeaveSessionAndMinimize;
this.GetAutoStart = GetAutoStart;
diff --git a/web/app/assets/javascripts/findSession.js b/web/app/assets/javascripts/findSession.js
index df765fb3a..98db4761f 100644
--- a/web/app/assets/javascripts/findSession.js
+++ b/web/app/assets/javascripts/findSession.js
@@ -85,6 +85,9 @@
context.JK.bindHoverEvents();
$ssSpinner.hide();
});
+
+ // context.JK.bindInstrumentHover($(CATEGORY.ACTIVE.id));
+ // context.JK.bindInstrumentHover($(CATEGORY.SCHEDULED.id));
}
/***************** ACTIVE SESSIONS *****************/
diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js
index 3c1e95027..ff031af99 100644
--- a/web/app/assets/javascripts/jam_rest.js
+++ b/web/app/assets/javascripts/jam_rest.js
@@ -503,6 +503,19 @@
});
}
+ function updateNetworkTesting(options) {
+ var id = getId(options);
+
+ return $.ajax({
+ type: "POST",
+ dataType: "json",
+ contentType: 'application/json',
+ url: "/api/users/" + id + "/is_network_testing",
+ data: JSON.stringify(options),
+ processData: false
+ });
+ }
+
function updateAvatar(options) {
var id = getId(options);
@@ -1222,6 +1235,7 @@
this.getInstruments = getInstruments;
this.getGenres = getGenres;
this.updateUdpReachable = updateUdpReachable;
+ this.updateNetworkTesting = updateNetworkTesting;
this.updateAvatar = updateAvatar;
this.deleteAvatar = deleteAvatar;
this.getFilepickerPolicy = getFilepickerPolicy;
diff --git a/web/app/assets/javascripts/jamkazam.js b/web/app/assets/javascripts/jamkazam.js
index 08d79d34a..d8fb6e4aa 100644
--- a/web/app/assets/javascripts/jamkazam.js
+++ b/web/app/assets/javascripts/jamkazam.js
@@ -123,11 +123,11 @@
*/
function ajaxError(jqXHR, textStatus, errorMessage) {
- if (jqXHR.status == 404) {
+ if (jqXHR.status === 404) {
logger.error("Unexpected ajax error: " + textStatus + ", msg:" + errorMessage);
app.notify({title: "Oops!", text: "What you were looking for is gone now."});
}
- else if (jqXHR.status = 422) {
+ else if (jqXHR.status === 422) {
logger.error("Unexpected ajax error: " + textStatus + ", msg: " + errorMessage + ", response: " + jqXHR.responseText);
// present a nicer message
try {
diff --git a/web/app/assets/javascripts/networkTestHelper.js b/web/app/assets/javascripts/networkTestHelper.js
index 0e6d3add8..601ecd860 100644
--- a/web/app/assets/javascripts/networkTestHelper.js
+++ b/web/app/assets/javascripts/networkTestHelper.js
@@ -167,6 +167,35 @@
return lastNetworkFailure;
}
+ function haltScoring() {
+ context.jamClient.SetLatencyTestBlocked(true)
+ rest.updateNetworkTesting({client_id: app.clientId, is_network_testing: true})
+ .fail(function(jqXHR) {
+
+ if(jqXHR.status == 404) {
+ // assume connection is missing
+ app.notifyAlert("Not Connected", "You must be connected to the server to run the network test.")
+ }
+ else {
+ app.notifyServerError(jqXHR, "Unable to tell server that we are beginning the network test")
+ }
+ })
+ }
+
+ function resumeScoring() {
+ context.jamClient.SetLatencyTestBlocked(false)
+ rest.updateNetworkTesting({client_id: app.clientId, is_network_testing: false})
+ .fail(function(jqXHR) {
+ if(jqXHR.status == 404) {
+ // assume connection is missing
+ // do nothing in this case
+ }
+ else {
+ app.notifyServerError(jqXHR, "Unable to tell server that we are ending the network test")
+ }
+ })
+ }
+
function storeLastNetworkFailure(reason, data) {
if (!trackedPass) {
lastNetworkFailure = {reason: reason, data: data};
@@ -471,63 +500,125 @@
}
}
+ function pauseForRecentScoresTime() {
+ var lastScoreTimes = context.jamClient.GetLastLatencyTestTimes()
+
+ console.log(lastScoreTimes)
+
+ return 0;
+
+ var noPause = 0;
+ var longAgo = 1000000;
+ var initiated = lastScoreTimes.initiatied;
+ var requested = lastScoreTimes.requested;
+
+ if(initiated === null || initiated === undefined) {
+ logger.warn("lastScoreTimes.initiated is not set");
+ initiated = longAgo;
+ }
+ if(requested === null || requested === undefined) {
+ logger.warn("lastScoreTimes.requested is not set");
+ requested = longAgo;
+ }
+
+ if(initiated == 0) {
+ logger.debug("lastScoreTimes.initiated is zero");
+ initiated = longAgo;
+ }
+ if(requested == 0) {
+ logger.debug("lastScoreTimes.requested is zero");
+ requested = longAgo;
+ }
+
+ if(initiated < 0) {
+ logger.debug("lastScoreTimes.initiated is less than zero");
+ initiated = longAgo;
+ }
+ if(requested < 0) {
+ logger.debug("lastScoreTimes.requested is less than zero");
+ requested = longAgo;
+ }
+
+ var mostRecentValue = initiated < requested ? initiated : requested;
+
+ if(mostRecentValue > gon.globalftue_network_test_min_wait_since_last_score * 1000) {
+ return noPause; // our last score was past our min wait; so no delay necessary
+ }
+ else {
+ // pause for the remainder of the min wait threshold
+ var remainder = gon.globalftue_network_test_min_wait_since_last_score * 1000 - mostRecentValue;
+
+ if(remainder > 1500) {
+ // we need to update the UI because this is a long time for a mystery pause
+ $startNetworkTestBtn.text('SHORT QUIET PERIOD...')
+ }
+
+ return remainder;
+ }
+ }
function prepareNetworkTest() {
if (scoring) return false;
- logger.info("starting network test");
- resetTestState();
- scoring = true;
- $self.triggerHandler(NETWORK_TEST_START);
- renderStartTest();
- rest.getLatencyTester()
- .done(function (response) {
- // ensure there are no tests ongoing
- serverClientId = response.client_id;
- testSummary.serverClientId = serverClientId;
+ setTimeout(function() {
- logger.info("beginning network test against client_id: " + serverClientId);
+ logger.info("starting network test");
+ resetTestState();
+ scoring = true;
+ $self.triggerHandler(NETWORK_TEST_START);
+ renderStartTest();
+ rest.getLatencyTester()
+ .done(function (response) {
+ // ensure there are no tests ongoing
- primePump()
- .done(function () {
- postPumpRun();
- })
- .fail(function () {
- logger.debug("unable to determine user's network type. primePump failed.")
- context.JK.Banner.showAlert({
- title: 'Unable to Determine Network Type',
- buttons: [
- {name: 'CANCEL', click: function () {
- cancelTest();
- }},
- {name: 'RUN NETWORK TEST ANYWAY', click: function () {
- attemptTestPass();
- ;
- }}
- ],
- html: "We are unable to determine if your computer is connected to your network using WiFi.
" +
- "We strongly advise against running the JamKazam application on a WiFi connection. " +
- "We recommend using a wired Ethernet connection from your computer to your router. " +
- "A WiFi connection is likely to cause significant issues in both latency and audio quality.
"})
- });
- })
- .fail(function (jqXHR) {
- if (jqXHR.status == 404) {
- // means there are no network testers available.
- // we have to skip this part of the UI
- testSummary.final = {reason: 'no_servers'}
- }
- else {
- if (context.JK.isNetworkError(arguments)) {
- testSummary.final = {reason: 'no_network'}
+ serverClientId = response.client_id;
+
+ testSummary.serverClientId = serverClientId;
+
+ logger.info("beginning network test against client_id: " + serverClientId);
+
+ primePump()
+ .done(function () {
+ postPumpRun();
+ })
+ .fail(function () {
+ logger.debug("unable to determine user's network type. primePump failed.")
+ context.JK.Banner.showAlert({
+ title: 'Unable to Determine Network Type',
+ buttons: [
+ {name: 'CANCEL', click: function () {
+ cancelTest();
+ }},
+ {name: 'RUN NETWORK TEST ANYWAY', click: function () {
+ attemptTestPass();
+ ;
+ }}
+ ],
+ html: "We are unable to determine if your computer is connected to your network using WiFi.
" +
+ "We strongly advise against running the JamKazam application on a WiFi connection. " +
+ "We recommend using a wired Ethernet connection from your computer to your router. " +
+ "A WiFi connection is likely to cause significant issues in both latency and audio quality.
"})
+ });
+ })
+ .fail(function (jqXHR) {
+ if (jqXHR.status == 404) {
+ // means there are no network testers available.
+ // we have to skip this part of the UI
+ testSummary.final = {reason: 'no_servers'}
}
else {
- testSummary.final = {reason: 'rest_api_error'}
+ if (context.JK.isNetworkError(arguments)) {
+ testSummary.final = {reason: 'no_network'}
+ }
+ else {
+ testSummary.final = {reason: 'rest_api_error'}
+ }
}
- }
- testFinished();
- })
+ testFinished();
+ })
+ }, pauseForRecentScoresTime())
+
return false;
}
@@ -844,6 +935,8 @@
this.reset = reset;
this.cancel = cancel;
this.getLastNetworkFailure = getLastNetworkFailure;
+ this.haltScoring = haltScoring;
+ this.resumeScoring = resumeScoring;
this.NETWORK_TEST_START = NETWORK_TEST_START;
this.NETWORK_TEST_DONE = NETWORK_TEST_DONE;
diff --git a/web/app/assets/javascripts/sessionList.js b/web/app/assets/javascripts/sessionList.js
index 06d26efa6..d58218dbf 100644
--- a/web/app/assets/javascripts/sessionList.js
+++ b/web/app/assets/javascripts/sessionList.js
@@ -369,7 +369,7 @@
var track = participant.tracks[j];
logger.debug("Find:Finding instruments. Participant tracks:", participant.tracks);
var inst = context.JK.getInstrumentIcon24(track.instrument_id);
- instrumentLogoHtml += '
';
+ instrumentLogoHtml += '
';
}
var id = participant.user.id;
@@ -400,7 +400,7 @@
for (j=0; j < user.instrument_list.length; j++) {
var instrument = user.instrument_list[j];
var inst = context.JK.getInstrumentIcon24(instrument.id);
- instrumentLogoHtml += '
';
+ instrumentLogoHtml += '
';
}
}
diff --git a/web/app/assets/javascripts/utils.js b/web/app/assets/javascripts/utils.js
index b83d901c7..2afdd2f53 100644
--- a/web/app/assets/javascripts/utils.js
+++ b/web/app/assets/javascripts/utils.js
@@ -239,6 +239,9 @@
var instrumentId = $element.attr('data-instrument-id');
if(instrumentId) {
+ if (instrumentId === "null") {
+ instrumentId = "not specified";
+ }
context.JK.hoverBubble($element, instrumentId, options);
}
else {
diff --git a/web/app/assets/javascripts/wizard/gear/step_network_test.js b/web/app/assets/javascripts/wizard/gear/step_network_test.js
index b82a622ae..17f28d06a 100644
--- a/web/app/assets/javascripts/wizard/gear/step_network_test.js
+++ b/web/app/assets/javascripts/wizard/gear/step_network_test.js
@@ -53,11 +53,13 @@
}
function beforeShow() {
+ networkTest.haltScoring();
networkTest.cancel();
updateButtons();
}
function beforeHide() {
+ networkTest.resumeScoring();
networkTest.cancel();
}
diff --git a/web/app/controllers/api_users_controller.rb b/web/app/controllers/api_users_controller.rb
index 955d0677d..57255ab66 100644
--- a/web/app/controllers/api_users_controller.rb
+++ b/web/app/controllers/api_users_controller.rb
@@ -672,8 +672,17 @@ class ApiUsersController < ApiController
def udp_reachable
Connection.transaction do
@connection = Connection.find_by_client_id!(params[:client_id])
- @connection.udp_reachable = params[:udp_reachable]
- @connection.save
+ # deliberately don't updated_at on connection! only heartbeats do that
+ Connection.where(:id => @connection.id).update_all(:udp_reachable => params[:udp_reachable])
+ respond_with_model(@connection)
+ end
+ end
+
+ def is_network_testing
+ Connection.transaction do
+ @connection = Connection.find_by_client_id!(params[:client_id])
+ # deliberately don't updated_at on connection! only heartbeats do that
+ Connection.where(:id => @connection.id).update_all(:is_network_testing => params[:is_network_testing])
respond_with_model(@connection)
end
end
diff --git a/web/config/application.rb b/web/config/application.rb
index 575654d27..6947079af 100644
--- a/web/config/application.rb
+++ b/web/config/application.rb
@@ -248,6 +248,8 @@ if defined?(Bundler)
config.ftue_network_test_packet_size = 60
# number of times that the backend retries before giving up
config.ftue_network_test_backend_retries = 10
+ # amount of time that we want passed until we run the next network test
+ config.ftue_network_test_min_wait_since_last_score = 5
# the maximum amount of allowable latency
config.ftue_maximum_gear_latency = 20
diff --git a/web/config/initializers/gon.rb b/web/config/initializers/gon.rb
index e2b1feb79..567b1e8e0 100644
--- a/web/config/initializers/gon.rb
+++ b/web/config/initializers/gon.rb
@@ -4,4 +4,5 @@ Gon.global.ftue_network_test_backend_retries = Rails.application.config.ftue_net
Gon.global.twitter_public_account = Rails.application.config.twitter_public_account
Gon.global.scoring_get_work_interval = Rails.application.config.scoring_get_work_interval
Gon.global.scoring_get_work_backoff_interval = Rails.application.config.scoring_get_work_backoff_interval
+Gon.global.ftue_network_test_min_wait_since_last_score = Rails.application.config.ftue_network_test_min_wait_since_last_score
Gon.global.env = Rails.env
diff --git a/web/config/routes.rb b/web/config/routes.rb
index 723298515..5bdab1017 100644
--- a/web/config/routes.rb
+++ b/web/config/routes.rb
@@ -297,6 +297,7 @@ SampleApp::Application.routes.draw do
# udp reachable (can stun?)
match '/users/:id/udp_reachable' => 'api_users#udp_reachable', :via => :post
+ match '/users/:id/is_network_testing' => 'api_users#is_network_testing', :via => :post
# social
match '/users/:id/share/session/:provider' => 'api_users#share_session', :via => :get
diff --git a/web/spec/controllers/api_feeds_controller_spec.rb b/web/spec/controllers/api_feeds_controller_spec.rb
index e6b73e23f..78a60b021 100644
--- a/web/spec/controllers/api_feeds_controller_spec.rb
+++ b/web/spec/controllers/api_feeds_controller_spec.rb
@@ -94,6 +94,9 @@ describe ApiFeedsController do
claimed_recording.recording.created_at = 3.days.ago
claimed_recording.recording.save!
+ ams = ActiveMusicSession.find(claimed_recording.recording.music_session.music_session.id)
+ ams.before_destroy
+
get :index, { limit: 1 }
json = JSON.parse(response.body, :symbolize_names => true)
json[:entries].length.should == 1
diff --git a/websocket-gateway/Gemfile b/websocket-gateway/Gemfile
index b0f63d310..06d45c942 100644
--- a/websocket-gateway/Gemfile
+++ b/websocket-gateway/Gemfile
@@ -34,7 +34,7 @@ gem 'rb-readline'
gem 'aasm', '3.0.16'
gem 'carrierwave'
gem 'fog'
-gem 'devise'
+gem 'devise', '3.3.0' # 3.4.0 causes uninitialized constant ActionController::Metal (NameError)
gem 'postgres-copy'
gem 'aws-sdk' #, '1.29.1'
gem 'bugsnag'