bigbluebutton_rails 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -3
  3. data/CHANGELOG.md +34 -0
  4. data/Dockerfile +23 -0
  5. data/Gemfile +3 -0
  6. data/Gemfile.lock +14 -5
  7. data/README.md +42 -59
  8. data/Rakefile +6 -3
  9. data/app/controllers/bigbluebutton/api/rooms_controller.rb +117 -0
  10. data/app/controllers/bigbluebutton/playback_types_controller.rb +0 -3
  11. data/app/controllers/bigbluebutton/recordings_controller.rb +37 -23
  12. data/app/controllers/bigbluebutton/rooms_controller.rb +6 -31
  13. data/app/controllers/bigbluebutton/servers_controller.rb +0 -14
  14. data/app/helpers/bigbluebutton_rails_helper.rb +4 -0
  15. data/app/models/bigbluebutton_attendee.rb +21 -0
  16. data/app/models/bigbluebutton_meeting.rb +5 -0
  17. data/app/models/bigbluebutton_playback_format.rb +3 -2
  18. data/app/models/bigbluebutton_recording.rb +164 -68
  19. data/app/models/bigbluebutton_room.rb +148 -59
  20. data/app/models/bigbluebutton_server.rb +1 -1
  21. data/app/views/bigbluebutton/api/error.rabl +10 -0
  22. data/app/views/bigbluebutton/api/rooms/index.rabl +45 -0
  23. data/app/views/bigbluebutton/api/rooms/join.rabl +6 -0
  24. data/app/views/bigbluebutton/api/rooms/running.rabl +10 -0
  25. data/app/views/bigbluebutton/recordings/play.html.erb +1 -0
  26. data/app/views/bigbluebutton/servers/_activity_list.html.erb +5 -5
  27. data/app/workers/{bigbluebutton_finish_meetings.rb → bigbluebutton_finish_meetings_worker.rb} +2 -2
  28. data/app/workers/{bigbluebutton_meeting_updater.rb → bigbluebutton_meeting_updater_worker.rb} +3 -3
  29. data/app/workers/bigbluebutton_recordings_for_room_worker.rb +27 -0
  30. data/app/workers/{bigbluebutton_update_recordings.rb → bigbluebutton_update_recordings_worker.rb} +2 -2
  31. data/app/workers/{bigbluebutton_update_server_configs.rb → bigbluebutton_update_server_configs_worker.rb} +3 -3
  32. data/bigbluebutton_rails.gemspec +1 -0
  33. data/config/locales/en.yml +21 -0
  34. data/config/locales/pt-br.yml +1 -0
  35. data/config/resque/resque.rake +7 -1
  36. data/config/resque/workers_schedule.yml +3 -3
  37. data/docker-compose.yml +68 -0
  38. data/dumb-init_1.2.0 +0 -0
  39. data/lib/bigbluebutton_rails.rb +1 -2
  40. data/lib/bigbluebutton_rails/api_controller_methods.rb +138 -0
  41. data/lib/bigbluebutton_rails/configuration.rb +33 -4
  42. data/lib/bigbluebutton_rails/controller_methods.rb +0 -31
  43. data/lib/bigbluebutton_rails/exceptions.rb +17 -0
  44. data/lib/bigbluebutton_rails/rails/routes.rb +14 -0
  45. data/lib/bigbluebutton_rails/version.rb +1 -1
  46. data/lib/generators/bigbluebutton_rails/templates/migration.rb +17 -3
  47. data/lib/generators/bigbluebutton_rails/templates/migration_2_2_0.rb +1 -1
  48. data/lib/generators/bigbluebutton_rails/templates/migration_2_2_0_b.rb +106 -0
  49. data/lib/generators/bigbluebutton_rails/templates/migration_2_3_0.rb +22 -0
  50. data/lib/tasks/bigbluebutton_rails/meetings.rake +1 -1
  51. data/spec/controllers/bigbluebutton/api/rooms_controller_spec.rb +498 -0
  52. data/spec/controllers/bigbluebutton/recordings_controller_spec.rb +84 -0
  53. data/spec/controllers/bigbluebutton/rooms_controller_exception_handling_spec.rb +2 -2
  54. data/spec/controllers/bigbluebutton/rooms_controller_spec.rb +24 -22
  55. data/spec/controllers/bigbluebutton/servers_controller_spec.rb +9 -0
  56. data/spec/factories/bigbluebutton_attendee.rb +7 -0
  57. data/spec/factories/bigbluebutton_meeting.rb +0 -1
  58. data/spec/factories/bigbluebutton_playback_type.rb +1 -0
  59. data/spec/factories/bigbluebutton_recording.rb +2 -4
  60. data/spec/helpers/bigbluebutton_rails_helper_spec.rb +10 -2
  61. data/spec/lib/bigbluebutton_rails/background_tasks_spec.rb +1 -0
  62. data/spec/lib/tasks/meetings_rake_spec.rb +1 -1
  63. data/spec/models/bigbluebutton_attendee_spec.rb +44 -0
  64. data/spec/models/bigbluebutton_meeting_db_spec.rb +0 -1
  65. data/spec/models/bigbluebutton_meeting_spec.rb +1 -0
  66. data/spec/models/bigbluebutton_playback_format_spec.rb +2 -0
  67. data/spec/models/bigbluebutton_playback_type_db_spec.rb +1 -0
  68. data/spec/models/bigbluebutton_recording_db_spec.rb +2 -2
  69. data/spec/models/bigbluebutton_recording_spec.rb +404 -72
  70. data/spec/models/bigbluebutton_room_spec.rb +435 -148
  71. data/spec/rails_app/config/database.yml.example +10 -11
  72. data/spec/rails_app/config/initializers/resque.rb +33 -0
  73. data/spec/rails_app/config/routes.rb +2 -0
  74. data/spec/rails_app/lib/tasks/db/populate.rake +73 -55
  75. data/spec/support/mocked_server.rb +1 -1
  76. data/spec/workers/{bigbluebutton_finish_meetings_spec.rb → bigbluebutton_finish_meetings_worker_spec.rb} +3 -3
  77. data/spec/workers/{bigbluebutton_meeting_updater_spec.rb → bigbluebutton_meeting_updater_worker_spec.rb} +8 -7
  78. data/spec/workers/bigbluebutton_recordings_for_room_worker_spec.rb +49 -0
  79. data/spec/workers/{bigbluebutton_update_recordings_spec.rb → bigbluebutton_update_recordings_worker_spec.rb} +3 -3
  80. data/spec/workers/{bigbluebutton_update_server_configs_spec.rb → bigbluebutton_update_server_configs_worker_spec.rb} +4 -4
  81. metadata +42 -14
  82. data/lib/classes/bigbluebutton_attendee.rb +0 -21
  83. data/spec/classes/bigbluebutton_attendee_spec.rb +0 -76
  84. data/spec/controllers/bigbluebutton/recordings_controller_json_responses_spec.rb +0 -111
  85. data/spec/controllers/bigbluebutton/rooms_controller_json_responses_spec.rb +0 -203
  86. data/spec/controllers/bigbluebutton/servers_controller_json_responses_spec.rb +0 -162
