Class: BigbluebuttonRoom

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

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#current_attendeesObject

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



66
67
68
# File 'app/models/bigbluebutton_room.rb', line 66

def current_attendees
  @current_attendees
end

#end_timeObject

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



66
67
68
# File 'app/models/bigbluebutton_room.rb', line 66

def end_time
  @end_time
end

#full_logout_urlObject

the full logout_url used when logout_url is a relative path



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

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



66
67
68
# File 'app/models/bigbluebutton_room.rb', line 66

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



66
67
68
# File 'app/models/bigbluebutton_room.rb', line 66

def moderator_count
  @moderator_count
end

#participant_countObject

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



66
67
68
# File 'app/models/bigbluebutton_room.rb', line 66

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.



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

def request_headers
  @request_headers
end

#runningObject

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



66
67
68
# File 'app/models/bigbluebutton_room.rb', line 66

def running
  @running
end

Class Method Details

.generate_dial_number(pattern = nil) ⇒ Object



552
553
554
555
556
557
558
559
560
561
# File 'app/models/bigbluebutton_room.rb', line 552

def self.generate_dial_number(pattern=nil)
  unless pattern.nil?
    unless BigbluebuttonRoom.maximum(:dial_number).nil?
      return BigbluebuttonRoom.maximum(:dial_number).next
    else
      return pattern.gsub('x', '0')
    end
    nil
  end
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



350
351
352
353
354
355
356
357
358
359
360
361
# File 'app/models/bigbluebutton_room.rb', line 350

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)


320
321
322
323
324
# File 'app/models/bigbluebutton_room.rb', line 320

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

#available_layoutsObject



524
525
526
527
# File 'app/models/bigbluebutton_room.rb', line 524

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

#available_layouts_for_selectObject



534
535
536
537
# File 'app/models/bigbluebutton_room.rb', line 534

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

#available_layouts_namesObject



529
530
531
532
# File 'app/models/bigbluebutton_room.rb', line 529

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

#create_meeting(user = nil, request = nil) ⇒ Object

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



333
334
335
336
337
338
339
340
341
342
343
344
345
346
# File 'app/models/bigbluebutton_room.rb', line 333

def create_meeting(user=nil, request=nil)
  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)
    true
  else
    false
  end
end

#create_meeting_record(response, server, user, user_opts) ⇒ Object



405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
# File 'app/models/bigbluebutton_room.rb', line 405

def create_meeting_record(response, server, user, user_opts)
  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

      attrs = {
        room: self,
        server_url: server.url,
        server_secret: server.secret,
        meetingid: self.meetingid,
        name: self.name,
        title: self.name,
        recorded: self.record_meeting,
        create_time: self.create_time,
        running: self.running,
        ended: false
      }

       = response[:metadata]
      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

      # the parameters the user might have overwritten in the create call
      # need to be mapped to the name of the attrs in BigbluebuttMeeting
      # note: recorded is not in the API response, so we can't just get these
      # attributes from there
      attrs_user = {
        meetingid: user_opts[:meetingID],
        name: user_opts[:name],
        recorded: user_opts[:record],
        creator_id: user_opts[:creator_id],
        creator_name: user_opts[:creator_name]
      }.delete_if { |k, v| v.nil? }
      attrs.merge!(attrs_user)

      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)


177
178
179
180
# File 'app/models/bigbluebutton_room.rb', line 177

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.current_attendees[0].user_name

The attributes changed are:

  • participant_count

  • moderator_count

  • running

  • has_been_forcibly_ended

  • create_time

  • end_time

  • current_attendees (array of BigbluebuttonAttendee)

Triggers API call: getMeetingInfo.



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'app/models/bigbluebutton_room.rb', line 137

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]
    @end_time = response[:endTime]
    @current_attendees = []
    if response[:attendees].present?
      response[:attendees].each do |att|
        attendee = BigbluebuttonAttendee.new
        attendee.from_hash(att)
        @current_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.



497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
# File 'app/models/bigbluebutton_room.rb', line 497

def fetch_new_token
  if self.room_options.is_modified? || block_given?
    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

    if block_given?
      config_xml = yield(config_xml)
    else
      # set the options on the XML
      # returns true if something was changed
      config_xml = self.room_options.set_on_config_xml(config_xml)
    end

    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



563
564
565
566
567
568
569
570
571
# File 'app/models/bigbluebutton_room.rb', line 563

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



460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
# File 'app/models/bigbluebutton_room.rb', line 460

