bigbluebutton_rails 2.2.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
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