@@ -2,7 +2,6 @@ class Bigbluebutton::PlaybackTypesController < ApplicationController
2
2
  include BigbluebuttonRails::InternalControllerMethods
3
3
 
4
4
  respond_to :html
5
- respond_to :json
6
5
  before_filter :find_playback_type, only: [:update]
7
6
 
8
7
  def update
@@ -12,13 +11,11 @@ class Bigbluebutton::PlaybackTypesController < ApplicationController
12
11
  message = t('bigbluebutton_rails.playback_types.notice.update.success')
13
12
  redirect_to_using_params request.referer, :notice => message
14
13
  }
15
- format.json { render :json => true, :status => :ok }
16
14
  else
17
15
  format.html {
18
16
  flash[:error] = @playback_type.errors.full_messages.join(", ")
19
17
  redirect_to_using_params request.referer
20
18
  }
21
- format.json { render :json => @playback_type.errors.full_messages, :status => :unprocessable_entity }
22
19
  end
23
20
  end
24
21
  end
@@ -2,11 +2,21 @@ class Bigbluebutton::RecordingsController < ApplicationController
2
2
  include BigbluebuttonRails::InternalControllerMethods
3
3
 
4
4
  respond_to :html
5
- respond_to :json, :only => [:index, :show, :update, :destroy, :publish, :unpublish]
6
5
  before_filter :find_recording, :except => [:index]
7
6
  before_filter :check_for_server, :only => [:publish, :unpublish]
8
7
  before_filter :find_playback, :only => [:play]
9
8
 
9
+ layout :determine_layout
10
+
11
+ def determine_layout
12
+ case params[:action].to_sym
13
+ when :play
14
+ false
15
+ else
16
+ 'application'
17
+ end
18
+ end
19
+
10
20
  def index
11
21
  @recordings ||= BigbluebuttonRecording.all
12
22
  respond_with(@recordings)
@@ -27,10 +37,8 @@ class Bigbluebutton::RecordingsController < ApplicationController
27
37
  message = t('bigbluebutton_rails.recordings.notice.update.success')
28
38
  redirect_to_using_params @recording, :notice => message
29
39
  }
30
- format.json { render :json => true, :status => :ok }
31
40
  else
32
41
  format.html { redirect_to_params_or_render :edit }
33
- format.json { render :json => @recording.errors.full_messages, :status => :unprocessable_entity }
34
42
  end
35
43
  end
36
44
  end
@@ -60,26 +68,29 @@ class Bigbluebutton::RecordingsController < ApplicationController
60
68
  redirect_to_using_params bigbluebutton_recordings_url, :notice => message
61
69
  end
62
70
  }
63
- format.json {
64
- if error
65
- render :json => { :message => message }, :status => :error
66
- else
67
- render :json => { :message => message }
68
- end
69
- }
70
71
  end
71
72
  end
72
73
 
73
74
  def play
