module JamRuby class IcecastServer < ActiveRecord::Base attr_accessor :skip_config_changed_flag attr_accessible :template_id, :mount_template_id, :limit_id, :admin_auth_id, :directory_id, :master_relay_id, :path_id, :logging_id, :security_id, :config_changed, :hostname, :location, :admin_email, :fileserve, as: :admin belongs_to :template, class_name: "JamRuby::IcecastTemplate", foreign_key: 'template_id', inverse_of: :servers belongs_to :mount_template, class_name: "JamRuby::IcecastMountTemplate", foreign_key: 'mount_template_id', inverse_of: :servers belongs_to :server_group, class_name: "JamRuby::IcecastServerGroup", foreign_key: 'icecast_server_group_id', inverse_of: :servers # all are overrides, because the template defines all of these as well. When building the XML, we will prefer these if set belongs_to :limit, class_name: "JamRuby::IcecastLimit", foreign_key: 'limit_id', inverse_of: :servers belongs_to :admin_auth, class_name: "JamRuby::IcecastAdminAuthentication", foreign_key: 'admin_auth_id', inverse_of: :servers belongs_to :directory, class_name: "JamRuby::IcecastDirectory", foreign_key: 'directory_id', inverse_of: :servers belongs_to :master_relay, class_name: "JamRuby::IcecastMasterServerRelay", foreign_key: 'master_relay_id', inverse_of: :servers belongs_to :path, class_name: "JamRuby::IcecastPath", foreign_key: 'path_id', inverse_of: :servers belongs_to :logging, class_name: "JamRuby::IcecastLogging", foreign_key: 'logging_id', inverse_of: :servers belongs_to :security, class_name: "JamRuby::IcecastSecurity", foreign_key: 'security_id', inverse_of: :servers has_many :listen_socket_servers, class_name: "JamRuby::IcecastServerSocket", inverse_of: :server has_many :listen_sockets, class_name: "JamRuby::IcecastListenSocket", :through => :listen_socket_servers, :source => :socket # mounts and relays are naturally server-specific, though #has_many :server_mounts, class_name: "JamRuby::IcecastServerMount", inverse_of: :server has_many :mounts, class_name: "JamRuby::IcecastMount", inverse_of: :server, :foreign_key => 'icecast_server_id' has_many :server_relays, class_name: "JamRuby::IcecastServerRelay", inverse_of: :relay has_many :relays, class_name: "JamRuby::IcecastRelay", :through => :server_relays, :source => :relay validates :config_changed, :inclusion => {:in => [0, 1]} validates :hostname, presence: true validates :fileserve, :inclusion => {:in => [0, 1]}, :if => lambda {|s| s.fileserve.present? } validates :server_id, presence: true validates :template, presence: true validates :mount_template, presence: true before_save :before_save, unless: lambda { skip_config_changed_flag } before_save :sanitize_active_admin after_save :after_save def before_save self.config_changed = 1 end def sanitize_active_admin self.template_id = nil if self.template_id == '' self.limit_id = nil if self.limit_id == '' self.admin_auth_id = nil if self.admin_auth_id == '' self.directory_id = nil if self.directory_id == '' self.master_relay_id = nil if self.master_relay_id == '' self.path_id = nil if self.path_id == '' self.logging_id = nil if self.logging_id == '' self.security_id = nil if self.security_id == '' end def after_save # if we set config_changed, then queue up a job if config_changed_was == 0 && config_changed == 1 IcecastConfigWriter.enqueue(self.server_id) end end # this method is the correct way to set config_changed to false # if you don't do it this way, then likely you'll get into a loop # config_changed = true, enqueued job, job executes, job accidentally flags config_changed by touching the model, and repeat def config_updated self.skip_config_changed_flag = true self.config_changed = 0 begin self.save! rescue raise ensure self.skip_config_changed_flag = false end end def pick_listen_socket(field) current_listen_sockets = listen_sockets.length > 0 ? listen_sockets : template.listen_sockets socket = current_listen_sockets.first socket[:field] if socket end # pick an icecast server with the least listeners * sources def self.find_best_server_for_user(user) chosen_server_id = nil chosen_server_weight = nil ActiveRecord::Base.connection_pool.with_connection do |connection| result = connection.execute('select SUM(listeners), SUM(sourced::int), icecast_servers.id FROM icecast_servers LEFT JOIN icecast_mounts ON icecast_servers.id = icecast_mounts.icecast_server_id WHERE icecast_server_group_id = \'' + user.icecast_server_group_id + '\' GROUP BY icecast_servers.id;') result.cmd_tuples.times do |i| listeners = result.getvalue(i, 0).to_i sourced = result.getvalue(i, 1).to_i icecast_server_id = result.getvalue(i, 2) # compute weight. source is much more intensive than listener, based on load tests again 2.3.0 # http://icecast.org/loadtest2.php weight = sourced * 10 + listeners if !chosen_server_id || (weight < chosen_server_weight) chosen_server_id = icecast_server_id chosen_server_weight = weight end end end IcecastServer.find(chosen_server_id) if chosen_server_id end def to_s server_id end def dumpXml (output=$stdout, indent=1) builder = ::Builder::XmlMarkup.new(:target => output, :indent => indent) builder.tag! 'icecast' do |root| root.tag! 'hostname', hostname root.tag! 'server-id', server_id root.tag! 'location', resolve_string(:location) if string_present?(:location) root.tag! 'admin', resolve_string(:admin_email) if string_present?(:admin_email) root.tag! 'fileserve', resolve_int(:fileserve) if int_present?(:fileserve) resolve_association(:limit).dumpXml(builder) if association_present?(:limit) resolve_association(:admin_auth).dumpXml(builder) if association_present?(:admin_auth) resolve_association(:directory).dumpXml(builder) if association_present?(:directory) resolve_association(:master_relay).dumpXml(builder) if association_present?(:master_relay) resolve_association(:path).dumpXml(builder) if association_present?(:path) resolve_association(:logging).dumpXml(builder) if association_present?(:logging) resolve_association(:security).dumpXml(builder) if association_present?(:security) current_listen_sockets = listen_sockets.length > 0 ? listen_sockets : template.listen_sockets current_listen_sockets.each do |listen_socket| listen_socket.dumpXml(builder) end relays.each do |relay| relay.dumpXml(builder) end mounts.each do |mount| mount.dumpXml(builder) end end end def resolve_string(field) self[field].present? ? self[field] : template && template[field] end def string_present?(field) val = resolve_string(field) val ? val.present? : false end def resolve_int(field) self[field] ? self[field]: template && template[field] end def int_present?(field) resolve_int(field) end def resolve_association(field) self.send(field) ? self.send(field) : template && template.send(field) end def association_present?(field) resolve_association(field) end end end