Class: BigbluebuttonRoom

Inherits:
ActiveRecord::Base
  • Object
show all
Includes:
ActiveModel::ForbiddenAttributesProtection
Defined in:
app/models/bigbluebutton_room.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#attendeesObject

Note: these params need to be fetched from the server before being accessed



61
62
63
# File 'app/models/bigbluebutton_room.rb', line 61

def attendees
  @attendees
end

#end_timeObject

Note: these params need to be fetched from the server before being accessed



61
62
63
# File 'app/models/bigbluebutton_room.rb', line 61

def end_time
  @end_time
end

#full_logout_urlObject

the full logout_url used when logout_url is a relative path



70
71
72
# File 'app/models/bigbluebutton_room.rb', line 70

def full_logout_url
  @full_logout_url
end

#has_been_forcibly_endedObject

Note: these params need to be fetched from the server before being accessed



61
62
63
# File 'app/models/bigbluebutton_room.rb', line 61

def has_been_forcibly_ended
  @has_been_forcibly_ended
end

#moderator_countObject

Note: these params need to be fetched from the server before being accessed



61
62
63
# File 'app/models/bigbluebutton_room.rb', line 61

def moderator_count
  @moderator_count
end

#participant_countObject

Note: these params need to be fetched from the server before being accessed



61
62
63
# File 'app/models/bigbluebutton_room.rb', line 61

def participant_count
  @participant_count
end

#request_headersObject

HTTP headers that will be passed to the BigBlueButtonApi object to send in all GET/POST requests to a webconf server. Currently used to send the client’s IP to the load balancer.



75
76
77
# File 'app/models/bigbluebutton_room.rb', line 75

def request_headers
  @request_headers
end

#runningObject

Note: these params need to be fetched from the server before being accessed



61
62
63
# File 'app/models/bigbluebutton_room.rb', line 61

def running
  @running
end

#start_timeObject

Note: these params need to be fetched from the server before being accessed



61
62
63
# File 'app/models/bigbluebutton_room.rb', line 61

def start_time
  @start_time
end

Instance Method Details

#add_domain_to_logout_url(protocol, host) ⇒ Object

add a domain name and/or protocol to the logout_url if needed it doesn’t save in the db, just updates the instance



293
294
295
296
297
298
299
300
301
302
303
304
# File 'app/models/bigbluebutton_room.rb', line 293

def add_domain_to_logout_url(protocol, host)
  unless logout_url.nil?
    url = logout_url.downcase
    unless url.nil? or url =~ /^[a-z]+:\/\//           # matches the protocol
      unless url =~ /^[a-z0-9]+([\-\.]{1}[a-z0-9]+)*/  # matches the host domain
        url = host + url
      end
      url = protocol + url
    end
    self.full_logout_url = url.downcase
  end
end

#attr_equal?(o) ⇒ Boolean

A more complete equal? method, comparing also the attibutes and the instance variables

Returns:

  • (Boolean)


263
264
265
266
267
# File 'app/models/bigbluebutton_room.rb', line 263

def attr_equal?(o)
  self == o and
    self.instance_variables_compare(o).empty? and
    self.attributes == o.attributes
end

#available_layoutsObject



435
436
437
438
# File 'app/models/bigbluebutton_room.rb', line 435

def available_layouts
  server = BigbluebuttonRails.configuration.select_server.call(self)
  server.present? ? server.available_layouts : []
end

#available_layouts_for_selectObject



445
446
447
448
# File 'app/models/bigbluebutton_room.rb', line 445

def available_layouts_for_select
  server = BigbluebuttonRails.configuration.select_server.call(self)
  server.present? ? server.available_layouts_for_select : []
end

#available_layouts_namesObject



440
441
442
443
# File 'app/models/bigbluebutton_room.rb', line 440

def available_layouts_names
  server = BigbluebuttonRails.configuration.select_server.call(self)
  server.present? ? server.available_layouts_names : []
end

#create_meeting(user = nil, request = nil, user_opts = {}) ⇒ Object

The create logic. Will create the meeting in this room unless it is already running. Returns true if the meeting was created.



276
277
278
279
280
281
282
283
284
285
286
287
288
289
# File 'app/models/bigbluebutton_room.rb', line 276

def create_meeting(user=nil, request=nil, user_opts={})
  fetch_is_running?
  unless is_running?

    # in case the meeting is not running but it's still in memory
    suppress(BigBlueButton::BigBlueButtonException) { send_end }

    add_domain_to_logout_url(request.protocol, request.host_with_port) unless request.nil?
    send_create(user, user_opts)
    true
  else
    false
  end