74
- respond_with do |format|
75
- format.html {
76
- if @playback
77
- redirect_to @playback.url
75
+ if @recording.present?
76
+ if @playback
77
+ if BigbluebuttonRails.configuration.playback_url_authentication
78
+ uri = @recording.token_url(bigbluebutton_user, request.remote_ip, @playback)
79
+ @playback_url = uri
78
80
  else
79
- flash[:error] = t('bigbluebutton_rails.recordings.errors.play.no_format')
80
- redirect_to_using_params bigbluebutton_recording_url(@recording)
81
+ @playback_url = @playback.url
81
82
  end
82
- }
83
+ if @playback.downloadable? || !BigbluebuttonRails.configuration.playback_iframe
84
+ redirect_to @playback_url
85
+ end
86
+ # else will render the default 'play' view
87
+ else
88
+ flash[:error] = t('bigbluebutton_rails.recordings.errors.play.no_format')
89
+ redirect_to_using_params bigbluebutton_recording_url(@recording)
90
+ end
91
+ else
92
+ flash[:error] = t('bigbluebutton_rails.recordings.errors.destroyed')
93
+ redirect_to my_home_path
83
94
  end
84
95
  end
85
96
 
@@ -105,7 +116,6 @@ class Bigbluebutton::RecordingsController < ApplicationController
105
116
  flash[:error] = message
106
117
  redirect_to bigbluebutton_recording_path(@recording)
107
118
  }
108
- format.json { render :json => message, :status => :error }
109
119
  end
110
120
  false
111
121
  else
@@ -126,7 +136,6 @@ class Bigbluebutton::RecordingsController < ApplicationController
126
136
  format.html {
127
137
  redirect_to_using_params bigbluebutton_recording_path(@recording), :notice => message
128
138
  }
129
- format.json { render :json => message }
130
139
  end
131
140
  rescue BigBlueButton::BigBlueButtonException => e
132
141
  respond_with do |format|
@@ -134,7 +143,6 @@ class Bigbluebutton::RecordingsController < ApplicationController
134
143
  flash[:error] = e.to_s[0..200]
135
144
  redirect_to_using_params bigbluebutton_recording_path(@recording)
136
145
  }
137
- format.json { render :json => e.to_s[0..200], :status => :error }
138
146
  end
139
147
  end
140
148
  end
@@ -154,11 +162,17 @@ class Bigbluebutton::RecordingsController < ApplicationController
154
162
  protected
155
163
 
156
164
  def find_playback
157
- if params[:type]
158
- @playback = @recording.playback_formats.where(:playback_type_id => BigbluebuttonPlaybackType.find_by_identifier(params[:type])).first
165
+ if @recording.present?
166
+ if params[:type]
167
+ @playback = @recording.playback_formats.where(:playback_type_id => BigbluebuttonPlaybackType.find_by_identifier(params[:type])).first
168
+ else
169
+ @playback = @recording.default_playback_format || @recording.playback_formats.first
170
+ end
159
171
  else
160
- @playback = @recording.default_playback_format || @recording.playback_formats.first
172
+ flash[:error] = t('bigbluebutton_rails.recordings.errors.destroyed')
173
+ redirect_to my_home_path
161
174
  end
175
+
162
176
  end
163
177
 
164
178
  end
@@ -14,8 +14,8 @@ class Bigbluebutton::RoomsController < ApplicationController
14
14
  before_filter :join_check_can_create, :only => :join
15
15
  before_filter :join_check_redirect_to_mobile, :only => :join
16
16
 
17
- respond_to :html, :except => :running
18
- respond_to :json, :only => [:running, :show, :new, :index, :create, :update]
17
+ respond_to :html, except: :running
18
+ respond_to :json, only: :running
19
19
 
20
20
  def index
21
21
  @rooms ||= BigbluebuttonRoom.all
@@ -50,15 +50,11 @@ class Bigbluebutton::RoomsController < ApplicationController
50
50
  format.html {
51
51
  redirect_to_using_params bigbluebutton_room_path(@room), :notice => message
52
52
  }
53
- format.json {
54
- render :json => { :message => message }, :status => :created
55
- }
56
53
  else
57
54
  format.html {
58
55
  message = t('bigbluebutton_rails.rooms.notice.create.failure')
59
56
  redirect_to_params_or_render :new, :error => message
60
57
  }
61
- format.json { render :json => @room.errors.full_messages, :status => :unprocessable_entity }
62
58
  end
63
59
  end
64
60
  end
@@ -70,13 +66,11 @@ class Bigbluebutton::RoomsController < ApplicationController
70
66
  format.html {
71
67
  redirect_to_using_params bigbluebutton_room_path(@room), :notice => message
72
68
  }
73
- format.json { render :json => { :message => message } }
74
69
  else
75
70
  format.html {
76
71
  message = t('bigbluebutton_rails.rooms.notice.update.failure')
77
72
  redirect_to_params_or_render :edit, :error => message
78
73
  }
79
- format.json { render :json => @room.errors.full_messages, :status => :unprocessable_entity }
80
74
  end
81
75
  end
82
76
  end
@@ -100,13 +94,6 @@ class Bigbluebutton::RoomsController < ApplicationController
100
94
  flash[:error] = message if error