def finish_meetings
  to_be_finished = BigbluebuttonMeeting.where(ended: false, room_id: self.id).to_a
  now = DateTime.now.strftime('%Q').to_i

  BigbluebuttonMeeting.where(ended: false)
    .where(room_id: self.id)
    .update_all(running: false, ended: true, finish_time: now)

  # 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, finish_time: now)

  if to_be_finished.count > 0
    # start trying to get the recording for this room
    # since we don't have a way to know exactly when a recording is done, we
    # have to keep polling the server for them
    # 3 times so it tries at: 4, 9, 14 and 19
    # no point trying more since there is a global synchronization process
    Resque.enqueue_in(1.minutes, ::BigbluebuttonRecordingsForRoomWorker, self.id, 10)
  end
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.



543
544
545
546
547
548
549
550
# File 'app/models/bigbluebutton_room.rb', line 543

def generate_dial_number!(pattern=nil)
  unless pattern.nil?
    dn = self.class.generate_dial_number(pattern)
    return self.update_attributes(dial_number: dn)
  else
    nil
  end
end

#get_current_meetingObject

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



371
372
373
374
375
376
377
# File 'app/models/bigbluebutton_room.rb', line 371

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



310
311
312
313
314
315
316
# File 'app/models/bigbluebutton_room.rb', line 310

def instance_variables_compare(o)
  vars = [ :@running, :@participant_count, :@moderator_count, :@current_attendees,
           :@has_been_forcibly_ended, :@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)


117
118
119
# File 'app/models/bigbluebutton_room.rb', line 117

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.



245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'app/models/bigbluebutton_room.rb', line 245

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

#parameterized_join_url(username, role, id, options = {}, user = nil) ⇒ Object



267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'app/models/bigbluebutton_room.rb', line 267

def parameterized_join_url(username, role, id, options={}, user=nil)
  opts = options.clone

  # gets the token with the configurations for this user/room
  if opts[:configToken].blank?
    token = self.fetch_new_token
    opts.merge!({ configToken: token }) unless token.blank?
  end

  # set the create time and the user id, if they exist
  opts.merge!({ createTime: self.create_time }) unless self.create_time.blank? || options[:createTime].present?
  opts.merge!({ userID: id }) unless id.blank? || options[:userID].present?

  # Get options passed by the application, if any
  user_opts = BigbluebuttonRails.configuration.get_join_options.call(self, user, { username: username, role: role })
  user_opts = {} if user_opts.blank?
  opts.merge!(user_opts)

  self.join_url(username, role, nil, opts)
end

#room_options_with_initializeObject

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



111
112
113
# File 'app/models/bigbluebutton_room.rb', line 111

def room_options_with_initialize
  room_options_without_initialize || build_room_options
end

#select_server(api_method = nil) ⇒ Object



573
574
575
576
577
578
579
580
# File 'app/models/bigbluebutton_room.rb', line 573

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) ⇒ 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.



206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'app/models/bigbluebutton_room.rb', line 206

def send_create(user=nil)
  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?

  # Get the user options to use when creating the meeting
  user_opts = BigbluebuttonRails.configuration.get_create_options.call(self, user)
  user_opts = {} if user_opts.blank?

  server, 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, server, user, user_opts)

      # 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(::BigbluebuttonMeetingUpdaterWorker, 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.



185
186
187
188
189
190
191
192
193
# File 'app/models/bigbluebutton_room.rb', line 185

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(::BigbluebuttonMeetingUpdaterWorker, self.id)

  response
end

#short_pathObject

Short URL for this room. Can be overwritten by applications that want to use a different route.



584
585
586
# File 'app/models/bigbluebutton_room.rb', line 584

def short_path
  Rails.application.routes.url_helpers.join_bigbluebutton_room_path(self)
end

#to_paramObject



326
327
328
# File 'app/models/bigbluebutton_room.rb', line 326

def to_param
  self.slug
end

#unique_meetingidObject



363
364
365
366
367
368
# File 'app/models/bigbluebutton_room.rb', line 363

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



380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
# File 'app/models/bigbluebutton_room.rb', line 380

def update_current_meeting_record(=nil, force_not_ended=false)
  unless self.create_time.nil?
    attrs = {
      :running => self.running,
      :create_time => self.create_time
    }
    # 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



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

def user_role(params)
  role = nil
  key = params.is_a?(String) ? params : (params && params.has_key?(:key) ? params[:key] : nil)

  unless key.blank?
    if self.moderator_key == key
      role = :moderator
    elsif self.attendee_key == key
      role = :attendee
    end
  end
  role
end