end

#create_meeting_record(metadata = nil) ⇒ Object



348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
# File 'app/models/bigbluebutton_room.rb', line 348

def create_meeting_record(=nil)
  unless get_current_meeting.present?
    if self.create_time.present?

      # to make sure there's no other meeting related to this room that
      # has not yet been set as ended
      self.finish_meetings

      server = BigbluebuttonRails.configuration.select_server.call(self)
      attrs = {
        room: self,
        server_url: server.url,
        server_secret: server.secret,
        meetingid: self.meetingid,
        name: self.name,
        recorded: self.record_meeting,
        create_time: self.create_time,
        running: self.running,
        ended: false,
        start_time: self.start_time.try(:utc)
      }
      unless .nil?
        begin
          attrs[:creator_id] = [BigbluebuttonRails.configuration.].to_i
          attrs[:creator_name] = [BigbluebuttonRails.configuration.]
        rescue
          attrs[:creator_id] = nil
          attrs[:creator_name] = nil
        end
      end
      BigbluebuttonMeeting.create(attrs)

      Rails.logger.error "Did not create a current meeting because there was no create_time on room #{self.meetingid}"
    else
      Rails.logger.error "Did not create a current meeting because there was no create_time on room #{self.meetingid}"
    end
  end
end

#fetch_is_running?Boolean

Fetches the BBB server to see if the meeting is running. Sets running

Triggers API call: isMeetingRunning.

Returns:

  • (Boolean)


146
147
148
149
# File 'app/models/bigbluebutton_room.rb', line 146

def fetch_is_running?
  server = BigbluebuttonRails.configuration.select_server.call(self, :is_meeting_running)
  @running = server.api.is_meeting_running?(self.meetingid)
end

#fetch_meeting_infoObject

Fetches info from BBB about this room. The response is parsed and stored in the model. You can access it using attributes such as:

room.participant_count
room.attendees[0].full_name

The attributes changed are:

  • participant_count

  • moderator_count

  • running

  • has_been_forcibly_ended

  • start_time

  • end_time

  • attendees (array of BigbluebuttonAttendee)

Triggers API call: getMeetingInfo.



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'app/models/bigbluebutton_room.rb', line 105

def fetch_meeting_info
  begin
    server = BigbluebuttonRails.configuration.select_server.call(self, :get_meeting_info)
    response = server.api.get_meeting_info(self.meetingid, self.moderator_api_password)

    @participant_count = response[:participantCount]
    @moderator_count = response[:moderatorCount]
    @running = response[:running]
    @has_been_forcibly_ended = response[:hasBeenForciblyEnded]
    @start_time = response[:startTime]
    @end_time = response[:endTime]
    @attendees = []
    if response[:attendees].present?
      response[:attendees].each do |att|
        attendee = BigbluebuttonAttendee.new
        attendee.from_hash(att)
        @attendees << attendee
      end
    end

    # a 'shortcut' to update meetings since we have all information we need
    # if we got here, it means the meeting is still in the server, so it's not ended
    self.update_attributes(create_time: response[:createTime]) unless self.new_record?
    self.update_current_meeting_record(response[:metadata], true)

  rescue BigBlueButton::BigBlueButtonException => e
    # note: we could catch only the 'notFound' error, but there are complications, so
    # it's better to end the meeting prematurely and open it again if needed than to
    # not end it at all (e.g. in case the server stops responding)
    Rails.logger.info "BigbluebuttonRoom: detected that a meeting ended in the room #{self.meetingid} after the error #{e.inspect}"

    self.update_attributes(create_time: nil) unless self.new_record?
    self.finish_meetings
  end

  response
end

#fetch_new_tokenObject

Gets a ‘configToken’ to use when joining the room. Returns a string with the token generated or nil if there’s no need for a token (the options set in the room are the default options or there are no options set in the room) or if an error occurred.

The entire process consists in these steps:

  • Go to the server get the default config.xml;

  • Modify the config.xml based on the room options set in the room;

  • Go to the server set the new config.xml;

  • Get the token identifier and return it.

Triggers API call: getDefaultConfigXML. Triggers API call: setConfigXML.



413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
# File 'app/models/bigbluebutton_room.rb', line 413