101
95
  redirect_to_using_params bigbluebutton_rooms_url
102
96
  }
103
- format.json {
104
- if error
105
- render :json => { :message => message }, :status => :error
106
- else
107
- render :json => { :message => message }
108
- end
109
- }
110
97
  end
111
98
  end
112
99
 
@@ -163,14 +150,12 @@ class Bigbluebutton::RoomsController < ApplicationController
163
150
  flash[:error] = message
164
151
  redirect_to_using_params :back
165
152
  }
166
- format.json { render :json => message, :status => :error }
167
153
  end
168
154
  else
169
155
  respond_with do |format|
170
156
  format.html {
171
157
  redirect_to_using_params bigbluebutton_room_path(@room), :notice => message
172
158
  }
173
- format.json { render :json => message }
174
159
  end
175
160
  end
176
161
 
@@ -224,7 +209,6 @@ class Bigbluebutton::RoomsController < ApplicationController
224
209
  message = t('bigbluebutton_rails.rooms.notice.generate_dial_number.success')
225
210
  respond_with do |format|
226
211
  format.html { redirect_to_using_params :back, notice: message }
227
- format.json { render json: true, status: :ok }
228
212
  end
229
213
  else
230
214
  message = t('bigbluebutton_rails.rooms.errors.generate_dial_number.not_unique')
@@ -233,7 +217,6 @@ class Bigbluebutton::RoomsController < ApplicationController
233
217
  flash[:error] = message
234
218
  redirect_to_using_params :back
235
219
  }
236
- format.json { render json: { message: message }, status: :error }
237
220
  end
238
221
  end
239
222
  end
@@ -241,7 +224,7 @@ class Bigbluebutton::RoomsController < ApplicationController
241
224
  protected
242
225
 
243
226
  def find_room
244
- @room ||= BigbluebuttonRoom.find_by_param(params[:id])
227
+ @room ||= BigbluebuttonRoom.find_by(param: params[:id])
245
228
  end
246
229
 
247
230
  def set_request_headers
@@ -336,8 +319,7 @@ class Bigbluebutton::RoomsController < ApplicationController
336
319
  # first check if we have to create the room and if the user can do it
337
320
  unless @room.fetch_is_running?
338
321
  if bigbluebutton_can_create?(@room, role)
339
- user_opts = bigbluebutton_create_options(@room)
340
- if @room.create_meeting(bigbluebutton_user, request, user_opts)
322
+ if @room.create_meeting(bigbluebutton_user, request)
341
323
  logger.info "Meeting created: id: #{@room.meetingid}, name: #{@room.name}, created_by: #{username}, time: #{Time.now.iso8601}"
342
324
  end
343
325
  else
@@ -347,16 +329,9 @@ class Bigbluebutton::RoomsController < ApplicationController
347
329
  end
348
330
  end
349
331
 
350
- # gets the token with the configurations for this user/room
351
- token = @room.fetch_new_token
352
- options = if token.nil? then {} else { :configToken => token } end
353
-
354
- # set the create time and the user id, if they exist
355
- options.merge!({ createTime: @room.create_time }) unless @room.create_time.blank?
356
- options.merge!({ userID: id }) unless id.blank?
357
-
358
332
  # room created and running, try to join it
359
- url = @room.join_url(username, role, nil, options)
333
+ url = @room.parameterized_join_url(username, role, id, {}, bigbluebutton_user)
334
+
360
335
  unless url.nil?
361
336
 
362
337
  # change the protocol to join with a mobile device
@@ -2,7 +2,6 @@ class Bigbluebutton::ServersController < ApplicationController
2
2
  include BigbluebuttonRails::InternalControllerMethods
3
3
 
4
4
  respond_to :html
5
- respond_to :json, :only => [:index, :show, :new, :create, :update, :destroy, :activity, :rooms]
6
5
  before_filter :find_server, :except => [:index, :new, :create]
7
6
 
8
7
  def index
@@ -47,14 +46,8 @@ class Bigbluebutton::ServersController < ApplicationController
47
46
  if error
48
47
  flash[:error] = message
49
48
  format.html { render :activity }
50
- format.json {
51
- array = @server.meetings
52
- array.insert(0, { :message => message })
53
- render :json => array, :status => :error
54
- }
55
49
  else
56
50
  format.html
57
- format.json
58
51
  end
59
52
  end
60
53
  end
@@ -68,10 +61,8 @@ class Bigbluebutton::ServersController < ApplicationController
68
61
  message = t('bigbluebutton_rails.servers.notice.create.success')
69
62
  redirect_to_using_params @server, :notice => message
70
63
  }
71
- format.json { render :json => @server, :status => :created }
72
64
  else
73
65
  format.html { redirect_to_params_or_render :new }
74
- format.json { render :json => @server.errors.full_messages, :status => :unprocessable_entity }
75
66
  end
76
67
  end
77
68
  end
@@ -83,10 +74,8 @@ class Bigbluebutton::ServersController < ApplicationController
83
74
  message = t('bigbluebutton_rails.servers.notice.update.success')
