bigbluebutton_rails 1.2.0 → 1.3.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- data/.ruby-version +1 -0
- data/.travis.yml +2 -2
- data/CHANGELOG.rdoc +15 -1
- data/Gemfile +15 -4
- data/Gemfile.lock +187 -125
- data/LICENSE +24 -0
- data/README.rdoc +96 -19
- data/Rakefile +32 -16
- data/TODO_08 +9 -10
- data/{spec/rails_app/public → app/assets}/stylesheets/bigbluebutton_rails.css +58 -79
- data/app/controllers/bigbluebutton/recordings_controller.rb +142 -0
- data/app/controllers/bigbluebutton/rooms_controller.rb +105 -50
- data/app/controllers/bigbluebutton/servers_controller.rb +78 -2
- data/app/helpers/bigbluebutton_rails_helper.rb +11 -4
- data/app/models/bigbluebutton_metadata.rb +43 -0
- data/app/models/bigbluebutton_playback_format.rb +9 -0
- data/app/models/bigbluebutton_recording.rb +191 -0
- data/app/models/bigbluebutton_room.rb +92 -66
- data/app/models/bigbluebutton_server.rb +49 -3
- data/app/views/bigbluebutton/recordings/_form.html.erb +67 -0
- data/app/views/bigbluebutton/recordings/_recordings.html.erb +56 -0
- data/app/views/bigbluebutton/recordings/edit.html.erb +5 -0
- data/app/views/bigbluebutton/recordings/index.html.erb +2 -0
- data/app/views/bigbluebutton/recordings/show.html.erb +84 -0
- data/app/views/bigbluebutton/rooms/_form.html.erb +28 -7
- data/app/views/bigbluebutton/rooms/_rooms.html.erb +19 -4
- data/app/views/bigbluebutton/rooms/edit.html.erb +0 -1
- data/app/views/bigbluebutton/rooms/invite.html.erb +2 -1
- data/app/views/bigbluebutton/rooms/join.html.erb +0 -2
- data/app/views/bigbluebutton/rooms/recordings.html.erb +2 -0
- data/app/views/bigbluebutton/rooms/show.html.erb +35 -15
- data/app/views/bigbluebutton/servers/_form.html.erb +4 -0
- data/app/views/bigbluebutton/servers/index.html.erb +8 -3
- data/app/views/bigbluebutton/servers/recordings.html.erb +2 -0
- data/app/views/bigbluebutton/servers/rooms.html.erb +1 -1
- data/app/views/bigbluebutton/servers/show.html.erb +54 -6
- data/bigbluebutton_rails.gemspec +1 -1
- data/config/locales/en.yml +105 -55
- data/config/schedule.rb +3 -0
- data/lib/bigbluebutton_rails.rb +52 -2
- data/lib/bigbluebutton_rails/controller_methods.rb +31 -0
- data/lib/bigbluebutton_rails/rails/routes.rb +50 -14
- data/lib/bigbluebutton_rails/version.rb +1 -1
- data/lib/generators/bigbluebutton_rails/install_generator.rb +8 -8
- data/lib/generators/bigbluebutton_rails/templates/migration.rb +37 -1
- data/lib/generators/bigbluebutton_rails/templates/migration_1_3_0.rb +55 -0
- data/lib/generators/bigbluebutton_rails/templates/migration_1_3_0_b.rb +13 -0
- data/lib/generators/bigbluebutton_rails/views_generator.rb +6 -2
- data/lib/tasks/bigbluebutton_rails/recordings.rake +18 -0
- data/spec/bigbluebutton_rails_spec.rb +2 -2
- data/spec/controllers/bigbluebutton/recordings_controller_json_responses_spec.rb +111 -0
- data/spec/controllers/bigbluebutton/recordings_controller_spec.rb +188 -0
- data/spec/controllers/bigbluebutton/rooms_controller_exception_handling_spec.rb +15 -10
- data/spec/controllers/bigbluebutton/rooms_controller_json_responses_spec.rb +14 -7
- data/spec/controllers/bigbluebutton/rooms_controller_spec.rb +327 -103
- data/spec/controllers/bigbluebutton/servers_controller_json_responses_spec.rb +12 -12
- data/spec/controllers/bigbluebutton/servers_controller_spec.rb +166 -28
- data/spec/factories/bigbluebutton_metadata.rb +11 -0
- data/spec/factories/bigbluebutton_playback_format.rb +8 -0
- data/spec/factories/bigbluebutton_recording.rb +13 -0
- data/spec/factories/bigbluebutton_room.rb +2 -1
- data/spec/factories/bigbluebutton_server.rb +1 -1
- data/spec/factories/user.rb +1 -0
- data/spec/generators/install_generator_spec.rb +21 -10
- data/spec/generators/views_generator_spec.rb +14 -4
- data/spec/models/bigbluebutton_metadata_db_spec.rb +13 -0
- data/spec/models/bigbluebutton_metadata_spec.rb +64 -0
- data/spec/models/bigbluebutton_playback_format_db_spec.rb +13 -0
- data/spec/models/bigbluebutton_playback_format_spec.rb +22 -0
- data/spec/models/bigbluebutton_recording_db_spec.rb +24 -0
- data/spec/models/bigbluebutton_recording_spec.rb +450 -0
- data/spec/models/bigbluebutton_room_db_spec.rb +3 -2
- data/spec/models/bigbluebutton_room_spec.rb +194 -166
- data/spec/models/bigbluebutton_server_spec.rb +107 -27
- data/spec/rails_app/.gitignore +2 -0
- data/spec/rails_app/app/assets/javascripts/application.js +2 -0
- data/spec/rails_app/app/controllers/application_controller.rb +2 -0
- data/spec/rails_app/app/controllers/custom_recordings_controller.rb +8 -0
- data/spec/rails_app/app/controllers/frontpage_controller.rb +7 -0
- data/spec/rails_app/app/views/frontpage/show.html.erb +5 -0
- data/spec/rails_app/app/views/layouts/application.html.erb +5 -2
- data/spec/rails_app/config/application.rb +13 -0
- data/spec/rails_app/config/environments/development.rb +7 -2
- data/spec/rails_app/config/environments/production.rb +9 -0
- data/spec/rails_app/config/environments/test.rb +10 -0
- data/spec/rails_app/config/routes.rb +20 -2
- data/spec/rails_app/features/edit_rooms.feature +2 -3
- data/spec/rails_app/features/edit_servers.feature +2 -3
- data/spec/rails_app/features/step_definitions/activity_monitor_servers_step.rb +2 -2
- data/spec/rails_app/features/step_definitions/common_steps.rb +5 -5
- data/spec/rails_app/features/step_definitions/create_rooms_steps.rb +2 -4
- data/spec/rails_app/features/step_definitions/create_servers_steps.rb +2 -2
- data/spec/rails_app/features/step_definitions/edit_rooms_steps.rb +2 -2
- data/spec/rails_app/features/step_definitions/edit_servers_steps.rb +2 -2
- data/spec/rails_app/features/step_definitions/join_rooms_steps.rb +2 -2
- data/spec/rails_app/features/step_definitions/list_and_show_rooms_steps.rb +2 -2
- data/spec/rails_app/features/support/templates.rb +2 -6
- data/spec/rails_app/lib/tasks/db/populate.rake +119 -0
- data/spec/routing/bigbluebutton/custom_controllers_routing_spec.rb +78 -1
- data/spec/routing/bigbluebutton/recordings_only_routing_spec.rb +43 -0
- data/spec/routing/bigbluebutton/recordings_routing_spec.rb +46 -0
- data/spec/routing/bigbluebutton/rooms_only_routing_spec.rb +67 -0
- data/spec/routing/bigbluebutton/rooms_routing_spec.rb +28 -2
- data/spec/routing/bigbluebutton/servers_only_routing_spec.rb +43 -0
- data/spec/routing/bigbluebutton/servers_routing_spec.rb +16 -1
- data/spec/support/matchers/have_same_attributes_as.rb +20 -3
- data/spec/support/matchers/shoulda/{be_boolean.rb → be_boolean_matcher.rb} +0 -0
- data/spec/support/mocked_server.rb +19 -3
- metadata +48 -28
- data/public/images/loading.gif +0 -0
- data/public/javascripts/jquery.min.js +0 -16
- data/public/stylesheets/bigbluebutton_rails.css +0 -148
- data/spec/rails_app/public/images/loading.gif +0 -0
- data/spec/rails_app/public/images/rails.png +0 -0
- data/spec/rails_app/public/index.html +0 -239
- data/spec/rails_app/public/javascripts/application.js +0 -2
- data/spec/rails_app/public/javascripts/controls.js +0 -965
- data/spec/rails_app/public/javascripts/dragdrop.js +0 -974
- data/spec/rails_app/public/javascripts/effects.js +0 -1123
- data/spec/rails_app/public/javascripts/jquery.min.js +0 -16
- data/spec/rails_app/public/javascripts/prototype.js +0 -6001
- data/spec/rails_app/public/javascripts/rails.js +0 -191
- data/spec/support/shared_examples/rooms_controller.rb +0 -37
@@ -1,17 +1,24 @@
|
|
1
1
|
module BigbluebuttonRailsHelper
|
2
2
|
|
3
|
-
# Returns the URL for a QR-code image
|
3
|
+
# Returns the URL for a QR-code image using Google Charts API
|
4
4
|
def qrcode_url(content, size=nil)
|
5
5
|
size ||= "200x200"
|
6
6
|
content = CGI::escape(content)
|
7
7
|
"https://chart.googleapis.com/chart?cht=qr&chs=#{size}&chl=#{content}&choe=UTF-8"
|
8
8
|
end
|
9
9
|
|
10
|
-
# TODO: improve it, showing all flashes
|
11
10
|
def bbb_rails_error_explanation
|
12
|
-
|
13
|
-
|
11
|
+
msgs = ""
|
12
|
+
flash.each do |key, msg|
|
13
|
+
msgs += content_tag(:div, msg, { :id => "error_explanation", :class => key })
|
14
14
|
end
|
15
|
+
msgs.html_safe
|
16
|
+
end
|
17
|
+
|
18
|
+
# Setup a BigbluebuttonRoom to show in the forms
|
19
|
+
def setup_bigbluebutton_room(room)
|
20
|
+
(room.metadata.count..10).each { room.metadata.build }
|
21
|
+
room
|
15
22
|
end
|
16
23
|
|
17
24
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
class BigbluebuttonMetadata < ActiveRecord::Base
|
2
|
+
belongs_to :owner, :polymorphic => true, :inverse_of => :metadata
|
3
|
+
|
4
|
+
validates :owner_id, :presence => true
|
5
|
+
validates :owner_type, :presence => true
|
6
|
+
|
7
|
+
validates :name, :presence => true
|
8
|
+
validates :name, :format => {
|
9
|
+
:with => /^[a-zA-Z]+[a-zA-Z\d-]*$/,
|
10
|
+
:message => I18n.t('bigbluebutton_rails.metadata.errors.name_format')
|
11
|
+
}
|
12
|
+
validates :name,
|
13
|
+
:uniqueness => { :scope => [:owner_id, :owner_type] }
|
14
|
+
validate :exclusion_of_name_in_reserved_metadata_keys
|
15
|
+
|
16
|
+
attr_accessible :name, :content
|
17
|
+
|
18
|
+
# metadata keys are only invalid when the metadata belongs to a room,
|
19
|
+
# metadata that will be used in a 'create' call
|
20
|
+
# TODO: a better solution for rails >= 3.1
|
21
|
+
# validates :name, :exclusion => {
|
22
|
+
# :in => lambda do |m|
|
23
|
+
# if m.owner_type == "BigbluebuttonRoom"
|
24
|
+
# BigbluebuttonRails.metadata_invalid_keys.map(&:to_s)
|
25
|
+
# else
|
26
|
+
# []
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
# }
|
30
|
+
def exclusion_of_name_in_reserved_metadata_keys
|
31
|
+
keys = if owner_type == "BigbluebuttonRoom"
|
32
|
+
BigbluebuttonRails.metadata_invalid_keys.map(&:to_s)
|
33
|
+
else
|
34
|
+
[]
|
35
|
+
end
|
36
|
+
if keys.include?(name)
|
37
|
+
# use the same message rails would generate for :exclusion => :in
|
38
|
+
msg = self.errors.generate_message(:name, :exclusion, {})
|
39
|
+
errors.add(:name, msg)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
class BigbluebuttonPlaybackFormat < ActiveRecord::Base
|
2
|
+
belongs_to :recording, :class_name => 'BigbluebuttonRecording'
|
3
|
+
|
4
|
+
validates :recording_id, :presence => true
|
5
|
+
|
6
|
+
validates :format_type, :presence => true
|
7
|
+
|
8
|
+
attr_accessible :recording_id, :format_type, :url, :length
|
9
|
+
end
|
@@ -0,0 +1,191 @@
|
|
1
|
+
class BigbluebuttonRecording < ActiveRecord::Base
|
2
|
+
belongs_to :server, :class_name => 'BigbluebuttonServer'
|
3
|
+
belongs_to :room, :class_name => 'BigbluebuttonRoom'
|
4
|
+
|
5
|
+
validates :server, :presence => true
|
6
|
+
|
7
|
+
validates :recordid,
|
8
|
+
:presence => true,
|
9
|
+
:uniqueness => true
|
10
|
+
|
11
|
+
attr_accessible :recordid, :meetingid, :name, :published, :start_time,
|
12
|
+
:end_time, :available
|
13
|
+
|
14
|
+
has_many :metadata,
|
15
|
+
:class_name => 'BigbluebuttonMetadata',
|
16
|
+
:as => :owner,
|
17
|
+
:dependent => :destroy
|
18
|
+
|
19
|
+
has_many :playback_formats,
|
20
|
+
:class_name => 'BigbluebuttonPlaybackFormat',
|
21
|
+
:foreign_key => 'recording_id',
|
22
|
+
:dependent => :destroy
|
23
|
+
|
24
|
+
scope :published, where(:published => true)
|
25
|
+
|
26
|
+
def to_param
|
27
|
+
self.recordid
|
28
|
+
end
|
29
|
+
|
30
|
+
# Syncs the recordings in the db with the array of recordings in 'recordings',
|
31
|
+
# as received from BigBlueButtonApi#get_recordings.
|
32
|
+
# Will add new recordings that are not in the db yet and update the ones that
|
33
|
+
# already are (matching by 'recordid'). Will NOT delete recordings from the db
|
34
|
+
# if they are not in the array but instead mark them as unavailable.
|
35
|
+
# 'server' is the BigbluebuttonServer object from which the recordings
|
36
|
+
# were fetched.
|
37
|
+
#
|
38
|
+
# TODO: catch exceptions on creating/updating recordings
|
39
|
+
def self.sync(server, recordings)
|
40
|
+
recordings.each do |rec|
|
41
|
+
rec_obj = BigbluebuttonRecording.find_by_recordid(rec[:recordID])
|
42
|
+
rec_data = adapt_recording_hash(rec)
|
43
|
+
BigbluebuttonRecording.transaction do
|
44
|
+
if rec_obj
|
45
|
+
logger.info "Sync recordings: updating recording #{rec_obj.inspect}"
|
46
|
+
logger.debug "Sync recordings: recording data #{rec_data.inspect}"
|
47
|
+
self.update_recording(server, rec_obj, rec_data)
|
48
|
+
else
|
49
|
+
logger.info "Sync recordings: creating recording"
|
50
|
+
logger.debug "Sync recordings: recording data #{rec_data.inspect}"
|
51
|
+
self.create_recording(server, rec_data)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# set as unavailable the recordings that are not in 'recordings'
|
57
|
+
recordIDs = recordings.map{ |rec| rec[:recordID] }
|
58
|
+
if recordIDs.length <= 0 # empty response
|
59
|
+
BigbluebuttonRecording
|
60
|
+
.where(:available => true)
|
61
|
+
.update_all(:available => false)
|
62
|
+
else
|
63
|
+
BigbluebuttonRecording
|
64
|
+
.where(:available => true)
|
65
|
+
.where("recordid NOT IN (?)", recordIDs)
|
66
|
+
.update_all(:available => false)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
protected
|
71
|
+
|
72
|
+
# Adapt keys in 'hash' from bigbluebutton-api-ruby's format to ours
|
73
|
+
def self.adapt_recording_hash(hash)
|
74
|
+
new_hash = hash.clone
|
75
|
+
mappings = {
|
76
|
+
:recordID => :recordid,
|
77
|
+
:meetingID => :meetingid,
|
78
|
+
:startTime => :start_time,
|
79
|
+
:endTime => :end_time
|
80
|
+
}
|
81
|
+
new_hash.keys.each { |k| new_hash[ mappings[k] ] = new_hash.delete(k) if mappings[k] }
|
82
|
+
new_hash
|
83
|
+
end
|
84
|
+
|
85
|
+
# Updates the BigbluebuttonRecording 'recording' with the data in the hash 'data'.
|
86
|
+
# The format expected for 'data' follows the format returned by
|
87
|
+
# BigBlueButtonApi#get_recordings but with the keys already converted to our format.
|
88
|
+
def self.update_recording(server, recording, data)
|
89
|
+
recording.server = server
|
90
|
+
recording.room = BigbluebuttonRails.match_room_recording(data)
|
91
|
+
recording.attributes = data.slice(:meetingid, :name, :published, :start_time, :end_time)
|
92
|
+
recording.available = true
|
93
|
+
recording.save!
|
94
|
+
|
95
|
+
sync_additional_data(recording, data)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Creates a new BigbluebuttonRecording with the data from 'data'.
|
99
|
+
# The format expected for 'data' follows the format returned by
|
100
|
+
# BigBlueButtonApi#get_recordings but with the keys already converted to our format.
|
101
|
+
def self.create_recording(server, data)
|
102
|
+
filtered = data.slice(:recordid, :meetingid, :name, :published, :start_time, :end_time)
|
103
|
+
recording = BigbluebuttonRecording.create(filtered)
|
104
|
+
recording.available = true
|
105
|
+
recording.room = BigbluebuttonRails.match_room_recording(data)
|
106
|
+
recording.server = server
|
107
|
+
recording.save!
|
108
|
+
|
109
|
+
sync_additional_data(recording, data)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Syncs data that's not directly stored in the recording itself but in
|
113
|
+
# associated models (e.g. metadata and playback formats).
|
114
|
+
# The format expected for 'data' follows the format returned by
|
115
|
+
# BigBlueButtonApi#get_recordings but with the keys already converted to our format.
|
116
|
+
def self.sync_additional_data(recording, data)
|
117
|
+
sync_metadata(recording, data[:metadata]) if data[:metadata]
|
118
|
+
if data[:playback] and data[:playback][:format]
|
119
|
+
sync_playback_formats(recording, data[:playback][:format])
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# Syncs the metadata objects of 'recording' with the data in 'metadata'.
|
124
|
+
# The format expected for 'metadata' follows the format returned by
|
125
|
+
# BigBlueButtonApi#get_recordings but with the keys already converted to our format.
|
126
|
+
def self.sync_metadata(recording, metadata)
|
127
|
+
# keys are stored as strings in the db
|
128
|
+
received_metadata = metadata.clone.stringify_keys
|
129
|
+
|
130
|
+
query = { :owner_id => recording.id, :owner_type => recording.class.to_s }
|
131
|
+
BigbluebuttonMetadata.where(query).each do |meta_db|
|
132
|
+
|
133
|
+
# the metadata in the db is also in the received data, update it in the db
|
134
|
+
if received_metadata.has_key?(meta_db.name)
|
135
|
+
meta_db.update_attributes({ :content => received_metadata[meta_db.name] })
|
136
|
+
received_metadata.delete(meta_db.name)
|
137
|
+
|
138
|
+
# the metadata is not in the received data, remove from the db
|
139
|
+
else
|
140
|
+
meta_db.destroy
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# for metadata that are not in the db yet
|
145
|
+
received_metadata.each do |name, content|
|
146
|
+
attrs = {
|
147
|
+
:name => name,
|
148
|
+
:content => content,
|
149
|
+
}
|
150
|
+
meta = BigbluebuttonMetadata.create(attrs)
|
151
|
+
meta.owner = recording
|
152
|
+
meta.save!
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# Syncs the playback formats objects of 'recording' with the data in 'formats'.
|
157
|
+
# The format expected for 'formats' follows the format returned by
|
158
|
+
# BigBlueButtonApi#get_recordings but with the keys already converted to our format.
|
159
|
+
def self.sync_playback_formats(recording, formats)
|
160
|
+
formats_copy = formats.clone
|
161
|
+
|
162
|
+
# make it an array if it's a hash with a single format
|
163
|
+
formats_copy = [ formats_copy ] if formats_copy.is_a?(Hash)
|
164
|
+
|
165
|
+
BigbluebuttonPlaybackFormat.where(:recording_id => recording.id).each do |format_db|
|
166
|
+
format = formats_copy.select{ |d|
|
167
|
+
!d[:type].blank? and d[:type] == format_db.format_type
|
168
|
+
}.first
|
169
|
+
|
170
|
+
# the format exists in the hash, update it in the db
|
171
|
+
if format
|
172
|
+
format_db.update_attributes({ :url => format[:url], :length => format[:length] })
|
173
|
+
formats_copy.delete(format)
|
174
|
+
|
175
|
+
# the format is not in the hash, remove from the db
|
176
|
+
else
|
177
|
+
format_db.destroy
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# for formats that are not in the db yet
|
182
|
+
formats_copy.each do |format|
|
183
|
+
unless format[:type].blank?
|
184
|
+
attrs = { :recording_id => recording.id, :format_type => format[:type],
|
185
|
+
:url => format[:url], :length => format[:length].to_i }
|
186
|
+
BigbluebuttonPlaybackFormat.create!(attrs)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
end
|
@@ -2,20 +2,39 @@ class BigbluebuttonRoom < ActiveRecord::Base
|
|
2
2
|
belongs_to :server, :class_name => 'BigbluebuttonServer'
|
3
3
|
belongs_to :owner, :polymorphic => true
|
4
4
|
|
5
|
+
has_many :recordings,
|
6
|
+
:class_name => 'BigbluebuttonRecording',
|
7
|
+
:foreign_key => 'room_id',
|
8
|
+
:dependent => :nullify
|
9
|
+
|
10
|
+
has_many :metadata,
|
11
|
+
:class_name => 'BigbluebuttonMetadata',
|
12
|
+
:as => :owner,
|
13
|
+
:dependent => :destroy,
|
14
|
+
:inverse_of => :owner
|
15
|
+
|
16
|
+
accepts_nested_attributes_for :metadata,
|
17
|
+
:allow_destroy => true,
|
18
|
+
:reject_if => :all_blank
|
19
|
+
|
5
20
|
validates :meetingid, :presence => true, :uniqueness => true,
|
6
21
|
:length => { :minimum => 1, :maximum => 100 }
|
7
22
|
validates :name, :presence => true, :uniqueness => true,
|
8
23
|
:length => { :minimum => 1, :maximum => 150 }
|
9
24
|
validates :welcome_msg, :length => { :maximum => 250 }
|
10
25
|
validates :private, :inclusion => { :in => [true, false] }
|
11
|
-
validates :randomize_meetingid, :inclusion => { :in => [true, false] }
|
12
26
|
validates :voice_bridge, :presence => true, :uniqueness => true
|
27
|
+
validates :record, :inclusion => { :in => [true, false] }
|
28
|
+
|
29
|
+
validates :duration,
|
30
|
+
:presence => true,
|
31
|
+
:numericality => { :only_integer => true, :greater_than_or_equal_to => 0 }
|
13
32
|
|
14
33
|
validates :param,
|
15
34
|
:presence => true,
|
16
35
|
:uniqueness => true,
|
17
|
-
:length => { :minimum =>
|
18
|
-
:format => { :with => /^[a-zA-Z\d_]+[a-zA-Z\d_-]*[a-zA-Z\d_]
|
36
|
+
:length => { :minimum => 1 },
|
37
|
+
:format => { :with => /^([a-zA-Z\d_]|[a-zA-Z\d_]+[a-zA-Z\d_-]*[a-zA-Z\d_]+)$/,
|
19
38
|
:message => I18n.t('bigbluebutton_rails.rooms.errors.param_format') }
|
20
39
|
|
21
40
|
# Passwords are 16 character strings
|
@@ -28,8 +47,8 @@ class BigbluebuttonRoom < ActiveRecord::Base
|
|
28
47
|
|
29
48
|
attr_accessible :name, :server_id, :meetingid, :attendee_password, :moderator_password,
|
30
49
|
:welcome_msg, :owner, :server, :private, :logout_url, :dial_number,
|
31
|
-
:voice_bridge, :max_participants, :owner_id, :owner_type,
|
32
|
-
:external, :param
|
50
|
+
:voice_bridge, :max_participants, :owner_id, :owner_type,
|
51
|
+
:external, :param, :record, :duration, :metadata_attributes
|
33
52
|
|
34
53
|
# Note: these params need to be fetched from the server before being accessed
|
35
54
|
attr_accessor :running, :participant_count, :moderator_count, :attendees,
|
@@ -37,10 +56,16 @@ class BigbluebuttonRoom < ActiveRecord::Base
|
|
37
56
|
|
38
57
|
after_initialize :init
|
39
58
|
before_validation :set_param
|
59
|
+
before_validation :set_passwords
|
40
60
|
|
41
61
|
# the full logout_url used when logout_url is a relative path
|
42
62
|
attr_accessor :full_logout_url
|
43
63
|
|
64
|
+
# HTTP headers that will be passed to the BigBlueButtonApi object to send
|
65
|
+
# in all GET/POST requests to a webconf server.
|
66
|
+
# Currently used to send the client's IP to the load balancer.
|
67
|
+
attr_accessor :request_headers
|
68
|
+
|
44
69
|
# Convenience method to access the attribute <tt>running</tt>
|
45
70
|
def is_running?
|
46
71
|
@running
|
@@ -61,7 +86,7 @@ class BigbluebuttonRoom < ActiveRecord::Base
|
|
61
86
|
# * <tt>end_time</tt>
|
62
87
|
# * <tt>attendees</tt> (array of <tt>BigbluebuttonAttendee</tt>)
|
63
88
|
#
|
64
|
-
# Triggers API call: <tt>
|
89
|
+
# Triggers API call: <tt>getMeetingInfo</tt>.
|
65
90
|
def fetch_meeting_info
|
66
91
|
require_server
|
67
92
|
|
@@ -85,7 +110,7 @@ class BigbluebuttonRoom < ActiveRecord::Base
|
|
85
110
|
|
86
111
|
# Fetches the BBB server to see if the meeting is running. Sets <tt>running</tt>
|
87
112
|
#
|
88
|
-
# Triggers API call: <tt>
|
113
|
+
# Triggers API call: <tt>isMeetingRunning</tt>.
|
89
114
|
def fetch_is_running?
|
90
115
|
require_server
|
91
116
|
@running = self.server.api.is_meeting_running?(self.meetingid)
|
@@ -93,13 +118,15 @@ class BigbluebuttonRoom < ActiveRecord::Base
|
|
93
118
|
|
94
119
|
# Sends a call to the BBB server to end the meeting.
|
95
120
|
#
|
96
|
-
# Triggers API call: <tt>
|
121
|
+
# Triggers API call: <tt>end</tt>.
|
97
122
|
def send_end
|
98
123
|
require_server
|
99
124
|
self.server.api.end_meeting(self.meetingid, self.moderator_password)
|
100
125
|
end
|
101
126
|
|
102
127
|
# Sends a call to the BBB server to create the meeting.
|
128
|
+
# 'username' is the name of the user that is creating the meeting.
|
129
|
+
# 'userid' is the id of the user that is creating the meeting.
|
103
130
|
#
|
104
131
|
# Will trigger 'select_server' to select a server where the meeting
|
105
132
|
# will be created. If a server is selected, the model is saved.
|
@@ -108,37 +135,15 @@ class BigbluebuttonRoom < ActiveRecord::Base
|
|
108
135
|
# * <tt>attendee_password</tt>
|
109
136
|
# * <tt>moderator_password</tt>
|
110
137
|
#
|
111
|
-
# Triggers API call: <tt>
|
112
|
-
def send_create
|
113
|
-
# updates the server whenever a meeting will be created
|
138
|
+
# Triggers API call: <tt>create</tt>.
|
139
|
+
def send_create(username=nil, userid=nil)
|
140
|
+
# updates the server whenever a meeting will be created and guarantees it has a meetingid
|
114
141
|
self.server = select_server
|
142
|
+
self.meetingid = unique_meetingid() if self.meetingid.nil?
|
115
143
|
self.save unless self.new_record?
|
116
144
|
require_server
|
117
145
|
|
118
|
-
|
119
|
-
response = do_create_meeting
|
120
|
-
|
121
|
-
# create a new random meetingid everytime create fails with "duplicateWarning"
|
122
|
-
else
|
123
|
-
self.meetingid = random_meetingid
|
124
|
-
|
125
|
-
count = 0
|
126
|
-
try_again = true
|
127
|
-
while try_again and count < 10
|
128
|
-
response = do_create_meeting
|
129
|
-
|
130
|
-
count += 1
|
131
|
-
try_again = false
|
132
|
-
unless response.nil?
|
133
|
-
if response[:returncode] && response[:messageKey] == "duplicateWarning"
|
134
|
-
self.meetingid = random_meetingid
|
135
|
-
try_again = true
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
146
|
+
response = do_create_meeting(username, userid)
|
142
147
|
unless response.nil?
|
143
148
|
self.attendee_password = response[:attendeePW]
|
144
149
|
self.moderator_password = response[:moderatorPW]
|
@@ -208,25 +213,18 @@ class BigbluebuttonRoom < ActiveRecord::Base
|
|
208
213
|
self.param
|
209
214
|
end
|
210
215
|
|
211
|
-
# The
|
212
|
-
#
|
213
|
-
#
|
214
|
-
def
|
216
|
+
# The create logic.
|
217
|
+
# Will create the meeting in this room unless it is already running.
|
218
|
+
# Returns true if the meeting was created.
|
219
|
+
def create_meeting(username, userid=nil, request=nil)
|
215
220
|
fetch_is_running?
|
216
|
-
|
217
|
-
# if the user is a moderator, create the room (if needed) and join it
|
218
|
-
if role == :moderator
|
221
|
+
unless is_running?
|
219
222
|
add_domain_to_logout_url(request.protocol, request.host_with_port) unless request.nil?
|
220
|
-
send_create
|
221
|
-
|
222
|
-
|
223
|
-
# normal user only joins if the conference is running
|
224
|
-
# if it's not, wait for a moderator to create the conference
|
223
|
+
send_create(username, userid)
|
224
|
+
true
|
225
225
|
else
|
226
|
-
|
226
|
+
false
|
227
227
|
end
|
228
|
-
|
229
|
-
ret
|
230
228
|
end
|
231
229
|
|
232
230
|
# add a domain name and/or protocol to the logout_url if needed
|
@@ -244,6 +242,13 @@ class BigbluebuttonRoom < ActiveRecord::Base
|
|
244
242
|
end
|
245
243
|
end
|
246
244
|
|
245
|
+
def unique_meetingid
|
246
|
+
# GUID
|
247
|
+
# Has to be globally unique in case more that one bigbluebutton_rails application is using
|
248
|
+
# the same web conference server.
|
249
|
+
"#{SecureRandom.uuid}-#{Time.now.to_i}"
|
250
|
+
end
|
251
|
+
|
247
252
|
protected
|
248
253
|
|
249
254
|
# Every room needs a server to be used.
|
@@ -269,9 +274,11 @@ class BigbluebuttonRoom < ActiveRecord::Base
|
|
269
274
|
end
|
270
275
|
|
271
276
|
def init
|
272
|
-
self[:meetingid] ||=
|
277
|
+
self[:meetingid] ||= unique_meetingid
|
273
278
|
self[:voice_bridge] ||= random_voice_bridge
|
274
279
|
|
280
|
+
@request_headers = {}
|
281
|
+
|
275
282
|
# fetched attributes
|
276
283
|
@participant_count = 0
|
277
284
|
@moderator_count = 0
|
@@ -282,15 +289,6 @@ class BigbluebuttonRoom < ActiveRecord::Base
|
|
282
289
|
@attendees = []
|
283
290
|
end
|
284
291
|
|
285
|
-
def random_meetingid
|
286
|
-
# TODO temporarily using the name to get a friendlier meetingid
|
287
|
-
if self[:name].blank?
|
288
|
-
SecureRandom.hex(8)
|
289
|
-
else
|
290
|
-
self[:name] + '-' + SecureRandom.random_number(9999).to_s
|
291
|
-
end
|
292
|
-
end
|
293
|
-
|
294
292
|
def random_voice_bridge
|
295
293
|
value = (70000 + SecureRandom.random_number(9999)).to_s
|
296
294
|
count = 1
|
@@ -301,14 +299,24 @@ class BigbluebuttonRoom < ActiveRecord::Base
|
|
301
299
|
value
|
302
300
|
end
|
303
301
|
|
304
|
-
def do_create_meeting
|
305
|
-
msg = (self.welcome_msg.nil? or self.welcome_msg.empty?) ? default_welcome_message : self.welcome_msg
|
302
|
+
def do_create_meeting(username=nil, userid=nil)
|
306
303
|
opts = {
|
307
|
-
:
|
308
|
-
:
|
304
|
+
:record => self.record,
|
305
|
+
:duration => self.duration,
|
306
|
+
:moderatorPW => self.moderator_password,
|
307
|
+
:attendeePW => self.attendee_password,
|
308
|
+
:welcome => self.welcome_msg.blank? ? default_welcome_message : self.welcome_msg,
|
309
|
+
:dialNumber => self.dial_number,
|
309
310
|
:logoutURL => self.full_logout_url || self.logout_url,
|
310
|
-
:maxParticipants => self.max_participants,
|
311
|
-
|
311
|
+
:maxParticipants => self.max_participants,
|
312
|
+
:voiceBridge => self.voice_bridge
|
313
|
+
}.merge(self.get_metadata_for_create)
|
314
|
+
|
315
|
+
# Add information about the user that is creating the meeting (if any)
|
316
|
+
opts.merge!({ "meta_#{BigbluebuttonRails.metadata_user_id}" => userid }) unless userid.nil?
|
317
|
+
opts.merge!({ "meta_#{BigbluebuttonRails.metadata_user_name}" => username }) unless username.nil?
|
318
|
+
|
319
|
+
self.server.api.request_headers = @request_headers # we need the client's IP
|
312
320
|
self.server.api.create_meeting(self.name, self.meetingid, opts)
|
313
321
|
end
|
314
322
|
|
@@ -327,4 +335,22 @@ class BigbluebuttonRoom < ActiveRecord::Base
|
|
327
335
|
end
|
328
336
|
end
|
329
337
|
|
338
|
+
# When setting a room as private we generate passwords in case they don't exist.
|
339
|
+
def set_passwords
|
340
|
+
if self.private_changed? and self.private
|
341
|
+
if self.moderator_password.blank?
|
342
|
+
self.moderator_password = SecureRandom.hex(4)
|
343
|
+
end
|
344
|
+
if self.attendee_password.blank?
|
345
|
+
self.attendee_password = SecureRandom.hex(4)
|
346
|
+
end
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
def get_metadata_for_create
|
351
|
+
self.metadata.inject({}) { |result, meta|
|
352
|
+
result["meta_#{meta.name}"] = meta.content; result
|
353
|
+
}
|
354
|
+
end
|
355
|
+
|
330
356
|
end
|