def fetch_new_token
  if self.room_options.is_modified?
    server = BigbluebuttonRails.configuration.select_server.call(self, :set_config_xml)

    # get the default XML we will use to create a new one
    config_xml = server.api.get_default_config_xml

    # set the options on the XML
    # returns true if something was changed
    config_xml = self.room_options.set_on_config_xml(config_xml)
    if config_xml
      server.update_config(config_xml)
      # get the new token for the room, and return it
      server.api.set_config_xml(self.meetingid, config_xml)
    else
      nil
    end
  else
    nil
  end
end

#fetch_recordings(filter = {}) ⇒ Object



465
466
467
468
469
470
471
472
473
# File 'app/models/bigbluebutton_room.rb', line 465

def fetch_recordings(filter={})
  server = BigbluebuttonRails.configuration.select_server.call(self, :get_recordings)
  if server.present?
    server.fetch_recordings(filter.merge({ meetingID: self.meetingid }))
    true
  else
    false
  end
end

#finish_meetingsObject

Sets all meetings related to this room as not running



388
389
390
391
392
393
394
395
396
397
398
# File 'app/models/bigbluebutton_room.rb', line 388

def finish_meetings
  BigbluebuttonMeeting.where(ended: false)
    .where(room_id: self.id)
    .update_all(running: false, ended: true)

  # in case there are inconsistent meetings marked as running
  # but that already ended
  BigbluebuttonMeeting.where(running: true, ended: true)
    .where(room_id: self.id)
    .update_all(running: false, ended: true)
end

#generate_dial_number!(pattern = nil) ⇒ Object