84
75
  redirect_to_using_params @server, :notice => message
85
76
  }
86
- format.json { render :json => true, :status => :ok }
87
77
  else
88
78
  format.html { redirect_to_params_or_render :edit }
89
- format.json { render :json => @server.errors.full_messages, :status => :unprocessable_entity }
90
79
  end
91
80
  end
92
81
  end
@@ -97,7 +86,6 @@ class Bigbluebutton::ServersController < ApplicationController
97
86
 
98
87
  respond_with do |format|
99
88
  format.html { redirect_to_using_params bigbluebutton_servers_url }
100
- format.json { render :json => true, :status => :ok }
101
89
  end
102
90
  end
103
91
 
@@ -174,7 +162,6 @@ class Bigbluebutton::ServersController < ApplicationController
174
162
  end
175
163
  redirect_to_using_params recordings_bigbluebutton_server_path(@server), :notice => message
176
164
  }
177
- format.json { render :json => message }
178
165
  end
179
166
  rescue BigBlueButton::BigBlueButtonException => e
180
167
  respond_with do |format|
@@ -182,7 +169,6 @@ class Bigbluebutton::ServersController < ApplicationController
182
169
  flash[:error] = e.to_s[0..200]
183
170
  redirect_to_using_params recordings_bigbluebutton_server_path(@server)
184
171
  }
185
- format.json { render :json => e.to_s, :status => :error }
186
172
  end
187
173
  end
188
174
  end
@@ -41,4 +41,8 @@ module BigbluebuttonRailsHelper
41
41
  "badges/apple_store_#{I18n.locale}.png"
42
42
  end
43
43
 
44
+ def api_type_of(obj)
45
+ type = obj.class.name.demodulize.underscore.dasherize
46
+ type.gsub(/bigbluebutton-/, '')
47
+ end
44
48
  end
@@ -0,0 +1,21 @@
1
+ class BigbluebuttonAttendee < ActiveRecord::Base
2
+ include ActiveModel::ForbiddenAttributesProtection
3
+
4
+ belongs_to :meeting, :class_name => 'BigbluebuttonMeeting',
5
+ :foreign_key => :bigbluebutton_meeting_id
6
+
7
+ validates :bigbluebutton_meeting_id, :presence => true
8
+
9
+ # TODO: no role on getStats yet, but it exists on getMeetings
10
+ attr_accessor :role
11
+
12
+ def duration
13
+ self.left_time - self.join_time
14
+ end
15
+
16
+ def from_hash(hash)
17
+ self.user_id = hash[:userID].to_s
18
+ self.user_name = hash[:fullName].to_s
19
+ self.role = hash[:role].to_s.downcase == "moderator" ? :moderator : :attendee
20
+ end
21
+ end
@@ -1,3 +1,4 @@
1
+ # coding: utf-8
1
2
  class BigbluebuttonMeeting < ActiveRecord::Base
2
3
  include ActiveModel::ForbiddenAttributesProtection
3
4
 
@@ -8,6 +9,10 @@ class BigbluebuttonMeeting < ActiveRecord::Base
8
9
  :foreign_key => 'meeting_id',
9
10
  :dependent => :nullify
10
11
 
12
+ has_many :attendees,
13
+ :class_name => 'BigbluebuttonAttendee',
14
+ :dependent => :destroy
15
+
11
16
  validates :room, :presence => true
12
17
 
13
18
  validates :meetingid, :presence => true, :length => { :minimum => 1, :maximum => 100 }
@@ -4,8 +4,9 @@ class BigbluebuttonPlaybackFormat < ActiveRecord::Base
4
4
  belongs_to :recording, :class_name => 'BigbluebuttonRecording'
5
5
  belongs_to :playback_type, :class_name => 'BigbluebuttonPlaybackType'
6
6
 
7
- delegate :name, :identifier, :visible, :visible?, :default, :default?, :description,
8
- to: :playback_type, allow_nil: true
7
+ delegate :name, :identifier, :visible, :visible?, :default, :default?,
8
+ :description, :downloadable, :downloadable?,
9
+ to: :playback_type, allow_nil: true
9
10
  alias_attribute :format_type, :identifier
10
11
 
11
12
  validates :recording_id, :presence => true
@@ -1,6 +1,8 @@
1
1
  class BigbluebuttonRecording < ActiveRecord::Base
2
2
  include ActiveModel::ForbiddenAttributesProtection
3
3
 
4
+ # NOTE: when adding new attributes to recordings, add them to `recording_changed?`
5
+
4
6
  belongs_to :server, class_name: 'BigbluebuttonServer'
5
7
  belongs_to :room, class_name: 'BigbluebuttonRoom'
6
8
  belongs_to :meeting, class_name: 'BigbluebuttonMeeting'
@@ -23,15 +25,46 @@ class BigbluebuttonRecording < ActiveRecord::Base
23
25
 
24
26
  scope :published, -> { where(:published => true) }
25
27
 
28
+ serialize :recording_users, Array
29
+
26
30
  def to_param
27
31
  self.recordid
28
32
  end
29
33
 
