Merge feature/video_mvp to develop and resolve conflicts.
This commit is contained in:
commit
fe9bd594b4
|
|
@ -484,6 +484,7 @@ message RecordingMasterMixComplete {
|
|||
optional string notification_id = 4;
|
||||
optional string created_at = 5;
|
||||
optional string claimed_recording_id = 6;
|
||||
<<<<<<< HEAD
|
||||
}
|
||||
|
||||
message RecordingStreamMixComplete {
|
||||
|
|
@ -493,6 +494,8 @@ message RecordingStreamMixComplete {
|
|||
optional string notification_id = 4;
|
||||
optional string created_at = 5;
|
||||
optional string claimed_recording_id = 6;
|
||||
=======
|
||||
>>>>>>> feature/video_mvp
|
||||
}
|
||||
|
||||
message DownloadAvailable {
|
||||
|
|
|
|||
|
|
@ -76,6 +76,17 @@ module JamRuby
|
|||
quick_mixes.find{|quick_mix| quick_mix.completed && !quick_mix.cleaned }
|
||||
end
|
||||
|
||||
def mix_state
|
||||
mix.state if mix
|
||||
end
|
||||
|
||||
def mix_error
|
||||
mix.error if mix
|
||||
end
|
||||
|
||||
def stream_mix
|
||||
quick_mixes.find{|quick_mix| quick_mix.completed && !quick_mix.cleaned }
|
||||
end
|
||||
|
||||
# this can probably be done more efficiently, but David needs this asap for a video
|
||||
def grouped_tracks
|
||||
|
|
@ -121,8 +132,6 @@ module JamRuby
|
|||
unless claimed_recordings.length > 0
|
||||
destroy
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
def not_still_finalizing_previous
|
||||
|
|
@ -446,7 +455,6 @@ module JamRuby
|
|||
|
||||
|
||||
# Further joining and criteria for the unioned object:
|
||||
|
||||
arel = arel.joins("INNER JOIN recordings ON recordings.id=recorded_items_all.recording_id") \
|
||||
.where('recorded_items_all.user_id' => user.id) \
|
||||
.where('recorded_items_all.fully_uploaded = ?', false) \
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ module JamRuby
|
|||
validates_uniqueness_of :uid, scope: :provider
|
||||
# token, secret, token_expiration can be missing
|
||||
|
||||
def self.goog_auth(user)
|
||||
def self.google_auth(user)
|
||||
self
|
||||
.where(:user_id => user.id)
|
||||
.where(:provider => 'google_login')
|
||||
|
|
|
|||
11
web/Gemfile
11
web/Gemfile
|
|
@ -37,13 +37,16 @@ gem 'compass-rails', '1.1.3' # 1.1.4 throws an exception on startup about !init
|
|||
gem 'rabl', '0.11.0' # for JSON API development
|
||||
gem 'gon', '~>4.1.0' # for passthrough of Ruby variables to Javascript variables
|
||||
gem 'eventmachine', '1.0.3'
|
||||
gem 'faraday', '~>0.9.0'
|
||||
gem 'amqp', '0.9.8'
|
||||
gem 'logging-rails', :require => 'logging/rails'
|
||||
gem 'omniauth', '1.1.1'
|
||||
gem 'omniauth-facebook', '1.4.1'
|
||||
gem 'omniauth-twitter'
|
||||
gem 'omniauth-google-oauth2', '0.2.1'
|
||||
gem 'google-api-client'
|
||||
gem 'google-api-client', '0.7.1'
|
||||
gem 'google-api-omniauth', '0.1.1'
|
||||
gem 'signet', '0.5.0'
|
||||
gem 'twitter'
|
||||
gem 'fb_graph', '2.5.9'
|
||||
gem 'sendgrid', '1.2.0'
|
||||
|
|
@ -107,7 +110,7 @@ end
|
|||
group :test, :cucumber do
|
||||
gem 'simplecov', '~> 0.7.1'
|
||||
gem 'simplecov-rcov'
|
||||
gem 'capybara'
|
||||
gem 'capybara', '2.4.4'
|
||||
#if ENV['JAMWEB_QT5'] == '1'
|
||||
# # necessary on platforms such as arch linux, where pacman -S qt5-webkit is your easiet option
|
||||
# gem "capybara-webkit", :git => 'git://github.com/thoughtbot/capybara-webkit.git'
|
||||
|
|
@ -115,15 +118,17 @@ group :test, :cucumber do
|
|||
gem "capybara-webkit"
|
||||
#end
|
||||
gem 'capybara-screenshot', '0.3.22' # 1.0.0 broke compat with rspec. maybe we need newer rspec
|
||||
gem 'selenium-webdriver'
|
||||
gem 'cucumber-rails', :require => false #, '1.3.0', :require => false
|
||||
gem 'guard-spork', '0.3.2'
|
||||
gem 'spork', '0.9.0'
|
||||
gem 'launchy', '2.1.0'
|
||||
gem 'launchy', '2.1.1'
|
||||
gem 'rack-test'
|
||||
# gem 'rb-fsevent', '0.9.1', :require => false
|
||||
# gem 'growl', '1.0.3'
|
||||
gem 'poltergeist'
|
||||
gem 'resque_spec'
|
||||
#gem 'thin'
|
||||
end
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -122,13 +122,17 @@
|
|||
var description = $('#recording-finished-dialog form textarea[name=description]').val();
|
||||
var genre = $('#recording-finished-dialog form select[name=genre]').val();
|
||||
var is_public = $('#recording-finished-dialog form input[name=is_public]').is(':checked')
|
||||
var save_video = $('#recording-finished-dialog form input[name=save_video]').is(':checked')
|
||||
var upload_to_youtube = $('#recording-finished-dialog form input[name=upload_to_youtube]').is(':checked')
|
||||
|
||||
rest.claimRecording({
|
||||
id: recording.id,
|
||||
name: name,
|
||||
description: description,
|
||||
genre: genre,
|
||||
is_public: is_public
|
||||
is_public: is_public,
|
||||
save_video: save_video,
|
||||
upload_to_youtube: upload_to_youtube
|
||||
})
|
||||
.done(function () {
|
||||
$dialog.data('result', {keep:true});
|
||||
|
|
@ -151,6 +155,12 @@
|
|||
var $is_public_errors = context.JK.format_errors('is_public', errors);
|
||||
if ($is_public_errors) $('#recording-finished-dialog form input[name=is_public]').closest('div.field').addClass('error').end().after($is_public_errors);
|
||||
|
||||
var $save_video_errors = context.JK.format_errors('save_video', errors);
|
||||
if ($save_video_errors) $('#recording-finished-dialog form input[name=save_video]').closest('div.field').addClass('error').end().after($save_video_errors);
|
||||
|
||||
var $upload_to_youtube_errors = context.JK.format_errors('upload_to_youtube', errors);
|
||||
if ($upload_to_youtube_errors) $('#recording-finished-dialog form input[name=upload_to_youtube]').closest('div.field').addClass('error').end().after($upload_to_youtube_errors);
|
||||
|
||||
var recording_error = context.JK.get_first_error('recording_id', errors);
|
||||
|
||||
if (recording_error) context.JK.showErrorDialog(app, "Unable to claim recording.", recording_error);
|
||||
|
|
@ -223,8 +233,9 @@
|
|||
|
||||
function initializeButtons() {
|
||||
var isPublic = $('#recording-finished-dialog input[name="is_public"]');
|
||||
|
||||
context.JK.checkbox(isPublic);
|
||||
context.JK.checkbox($('#recording-finished-dialog input[name="save_video"]'));
|
||||
context.JK.checkbox($('#recording-finished-dialog input[name="upload_to_youtube"]'));
|
||||
}
|
||||
|
||||
function initialize() {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
#recording-finished-dialog {
|
||||
width:1000px;
|
||||
height:auto;
|
||||
div[purpose=description], div[purpose=is_public] {
|
||||
div[purpose=description] {
|
||||
margin-top:20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
label[for=is_public], label[for=playback-mode-preview-all], label[for=playback-mode-preview-me] {
|
||||
|
|
@ -23,11 +24,21 @@
|
|||
margin-top:20px;
|
||||
}
|
||||
|
||||
div[purpose=is_public] .icheckbox_minimal {
|
||||
div[purpose=is_public], div[purpose=upload_to_youtube], div[purpose=save_video] {
|
||||
.icheckbox_minimal {
|
||||
display:inline-block;
|
||||
position:relative;
|
||||
top:3px;
|
||||
margin-right:3px;
|
||||
top:1px;
|
||||
margin-top:3px;
|
||||
margin-bottom:1px;
|
||||
margin-right:2px;
|
||||
}
|
||||
label {
|
||||
display: inline-block;
|
||||
margin-bottom:4px;
|
||||
margin-right:2px;
|
||||
}
|
||||
clear: left;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
class ApiRecordingsController < ApiController
|
||||
|
||||
before_filter :api_signed_in_user, :except => [ :add_like ]
|
||||
before_filter :look_up_recording, :only => [ :show, :stop, :claim, :discard, :keep ]
|
||||
before_filter :parse_filename, :only => [ :download, :upload_next_part, :upload_sign, :upload_part_complete, :upload_complete ]
|
||||
before_filter :lookup_recording, :only => [ :show, :stop, :claim, :discard, :keep ]
|
||||
before_filter :lookup_recorded_track, :only => [ :download, :upload_next_part, :upload_sign, :upload_part_complete, :upload_complete ]
|
||||
before_filter :lookup_recorded_video, :only => [ :video_upload_sign, :video_upload_start, :video_upload_complete ]
|
||||
before_filter :lookup_stream_mix, :only => [ :upload_next_part_stream_mix, :upload_sign_stream_mix, :upload_part_complete_stream_mix, :upload_complete_stream_mix ]
|
||||
|
||||
respond_to :json
|
||||
|
|
@ -210,6 +210,135 @@ class ApiRecordingsController < ApiController
|
|||
end
|
||||
end
|
||||
|
||||
# POST /api/recordings/:id/videos/:video_id/upload_sign
|
||||
def video_upload_sign
|
||||
length = params[:length]
|
||||
@youtube_client.upload_sign(current_user, @recorded_video.url, length)
|
||||
end
|
||||
|
||||
# POST /api/recordings/:id/videos/:video_id/upload_complete
|
||||
def video_upload_start
|
||||
length = params[:length]
|
||||
@youtube_client.get_upload_status(current_user, @recorded_video.url, length)
|
||||
end
|
||||
|
||||
# POST /api/recordings/:id/videos/:video_id/upload_complete
|
||||
def video_upload_complete
|
||||
if @youtube_client.complete_upload(@recorded_video)
|
||||
render :status => 200
|
||||
else
|
||||
render :status => 422
|
||||
end
|
||||
end
|
||||
|
||||
def upload_next_part_stream_mix
|
||||
length = params[:length]
|
||||
md5 = params[:md5]
|
||||
|
||||
@quick_mix.upload_next_part(length, md5)
|
||||
|
||||
if @quick_mix.errors.any?
|
||||
|
||||
response.status = :unprocessable_entity
|
||||
# this is not typical, but please don't change this line unless you are sure it won't break anything
|
||||
# this is needed because after_rollback in the RecordedTrackObserver touches the model and something about it's
|
||||
# state doesn't cause errors to shoot out like normal.
|
||||
render :json => { :errors => @quick_mix.errors }, :status => 422
|
||||
else
|
||||
result = {
|
||||
:part => @quick_mix.next_part_to_upload,
|
||||
:offset => @quick_mix.file_offset.to_s
|
||||
}
|
||||
|
||||
render :json => result, :status => 200
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def upload_sign_stream_mix
|
||||
render :json => @quick_mix.upload_sign(params[:md5]), :status => 200
|
||||
end
|
||||
|
||||
def upload_part_complete_stream_mix
|
||||
part = params[:part]
|
||||
offset = params[:offset]
|
||||
|
||||
@quick_mix.upload_part_complete(part, offset)
|
||||
|
||||
if @quick_mix.errors.any?
|
||||
response.status = :unprocessable_entity
|
||||
respond_with @quick_mix
|
||||
else
|
||||
render :json => {}, :status => 200
|
||||
end
|
||||
end
|
||||
|
||||
def upload_complete_stream_mix
|
||||
@quick_mix.upload_complete
|
||||
|
||||
if @quick_mix.errors.any?
|
||||
response.status = :unprocessable_entity
|
||||
respond_with @quick_mix
|
||||
return
|
||||
else
|
||||
render :json => {}, :status => 200
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def upload_next_part_stream_mix
|
||||
length = params[:length]
|
||||
md5 = params[:md5]
|
||||
|
||||
@quick_mix.upload_next_part(length, md5)
|
||||
|
||||
if @quick_mix.errors.any?
|
||||
|
||||
response.status = :unprocessable_entity
|
||||
# this is not typical, but please don't change this line unless you are sure it won't break anything
|
||||
# this is needed because after_rollback in the RecordedTrackObserver touches the model and something about it's
|
||||
# state doesn't cause errors to shoot out like normal.
|
||||
render :json => { :errors => @quick_mix.errors }, :status => 422
|
||||
else
|
||||
result = {
|
||||
:part => @quick_mix.next_part_to_upload,
|
||||
:offset => @quick_mix.file_offset.to_s
|
||||
}
|
||||
|
||||
render :json => result, :status => 200
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def upload_sign_stream_mix
|
||||
render :json => @quick_mix.upload_sign(params[:md5]), :status => 200
|
||||
end
|
||||
|
||||
def upload_part_complete_stream_mix
|
||||
part = params[:part]
|
||||
offset = params[:offset]
|
||||
|
||||
@quick_mix.upload_part_complete(part, offset)
|
||||
|
||||
if @quick_mix.errors.any?
|
||||
response.status = :unprocessable_entity
|
||||
respond_with @quick_mix
|
||||
else
|
||||
render :json => {}, :status => 200
|
||||
end
|
||||
end
|
||||
|
||||
def upload_complete_stream_mix
|
||||
@quick_mix.upload_complete
|
||||
|
||||
if @quick_mix.errors.any?
|
||||
response.status = :unprocessable_entity
|
||||
respond_with @quick_mix
|
||||
return
|
||||
else
|
||||
render :json => {}, :status => 200
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def upload_next_part_stream_mix
|
||||
|
|
@ -267,7 +396,13 @@ class ApiRecordingsController < ApiController
|
|||
end
|
||||
|
||||
private
|
||||
def parse_filename
|
||||
|
||||
def lookup_recording
|
||||
@recording = Recording.find(params[:id])
|
||||
raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR unless @recording.has_access?(current_user)
|
||||
end
|
||||
|
||||
def lookup_recorded_track
|
||||
@recorded_track = RecordedTrack.find_by_recording_id_and_client_track_id!(params[:id], params[:track_id])
|
||||
raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR unless @recorded_track.recording.has_access?(current_user)
|
||||
end
|
||||
|
|
@ -277,9 +412,9 @@ class ApiRecordingsController < ApiController
|
|||
raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR unless @quick_mix.recording.has_access?(current_user)
|
||||
end
|
||||
|
||||
def look_up_recording
|
||||
@recording = Recording.find(params[:id])
|
||||
raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR unless @recording.has_access?(current_user)
|
||||
def lookup_recorded_video
|
||||
@recorded_video = RecordedVideo.find_by_recording_id_and_client_video_source_id!(params[:id], params[:video_id])
|
||||
raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR unless @recorded_video.recording.has_access?(current_user)
|
||||
end
|
||||
|
||||
end
|
||||
end # class
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ class GmailController < ApplicationController
|
|||
render :nothing => true, :status => 404
|
||||
return
|
||||
end
|
||||
authorization = UserAuthorization.goog_auth(current_user)
|
||||
authorization = UserAuthorization.google_auth(current_user)
|
||||
if authorization.empty?
|
||||
render :nothing => true, :status => 404
|
||||
return
|
||||
|
|
|
|||
|
|
@ -1,58 +0,0 @@
|
|||
<!-- Invitation Dialog -->
|
||||
<div class="dialog recordingFinished-overlay ftue-overlay tall" layout="dialog" layout-id="recordingFinished" id="recording-finished-dialog">
|
||||
|
||||
<div class="content-head">
|
||||
<%= image_tag "content/recordbutton-off.png", {:height => 20, :width => 20, :class => 'content-icon'} %>
|
||||
<h1>recording finished</h1>
|
||||
</div>
|
||||
|
||||
<div class="dialog-inner">
|
||||
Fill out the fields below and click the "SAVE" button to save this recording to your library. If you do not want to
|
||||
keep the recording, click the "DISCARD" button.
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<form class="left w40 mr20">
|
||||
<div class="left w50 mr20">
|
||||
<div class="field w100">
|
||||
<label for="name">Recording name:</label><br/>
|
||||
<input type="text" name="name" id="claim-recording-name" class="recording-name" class="w100"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right w40 genre-selector">
|
||||
<div class="field">
|
||||
<!-- genre box -->
|
||||
<label for="genre">Genre:</label><br/>
|
||||
<select name="genre"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field w100 left" purpose="description">
|
||||
<label for="description">Description:</label>
|
||||
<textarea class="w100" name="description" id="claim-recording-description"></textarea>
|
||||
</div>
|
||||
<div class="field left" purpose="is_public">
|
||||
<input type="checkbox" checked="checked" name="is_public"/><label for="is_public">Public Recording</label> <!--<a href="#"><<img src="images/shared/icon_help.png" width="12" height="12" /></a>-->
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="left w50 ml30">
|
||||
Preview Recording:
|
||||
|
||||
<%= render "clients/play_controls" %>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
</div>
|
||||
|
||||
|
||||
<br clear="left"/><br/>
|
||||
|
||||
<div class="right">
|
||||
<a href="#" class="button-grey" id="discard-session-recording">DISCARD</a> <a href="#" class="button-orange" id="keep-session-recording">SAVE</a>
|
||||
|
||||
</div>
|
||||
|
||||
<br clear="all"/>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
/ Invitation Dialog
|
||||
#recording-finished-dialog.dialog.recordingFinished-overlay.ftue-overlay.tall{:layout => "dialog", "layout-id" => "recordingFinished"}
|
||||
.content-head
|
||||
= image_tag "content/recordbutton-off.png", {:height => 20, :width => 20, :class => 'content-icon'}
|
||||
%h1 recording finished
|
||||
.dialog-inner
|
||||
Fill out the fields below and click the "SAVE" button to save this recording to your library. If you do not want to
|
||||
keep the recording, click the "DISCARD" button.
|
||||
%br/
|
||||
%br/
|
||||
%form.left.w40.mr20
|
||||
.left.w50.mr20
|
||||
.field.w100
|
||||
%label{:for => "name"} Recording name:
|
||||
%br/
|
||||
%input#claim-recording-name.w100{:name => "name", :type => "text"}/
|
||||
.right.w40.genre-selector
|
||||
.field
|
||||
/ genre box
|
||||
%label{:for => "genre"} Genre:
|
||||
%br/
|
||||
%select{:name => "genre"}
|
||||
.field.w100.left{:purpose => "description"}
|
||||
%label{:for => "description"} Description:
|
||||
%textarea#claim-recording-description.w100{:name => "description"}
|
||||
.field.left{:purpose => "save_video"}
|
||||
%input{:checked => "checked", :name => "save_video", :type => "checkbox"}/
|
||||
%label{:for => "save_video"} Save Video to Computer
|
||||
.field.left{:purpose => "upload_to_youtube"}
|
||||
%input{:checked => "checked", :name => "upload_to_youtube", :type => "checkbox"}/
|
||||
%label{:for => "upload_to_youtube"} Upload Video to YouTube
|
||||
.field.left{:purpose => "is_public"}
|
||||
%input{:checked => "checked", :name => "is_public", :type => "checkbox"}/
|
||||
%label{:for => "is_public"} Public Recording
|
||||
/ <a href="#"><<img src="images/shared/icon_help.png" width="12" height="12" /></a>
|
||||
.left.w50.ml30
|
||||
Preview Recording:
|
||||
|
||||
\#{render "clients/play_controls"}
|
||||
%br/
|
||||
%br/
|
||||
%br{:clear => "left"}/
|
||||
%br/
|
||||
.right
|
||||
%a#discard-session-recording.button-grey{:href => "#"}> DISCARD
|
||||
\
|
||||
%a#keep-session-recording.button-orange{:href => "#"} SAVE
|
||||
%br{:clear => "all"}/
|
||||
|
|
@ -143,6 +143,7 @@ if defined?(Bundler)
|
|||
# google api keys
|
||||
config.google_client_id = '785931784279-gd0g8on6sc0tuesj7cu763pitaiv2la8.apps.googleusercontent.com'
|
||||
config.google_secret = 'UwzIcvtErv9c2-GIsNfIo7bA'
|
||||
config.google_email = '785931784279-gd0g8on6sc0tuesj7cu763pitaiv2la8@developer.gserviceaccount.com'
|
||||
|
||||
if Rails.env == 'production'
|
||||
config.desk_url = 'https://jamkazam.desk.com'
|
||||
|
|
|
|||
|
|
@ -401,6 +401,8 @@ SampleApp::Application.routes.draw do
|
|||
match '/recordings/:id/comments' => 'api_recordings#add_comment', :via => :post, :as => 'api_recordings_add_comment'
|
||||
match '/recordings/:id/likes' => 'api_recordings#add_like', :via => :post, :as => 'api_recordings_add_like'
|
||||
match '/recordings/:id/discard' => 'api_recordings#discard', :via => :post, :as => 'api_recordings_discard'
|
||||
|
||||
# Recordings - recorded_tracks
|
||||
match '/recordings/:id/tracks/:track_id' => 'api_recordings#show_recorded_track', :via => :get, :as => 'api_recordings_show_recorded_track'
|
||||
match '/recordings/:id/tracks/:track_id/download' => 'api_recordings#download', :via => :get, :as => 'api_recordings_download'
|
||||
match '/recordings/:id/tracks/:track_id/upload_next_part' => 'api_recordings#upload_next_part', :via => :get
|
||||
|
|
@ -408,10 +410,17 @@ SampleApp::Application.routes.draw do
|
|||
match '/recordings/:id/tracks/:track_id/upload_part_complete' => 'api_recordings#upload_part_complete', :via => :post
|
||||
match '/recordings/:id/tracks/:track_id/upload_complete' => 'api_recordings#upload_complete', :via => :post
|
||||
match '/recordings/:id/stream_mix/upload_next_part' => 'api_recordings#upload_next_part_stream_mix', :via => :get
|
||||
|
||||
# Recordings - stream_mix
|
||||
match '/recordings/:id/stream_mix/upload_sign' => 'api_recordings#upload_sign_stream_mix', :via => :get
|
||||
match '/recordings/:id/stream_mix/upload_part_complete' => 'api_recordings#upload_part_complete_stream_mix', :via => :post
|
||||
match '/recordings/:id/stream_mix/upload_complete' => 'api_recordings#upload_complete_stream_mix', :via => :post
|
||||
|
||||
# Recordings - recorded_videos
|
||||
match '/recordings/:id/tracks/:video_id/upload_sign' => 'api_recordings#video_upload_sign', :via => :get
|
||||
match '/recordings/:id/videos/:video_id/upload_start' => 'api_recordings#video_upload_start', :via => :post
|
||||
match '/recordings/:id/videos/:video_id/upload_complete' => 'api_recordings#video_upload_complete', :via => :post
|
||||
|
||||
# Claimed Recordings
|
||||
match '/claimed_recordings' => 'api_claimed_recordings#index', :via => :get
|
||||
match '/claimed_recordings/:id' => 'api_claimed_recordings#show', :via => :get
|
||||
|
|
|
|||
|
|
@ -0,0 +1,387 @@
|
|||
require 'faraday'
|
||||
#require 'thin'
|
||||
require 'launchy'
|
||||
require 'cgi'
|
||||
require 'json'
|
||||
require 'google/api_client'
|
||||
require 'google/api_client/client_secrets'
|
||||
require 'google/api_client/auth/installed_app'
|
||||
require 'socket' # Provides TCPServer and TCPSocket classes
|
||||
# require 'youtube_client'; c = YouTubeClient.new
|
||||
# Youtube API functionality:
|
||||
module JamRuby
|
||||
class YouTubeClient
|
||||
attr_accessor :client
|
||||
attr_accessor :api
|
||||
attr_accessor :request
|
||||
attr_accessor :server
|
||||
attr_accessor :socket
|
||||
attr_accessor :config
|
||||
attr_accessor :redirect_uri
|
||||
|
||||
def initialize()
|
||||
Rails.logger.info("Initializing client...")
|
||||
self.config = Rails.application.config
|
||||
self.redirect_uri='http://localhost:2112/auth/google_login/callback'
|
||||
self.client = Google::APIClient.new(
|
||||
:application_name => 'JamKazam',
|
||||
:application_version => '1.0.0'
|
||||
)
|
||||
|
||||
youtube = client.discovered_api('youtube', 'v3')
|
||||
# client.authorization = nil
|
||||
# result = client.execute
|
||||
# :key => config.youtube_developer_key,
|
||||
# :api_method => youtube.videos.list,
|
||||
# :parameters => {:id => '<YOUR_VIDEO_ID>', :part => 'snippet'}
|
||||
# result = JSON.parse(result.data.to_json)
|
||||
end
|
||||
|
||||
# Return a login URL that will show a web page with
|
||||
def get_login_url(username=nil)
|
||||
uri = "https://accounts.google.com/o/oauth2/auth"
|
||||
uri << "?scope=#{CGI.escape('https://www.googleapis.com/auth/youtube https://www.googleapis.com/auth/youtube.upload https://gdata.youtube.com email profile ')}" # # https://www.googleapis.com/auth/youtube https://www.googleapis.com/auth/youtube.upload
|
||||
uri << "&client_id=#{CGI.escape(self.config.google_email)}"
|
||||
#uri << "&client_secret=#{CGI.escape(self.config.google_secret)}"
|
||||
uri << "&response_type=code"
|
||||
uri << "&access_type=online"
|
||||
uri << "&prompt=consent"
|
||||
uri << "&state=4242"
|
||||
uri << "&redirect_uri=#{redirect_uri}"
|
||||
if username.present?
|
||||
uri << "&login_hint=#{(username)}"
|
||||
end
|
||||
uri
|
||||
end
|
||||
|
||||
# Contacts youtube and prepares an upload to youtube. This
|
||||
# process is somewhat painful, even in ruby, so we do the preparation
|
||||
# and the client does the actual upload using the URL returned:
|
||||
|
||||
# https://developers.google.com/youtube/v3/docs/videos/insert
|
||||
# https://developers.google.com/youtube/v3/guides/using_resumable_upload_protocol
|
||||
def upload_sign(user, filename, length)
|
||||
raise ArgumentError, "Length is required and should be > 0" if length.to_i.zero?
|
||||
# Something like this:
|
||||
# POST /upload/youtube/v3/videos?uploadType=resumable&part=snippet,status,contentDetails HTTP/1.1
|
||||
# Host: www.googleapis.com
|
||||
# Authorization: Bearer AUTH_TOKEN
|
||||
# Content-Length: 278
|
||||
# Content-Type: application/json; charset=UTF-8
|
||||
# X-Upload-Content-Length: 3000000
|
||||
# X-Upload-Content-Type: video/*
|
||||
|
||||
# {
|
||||
# "snippet": {
|
||||
# "title": "My video title",
|
||||
# "description": "This is a description of my video",
|
||||
# "tags": ["cool", "video", "more keywords"],
|
||||
# "categoryId": 22
|
||||
# },
|
||||
# "status": {
|
||||
# "privacyStatus": "public",
|
||||
# "embeddable": True,
|
||||
# "license": "youtube"
|
||||
# }
|
||||
# }
|
||||
auth = UserAuthorization.google_auth(user).first
|
||||
if auth.nil? || auth.token.nil?
|
||||
raise SecurityError, "No current google token found for user #{user}"
|
||||
end
|
||||
|
||||
video_data = {
|
||||
"snippet"=> {
|
||||
"title"=> filename,
|
||||
"description"=> filename,
|
||||
"tags"=> ["cool", "video", "more keywords"],
|
||||
"categoryId"=>1
|
||||
},
|
||||
"status"=> {
|
||||
"privacyStatus"=> "public",
|
||||
"embeddable"=> true,
|
||||
"license"=> "youtube"
|
||||
}
|
||||
}
|
||||
|
||||
conn = Faraday.new(:url =>"https://www.googleapis.com",:ssl => {:verify => false}) do |faraday|
|
||||
faraday.request :url_encoded
|
||||
faraday.response :logger
|
||||
faraday.adapter Faraday.default_adapter
|
||||
end
|
||||
|
||||
video_json=video_data.to_json
|
||||
# result = conn.post do |req|
|
||||
# req.url('/upload/youtube/v3/videos?uploadType=resumable&part=snippet,status')
|
||||
# req.headers['authorization']="bearer #{(auth.token)}"
|
||||
# req.headers['content-type']='application/json;charset=utf-8'
|
||||
# req.headers['x-Upload-Content-Length']="#{length}"
|
||||
# req.headers['x-upload-content-type']="video/*"
|
||||
# req.body = video_json
|
||||
# end
|
||||
# access_token=#{CGI.escape(auth.token)}
|
||||
result = conn.post("/upload/youtube/v3/videos?access_token=#{CGI.escape(auth.token)}&uploadType=resumable&part=snippet,status,contentDetails",
|
||||
video_json,
|
||||
{
|
||||
# 'client_id'=>"#{(self.config.google_email)}",
|
||||
# 'client_secret'=>config.google_secret,
|
||||
#'Authorization'=>"bearer #{(auth.token)}",
|
||||
'content-type'=>'application/json;charset=utf-8',
|
||||
'x-Upload-Content-Length'=>"#{length}",
|
||||
'x-upload-content-type'=>"video/*"
|
||||
}
|
||||
)
|
||||
|
||||
#puts result.inspect
|
||||
# Response should something look like:
|
||||
# HTTP/1.1 200 OK
|
||||
# Location: https://www.googleapis.com/upload/youtube/v3/videos?uploadType=resumable&upload_id=xa298sd_f&part=snippet,status,contentDetails
|
||||
# Content-Length: 0
|
||||
|
||||
if (result.nil? || result.status!=200 || result.headers['location'].blank?)
|
||||
msg = "Failed signing with status=#{result.status} #{result.inspect}: "
|
||||
if result.body.present? && result.body.length > 2
|
||||
msg << result.body.inspect# JSON.parse(result.body).inspect
|
||||
end
|
||||
|
||||
# TODO: how to test for this:
|
||||
# If reason is "youtubeSignupRequired"
|
||||
# If the user's youtube account is unlinked, they'll have to go here.
|
||||
# http://m.youtube.com/create_channel. With v3, there is no automated way to do this.
|
||||
raise msg
|
||||
else
|
||||
# This has everything one needs to start the upload to youtube:
|
||||
{
|
||||
"method" => "PUT",
|
||||
"url" => result.headers['location'],
|
||||
"Authorization" => "Bearer #{auth.token}",
|
||||
"Content-Length" => length,
|
||||
"Content-Type" => "video/*"
|
||||
}
|
||||
end
|
||||
|
||||
# This has everything one needs to start the upload to youtube:
|
||||
end
|
||||
|
||||
# https://developers.google.com/youtube/v3/guides/using_resumable_upload_protocol#Check_Upload_Status
|
||||
def get_upload_status(user, upload_url, length)
|
||||
auth = UserAuthorization.google_auth(user).first
|
||||
if auth.nil? || auth.token.nil?
|
||||
raise SecurityError, "No current google token found for user #{user}"
|
||||
end
|
||||
|
||||
# PUT UPLOAD_URL HTTP/1.1
|
||||
# Authorization: Bearer AUTH_TOKEN
|
||||
# Content-Length: 0
|
||||
# Content-Range: bytes */CONTENT_LENGTH
|
||||
RestClient.put(upload_url, nil, {
|
||||
'Authorization' => "Bearer #{auth.token}",
|
||||
'Content-Length'=> "0",
|
||||
'Content-Range' => "bytes */#{length}"
|
||||
}) do |response, request, result|
|
||||
# puts "response: #{response.class}: #{response.code} / #{response.headers} / #{response.body}"
|
||||
# Result looks like this:
|
||||
# 308 Resume Incomplete
|
||||
# Content-Length: 0
|
||||
# Range: bytes=0-999999
|
||||
case(response.code)
|
||||
when 200..207
|
||||
result_hash = {
|
||||
"offset" => 0,
|
||||
"length" => length,
|
||||
"status" => response.code
|
||||
}
|
||||
when 308
|
||||
range_str = response.headers['Range']
|
||||
if range_str.nil?
|
||||
range = 0..length
|
||||
else
|
||||
range = range_str.split("-")
|
||||
end
|
||||
result_hash = {
|
||||
"offset" => range.first.to_i,
|
||||
"length" => range.last.to_i,
|
||||
"status" => response.code
|
||||
}
|
||||
else
|
||||
raise "Unexpected status from youtube: [#{response.code}] with headers: #{response.headers.inspect}"
|
||||
end
|
||||
|
||||
result_hash
|
||||
end
|
||||
end
|
||||
|
||||
# @return true if file specified by URL uploaded, false otherwise
|
||||
def verify_upload(user, upload_url, length)
|
||||
status_hash=get_upload_status(user, upload_url, length)
|
||||
(status_hash['status']>=200 && status_hash['status']<300)
|
||||
end
|
||||
|
||||
def complete_upload(recorded_video)
|
||||
if (verify_upload(recorded_video.user, recorded_video.url, recorded_video.length))
|
||||
recorded_video.update_attribute(:fully_uploaded, true)
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
# This will also sign in and prompt for login as necessary;
|
||||
# currently requires the server to be running at localhost:3000
|
||||
def signin_flow()
|
||||
config = Rails.application.config
|
||||
|
||||
self.client = Google::APIClient.new(
|
||||
:application_name => 'JamKazam',
|
||||
:application_version => '1.0.0'
|
||||
)
|
||||
|
||||
flow = Google::APIClient::InstalledAppFlow.new(
|
||||
:client_id => config.google_client_id,
|
||||
:client_secret => config.google_secret,
|
||||
:redirect_uri=>redirect_uri,
|
||||
:scope => 'email profile'
|
||||
)
|
||||
|
||||
self.client.authorization = flow.authorize
|
||||
end
|
||||
|
||||
# Must manually confirm to obtain refresh token:
|
||||
# 4/ZwtU8nNgiEiu2JlJMrmnnw.Qo7Zys7XjRoZPm8kb2vw2M2j2ZEskgI
|
||||
def get_refresh_token
|
||||
config = Rails.application.config
|
||||
conn = Faraday.new(:url => 'https://accounts.google.com',:ssl => {:verify => false}) do |faraday|
|
||||
faraday.request :url_encoded
|
||||
faraday.response :logger
|
||||
faraday.adapter Faraday.default_adapter
|
||||
end
|
||||
|
||||
wait_for_callback do |refresh_token|
|
||||
Rails.logger.info("The refresh_token is #{refresh_token}")
|
||||
end
|
||||
|
||||
|
||||
result = conn.get '/o/oauth2/auth', {
|
||||
'scope'=>'email profile',
|
||||
'client_id'=>config.google_client_id,
|
||||
'response_type'=>"code",
|
||||
'access_type'=>"offline",
|
||||
'redirect_uri'=>redirect_uri
|
||||
}
|
||||
end
|
||||
|
||||
def get_access_token(refresh_token)
|
||||
refresh_token = "4/g9uZ8S4lq2Bj1J8PPIkgOFKhTKmCHSmRe68iHA75hRg.gj8Nt5bpVYQdPm8kb2vw2M23tnRnkgI"
|
||||
#refresh_token = "4/ZwtU8nNgiEiu2JlJMrmnnw.Qo7Zys7XjRoZPm8kb2vw2M2j2ZEskgI"
|
||||
config = Rails.application.config
|
||||
conn = Faraday.new(:url => 'https://accounts.google.com',:ssl => {:verify => false}) do |faraday|
|
||||
faraday.request :url_encoded
|
||||
faraday.response :logger
|
||||
faraday.adapter Faraday.default_adapter
|
||||
end
|
||||
|
||||
wait_for_callback do |access_token|
|
||||
Rails.logger.info("The access_token is #{access_token}")
|
||||
#self.server.stop()
|
||||
end
|
||||
|
||||
result = conn.post '/o/oauth2/token', nil, {
|
||||
'scope'=>'email profile',
|
||||
'client_id'=>config.google_client_id,
|
||||
'client_secret'=>config.google_secret,
|
||||
'refresh_token'=>refresh_token,
|
||||
#'response_type'=>"code",
|
||||
'grant_type'=>"refresh_token",
|
||||
#'access_type'=>"offline",
|
||||
'redirect_uri'=>redirect_uri
|
||||
}
|
||||
|
||||
Rails.logger.info("REsult: #{result.inspect}\n\n")
|
||||
end
|
||||
|
||||
def wait_for_callback(port=3000)
|
||||
shutdown()
|
||||
self.server = Thread.new {
|
||||
Rails.logger.info("STARTING SERVER THREAD...")
|
||||
tcp_server = TCPServer.new('localhost', port)
|
||||
|
||||
self.socket = tcp_server.accept
|
||||
if self.socket
|
||||
request = self.socket.gets
|
||||
Rails.logger.info("REQUEST: #{request}")
|
||||
|
||||
params=CGI.parse(request)
|
||||
code = params['code'].first
|
||||
# Whack the end part:
|
||||
access_code = code ? code.split(" ").first : ""
|
||||
|
||||
status = (access_code.present?) ? 'OK' : 'EMPTY'
|
||||
Rails.logger.info("access_code is #{status}")
|
||||
token=exchange_for_token(access_code)
|
||||
yield(token)
|
||||
|
||||
response = "#{status}\n"
|
||||
self.socket.print "HTTP/1.1 200 OK\r\n" +
|
||||
"Content-Type: text/plain\r\n" +
|
||||
"Content-Length: #{response.bytesize}\r\n" +
|
||||
"Connection: close\r\n"
|
||||
|
||||
self.socket.print "\r\n"
|
||||
self.socket.print response
|
||||
self.socket.close
|
||||
socket=nil
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
def exchange_for_token(access_code)
|
||||
#puts "EXCHANGING token for code: [#{access_code}] #{access_code.class}"
|
||||
|
||||
conn = Faraday.new(:url =>"https://accounts.google.com",:ssl => {:verify => false}) do |faraday|
|
||||
faraday.request :url_encoded
|
||||
# faraday.request :multipart
|
||||
faraday.response :logger
|
||||
faraday.adapter Faraday.default_adapter
|
||||
end
|
||||
|
||||
exchange_parms={
|
||||
'grant_type'=>'authorization_code',
|
||||
'code'=>(access_code),
|
||||
'client_id'=>(config.google_email),#CGI.escape(config.google_client_id),
|
||||
'client_secret'=>(config.google_secret),
|
||||
'redirect_uri'=>(redirect_uri),
|
||||
}
|
||||
|
||||
result = conn.post('/o/oauth2/token', exchange_parms)
|
||||
if result.body.nil? || result.body.blank?
|
||||
raise "Result not in correct form: [#{result.body}]"
|
||||
end
|
||||
|
||||
body_hash = JSON.parse(result.body)
|
||||
|
||||
#puts "RESULT #{result.body.class}: #{result.body}"
|
||||
#puts "EXCHANGING for token: [#{body_hash['access_token']}]"
|
||||
body_hash['access_token']
|
||||
end
|
||||
|
||||
# shutdown
|
||||
def shutdown()
|
||||
Rails.logger.info("Stopping oauth server...")
|
||||
#Thread.kill(self.server)
|
||||
if (self.socket)
|
||||
begin
|
||||
self.socket.close
|
||||
rescue IOError
|
||||
# Expected for most cases:
|
||||
Rails.logger.info("Socket already closed.")
|
||||
end
|
||||
self.socket = nil
|
||||
end
|
||||
|
||||
# if (self.server)
|
||||
# Thread.kill(self.server)
|
||||
# self.server = nil
|
||||
# end
|
||||
end
|
||||
end # class
|
||||
end # module
|
||||
|
||||
|
|
@ -272,9 +272,23 @@ FactoryGirl.define do
|
|||
factory :track, :class => JamRuby::Track do
|
||||
sound "mono"
|
||||
sequence(:client_track_id) { |n| "client_track_id_seq_#{n}"}
|
||||
|
||||
end
|
||||
|
||||
factory :video_source, :class => JamRuby::VideoSource do
|
||||
#client_video_source_id "test_source_id"
|
||||
sequence(:client_video_source_id) { |n| "client_video_source_id#{n}"}
|
||||
end
|
||||
|
||||
factory :recording, :class => JamRuby::Recording do
|
||||
association :owner, factory: :user
|
||||
association :music_session, factory: :active_music_session
|
||||
|
||||
factory :recording_with_track do
|
||||
before(:create) { |recording|
|
||||
recording.recorded_tracks << FactoryGirl.create(:recorded_track, recording: recording, user: recording.owner)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
factory :recorded_track, :class => JamRuby::RecordedTrack do
|
||||
instrument JamRuby::Instrument.first
|
||||
|
|
@ -289,16 +303,13 @@ FactoryGirl.define do
|
|||
association :recording, factory: :recording
|
||||
end
|
||||
|
||||
factory :recording, :class => JamRuby::Recording do
|
||||
|
||||
association :owner, factory: :user
|
||||
association :music_session, factory: :active_music_session
|
||||
|
||||
factory :recording_with_track do
|
||||
before(:create) { |recording|
|
||||
recording.recorded_tracks << FactoryGirl.create(:recorded_track, recording: recording, user: recording.owner)
|
||||
}
|
||||
end
|
||||
factory :recorded_video, :class => JamRuby::RecordedVideo do
|
||||
sequence(:recording_id) { |n| "recording_id-#{n}"}
|
||||
sequence(:client_video_source_id) { |n| "client_video_source_id-#{n}"}
|
||||
fully_uploaded true
|
||||
length 1
|
||||
association :user, factory: :user
|
||||
association :recording, factory: :recording
|
||||
end
|
||||
|
||||
factory :claimed_recording, :class => JamRuby::ClaimedRecording do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
require 'spec_helper'
|
||||
require 'youtube_client'
|
||||
|
||||
describe "OAuth", :slow=>true, :js=>true, :type=>:feature, :capybara_feature=>true do
|
||||
|
||||
subject { page }
|
||||
|
||||
before(:all) do
|
||||
Capybara.javascript_driver = :poltergeist
|
||||
Capybara.current_driver = Capybara.javascript_driver
|
||||
Capybara.default_wait_time = 10
|
||||
@previous_run_server = Capybara.run_server
|
||||
Capybara.run_server = false
|
||||
@user=FactoryGirl.create(:user, :email=>"jamkazamtest@gmail.com")
|
||||
end
|
||||
|
||||
before(:each) do
|
||||
@youtube_client = YouTubeClient.new()
|
||||
end
|
||||
|
||||
after(:each) do
|
||||
@youtube_client.shutdown if @youtube_client
|
||||
@youtube_client=nil
|
||||
@user.user_authorizations.destroy_all
|
||||
#page.driver.remove_cookie(:remember_token)
|
||||
end
|
||||
|
||||
after(:all) do
|
||||
@user.destroy
|
||||
Capybara.run_server = @previous_run_server
|
||||
end
|
||||
|
||||
it "client should not authorize a wrong password" do
|
||||
expect {
|
||||
authorize_google_user(@youtube_client, @user, "f00bar")
|
||||
}.to raise_error
|
||||
|
||||
@user.reload
|
||||
@user.user_authorizations.count.should eq(0)
|
||||
end
|
||||
|
||||
it "client should authorize a google user" do
|
||||
authorize_google_user(@youtube_client, @user, "stinkyblueberryjam")
|
||||
save_screenshot("working.png")
|
||||
@user.reload
|
||||
@user.user_authorizations.count.should eq(1)
|
||||
|
||||
google_auth = UserAuthorization.google_auth(@user).first
|
||||
google_auth.should_not be_nil
|
||||
google_auth.token.should_not be_nil
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
require 'spec_helper'
|
||||
require 'youtube_client'
|
||||
require 'rest_client'
|
||||
|
||||
describe "YouTube", :slow=>true, :js=>true, :type => :feature, :capybara_feature => true do
|
||||
subject { page }
|
||||
|
||||
before(:all) do
|
||||
Capybara.javascript_driver = :poltergeist
|
||||
Capybara.current_driver = Capybara.javascript_driver
|
||||
Capybara.default_wait_time = 10
|
||||
@previous_run_server = Capybara.run_server
|
||||
Capybara.run_server = false
|
||||
@user=FactoryGirl.create(:user, :email => "jamkazamtest@gmail.com")
|
||||
@youtube_client = YouTubeClient.new()
|
||||
authorize_google_user(@youtube_client, @user, "stinkyblueberryjam")
|
||||
google_auth = UserAuthorization.google_auth(@user).first # Consider returning this from above now that it is reliable
|
||||
end
|
||||
|
||||
after(:all) do
|
||||
@user.destroy
|
||||
@youtube_client.shutdown
|
||||
Capybara.run_server = @previous_run_server
|
||||
end
|
||||
|
||||
it "should retrieve upload url" do
|
||||
length = 3276
|
||||
upload_hash=@youtube_client.upload_sign(@user, "test_video.mp4", length)
|
||||
upload_hash.should_not be_nil
|
||||
upload_hash.length.should be >=1
|
||||
upload_hash['method'].should eq("PUT")
|
||||
upload_hash['url'].should_not be_nil
|
||||
upload_hash['Authorization'].should_not be_nil
|
||||
upload_hash['Content-Length'].should_not be_nil
|
||||
upload_hash['Content-Length'].should eq(length)
|
||||
upload_hash['Content-Type'].should_not be_nil
|
||||
|
||||
@youtube_client.verify_upload(@user, upload_hash['url'], length).should be_false
|
||||
end
|
||||
|
||||
it "upload url should allow uploading" do
|
||||
vid_path = Rails.root.join('spec', 'files', 'test_video.mp4')
|
||||
length = File.size?(vid_path)
|
||||
upload_hash=@youtube_client.upload_sign(@user, "test_video.mp4", length)
|
||||
#puts upload_hash.inspect
|
||||
upload_hash.should_not be_nil
|
||||
upload_hash.length.should be >=1
|
||||
upload_hash['method'].should eq("PUT")
|
||||
upload_hash['url'].should_not be_nil
|
||||
upload_hash['Authorization'].should_not be_nil
|
||||
upload_hash['Content-Length'].should_not be_nil
|
||||
upload_hash['Content-Length'].should eq(length)
|
||||
upload_hash['Content-Type'].should_not be_nil
|
||||
|
||||
# Upload this file as the client would:
|
||||
RestClient.put(upload_hash['url'], File.read(vid_path))
|
||||
@youtube_client.verify_upload(@user, upload_hash['url'], length).should be_true
|
||||
#@youtube_client.get_upload_status(@user, upload_hash['url'], length)
|
||||
end
|
||||
|
||||
it "sets upload flag when complete" do
|
||||
@music_session = FactoryGirl.create(:active_music_session, :creator => @user, :musician_access => true)
|
||||
@connection = FactoryGirl.create(:connection, :user => @user, :music_session => @music_session)
|
||||
@video_source = FactoryGirl.create(:video_source, :connection => @connection)
|
||||
@recording = FactoryGirl.create(:recording, owner: @user, band: nil, duration:1)
|
||||
|
||||
|
||||
vid_path = Rails.root.join('spec', 'files', 'test_video.mp4')
|
||||
length = File.size?(vid_path)
|
||||
upload_hash=@youtube_client.upload_sign(@user, "test_video.mp4", length)
|
||||
upload_hash.should_not be_nil
|
||||
upload_hash['url'].should_not be_nil
|
||||
RestClient.put(upload_hash['url'], File.read(vid_path))
|
||||
|
||||
recorded_video = FactoryGirl.create(:recorded_video,
|
||||
recording: @recording,
|
||||
user: @recording.owner,
|
||||
fully_uploaded: false,
|
||||
url: upload_hash['url'],
|
||||
length: length
|
||||
)
|
||||
|
||||
@recording.recorded_videos << recorded_video
|
||||
|
||||
@youtube_client.verify_upload(@user, upload_hash['url'], length).should be_true
|
||||
@youtube_client.complete_upload(recorded_video).should be_true
|
||||
recorded_video.fully_uploaded.should be_true
|
||||
end
|
||||
end
|
||||
Binary file not shown.
|
|
@ -192,10 +192,11 @@ bputs "before register capybara"
|
|||
end
|
||||
|
||||
config.before(:each) do
|
||||
if example.metadata[:js]
|
||||
if example.metadata[:js] && (Capybara.current_driver.nil? || Capybara.current_driver.empty? || Capybara.current_driver==:poltergeist)
|
||||
page.driver.resize(1920, 1080)
|
||||
page.driver.headers = { 'User-Agent' => 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:25.0) Gecko/20100101 Firefox/25.0' }
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
config.before(:each, :js => true) do
|
||||
|
|
@ -212,7 +213,6 @@ bputs "before register capybara"
|
|||
|
||||
Capybara.reset_sessions!
|
||||
reset_session_mapper
|
||||
|
||||
end
|
||||
|
||||
config.after(:each) do
|
||||
|
|
|
|||
|
|
@ -73,6 +73,45 @@ def wipe_s3_test_bucket
|
|||
end
|
||||
end
|
||||
|
||||
def authorize_google_user(youtube_client, user, google_password)
|
||||
youtube_client.wait_for_callback(2112) do |access_token|
|
||||
#puts("Authorizing with token #{access_token}")
|
||||
user_auth_hash = {
|
||||
:provider => "google_login",
|
||||
:uid => user.email,
|
||||
:token => access_token,
|
||||
:token_expiration => nil,
|
||||
:secret => ""
|
||||
}
|
||||
authorization = user.user_authorizations.build(user_auth_hash)
|
||||
authorization.save
|
||||
end
|
||||
|
||||
url = youtube_client.get_login_url(user.email)
|
||||
#puts("Login URL: #{url}")
|
||||
visit url
|
||||
sleep(1)
|
||||
# save_screenshot("initial.png")
|
||||
|
||||
# Fill in password (the username is filled in with a hint in URL):
|
||||
# fill_in "Usernm", with: user.email
|
||||
fill_in "Passwd", with: google_password
|
||||
#save_screenshot("password.png")
|
||||
|
||||
find('#signIn').trigger(:click)
|
||||
# Wait for submit to enable and then click it:
|
||||
sleep(5)
|
||||
|
||||
#save_screenshot("signin.png")
|
||||
|
||||
#save_screenshot("submit.png")
|
||||
find('#submit_approve_access').trigger(:click)
|
||||
#save_screenshot("log2.png")
|
||||
sleep(5)
|
||||
#save_screenshot("submitted.png")
|
||||
|
||||
youtube_client
|
||||
end
|
||||
|
||||
def sign_in(user)
|
||||
visit signin_path
|
||||
|
|
@ -117,6 +156,7 @@ def sign_in_poltergeist(user, options = {})
|
|||
end
|
||||
|
||||
visit signin_path
|
||||
page.should have_selector('#landing-inner form.signin-form')
|
||||
|
||||
within('#landing-inner form.signin-form') do
|
||||
fill_in "Email Address:", with: user.email
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
For access to the youtube and google APIs, we need an access_token
|
||||
|
||||
To obtain an access token, one must actually log into google using a browser running javascript. This redirects to the URL specified, as long as it is specified in the oauth configuration.
|
||||
|
||||
Getting an access token for the purposes of automated testing is tricky, but possible using Capybara with a javascript-enabled driver. (Note, web/spec/support/utilities.rb utilizes the JK youtube client to perform the intricate bits):
|
||||
|
||||
1) Obtain the login URL. It's ugly, but we can get it from the YouTubeClient. It contains the callback URL, as well as a "hint" that will fill in the username for us.
|
||||
2) Start a web server on an enabled callback server, such as localhost:3000
|
||||
3) Obtain the URL using a known test user
|
||||
4) Visit the URL in a capybara test
|
||||
4a) Fill in password with the right value
|
||||
4b) Click the login button
|
||||
4c) The approve page should load. Wait for the approve button to be enabled. This is usually a second or two after the page loads, but not immediately.
|
||||
4d) Click the approve button
|
||||
5) After google approves, some javascript will redirect to our test web server, which contains a code. This is not the access_token, but a one-time code that can be exchanged for an access_token, again POSTing to google's auth server. You can see it in gory detail in YouTubeClient.exchange_for_token.
|
||||
6) If all goes well, the test web server will call back the invoker with a real access token.
|
||||
7) For testing purposes, stick the access token in the user.user_authorizations table for the user for which we are testing.
|
||||
|
||||
Notes:
|
||||
* When authenticating, client_id is required by several of the APIs. However, this doesn't work for /o/oauth2/token. What actually works is the "email" value from the developer console. This is now saved in the app as well.
|
||||
|
||||
The tests in question use the following credentials:
|
||||
u: jamkazamtest@gmail.com
|
||||
p: stinkyblueberryjam
|
||||
|
||||
Also, a server is started on port 2112, as 3000 was already being used on the build server.
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.3 KiB |
Loading…
Reference in New Issue