Evita il riempimento disco da segmenti MediaMTX post-sessione con cron orario; documenta come board, overlay e regolamenti sportivi si relazionano nel sistema. Co-authored-by: Cursor <cursoragent@cursor.com>
104 lines
2.9 KiB
Ruby
104 lines
2.9 KiB
Ruby
module Recordings
|
|
class CleanupLocal
|
|
ACTIVE_SESSION_STATUSES = %w[live connecting reconnecting paused].freeze
|
|
REMOVABLE_RECORDING_STATUSES = %w[ready failed expired].freeze
|
|
|
|
Result = Struct.new(:removed, :freed_bytes, :skipped, keyword_init: true)
|
|
|
|
def initialize(online_paths: nil, logger: Rails.logger)
|
|
@online_paths = online_paths
|
|
@logger = logger
|
|
@removed = 0
|
|
@freed_bytes = 0
|
|
@skipped = 0
|
|
end
|
|
|
|
def call
|
|
online = @online_paths || Mediamtx::Client.new.online_path_names
|
|
|
|
each_live_match_dir do |dir_path, session_id, path_name|
|
|
if online.include?(path_name)
|
|
@skipped += 1
|
|
next
|
|
end
|
|
|
|
session = StreamSession.find_by(id: session_id)
|
|
unless removable?(session)
|
|
@skipped += 1
|
|
next
|
|
end
|
|
|
|
remove_dir(dir_path)
|
|
cleanup_mediamtx_path(session) if session
|
|
end
|
|
|
|
Result.new(removed: @removed, freed_bytes: @freed_bytes, skipped: @skipped)
|
|
end
|
|
|
|
private
|
|
|
|
def each_live_match_dir
|
|
base = File.join(MatchLiveTv.recordings_local_path, "live")
|
|
return unless Dir.exist?(base)
|
|
|
|
Dir.children(base).sort.each do |entry|
|
|
next unless entry.start_with?("match_")
|
|
|
|
session_id = entry.delete_prefix("match_")
|
|
path_name = "live/#{entry}"
|
|
dir_path = File.join(base, entry)
|
|
next unless File.directory?(dir_path)
|
|
|
|
yield dir_path, session_id, path_name
|
|
end
|
|
end
|
|
|
|
def removable?(session)
|
|
return true if session.nil?
|
|
|
|
return false if ACTIVE_SESSION_STATUSES.include?(session.status)
|
|
|
|
recording = Recording.find_by(stream_session: session)
|
|
return true if recording&.status.in?(REMOVABLE_RECORDING_STATUSES)
|
|
|
|
return false unless session.status.in?(%w[ended error])
|
|
|
|
ended_at = session.ended_at || session.updated_at
|
|
grace = recording&.status == "processing" ? processing_grace : ended_grace
|
|
ended_at < grace.ago
|
|
end
|
|
|
|
def processing_grace
|
|
ENV.fetch("RECORDINGS_LOCAL_PROCESSING_GRACE_HOURS", "6").to_i.hours
|
|
end
|
|
|
|
def ended_grace
|
|
ENV.fetch("RECORDINGS_LOCAL_ENDED_GRACE_HOURS", "2").to_i.hours
|
|
end
|
|
|
|
def remove_dir(path)
|
|
size = dir_size(path)
|
|
FileUtils.rm_rf(path)
|
|
@removed += 1
|
|
@freed_bytes += size
|
|
@logger.info("[Recordings::CleanupLocal] removed #{path} (#{size} bytes)")
|
|
rescue StandardError => e
|
|
@logger.warn("[Recordings::CleanupLocal] failed #{path}: #{e.message}")
|
|
end
|
|
|
|
def dir_size(path)
|
|
`du -sb #{Shellwords.escape(path)} 2>/dev/null`.split.first.to_i
|
|
rescue StandardError
|
|
0
|
|
end
|
|
|
|
def cleanup_mediamtx_path(session)
|
|
return unless session.status.in?(%w[ended error])
|
|
|
|
Mediamtx::Client.new.delete_path(session)
|
|
rescue Mediamtx::Client::Error => e
|
|
@logger.warn("[Recordings::CleanupLocal] delete_path #{session.id}: #{e.message}")
|
|
end
|
|
end
|
|
end
|