34
+ def get_token(user, ip)
35
+ server = BigbluebuttonServer.default
36
+ user.present? ? authName = user.username : authName = "anonymous"
37
+ api_token = server.api.send_api_request(:getRecordingToken, { authUser: authName, authAddr: ip, meetingID: self.recordid })
38
+ str_token = api_token[:token]
39
+ str_token
40
+ end
41
+
42
+ # Passing it on the url
43
+ #
44
+ def token_url(user, ip, playback)
45
+ auth_token = get_token(user, ip)
46
+ if auth_token.present?
47
+ uri = playback.url
48
+ uri += URI.parse(uri).query.blank? ? "?" : "&"
49
+ uri += "token=#{auth_token}"
50
+ uri
51
+ end
52
+ end
53
+
30
54
  def default_playback_format
31
55
  playback_formats.joins(:playback_type)
32
56
  .where("bigbluebutton_playback_types.default = ?", true).first
33
57
  end
34
58
 
59
+ # Remove this recording from the server
60
+ def delete_from_server!
61
+ if self.server.present?
62
+ self.server.send_delete_recordings(self.recordid)
63
+ else
64
+ false
65
+ end
66
+ end
67
+
35
68
  # Returns the overall (i.e. for all recordings) average length of recordings in seconds
36
69
  # Uses the length of the default playback format
37
70
  def self.overall_average_length
@@ -47,6 +80,56 @@ class BigbluebuttonRecording < ActiveRecord::Base
47
80
  avg.nil? ? 0 : avg
48
81
  end
49
82
 
83
+ # Compares a recording from the db with data from a getRecordings call.
84
+ # If anything changed in the recording, returns true.
85
+ # We select only the attributes that are saved and turn it all into sorted arrays
86
+ # to compare. If new attributes are stored in recordings, they should be added here.
87
+ #
88
+ # This was created to speed up the full sync of recordings.
89
+ # In the worst case the comparison is wrong and we're updating them all (same as
90
+ # not using this method at all, which is ok).
91
+ def self.recording_changed?(recording, data)
92
+ begin
93
+ # the attributes that are considered in the comparison
94
+ keys = [:end_time, :meetingid, :metadata, :playback, :published, :recordid, :size, :start_time] # rawSize is not stored at the moment
95
+ keys_formats = [:length, :type, :url] # :size, :processingTime are not stored at the moment
96
+
97
+ # the data from getRecordings
98
+ data_clone = data.deep_dup
99
+ data_clone[:size] = data_clone[:size].to_s if data_clone.key?(:size)
100
+ data_clone[:metadata] = data_clone[:metadata].sort if data_clone.key?(:metadata)
101
+ if data_clone.key?(:playback) && data_clone[:playback].key?(:format)
102
+ data_clone[:playback][:format] = [data_clone[:playback][:format]] unless data_clone[:playback][:format].is_a?(Array)
103
+ data_clone[:playback] = data_clone[:playback][:format].map{ |f|
104
+ f.slice(*keys_formats).sort
105
+ }.sort
106
+ else
107
+ data_clone[:playback] = []
108
+ end
109
+ data_clone[:end_time] = data_clone[:end_time].to_i if data_clone.key?(:end_time)
110
+ data_clone[:start_time] = data_clone[:start_time].to_i if data_clone.key?(:start_time)
111
+ data_clone = data_clone.slice(*keys)
112
+ data_sorted = data_clone.sort
113
+
114
+ # the data from the recording in the db
115
+ attrs = recording.attributes.symbolize_keys.slice(*keys)
116
+ attrs[:size] = attrs[:size].to_s if attrs.key?(:size)
117
+ attrs[:metadata] = recording.metadata.pluck(:name, :content).map{ |i| [i[0].to_sym, i[1]] }.sort
118
+ attrs[:playback] = recording.playback_formats.map{ |f|
119
+ r = f.attributes.symbolize_keys.slice(*keys_formats)
120
+ r[:type] = f.format_type
121
+ r.sort
122
+ }.sort
123
+ attrs = attrs.sort
124
+
125
+ # compare
126
+ data_sorted.to_s != attrs.to_s
127
+ rescue StandardError => e
128
+ logger.error "Error comparing recordings: #{e.inspect}"
129
+ true # always update recordings if on error
130
+ end
131
+ end
132
+
50
133
  # Syncs the recordings in the db with the array of recordings in 'recordings',
51
134
  # as received from BigBlueButtonApi#get_recordings.
52
135
  # Will add new recordings that are not in the db yet and update the ones that
@@ -60,18 +143,24 @@ class BigbluebuttonRecording < ActiveRecord::Base
60
143
  recordings.each do |rec|
61
144
  rec_obj = BigbluebuttonRecording.find_by_recordid(rec[:recordID])
62
145
  rec_data = adapt_recording_hash(rec)
