305 lines
11 KiB
Ruby
305 lines
11 KiB
Ruby
require 'aasm'
|
|
|
|
module JamRuby
|
|
class Connection < ActiveRecord::Base
|
|
|
|
include HtmlSanitize
|
|
include AASM
|
|
|
|
# client_types
|
|
TYPE_CLIENT = 'client'
|
|
TYPE_BROWSER = 'browser'
|
|
TYPE_LATENCY_TESTER = 'latency_tester'
|
|
CLIENT_TYPES = [TYPE_CLIENT, TYPE_BROWSER, TYPE_LATENCY_TESTER]
|
|
|
|
attr_accessor :joining_session
|
|
|
|
self.primary_key = 'id'
|
|
|
|
belongs_to :user, :class_name => "JamRuby::User"
|
|
belongs_to :music_session, :class_name => "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 :backing_tracks, :class_name => "JamRuby::BackingTrack", :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
|
|
has_one :jamblaster, class_name: "JamRuby::Jamblaster", foreign_key: "client_id"
|
|
|
|
validates :metronome_open, :inclusion => {:in => [true, false]}
|
|
validates :as_musician, :inclusion => {:in => [true, false, nil]}
|
|
validates :client_type, :inclusion => {:in => CLIENT_TYPES}
|
|
validates_numericality_of :last_jam_audio_latency, greater_than: 0, :allow_nil => true
|
|
validate :can_join_music_session, :if => :joining_session?
|
|
validate :user_or_latency_tester_present
|
|
|
|
# this is no longer required with the new no-input profile
|
|
#after_save :require_at_least_one_track_when_in_session, :if => :joining_session?
|
|
after_create :did_create
|
|
after_save :report_add_participant
|
|
|
|
include AASM
|
|
IDLE_STATE = :idle
|
|
CONNECT_STATE = :connected
|
|
STALE_STATE = :stale
|
|
EXPIRED_STATE = :expired
|
|
|
|
aasm do
|
|
state IDLE_STATE, :initial => true
|
|
state CONNECT_STATE
|
|
state STALE_STATE
|
|
state EXPIRED_STATE
|
|
|
|
event :connect do
|
|
transitions :from => IDLE_STATE, :to => CONNECT_STATE
|
|
transitions :from => STALE_STATE, :to => CONNECT_STATE
|
|
end
|
|
event :stale do
|
|
transitions :from => CONNECT_STATE, :to => STALE_STATE
|
|
transitions :from => IDLE_STATE, :to => STALE_STATE
|
|
end
|
|
event :expire, :after => :did_expire do
|
|
transitions :from => CONNECT_STATE, :to => EXPIRED_STATE
|
|
transitions :from => STALE_STATE, :to => EXPIRED_STATE
|
|
transitions :from => IDLE_STATE, :to => EXPIRED_STATE
|
|
end
|
|
end
|
|
|
|
def state_message
|
|
case self.aasm_state.to_sym
|
|
when CONNECT_STATE
|
|
'Connected'
|
|
when STALE_STATE
|
|
'Stale'
|
|
else
|
|
'Idle'
|
|
end
|
|
end
|
|
|
|
def in_session?
|
|
!music_session_id.nil?
|
|
end
|
|
|
|
def in_scoring_timeout?
|
|
scoring_timeout > Time.now
|
|
end
|
|
|
|
def did_expire
|
|
self.destroy
|
|
end
|
|
|
|
def joining_session?
|
|
joining_session
|
|
end
|
|
|
|
def same_network_jamblasters
|
|
# return all jamblasters that are currently connected with the same public IP address (don't include this one though)
|
|
Jamblaster.joins(:connection).where("connections.ip_address = ?", ip_address).where("connections.id != ?", id).limit(100)
|
|
end
|
|
|
|
|
|
def can_join_music_session
|
|
|
|
if music_session_id_changed? and !(music_session_id_was.nil? or music_session_id_was.blank?)
|
|
errors.add(:music_session, ValidationMessages::CANT_JOIN_MULTIPLE_SESSIONS)
|
|
return false
|
|
end
|
|
|
|
if music_session.nil?
|
|
errors.add(:music_session, ValidationMessages::MUSIC_SESSION_MUST_BE_SPECIFIED)
|
|
return false
|
|
end
|
|
|
|
|
|
if as_musician
|
|
unless self.user.musician
|
|
errors.add(:as_musician, ValidationMessages::FAN_CAN_NOT_JOIN_AS_MUSICIAN)
|
|
return false
|
|
end
|
|
|
|
if music_session.musician_access
|
|
if music_session.approval_required
|
|
if !(music_session.music_session.creator.id == user.id ||
|
|
music_session.creator.id == user.id ||
|
|
music_session.invited_musicians.exists?(user.id) ||
|
|
music_session.is_lesson_member?(user) ||
|
|
(music_session.friends_can_join && user.friends?(music_session.creator)))
|
|
errors.add(:approval_required, ValidationMessages::INVITE_REQUIRED)
|
|
return false
|
|
end
|
|
end
|
|
else
|
|
|
|
if !(music_session.music_session.creator.id == user.id ||
|
|
music_session.creator.id == user.id ||
|
|
music_session.invited_musicians.exists?(user.id) ||
|
|
music_session.is_lesson_member?(user) ||
|
|
(music_session.friends_can_join && user.friends?(music_session.creator)))
|
|
errors.add(:musician_access, ValidationMessages::INVITE_REQUIRED)
|
|
return false
|
|
end
|
|
end
|
|
else
|
|
unless self.music_session.fan_access
|
|
# it's someone joining as a fan, and the only way a fan can join is if fan_access is true
|
|
errors.add(:fan_access, ValidationMessages::FANS_CAN_NOT_JOIN)
|
|
return false
|
|
end
|
|
end
|
|
|
|
if music_session.is_recording?
|
|
errors.add(:music_session, ValidationMessages::CANT_JOIN_RECORDING_SESSION)
|
|
end
|
|
|
|
# same logic as Search.scope_schools_together_sessions
|
|
if !self.user.is_platform_instructor
|
|
if self.user.school_id.nil? && !music_session.school_id.nil?
|
|
errors.add(:music_session, ValidationMessages::CANT_JOIN_SCHOOL_SESSION)
|
|
elsif !self.user.school_id.nil? && (self.user.school_id != music_session.school_id)
|
|
errors.add(:music_session, ValidationMessages::CAN_ONLY_JOIN_SAME_SCHOOL_SESSION)
|
|
end
|
|
end
|
|
|
|
if self.user.license_expired?
|
|
errors.add(:music_session, ValidationMessages::LICENSE_EXPIRED)
|
|
end
|
|
|
|
if self.user.license_not_started?
|
|
errors.add(:music_session, ValidationMessages::LICENSE_NOT_STARTED)
|
|
end
|
|
|
|
# unless user.admin?
|
|
# num_sessions = Connection.where(:user_id => user_id)
|
|
# .where(["(music_session_id IS NOT NULL) AND (aasm_state != ?)",EXPIRED_STATE.to_s])
|
|
# .count
|
|
# if 0 < num_sessions
|
|
# errors.add(:music_session, ValidationMessages::CANT_JOIN_MULTIPLE_SESSIONS)
|
|
# return false;
|
|
# end
|
|
# end
|
|
|
|
return true
|
|
end
|
|
|
|
|
|
# decides if a given user can access this client with p2p messaging
|
|
# the answer is yes if the user is in the same music session
|
|
def access_p2p?(user)
|
|
return self.music_session.users.exists?(user)
|
|
end
|
|
|
|
def did_create
|
|
# self.user.update_lat_lng(self.ip_address) if self.user && self.ip_address
|
|
end
|
|
|
|
def report_add_participant
|
|
if self.music_session_id_changed? &&
|
|
self.music_session.present? &&
|
|
self.connected? &&
|
|
self.as_musician? &&
|
|
0 < (count = self.music_session.connected_participant_count)
|
|
#GoogleAnalyticsEvent.report_session_participant(count)
|
|
end
|
|
true
|
|
end
|
|
|
|
def join_the_session(music_session, as_musician, tracks, user, audio_latency, client_role = nil, parent_client_id = nil, videos=nil)
|
|
self.music_session_id = music_session.id
|
|
self.as_musician = as_musician == true # this is deliberate; otherwise we create a warning in one our tests that passes 'blarg' (rails warning about casting strings to false)
|
|
self.joining_session = true
|
|
self.joined_session_at = Time.now
|
|
self.client_role = client_role
|
|
self.parent_client_id = parent_client_id
|
|
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
|
|
if as_musician
|
|
user.update_addr_loc(self, User::JAM_REASON_JOIN)
|
|
user.update_audio_latency(self, audio_latency) if audio_latency # try not to let a previously recorded value get nil'ed
|
|
end
|
|
end
|
|
|
|
def associate_tracks(tracks)
|
|
self.tracks.clear()
|
|
unless tracks.nil?
|
|
tracks.each do |track|
|
|
t = Track.new
|
|
t.instrument = Instrument.find_by_id(track["instrument_id"]) || Instrument.find('acoustic guitar')
|
|
t.connection = self
|
|
t.sound = track["sound"]
|
|
t.client_track_id = track["client_track_id"]
|
|
t.client_resource_id = track["client_resource_id"]
|
|
t.save # todo what if it fails?
|
|
self.tracks << t
|
|
end
|
|
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
|
|
|
|
# this will set a connections's _locidispid = 0 if there are no geoiplocations/blocks that match their IP address, or if there are no JamIsps that match the IP address
|
|
# otherwise, locidispid will be updated to the correct new value.
|
|
# updates all connections's locidispids
|
|
table_suffix = use_copied ? '_copied' : ''
|
|
Connection.connection.execute("UPDATE connections SET locidispid = COALESCE((SELECT geolocs.locid as geolocid FROM geoipblocks#{table_suffix} as geoblocks INNER JOIN geoiplocations#{table_suffix} as geolocs ON geoblocks.locid = geolocs.locid WHERE geoblocks.geom && ST_MakePoint(addr, 0) AND addr BETWEEN geoblocks.beginip AND geoblocks.endip LIMIT 1) * 1000000::bigint +(SELECT coid FROM jamisp#{table_suffix} as jisp WHERE geom && ST_MakePoint(addr, 0) AND addr BETWEEN beginip AND endip LIMIT 1), 0) ").check
|
|
end
|
|
|
|
def self.after_maxmind_import
|
|
update_locidispids
|
|
end
|
|
|
|
def self.stats
|
|
stats = {}
|
|
|
|
CLIENT_TYPES.each do |type|
|
|
stats[type] = 0
|
|
end
|
|
|
|
Connection.select('count(client_type) AS client_type_count, client_type').group('client_type').all.each do |result|
|
|
stats[result['client_type']] = result['client_type_count'].to_i
|
|
end
|
|
|
|
result = Connection.select('count(id) AS total, count(CASE WHEN scoring_timeout > NOW() THEN 1 ELSE null END) AS scoring_timeout_count, count(music_session_id) AS in_session, count(as_musician) AS musicians, count(CASE WHEN udp_reachable THEN 1 ELSE null END) AS udp_reachable_count, count(CASE WHEN is_network_testing THEN 1 ELSE null END) AS is_network_testing_count')[0]
|
|
|
|
stats['count'] = result['total'].to_i
|
|
stats['scoring_timeout'] = result['scoring_timeout_count'].to_i
|
|
stats['in_session'] = result['in_session'].to_i
|
|
stats['musicians'] = result['musicians'].to_i
|
|
stats['udp_reachable'] = result['udp_reachable_count'].to_i
|
|
stats['networking_testing'] = result['is_network_testing_count'].to_i
|
|
|
|
stats
|
|
end
|
|
|
|
private
|
|
|
|
def require_at_least_one_track_when_in_session
|
|
if tracks.count == 0
|
|
errors.add(:tracks, ValidationMessages::SELECT_AT_LEAST_ONE)
|
|
end
|
|
end
|
|
|
|
def user_or_latency_tester_present
|
|
if user.nil? && client_type != TYPE_LATENCY_TESTER
|
|
puts client_type
|
|
errors.add(:connection, ValidationMessages::USER_OR_LATENCY_TESTER_PRESENT)
|
|
end
|
|
end
|
|
|
|
end
|
|
end
|