Generates a new dial number following ‘pattern` and saves it in the room, returning the results of `update_attributes`. Will always generate a unique number. Tries several times if the number already exists and returns `nil` in case it wasn’t possible to generate a unique value.



454
455
456
457
458
459
460
461
462
463
# File 'app/models/bigbluebutton_room.rb', line 454

def generate_dial_number!(pattern=nil)
  pattern ||= 'xxxx-xxxx'
  20.times do
    dn = BigbluebuttonRails::DialNumber.randomize(pattern)
    if BigbluebuttonRoom.where(dial_number: dn).empty?
      return self.update_attributes(dial_number: dn)
    end
  end
  nil
end

#get_current_meetingObject

Returns the current meeting running on this room, if any.



314
315
316
317
318
319
320
# File 'app/models/bigbluebutton_room.rb', line 314

def get_current_meeting
  unless self.create_time.nil?
    BigbluebuttonMeeting.find_by(room_id: self.id, create_time: self.create_time)
  else
    nil
  end
end

#instance_variables_compare(o) ⇒ Object

Compare the instance variables of two models to define if they are equal Returns a hash with the variables with different values or an empty hash if they are have all equal values. From: alicebobandmallory.com/articles/2009/11/02/comparing-instance-variables-in-ruby



253
254
255
256
257
258
259
# File 'app/models/bigbluebutton_room.rb', line 253

def instance_variables_compare(o)
  vars = [ :@running, :@participant_count, :@moderator_count, :@attendees,
           :@has_been_forcibly_ended, :@start_time, :@end_time ]
  Hash[*vars.map { |v|
         self.instance_variable_get(v)!=o.instance_variable_get(v) ?
         [v,o.instance_variable_get(v)] : []}.flatten]
end

#is_running?Boolean

Convenience method to access the attribute running

Returns:

  • (Boolean)


85
86
87
# File 'app/models/bigbluebutton_room.rb', line 85

def is_running?
  @running
end

#join_url(username, role, key = nil, options = {}) ⇒ Object

Returns the URL to join this room.

username

Name of the user

role

Role of the user in this room. Can be [:moderator, :attendee]

key

Key to be use (in case role == nil)

options

Additional options to use when generating the URL

Uses the API but does not require a request to the server.



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'app/models/bigbluebutton_room.rb', line 210

def join_url(username, role, key=nil, options={})
  server = BigbluebuttonRails.configuration.select_server.call(self, :join_meeting_url)

  pass = case role
         when :moderator
           self.moderator_api_password
         when :attendee
           self.attendee_api_password
         when :guest
           if BigbluebuttonRails.configuration.guest_support
             options = { guest: true }.merge(options)
           end
           self.attendee_api_password
         else
           map_key_to_internal_password(key)
         end

  r = server.api.join_meeting_url(self.meetingid, username, pass, options)
  r.strip! unless r.nil?
  r
end

#room_options_with_initializeObject

In case there’s no room_options created yet, build one (happens usually when an old database is migrated).



79
80
81
# File 'app/models/bigbluebutton_room.rb', line 79

def room_options_with_initialize
  room_options_without_initialize || build_room_options
end

#select_server(api_method = nil) ⇒ Object



475
476
477
478
479
480
481
482
# File 'app/models/bigbluebutton_room.rb', line 475

def select_server(api_method=nil)
  server = BigbluebuttonServer.first
  if server.nil?
    msg = I18n.t('bigbluebutton_rails.rooms.errors.server.nil')
    raise BigbluebuttonRails::ServerRequired.new(msg)
  end
  server
end

#send_create(user = nil, user_opts = {}) ⇒ Object

Sends a call to the BBB server to create the meeting. ‘user’ is the object that represents the user that is creating the meeting. ‘user_opts’ is a hash of parameters to override the parameters sent in the create

request. Can be passed by the application to enforce some values over the values
that are taken from the database.

With the response, updates the following attributes:

  • attendee_api_password

  • moderator_api_password

Triggers API call: create.



175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'app/models/bigbluebutton_room.rb', line 175

def send_create(user=nil, user_opts={})
  self.meetingid = unique_meetingid() if self.meetingid.blank?
  self.moderator_api_password = internal_password() if self.moderator_api_password.blank?
  self.attendee_api_password = internal_password() if self.attendee_api_password.blank?
  self.save unless self.new_record?

  response = internal_create_meeting(user, user_opts)
  unless response.nil?
    self.attendee_api_password = response[:attendeePW]
    self.moderator_api_password = response[:moderatorPW]
    self.create_time = response[:createTime]
    self.voice_bridge = response[:voiceBridge] if response.has_key?(:voiceBridge)

    unless self.new_record?
      self.save

      # creates the meeting object since the create was successful
      create_meeting_record(response[:metadata])

      # enqueue an update in the meeting with a small delay we assume to be
      # enough for the user to fully join the meeting
      Resque.enqueue(::BigbluebuttonMeetingUpdater, self.id, 10.seconds)
    end
  end

  response
end

#send_endObject

Sends a call to the BBB server to end the meeting.

Triggers API call: end.



154
155
156
157
158
159
160
161
162
# File 'app/models/bigbluebutton_room.rb', line 154

def send_end
  server = BigbluebuttonRails.configuration.select_server.call(self, :end)
  response = server.api.end_meeting(self.meetingid, self.moderator_api_password)

  # enqueue an update in the meeting to end it faster
  Resque.enqueue(::BigbluebuttonMeetingUpdater, self.id)

  response
end

#to_paramObject



269
270
271
# File 'app/models/bigbluebutton_room.rb', line 269

def to_param
  self.param
end

#unique_meetingidObject



306
307
308
309
310
311
# File 'app/models/bigbluebutton_room.rb', line 306

def unique_meetingid
  # GUID
  # Has to be globally unique in case more that one bigbluebutton_rails application is using
  # the same web conference server.
  "#{SecureRandom.uuid}-#{Time.now.to_i}"
end

#update_current_meeting_record(metadata = nil, force_not_ended = false) ⇒ Object

Updates the current meeting associated with this room



323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
# File 'app/models/bigbluebutton_room.rb', line 323

def update_current_meeting_record(=nil, force_not_ended=false)
  unless self.create_time.nil?
    attrs = {
      :running => self.running,
      :start_time => self.start_time.try(:utc)
    }
    # note: it's important to update the 'ended' attr so the meeting is
    # reopened in case it was mistakenly considered as ended
    attrs[:ended] = false if force_not_ended

    unless .nil?
      begin
        attrs[:creator_id] = [BigbluebuttonRails.configuration.].to_i
        attrs[:creator_name] = [BigbluebuttonRails.configuration.]
      rescue
        attrs[:creator_id] = nil
        attrs[:creator_name] = nil
      end
    end

    meeting = self.get_current_meeting
    meeting.update_attributes(attrs) if meeting.present?
  end
end

#user_role(params) ⇒ Object

Returns the role of the user based on the key given. The return value can be :moderator, :attendee, or nil if the key given does not match any of the room keys.

params

Hash with a key :key



237
238
239
240
241
242
243
244
245
246
247
# File 'app/models/bigbluebutton_room.rb', line 237

def user_role(params)
  role = nil
  if params && params.has_key?(:key)
    if self.moderator_key == params[:key]
      role = :moderator
    elsif self.attendee_key == params[:key]
      role = :attendee
    end
  end
  role
end