63
- BigbluebuttonRecording.transaction do
64
- if rec_obj
65
- logger.info "Sync recordings: updating recording #{rec_obj.inspect}"
66
- logger.debug "Sync recordings: recording data #{rec_data.inspect}"
67
- self.update_recording(server, rec_obj, rec_data)
68
- else
69
- logger.info "Sync recordings: creating recording"
70
- logger.debug "Sync recordings: recording data #{rec_data.inspect}"
71
- self.create_recording(server, rec_data)
146
+ changed = !rec_obj.present? ||
147
+ self.recording_changed?(rec_obj, rec_data)
148
+
149
+ if changed
150
+ BigbluebuttonRecording.transaction do
151
+ if rec_obj
152
+ logger.info "Sync recordings: updating recording #{rec_obj.inspect}"
153
+ logger.debug "Sync recordings: recording data #{rec_data.inspect}"
154
+ self.update_recording(server, rec_obj, rec_data)
155
+ else
156
+ logger.info "Sync recordings: creating recording"
157
+ logger.debug "Sync recordings: recording data #{rec_data.inspect}"
158
+ self.create_recording(server, rec_data)
159
+ end
72
160
  end
73
161
  end
74
162
  end
163
+ cleanup_playback_types
75
164
 
76
165
  # set as unavailable the recordings that are not in 'recordings', but
77
166
  # only in a full synchronization process, which means that the recordings
@@ -107,6 +196,22 @@ class BigbluebuttonRecording < ActiveRecord::Base
107
196
  new_hash
108
197
  end
109
198
 
199
+ def self.adapt_recording_users(original)
200
+ if original.present? && original.size > 0
201
+ users = original[:user]
202
+ users = [users] unless users.is_a?(Array)
203
+ users = users.map{ |u|
204
+ id = u[:externalUserID]
205
+ begin
206
+ id = Integer(id)
207
+ rescue
208
+ end
209
+ id
210
+ }
211
+ return users
212
+ end
213
+ end
214
+
110
215
  # Updates the BigbluebuttonRecording 'recording' with the data in the hash 'data'.
111
216
  # The format expected for 'data' follows the format returned by
112
217
  # BigBlueButtonApi#get_recordings but with the keys already converted to our format.
@@ -115,6 +220,7 @@ class BigbluebuttonRecording < ActiveRecord::Base
115
220
  recording.room = BigbluebuttonRails.configuration.match_room_recording.call(data)
116
221
  recording.attributes = data.slice(:meetingid, :name, :published, :start_time, :end_time, :size)
117
222
  recording.available = true
223
+ recording.recording_users = adapt_recording_users(data[:recordingUsers])
118
224
  recording.save!
119
225
 
120
226
  sync_additional_data(recording, data)
@@ -129,8 +235,9 @@ class BigbluebuttonRecording < ActiveRecord::Base
129
235
  recording.available = true
130
236
  recording.room = BigbluebuttonRails.configuration.match_room_recording.call(data)
131
237
  recording.server = server
132
- recording.description = I18n.t('bigbluebutton_rails.recordings.default.description', :time => recording.start_time.utc.to_formatted_s(:long))
238
+ recording.description = I18n.t('bigbluebutton_rails.recordings.default.description', :time => Time.at(recording.start_time).utc.to_formatted_s(:long))
133
239
  recording.meeting = BigbluebuttonRecording.find_matching_meeting(recording)
240
+ recording.recording_users = adapt_recording_users(data[:recordingUsers])
134
241
  recording.save!
135
242
 
136
243
  sync_additional_data(recording, data)
@@ -154,73 +261,60 @@ class BigbluebuttonRecording < ActiveRecord::Base
154
261
  # keys are stored as strings in the db
155
262
  received_metadata = metadata.clone.stringify_keys
156
263
 
157
- query = { :owner_id => recording.id, :owner_type => recording.class.to_s }
158
- BigbluebuttonMetadata.where(query).each do |meta_db|
159
-
160
- # the metadata in the db is also in the received data, update it in the db
161
- if received_metadata.has_key?(meta_db.name)
162
- meta_db.update_attributes({ :content => received_metadata[meta_db.name] })
163
- received_metadata.delete(meta_db.name)
164
-
165
- # the metadata is not in the received data, remove from the db
166
- else
167
- meta_db.destroy
168
- end
169
- end
264
+ # get all metadata for this recording
265
+ # note: it's a little slower than removing all metadata and adding again,
266
+ # but it's cleaner to just update it and the loss of performance is small
267
+ query = { owner_id: recording.id, owner_type: recording.class.to_s }
268
+ metas = BigbluebuttonMetadata.where(query).all
170
269
 
171
- # for metadata that are not in the db yet
270
+ # batch insert all metadata
271
+ columns = [ :id, :name, :content, :owner_id, :owner_type ]
272
+ values = []
172
273
  received_metadata.each do |name, content|
173
- attrs = {
174
- :name => name,
175
- :content => content,
176
- }
177
- meta = BigbluebuttonMetadata.create(attrs)
178
- meta.owner = recording
179
- meta.save!
274
+ id = metas.select{ |m| m.name == name }.first.try(:id)
275
+ values << [ id, name, content, recording.id, recording.class.to_s ]
180
276
  end
