jam-cloud/ruby/lib/jam_ruby/models/icecast_mount.rb

218 lines
9.6 KiB
Ruby

module JamRuby
class IcecastMount < ActiveRecord::Base
@@log = Logging.logger[IcecastMount]
attr_accessible :authentication_id, :name, :source_username, :source_pass, :max_listeners, :max_listener_duration,
:dump_file, :intro, :fallback_mount, :fallback_override, :fallback_when_full, :charset, :is_public,
:stream_name, :stream_description, :stream_url, :genre, :bitrate, :mime_type, :subtype, :burst_size,
:mp3_metadata_interval, :hidden, :on_connect, :on_disconnect,
:music_session_id, :icecast_server_id, :icecast_mount_template_id, :listeners, :sourced,
:sourced_needs_changing_at, as: :admin
belongs_to :authentication, class_name: "JamRuby::IcecastUserAuthentication", inverse_of: :mount, :foreign_key => 'authentication_id'
belongs_to :music_session, class_name: "JamRuby::MusicSession", inverse_of: :mount, foreign_key: 'music_session_id'
belongs_to :server, class_name: "JamRuby::IcecastServer", inverse_of: :mounts, foreign_key: 'icecast_server_id'
belongs_to :mount_template, class_name: "JamRuby::IcecastMountTemplate", inverse_of: :mounts, foreign_key: 'icecast_mount_template_id'
validates :name, presence: true, uniqueness: true
validates :source_username, length: {minimum: 5}, if: lambda {|m| m.source_username.present?}
validates :source_pass, length: {minimum: 5}, if: lambda {|m| m.source_pass.present?}
validates :max_listeners, length: {in: 1..15000}, if: lambda {|m| m.max_listeners.present?}
validates :max_listener_duration, length: {in: 1..3600 * 48}, if: lambda {|m| m.max_listener_duration.present?}
validates :fallback_override, :inclusion => {:in => [0, 1]} , if: lambda {|m| m.fallback_mount.present?}
validates :fallback_when_full, :inclusion => {:in => [0, 1]} , if: lambda {|m| m.fallback_mount.present?}
validates :is_public, presence: true, :inclusion => {:in => [-1, 0, 1]}
validates :bitrate, numericality: {only_integer: true}, if: lambda {|m| m.bitrate.present?}
validates :burst_size, numericality: {only_integer: true}, if: lambda {|m| m.burst_size.present?}
validates :mp3_metadata_interval, numericality: {only_integer: true}, if: lambda {|m| m.mp3_metadata_interval.present?}
validates :hidden, :inclusion => {:in => [0, 1]}
validates :server, presence: true
validate :name_has_correct_format
before_save :sanitize_active_admin
after_save :after_save
after_save :poke_config
before_destroy :poke_config
def name_has_correct_format
errors.add(:name, "must start with /") unless name && name.start_with?('/')
end
def poke_config
server.update_attribute(:config_changed, 1) if server
end
def after_save
server.update_attribute(:config_changed, 1)
if !sourced_was && sourced
# went from NOT SOURCED to SOURCED
notify_source_up
elsif sourced_was && !sourced
# went from SOURCED to NOT SOURCED
notify_source_down
end
if listeners_was == 0 && listeners > 0 && !sourced
# listener count went above 0 and there is no source. ask the musician clients to source
notify_source_up_requested
end
# Note:
# Notification.send_source_down_requested does not occur here.
# we set up a cron that checks for streams that have not been successfully source up/down (after timeout ) in IcecastSourceCheck
end
def sanitize_active_admin
self.authentication_id = nil if self.authentication_id == ''
self.music_session_id = nil if self.music_session_id == ''
self.icecast_server_id = nil if self.icecast_server_id == ''
end
# creates a templated
def self.build_session_mount(music_session)
# only public sessions get mounts currently
return nil unless music_session.fan_access
icecast_server = IcecastServer.find_best_server_for_user(music_session.creator)
mount = nil
if icecast_server && icecast_server.mount_template_id
# we have a server with an associated mount_template; we can create a mount automatically
mount = icecast_server.mount_template.build_session_mount(music_session)
mount.server = icecast_server
end
mount
end
def source_up
with_lock do
self.sourced = true
self.sourced_needs_changing_at = nil
save(validate: false)
end
end
def source_down
with_lock do
self.sourced = false
self.sourced_needs_changing_at = nil
save(validate: false)
end
end
def listener_add
with_lock do
self.sourced_needs_changing_at = Time.now if listeners == 0
# this is completely unsafe without that 'with_lock' statement above
self.listeners = self.listeners + 1
save(validate: false)
end
end
def listener_remove
if listeners == 0
@@log.warn("listeners is at 0, but we are being asked to remove a listener. maybe we missed a listener_add request earlier")
return
end
with_lock do
self.sourced_needs_changing_at = Time.now if listeners == 1
# this is completely unsafe without that 'with_lock' statement above
self.listeners = self.listeners - 1
save(validations: false)
end
end
def notify_source_up_requested
Notification.send_source_up_requested(music_session,
server.hostname,
server.pick_listen_socket(:port),
name,
resolve_string(:source_username),
resolve_string(:source_pass),
resolve_int(:bitrate)) if music_session_id
end
def notify_source_down_requested
Notification.send_source_down_requested(music_session, name)
end
def notify_source_up
Notification.send_source_up(music_session) if music_session_id
end
def notify_source_down
Notification.send_source_down(music_session) if music_session_id
end
# Check if the icecast_mount specifies the value; if not, use the mount_template's value take effect
def dumpXml(builder)
builder.tag! 'mount' do |mount|
mount.tag! 'mount-name', name
mount.tag! 'username', resolve_string(:source_username) if string_present?(:source_username)
mount.tag! 'password', resolve_string(:source_pass) if string_present?(:source_pass)
mount.tag! 'max-listeners', resolve_int(:max_listeners) if int_present?(:max_listeners)
mount.tag! 'max-listener-duration', resolve_string(:max_listener_duration) if int_present?(:max_listener_duration)
mount.tag! 'dump-file', resolve_string(:dump_file) if string_present?(:dump_file)
mount.tag! 'intro', resolve_string(:intro) if string_present?(:intro)
mount.tag! 'fallback-mount', resolve_string(:fallback_mount) if string_present?(:fallback_mount)
mount.tag! 'fallback-override', resolve_int(:fallback_override) if int_present?(:fallback_override)
mount.tag! 'fallback-when-full', resolve_int(:fallback_when_full) if int_present?(:fallback_when_full)
mount.tag! 'charset', resolve_string(:charset) if string_present?(:charset)
mount.tag! 'public', resolve_int(:is_public) if int_present?(:is_public)
mount.tag! 'stream-name', resolve_string(:stream_name) if string_present?(:stream_name)
mount.tag! 'stream-description', resolve_string(:stream_description) if string_present?(:stream_description)
mount.tag! 'stream-url', resolve_string(:stream_url) if string_present?(:stream_url)
mount.tag! 'genre', resolve_string(:genre) if string_present?(:genre)
mount.tag! 'bitrate', resolve_int(:bitrate) if int_present?(:bitrate)
mount.tag! 'type', resolve_string(:mime_type) if string_present?(:mime_type)
mount.tag! 'subtype', resolve_string(:subtype) if string_present?(:subtype)
mount.tag! 'burst-size', resolve_int(:burst_size) if int_present?(:burst_size)
mount.tag! 'mp3-metadata-interval', resolve_int(:mp3_metadata_interval) if int_present?(:mp3_metadata_interval)
mount.tag! 'hidden', resolve_int(:hidden) if int_present?(:hidden)
mount.tag! 'on-connect', resolve_string(:on_connect) if string_present?(:on_connect)
mount.tag! 'on-disconnect', resolve_string(:on_disconnect) if string_present?(:on_disconnect)
authentication.dumpXml(builder) if authentication
end
end
def url
raise "Unassociated server to mount" if self.server.nil?
"http://#{server.hostname}:#{server.pick_listen_socket(:port)}#{self.name}"
end
def resolve_string(field)
self[field].present? ? self[field] : mount_template && mount_template[field]
end
def string_present?(field)
val = resolve_string(field)
val ? val.present? : false
end
def resolve_int(field)
!self[field].nil? ? self[field]: mount_template && mount_template[field]
end
def int_present?(field)
resolve_int(field)
end
end
end