bigbluebutton-Github/record-and-playback/core/lib/recordandplayback.rb
Pedro Beschorner Marin 0c4cf0135d Handle pad's events and Meteor's instances
Since Meteor was split in multiple process and events started to be
filtered by instances, all Etherpad's Redis events were being discarded.

Etherpad has a Redis' publisher plugin that is unaware of BigBlueButton's
existence. All the communication between them is kept simple with minimal
of internal data exchange. The concept of distincts subscribers at Meteor's
side broke part of this simplicity and, now, Etherpad has to know which
instance must receive it's messages. To provide such information I decided
to include Meteor's instance as part of the pad's id. Should look like:

 - [instanceId]padId for the shared notes
 - [instanceId]padId_cc_(locale) for the closed captions

With those changes the pad id generation made at the recording scripts had to
be re-done because there is no instance id available. Pad id is now recorded at
akka-apps and queried while archiving the shared notes.
2021-03-23 18:03:50 -03:00

255 lines
6.7 KiB
Ruby

# Set encoding to utf-8
# encoding: UTF-8
#
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
#
# Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
#
# This program is free software; you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free Software
# Foundation; either version 3.0 of the License, or (at your option) any later
# version.
#
# BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
#
require_relative 'boot'
require 'recordandplayback/events_archiver'
require 'recordandplayback/generators/events'
require 'recordandplayback/generators/audio'
require 'recordandplayback/generators/video'
require 'recordandplayback/generators/audio_processor'
require 'recordandplayback/generators/presentation'
require 'open4'
require 'pp'
require 'absolute_time'
require 'logger'
require 'find'
require 'rubygems'
require 'net/http'
require 'journald/logger'
require 'shellwords'
require 'English'
module BigBlueButton
class MissingDirectoryException < RuntimeError
end
class FileNotFoundException < RuntimeError
end
class ExecutionStatus
def initialize
@output = []
@errors = []
@detailedStatus = nil
end
attr_accessor :output
attr_accessor :errors
attr_accessor :detailedStatus
def success?
@detailedStatus.success?
end
def exited?
@detailedStatus.exited?
end
def exitstatus
@detailedStatus.exitstatus
end
end
# BigBlueButton logs information about its progress.
# Replace with your own logger if you desire.
#
# @param [Logger] log your own logger
# @return [Logger] the logger you set
def self.logger=(log)
@logger = log
end
# Get BigBlueButton logger.
#
# @return [Logger]
def self.logger
return @logger if @logger
logger = Journald::Logger.new('bbb-rap')
logger.level = Logger::INFO
@logger = logger
end
def self.redis_publisher=(publisher)
@redis_publisher = publisher
end
def self.redis_publisher
return @redis_publisher
end
def self.execute(command, fail_on_error = true)
BigBlueButton.logger.info("Executing: #{command.respond_to?(:to_ary) ? Shellwords.join(command) : command}")
IO.popen(command, err: %i[child out]) do |io|
io.each_line do |line|
BigBlueButton.logger.info(line.chomp)
end
end
status = $CHILD_STATUS
BigBlueButton.logger.info("Success?: #{status.success?}")
BigBlueButton.logger.info("Process exited? #{status.exited?}")
BigBlueButton.logger.info("Exit status: #{status.exitstatus}")
raise 'Execution failed' if status.success? == false && fail_on_error
status
end
def self.exec_ret(*command)
execute(command, false).exitstatus
end
def self.exec_redirect_ret(outio, *command)
BigBlueButton.logger.info "Executing: #{Shellwords.join(command)}"
BigBlueButton.logger.info "Sending output to #{outio}"
IO.pipe do |r, w|
pid = spawn(*command, :out => outio, :err => w)
w.close
r.each_line do |line|
BigBlueButton.logger.info line.chomp
end
Process.waitpid(pid)
BigBlueButton.logger.info "Exit status: #{$?.exitstatus}"
return $?.exitstatus
end
end
def self.hash_to_str(hash)
return PP.pp(hash, "")
end
def self.monotonic_clock()
return (AbsoluteTime.now * 1000).to_i
end
def self.download(url, output)
BigBlueButton.logger.info "Downloading #{url} to #{output}"
uri = URI.parse(url)
if ["http", "https", "ftp"].include? uri.scheme
response = Net::HTTP.start(uri.host, uri.port) {|http|
http.head(uri.request_uri)
}
unless response.is_a? Net::HTTPSuccess
raise "File not available: #{response.message}"
end
end
if uri.scheme.nil?
url = "file://" + url
uri = URI.parse(url)
end
Net::HTTP.start(uri.host, uri.port) do |http|
request = Net::HTTP::Get.new uri.request_uri
http.request request do |response|
open output, 'w' do |io|
response.read_body do |chunk|
io.write chunk
end
end
end
end
end
def self.try_download(url, output)
begin
self.download(url, output)
rescue Exception => e
BigBlueButton.logger.error "Failed to download file: #{e.to_s}"
FileUtils.rm_f output
end
end
def self.get_dir_size(dir_name)
size = 0
if FileTest.directory?(dir_name)
Find.find(dir_name) {|f| size += File.size(f)}
end
size.to_s
end
def self.add_tag_to_xml(xml_filename, parent_xpath, tag, content)
if File.exist? xml_filename
doc = Nokogiri::XML(File.read(xml_filename)) {|x| x.noblanks}
node = doc.at_xpath("#{parent_xpath}/#{tag}")
node.remove if not node.nil?
node = Nokogiri::XML::Node.new tag, doc
node.content = content
doc.at(parent_xpath) << node
xml_file = File.new(xml_filename, "w")
xml_file.write(doc.to_xml(:indent => 2))
xml_file.close
end
end
def self.add_raw_size_to_metadata(dir_name, raw_dir_name)
size = BigBlueButton.get_dir_size(raw_dir_name)
BigBlueButton.add_tag_to_xml("#{dir_name}/metadata.xml", "//recording", "raw_size", size)
end
def self.add_playback_size_to_metadata(dir_name)
size = BigBlueButton.get_dir_size(dir_name)
BigBlueButton.add_tag_to_xml("#{dir_name}/metadata.xml", "//recording/playback", "size", size)
end
def self.add_download_size_to_metadata(dir_name)
size = BigBlueButton.get_dir_size(dir_name)
BigBlueButton.add_tag_to_xml("#{dir_name}/metadata.xml", "//recording/download", "size", size)
end
def self.record_id_to_timestamp(r)
r.split("-")[1].to_i / 1000
end
def self.done_to_timestamp(r)
BigBlueButton.record_id_to_timestamp(File.basename(r, ".done"))
end
def self.rap_core_path
File.expand_path('../../', __FILE__)
end
def self.rap_scripts_path
File.join(BigBlueButton.rap_core_path, 'scripts')
end
def self.read_props
return @props if @props
filepath = File.join(BigBlueButton.rap_scripts_path, 'bigbluebutton.yml')
@props = YAML::load(File.open(filepath))
end
def self.create_redis_publisher
props = BigBlueButton.read_props
redis_host = props['redis_host']
redis_port = props['redis_port']
redis_password = props['redis_password']
BigBlueButton.redis_publisher = BigBlueButton::RedisWrapper.new(redis_host, redis_port, redis_password)
end
end