277
+ BigbluebuttonMetadata.import! columns, values, validate: true,
278
+ on_duplicate_key_update: [:name, :content]
279
+
280
+ # delete all that doesn't exist anymore
281
+ BigbluebuttonMetadata.where(query).where.not(name: received_metadata.keys).delete_all
181
282
  end
182
283
 
183
284
  # Syncs the playback formats objects of 'recording' with the data in 'formats'.
184
285
  # The format expected for 'formats' follows the format returned by
185
286
  # BigBlueButtonApi#get_recordings but with the keys already converted to our format.
186
287
  def self.sync_playback_formats(recording, formats)
187
- formats_copy = formats.clone
188
288
 
189
- # make it an array if it's a hash with a single format
289
+ # clone and make it an array if it's a hash with a single format
290
+ formats_copy = formats.clone
190
291
  formats_copy = [formats_copy] if formats_copy.is_a?(Hash)
191
292
 
192
- BigbluebuttonPlaybackFormat.where(recording_id: recording.id).each do |format_db|
193
- format = formats_copy.select do |d|
194
- !d[:type].blank? && d[:type] == format_db.format_type
195
- end.first
196
-
197
- # the format exists in the hash, update it in the db
198
- if format
199
- format_db.update_attributes({ url: format[:url], length: format[:length] })
200
- formats_copy.delete(format)
201
-
202
- # the format is not in the hash, remove from the db
203
- else
204
- format_db.destroy
205
- end
206
- end
293
+ # remove all formats for this recording
294
+ # note: easier than updating the formats because they don't have a clear key
295
+ # to match by
296
+ BigbluebuttonPlaybackFormat.where(recording_id: recording.id).delete_all
207
297
 
208
- # create the formats that are not in the db yet
298
+ # batch insert all playback formats
299
+ columns = [ :recording_id, :url, :length , :playback_type_id ]
300
+ values = []
209
301
  formats_copy.each do |format|
210
302
  unless format[:type].blank?
211
- playback_type = BigbluebuttonPlaybackType.find_by_identifier(format[:type])
303
+ playback_type = BigbluebuttonPlaybackType.find_by(identifier: format[:type])
212
304
  if playback_type.nil?
213
- attrs = { identifier: format[:type], visible: true }
305
+ downloadable = BigbluebuttonRails.configuration.downloadable_playback_types.include?(format[:type])
306
+ attrs = {
307
+ identifier: format[:type],
308
+ visible: true,
309
+ downloadable: downloadable
310
+ }
214
311
  playback_type = BigbluebuttonPlaybackType.create!(attrs)
215
312
  end
216
313
 
217
- attrs = { recording_id: recording.id, url: format[:url],
218
- length: format[:length].to_i, playback_type_id: playback_type.id }
219
- BigbluebuttonPlaybackFormat.create!(attrs)
314
+ values << [ recording.id, format[:url], format[:length].to_i, playback_type.id ]
220
315
  end
221
316
  end
222
-
223
- cleanup_playback_types
317
+ BigbluebuttonPlaybackFormat.import! columns, values, validate: true
224
318
  end
225
319
 
226
320
  # Remove the unused playback types from the list.
@@ -230,20 +324,22 @@ class BigbluebuttonRecording < ActiveRecord::Base
230
324
  end
231
325
 
232
326
  # Finds the BigbluebuttonMeeting that generated this recording. The meeting is searched using
233
- # the room associated with this recording and the create time of the meeting, taken from
234
- # the recording's ID.
327
+ # the meetingid associated with this recording and the create time of the meeting, taken from
328
+ # the recording's ID. There are some flexible clauses that try to match very close or truncated
329
+ # timestamps from recordings start times to meeting create times.
235
330
  def self.find_matching_meeting(recording)
236
331
  meeting = nil
237
-
238
- unless recording.nil? or recording.room.nil?
239
-
240
- # recordid is something like: 'dd2816950ce2f1e0a928c1a5b8d5b526e9b3e32c-1381978014526'
241
- # the create time of the meeting is the timestamp at the end
242
- start_time = recording.recordid.match(/-(\d*)$/)
243
- unless start_time.nil?
244
- start_time = start_time[1]
245
- start_time = Time.at(start_time.to_i).to_datetime.utc
246
- meeting = BigbluebuttonMeeting.where(:room_id => recording.room.id, :start_time => start_time).last
332
+ unless recording.nil? #or recording.room.nil?
333
+ unless recording.start_time.nil?
334
+ start_time = recording.start_time
335
+ meeting = BigbluebuttonMeeting.where("meetingid = ? AND create_time = ?", recording.meetingid, start_time).last
336
+ if meeting.nil?
337
+ meeting = BigbluebuttonMeeting.where("meetingid = ? AND create_time DIV 1000 = ?", recording.meetingid, start_time).last
338
+ end
339
+ if meeting.nil?
340
+ div_start_time = (start_time/10)
341
+ meeting = BigbluebuttonMeeting.where("meetingid = ? AND create_time DIV 10 = ?", recording.meetingid, div_start_time).last
342
+ end
247
343
  logger.info "Recording: meeting found for the recording #{recording.inspect}: #{meeting.inspect}"
248
344
  end
249
345
  end