2014-05-27 03:52:59 +00:00
require 'iso-639'
2012-10-03 03:50:23 +00:00
module JamRuby
class MusicSession < ActiveRecord :: Base
2014-08-19 01:41:44 +00:00
include HtmlSanitize
html_sanitize strict : [ :name , :description ]
2014-01-21 14:51:03 +00:00
2014-06-17 22:16:49 +00:00
@@log = Logging . logger [ MusicSession ]
2014-05-15 17:22:21 +00:00
NO_RECURRING = 'once'
RECURRING_WEEKLY = 'weekly'
RECURRING_MODES = [ NO_RECURRING , RECURRING_WEEKLY ]
2014-07-30 20:11:03 +00:00
attr_accessor :legal_terms , :language_description , :access_description , :scheduling_info_changed
2014-05-29 16:32:22 +00:00
2014-06-15 19:49:43 +00:00
attr_accessor :approved_rsvps , :open_slots , :pending_invitations
2014-05-06 22:50:41 +00:00
self . table_name = " music_sessions "
2014-03-09 13:38:46 +00:00
2014-05-06 13:34:38 +00:00
self . primary_key = 'id'
2012-10-25 12:11:10 +00:00
2014-05-06 21:17:26 +00:00
belongs_to :creator , :class_name = > 'JamRuby::User' , :foreign_key = > :user_id , :inverse_of = > :music_session_histories
2014-05-06 13:34:38 +00:00
2014-05-06 21:17:26 +00:00
belongs_to :band , :class_name = > 'JamRuby::Band' , :foreign_key = > :band_id , :inverse_of = > :music_sessions
2014-05-06 13:34:38 +00:00
2014-05-06 21:17:26 +00:00
belongs_to :active_music_session , :class_name = > 'JamRuby::ActiveMusicSession' , foreign_key : :music_session_id
2014-05-06 13:34:38 +00:00
has_many :music_session_user_histories , :class_name = > " JamRuby::MusicSessionUserHistory " , :foreign_key = > " music_session_id " , :dependent = > :delete_all
has_many :comments , :class_name = > " JamRuby::MusicSessionComment " , :foreign_key = > " music_session_id "
2014-05-24 07:58:47 +00:00
has_many :session_info_comments , :class_name = > " JamRuby::SessionInfoComment " , :foreign_key = > " music_session_id "
2014-05-06 13:34:38 +00:00
has_many :likes , :class_name = > " JamRuby::MusicSessionLiker " , :foreign_key = > " session_id "
has_many :plays , :class_name = > " JamRuby::PlayablePlay " , :as = > :playable , :dependent = > :destroy
2014-07-14 21:43:46 +00:00
has_one :share_token , :class_name = > " JamRuby::ShareToken " , :inverse_of = > :shareable , :foreign_key = > 'shareable_id'
has_one :feed , :class_name = > " JamRuby::Feed " , :inverse_of = > :music_session , :foreign_key = > 'music_session_id' , :dependent = > :destroy
2014-05-06 21:17:26 +00:00
belongs_to :genre , :class_name = > " JamRuby::Genre " , :inverse_of = > :music_sessions , :foreign_key = > 'genre_id'
2014-05-06 13:34:38 +00:00
has_many :join_requests , :foreign_key = > " music_session_id " , :inverse_of = > :music_session , :class_name = > " JamRuby::JoinRequest " , :foreign_key = > " music_session_id "
has_many :invitations , :foreign_key = > " music_session_id " , :inverse_of = > :music_session , :class_name = > " JamRuby::Invitation " , :foreign_key = > " music_session_id "
has_many :invited_musicians , :through = > :invitations , :class_name = > " JamRuby::User " , :foreign_key = > " receiver_id " , :source = > :receiver
has_many :fan_invitations , :foreign_key = > " music_session_id " , :inverse_of = > :music_session , :class_name = > " JamRuby::FanInvitation " , :foreign_key = > " music_session_id "
has_many :invited_fans , :through = > :fan_invitations , :class_name = > " JamRuby::User " , :foreign_key = > " receiver_id " , :source = > :receiver
2014-05-19 15:35:38 +00:00
has_many :rsvp_slots , :class_name = > " JamRuby::RsvpSlot " , :foreign_key = > " music_session_id " , :dependent = > :destroy
2014-05-26 05:35:57 +00:00
has_many :music_notations , :class_name = > " JamRuby::MusicNotation " , :foreign_key = > " music_session_id "
2013-07-23 20:20:39 +00:00
2014-05-06 21:17:26 +00:00
validates :genre , :presence = > true
2013-07-26 08:07:24 +00:00
validates :description , :presence = > true , :no_profanity = > true
2012-11-16 02:50:03 +00:00
validates :fan_chat , :inclusion = > { :in = > [ true , false ] }
validates :fan_access , :inclusion = > { :in = > [ true , false ] }
validates :approval_required , :inclusion = > { :in = > [ true , false ] }
validates :musician_access , :inclusion = > { :in = > [ true , false ] }
2014-07-08 18:34:03 +00:00
validates :is_unstructured_rsvp , :inclusion = > { :in = > [ true , false ] }
2013-06-11 01:25:42 +00:00
validates :legal_terms , :inclusion = > { :in = > [ true ] } , :on = > :create
2012-11-30 15:23:43 +00:00
validates :creator , :presence = > true
2014-07-11 22:21:55 +00:00
validates :timezone , presence : true , if : Proc . new { | session | session . scheduled_start }
validates :scheduled_duration , presence : true , if : Proc . new { | session | session . scheduled_start }
2012-11-30 15:23:43 +00:00
validate :creator_is_musician
2014-07-11 22:21:55 +00:00
validate :validate_timezone
2012-11-30 15:23:43 +00:00
2014-05-06 13:34:38 +00:00
before_create :generate_share_token
before_create :add_to_feed
2014-07-11 22:21:55 +00:00
#before_save :update_scheduled_start
2014-07-30 20:11:03 +00:00
before_save :check_scheduling_info_changed
2014-03-06 02:30:40 +00:00
2014-05-06 13:34:38 +00:00
SHARE_TOKEN_LENGTH = 8
2014-03-06 04:26:17 +00:00
2014-05-06 13:34:38 +00:00
SEPARATOR = '|'
2014-03-06 04:26:17 +00:00
2014-07-30 20:11:03 +00:00
def check_scheduling_info_changed
@scheduling_info_changed = scheduled_start_changed?
true
end
2014-05-06 13:34:38 +00:00
def add_to_feed
feed = Feed . new
feed . music_session = self
2014-02-06 21:02:04 +00:00
end
2014-07-11 22:21:55 +00:00
def update_scheduled_start
# it's very important that this only run if timezone changes, or scheduled_start changes
if self . scheduled_start && ( self . scheduled_start_changed? || self . timezone_changed? )
self . scheduled_start = MusicSession . parse_scheduled_start ( self . scheduled_start , self . timezone )
end
end
2014-05-06 13:34:38 +00:00
def comment_count
self . comments . size
2014-01-21 14:51:03 +00:00
end
2014-01-12 17:51:06 +00:00
2014-06-03 05:10:25 +00:00
# copies all relevant info for the recurring session
def copy
MusicSession . transaction do
# copy base music_session data
new_session = MusicSession . new
new_session . description = self . description
new_session . user_id = self . user_id
new_session . band_id = self . band_id
new_session . fan_access = self . fan_access
2014-07-11 06:23:10 +00:00
new_session . scheduled_start = self . scheduled_start + 1 . week
2014-06-03 05:10:25 +00:00
new_session . scheduled_duration = self . scheduled_duration
new_session . musician_access = self . musician_access
new_session . approval_required = self . approval_required
new_session . fan_chat = self . fan_chat
new_session . genre_id = self . genre_id
new_session . legal_policy = self . legal_policy
new_session . language = self . language
new_session . name = self . name
new_session . recurring_mode = self . recurring_mode
new_session . timezone = self . timezone
new_session . open_rsvps = self . open_rsvps
2014-07-11 06:23:10 +00:00
new_session . is_unstructured_rsvp = self . is_unstructured_rsvp
new_session . legal_terms = true
2014-06-05 03:17:45 +00:00
2014-06-04 05:47:28 +00:00
# copy rsvp_slots, rsvp_requests, and rsvp_requests_rsvp_slots
2014-06-03 05:10:25 +00:00
RsvpSlot . find_each ( :conditions = > " music_session_id = ' #{ self . id } ' " ) do | slot |
new_slot = RsvpSlot . new
new_slot . instrument_id = slot . instrument_id
new_slot . proficiency_level = slot . proficiency_level
2014-07-30 01:52:08 +00:00
new_slot . is_unstructured_rsvp = slot . is_unstructured_rsvp
2014-06-05 04:55:15 +00:00
new_session . rsvp_slots << new_slot
2014-06-04 05:47:28 +00:00
# get the request for this slot that was approved (should only be ONE)
rsvp_request_slot = RsvpRequestRsvpSlot . where ( " chosen = true AND rsvp_slot_id = ? " , slot . id ) . first
unless rsvp_request_slot . nil?
rsvp = RsvpRequest . find_by_id ( rsvp_request_slot . rsvp_request_id )
new_rsvp = RsvpRequest . new
new_rsvp . user_id = rsvp . user_id
new_rsvp_req_slot = RsvpRequestRsvpSlot . new
2014-06-05 04:55:15 +00:00
new_rsvp_req_slot . rsvp_request = new_rsvp
new_rsvp_req_slot . rsvp_slot = new_slot
2014-06-04 05:47:28 +00:00
new_rsvp_req_slot . chosen = true
2014-06-05 03:17:45 +00:00
# if this slot was not chosen, try to get any RSVPs that were 1-time cancellations and copy those
else
rejected_req_slots = RsvpRequestRsvpSlot . where ( " (chosen = false OR chosen is null) AND rsvp_slot_id = ? " , slot . id ) . order ( " created_at ASC " )
rejected_req_slots . each do | req_slot |
# get RsvpRequest corresponding to this RsvpRequestRsvpSlot
rsvp = RsvpRequest . find_by_id ( req_slot . rsvp_request_id )
# if the RSVP was canceled (but all future sessions were NOT canceled), then copy this one and break
if rsvp . canceled && ! rsvp . cancel_all
new_rsvp = RsvpRequest . new
new_rsvp . user_id = rsvp . user_id
new_rsvp_req_slot = RsvpRequestRsvpSlot . new
2014-06-05 04:55:15 +00:00
new_rsvp_req_slot . rsvp_request = new_rsvp
new_rsvp_req_slot . rsvp_slot = new_slot
2014-06-05 03:17:45 +00:00
new_rsvp_req_slot . chosen = true
break
end
2014-06-04 05:47:28 +00:00
end
end
2014-06-03 05:10:25 +00:00
end
# copy music_notations
MusicNotation . find_each ( :conditions = > " music_session_id = ' #{ self . id } ' " ) do | notation |
new_notation = MusicNotation . new
new_notation . user_id = notation . user_id
2014-06-05 04:55:15 +00:00
new_notation . music_session = new_session
2014-06-03 05:10:25 +00:00
new_notation . file_url = notation . file_url
2014-07-11 06:23:10 +00:00
new_notation . file_name = notation . file_name
2014-06-03 05:10:25 +00:00
new_notation . size = notation . size
2014-06-05 04:55:15 +00:00
new_session . music_notations << new_notation
2014-06-03 05:10:25 +00:00
end
2014-07-11 06:23:10 +00:00
new_session . save!
2014-06-03 05:10:25 +00:00
# mark the next session as scheduled
self . next_session_scheduled = true
self . save
end
end
2014-05-06 13:34:38 +00:00
def grouped_tracks
tracks = [ ]
self . music_session_user_histories . each do | msuh |
user = User . find ( msuh . user_id )
t = Track . new
t . musician = user
t . instrument_ids = [ ]
# this treats each track as a "user", which has 1 or more instruments in the session
unless msuh . instruments . blank?
instruments = msuh . instruments . split ( SEPARATOR )
instruments . each do | instrument |
if ! t . instrument_ids . include? instrument
t . instrument_ids << instrument
end
end
end
tracks << t
2014-01-05 03:47:23 +00:00
end
2014-05-06 13:34:38 +00:00
tracks
2014-01-05 03:47:23 +00:00
end
2014-05-24 07:58:47 +00:00
def can_join? user , as_musician
if as_musician
unless user . musician
return false # "a fan can not join a music session as a musician"
end
if self . musician_access
if self . approval_required
return self . invited_musicians . exists? ( user )
else
return true
end
else
# the creator can always join, and the invited users can join
return self . creator == user || self . invited_musicians . exists? ( user )
end
else
# it's a fan, and the only way a fan can join is if fan_access is true
self . fan_access
end
end
2014-05-06 13:34:38 +00:00
def self . index ( current_user , user_id , band_id = nil , genre = nil )
hide_private = false
if current_user . id != user_id
hide_private = false # TODO: change to true once public flag exists
2012-11-30 15:23:43 +00:00
end
2012-12-06 01:56:12 +00:00
query = MusicSession
2014-05-06 13:34:38 +00:00
. joins (
2012-12-05 19:10:58 +00:00
%Q{
2012-12-06 01:56:12 +00:00
LEFT OUTER JOIN
2014-05-06 13:34:38 +00:00
music_sessions_user_history
2012-12-06 01:56:12 +00:00
ON
2014-05-06 22:50:41 +00:00
music_sessions . id = music_sessions_user_history . music_session_id
2012-12-06 01:56:12 +00:00
}
)
2014-05-06 13:34:38 +00:00
. where (
2014-03-06 02:30:40 +00:00
%Q{
2014-05-06 22:50:41 +00:00
music_sessions . user_id = '#{user_id}'
2012-12-06 01:56:12 +00:00
}
)
2014-03-06 02:30:40 +00:00
2014-05-06 13:34:38 +00:00
#query = query.where("public = false") unless !hide_private
2014-05-06 22:50:41 +00:00
query = query . where ( " music_sessions.band_id = ' #{ band_id } " ) unless band_id . nil?
2014-07-02 13:57:18 +00:00
query = query . where ( " music_sessions.genres like '% #{ genre } %' " ) unless genre . nil?
2012-12-06 01:56:12 +00:00
return query
2012-11-30 15:23:43 +00:00
end
2012-11-02 06:51:52 +00:00
2014-05-21 19:11:50 +00:00
def self . scheduled user
2014-07-10 16:27:26 +00:00
query = MusicSession . where ( " music_sessions.canceled = FALSE " )
query = query . where ( " music_sessions.user_id = ' #{ user . id } ' " )
2014-07-12 03:44:49 +00:00
query = query . where ( " music_sessions.scheduled_start IS NULL OR music_sessions.scheduled_start > NOW() - '12 hour'::INTERVAL " )
2014-05-21 19:11:50 +00:00
query = query . order ( " music_sessions.scheduled_start ASC " )
2014-06-24 15:33:31 +00:00
query
2014-05-21 19:11:50 +00:00
end
2014-07-12 13:55:58 +00:00
# if only_approved is set, then only return sessions where the current user has been chosen
def self . scheduled_rsvp ( user , only_approved = false )
filter_approved = only_approved ? 'AND rrrs.chosen = true' : ''
2014-07-12 03:44:49 +00:00
MusicSession . where ( %Q{ music_sessions.canceled = FALSE AND
( music_sessions . scheduled_start is NULL OR music_sessions . scheduled_start > NOW ( ) - '4 hour' :: INTERVAL ) AND
music_sessions . id in (
2014-06-29 13:54:51 +00:00
select distinct ( rs . music_session_id )
from rsvp_slots rs
where rs . id in (
select rrrs . rsvp_slot_id
from rsvp_requests rr
inner join rsvp_requests_rsvp_slots rrrs on rr . id = rrrs . rsvp_request_id
2014-07-12 13:55:58 +00:00
where rr . user_id = '#{user.id}' #{filter_approved}
2014-06-29 13:54:51 +00:00
)
) }
2014-07-11 22:21:55 +00:00
) . order ( :scheduled_start )
2014-06-29 13:54:51 +00:00
end
2014-05-19 15:35:38 +00:00
def self . create user , options
band = Band . find ( options [ :band ] ) unless options [ :band ] . nil?
2014-05-15 15:49:06 +00:00
ms = MusicSession . new
2014-05-19 15:35:38 +00:00
ms . name = options [ :name ]
ms . description = options [ :description ]
ms . genre_id = ( options [ :genres ] . length > 0 ? options [ :genres ] [ 0 ] : nil ) if options [ :genres ]
ms . musician_access = options [ :musician_access ]
ms . approval_required = options [ :approval_required ]
ms . fan_access = options [ :fan_access ]
ms . fan_chat = options [ :fan_chat ]
2014-05-15 15:49:06 +00:00
ms . band = band
2014-05-19 15:35:38 +00:00
ms . legal_policy = options [ :legal_policy ]
ms . language = options [ :language ]
2014-06-18 22:15:56 +00:00
ms . scheduled_duration = options [ :duration ] . to_i * 1 . minutes if options [ :duration ]
2014-05-19 15:35:38 +00:00
ms . recurring_mode = options [ :recurring_mode ] if options [ :recurring_mode ]
2014-06-19 18:43:21 +00:00
ms . timezone = options [ :timezone ] if options [ :timezone ]
2014-05-15 15:49:06 +00:00
ms . legal_terms = true
2014-07-03 04:51:54 +00:00
ms . open_rsvps = options [ :open_rsvps ] if options [ :open_rsvps ]
2014-05-15 15:49:06 +00:00
ms . creator = user
2014-07-08 18:34:03 +00:00
ms . is_unstructured_rsvp = options [ :isUnstructuredRsvp ] if options [ :isUnstructuredRsvp ]
2014-07-11 22:21:55 +00:00
ms . scheduled_start = parse_scheduled_start ( options [ :start ] , options [ :timezone ] ) if options [ :start ] && options [ :timezone ]
2014-05-15 15:49:06 +00:00
ms . save
2014-05-19 15:35:38 +00:00
unless ms . errors . any?
ms . reload
2014-06-18 22:15:56 +00:00
rsvp_slot_ids = [ ]
self_rsvp_slot_ids = [ ]
options [ :rsvp_slots ] . each do | rs |
rsvp = RsvpSlot . new
rsvp . instrument = Instrument . find ( rs [ :instrument_id ] )
rsvp . proficiency_level = rs [ :proficiency_level ]
rsvp . music_session = ms
rsvp . save
ms . rsvp_slots << rsvp
2014-06-19 18:43:21 +00:00
if rs [ :approve ] == true
2014-06-18 22:15:56 +00:00
self_rsvp_slot_ids . push rsvp . id
else
rsvp_slot_ids . push rsvp . id
end
end if options [ :rsvp_slots ]
2014-06-19 18:43:21 +00:00
RsvpRequest . create ( { session_id : ms . id , rsvp_slots : self_rsvp_slot_ids , :autoapprove = > true } , user )
2014-05-19 15:35:38 +00:00
options [ :invitations ] . each do | invite_id |
invitation = Invitation . new
receiver = User . find ( invite_id )
invitation . sender = user
invitation . receiver = receiver
invitation . music_session = ms
invitation . save
ms . invitations << invitation
2014-06-18 22:15:56 +00:00
Notification . send_scheduled_session_invitation ( ms , receiver )
2014-05-19 15:35:38 +00:00
end if options [ :invitations ]
options [ :music_notations ] . each do | notation_id |
notation = MusicNotation . find ( notation_id )
notation . music_session = ms
notation . save
ms . music_notations << notation
end if options [ :music_notations ]
ms . save
end
ms
2014-05-15 15:49:06 +00:00
end
2014-05-22 09:49:35 +00:00
def self . update user , options
music_session = MusicSession . find ( options [ :id ] )
if music_session . creator == current_user
Notification . send_scheduled_session_cancelled music_session
music_session . destroy
respond_with responder : ApiResponder , :status = > 204
else
render :json = > { :message = > ValidationMessages :: PERMISSION_VALIDATION_ERROR } , :status = > 404
end
end
2014-05-06 13:34:38 +00:00
def unique_users
User
. joins ( :music_session_user_histories )
. group ( " users.id " )
. order ( " users.id " )
2014-05-06 21:17:26 +00:00
. where ( %Q{ music_sessions_user_history.music_session_id = ' #{ id } ' } )
2012-11-02 06:51:52 +00:00
end
2014-05-06 13:34:38 +00:00
# returns one user history per user, with instruments all crammed together, and with total duration
def unique_user_histories
MusicSessionUserHistory
. joins ( :user )
. select ( " STRING_AGG(instruments, '|') AS total_instruments,
SUM ( date_part ( 'epoch' , COALESCE ( music_sessions_user_history . session_removed_at , music_sessions_user_history . created_at ) - music_sessions_user_history . created_at ) ) AS total_duration ,
music_sessions_user_history . user_id , music_sessions_user_history . music_session_id , users . first_name , users . last_name , users . photo_url " )
. group ( " music_sessions_user_history.user_id, music_sessions_user_history.music_session_id, users.first_name, users.last_name, users.photo_url " )
. order ( " music_sessions_user_history.user_id " )
2014-05-06 21:17:26 +00:00
. where ( %Q{ music_sessions_user_history.music_session_id = ' #{ id } ' } )
2012-11-02 06:51:52 +00:00
end
2014-05-06 13:34:38 +00:00
def duration_minutes
end_time = self . session_removed_at || Time . now
( end_time - self . created_at ) / 60 . 0
2012-11-02 06:51:52 +00:00
end
2014-05-06 13:34:38 +00:00
def music_session_user_histories
@msuh || = JamRuby :: MusicSessionUserHistory
2014-05-06 21:17:26 +00:00
. where ( :music_session_id = > self . id )
2014-05-06 13:34:38 +00:00
. order ( 'created_at DESC' )
2012-10-03 03:50:23 +00:00
end
2013-11-03 20:55:55 +00:00
2014-05-06 13:34:38 +00:00
def comments
@comments || = JamRuby :: MusicSessionComment
2014-05-06 21:17:26 +00:00
. where ( :music_session_id = > self . id )
2014-05-06 13:34:38 +00:00
. order ( 'created_at DESC' )
2014-01-06 20:35:35 +00:00
end
2014-05-06 13:34:38 +00:00
def likes
@likes || = JamRuby :: MusicSessionLiker
. where ( :music_session_id = > self . music_session_id )
2013-11-03 20:55:55 +00:00
end
2014-05-06 13:34:38 +00:00
# these are 'users that are a part of this session'
# which means are currently in the music_session, or, rsvp'ed, or creator
def part_of_session? user
# XXX check RSVP'ed
2014-05-06 21:17:26 +00:00
user == self . creator || ( active_music_session ? active_music_session . users . exists? ( user ) : false )
2014-01-05 03:47:23 +00:00
end
2014-05-06 13:34:38 +00:00
def is_over?
2014-05-06 21:17:26 +00:00
active_music_session . nil?
2013-11-03 20:55:55 +00:00
end
2014-05-06 13:34:38 +00:00
def has_mount?
2014-07-30 02:42:35 +00:00
! active_music_session . nil? && ! active_music_session . mount . nil?
2013-11-03 20:55:55 +00:00
end
2014-07-10 16:38:17 +00:00
def can_cancel? user
self . creator == user
2014-05-24 07:58:47 +00:00
end
def legal_policy_url
2014-06-30 21:24:56 +00:00
# TODO: move to DB or config file or helper
2014-07-21 05:05:07 +00:00
case legal_policy . downcase
2014-05-24 07:58:47 +00:00
when " standard "
2014-07-21 05:05:07 +00:00
return " session-legal-policies/standard "
2014-05-24 07:58:47 +00:00
when " creative "
2014-07-21 05:05:07 +00:00
return " session-legal-policies/creativecommons "
2014-05-24 07:58:47 +00:00
when " offline "
2014-07-21 05:05:07 +00:00
return " session-legal-policies/offline "
2014-05-24 07:58:47 +00:00
when " jamtracks "
2014-07-21 05:05:07 +00:00
return " session-legal-policies/jamtracks "
2014-05-24 07:58:47 +00:00
else
return " "
2014-05-26 05:35:57 +00:00
end
end
2014-05-27 03:52:59 +00:00
def language_description
2014-05-29 05:28:40 +00:00
if self . language . blank?
2014-07-02 13:57:18 +00:00
self . language = " en "
2014-05-29 05:28:40 +00:00
end
iso639Details = ISO_639 . find_by_code ( self . language )
unless iso639Details . blank?
return iso639Details . english_name
else
return " English "
end
2014-05-27 03:52:59 +00:00
end
2014-07-01 04:36:06 +00:00
def scheduled_end_time
end
2014-07-12 01:54:54 +00:00
def timezone_id
MusicSession . split_timezone ( timezone ) [ 0 ]
end
2014-06-09 02:43:07 +00:00
def timezone_description
2014-07-12 01:54:54 +00:00
MusicSession . split_timezone ( timezone ) [ 1 ]
2014-06-09 02:43:07 +00:00
end
def musician_access_description
if self . musician_access && self . approval_required
" Musicians may join by approval "
elsif self . musician_access && ! self . approval_required
" Musicians may join at will "
elsif ! self . musician_access && ! self . approval_required
" Only RSVP musicians may join "
2014-05-27 04:32:14 +00:00
end
2014-06-09 02:43:07 +00:00
end
2014-05-27 04:32:14 +00:00
2014-06-09 02:43:07 +00:00
def fan_access_description
if self . fan_access && self . fan_chat
" Fans may listen, chat with the band "
elsif self . fan_access && ! self . fan_chat
" Fans may listen, chat with each other "
elsif ! self . fan_access && ! self . fan_chat
" Fans may not listen to session "
2014-05-27 04:32:14 +00:00
end
2014-06-09 02:43:07 +00:00
end
2014-05-27 04:32:14 +00:00
2014-06-09 02:43:07 +00:00
def access_description
2014-07-10 20:22:00 +00:00
" #{ musician_access_description } . #{ fan_access_description } . "
2014-05-27 04:32:14 +00:00
end
2014-05-28 02:35:26 +00:00
# retrieve users that have approved RSVPs
2014-05-26 05:35:57 +00:00
def approved_rsvps
2014-08-19 19:23:41 +00:00
User . find_by_sql ( %Q{ select distinct ON(u.id) u.id, u.photo_url, u.first_name, u.last_name, u.last_jam_audio_latency, json_agg(ii.id) as instrument_ids, json_agg(ii.description) as instrument_descriptions, json_agg(rs.proficiency_level) as instrument_proficiencies, json_agg(rr.id) as rsvp_request_ids
2014-06-19 18:43:21 +00:00
from rsvp_slots rs
inner join rsvp_requests_rsvp_slots rrrs on rrrs . rsvp_slot_id = rs . id
inner join rsvp_requests rr on rrrs . rsvp_request_id = rr . id
2014-07-10 20:22:00 +00:00
left join instruments ii on ii . id = rs . instrument_id
2014-06-19 18:43:21 +00:00
inner join users u on u . id = rr . user_id
2014-07-10 20:22:00 +00:00
where rrrs . chosen = true AND rs . music_session_id = '#{self.id}' AND rr . canceled != TRUE
group by u . id order by u . id }
2014-06-19 18:43:21 +00:00
)
end
2014-05-28 02:35:26 +00:00
# get all slots for this session and perform a set difference with all chosen slots;
# this will return those that are not filled yet
2014-07-08 18:34:03 +00:00
# this method excludes rsvp_slots marked as 'is_unstructured_rsvp = true'
2014-05-26 05:35:57 +00:00
def open_slots
2014-06-19 18:43:21 +00:00
RsvpSlot . find_by_sql ( %Q{ select rs.*, ii.description
from rsvp_slots rs
inner join instruments ii on ii . id = rs . instrument_id
where rs . music_session_id = '#{self.id}'
2014-07-08 18:34:03 +00:00
and rs . is_unstructured_rsvp = false
2014-06-19 18:43:21 +00:00
except
select distinct rs . * , iii . description
from rsvp_slots rs
inner join instruments iii on iii . id = rs . instrument_id
inner join rsvp_requests_rsvp_slots rrrs on rrrs . rsvp_slot_id = rs . id
where rs . music_session_id = '#{self.id}'
2014-05-26 18:00:34 +00:00
and rrrs . chosen = true
}
)
2014-05-26 05:35:57 +00:00
end
2014-05-28 02:35:26 +00:00
# retrieve users that have invitations but have not submitted an RSVP request for this session
2014-05-26 05:35:57 +00:00
def pending_invitations
2014-08-12 04:24:57 +00:00
User . find_by_sql ( %Q{ select distinct u.id, u.email, u.photo_url, u.first_name, u.last_name
2014-05-28 02:35:26 +00:00
from users u
inner join invitations i on u . id = i . receiver_id
2014-05-26 05:35:57 +00:00
left join rsvp_requests rr on rr . user_id = i . receiver_id
where i . music_session_id = '#{self.id}'
2014-06-14 17:13:45 +00:00
and rr . user_id is null }
)
2014-05-24 07:58:47 +00:00
end
2014-06-29 13:54:51 +00:00
# retrieve pending RsvpRequests
def pending_rsvp_requests
RsvpRequest . index ( self , nil , { status : 'pending' } )
end
2014-05-06 13:34:38 +00:00
def recordings
Recording . where ( music_session_id : self . id )
2014-01-05 03:47:23 +00:00
end
2014-05-06 13:34:38 +00:00
def end_history
self . update_attribute ( :session_removed_at , Time . now )
2014-01-05 03:47:23 +00:00
2012-10-25 12:11:10 +00:00
2014-05-06 13:34:38 +00:00
# ensure all user histories are closed
music_session_user_histories . each do | music_session_user_history |
music_session_user_history . end_history
# then update any users that need their user progress updated
if music_session_user_history . duration_minutes > 15 && music_session_user_history . max_concurrent_connections > = 3
music_session_user_history . user . update_progression_field ( :first_real_music_session_at )
end
end
2014-03-03 22:13:23 +00:00
end
2014-05-06 13:34:38 +00:00
def self . removed_music_session ( session_id )
hist = self
2014-05-06 21:17:26 +00:00
. where ( :id = > session_id )
2014-05-06 13:34:38 +00:00
. limit ( 1 )
. first
hist . end_history if hist
Notification . send_session_ended ( session_id )
2014-03-09 13:38:46 +00:00
end
2014-05-06 13:34:38 +00:00
def remove_non_alpha_num ( token )
token . gsub ( / [^0-9A-Za-z] / , '' )
2014-03-09 13:38:46 +00:00
end
2014-06-15 02:27:34 +00:00
def tag
nil unless has_attribute? ( :tag )
a = read_attribute ( :tag )
a . nil? ? nil : a . to_i
end
def latency
nil unless has_attribute? ( :latency )
a = read_attribute ( :latency )
a . nil? ? nil : a . to_i
end
2012-10-25 12:11:10 +00:00
2014-06-17 19:10:24 +00:00
# initialize the two temporary tables we use to drive sms_index and sms_users
def self . sms_init ( current_user , options = { } )
2014-08-18 15:37:55 +00:00
session_id = options [ :session_id ] || 'any'
2014-06-17 19:10:24 +00:00
2014-08-18 15:37:55 +00:00
my_locidispid = current_user . last_jam_locidispid
2014-06-17 19:10:24 +00:00
# 13 is an average audio gear value we use if they have not qualified any gear
2014-08-18 15:37:55 +00:00
my_audio_latency = current_user . last_jam_audio_latency || 13
2014-07-23 20:11:02 +00:00
locidispid_expr = my_locidispid ? " #{ my_locidispid } ::bigint " : '0::bigint' # Have to pass in zero; NULL fails silently in the stored proc
2014-06-17 19:10:24 +00:00
2014-08-18 15:37:55 +00:00
self . connection . execute ( " SELECT sms_index(' #{ current_user . id } '::varchar, #{ locidispid_expr } , #{ my_audio_latency } ::integer, #{ ActiveRecord :: Base . connection . quote ( session_id ) } ::varchar) " ) . check
2014-06-17 19:10:24 +00:00
end
# Generate a list of music sessions (that are active) filtered by genre, language, keyword, and sorted
# (and tagged) by rsvp'd (1st), invited (2nd), and musician can join (3rd). within a group tagged the
# same, sorted by score. date seems irrelevant as these are active sessions. sms_init must be called
# first.
def self . sms_query ( current_user , options = { } )
client_id = options [ :client_id ]
genre = options [ :genre ]
lang = options [ :lang ]
keyword = options [ :keyword ]
offset = options [ :offset ]
limit = options [ :limit ]
2014-06-17 22:16:49 +00:00
day = options [ :day ]
timezone_offset = options [ :timezone_offset ]
2014-06-17 19:10:24 +00:00
query = MusicSession
. select ( 'music_sessions.*' )
# this is not really needed when sms_music_session_tmp is joined
# unless there is something specific we need out of active_music_sessions
# query = query.joins(
# %Q{
# INNER JOIN
# active_music_sessions
# ON
# active_music_sessions.id = music_sessions.id
# }
# )
# .select('1::integer as tag, 15::integer as latency')
# integrate sms_music_session_tmp into the processing
# then we can join sms_music_session_tmp and not join active_music_sessions
query = query . joins (
%Q{
INNER JOIN
sms_music_session_tmp
ON
sms_music_session_tmp . music_session_id = music_sessions . id
}
)
. select ( 'sms_music_session_tmp.tag, sms_music_session_tmp.latency' )
query = query . order (
%Q{
tag , latency , music_sessions . id
}
)
. group (
%Q{
tag , latency , music_sessions . id
}
)
2014-06-17 22:16:49 +00:00
# if not specified, default offset to 0
offset || = 0
offset = offset . to_i
# if not specified, default limit to 20
limit || = 20
limit = limit . to_i
query = query . offset ( offset )
query = query . limit ( limit )
query = query . where ( " music_sessions.genre_id = ? " , genre ) unless genre . blank?
query = query . where ( 'music_sessions.language = ?' , lang ) unless lang . blank?
2014-08-15 02:28:30 +00:00
query = query . where ( " (description_tsv @@ to_tsquery('jamenglish', ?)) " , ActiveRecord :: Base . connection . quote ( keyword ) + ':*' ) unless keyword . blank?
2014-06-17 22:16:49 +00:00
if ! day . blank? && ! timezone_offset . blank?
begin
day = Date . parse ( day )
next_day = day + 1
timezone_offset = timezone_offset . to_i
2014-07-02 13:57:18 +00:00
if timezone_offset == 0
timezone_offset = '' # no offset to specify in this case
elsif timezone_offset > 0
2014-06-25 15:21:36 +00:00
timezone_offset = " + #{ timezone_offset } "
end
2014-06-17 22:16:49 +00:00
query = query . where ( " scheduled_start BETWEEN TIMESTAMP WITH TIME ZONE ' #{ day } 00:00:00 #{ timezone_offset } '
AND TIMESTAMP WITH TIME ZONE '#{next_day} 00:00:00#{timezone_offset}' " )
rescue Exception = > e
# do nothing. bad date probably
@@log . warn ( " unable to parse day= #{ day } , timezone_offset= #{ timezone_offset } , e= #{ e } " )
2014-06-17 19:10:24 +00:00
end
end
return query
end
# returns the set of users in a music_sessions and the music_session they are in and their latency.
# sms_init must be called first.
# user.audio_latency / 2 , + other_user.audio_latency of them / 2, + network latency /2
def self . sms_users
2014-08-18 15:37:55 +00:00
return User . select ( 'users.*, sms_users_tmp.music_session_id, sms_users_tmp.full_score, sms_users_tmp.audio_latency, sms_users_tmp.internet_score' )
2014-06-17 19:10:24 +00:00
. joins (
%Q{
INNER JOIN
sms_users_tmp
ON
sms_users_tmp . user_id = users . id
}
)
. order ( 'sms_users_tmp.music_session_id, sms_users_tmp.user_id' )
end
# wrap me in a transaction!
# note that these queries must be actualized before the end of the transaction
# else the temporary tables created by sms_init will be gone.
def self . sms_index ( current_user , params )
MusicSession . sms_init ( current_user , params )
2014-06-17 22:16:49 +00:00
music_sessions = MusicSession . sms_query ( current_user , params ) . all
2014-06-17 19:10:24 +00:00
music_session_users = MusicSession . sms_users . all
user_scores = { }
music_session_users . each do | user |
2014-08-18 15:37:55 +00:00
user_scores [ user . id ] = { full_score : user . full_score , audio_latency : user . audio_latency , internet_score : user . internet_score }
2014-06-17 19:10:24 +00:00
end
2014-08-18 15:37:55 +00:00
2014-06-17 19:10:24 +00:00
[ music_sessions , user_scores ]
end
2014-08-18 15:37:55 +00:00
# returns a single session, but populates any other user info with latency scores, so that show_history.rabl can do it's business
def self . session_with_scores ( current_user , music_session_id )
MusicSession . sms_init ( current_user , { session_id : music_session_id } )
music_session = MusicSession . find ( music_session_id )
music_session_users = MusicSession . sms_users . all
user_scores = { }
music_session_users . each do | user |
user_scores [ user . id ] = { full_score : user . full_score , audio_latency : user . audio_latency , internet_score : user . internet_score }
end
[ music_session , user_scores ]
end
2014-07-12 13:55:58 +00:00
def self . upcoming_sessions
end
2014-07-11 22:21:55 +00:00
# converts the passed scheduled_start into the database timezone using the specified timezone offset.
2014-07-11 03:08:03 +00:00
# timezone comes in as TIMEZONE DISPLAY, TIMEZONE ID
def self . parse_scheduled_start ( scheduled_start , timezone_param )
result = scheduled_start
2014-07-12 01:54:54 +00:00
tz_identifier = split_timezone ( timezone_param ) [ 0 ]
begin
timezone = ActiveSupport :: TimeZone . new ( tz_identifier )
rescue Exception = > e
@@log . error ( " unable to find timezone= #{ tz_identifier } , e= #{ e } " )
end
2014-07-11 22:21:55 +00:00
2014-07-12 01:54:54 +00:00
if timezone
begin
# first convert the time provided, and convert to the specified timezone (local_to_utc)
# then, convert that to the system timezone, under the ASSUMPTION that the database is configured to use the system timezone
# you can get into trouble if your dev database is not using the system timezone of the web machine
2014-07-11 22:21:55 +00:00
2014-07-12 01:54:54 +00:00
result = timezone . parse ( scheduled_start )
rescue Exception = > e
@@log . error ( " unable to convert #{ scheduled_start } to #{ timezone } , e= #{ e } " )
puts " unable to convert #{ scheduled_start } to #{ timezone } , e= #{ e } "
2014-07-11 03:08:03 +00:00
end
end
result
end
2014-06-17 19:10:24 +00:00
2014-07-14 18:40:00 +00:00
def scheduled_start_date
if self . scheduled_start_time . blank?
" "
else
self . scheduled_start . strftime " %a %e %B %Y "
end
end
2014-07-11 22:21:55 +00:00
def scheduled_start_time
2014-07-14 18:40:00 +00:00
if scheduled_start && scheduled_duration
start_time = scheduled_start
tz_identifier , tz_display = MusicSession . split_timezone ( timezone )
begin
tz = TZInfo :: Timezone . get ( tz_identifier )
rescue Exception = > e
@@log . error ( " unable to find timezone= #{ tz_identifier } , e= #{ e } " )
end
if tz
begin
start_time = tz . utc_to_local ( scheduled_start . utc )
rescue Exception = > e
@@log . error ( " unable to convert #{ scheduled_start } to #{ tz } , e= #{ e } " )
puts " unable to convert #{ e } "
end
end
start_time
2014-07-11 22:21:55 +00:00
else
" "
end
end
2014-07-12 01:54:54 +00:00
# takes our stored timezone which is DISPLAY,ID and returns an array of the two values (id first, then description)
def self . split_timezone ( tz )
result = [ nil , nil ]
if tz
index = tz . rindex ( ',' )
if index
tz_display = tz [ 0 , index ]
tz_identifier = tz [ ( index + 1 ) .. - 1 ]
result = [ tz_identifier , tz_display ]
end
end
result
end
2014-07-11 22:21:55 +00:00
# should create a timestamp like:
#
# with_timezone = TRUE
# Tuesday, April 29, 8:00-9:00 PM TIMEZONE (where TIMEZONE is the TIMEZONE defined in the MusicSession when it was created)
#
# with_timezone = FALSE
# Thursday, July 10 - 10:00pm
# this should be in a helper
def pretty_scheduled_start ( with_timezone )
2014-07-12 02:11:34 +00:00
2014-07-12 01:54:54 +00:00
if scheduled_start && scheduled_duration
2014-07-11 22:21:55 +00:00
start_time = scheduled_start
timezone_display = 'UTC'
2014-07-12 01:54:54 +00:00
tz_identifier , tz_display = MusicSession . split_timezone ( timezone )
begin
tz = TZInfo :: Timezone . get ( tz_identifier )
rescue Exception = > e
@@log . error ( " unable to find timezone= #{ tz_identifier } , e= #{ e } " )
end
if tz
2014-07-11 22:21:55 +00:00
begin
2014-07-12 01:54:54 +00:00
start_time = tz . utc_to_local ( scheduled_start . utc )
timezone_display = tz_display
2014-07-11 22:21:55 +00:00
rescue Exception = > e
2014-07-12 01:54:54 +00:00
@@log . error ( " unable to convert #{ scheduled_start } to #{ tz } , e= #{ e } " )
puts " unable to convert #{ e } "
2014-07-11 22:21:55 +00:00
end
end
2014-07-12 01:54:54 +00:00
2014-07-11 22:21:55 +00:00
duration = scheduled_duration
# you can put seconds into the scheduled_duration field, but once stored, it comes back out as a string
if scheduled_duration . class == String
begin
bits = scheduled_duration . split ( ':' )
duration = bits [ 0 ] . to_i . hours + bits [ 1 ] . to_i . minutes + bits [ 2 ] . to_i . seconds
rescue Exception = > e
duration = 1 . hours
@@log . error ( " unable to parse duration #{ scheduled_duration } " )
end
end
end_time = start_time + duration
if with_timezone
2014-07-12 22:24:12 +00:00
" #{ start_time . strftime ( " %A, %B %e " ) } , #{ start_time . strftime ( " %l:%M " ) . strip } - #{ end_time . strftime ( " %l:%M %p " ) . strip } #{ timezone_display } "
2014-07-11 22:21:55 +00:00
else
2014-07-12 22:24:12 +00:00
" #{ start_time . strftime ( " %A, %B %e " ) } - #{ start_time . strftime ( " %l:%M%P " ) . strip } "
2014-07-11 22:21:55 +00:00
end
else
" Date and time TBD "
end
end
2014-06-17 19:10:24 +00:00
private
2014-05-06 13:34:38 +00:00
def generate_share_token
token = loop do
token = SecureRandom . urlsafe_base64 ( SHARE_TOKEN_LENGTH , false )
token = remove_non_alpha_num ( token )
token . upcase!
break token unless ShareToken . exists? ( token : token )
end
self . share_token = ShareToken . new
self . share_token . token = token
self . share_token . shareable_type = " session "
2014-02-20 07:45:51 +00:00
end
2014-05-06 13:34:38 +00:00
2014-05-06 21:17:26 +00:00
def creator_is_musician
unless creator && creator . musician?
errors . add ( :creator , ValidationMessages :: MUST_BE_A_MUSICIAN )
end
end
2014-07-11 22:21:55 +00:00
def validate_timezone
if timezone
index = timezone . rindex ( ',' )
if index
tz_identifier = timezone [ ( index + 1 ) .. - 1 ]
begin
TZInfo :: Timezone . get ( tz_identifier )
rescue Exception = > e
@@log . error ( " unable to find timezone= #{ tz_identifier } , e= #{ e } " )
errors . add ( :timezone , ValidationMessages :: MUST_BE_KNOWN_TIMEZONE )
end
end
end
end
2012-10-03 03:50:23 +00:00
end
end