From de1d7096213111bd4bd7295d9e9db19cf1534555 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Sun, 16 Nov 2014 16:24:43 -0600 Subject: [PATCH 01/72] Fix fat finger. --- web/app/views/clients/_home.html.slim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/app/views/clients/_home.html.slim b/web/app/views/clients/_home.html.slim index 444b52bd9..5492f904c 100644 --- a/web/app/views/clients/_home.html.slim +++ b/web/app/views/clients/_home.html.slim @@ -3,7 +3,7 @@ -jamtracks=Rails.configuration.jam_tracks_available -if (jamtracks) -small_tile_size="2.4" - -column_positions=["0.0,1", "2.4,1", "4.8,1", "7.2,1", "9.6,1" + -column_positions=["0.0,1", "2.4,1", "4.8,1", "7.2,1", "9.6,1"] -else -small_tile_size="3.0" -column_positions=["0,1", "3,1", "0,1", "6,1", "9,1"] From ab2b4fb5da35151f7a468da59823260bc3ecf091 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Sun, 16 Nov 2014 16:58:53 -0600 Subject: [PATCH 02/72] VRFS-2473 - Disallow blank options on jam_track track instrument field, as well as type field. --- .../views/admin/jam_tracks/_jam_track_track_fields.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/admin/app/views/admin/jam_tracks/_jam_track_track_fields.html.haml b/admin/app/views/admin/jam_tracks/_jam_track_track_fields.html.haml index 11200771b..ad5b37cc6 100644 --- a/admin/app/views/admin/jam_tracks/_jam_track_track_fields.html.haml +++ b/admin/app/views/admin/jam_tracks/_jam_track_track_fields.html.haml @@ -1,8 +1,8 @@ = f.inputs name: 'Track fields' do %ol.nested-fields - = f.input :track_type, :as => :select, collection: JamRuby::JamTrackTrack::TRACK_TYPE - = f.input :instrument, collection: Instrument.all + = f.input :track_type, :as => :select, collection: JamRuby::JamTrackTrack::TRACK_TYPE, include_blank: false + = f.input :instrument, collection: Instrument.all, include_blank: false = f.input :part = f.input :position From 3484f169bae2cd326268d9b77fe7bf609cef3c45 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Sun, 16 Nov 2014 16:59:54 -0600 Subject: [PATCH 03/72] VRFS-2475 - Disallow blank option for Time Signature, Status, Type, Licensor, Pro, Genre and Sales Region fields. --- admin/app/views/admin/jam_tracks/_form.html.haml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/admin/app/views/admin/jam_tracks/_form.html.haml b/admin/app/views/admin/jam_tracks/_form.html.haml index 5c2028673..263aa28e3 100644 --- a/admin/app/views/admin/jam_tracks/_form.html.haml +++ b/admin/app/views/admin/jam_tracks/_form.html.haml @@ -5,16 +5,16 @@ = f.input :name = f.input :description = f.input :bpm - = f.input :time_signature, collection: JamRuby::JamTrack::TIME_SIGNATURES - = f.input :status, collection: JamRuby::JamTrack::STATUS - = f.input :recording_type, collection: JamRuby::JamTrack::RECORDING_TYPE + = f.input :time_signature, collection: JamRuby::JamTrack::TIME_SIGNATURES, include_blank: false + = f.input :status, collection: JamRuby::JamTrack::STATUS, include_blank: false + = f.input :recording_type, collection: JamRuby::JamTrack::RECORDING_TYPE, include_blank: false = f.input :original_artist = f.input :songwriter = f.input :publisher - = f.input :licensor, collection: JamRuby::JamTrackLicensor.all - = f.input :pro, collection: JamRuby::JamTrack::PRO - = f.input :genre, collection: JamRuby::Genre.all - = f.input :sales_region, collection: JamRuby::JamTrack::SALES_REGION + = f.input :licensor, collection: JamRuby::JamTrackLicensor.all, include_blank: false + = f.input :pro, collection: JamRuby::JamTrack::PRO, include_blank: false + = f.input :genre, collection: JamRuby::Genre.all, include_blank: false + = f.input :sales_region, collection: JamRuby::JamTrack::SALES_REGION, include_blank: false = f.input :price = f.input :reproduction_royalty, :label => 'Reproduction Royalty' = f.input :public_performance_royalty, :label => 'Public Performance Royalty' From 220195ef5cf5f2b5ec8ca8ab459a7b53054338f1 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Sun, 16 Nov 2014 17:00:31 -0600 Subject: [PATCH 04/72] Friendlier development environment for active admin UI. --- admin/config/environments/development.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/admin/config/environments/development.rb b/admin/config/environments/development.rb index ffd6b0e8f..d02c02b61 100644 --- a/admin/config/environments/development.rb +++ b/admin/config/environments/development.rb @@ -43,4 +43,7 @@ JamAdmin::Application.configure do # Show the logging configuration on STDOUT config.show_log_configuration = true + + # set to false to instead use amazon. You will also need to supply amazon secrets + config.store_artifacts_to_disk = true end From 696cdc028377b7855a157518873258ba9caa00fd Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Sun, 16 Nov 2014 17:30:49 -0600 Subject: [PATCH 05/72] VRFS-2476 : Field size and length to improve JamTracks Admin UI. --- admin/app/views/admin/jam_tracks/_form.html.haml | 10 +++++----- .../admin/jam_tracks/_jam_track_track_fields.html.haml | 2 +- admin/config/environments/development.rb | 3 --- admin/config/environments/production.rb | 3 ++- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/admin/app/views/admin/jam_tracks/_form.html.haml b/admin/app/views/admin/jam_tracks/_form.html.haml index 263aa28e3..d9346aa8f 100644 --- a/admin/app/views/admin/jam_tracks/_form.html.haml +++ b/admin/app/views/admin/jam_tracks/_form.html.haml @@ -2,15 +2,15 @@ = f.semantic_errors *f.object.errors.keys = f.inputs name: 'JamTrack fields' do - = f.input :name - = f.input :description + = f.input :name, :input_html => { :rows=>1, :maxlength=>200 } + = f.input :description, :input_html => { :rows=>5, :maxlength=>1000 } = f.input :bpm = f.input :time_signature, collection: JamRuby::JamTrack::TIME_SIGNATURES, include_blank: false = f.input :status, collection: JamRuby::JamTrack::STATUS, include_blank: false = f.input :recording_type, collection: JamRuby::JamTrack::RECORDING_TYPE, include_blank: false - = f.input :original_artist - = f.input :songwriter - = f.input :publisher + = f.input :original_artist, :input_html => { :rows=>2, :maxlength=>200 } + = f.input :songwriter, :input_html => { :rows=>5, :maxlength=>1000 } + = f.input :publisher, :input_html => { :rows=>5, :maxlength=>1000 } = f.input :licensor, collection: JamRuby::JamTrackLicensor.all, include_blank: false = f.input :pro, collection: JamRuby::JamTrack::PRO, include_blank: false = f.input :genre, collection: JamRuby::Genre.all, include_blank: false diff --git a/admin/app/views/admin/jam_tracks/_jam_track_track_fields.html.haml b/admin/app/views/admin/jam_tracks/_jam_track_track_fields.html.haml index ad5b37cc6..c9835865b 100644 --- a/admin/app/views/admin/jam_tracks/_jam_track_track_fields.html.haml +++ b/admin/app/views/admin/jam_tracks/_jam_track_track_fields.html.haml @@ -3,7 +3,7 @@ %ol.nested-fields = f.input :track_type, :as => :select, collection: JamRuby::JamTrackTrack::TRACK_TYPE, include_blank: false = f.input :instrument, collection: Instrument.all, include_blank: false - = f.input :part + = f.input :part, :input_html => { :rows=>1, :maxlength=>20 } = f.input :position - if f.object.new_record? diff --git a/admin/config/environments/development.rb b/admin/config/environments/development.rb index d02c02b61..ffd6b0e8f 100644 --- a/admin/config/environments/development.rb +++ b/admin/config/environments/development.rb @@ -43,7 +43,4 @@ JamAdmin::Application.configure do # Show the logging configuration on STDOUT config.show_log_configuration = true - - # set to false to instead use amazon. You will also need to supply amazon secrets - config.store_artifacts_to_disk = true end diff --git a/admin/config/environments/production.rb b/admin/config/environments/production.rb index a071fa961..981c09d7c 100644 --- a/admin/config/environments/production.rb +++ b/admin/config/environments/production.rb @@ -19,7 +19,7 @@ JamAdmin::Application.configure do # Generate digests for assets URLs config.assets.digest = true - + # Defaults to nil and saved in location specified by config.assets.prefix # config.assets.manifest = YOUR_PATH @@ -73,4 +73,5 @@ JamAdmin::Application.configure do config.aws_bucket_public = 'jamkazam-public' config.aws_bucket = 'jamkazam' + end From 2d3bbd2a47e0b9555f7d3653ca8111e909ef0bfc Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Sun, 16 Nov 2014 20:20:54 -0600 Subject: [PATCH 06/72] VRFS-2480 : Recurly specs, fixes and new implementation. --- web/app/controllers/api_recurly_controller.rb | 105 +++++++++-------- web/config/environments/development.rb | 2 +- web/config/environments/test.rb | 1 + web/config/routes.rb | 5 +- web/spec/controllers/api_recurly_spec.rb | 111 ++++++++++++------ 5 files changed, 141 insertions(+), 83 deletions(-) diff --git a/web/app/controllers/api_recurly_controller.rb b/web/app/controllers/api_recurly_controller.rb index 4c841320d..2fd7a0341 100644 --- a/web/app/controllers/api_recurly_controller.rb +++ b/web/app/controllers/api_recurly_controller.rb @@ -1,11 +1,10 @@ class ApiRecurlyController < ApiController - before_filter :api_signed_in_user respond_to :json # create Recurly account def create_account - logger.debug(params[:billing_info]) + #logger.debug(params[:billing_info]) if current_user.recurly_code.nil? @account = Recurly::Account.create( account_code: current_user.id, @@ -31,44 +30,38 @@ class ApiRecurlyController < ApiController @account.billing_info = params[:billing_info] @account.billing_info.save - logger.debug @account + #logger.debug @account.to_json end - respond_with @account - + render :json=>@account.to_json rescue Recurly::Error, NoMethodError => e - render :json => { :message => e.inspect }, :status => 404 + render json: { :message => e.inspect, error: e }, :status => 404 + end + + def delete_account + @account = Recurly::Account.find(current_user.recurly_code) + if @account.present? + @account.destroy + end + render :json=>{}, :status=>200 + rescue Recurly::Error, NoMethodError => e + render json: { message: ValidationMessages::RECURLY_ERROR, error: e}, :status => 404 end # get Recurly account def get_account - @account = Recurly::Account.find(current_user.reculry_code) - respond_with @account + @account = Recurly::Account.find(current_user.recurly_code) + render :json=>@account.to_json rescue Recurly::Error, NoMethodError => e - render :json => { message: ValidationMessages::RECURLY_ERROR}, :status => 404 + render json: { message: ValidationMessages::RECURLY_ERROR, error: e}, :status => 404 end # update Recurly account def update_account - if current_user.recurly_code.nil? - @account = Recurly::Account.create( - account_code: current_user.id, - email: current_user.email, - first_name: current_user.first_name, - last_name: current_user.last_name, - address: { - city: current_user.city, - state: current_user.state, - country: current_user.country - } - ) - else - @account = Recurly::Account.get(current_user.recurly_code) - end - + safe_retrieve_account() @account.first_name = current_user.first_name @account.last_name = current_user.last_name @account.email = current_user.email - @account.update + @account.save if @account.errors.any? response.status = 404 @@ -76,18 +69,18 @@ class ApiRecurlyController < ApiController current_user.recurly_code = @account.account_code current_user.save end - respond_with @account - + render :json=>@account.to_json rescue Recurly::Error, NoMethodError => e - render :json => { message: ValidationMessages::RECURLY_ERROR}, :status => 404 + raise e + render json: { message: ValidationMessages::RECURLY_ERROR, error: e}, :status => 404 end # get subscription def get_subscription - @account = Recurly::Acount.find(current_user.reculry_code) - respond_with @account.subscriptions.last + safe_retrieve_account() + render :json=>@account.subscriptions.last.to_json rescue Recurly::Error, NoMethodError => e - render :json => { message: ValidationMessages::RECURLY_GET_ACCOUNT_ERROR}, :status => 404 + render json: { message: ValidationMessages::RECURLY_ACCOUNT_ERROR, error: e}, :status => 404 end # create subscription @@ -97,44 +90,44 @@ class ApiRecurlyController < ApiController # get Billing Information def billing_info if current_user.recurly_code.nil? - render :json => { message: ValidationMessages::RECURLY_ACCOUNT_ERROR }, :status => 404 and return + render json: { message: ValidationMessages::RECURLY_ACCOUNT_ERROR }, :status => 404 and return else @account = Recurly::Account.find(current_user.recurly_code) - logger.debug @account - respond_with @account.billing_info + #logger.debug @account + render json: @account.billing_info end rescue Recurly::Error, NoMethodError => e - render :json => { message: ValidationMessages::RECURLY_ERROR}, :status => 404 + render json: { message: ValidationMessages::RECURLY_ERROR, error: e}, :status => 404 end # update Billing Information def update_billing_info if current_user.recurly_code.nil? - render :json => { message: ValidationMessages::RECURLY_ACCOUNT_ERROR }, :status => 404 and return + render json: { message: ValidationMessages::RECURLY_ACCOUNT_ERROR }, :status => 404 and return else if params[:first_name].blank? or params[:last_name].blank? or params[:number].blank? or params[:year].blank? or params[:month].blank? or params[:verification_value].blank? - render :json => { message: ValidationMessages::RECURLY_PARAMETER_ERROR }, :status => 404 and return + render json: { message: ValidationMessages::RECURLY_PARAMETER_ERROR }, :status => 404 and return end - @account = Recurly::Acount.find(current_user.reculry_code) + @account = Recurly::Account.find(current_user.recurly_code) @account.billing_info = params @account.billing_info.save - if @account.erros.any? + if @account.errors.any? response.status = :unprocessable_entity end - respond_with @account + render json: @account end rescue Recurly::Error, NoMethodError => e - render :json => { message: ValidationMessages::RECURLY_ERROR}, :status => 404 + render json: { message: ValidationMessages::RECURLY_ERROR, error: e}, :status => 404 end def place_order if current_user.recurly_code.nil? - render :json => { message: ValidationMessages::RECURLY_ACCOUNT_ERROR }, :status => 404 and return + render json: { message: ValidationMessages::RECURLY_ACCOUNT_ERROR }, :status => 404 and return else if params[:first_name].blank? or params[:last_name].blank? or params[:number].blank? or params[:year].blank? or params[:month].blank? or params[:verification_value].blank? - render :json => { message: ValidationMessages::RECURLY_PARAMETER_ERROR }, :status => 404 and return + render json: { message: ValidationMessages::RECURLY_PARAMETER_ERROR }, :status => 404 and return end @account = Recurly::Account.find(current_user.recurly_code) @account.billing_info = params @@ -142,14 +135,32 @@ class ApiRecurlyController < ApiController # create subscription. - if @account.erros.any? + if @account.errors.any? response.status = :unprocessable_entity end - respond_with @account + render json: @account end rescue Recurly::Error, NoMethodError => e - render :json => { message: ValidationMessages::RECURLY_ERROR}, :status => 404 + render json: { message: ValidationMessages::RECURLY_ERROR, error: e}, :status => 404 end +private + def safe_retrieve_account + if current_user.recurly_code.nil? + @account = Recurly::Account.create( + account_code: current_user.id, + email: current_user.email, + first_name: current_user.first_name, + last_name: current_user.last_name, + address: { + city: current_user.city, + state: current_user.state, + country: current_user.country + } + ) + else + @account = Recurly::Account.find(current_user.recurly_code) + end + end end \ No newline at end of file diff --git a/web/config/environments/development.rb b/web/config/environments/development.rb index 3cc7be84b..8aeccd95f 100644 --- a/web/config/environments/development.rb +++ b/web/config/environments/development.rb @@ -89,5 +89,5 @@ SampleApp::Application.configure do config.youtube_developer_key = "AI39si5bPqiNc5GQHscWJh9Wl1WTAr9aZqr_YncUvaR7Kz0rgPdBVWVubHZ94xZ3KLIBqtE9mu3VZe-UpMU80QxXoC66kBNp7A" config.youtube_app_name = "JamKazamDev" - config.jam_tracks_available=false + config.jam_tracks_available=true end diff --git a/web/config/environments/test.rb b/web/config/environments/test.rb index 5f2faac49..c4a57c3b1 100644 --- a/web/config/environments/test.rb +++ b/web/config/environments/test.rb @@ -98,6 +98,7 @@ SampleApp::Application.configure do # Use Public Keys to identify your site when using Recurly.js. See https://docs.recurly.com/js/#include to learn more. config.recurly_public_api_key = 'sc-s6G2OA80Rwyvsb1RmS3mAE' config.recurly_subdomain = 'jamkazam-test' + config.jam_tracks_available=true end diff --git a/web/config/routes.rb b/web/config/routes.rb index 7e298ddeb..43b9b9114 100644 --- a/web/config/routes.rb +++ b/web/config/routes.rb @@ -232,8 +232,11 @@ SampleApp::Application.routes.draw do match '/users/:id/set_password' => 'api_users#set_password', :via => :post # recurly - match '/recurly/create_account' => 'api_recurly#create_account', :via => :post + match '/recurly/create_account' => 'api_recurly#create_account', :via => :post + match '/recurly/delete_account' => 'api_recurly#delete_account', :via => :delete match '/recurly/get_account' => 'api_recurly#get_account', :via => :get + #match '/recurly/get_subscription' => 'api_recurly#get_subscription', :via => :get + match '/recurly/update_account' => 'api_recurly#update_account', :via => :post match '/recurly/billing_info' => 'api_recurly#billing_info', :via => :get match '/recurly/update_billing_info' => 'api_recurly#update_billing_info', :via => :put match '/recurly/place_order' => 'api_recurly#place_order', :via => :put diff --git a/web/spec/controllers/api_recurly_spec.rb b/web/spec/controllers/api_recurly_spec.rb index eb3abbd8f..5cb254793 100644 --- a/web/spec/controllers/api_recurly_spec.rb +++ b/web/spec/controllers/api_recurly_spec.rb @@ -1,34 +1,77 @@ -# require 'spec_helper' -# -# describe ApiRecurlyController do -# render_views -# -# let(:user) { FactoryGirl.create(:user) } -# let(:jamtrack) { FactoryGirl.create(:jam_track) } -# -# before(:each) do -# @billing_info = {} -# @billing_info[:first_name] = user.first_name -# @billing_info[:last_name] = user.last_name -# @billing_info[:address1] = 'Test Address 1' -# @billing_info[:address2] = 'Test Address 2' -# @billing_info[:city] = user.city -# @billing_info[:state] = user.state -# @billing_info[:country] = user.country -# @billing_info[:zip] = '12345' -# @billing_info[:number] = '4111-1111-1111-1111' -# @billing_info[:month] = '08' -# @billing_info[:year] = '2017' -# @billing_info[:verification_value] = '1111' -# controller.current_user = user -# end -# -# describe "start" do -# it "should create recurly account" do -# post :create_account, { :format => 'json', :billing_info => @billing_info} -# response_body = JSON.parse(response.body) -# puts response_body -# response.should be_success -# end -# end -# end +require 'spec_helper' +require 'recurly/account' +describe ApiRecurlyController do + render_views + + let(:user) { FactoryGirl.create(:user) } + let(:jamtrack) { FactoryGirl.create(:jam_track) } + + before(:each) do + @billing_info = {} + @billing_info[:first_name] = user.first_name + @billing_info[:last_name] = user.last_name + @billing_info[:address1] = 'Test Address 1' + @billing_info[:address2] = 'Test Address 2' + @billing_info[:city] = user.city + @billing_info[:state] = user.state + @billing_info[:country] = user.country + @billing_info[:zip] = '12345' + @billing_info[:number] = '4111-1111-1111-1111' + @billing_info[:month] = '08' + @billing_info[:year] = '2017' + @billing_info[:verification_value] = '1111' + controller.current_user = user + end + + after(:each) do + if (user.recurly_code) + @account = Recurly::Account.find(user.recurly_code) + if @account.present? + @account.destroy + end + end + end + + it "should create account" do + post :create_account, { :format => 'json', :billing_info => @billing_info} + response.should be_success + end + + it "should retrieve account" do + post :create_account, { :format => 'json', :billing_info => @billing_info} + response.should be_success + + get :get_account, { :format => 'json'} + body = JSON.parse(response.body) + response.should be_success + puts "body: #{body}" + body['attributes']['email'].should eq(user.email) + end + + it "should update account" do + post :create_account, { :format => 'json', :billing_info => @billing_info} + response.should be_success + body = JSON.parse(response.body) + body['attributes']['first_name'].should eq("Person") + + user.update_attribute(:first_name, "Thing") + post :update_account + body = JSON.parse(response.body) + body['attributes']['first_name'].should eq("Thing") + + get :get_account, { :format => 'json'} + response.should be_success + body = JSON.parse(response.body) + body['attributes']['first_name'].should eq("Thing") + end + + # Note: We don't have any subscriptions yet: + it "should get subscription" do + pending "We don't have any subscriptions yet -- uncomment in routes" + get :get_subscription, { :format => 'json'} + response.should be_success + body = JSON.parse(response.body) + puts body['attributes'] + end + +end From 7f88a7550cd0303ee97a1e521bbd85b5ea3f962c Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Mon, 17 Nov 2014 21:24:39 -0600 Subject: [PATCH 07/72] VRFS-2480 : Incremental --- web/app/assets/javascripts/jam_rest.js | 7 +- web/app/controllers/api_recurly_controller.rb | 110 +++++++++--------- web/config/routes.rb | 2 +- web/spec/controllers/api_recurly_spec.rb | 40 ++++++- 4 files changed, 96 insertions(+), 63 deletions(-) diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js index ad1993899..23a65f4c5 100644 --- a/web/app/assets/javascripts/jam_rest.js +++ b/web/app/assets/javascripts/jam_rest.js @@ -1341,16 +1341,17 @@ function updateBillingInfo(options) { return $.ajax({ - type: "PUT", - url: '/api/recurly/update_billing_info?' + $.param(options), + type: "POST", + url: '/api/recurly/update_billing_info?' + $.param({billing_info: options}), dataType: "json", + //data: JSON.stringify({"billing_info": $.param(options)}), contentType: 'application/json' }); } function placeOrder(options) { return $.ajax({ - type: "PUT", + type: "POST", lll url: '/api/recurly/place_order?' + $.param(options), dataType: "json", contentType: 'application/json' diff --git a/web/app/controllers/api_recurly_controller.rb b/web/app/controllers/api_recurly_controller.rb index 2fd7a0341..67064ba83 100644 --- a/web/app/controllers/api_recurly_controller.rb +++ b/web/app/controllers/api_recurly_controller.rb @@ -1,38 +1,25 @@ class ApiRecurlyController < ApiController before_filter :api_signed_in_user + before_filter :obtain_account, only: [:create_account, :update_account, :get_subscription, :update_billing_info] respond_to :json # create Recurly account def create_account #logger.debug(params[:billing_info]) - if current_user.recurly_code.nil? - @account = Recurly::Account.create( - account_code: current_user.id, - email: current_user.email, - first_name: current_user.first_name, - last_name: current_user.last_name, - address: { - city: current_user.city, - state: current_user.state, - country: current_user.country - } - ) - else - @account = Recurly::Account.find(current_user.recurly_code) - end - - if @account.errors.any? - response.status = 404 - else - current_user.recurly_code = @account.account_code - current_user.save + + + current_user.recurly_code = @account.account_code + current_user.save + if params[:billing_info] && params[:billing_info].any? @account.billing_info = params[:billing_info] @account.billing_info.save - - #logger.debug @account.to_json end - render :json=>@account.to_json + + if @account.billing_info.errors.any? + render json: { :message => @account.errors.inspect} + end + rescue Recurly::Error, NoMethodError => e render json: { :message => e.inspect, error: e }, :status => 404 end @@ -42,7 +29,7 @@ class ApiRecurlyController < ApiController if @account.present? @account.destroy end - render :json=>{}, :status=>200 + render json: {}, status: 200 rescue Recurly::Error, NoMethodError => e render json: { message: ValidationMessages::RECURLY_ERROR, error: e}, :status => 404 end @@ -50,26 +37,25 @@ class ApiRecurlyController < ApiController # get Recurly account def get_account @account = Recurly::Account.find(current_user.recurly_code) - render :json=>@account.to_json + render json: @account.to_json rescue Recurly::Error, NoMethodError => e render json: { message: ValidationMessages::RECURLY_ERROR, error: e}, :status => 404 end # update Recurly account def update_account - safe_retrieve_account() @account.first_name = current_user.first_name @account.last_name = current_user.last_name @account.email = current_user.email @account.save - if @account.errors.any? - response.status = 404 + if @account.errors.any? + render status: 404, json: { message: ValidationMessages::RECURLY_ACCOUNT_ERROR} else current_user.recurly_code = @account.account_code current_user.save - end - render :json=>@account.to_json + render json: @account.to_json + end rescue Recurly::Error, NoMethodError => e raise e render json: { message: ValidationMessages::RECURLY_ERROR, error: e}, :status => 404 @@ -77,8 +63,7 @@ class ApiRecurlyController < ApiController # get subscription def get_subscription - safe_retrieve_account() - render :json=>@account.subscriptions.last.to_json + render json: @account.subscriptions.last.to_json rescue Recurly::Error, NoMethodError => e render json: { message: ValidationMessages::RECURLY_ACCOUNT_ERROR, error: e}, :status => 404 end @@ -89,53 +74,66 @@ class ApiRecurlyController < ApiController # get Billing Information def billing_info + puts "billing_info1" if current_user.recurly_code.nil? - render json: { message: ValidationMessages::RECURLY_ACCOUNT_ERROR }, :status => 404 and return + puts "billing_info2" + render json: { message: ValidationMessages::RECURLY_ACCOUNT_ERROR }, :status => 404 else + puts "billing_info3" @account = Recurly::Account.find(current_user.recurly_code) - #logger.debug @account - render json: @account.billing_info + puts "billing_info3a" + logger.debug("@account:#{@account}") + puts "billing_infob#{@account}" + billing_json = @account.billing_info.nil? ? {}.to_json : @account.billing_info.to_json + render json: billing_json end rescue Recurly::Error, NoMethodError => e + puts "billing_info4#{e}" + logger.debug("Exception #{e}") render json: { message: ValidationMessages::RECURLY_ERROR, error: e}, :status => 404 end # update Billing Information def update_billing_info + puts "update_billing_info with params: #{params.inspect}" if current_user.recurly_code.nil? - render json: { message: ValidationMessages::RECURLY_ACCOUNT_ERROR }, :status => 404 and return + render json: { message: ValidationMessages::RECURLY_ACCOUNT_ERROR }, :status => 404 else - if params[:first_name].blank? or params[:last_name].blank? or params[:number].blank? or params[:year].blank? or params[:month].blank? or params[:verification_value].blank? - render json: { message: ValidationMessages::RECURLY_PARAMETER_ERROR }, :status => 404 and return - end - @account = Recurly::Account.find(current_user.recurly_code) - @account.billing_info = params - @account.billing_info.save + billing_params = params[:billing_info] + if !billing_params || billing_params[:first_name].blank? || billing_params[:last_name].blank? || billing_params[:number].blank? || billing_params[:year].blank? || billing_params[:month].blank? || billing_params[:verification_value].blank? + render json: { message: ValidationMessages::RECURLY_PARAMETER_ERROR }, :status => 404 + else + @account = Recurly::Account.find(current_user.recurly_code) + @account.billing_info=billing_params + #@account.billing_info.save + @account.save - if @account.errors.any? - response.status = :unprocessable_entity - end + if @account.nil? || @account.errors.any? + response.status = :unprocessable_entity + end - render json: @account + render json: @account + end end rescue Recurly::Error, NoMethodError => e + raise e render json: { message: ValidationMessages::RECURLY_ERROR, error: e}, :status => 404 end def place_order if current_user.recurly_code.nil? - render json: { message: ValidationMessages::RECURLY_ACCOUNT_ERROR }, :status => 404 and return + render json: { message: ValidationMessages::RECURLY_ACCOUNT_ERROR }, :status => 404 else - if params[:first_name].blank? or params[:last_name].blank? or params[:number].blank? or params[:year].blank? or params[:month].blank? or params[:verification_value].blank? - render json: { message: ValidationMessages::RECURLY_PARAMETER_ERROR }, :status => 404 and return + if params[:first_name].blank? || params[:last_name].blank? || params[:number].blank? || params[:year].blank? || params[:month].blank? || params[:verification_value].blank? + render json: { message: ValidationMessages::RECURLY_PARAMETER_ERROR }, :status => 404 end @account = Recurly::Account.find(current_user.recurly_code) - @account.billing_info = params + @account.billing_info=billing_params @account.billing_info.save # create subscription. - if @account.errors.any? + if @account.nil? || @account.errors.any? response.status = :unprocessable_entity end @@ -146,7 +144,7 @@ class ApiRecurlyController < ApiController end private - def safe_retrieve_account + def obtain_account if current_user.recurly_code.nil? @account = Recurly::Account.create( account_code: current_user.id, @@ -162,5 +160,11 @@ private else @account = Recurly::Account.find(current_user.recurly_code) end + + if @account.nil? + render json: {message: ValidationMessages::RECURLY_ACCOUNT_ERROR}, status: 404 + elsif @account.errors.any? + render json: {message: @account.errors.inspect}, status: 404 + end end end \ No newline at end of file diff --git a/web/config/routes.rb b/web/config/routes.rb index 43b9b9114..7fc14c825 100644 --- a/web/config/routes.rb +++ b/web/config/routes.rb @@ -239,7 +239,7 @@ SampleApp::Application.routes.draw do match '/recurly/update_account' => 'api_recurly#update_account', :via => :post match '/recurly/billing_info' => 'api_recurly#billing_info', :via => :get match '/recurly/update_billing_info' => 'api_recurly#update_billing_info', :via => :put - match '/recurly/place_order' => 'api_recurly#place_order', :via => :put + match '/recurly/place_order' => 'api_recurly#place_order', :via => :post # login/logout match '/auth_session' => 'api_users#auth_session_create', :via => :post diff --git a/web/spec/controllers/api_recurly_spec.rb b/web/spec/controllers/api_recurly_spec.rb index 5cb254793..e15214c66 100644 --- a/web/spec/controllers/api_recurly_spec.rb +++ b/web/spec/controllers/api_recurly_spec.rb @@ -1,5 +1,6 @@ require 'spec_helper' -require 'recurly/account' +#require 'recurly/account' + describe ApiRecurlyController do render_views @@ -33,15 +34,18 @@ describe ApiRecurlyController do end it "should create account" do - post :create_account, { :format => 'json', :billing_info => @billing_info} - response.should be_success + post :create_account, {:format => 'json', :billing_info=>@billing_info} + response.should be_success + body = JSON.parse(response.body) + body[:billing_info].should_not be_nil + puts "BODY: #{body}" end it "should retrieve account" do - post :create_account, { :format => 'json', :billing_info => @billing_info} + post :create_account, {:format => 'json', :billing_info=>@billing_info} response.should be_success - get :get_account, { :format => 'json'} + get :get_account body = JSON.parse(response.body) response.should be_success puts "body: #{body}" @@ -49,7 +53,7 @@ describe ApiRecurlyController do end it "should update account" do - post :create_account, { :format => 'json', :billing_info => @billing_info} + post :create_account response.should be_success body = JSON.parse(response.body) body['attributes']['first_name'].should eq("Person") @@ -74,4 +78,28 @@ describe ApiRecurlyController do puts body['attributes'] end + it "should update billing info" do + post :create_account + response.should be_success + body = JSON.parse(response.body) + body['attributes']['first_name'].should eq("Person") + + @billing_info[:state] = "NE" + put :update_billing_info, {:format => 'json', :billing_info=>@billing_info} + response.should be_success + body = JSON.parse(response.body) + puts "BILLING_ BODY: #{body}" + + get :billing_info + response.should be_success + puts "response.body: #{response.body}" + body = JSON.parse(response.body) + puts "body: #{body}" + + end + + it "should place order" do + put :place_order + end + end From 0b5d85f4f2246c5dc0a5dadfac4b69986a849e04 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Fri, 21 Nov 2014 17:34:30 -0600 Subject: [PATCH 08/72] VRFS-2480 : Extract recurly logic from controller for cleaner, easier-to-test code. --- web/lib/recurly_client.rb | 103 +++++++++++++++++++++++ web/spec/managers/recurly_client_spec.rb | 81 ++++++++++++++++++ 2 files changed, 184 insertions(+) create mode 100644 web/lib/recurly_client.rb create mode 100644 web/spec/managers/recurly_client_spec.rb diff --git a/web/lib/recurly_client.rb b/web/lib/recurly_client.rb new file mode 100644 index 000000000..101d16ef1 --- /dev/null +++ b/web/lib/recurly_client.rb @@ -0,0 +1,103 @@ +require 'recurly' +module JamRuby + class RecurlyClient + def initialize() + end + + def create_account(current_user, billing_info=nil) + options = account_hash(current_user, billing_info) + begin + account = Recurly::Account.create(options) + raise RecurlyClientError, account.errors.inspect if account.errors.any? + rescue Recurly::Error, NoMethodError => x + raise RecurlyClientError, x.to_s + else + current_user.update_attribute(:recurly_code, account.account_code) + account + end + end + + def delete_account(current_user) + account = get_account(current_user) + if (account) + begin + account.destroy + rescue Recurly::Error, NoMethodError => x + raise RecurlyClientError, x.to_s + end + else + raise RecurlyClientError, "Could not find account to delete." + end + account + end + + def get_account(current_user) + (current_user && current_user.recurly_code) ? Recurly::Account.find(current_user.recurly_code) : nil + end + + def update_account(current_user, billing_info=nil) + account = get_account(current_user) + if(account.present?) + options = account_hash(current_user, billing_info) + begin + account.update_attributes(options) + rescue Recurly::Error, NoMethodError => x + raise RecurlyClientError, x.to_s + end + end + account + end + + def update_billing_info(current_user, billing_info=nil) + account = get_account(current_user) + if (account.present?) + begin + account.billing_info=billing_info + account.billing_info.save + rescue Recurly::Error, NoMethodError => x + raise RecurlyClientError, x.to_s + end + + raise RecurlyClientError, account.errors.inspect if account.errors.any? + else + raise RecurlyClientError, "Could not find account to update billing info." + end + account + end + + def place_order(current_user, billing_info=nil) + end + + def find_or_create_account(current_user, billing_info=nil) + account = get_account(current_user) + + if(account.nil?) + account = create_account(current_user, billing_info) + end + account + end + + + private + def account_hash(current_user, billing_info) + options = { + account_code: current_user.id, + email: current_user.email, + first_name: current_user.first_name, + last_name: current_user.last_name, + address: { + city: current_user.city, + state: current_user.state, + country: current_user.country + } + } + + options[:billing_info] = billing_info if billing_info + options + end + end # class + + class RecurlyClientError < Exception + end +end # module + \ No newline at end of file diff --git a/web/spec/managers/recurly_client_spec.rb b/web/spec/managers/recurly_client_spec.rb new file mode 100644 index 000000000..d68031872 --- /dev/null +++ b/web/spec/managers/recurly_client_spec.rb @@ -0,0 +1,81 @@ +require 'spec_helper' +require "recurly_client" +describe RecurlyClient do + let(:jamtrack) { FactoryGirl.create(:jam_track) } + #let(:client) { RecurlyClient.new } + + before :all do + @client = RecurlyClient.new + @user = FactoryGirl.create(:user) + end + + before(:each) do + @billing_info = {} + @billing_info[:first_name] = @user.first_name + @billing_info[:last_name] = @user.last_name + @billing_info[:address1] = 'Test Address 1' + @billing_info[:address2] = 'Test Address 2' + @billing_info[:city] = @user.city + @billing_info[:state] = @user.state + @billing_info[:country] = @user.country + @billing_info[:zip] = '12345' + @billing_info[:number] = '4111-1111-1111-1111' + @billing_info[:month] = '08' + @billing_info[:year] = '2017' + @billing_info[:verification_value] = '111' + end + + # after(:all) do + # if (@user.recurly_code) + # account = Recurly::Account.find(@user.recurly_code) + # if account.present? + # account.destroy + # end + # end + # end + + it "can create account" do + account = @client.create_account(@user, @billing_info) + account.should_not be_nil + @user.recurly_code.should eq(account.account_code) + end + + describe "with account" do + before :each do + @account = @client.find_or_create_account(@user, @billing_info) + end + + it "can find account" do + @user.recurly_code.should_not be_nil + found = @client.get_account(@user) + found.should_not be_nil + found.first_name.should eq(@user.first_name) + found.last_name.should eq(@user.last_name) + found.email.should eq(@user.email) + end + + it "can update account" do + @user.first_name = "Foobar" + @client.update_account(@user, @billing_info) + found = @client.get_account(@user) + found.first_name.should eq(@user.first_name) + end + + it "can update billing" do + @billing_info['address1'] = "2112 Foobar Lane" + @client.update_billing_info(@user, @billing_info) + found = @client.get_account(@user) + found.billing_info.address1.should eq(@billing_info['address1']) + end + end + + it "can remove account" do + @client.find_or_create_account(@user, @billing_info) + @client.get_account(@user).should_not be_nil + @client.delete_account(@user) + found=@client.get_account(@user) + found.should_not be_nil + found.state.should eq('closed') + end +end + From b35f3ef9ef0f82af86503fb5b40d0ebd13438202 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Mon, 24 Nov 2014 15:05:38 -0600 Subject: [PATCH 09/72] Fix fat finger. --- web/app/assets/javascripts/jam_rest.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js index 23a65f4c5..1464c9368 100644 --- a/web/app/assets/javascripts/jam_rest.js +++ b/web/app/assets/javascripts/jam_rest.js @@ -1351,7 +1351,7 @@ function placeOrder(options) { return $.ajax({ - type: "POST", lll + type: "POST", url: '/api/recurly/place_order?' + $.param(options), dataType: "json", contentType: 'application/json' From 9e95f75e2ebadee9e14174dccc76f90943918bf2 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Mon, 24 Nov 2014 15:06:45 -0600 Subject: [PATCH 10/72] VRFS-2480 : Card number was getting whacked from submittal due to a bad javascript assignment. --- web/app/assets/javascripts/order.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web/app/assets/javascripts/order.js b/web/app/assets/javascripts/order.js index b6a78bff9..e61694a15 100644 --- a/web/app/assets/javascripts/order.js +++ b/web/app/assets/javascripts/order.js @@ -318,15 +318,15 @@ billing_info.month = card_month; billing_info.year = card_year; billing_info.verification_value = card_verify; - + + if (shipping_as_billing) { - shipping_info = billing_info; + shipping_info = $.extend({},billing_info); delete shipping_info.number; delete shipping_info.month; delete shipping_info.year; delete shipping_info.verification_value; - } - else { + } else { shipping_info.first_name = shipping_first_name; shipping_info.last_name = shipping_last_name; shipping_info.address1 = shipping_address1; From b3d80b3be6ba5893fd64a0ee8dcf58923d8bf16d Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Mon, 24 Nov 2014 15:07:55 -0600 Subject: [PATCH 11/72] VRFS-2480 : Fix UI problem where checkout button was obscured. --- .../assets/stylesheets/client/shoppingCart.css.scss | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/web/app/assets/stylesheets/client/shoppingCart.css.scss b/web/app/assets/stylesheets/client/shoppingCart.css.scss index 63f333543..c031e5982 100644 --- a/web/app/assets/stylesheets/client/shoppingCart.css.scss +++ b/web/app/assets/stylesheets/client/shoppingCart.css.scss @@ -2,6 +2,12 @@ .content-body { padding: 50px 20px 20px 20px; + overflow: scroll; + .shopping-cart-content { + //padding-top: 20px; + margin-bottom: 20px; + overflow: scroll; + } .checkout-image { width: 10%; @@ -52,8 +58,8 @@ .cart-items { width: 100%; - height: 300px; - overflow: auto; + min-height: 200px; + // overflow: auto; margin-top: 30px; } From 09163d2778c06455567e8833fe053aa9eb4a1302 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Tue, 25 Nov 2014 13:59:24 -0600 Subject: [PATCH 12/72] Fix javascript to get credit card expiration year from correct field. --- web/app/assets/javascripts/order.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/app/assets/javascripts/order.js b/web/app/assets/javascripts/order.js index e61694a15..77ed30e3a 100644 --- a/web/app/assets/javascripts/order.js +++ b/web/app/assets/javascripts/order.js @@ -267,7 +267,7 @@ var card_name = $paymentMethod.find("#card-name").val(); var card_number = $paymentMethod.find("#card-number").val(); - var card_year = $paymentMethod.find("#card_expire-date_3i").val(); + var card_year = $paymentMethod.find("#card_expire-date_1i").val(); var card_month = $paymentMethod.find("#card_expire-date_2i").val(); var card_verify = $paymentMethod.find("#card-verify").val(); From 300426e2c9fbe4c88fd736909404dd286740b06b Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Tue, 25 Nov 2014 14:34:07 -0600 Subject: [PATCH 13/72] VRFS-2480 : Additional recurly config --- web/config/initializers/backtrace_silencers.rb | 2 +- web/config/initializers/recurly.rb | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/web/config/initializers/backtrace_silencers.rb b/web/config/initializers/backtrace_silencers.rb index 59385cdf3..2eb641eb0 100644 --- a/web/config/initializers/backtrace_silencers.rb +++ b/web/config/initializers/backtrace_silencers.rb @@ -4,4 +4,4 @@ # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. -# Rails.backtrace_cleaner.remove_silencers! +#Rails.backtrace_cleaner.remove_silencers! diff --git a/web/config/initializers/recurly.rb b/web/config/initializers/recurly.rb index c90180051..f25a01e38 100644 --- a/web/config/initializers/recurly.rb +++ b/web/config/initializers/recurly.rb @@ -1,2 +1,4 @@ Recurly.api_key = Rails.configuration.recurly_private_api_key Recurly.subdomain = Rails.configuration.recurly_subdomain +Recurly.default_currency = 'USD' +Recurly.logger = Rails.logger From 7f59067226bb085d313f371389eda56971639a75 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Tue, 25 Nov 2014 14:35:05 -0600 Subject: [PATCH 14/72] VRFS-2480 : Refactor and cleanup, incremental. --- web/app/controllers/api_recurly_controller.rb | 189 +++++------------- web/app/views/clients/_web_filter.html.haml | 1 - web/config/routes.rb | 2 +- web/lib/recurly_client.rb | 27 ++- web/spec/controllers/api_recurly_spec.rb | 99 +++++---- web/spec/managers/recurly_client_spec.rb | 24 ++- 6 files changed, 154 insertions(+), 188 deletions(-) diff --git a/web/app/controllers/api_recurly_controller.rb b/web/app/controllers/api_recurly_controller.rb index 67064ba83..e3f39baec 100644 --- a/web/app/controllers/api_recurly_controller.rb +++ b/web/app/controllers/api_recurly_controller.rb @@ -1,170 +1,85 @@ +require 'recurly_client' class ApiRecurlyController < ApiController before_filter :api_signed_in_user - before_filter :obtain_account, only: [:create_account, :update_account, :get_subscription, :update_billing_info] + before_filter :create_client respond_to :json # create Recurly account def create_account - #logger.debug(params[:billing_info]) - - - current_user.recurly_code = @account.account_code - current_user.save - - if params[:billing_info] && params[:billing_info].any? - @account.billing_info = params[:billing_info] - @account.billing_info.save - end - - if @account.billing_info.errors.any? - render json: { :message => @account.errors.inspect} - end - - rescue Recurly::Error, NoMethodError => e - render json: { :message => e.inspect, error: e }, :status => 404 + @account = @client.find_or_create_account(current_user, params[:billing_info]) + puts "JSON: #{JSON.generate(@account)}" + render :json=>account_json(@account) + rescue RecurlyClientError => x + render json: { :message => x.inspect, errors: x.errors }, :status => 404 end def delete_account - @account = Recurly::Account.find(current_user.recurly_code) - if @account.present? - @account.destroy - end + @client.delete_account(current_user) render json: {}, status: 200 - rescue Recurly::Error, NoMethodError => e - render json: { message: ValidationMessages::RECURLY_ERROR, error: e}, :status => 404 + rescue RecurlyClientError => x + render json: { :message => x.inspect, errors: x.errors}, :status => 404 end # get Recurly account def get_account - @account = Recurly::Account.find(current_user.recurly_code) - render json: @account.to_json - rescue Recurly::Error, NoMethodError => e - render json: { message: ValidationMessages::RECURLY_ERROR, error: e}, :status => 404 + @account=@client.get_account(current_user) + + render :json=>account_json(@account) + rescue RecurlyClientError => e + render json: { message: x.inspect, errors: x.errors}, :status => 404 end # update Recurly account def update_account - @account.first_name = current_user.first_name - @account.last_name = current_user.last_name - @account.email = current_user.email - @account.save - - if @account.errors.any? - render status: 404, json: { message: ValidationMessages::RECURLY_ACCOUNT_ERROR} - else - current_user.recurly_code = @account.account_code - current_user.save - render json: @account.to_json - end - rescue Recurly::Error, NoMethodError => e - raise e - render json: { message: ValidationMessages::RECURLY_ERROR, error: e}, :status => 404 - end - - # get subscription - def get_subscription - render json: @account.subscriptions.last.to_json - rescue Recurly::Error, NoMethodError => e - render json: { message: ValidationMessages::RECURLY_ACCOUNT_ERROR, error: e}, :status => 404 - end - - # create subscription - def create_subscription + @account=@client.update_account(current_user, params[:billing_info]) + render :json=>account_json(@account) + rescue RecurlyClientError => x + render json: { message: x.inspect, errors: x.errors}, :status => 404 end # get Billing Information def billing_info - puts "billing_info1" - if current_user.recurly_code.nil? - puts "billing_info2" - render json: { message: ValidationMessages::RECURLY_ACCOUNT_ERROR }, :status => 404 - else - puts "billing_info3" - @account = Recurly::Account.find(current_user.recurly_code) - puts "billing_info3a" - logger.debug("@account:#{@account}") - puts "billing_infob#{@account}" - billing_json = @account.billing_info.nil? ? {}.to_json : @account.billing_info.to_json - render json: billing_json - end - rescue Recurly::Error, NoMethodError => e - puts "billing_info4#{e}" - logger.debug("Exception #{e}") - render json: { message: ValidationMessages::RECURLY_ERROR, error: e}, :status => 404 + @account = @client.get_account(current_user) + # @billing = @account.billing_info + # @billing ||= @account + render :json=> account_json(@account) + rescue RecurlyClientError => x + render json: { message: x.inspect, errors: x.errors}, :status => 404 end # update Billing Information def update_billing_info - puts "update_billing_info with params: #{params.inspect}" - if current_user.recurly_code.nil? - render json: { message: ValidationMessages::RECURLY_ACCOUNT_ERROR }, :status => 404 - else - billing_params = params[:billing_info] - if !billing_params || billing_params[:first_name].blank? || billing_params[:last_name].blank? || billing_params[:number].blank? || billing_params[:year].blank? || billing_params[:month].blank? || billing_params[:verification_value].blank? - render json: { message: ValidationMessages::RECURLY_PARAMETER_ERROR }, :status => 404 - else - @account = Recurly::Account.find(current_user.recurly_code) - @account.billing_info=billing_params - #@account.billing_info.save - @account.save - - if @account.nil? || @account.errors.any? - response.status = :unprocessable_entity - end - - render json: @account - end - end - rescue Recurly::Error, NoMethodError => e - raise e - render json: { message: ValidationMessages::RECURLY_ERROR, error: e}, :status => 404 + @account=@client.update_billing_info(current_user, params[:billing_info]) + puts "Updated billing on account: #{@account}" + render :json=> account_json(@account) + rescue RecurlyClientError => x + render json: { message: x.inspect, errors: x.errors}, :status => 404 end def place_order - if current_user.recurly_code.nil? - render json: { message: ValidationMessages::RECURLY_ACCOUNT_ERROR }, :status => 404 - else - if params[:first_name].blank? || params[:last_name].blank? || params[:number].blank? || params[:year].blank? || params[:month].blank? || params[:verification_value].blank? - render json: { message: ValidationMessages::RECURLY_PARAMETER_ERROR }, :status => 404 - end - @account = Recurly::Account.find(current_user.recurly_code) - @account.billing_info=billing_params - @account.billing_info.save - - # create subscription. - - if @account.nil? || @account.errors.any? - response.status = :unprocessable_entity - end - - render json: @account - end - rescue Recurly::Error, NoMethodError => e - render json: { message: ValidationMessages::RECURLY_ERROR, error: e}, :status => 404 + @client.update_billing_info(current_user, params[:billing_info]) + render :json=>{}, :status=>200 + rescue RecurlyClientError => x + render json: { message: x.inspect, errors: x.errors}, :status => 404 end private - def obtain_account - if current_user.recurly_code.nil? - @account = Recurly::Account.create( - account_code: current_user.id, - email: current_user.email, - first_name: current_user.first_name, - last_name: current_user.last_name, - address: { - city: current_user.city, - state: current_user.state, - country: current_user.country - } - ) - else - @account = Recurly::Account.find(current_user.recurly_code) - end - - if @account.nil? - render json: {message: ValidationMessages::RECURLY_ACCOUNT_ERROR}, status: 404 - elsif @account.errors.any? - render json: {message: @account.errors.inspect}, status: 404 - end + def create_client + @client = RecurlyClient.new end -end \ No newline at end of file + + def account_json(account) + { + :first_name => account.first_name, + :last_name => account.last_name, + :email => account.email, + :address1 => account.billing_info ? account.billing_info.address1 : nil, + :address2 => account.billing_info ? account.billing_info.address2 : nil, + :city => account.billing_info ? account.billing_info.city : nil, + :state => account.billing_info ? account.billing_info.state : nil, + :zip => account.billing_info ? account.billing_info.zip : nil, + :country => account.billing_info ? account.billing_info.country : nil + } + end + +end # class \ No newline at end of file diff --git a/web/app/views/clients/_web_filter.html.haml b/web/app/views/clients/_web_filter.html.haml index 82ef6f85e..1c64bfd59 100644 --- a/web/app/views/clients/_web_filter.html.haml +++ b/web/app/views/clients/_web_filter.html.haml @@ -57,7 +57,6 @@ / @end score filter -elsif :jamtrack==filter_label / @begin availability filter - %h3 hey =content_tag(:div, 'Availability:', :class => 'filter-element desc') =select_tag("#{filter_label}_availability", options_for_select([['Any', '']].concat(JamRuby::JamTrack::SALES_REGION), 'United States'), {:class => "easydropdown"}) / @end availability filter diff --git a/web/config/routes.rb b/web/config/routes.rb index 7fc14c825..12bd5a3a6 100644 --- a/web/config/routes.rb +++ b/web/config/routes.rb @@ -236,7 +236,7 @@ SampleApp::Application.routes.draw do match '/recurly/delete_account' => 'api_recurly#delete_account', :via => :delete match '/recurly/get_account' => 'api_recurly#get_account', :via => :get #match '/recurly/get_subscription' => 'api_recurly#get_subscription', :via => :get - match '/recurly/update_account' => 'api_recurly#update_account', :via => :post + match '/recurly/update_account' => 'api_recurly#update_account', :via => :put match '/recurly/billing_info' => 'api_recurly#billing_info', :via => :get match '/recurly/update_billing_info' => 'api_recurly#update_billing_info', :via => :put match '/recurly/place_order' => 'api_recurly#place_order', :via => :post diff --git a/web/lib/recurly_client.rb b/web/lib/recurly_client.rb index 101d16ef1..b4dcb02a3 100644 --- a/web/lib/recurly_client.rb +++ b/web/lib/recurly_client.rb @@ -6,15 +6,19 @@ module JamRuby def create_account(current_user, billing_info=nil) options = account_hash(current_user, billing_info) + account = nil begin account = Recurly::Account.create(options) - raise RecurlyClientError, account.errors.inspect if account.errors.any? + raise RecurlyClientError.new(account.errors) if account.errors.any? rescue Recurly::Error, NoMethodError => x + puts "Errorrr: #{x.inspect}" raise RecurlyClientError, x.to_s else - current_user.update_attribute(:recurly_code, account.account_code) - account - end + if account + current_user.update_attribute(:recurly_code, account.account_code) + end + end + account end def delete_account(current_user) @@ -53,12 +57,14 @@ module JamRuby if (account.present?) begin account.billing_info=billing_info + puts "Saving..." account.billing_info.save + puts "...saved" rescue Recurly::Error, NoMethodError => x raise RecurlyClientError, x.to_s end - raise RecurlyClientError, account.errors.inspect if account.errors.any? + raise RecurlyClientError.new(account.errors) if account.errors.any? else raise RecurlyClientError, "Could not find account to update billing info." end @@ -98,6 +104,15 @@ module JamRuby end # class class RecurlyClientError < Exception - end + attr_accessor :errors + def initialize(data) + if data.respond_to?('has_key?') + self.errors = data + else + puts "2222" + self.errors = {:message=>data.to_s} + end + end # initialize + end # RecurlyClientError end # module \ No newline at end of file diff --git a/web/spec/controllers/api_recurly_spec.rb b/web/spec/controllers/api_recurly_spec.rb index e15214c66..4858fe353 100644 --- a/web/spec/controllers/api_recurly_spec.rb +++ b/web/spec/controllers/api_recurly_spec.rb @@ -1,72 +1,84 @@ require 'spec_helper' +require 'recurly_client' #require 'recurly/account' -describe ApiRecurlyController do +describe ApiRecurlyController, :type=>:controller do render_views - let(:user) { FactoryGirl.create(:user) } - let(:jamtrack) { FactoryGirl.create(:jam_track) } + # let(:user) { FactoryGirl.create(:user) } + # let(:jamtrack) { FactoryGirl.create(:jam_track) } before(:each) do + @user = FactoryGirl.create(:user) + #@jamtrack = FactoryGirl.create(:jam_track) @billing_info = {} - @billing_info[:first_name] = user.first_name - @billing_info[:last_name] = user.last_name + @billing_info[:first_name] = @user.first_name + @billing_info[:last_name] = @user.last_name @billing_info[:address1] = 'Test Address 1' @billing_info[:address2] = 'Test Address 2' - @billing_info[:city] = user.city - @billing_info[:state] = user.state - @billing_info[:country] = user.country + @billing_info[:city] = @user.city + @billing_info[:state] = @user.state + @billing_info[:country] = @user.country @billing_info[:zip] = '12345' @billing_info[:number] = '4111-1111-1111-1111' @billing_info[:month] = '08' @billing_info[:year] = '2017' - @billing_info[:verification_value] = '1111' - controller.current_user = user + @billing_info[:verification_value] = '111' + @billing_info[:vat_number] = '' + controller.current_user = @user end after(:each) do - if (user.recurly_code) - @account = Recurly::Account.find(user.recurly_code) + if (@user.recurly_code) + @account = Recurly::Account.find(@user.recurly_code) if @account.present? @account.destroy end end end - it "should create account" do + it "should send correct error" do + @billing_info[:number]='121' post :create_account, {:format => 'json', :billing_info=>@billing_info} - response.should be_success + response.status.should == 404 body = JSON.parse(response.body) - body[:billing_info].should_not be_nil - puts "BODY: #{body}" + puts "body.inspect: #{body.inspect}" + body['errors'].should have(1).items + body['errors']['number'].should_not be_nil + end + + it "should create account" do + post :create_account, {:format => 'json'} + response.should be_success end it "should retrieve account" do - post :create_account, {:format => 'json', :billing_info=>@billing_info} + post :create_account, {:format => 'json'} response.should be_success get :get_account body = JSON.parse(response.body) response.should be_success puts "body: #{body}" - body['attributes']['email'].should eq(user.email) + body['email'].should eq(@user.email) end it "should update account" do post :create_account response.should be_success body = JSON.parse(response.body) - body['attributes']['first_name'].should eq("Person") + body['first_name'].should eq("Person") - user.update_attribute(:first_name, "Thing") - post :update_account + @user.update_attribute(:first_name, "Thing") + controller.current_user = @user + put :update_account body = JSON.parse(response.body) - body['attributes']['first_name'].should eq("Thing") + body['first_name'].should eq("Thing") get :get_account, { :format => 'json'} response.should be_success body = JSON.parse(response.body) - body['attributes']['first_name'].should eq("Thing") + body['first_name'].should eq("Thing") end # Note: We don't have any subscriptions yet: @@ -75,17 +87,39 @@ describe ApiRecurlyController do get :get_subscription, { :format => 'json'} response.should be_success body = JSON.parse(response.body) - puts body['attributes'] + puts body end it "should update billing info" do - post :create_account - response.should be_success - body = JSON.parse(response.body) - body['attributes']['first_name'].should eq("Person") + #pending "this fails in the controller spec only, possibly because it contacts an external site." + $enable_tracing = false + $trace_out = open('trace.txt', 'w') + + set_trace_func proc { |event, file, line, id, binding, classname| + if $enable_tracing && event == 'call' && !file.start_with?("/Users/tangledpath/.ddrvm/") + $trace_out.puts "#{file}:#{line} #{classname}##{id}" + end + } + + $enable_tracing = true + + post :create_account + puts "BACK FROM ACCOUNT: #{response.body}" + response.should be_success + puts "BACK FROM ACCOUNT 1" + body = JSON.parse(response.body) + puts "BACK FROM ACCOUNT 2 #{body}" + body['first_name'].should eq("Person") + +puts "BACK FROM ACCOUNT 3" + @billing_info[:state] = "NE" + puts "BEFORE BILLING" + put :update_billing_info, {:format => 'json', :billing_info=>@billing_info} + puts "BACK FROM billing 1" + response.should be_success body = JSON.parse(response.body) puts "BILLING_ BODY: #{body}" @@ -94,12 +128,7 @@ describe ApiRecurlyController do response.should be_success puts "response.body: #{response.body}" body = JSON.parse(response.body) - puts "body: #{body}" - + puts "body: #{body}" end - it "should place order" do - put :place_order - end - -end +end # spec diff --git a/web/spec/managers/recurly_client_spec.rb b/web/spec/managers/recurly_client_spec.rb index d68031872..e15b5fb6e 100644 --- a/web/spec/managers/recurly_client_spec.rb +++ b/web/spec/managers/recurly_client_spec.rb @@ -25,14 +25,14 @@ describe RecurlyClient do @billing_info[:verification_value] = '111' end - # after(:all) do - # if (@user.recurly_code) - # account = Recurly::Account.find(@user.recurly_code) - # if account.present? - # account.destroy - # end - # end - # end + after(:each) do + if (@user.recurly_code) + account = Recurly::Account.find(@user.recurly_code) + if account.present? + account.destroy + end + end + end it "can create account" do account = @client.create_account(@user, @billing_info) @@ -40,6 +40,11 @@ describe RecurlyClient do @user.recurly_code.should eq(account.account_code) end + it "can create account with errors" do + @billing_info[:verification_value] = '1111' + expect {@client.create_account(@user, @billing_info)}.to raise_error(RecurlyClientError) + end + describe "with account" do before :each do @account = @client.find_or_create_account(@user, @billing_info) @@ -67,6 +72,9 @@ describe RecurlyClient do found = @client.get_account(@user) found.billing_info.address1.should eq(@billing_info['address1']) end + + it "purchases jamtrack" do + end end it "can remove account" do From adecb2c9adb0436c78177c7cc30e0deaf013a3fe Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Mon, 1 Dec 2014 11:45:44 -0600 Subject: [PATCH 15/72] Remove stray puts. --- web/spec/controllers/api_recurly_spec.rb | 33 ++++++++---------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/web/spec/controllers/api_recurly_spec.rb b/web/spec/controllers/api_recurly_spec.rb index 4858fe353..11ee343d3 100644 --- a/web/spec/controllers/api_recurly_spec.rb +++ b/web/spec/controllers/api_recurly_spec.rb @@ -104,31 +104,20 @@ describe ApiRecurlyController, :type=>:controller do $enable_tracing = true - post :create_account - puts "BACK FROM ACCOUNT: #{response.body}" - response.should be_success - puts "BACK FROM ACCOUNT 1" - body = JSON.parse(response.body) - puts "BACK FROM ACCOUNT 2 #{body}" - body['first_name'].should eq("Person") - -puts "BACK FROM ACCOUNT 3" - - @billing_info[:state] = "NE" - puts "BEFORE BILLING" - - put :update_billing_info, {:format => 'json', :billing_info=>@billing_info} - puts "BACK FROM billing 1" - - response.should be_success + post :create_account + response.should be_success body = JSON.parse(response.body) - puts "BILLING_ BODY: #{body}" + body['first_name'].should eq("Person") + @billing_info[:state] = "NE" + + put :update_billing_info, {:format => 'json', :billing_info=>@billing_info} + + response.should be_success + body = JSON.parse(response.body) get :billing_info - response.should be_success - puts "response.body: #{response.body}" - body = JSON.parse(response.body) - puts "body: #{body}" + response.should be_success + body = JSON.parse(response.body) end end # spec From a77cc46b4cce09402285c0fe536b12ae35bc119c Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Mon, 1 Dec 2014 11:52:13 -0600 Subject: [PATCH 16/72] Remove puts. Disable tracing for billing test. --- web/app/controllers/api_recurly_controller.rb | 2 -- web/spec/controllers/api_recurly_spec.rb | 16 ++++++++-------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/web/app/controllers/api_recurly_controller.rb b/web/app/controllers/api_recurly_controller.rb index e3f39baec..42d74c14d 100644 --- a/web/app/controllers/api_recurly_controller.rb +++ b/web/app/controllers/api_recurly_controller.rb @@ -7,7 +7,6 @@ class ApiRecurlyController < ApiController # create Recurly account def create_account @account = @client.find_or_create_account(current_user, params[:billing_info]) - puts "JSON: #{JSON.generate(@account)}" render :json=>account_json(@account) rescue RecurlyClientError => x render json: { :message => x.inspect, errors: x.errors }, :status => 404 @@ -50,7 +49,6 @@ class ApiRecurlyController < ApiController # update Billing Information def update_billing_info @account=@client.update_billing_info(current_user, params[:billing_info]) - puts "Updated billing on account: #{@account}" render :json=> account_json(@account) rescue RecurlyClientError => x render json: { message: x.inspect, errors: x.errors}, :status => 404 diff --git a/web/spec/controllers/api_recurly_spec.rb b/web/spec/controllers/api_recurly_spec.rb index 11ee343d3..aec34e3b7 100644 --- a/web/spec/controllers/api_recurly_spec.rb +++ b/web/spec/controllers/api_recurly_spec.rb @@ -93,16 +93,16 @@ describe ApiRecurlyController, :type=>:controller do it "should update billing info" do #pending "this fails in the controller spec only, possibly because it contacts an external site." - $enable_tracing = false - $trace_out = open('trace.txt', 'w') + # $enable_tracing = false + # $trace_out = open('trace.txt', 'w') - set_trace_func proc { |event, file, line, id, binding, classname| - if $enable_tracing && event == 'call' && !file.start_with?("/Users/tangledpath/.ddrvm/") - $trace_out.puts "#{file}:#{line} #{classname}##{id}" - end - } + # set_trace_func proc { |event, file, line, id, binding, classname| + # if $enable_tracing && event == 'call' && !file.start_with?("/Users/tangledpath/.ddrvm/") + # $trace_out.puts "#{file}:#{line} #{classname}##{id}" + # end + # } - $enable_tracing = true + # $enable_tracing = true post :create_account response.should be_success From 0ec65738f15a87b026e3cbd2b6f1383245eceb32 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Mon, 1 Dec 2014 11:59:15 -0600 Subject: [PATCH 17/72] Kill another puts. --- web/lib/recurly_client.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/web/lib/recurly_client.rb b/web/lib/recurly_client.rb index b4dcb02a3..fed21a7a8 100644 --- a/web/lib/recurly_client.rb +++ b/web/lib/recurly_client.rb @@ -11,7 +11,7 @@ module JamRuby account = Recurly::Account.create(options) raise RecurlyClientError.new(account.errors) if account.errors.any? rescue Recurly::Error, NoMethodError => x - puts "Errorrr: #{x.inspect}" + puts raise RecurlyClientError, x.to_s else if account @@ -56,10 +56,8 @@ module JamRuby account = get_account(current_user) if (account.present?) begin - account.billing_info=billing_info - puts "Saving..." - account.billing_info.save - puts "...saved" + account.billing_info=billing_info + account.billing_info.save rescue Recurly::Error, NoMethodError => x raise RecurlyClientError, x.to_s end From 41096f08ad4fea31c5e01951d7b7cb1e52c4c8cd Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Mon, 1 Dec 2014 18:45:41 -0600 Subject: [PATCH 18/72] VRFS-2480: Additional back-end and UI functionality required for checking out. --- .../views/admin/jam_tracks/_form.html.haml | 9 +- .../_jam_track_track_fields.html.haml | 3 +- ruby/lib/jam_ruby/models/jam_track.rb | 2 +- ruby/lib/jam_ruby/models/jam_track_right.rb | 2 +- ruby/lib/jam_ruby/models/shopping_cart.rb | 2 +- web/app/assets/javascripts/jam_rest.js | 12 ++- web/app/assets/javascripts/order.js | 92 ++++++++++++------- .../stylesheets/client/checkout.css.scss | 4 + .../stylesheets/client/shoppingCart.css.scss | 3 + web/app/controllers/api_recurly_controller.rb | 19 +++- .../api_shopping_carts_controller.rb | 5 + web/app/views/clients/_order.html.slim | 15 ++- .../views/clients/_shopping_cart.html.haml | 2 +- web/config/routes.rb | 3 +- web/lib/recurly_client.rb | 41 ++++++++- web/spec/factories.rb | 2 +- 16 files changed, 163 insertions(+), 53 deletions(-) diff --git a/admin/app/views/admin/jam_tracks/_form.html.haml b/admin/app/views/admin/jam_tracks/_form.html.haml index d9346aa8f..fadc93b32 100644 --- a/admin/app/views/admin/jam_tracks/_form.html.haml +++ b/admin/app/views/admin/jam_tracks/_form.html.haml @@ -15,12 +15,13 @@ = f.input :pro, collection: JamRuby::JamTrack::PRO, include_blank: false = f.input :genre, collection: JamRuby::Genre.all, include_blank: false = f.input :sales_region, collection: JamRuby::JamTrack::SALES_REGION, include_blank: false - = f.input :price + = f.input :price, :required=>true, :input_html=>{type:'numeric'} = f.input :reproduction_royalty, :label => 'Reproduction Royalty' = f.input :public_performance_royalty, :label => 'Public Performance Royalty' - = f.input :reproduction_royalty_amount - = f.input :licensor_royalty_amount - = f.input :pro_royalty_amount + = f.input :reproduction_royalty_amount, :required=>true, :input_html=>{type:'numeric'} + = f.input :licensor_royalty_amount, :required=>true, :input_html=>{type:'numeric'} + = f.input :pro_royalty_amount, :required=>true, :input_html=>{type:'numeric'} + = f.input :plan_code, :label=>'Recurly Plan Code', :required=>true = f.input :url, :as => :file, :label => 'Audio File' = f.semantic_fields_for :jam_track_tracks do |track| diff --git a/admin/app/views/admin/jam_tracks/_jam_track_track_fields.html.haml b/admin/app/views/admin/jam_tracks/_jam_track_track_fields.html.haml index c9835865b..a388e0f8e 100644 --- a/admin/app/views/admin/jam_tracks/_jam_track_track_fields.html.haml +++ b/admin/app/views/admin/jam_tracks/_jam_track_track_fields.html.haml @@ -3,7 +3,8 @@ %ol.nested-fields = f.input :track_type, :as => :select, collection: JamRuby::JamTrackTrack::TRACK_TYPE, include_blank: false = f.input :instrument, collection: Instrument.all, include_blank: false - = f.input :part, :input_html => { :rows=>1, :maxlength=>20 } + = f.input :part, :required=>true, :input_html => { :rows=>1, :maxlength=>20, :type=>'numeric' } + = f.input :position - if f.object.new_record? diff --git a/ruby/lib/jam_ruby/models/jam_track.rb b/ruby/lib/jam_ruby/models/jam_track.rb index b3f11dbb1..38b1a9e5d 100644 --- a/ruby/lib/jam_ruby/models/jam_track.rb +++ b/ruby/lib/jam_ruby/models/jam_track.rb @@ -15,7 +15,7 @@ module JamRuby attr_accessible :name, :description, :bpm, :time_signature, :status, :recording_type, :original_artist, :songwriter, :publisher, :licensor, :licensor_id, :pro, :genre, :genre_id, :sales_region, :price, :reproduction_royalty, :public_performance_royalty, :reproduction_royalty_amount, - :licensor_royalty_amount, :pro_royalty_amount, :jam_track_tracks_attributes, as: :admin + :licensor_royalty_amount, :pro_royalty_amount, :jam_track_tracks_attributes, :plan_code, as: :admin validates :name, presence: true, uniqueness: true, length: {maximum: 200} validates :description, length: {maximum: 1000} diff --git a/ruby/lib/jam_ruby/models/jam_track_right.rb b/ruby/lib/jam_ruby/models/jam_track_right.rb index 2a741a3a3..f06c823e6 100644 --- a/ruby/lib/jam_ruby/models/jam_track_right.rb +++ b/ruby/lib/jam_ruby/models/jam_track_right.rb @@ -2,7 +2,7 @@ module JamRuby # describes what users have rights to which tracks class JamTrackRight < ActiveRecord::Base - + attr_accessible :user, :jam_track, :user_id, :jam_track_id belongs_to :user, class_name: "JamRuby::User" # the owner, or purchaser of the jam_track belongs_to :jam_track, class_name: "JamRuby::JamTrack" diff --git a/ruby/lib/jam_ruby/models/shopping_cart.rb b/ruby/lib/jam_ruby/models/shopping_cart.rb index ccad1b9d4..da1e567f6 100644 --- a/ruby/lib/jam_ruby/models/shopping_cart.rb +++ b/ruby/lib/jam_ruby/models/shopping_cart.rb @@ -13,7 +13,7 @@ module JamRuby def product_info product = self.cart_product - {name: product.name, price: product.price} unless product.nil? + {name: product.name, price: product.price, product_id: cart_id} unless product.nil? end def cart_product diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js index 1464c9368..52075663e 100644 --- a/web/app/assets/javascripts/jam_rest.js +++ b/web/app/assets/javascripts/jam_rest.js @@ -1312,6 +1312,15 @@ }) } + function clearShoppingCart(options) { + return $.ajax({ + type: "DELETE", + url: '/api/shopping_carts/clear_all', + dataType: "json", + contentType: 'application/json' + }) + } + function getRecurlyAccount() { return $.ajax({ type: "GET", @@ -1341,7 +1350,7 @@ function updateBillingInfo(options) { return $.ajax({ - type: "POST", + type: "PUT", url: '/api/recurly/update_billing_info?' + $.param({billing_info: options}), dataType: "json", //data: JSON.stringify({"billing_info": $.param(options)}), @@ -1481,6 +1490,7 @@ this.addJamtrackToShoppingCart = addJamtrackToShoppingCart; this.getShoppingCarts = getShoppingCarts; this.removeShoppingCart = removeShoppingCart; + this.clearShoppingCart = clearShoppingCart; this.getRecurlyAccount = getRecurlyAccount; this.createRecurlyAccount = createRecurlyAccount; this.getBillingInfo = getBillingInfo; diff --git a/web/app/assets/javascripts/order.js b/web/app/assets/javascripts/order.js index 77ed30e3a..fb741ad31 100644 --- a/web/app/assets/javascripts/order.js +++ b/web/app/assets/javascripts/order.js @@ -15,6 +15,7 @@ var $shippingAsBilling = null; var $paymentInfoPanel = null; var $orderPanel = null; + var $thanksPanel = null; var $orderContent = null; var userDetail = null; var step = null; @@ -23,8 +24,7 @@ var shipping_as_billing = null; function beforeShow() { - beforeShowPaymentInfo(); -// moveToOrder(); + beforeShowPaymentInfo(); } function beforeShowPaymentInfo() { @@ -85,6 +85,7 @@ function next(e) { e.preventDefault(); + $("#order_error").addClass("hidden") // validation var billing_first_name = $billingInfo.find("#billing-first-name").val(); @@ -317,8 +318,7 @@ billing_info.number = card_number; billing_info.month = card_month; billing_info.year = card_year; - billing_info.verification_value = card_verify; - + billing_info.verification_value = card_verify; if (shipping_as_billing) { shipping_info = $.extend({},billing_info); @@ -340,18 +340,13 @@ $paymentInfoPanel.find("#payment-info-next").addClass("disabled"); $paymentInfoPanel.find("#payment-info-next").off("click"); - if (userDetail.has_recurly_account) { - rest.updateBillingInfo(billing_info) - .done(function() { - }) - .fail(errorHandling); - } - else { - rest.createRecurlyAccount({billing_info: billing_info}) - .done(function() { - }) - .fail(errorHandling); - } + rest.createRecurlyAccount({billing_info: billing_info}) + .done(function() { + moveToOrder(); + $paymentInfoPanel.find("#payment-info-next").removeClass("disabled"); + $paymentInfoPanel.find("#payment-info-next").on("click", next); + }) + .fail(errorHandling); } function errorHandling(xhr, ajaxOptions, thrownError) { @@ -368,9 +363,18 @@ } }); - $paymentInfoPanel.find("#payment-info-next").removeClass("disabled"); - $paymentInfoPanel.find("#payment-info-next").on("click", next); -// moveToOrder(); + $paymentInfoPanel.find("#payment-info-next").addClass("disabled"); + $paymentInfoPanel.find("#payment-info-next").on('click', next); + } + + function orderErrorHandling(xhr, ajaxOptions, thrownError) { + var message = "Error submitting payment: " + $.each(xhr.responseJSON.errors, function(key, error) { + message += key + ": " + error + }) + $("#order_error").text(message) + $("#order_error").removeClass("hidden") + $orderContent.find(".place-order").on('click', placeOrder) } function beforeShowOrder() { @@ -392,31 +396,32 @@ } function renderOrderPage(carts) { - var data = {}; + var data = {} - var sub_total = 0; + var sub_total = 0.0 + var taxes = 0.0 $.each(carts, function(index, cart) { - sub_total += parseFloat(cart.product_info.price) * parseFloat(cart.quantity); + sub_total += parseFloat(cart.product_info.price) * parseFloat(cart.quantity) }); - data.sub_total = sub_total.toFixed(2); - data.taxes = 12.01; - - data.carts = carts; - data.billing_info = billing_info; - data.shipping_info = shipping_info; - data.shipping_as_billing = shipping_as_billing; + data.grand_total = (sub_total + taxes).toFixed(2) + data.sub_total = sub_total.toFixed(2) + data.taxes = taxes.toFixed(2) + data.carts = carts + data.billing_info = billing_info + data.shipping_info = shipping_info + data.shipping_as_billing = shipping_as_billing var orderContentHtml = $( context._.template( $('#template-order-content').html(), data, {variable: 'data'} ) - ); + ) - $orderContent.append(orderContentHtml); + $orderContent.append(orderContentHtml) - $orderPanel.find(".change-payment-info").on('click', moveToPaymentInfo); - $orderContent.find(".place-order").on('click', placeOrder); + $orderPanel.find(".change-payment-info").on('click', moveToPaymentInfo) + $orderContent.find(".place-order").on('click', placeOrder) } function moveToOrder() { @@ -425,6 +430,15 @@ beforeShowOrder(); } + function moveToThanks() { + $("#order_error").addClass("hidden") + $paymentInfoPanel.addClass("hidden") + $orderPanel.addClass("hidden") + $thanksPanel.removeClass("hidden") + rest.clearShoppingCart() + beforeShowOrder() + } + function moveToPaymentInfo(e) { e.preventDefault(); $paymentInfoPanel.removeClass("hidden"); @@ -447,6 +461,17 @@ function placeOrder(e) { e.preventDefault(); + $orderContent.find(".place-order").off('click') + rest.getShoppingCarts() + .done(function(carts) { + var jam_track_ids = _.map(carts, function(cart){ + return cart.product_info.product_id + }) + rest.placeOrder({jam_tracks: jam_track_ids}) + .done(moveToThanks) + .fail(orderErrorHandling); + } + ).fail(app.ajaxError); } function events() { @@ -488,6 +513,7 @@ $screen = $("#orderScreen"); $paymentInfoPanel = $screen.find(".checkout-payment-info"); $orderPanel = $screen.find(".order-panel"); + $thanksPanel = $screen.find(".thanks-panel"); $navigation = $screen.find(".checkout-navigation-bar"); $billingInfo = $paymentInfoPanel.find(".billing-address"); $shippingInfo = $paymentInfoPanel.find(".shipping-address"); diff --git a/web/app/assets/stylesheets/client/checkout.css.scss b/web/app/assets/stylesheets/client/checkout.css.scss index 7cb65eac0..6f9bc6a40 100644 --- a/web/app/assets/stylesheets/client/checkout.css.scss +++ b/web/app/assets/stylesheets/client/checkout.css.scss @@ -166,6 +166,10 @@ } } +.thanks-panel { + padding: 30px; +} + .order-panel { padding: 30px; diff --git a/web/app/assets/stylesheets/client/shoppingCart.css.scss b/web/app/assets/stylesheets/client/shoppingCart.css.scss index c031e5982..f8a8c6fdb 100644 --- a/web/app/assets/stylesheets/client/shoppingCart.css.scss +++ b/web/app/assets/stylesheets/client/shoppingCart.css.scss @@ -79,6 +79,9 @@ .no-cart-items { margin-top: 30px; text-align: center; + font-style: italic; + font-weight: bold; + font-size: 15px; } } } \ No newline at end of file diff --git a/web/app/controllers/api_recurly_controller.rb b/web/app/controllers/api_recurly_controller.rb index 42d74c14d..575366d55 100644 --- a/web/app/controllers/api_recurly_controller.rb +++ b/web/app/controllers/api_recurly_controller.rb @@ -55,8 +55,23 @@ class ApiRecurlyController < ApiController end def place_order - @client.update_billing_info(current_user, params[:billing_info]) - render :json=>{}, :status=>200 + error=nil + puts "PLACING ORDER #{params.inspect}" + params[:jam_tracks].each do |jam_track_id| + jam_track = JamTrack.where("id=?", jam_track_id).first + if jam_track + @client.place_order(current_user, jam_track) + else + error="JamTrack not found for '#{jam_track_id}'" + break + end + end + + if error + render json: { errors: {message:error}}, :status => 404 + else + render :json=>{}, :status=>200 + end rescue RecurlyClientError => x render json: { message: x.inspect, errors: x.errors}, :status => 404 end diff --git a/web/app/controllers/api_shopping_carts_controller.rb b/web/app/controllers/api_shopping_carts_controller.rb index d1109e821..5cff85b97 100644 --- a/web/app/controllers/api_shopping_carts_controller.rb +++ b/web/app/controllers/api_shopping_carts_controller.rb @@ -50,4 +50,9 @@ class ApiShoppingCartsController < ApiController respond_with responder: ApiResponder, :status => 204 end + def clear_all + ShoppingCart.where("user_id=?", current_user).destroy_all + render :json=>{}, :status=>200 + end + end diff --git a/web/app/views/clients/_order.html.slim b/web/app/views/clients/_order.html.slim index e057b9756..241af7217 100644 --- a/web/app/views/clients/_order.html.slim +++ b/web/app/views/clients/_order.html.slim @@ -5,6 +5,7 @@ div layout="screen" layout-id="order" id="orderScreen" class="screen secondary" h1 check out = render "screen_navigation" .content-body + #order_error.error.hidden .content-body-scroller .content-wrapper .checkout-navigation-bar @@ -172,6 +173,15 @@ div layout="screen" layout-id="order" id="orderScreen" class="screen secondary" .clearall .order-content + .thanks-panel.hidden + h2 Thank you for your order! + br + .thanks-detail We'll send you an email confirming your order shortly. + br + .thanks-detail If you purchased any JamTracks, the next time you run the JamKazam application, your JamTracks will automatically be downloaded to the app, and you will receive a notification when the download is complete. + + + script type='text/template' id='template-order-content' .order-left-page @@ -206,7 +216,8 @@ script type='text/template' id='template-order-content' .clearall /= image_tag '' - | ending in 1234 + ="Ending in: {{data.billing_info.number.slice(-4)}}" + .clearall .order-items-page .cart-items @@ -254,7 +265,7 @@ script type='text/template' id='template-order-content' hr b.order-total .left Order Total: - .right= "${{data.sub_total + data.taxes}}}" + .right= "${{data.grand_total}}" .clearall br div style="text-align: left;" diff --git a/web/app/views/clients/_shopping_cart.html.haml b/web/app/views/clients/_shopping_cart.html.haml index 21c443b80..df3be13f9 100644 --- a/web/app/views/clients/_shopping_cart.html.haml +++ b/web/app/views/clients/_shopping_cart.html.haml @@ -28,7 +28,7 @@ %span{style: "text-decoration: underline;"} Quantity .clearall = "{% if (data.carts.length == 0) { %}" - .no-cart-items You have no carts now. + .no-cart-items Nothing in cart = "{% } %}" = "{% _.each(data.carts, function(cart) { %}" .cart-item{"cart-id" => "{{cart.id}}"} diff --git a/web/config/routes.rb b/web/config/routes.rb index 12bd5a3a6..38cc43398 100644 --- a/web/config/routes.rb +++ b/web/config/routes.rb @@ -196,7 +196,8 @@ SampleApp::Application.routes.draw do match '/shopping_carts/add_jamtrack' => 'api_shopping_carts#add_jamtrack', :via => :post match '/shopping_carts' => 'api_shopping_carts#index', :via => :get match '/shopping_carts' => 'api_shopping_carts#remove_cart', :via => :delete - + match '/shopping_carts/clear_all' => 'api_shopping_carts#clear_all', :via => :delete + # RSVP requests match '/rsvp_requests' => 'api_rsvp_requests#index', :via => :get match '/rsvp_requests' => 'api_rsvp_requests#create', :via => :post diff --git a/web/lib/recurly_client.rb b/web/lib/recurly_client.rb index fed21a7a8..e6d0cd6f7 100644 --- a/web/lib/recurly_client.rb +++ b/web/lib/recurly_client.rb @@ -10,8 +10,7 @@ module JamRuby begin account = Recurly::Account.create(options) raise RecurlyClientError.new(account.errors) if account.errors.any? - rescue Recurly::Error, NoMethodError => x - puts + rescue Recurly::Error, NoMethodError => x raise RecurlyClientError, x.to_s else if account @@ -69,7 +68,33 @@ module JamRuby account end - def place_order(current_user, billing_info=nil) + def place_order(current_user, jam_track) + account = get_account(current_user) + if (account.present?) + begin + subscription = Recurly::Subscription.create(:account=>account, :plan_code=>jam_track.plan_code) + raise RecurlyClientError.new(subscription.errors) if subscription.errors.any? + + # Reload and make sure it went through: + account = get_account(current_user) + + + paid_subscription = account.subscriptions.last + raise RecurlyClientError, "Subscription not found" if paid_subscription.nil? + raise RecurlyClientError, "Plan code '#{paid_subscription.plan_code}' doesn't match jam track: '#{jam_track.plan_code}'" if paid_subscription.plan_code != jam_track.plan_code + + jam_track_right=JamRuby::JamTrackRight.find_or_initialize_by_user_id_and_jam_track_id(current_user, jam_track) + raise RecurlyClientError.new("Error creating jam_track_right for jam_track: #{jam_track.id}") if jam_track_right.nil? + raise RecurlyClientError.new(jam_track_right.errors) if jam_track_right.errors.any? + rescue Recurly::Error, NoMethodError => x + raise RecurlyClientError, x.to_s + end + + raise RecurlyClientError.new(account.errors) if account.errors.any? + else + raise RecurlyClientError, "Could not find account to place order." + end + account end def find_or_create_account(current_user, billing_info=nil) @@ -77,6 +102,8 @@ module JamRuby if(account.nil?) account = create_account(current_user, billing_info) + else + update_billing_info(current_user, billing_info) end account end @@ -107,10 +134,16 @@ module JamRuby if data.respond_to?('has_key?') self.errors = data else - puts "2222" self.errors = {:message=>data.to_s} end end # initialize + + def to_s + s=super + s << ", errors: #{errors.inspect}" if self.errors.any? + s + end + end # RecurlyClientError end # module \ No newline at end of file diff --git a/web/spec/factories.rb b/web/spec/factories.rb index 94277cc68..4e2ad4226 100644 --- a/web/spec/factories.rb +++ b/web/spec/factories.rb @@ -708,7 +708,7 @@ FactoryGirl.define do reproduction_royalty_amount 0.999 licensor_royalty_amount 0.999 pro_royalty_amount 0.999 - + plan_code 'jamtrack-acdc-backinblack' ignore do make_track true end From 2e5b3add3c7572a3bd09d3a57f699c323d54708d Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Mon, 1 Dec 2014 18:46:05 -0600 Subject: [PATCH 19/72] VRFS-2480: Recurly specs to verify placing an order. --- web/spec/managers/recurly_client_spec.rb | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/web/spec/managers/recurly_client_spec.rb b/web/spec/managers/recurly_client_spec.rb index e15b5fb6e..8b1825cba 100644 --- a/web/spec/managers/recurly_client_spec.rb +++ b/web/spec/managers/recurly_client_spec.rb @@ -5,11 +5,12 @@ describe RecurlyClient do #let(:client) { RecurlyClient.new } before :all do - @client = RecurlyClient.new - @user = FactoryGirl.create(:user) + @client = RecurlyClient.new + @jamtrack = FactoryGirl.create(:jam_track) end before(:each) do + @user = FactoryGirl.create(:user) @billing_info = {} @billing_info[:first_name] = @user.first_name @billing_info[:last_name] = @user.last_name @@ -85,5 +86,22 @@ describe RecurlyClient do found.should_not be_nil found.state.should eq('closed') end + + it "can place order" do + @client.find_or_create_account(@user, @billing_info) + expect{@client.place_order(@user, @jamtrack)}.not_to raise_error() + subs = @client.get_account(@user).subscriptions + subs.should_not be_nil + subs.should have(1).items + @user.jam_track_rights.should_not be_nil + @user.jam_track_rights.should have(1).items + @user.jam_track_rights.last.jam_track.id.should eq(@jamtrack.id) + end + + it "detects error on double order" do + @client.find_or_create_account(@user, @billing_info) + expect{@client.place_order(@user, @jamtrack)}.not_to raise_error() + expect{@client.place_order(@user, @jamtrack)}.to raise_error(RecurlyClientError) + end end From e85be7f0d5585fd688413cf226f9b0c649de4490 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Tue, 2 Dec 2014 20:01:20 +0000 Subject: [PATCH 20/72] README.md edited online with Bitbucket --- web/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/README.md b/web/README.md index 03b796614..b20dca35d 100644 --- a/web/README.md +++ b/web/README.md @@ -1,4 +1,4 @@ -* TODO +-- TODO Jasmine Javascript Unit Tests ============================= From 0808ce04f9dc5de707b19560db8359a5cd83893c Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Wed, 3 Dec 2014 13:48:14 -0600 Subject: [PATCH 21/72] Address test failure by actually creating the jam_track_right. --- web/lib/recurly_client.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/lib/recurly_client.rb b/web/lib/recurly_client.rb index e6d0cd6f7..4992814cb 100644 --- a/web/lib/recurly_client.rb +++ b/web/lib/recurly_client.rb @@ -83,7 +83,7 @@ module JamRuby raise RecurlyClientError, "Subscription not found" if paid_subscription.nil? raise RecurlyClientError, "Plan code '#{paid_subscription.plan_code}' doesn't match jam track: '#{jam_track.plan_code}'" if paid_subscription.plan_code != jam_track.plan_code - jam_track_right=JamRuby::JamTrackRight.find_or_initialize_by_user_id_and_jam_track_id(current_user, jam_track) + jam_track_right=JamRuby::JamTrackRight.find_or_create_by_user_id_and_jam_track_id(current_user.id, jam_track.id) raise RecurlyClientError.new("Error creating jam_track_right for jam_track: #{jam_track.id}") if jam_track_right.nil? raise RecurlyClientError.new(jam_track_right.errors) if jam_track_right.errors.any? rescue Recurly::Error, NoMethodError => x From b6850ccce4361807e9dadc89453a4d0d4c8df372 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Wed, 3 Dec 2014 16:12:47 -0600 Subject: [PATCH 22/72] VRFS-2481 : Cascading deletes for join table. Add s3 URL. --- db/manifest | 3 ++- db/up/jam_track_rights_updates.sql | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 db/up/jam_track_rights_updates.sql diff --git a/db/manifest b/db/manifest index 13d9784e6..383f6f46b 100755 --- a/db/manifest +++ b/db/manifest @@ -228,4 +228,5 @@ user_syncs_fix_dup_tracks_2408.sql deletable_recordings.sql jam_tracks.sql shopping_carts.sql -recurly.sql \ No newline at end of file +recurly.sql +jam_track_rights_updates.sql \ No newline at end of file diff --git a/db/up/jam_track_rights_updates.sql b/db/up/jam_track_rights_updates.sql new file mode 100644 index 000000000..ade318969 --- /dev/null +++ b/db/up/jam_track_rights_updates.sql @@ -0,0 +1,7 @@ +ALTER TABLE jam_track_rights + DROP CONSTRAINT jam_track_rights_user_id_fkey, + DROP CONSTRAINT jam_track_rights_jam_track_id_fkey, + ADD CONSTRAINT jam_track_rights_user_id_fkey FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + ADD CONSTRAINT jam_track_rights_jam_track_id_fkey FOREIGN KEY(jam_track_id) REFERENCES jam_tracks(id) ON DELETE CASCADE, + ADD COLUMN s3_url VARCHAR(2048); + From bac1f69c88ee7bf500e91dc0f6c6afc86ec85fe4 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Thu, 4 Dec 2014 00:32:46 +0000 Subject: [PATCH 23/72] README.md edited online with Bitbucket --- web/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/README.md b/web/README.md index b20dca35d..34ebc350b 100644 --- a/web/README.md +++ b/web/README.md @@ -1,4 +1,4 @@ --- TODO +== TODO Jasmine Javascript Unit Tests ============================= From 207b7b9e2050c673acf60bb5154fcdd6f090635b Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Fri, 5 Dec 2014 15:03:40 -0600 Subject: [PATCH 24/72] VRFS-1890 : Mechanism to move track up/down, hook up in admin UI. --- admin/app/admin/jam_tracks.rb | 45 ++++++++++--- admin/app/assets/javascripts/jam_track.js | 73 +++++++++++---------- ruby/lib/jam_ruby/models/jam_track_track.rb | 51 +++++++++++++- 3 files changed, 125 insertions(+), 44 deletions(-) diff --git a/admin/app/admin/jam_tracks.rb b/admin/app/admin/jam_tracks.rb index 0f6d99e08..ed1e746ab 100644 --- a/admin/app/admin/jam_tracks.rb +++ b/admin/app/admin/jam_tracks.rb @@ -11,6 +11,18 @@ ActiveAdmin.register JamRuby::JamTrack, :as => 'JamTracks' do form :partial => 'form' index do + + # default_actions # use this for all view/edit/delete links + column "Actions" do |jam_track| + links = ''.html_safe + clz = "member_link view_link show_tracks" + clz += ' expand' if params[:focus_track]==jam_track.id + links << link_to("Show Tracks", '#', :class => clz) + links << link_to("Update", edit_resource_path(jam_track), :class => "member_link edit_link") + links + end + + column :id column :name column :description @@ -40,18 +52,35 @@ ActiveAdmin.register JamRuby::JamTrack, :as => 'JamTracks' do column :track_type column :instrument column :part - column :track do |track| + + column "" do |track| + if track.position > 1 + link_to 'Move Up', "jam_tracks/#{track.id}/move_up" + end + end + column "" do |track| + if track.position < jam_track.jam_track_tracks.count + link_to 'Move Down', "jam_tracks/#{track.id}/move_down" + end + end + column "" do |track| link_to 'Play', '#' end end end - # default_actions # use this for all view/edit/delete links - column "Actions" do |jam_track| - links = ''.html_safe - links << link_to("Show Tracks", '#', :class => "member_link view_link show_tracks") - links << link_to("Update", edit_resource_path(jam_track), :class => "member_link edit_link") - links - end + + end + + member_action :move_up, :method => :get do + track = JamTrackTrack.where("id=?",params[:id]).first + track.move_up + redirect_to("/admin/jam_tracks?focus_track=#{track.jam_track_id}", {:notice => "Moved Up."}) + end + + member_action :move_down, :method => :get do + track = JamTrackTrack.where("id=?",params[:id]).first + track.move_down + redirect_to("/admin/jam_tracks?focus_track=#{track.jam_track_id}", {:notice => "Moved Down."}) end end \ No newline at end of file diff --git a/admin/app/assets/javascripts/jam_track.js b/admin/app/assets/javascripts/jam_track.js index a8781672a..e1b6852bf 100644 --- a/admin/app/assets/javascripts/jam_track.js +++ b/admin/app/assets/javascripts/jam_track.js @@ -1,43 +1,48 @@ +function showTracks(rowJamTrack) { + var $jamTrackTracks = rowJamTrack.find("td.jam_track_tracks"); + + var name=rowJamTrack.find("td.name").text() + var count = $jamTrackTracks.find("table tbody tr").length; + + if (rowJamTrack.next().attr('id') == "jam_track_tracks_detail") { + $(this).html("Show Tracks"); + rowJamTrack.next().remove(); + } else { + $(this).html('Hide Tracks'); + if (count == 0) { + rowJamTrack.after( + $("").html( + $("") + ).append( + $("").html( + "No Tracks" + ) + ) + ); + } + else { + rowJamTrack.after( + $("").html( + $("Tracks in '" + name + "':") + ).append( + $("").html( + $jamTrackTracks.html() + ) + ) + ); + } + } +} + $(document).ready(function() { $("th.jam_track_tracks").css('display', 'none'); $("td.jam_track_tracks").css('display', 'none'); - + showTracks($("a.expand").parents("tr")) + $(".show_tracks").click(function(e) { e.preventDefault(); var $rowJamTrack = $(this).parents('tr'); - var $jamTrackTracks = $($rowJamTrack).find("td.jam_track_tracks"); - - var count = $jamTrackTracks.find("table tbody tr").length; - - if ($rowJamTrack.next().attr('id') == "jam_track_tracks_detail") { - $(this).html("Show Tracks"); - $rowJamTrack.next().remove(); - } - else { - $(this).html('Hide Tracks'); - if (count == 0) { - $rowJamTrack.after( - $("").html( - $("") - ).append( - $("").html( - "No Tracks" - ) - ) - ); - } - else { - $rowJamTrack.after( - $("").html( - $("") - ).append( - $("").html( - $jamTrackTracks.html() - ) - ) - ); - } - } + showTracks($rowJamTrack) }) }); \ No newline at end of file diff --git a/ruby/lib/jam_ruby/models/jam_track_track.rb b/ruby/lib/jam_ruby/models/jam_track_track.rb index f9e8ebcb9..fd527b9eb 100644 --- a/ruby/lib/jam_ruby/models/jam_track_track.rb +++ b/ruby/lib/jam_ruby/models/jam_track_track.rb @@ -43,5 +43,52 @@ module JamRuby # I think we have to make a special case for 'previews', but maybe that's just up to the controller to not check can_download? jam_track.owners.include?(user) end - end -end + + def move_up + #normalize_position + if self.position > 1 + # Switch with previous + previous_track = self.jam_track.jam_track_tracks.where("position=?", self.position-1).first + if previous_track + JamTrack.transaction do + previous_track.position,self.position = self.position,previous_track.position + previous_track.save(validate:false) + self.save(validate:false) + end + end + end + end + + def move_down + count=normalize_position + if self.position < count + # Switch with next: + next_track = self.jam_track.jam_track_tracks.where("position=?", self.position+1).first + if next_track + next_track.position,self.position = self.position,next_track.position + next_track.save(validate:false) + self.save(validate:false) + end + end + end + + private + def normalize_position + parent = self.jam_track + position = 0 + if parent + JamTrack.transaction do + parent.jam_track_tracks.each do |jtt| + position += 1 + if jtt.position != position + jtt.position = position + jtt.save(validate:false) + end + end + end + end + position + end # normalize_position + + end # class +end # module From 8634d4f7263bc328c9bcddc900950d4cf3755980 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Sat, 6 Dec 2014 19:55:59 -0600 Subject: [PATCH 25/72] VRFS-1959 : change the primary key (id) to be a big int and to share the sequence used by mixes.id and recorded_tracks.id Also modify references as necessary, both from other tables and various code usages. --- db/manifest | 2 +- db/up/jam_track_rights_updates.sql | 7 ------- db/up/jam_track_updates.sql | 29 ++++++++++++++++++++++++++ web/app/assets/javascripts/jamtrack.js | 1 - web/app/views/api_jamtracks/show.rabl | 2 +- 5 files changed, 31 insertions(+), 10 deletions(-) delete mode 100644 db/up/jam_track_rights_updates.sql create mode 100644 db/up/jam_track_updates.sql diff --git a/db/manifest b/db/manifest index 383f6f46b..7ab029e4d 100755 --- a/db/manifest +++ b/db/manifest @@ -229,4 +229,4 @@ deletable_recordings.sql jam_tracks.sql shopping_carts.sql recurly.sql -jam_track_rights_updates.sql \ No newline at end of file +jam_track_updates.sql \ No newline at end of file diff --git a/db/up/jam_track_rights_updates.sql b/db/up/jam_track_rights_updates.sql deleted file mode 100644 index ade318969..000000000 --- a/db/up/jam_track_rights_updates.sql +++ /dev/null @@ -1,7 +0,0 @@ -ALTER TABLE jam_track_rights - DROP CONSTRAINT jam_track_rights_user_id_fkey, - DROP CONSTRAINT jam_track_rights_jam_track_id_fkey, - ADD CONSTRAINT jam_track_rights_user_id_fkey FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, - ADD CONSTRAINT jam_track_rights_jam_track_id_fkey FOREIGN KEY(jam_track_id) REFERENCES jam_tracks(id) ON DELETE CASCADE, - ADD COLUMN s3_url VARCHAR(2048); - diff --git a/db/up/jam_track_updates.sql b/db/up/jam_track_updates.sql new file mode 100644 index 000000000..1c42254ed --- /dev/null +++ b/db/up/jam_track_updates.sql @@ -0,0 +1,29 @@ +-- Drop Jam Track Tracks constraints: +ALTER TABLE jam_track_tracks + DROP CONSTRAINT jam_track_tracks_jam_track_id_fkey; + +-- Drop Jam Track Tracks constraints: +ALTER TABLE jam_track_rights + DROP CONSTRAINT jam_track_rights_user_id_fkey, + DROP CONSTRAINT jam_track_rights_jam_track_id_fkey; + +-- Change Jam Tracks ID type to BIGINT so it can work like the other downloadable items: +ALTER TABLE jam_tracks + ALTER COLUMN id DROP DEFAULT, + ALTER COLUMN id TYPE BIGINT USING nextval('tracks_next_tracker_seq'), + ALTER COLUMN id SET DEFAULT nextval('tracks_next_tracker_seq'); + +-- Change referencing ID type and re-add constraints: +ALTER TABLE jam_track_tracks + ALTER COLUMN jam_track_id TYPE BIGINT USING 0, + ALTER COLUMN jam_track_id SET NOT NULL, + ADD CONSTRAINT jam_track_tracks_jam_track_id_fkey FOREIGN KEY(jam_track_id) REFERENCES jam_tracks(id) ON DELETE CASCADE; + +-- Change referencing ID type and re-add constraints. Also +-- add S3 URL for user-specific downloads: +ALTER TABLE jam_track_rights + ADD COLUMN s3_url VARCHAR(2048), + ALTER COLUMN jam_track_id TYPE BIGINT USING 0, + ALTER COLUMN jam_track_id SET NOT NULL, + ADD CONSTRAINT jam_track_rights_user_id_fkey FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + ADD CONSTRAINT jam_track_rights_jam_track_id_fkey FOREIGN KEY(jam_track_id) REFERENCES jam_tracks(id) ON DELETE CASCADE; diff --git a/web/app/assets/javascripts/jamtrack.js b/web/app/assets/javascripts/jamtrack.js index 6ae767514..a0771762a 100644 --- a/web/app/assets/javascripts/jamtrack.js +++ b/web/app/assets/javascripts/jamtrack.js @@ -156,7 +156,6 @@ e.preventDefault(); var params = {id: $(e.target).attr("data-jamtrack-id")}; - rest.addJamtrackToShoppingCart(params) .done(function(response) { context.location = "/client#/shoppingCart"; diff --git a/web/app/views/api_jamtracks/show.rabl b/web/app/views/api_jamtracks/show.rabl index dde7bb3dd..f47fec76e 100644 --- a/web/app/views/api_jamtracks/show.rabl +++ b/web/app/views/api_jamtracks/show.rabl @@ -7,7 +7,7 @@ node :genres do |item| end node :added_cart do |item| - current_user.shopping_carts.map(&:cart_id).include? item.id + current_user.shopping_carts.where("cart_id='?'",item.id).count != 0 end child(:jam_track_tracks => :tracks) { From 70d58a53ec9296434d66f73e86196a8d3c11267d Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Sun, 7 Dec 2014 22:35:16 +0000 Subject: [PATCH 26/72] Bump --- web/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/README.md b/web/README.md index 34ebc350b..c455e8c52 100644 --- a/web/README.md +++ b/web/README.md @@ -1,4 +1,4 @@ -== TODO +== TODO: Jasmine Javascript Unit Tests ============================= From 5c849f2faae63fa72753dff5773ee12cfa6ec9f0 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Sun, 14 Dec 2014 18:18:04 -0600 Subject: [PATCH 27/72] VRFS-1952 : Schema changes for proper type and incremental code changes. WIP. --- db/up/jam_track_updates.sql | 13 ++++++++++--- ruby/lib/jam_ruby/models/jam_track.rb | 13 +++++++++++++ ruby/lib/jam_ruby/models/jam_track_right.rb | 8 ++++++++ ...s_controller.rb => api_jam_tracks_controller.rb} | 0 4 files changed, 31 insertions(+), 3 deletions(-) rename web/app/controllers/{api_jamtracks_controller.rb => api_jam_tracks_controller.rb} (100%) diff --git a/db/up/jam_track_updates.sql b/db/up/jam_track_updates.sql index 1c42254ed..8eaaf1bd6 100644 --- a/db/up/jam_track_updates.sql +++ b/db/up/jam_track_updates.sql @@ -8,10 +8,11 @@ ALTER TABLE jam_track_rights DROP CONSTRAINT jam_track_rights_jam_track_id_fkey; -- Change Jam Tracks ID type to BIGINT so it can work like the other downloadable items: +CREATE SEQUENCE jam_tracks_next_seq; ALTER TABLE jam_tracks ALTER COLUMN id DROP DEFAULT, - ALTER COLUMN id TYPE BIGINT USING nextval('tracks_next_tracker_seq'), - ALTER COLUMN id SET DEFAULT nextval('tracks_next_tracker_seq'); + ALTER COLUMN id TYPE BIGINT USING nextval('jam_tracks_next_seq'), + ALTER COLUMN id SET DEFAULT nextval('jam_tracks_next_seq'); -- Change referencing ID type and re-add constraints: ALTER TABLE jam_track_tracks @@ -22,7 +23,13 @@ ALTER TABLE jam_track_tracks -- Change referencing ID type and re-add constraints. Also -- add S3 URL for user-specific downloads: ALTER TABLE jam_track_rights - ADD COLUMN s3_url VARCHAR(2048), + ALTER COLUMN id DROP DEFAULT, + ALTER COLUMN id TYPE BIGINT USING nextval('tracks_next_tracker_seq'), + ALTER COLUMN id SET DEFAULT nextval('tracks_next_tracker_seq'), + ADD COLUMN url VARCHAR(2048), + ADD COLUMN md5 VARCHAR, + ADD COLUMN length INTEGER NOT NULL DEFAULT 0, + ADD COLUMN download_count INTEGER NOT NULL DEFAULT 0, ALTER COLUMN jam_track_id TYPE BIGINT USING 0, ALTER COLUMN jam_track_id SET NOT NULL, ADD CONSTRAINT jam_track_rights_user_id_fkey FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, diff --git a/ruby/lib/jam_ruby/models/jam_track.rb b/ruby/lib/jam_ruby/models/jam_track.rb index 38b1a9e5d..f93087046 100644 --- a/ruby/lib/jam_ruby/models/jam_track.rb +++ b/ruby/lib/jam_ruby/models/jam_track.rb @@ -96,6 +96,19 @@ module JamRuby end end + def self.list_downloads(user, limit = 100, since = 0) + since = 0 unless since || since == '' # guard against nil + downloads = [] + + user.jam_track_rights.limit(limit).collect do |jt_right| + downloads << jt_right + end + + #JamTrack.joins(:recording).joins(:recording => :claimed_recordings) + end + + + private def sanitize_active_admin diff --git a/ruby/lib/jam_ruby/models/jam_track_right.rb b/ruby/lib/jam_ruby/models/jam_track_right.rb index f06c823e6..57c0a444f 100644 --- a/ruby/lib/jam_ruby/models/jam_track_right.rb +++ b/ruby/lib/jam_ruby/models/jam_track_right.rb @@ -8,7 +8,15 @@ module JamRuby validates :user, presence:true validates :jam_track, presence:true + validate :verify_download_count validates_uniqueness_of :user_id, scope: :jam_track_id + MAX_JAM_TRACK_DOWNLOADS = 1000 + + def verify_download_count + if (self.download_count < 0 || self.download_count > MAX_JAM_TRACK_DOWNLOADS) && !@current_user.admin + errors.add(:download_count, "must be less than or equal to #{MAX_JAM_TRACK_DOWNLOADS}") + end + end end end diff --git a/web/app/controllers/api_jamtracks_controller.rb b/web/app/controllers/api_jam_tracks_controller.rb similarity index 100% rename from web/app/controllers/api_jamtracks_controller.rb rename to web/app/controllers/api_jam_tracks_controller.rb From f11457f5edd06c7a51905b10ebd8570da65348a4 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Sun, 14 Dec 2014 20:03:49 -0600 Subject: [PATCH 28/72] VRFS-2481 : Jam Tracks python layer and source. --- ruby/bin/jamtrack.rb | 1 + ruby/lib/jam_ruby.rb | 2 + .../app/uploaders/jam_track_right_uploader.rb | 28 ++ ruby/lib/jam_ruby/jam_tracks_manager.rb | 22 + ruby/lib/jam_ruby/models/jam_track_right.rb | 12 +- ruby/lib/py/jam_tracks/jamtrack.info | 1 + ruby/lib/py/jam_tracks/jamtracklist.txt | 8 + ruby/lib/py/jam_tracks/jkaes.py | 212 +++++++++ ruby/lib/py/jam_tracks/jkaes.pyc | Bin 0 -> 9156 bytes ruby/lib/py/jam_tracks/jkasset.py | 431 ++++++++++++++++++ ruby/lib/py/jam_tracks/jkasset.pyc | Bin 0 -> 11420 bytes ruby/lib/py/jam_tracks/jkcreate.py | 249 ++++++++++ ruby/lib/py/jam_tracks/jkmedia.py | 44 ++ ruby/lib/py/jam_tracks/jkrsa.py | 194 ++++++++ ruby/lib/py/jam_tracks/jktrack.py | 104 +++++ ruby/lib/py/jam_tracks/jktrack.pyc | Bin 0 -> 5712 bytes ruby/lib/py/jam_tracks/jkunpack.py | 56 +++ ruby/lib/py/jam_tracks/jkzify.rb | 98 ++++ 18 files changed, 1461 insertions(+), 1 deletion(-) create mode 100644 ruby/bin/jamtrack.rb create mode 100644 ruby/lib/jam_ruby/app/uploaders/jam_track_right_uploader.rb create mode 100644 ruby/lib/jam_ruby/jam_tracks_manager.rb create mode 100644 ruby/lib/py/jam_tracks/jamtrack.info create mode 100644 ruby/lib/py/jam_tracks/jamtracklist.txt create mode 100755 ruby/lib/py/jam_tracks/jkaes.py create mode 100644 ruby/lib/py/jam_tracks/jkaes.pyc create mode 100755 ruby/lib/py/jam_tracks/jkasset.py create mode 100644 ruby/lib/py/jam_tracks/jkasset.pyc create mode 100755 ruby/lib/py/jam_tracks/jkcreate.py create mode 100644 ruby/lib/py/jam_tracks/jkmedia.py create mode 100755 ruby/lib/py/jam_tracks/jkrsa.py create mode 100755 ruby/lib/py/jam_tracks/jktrack.py create mode 100644 ruby/lib/py/jam_tracks/jktrack.pyc create mode 100755 ruby/lib/py/jam_tracks/jkunpack.py create mode 100755 ruby/lib/py/jam_tracks/jkzify.rb diff --git a/ruby/bin/jamtrack.rb b/ruby/bin/jamtrack.rb new file mode 100644 index 000000000..2152352db --- /dev/null +++ b/ruby/bin/jamtrack.rb @@ -0,0 +1 @@ +{"SKU": "0044454545454", "METADATA_VER": 1, "TrackData": {"track_2": {"length": 0.1, "bitrate": 96000}, "track_1": {"length": 264.5681632653061, "bitrate": 128003}, "track_0": {"length": 405.054693877551, "bitrate": 128003}}, "TrackInstrument": {"track_1": "Alto Sax", "track_0": "Bass Guitar"}, "Title": "SmFtS2F6YW0gcm9sbGluZyBpbiB0aGUgZG91Z2g="} \ No newline at end of file diff --git a/ruby/lib/jam_ruby.rb b/ruby/lib/jam_ruby.rb index 07234042f..b3b1a3ee1 100755 --- a/ruby/lib/jam_ruby.rb +++ b/ruby/lib/jam_ruby.rb @@ -74,6 +74,7 @@ require "jam_ruby/app/uploaders/mix_uploader" require "jam_ruby/app/uploaders/music_notation_uploader" require "jam_ruby/app/uploaders/jam_track_uploader" require "jam_ruby/app/uploaders/jam_track_track_uploader" +require "jam_ruby/app/uploaders/jam_track_right_uploader" require "jam_ruby/app/uploaders/max_mind_release_uploader" require "jam_ruby/lib/desk_multipass" require "jam_ruby/lib/ip" @@ -189,6 +190,7 @@ require "jam_ruby/models/jam_company" require "jam_ruby/models/user_sync" require "jam_ruby/models/video_source" require "jam_ruby/models/recorded_video" +require "jam_ruby/jam_tracks_manager" include Jampb diff --git a/ruby/lib/jam_ruby/app/uploaders/jam_track_right_uploader.rb b/ruby/lib/jam_ruby/app/uploaders/jam_track_right_uploader.rb new file mode 100644 index 000000000..e33494063 --- /dev/null +++ b/ruby/lib/jam_ruby/app/uploaders/jam_track_right_uploader.rb @@ -0,0 +1,28 @@ +class JamTrackRightUploader < CarrierWave::Uploader::Base + # include CarrierWaveDirect::Uploader + include CarrierWave::MimeTypes + + process :set_content_type + + def initialize(*) + super + JamRuby::UploaderConfiguration.set_aws_private_configuration(self) + end + + # Add a white list of extensions which are allowed to be uploaded. + def extension_white_list + %w(jkz) + end + + def store_dir + nil + end + + def md5 + @md5 ||= ::Digest::MD5.file(current_path).hexdigest + end + + def filename + "#{model.store_dir}/#{model.filename}" if model.id + end +end diff --git a/ruby/lib/jam_ruby/jam_tracks_manager.rb b/ruby/lib/jam_ruby/jam_tracks_manager.rb new file mode 100644 index 000000000..07b264df0 --- /dev/null +++ b/ruby/lib/jam_ruby/jam_tracks_manager.rb @@ -0,0 +1,22 @@ +module JamRuby + + # describes an audio track (like the drums, or guitar) that comprises a JamTrack + class JamTracksManager + class << self + def save_jam_track(jam_track, user) + spec = Gem::Specification.find_by_name("jam_ruby") + py_root = spec.gem_dir + "/lib/py/jam_tracks/" + puts "Executing python in #{py_root}" + + sku=""#jam_track.sku + public_key="" + private_key="" + tracks_filename="" + output_jkz="" + title="" + `python #{py_root}jkcreate.py -D -k #{sku} -c art.png -p #{public_key} -s #{private_key} -I #{tracks_filename} -o #{output_jkz} -t '#{title}'` + end + end + end + +end diff --git a/ruby/lib/jam_ruby/models/jam_track_right.rb b/ruby/lib/jam_ruby/models/jam_track_right.rb index 57c0a444f..33a036a05 100644 --- a/ruby/lib/jam_ruby/models/jam_track_right.rb +++ b/ruby/lib/jam_ruby/models/jam_track_right.rb @@ -2,7 +2,7 @@ module JamRuby # describes what users have rights to which tracks class JamTrackRight < ActiveRecord::Base - attr_accessible :user, :jam_track, :user_id, :jam_track_id + attr_accessible :user, :jam_track, :user_id, :jam_track_id, :url, :md5, :length, :download_count belongs_to :user, class_name: "JamRuby::User" # the owner, or purchaser of the jam_track belongs_to :jam_track, class_name: "JamRuby::JamTrack" @@ -11,6 +11,10 @@ module JamRuby validate :verify_download_count validates_uniqueness_of :user_id, scope: :jam_track_id + + # Uploads the JKZ: + mount_uploader :url, JamTrackRightUploader + MAX_JAM_TRACK_DOWNLOADS = 1000 def verify_download_count @@ -18,5 +22,11 @@ module JamRuby errors.add(:download_count, "must be less than or equal to #{MAX_JAM_TRACK_DOWNLOADS}") end end + + # Create user-specific JKZ for the associated jam_track: + def create_jkz + + end + end end diff --git a/ruby/lib/py/jam_tracks/jamtrack.info b/ruby/lib/py/jam_tracks/jamtrack.info new file mode 100644 index 000000000..2152352db --- /dev/null +++ b/ruby/lib/py/jam_tracks/jamtrack.info @@ -0,0 +1 @@ +{"SKU": "0044454545454", "METADATA_VER": 1, "TrackData": {"track_2": {"length": 0.1, "bitrate": 96000}, "track_1": {"length": 264.5681632653061, "bitrate": 128003}, "track_0": {"length": 405.054693877551, "bitrate": 128003}}, "TrackInstrument": {"track_1": "Alto Sax", "track_0": "Bass Guitar"}, "Title": "SmFtS2F6YW0gcm9sbGluZyBpbiB0aGUgZG91Z2g="} \ No newline at end of file diff --git a/ruby/lib/py/jam_tracks/jamtracklist.txt b/ruby/lib/py/jam_tracks/jamtracklist.txt new file mode 100644 index 000000000..6fa0a517d --- /dev/null +++ b/ruby/lib/py/jam_tracks/jamtracklist.txt @@ -0,0 +1,8 @@ +You Shook Me All Night Long/You Shook Me All Night Long Stem - Bass.ogg+Bass +You Shook Me All Night Long/You Shook Me All Night Long Stem - Drums.ogg+Drums +You Shook Me All Night Long/You Shook Me All Night Long Stem - Guitar - Main.ogg+Guitar Main +You Shook Me All Night Long/You Shook Me All Night Long Stem - Guitar - Rhythm 2.ogg+Guitar Rhythm 2 +You Shook Me All Night Long/You Shook Me All Night Long Stem - Guitar - Rhythm.ogg+Guitar Rhythm +You Shook Me All Night Long/You Shook Me All Night Long Stem - Guitar - Solo.ogg+Guitar Solo +You Shook Me All Night Long/You Shook Me All Night Long Stem - Vocals - Background.ogg+Vocals Background +You Shook Me All Night Long/You Shook Me All Night Long Stem - Vocals - Lead.ogg+Vocals Lead diff --git a/ruby/lib/py/jam_tracks/jkaes.py b/ruby/lib/py/jam_tracks/jkaes.py new file mode 100755 index 000000000..49966001f --- /dev/null +++ b/ruby/lib/py/jam_tracks/jkaes.py @@ -0,0 +1,212 @@ +#!/usr/bin/env python + +from Crypto.Cipher import AES +from Crypto import Random +import base64 +import os +import binascii +import json +import sys + +class JKaes: + + # the key size for the cipher object; must be 16, 24, or 32 for AES + KEY_SIZE = 16 + #always 16 bytes for aes + BLOCK_SIZE = 16 + + # the character used for padding--with a block cipher such as AES, the value + # you encrypt must be a multiple of BLOCK_SIZE in length. This character is + # used to ensure that your value is always a multiple of BLOCK_SIZE + #PADDING_CHARACTER = '{' + PADDING_CHARACTER = b'0' + PADDING = 0 + op = 'encode' + musicFile = '' + + KEY='' + IV='' + + CIPHER_MODE = AES.MODE_CBC + # CIPHER_MODE = AES.MODE_CFB + + def __init__(self, fileName, keyFile='',operation='encode'): + self.op = operation + self.musicFile = fileName + if len(keyFile) != 0: + self.initKeyFromFile(keyFile) + else: + if operation == 'encode': + self.generateNewKeys() + + # one-liner to sufficiently pad the text to be encrypted + def updatePadSize(self,dlen): + self.PADDING = (self.BLOCK_SIZE - dlen % self.BLOCK_SIZE) + + def getPadding(self): + return self.PADDING + + def pad(self,s): + self.updatePadSize(len(s)) + pdata = self.PADDING * self.PADDING_CHARACTER + if len(pdata) != self.PADDING : + sys.exit('Pad data length %d does match %d. Check python pad character size!' % + (len(pdata), self.PADDING)) + s = s + pdata + return s + + def EncodeAEStoB64(self,c, s): + return base64.b64encode(c.encrypt(self.pad(s))) + + def b64DecodeAES(self,c, e): + return c.decrypt(base64.b64decode(e)).rstrip(self.PADDING_CHARACTER) + + + def initKeyFromFile(self,fname): + (self.KEY,self.IV)= self.readAesKeyFile(fname) + + def generateNewKeys(self): + self.KEY = self.generateAesKey(); + self.IV = self.generateAesIv() + + def generateAesKey(self,keySize=KEY_SIZE): + # generate a random secret key + #secret = Random.get_random_bytes(keySize) + secret = Random.new().read(keySize) + return secret + + def generateAesIv(self): + secret = Random.new().read(AES.block_size) + return secret + + def getKeyStr(self): + str = '%s_%s_%s' % (len(self.KEY),base64.b64encode(self.IV),base64.b64encode(self.KEY)) + #print 'okey=',str + return str + def getKeyModeStr(self): + if self.CIPHER_MODE == AES.MODE_CBC: + return 'CBC' + elif self.CIPHER_MODE == AES.MODE_CFB: + return 'CFB' + else: + sys.exit('Bad AES key mode') + + def setKey(self, content): + (keySize, ivstr, keystr) = content.split('_') + #print 'ikey=',content + self.IV = base64.b64decode(ivstr) + self.KEY = base64.b64decode(keystr) + + def writeAesKeytoFile(self, iv,key,Keyfilename): + #initialization vector - iv + #key - must be the same size as the iv + #output filename + str = '%s_%s_%s' % (len(key),base64.b64encode(iv),base64.b64encode(key)) + #print 'okey=',str + fo = open(Keyfilename, 'w') + fo.write(str) + fo.close() + + def writeAesKeytoFile(self,Keyfilename): + self.writeAesKeytoFile(self.IV,self.KEY,Keyfilename) + + def readAesKeyFile(self,keyFilename): + # Given the filename of a file that contains a public or private key, + # return the key as a (n,e) or (n,d) tuple value. + fo = open(keyFilename) + content = fo.read() + fo.close() + (keySize, ivstr, keystr) = content.split('_') + + #print 'ikey=',content + iv = base64.b64decode(ivstr) + key = base64.b64decode(keystr) + return (int(keySize), iv, key) + + + def WriteFileData(self,outfile,data): + #ddata = base64.b64decode(data) + fi = open(outfile,'wb') + fi.write(data); + + def LoadFileData(self, infile): + fi = open(infile,'rb') + data = fi.read(); + return data + #edata = base64.b64encode(data) + #return edata + + + def WriteAesFileData(self, outfile,data): + # These files are assumed to be in cypher text - so we won't b64 encode/decode them + fo = open(outfile,'wb') + fo.write(data) + + def LoadAesFileData(self, infile): + # These files are assumed to be in cypher text - so we won't b64 encode/decode them + fi = open(infile,'rb') + data = fi.read(); + return data + + def EncodeFileToMsgWithKey(self, key, iv, infile): + infileData = self.LoadFileData(infile) + pdata = self.pad(infileData) + cipher = AES.new(key, self.CIPHER_MODE, iv) + msg = cipher.encrypt(pdata) + return msg + + + def EncodeFileWithKeyAndWrite(self, key, iv, infile,outFile): + msg = self.EncodeFileToMsgWithKey(key, iv, infile) + self.WriteAesFileData(outFile,msg) + return msg + + def EncodeFileToMsg(self, infile): + infileData = self.LoadFileData(infile) + # file data must be in plain text + pdata = self.pad(infileData) + cipher = AES.new(self.KEY, self.CIPHER_MODE, self.IV) + msg = cipher.encrypt(pdata) + return msg + + + def EncodeFileAndWrite(self, infile,outFile): + edata = self.EncodeFileToMsg(infile) + self.WriteAesFileData(outFile,edata) + #return edata + + def DecodeFileToMsgWithKey(self,key, iv, infile): + infileData = self.LoadAesFileData(infile) + cipher = AES.new(key, self.CIPHER_MODE, iv) + #expects the file to have the right number of blocks + data = cipher.decrypt(infileData) + return data + + def DecodeFileWithKeyAndWrite(self,key, iv, infile,outFile): + msg = self.DecodeFileToMsgWithKey(key, iv, infile) + self.WriteFileData(outFile,msg) + + def DecodeFileToMsg(self, infile): + msg = self.DecodeFileToMsgWithKey(self.KEY, self.IV, infile) + return msg + + def DecodeFileAndWrite(self, infile,outFile): + data = self.DecodeFileToMsg(infile) + self.WriteFileData(outFile,data) + #return data + + def DecodeMsg(self, pdata): + cipher = AES.new(self.KEY,self.CIPHER_MODE, self.IV) + data = cipher.decrypt(pdata) + return data + + def DecodeU64Msg(self, pdata): + cipher = AES.new(self.KEY, self.CIPHER_MODE, self.IV) + data = self.b64DecodeAES(cipher, pdata) + return data + + + + + + diff --git a/ruby/lib/py/jam_tracks/jkaes.pyc b/ruby/lib/py/jam_tracks/jkaes.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ea98827c8205f5665c145f09a6d2e490d3e4f7e3 GIT binary patch literal 9156 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',tname + # write the content to tmp file + outTmpFile = tempfile.NamedTemporaryFile() + outTmpFile.write(fdata) + + track = JKtrack('dec_'+fname) + track.setKeyStr(aesKey) + track.DecodeBufferToFile(fdata,tname,padding) + if COMPARE_PARSEFILE: + # write the encrypted files out + fi = open('enc_'+tname,'wb') + fi.write(fdata); + fi.close() + + if filecmp.cmp(tname,fname): + print 'OK: file %s match %s' % (fname,tname) + else: + print 'BAD: file %s does not match %s' % (fname,tname) + + def parseContainer(self, rsaKeyFile): + # file must be zip file + zf = zipfile.ZipFile(self.packageFileInfo) + + #now decrypt data with rsa public key + rsa = JKrsa() + rsa.loadKey(rsaKeyFile) + + # get the jam package info file + pinfo = self.extractMediaMetaData(JAMTRACK_PACKAGE_INFO_FILE,rsa,zf) + fi = open(JAMTRACK_PACKAGE_INFO_FILE,'wb') + fi.write(pinfo); + fi.close() + + #get the media details + estr = self.extractMediaMetaData(JAMTRACK_MEDIA_META_FILE,rsa,zf) + data = json.loads(estr) + if DEBUG_ENABLE: + pprint(data) + + try: + coverFileInfoArray = data[JAMTRACK_COVER_ART_TAG] + try: + coverFileInfo= coverFileInfoArray[0] + cfile = coverFileInfo['datafile'] + aesKey = coverFileInfo['key'] + fname = coverFileInfo['filename'] + fsize = coverFileInfo['size'] + padding = coverFileInfo['padding'] + try: + fdata = zf.read(cfile) + except KeyError: + print 'ERROR: Did not find %s in zip file' % cfile + sys.exit() + else: + self.decryptAesFile(aesKey,fdata,fname,cfile,padding) + except: + print 'no cover art data' + except: + print 'no cover art' + + #now extract the file + mediaFilesInfo = data["tracks"] + + for mediaFileInfo in mediaFilesInfo: + aesKey = mediaFileInfo['key'] + fname = mediaFileInfo['filename'] + fsize = mediaFileInfo['size'] + tname = mediaFileInfo['trackname'] + padding = mediaFileInfo['padding'] + fdata = '' + try: + fdata = zf.read(tname) + except KeyError: + print 'ERROR: Did not find %s in zip file' % tname + sys.exit() + else: + self.decryptAesFile(aesKey,fdata,fname,tname,padding) + + diff --git a/ruby/lib/py/jam_tracks/jkasset.pyc b/ruby/lib/py/jam_tracks/jkasset.pyc new file mode 100644 index 0000000000000000000000000000000000000000..efd5ed263d50a59b13574b884d3cc839c725297c GIT binary patch literal 11420 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 YFM9uiZ@(}x0jx5j0wnWS6hXoN4;vafAOHXW literal 0 HcmV?d00001 diff --git a/ruby/lib/py/jam_tracks/jkcreate.py b/ruby/lib/py/jam_tracks/jkcreate.py new file mode 100755 index 000000000..e2272e17b --- /dev/null +++ b/ruby/lib/py/jam_tracks/jkcreate.py @@ -0,0 +1,249 @@ +import jkasset +import json +import jkasset +from pprint import pprint +import random +import sys, getopt +import jkmedia +import base64 +import tempfile +import argparse +from types import * +import unicodedata + +parser ='' +inputfiles = [] + +def help(): + parser.print_help() + + + +def media_arg(string): + #print 'media_arg:', string + try: + fname,ext = string.split('+') + #print fname + if fname == '': + print "### No music file in command line: ", string + raise argparse.ArgumentTypeError("Track file name empty") + + inputfiles.append(string) + except: + if string: + inputfiles.append(string) + pass + + return string + +def loadTracksFromList(fileName): + try: + lines = [line.strip() for line in open(fileName)] + #print lines + global inputfiles + inputfiles = lines + except: + print "Unable to open media filelist", fileName + help() + sys.exit(-1) + +def process(argv): + #print argv + global parser + parser = argparse.ArgumentParser( + prog='jkcreate', + description='JamTrack Packaging Tool', + epilog="Note: Meta file values may be update with title,sku,etc") + + parser.add_argument("-D", "--verbosity", + help="increase output verbosity", + action="store_true") + + parser.add_argument("-t", "--title", metavar='title', type=str, + help="Title of JamTrack. If not specified, the title in the metafile is used.") + + parser.add_argument("-k", "--sku", type=str, + help="The product SKU to use. If not specified, the SKU in the metafile is used.",required=True,) + + parser.add_argument("-c", "--cover", metavar='coverArtFile', type=str, + help="The cover art file. File should be JPEG or PNG.") + + parser.add_argument("-p", "--pkey", metavar='pkey.pem',type=str, + help="The output public key file. File should be .pem",required=True,) + + parser.add_argument("-s", "--skey", metavar='skey.pem', type=str, + help="The output private key file. File should be .pem",required=True,) + + + parser.add_argument("-m", "--meta",metavar='trackMetaFile', type=str, + help="The input JamTrack JSON metafile file. Optional") + + parser.add_argument("-o", "--ofile", metavar='JamTrackPage', type=str, + help="The output JamTrack file (.jkz)",required=True,) + + parser.add_argument("-I", "--infilesList", metavar='musicTrackFileList', type=str, + help=" -I 'file containing tracks'") + + parser.add_argument("-i", "--infiles", metavar='musictrack', type=media_arg, nargs='*', + help=" -i 'Lumme.ogg+Bass Guitar' -i 'Hydrate-Kenny_Beltrey.ogg+Bass Guitar'",) + + + #parser.print_help() + args = parser.parse_args(argv) + #print args + + try: + if args.verbosity: + jkasset.DEBUG_ENABLE = 1 + jkasset.COMPARE_PARSEFILE = True + #args.sku = random.randint(1, 100000000) + except: + pass + + + try: + if args.infilesList: + loadTracksFromList(args.infilesList) + except: + pass + #create json struct from info + metaJson={} + try: + if args.meta: + #try to load json data + try: + json_data=open(args.meta).read() + + metaJson = json.loads(json_data) + if jkasset.DEBUG_ENABLE : + pprint(metaJson) + except: + print 'Error processing json from file: ', args.meta + help() + sys.exit(-1) + except: + print 'No meta file specified' + pass + + jdata={} + mdata=[] + instdata={} + + if any(metaJson): + try: + #pickup the instrument data passed in - if any + instdata = metaJson["TrackInstrument"] + except: + pass + + i = 0 + trackLength = {} + trackLenStats=[] + + #print inputfiles + + if not any(inputfiles) : + print 'No track files specified ' + help() + sys.exit(-1) + + for mediaFileInfo in inputfiles: + #update track name and instrument name + #print mediaFileInfo + mediaFileInfo.strip(); + mlist = mediaFileInfo.split("+") + mediaFileInfo = mlist[0] + + trackName = 'track_%02d' % (i) + i = i + 1 + mdata.append({"name" : mediaFileInfo, "trackName": trackName}) + + try: + minfo = jkmedia.JKTrackInfo(mediaFileInfo) ; + tinfo={}; + tinfo['bitrate']=minfo.bitrate + tinfo['length'] = minfo.trackLength + trackLenStats.append(minfo.trackLength) + trackLength[trackName]= tinfo + except: + print("Cannot find file or read media information for '%s'"% mediaFileInfo) + #help() + sys.exit(-1) + + try: + instdata[trackName]=mlist[1] + except: + if instdata.has_key(trackName) == False: + print "No instrument for track ",trackName + pass + + try: + if args.title: + try: + title = base64.b64encode(args.title) + metaJson['Title']=title + except: + print 'Error processing title : ', title + help() + sys.exit(-1) + + if args.sku: + try: + metaJson['SKU']=args.sku + except: + print 'Error sku' + help() + sys.exit(-1) + + metaJson['METADATA_VER'] = 1 + + except: + #These are both optional.. + pass + + if any(instdata): + metaJson['TrackInstrument'] = instdata + if len (instdata) > len(mdata): + print "Number of instruments ", len (instdata)," in meta data greater than number of tracks ",len(mdata) + help() + sys.exit(-1) + + if any(trackLength): + metaJson['TrackData'] = trackLength + + #dump the meta data to temp file + + metaTmp = tempfile.NamedTemporaryFile(); + metaTmp.write(json.dumps(metaJson)) + metaTmp.seek(0) + + jdata["tracks"] = mdata + jdata["rsa_priv_file"]= args.skey + jdata["rsa_pub_file"]= args.pkey + jdata["container_file"]= args.ofile + + #jdata["jamktrack_info"]= trackMetaFile + jdata["jamktrack_info"]= metaTmp.name + + try: + jdata["coverart"]= args.cover + except: + print 'No cover art specified' + + data = json.dumps(jdata) + #pprint(data) + + fname = 'jt_metadata.json' + fo = open(fname, 'w') + fo.write(data) + fo.close() + + jas = jkasset.JKasset(fname) + + +if __name__ == "__main__": + random.seed() + process(sys.argv[1:]) + + + diff --git a/ruby/lib/py/jam_tracks/jkmedia.py b/ruby/lib/py/jam_tracks/jkmedia.py new file mode 100644 index 000000000..035abc3a4 --- /dev/null +++ b/ruby/lib/py/jam_tracks/jkmedia.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +import base64 +import os +import binascii +import json +import sys +from optparse import OptionParser +#from import mutagen.ogg +#from import mutagen.oggvorbis + +class JKTrackInfo: + trackLength =0 + bitrate = 0 + mediaFile=''; + + + def __init__(self, fileName): + #get the file extension + self.mediaFile = fileName + fileName, fileExtension = os.path.splitext(self.mediaFile) + audio = '' + if fileExtension == '.ogg' : + from mutagen.oggvorbis import OggVorbis + audio = OggVorbis(self.mediaFile) + elif fileExtension == '.wav' : + from mutagen.wavpack import WavPack + audio = WavPack(self.mediaFile) + elif fileExtension == '.mp3' : + from mutagen.mp3 import MP3 + audio = MP3(self.mediaFile) + elif fileExtension == '.flac' : + from mutagen.flac import FLAC + audio = FLAC(self.mediaFile) + else: + print "Unsupported filetype '",fileExtension, "' for file ", self.mediaFile + raise Exception("Unsupported filetype") + + print audio.info.length, audio.info.bitrate + self.bitrate = audio.info.bitrate + self.trackLength = audio.info.length + + + + diff --git a/ruby/lib/py/jam_tracks/jkrsa.py b/ruby/lib/py/jam_tracks/jkrsa.py new file mode 100755 index 000000000..4d0e38600 --- /dev/null +++ b/ruby/lib/py/jam_tracks/jkrsa.py @@ -0,0 +1,194 @@ +# RSA Cipher +# http://inventwithpython.com/hacking (BSD Licensed) + +from Crypto.Cipher import PKCS1_OAEP +from Crypto.PublicKey import RSA +from Crypto import Random +import base64 +import sys + +class JKrsa: + # IMPORTANT: The block size MUST be less than or equal to the key size! + # (Note: The block size is in bytes, the key size is in bits. There + # are 8 bits in 1 byte.) + #DEFAULT_BLOCK_SIZE = 128-2 # 128 bytes + DEFAULT_BLOCK_SIZE = 64 # 128 bytes + + + RSA_KEYSIZE = 1024 + BYTE_SIZE = 256 # One byte has 256 different values. + RSAKey=''; + PADDING = 0 + PADDING_CHARACTER =b'\0' + encipher=''; + decipher='' + + def __init__(self, operation='encode'): + self.op = operation + + def updatePadSize(self,dlen): + self.PADDING = (self.DEFAULT_BLOCK_SIZE - dlen % self.DEFAULT_BLOCK_SIZE) + + def getPadding(self): + return self.PADDING + + def pad(self,s): + self.updatePadSize(len(s)) + pdata = self.PADDING * self.PADDING_CHARACTER + if len(pdata) != self.PADDING : + sys.exit('Pad data length %d does match %d. Check python pad character size!', + (len(pdata), self.PADDING)) + s = s + pdata + return s + + def EncodeRSA(self,c, s): + #print 'len=',len(s) + estr = self.encipher.encrypt(s) + #print 'elen=',len(estr) + #print estr + return estr + + def DecodeRSA(self,c, e): + #print 'len=',len(e) + str = self.decipher.decrypt(e) + return str + + def generateKey(self, privateFileName='pri_Key.pem', publicFileName='pub_Key.pem'): + self.RSAKey = RSA.generate(self.RSA_KEYSIZE) + + public_key = self.RSAKey.publickey().exportKey("PEM") + private_key = self.RSAKey.exportKey("PEM") + + if privateFileName: + f = open(privateFileName,'w') + f.write(self.RSAKey.exportKey("PEM")) + f.close() + + f = open(privateFileName,'r') + skey = RSA.importKey(f.read()) + self.decipher = PKCS1_OAEP.new(skey) + f.close() + + if publicFileName: + f = open(publicFileName,'w') + f.write(self.RSAKey.publickey().exportKey("PEM")) + f.close() + + f = open(publicFileName,'r') + pkey = RSA.importKey(f.read()) + self.encipher = PKCS1_OAEP.new(pkey) + f.close() + + return private_key, public_key + + def loadKey(self, fileName='pri_Key.pem'): + f = open(fileName,'r') + self.RSAKey = RSA.importKey(f.read()) + self.decipher = PKCS1_OAEP.new(self.RSAKey) + f.close() + + def WriteFileData(self,outfile,data): + #ddata = base64.b64decode(data) + fi = open(outfile,'wb') + fi.write(data); + + def LoadFileData(self, infile): + fi = open(infile,'rb') + data = fi.read(); + #edata = base64.b64encode(data) + return data + + def WriteRSAFileData(self, outfile,data): + # These files are assumed to be in cypher text - so we won't b64 encode/decode them + fo = open(outfile,'wb') + #edata = base64.b64encode(data) + fo.write(data) + + + def LoadRSAFileData(self, infile): + # These files are assumed to be in cypher text - so we won't b64 encode/decode them + fi = open(infile,'rb') + data = fi.read(); + return data + + def getBlocksFromText(self,message, blockSize=DEFAULT_BLOCK_SIZE): + # Converts a string message to a list of block integers. Each integer + # represents 128 (or whatever blockSize is set to) string characters. + # messageBytes = message.encode('ascii') # convert the string to bytes + blockInts = [] + blockInts = [message[i:i+blockSize] for i in range(0, len(message), blockSize)] + return blockInts + + + def encryptMesssage(self, message, blockSize=DEFAULT_BLOCK_SIZE): + keySize = self.RSAKey.size() + 1; + + # Check that key size is greater than block size. + if keySize < blockSize * 8: # * 8 to convert bytes to bits + sys.exit('ERROR: Block size is %d bits and key size is %d bits.' + ' The RSA cipher requires the block size to be equal to' + ' or less than the key size. Either decrease the' + ' block size or use different keys.' % (blockSize * 8, keySize)) + + if len(message) % blockSize : # * 8 to convert bytes to bits + sys.exit('ERROR: Block size is %d and data size is %d .' + ' The RSA cipher requires the block size to be equal to' + ' or less than the key size. Either decrease the' + ' block size or use different keys.' % (blockSize, len(message))) + + + encryptedContent = [] + for block in self.getBlocksFromText(message, blockSize): + encryptedContent_ = self.EncodeRSA(self.RSAKey,block); + encryptedContent.extend(encryptedContent_) + + return encryptedContent + + def decryptMesssage(self, message): + #print message + #split in equal chunks + decryptedContent = [] + for block in self.getBlocksFromText(message, self.DEFAULT_BLOCK_SIZE*2): + decryptedContent_ = self.DecodeRSA(self.RSAKey,block); + decryptedContent.extend(decryptedContent_) + + return decryptedContent + + + def encryptAndWriteToFile(self, keyFilename, inFile,outFilename, blockSize=DEFAULT_BLOCK_SIZE): + # Using a key from a key file, encrypt the message and save it to a + # file. Returns the encrypted message string. + self.loadKey(keyFilename) + keySize = self.RSAKey.size()+1; + + # Check that key size is greater than block size. + if keySize < blockSize * 8: # * 8 to convert bytes to bits + sys.exit('ERROR: Block size is %s bits and key size is %s bits.' + ' The RSA cipher requires the block size to be equal to' + ' or less than the key size. Either decrease the' + ' block size or use different keys.' % (blockSize * 8, keySize)) + + # Encrypt the message + message = self.LoadFileData(inFile); + message = self.pad(message) + + encryptedContent = self.encryptMesssage(message); + self.WriteRSAFileData(outFilename,encryptedContent) + # Also return the encrypted string. + #return encryptedContent + + + def readFromFileAndDecrypt(self, keyFilename, inFile, outFilename): + # Using a key from a key file, read an encrypted message from a file + # and then decrypt it. Returns the decrypted message string. + self.loadKey(keyFilename) + + # Read in the message length and the encrypted message from the file. + message = self.LoadRSAFileData(inFile) + + decryptMessage = self.decryptMesssage(message) + self.WriteFileData(outFilename,decryptMessage) + + + + diff --git a/ruby/lib/py/jam_tracks/jktrack.py b/ruby/lib/py/jam_tracks/jktrack.py new file mode 100755 index 000000000..3e0dcd4b3 --- /dev/null +++ b/ruby/lib/py/jam_tracks/jktrack.py @@ -0,0 +1,104 @@ +import jkaes +import tempfile +from jkaes import JKaes +import os +class JKtrack: + mediaFile = '' + Name = '' + aesInKeyFile='' + aesCipher = '' + outTmpFile ='' + outPutFile = '' + def __init__(self, fileName, title='Unknown', inKeyFile=''): + self.mediaFile = fileName + self.Name = title + self.aesInKeyFile = inKeyFile + self.aesCipher = JKaes(self.mediaFile, self.aesInKeyFile) + + def getOutputFileName(self): + return self.outPutFile; + + def getKeyStr(self): + return self.aesCipher.getKeyStr(); + + def getEncryptPadding(self): + return self.aesCipher.getPadding() + + def setKeyStr(self,key): + self.aesCipher.setKey(key); + + def getKeyModeStr(self): + return self.aesCipher.getKeyModeStr() + + def getCoverEncryptFilePackageName(self,instance=-1): + #preserve the extentsion on the original name + fileName, fileExtension = os.path.splitext(self.mediaFile) + if instance != -1: + nm = "cover_%02d%s" % (instance,fileExtension) + return nm + else: + nm = os.path.basename(self.mediaFile) + return nm + + def getEncryptFilePackageName(self,instance=-1): + #preserve the extentsion on the original name + fileName, fileExtension = os.path.splitext(self.mediaFile) + if instance != -1: + nm = "track_%02d%s" % (instance,fileExtension) + return nm + else: + nm = os.path.basename(self.mediaFile) + return nm + + def Encode(self, outFile): + self.outPutFile = outFile + self.aesCipher.EncodeFileAndWrite( self.mediaFile, outFile) + + def EncodeToTmpFile(self): + self.outTmpFile = tempfile.NamedTemporaryFile() + self.Encode(self.outTmpFile.name) + + def EncodeWithKeyToFile(self,keyFile,outFile): + self.aesCipher.readAesKeyFile(keyFile) + self.Encode(outFile) + + def Decode(self, outFile): + self.outPutFile = outFile + self.aesCipher.DecodeFileAndWrite( self.mediaFile, outFile) + + def DecodeToTmpFile(self): + self.outTmpFile = tempfile.NamedTemporaryFile() + self.Decode(self.outTmpFile.name) + + def DecodeWithKeyToFile(self,keyFile,outFile): + self.aesCipher.readAesKeyFile(keyFile) + self.Decode(outFile) + + + def DecodeBufferToFile(self,msg,outfile,padding=0): + dmsg = self.aesCipher.DecodeMsg(msg) + fi = open(outfile,'wb') + fi.write(dmsg); + if padding > 0: + #trunckage the file by + fi.seek(-padding, os.SEEK_END) + fi.truncate() + fi.close() + return dmsg + + def DecodeU64Buffer(self,msg): + return self.aesCipher.DecodeU64Msg(msg) + + def setKey(self, keyFile): + self.aesCipher.readAesKeyFile(keyFile) + + def saveKey(self,keyFile): + self.aesCipher.writeAesKeytoFile(keyFile) + + def saveEncFileCopy(self,outfile): + fi = open(self.outPutFile,'rb') + fi.seek(0) + dmsg = fi.read(); + fo = open(outfile,'wb') + fo.write(dmsg); + return dmsg \ No newline at end of file diff --git a/ruby/lib/py/jam_tracks/jktrack.pyc b/ruby/lib/py/jam_tracks/jktrack.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d065cbbe6aee5c45bc364f9d9dba2207c51113bd GIT binary patch literal 5712 zcmc&&>uwuG6h3P^cc)3yB)tJDl!6hoE)^6(xs^0XrD~`qsY*j7Yvb9Z>&x2OowN-~ zeJGux2C{o}8Dk4FTbI}78`cdu2hzUbmkbMv~PFnV{I+2sJ zK1tFzGbMIZfwI^!1*XMf|Ag#=!BYxPDm){0N;k}kEh}(Z?6d-NVoxb>M(m6N^I~Te zSP+YCb9%y}*fTnLR_we2=fo~3a9-@90vE)rR?dP|hbAhKheGsA>wH0qvyE~k%sg-2aMpJ=n-5o_= ztHnv|YBds{ksb*q^r_R-dQGKAW20mQCdq}|8YcmEw%vnmN2Ybf@Sq_Qej3MbMefIK z*Y#K_z&T821#}X;O8YdXJ?)KvzSZM^`wbaWq2o;GZq=nH!G)Ot+6A=#Ks03!q926T z#>v)6Q0;QrFDLbMr`yAP?f@GUX|Ji>%wwW2d$`|TpkeP-6)?_as)CB@g;oTg6lq2~ zL$oHkn@0=HU*v$;|D6@l&b$@f@7U<1EQwb17FtfY=88I*U`(L^RS!P_K#+t3Ah=N> zMXKUSL0;mA(1!#NNV6#EEv5m+$)ao)WbtK5c1yCFzluTc)qKIyorq;c5oZh~_0F>> ztzEf(!(PdJ&?GsXOk*FTmIj}!+lpQE!Wj*VRQE=hMG2f)-_bCl8{;HH#H&Y!x^fgU z!{+h}7bRKTNgNMP+C#JvN*8)vB3rrXGuk~s;tjVne!q0ih|5phb z=npEvEP_bGWNGNJDt2=o&_LYg0v9M|Lsu|wa20JxDTv4+*Vl{mLoB{tq||rct}kOS z$m)j;oZ-e-S-o&WQ!}(6ANH9wfE~_Cecn#&qZB5ok~t8Bm?4nkG{FHJk}w$mJT0FB z%19}*aX*`pYxU&!0C;!Y)6iP$`7>R!AdS+|7^}4f{mVt|E?viwZ)Q!qhM7(prdqPN zJejYxyX4i-sMTfFCz2N%3cJvS<2H?4E+2wGPpKh3TB} zSszM{*Y58`e`ELNQjcY2{@4pp8oVds_JGx6GkEh34mFqAy~~c_#9TplB6SiC+4I&n z{qt^<}WzB17Y z;rWIVRG9$&LDBD-xSwrlpzCy_L@(ISnFsQ#TGZ6csiny3^76gf^2!|@xO6+IhuFse zi)&XF>1Evq5c1o`v3c^)uuHh>t93owas$13Gah1mVX*gXez(238KpkFtY9{qMJK^r zFcVA%<-$Dr$w8>;$5`$*CA!>3UAcc;*P%x}K$y!F;jRpsw znbC=2?40pTM -s ' + + +def process(argv): + + global parser + parser = argparse.ArgumentParser( + prog='jkunpack', + description='JamTrack UnPackaging Tool', + epilog="Note: Files may be overwritten during unpacking") + + parser.add_argument("-D", "--verbosity", + help="increase output verbosity", + action="store_true") + + parser.add_argument("-s", "--skey", metavar='skey.pem', type=str, + help="The private key file. File should be .pem",required=True) + + parser.add_argument("-i", "--ifile", metavar='JamTrackPage', type=str, + help="The input JamTrack file (.jkz)",required=True) + + parser.print_help() + args = parser.parse_args(argv) + print args + + try: + if args.verbosity: + jkasset.DEBUG_ENABLE = 1 + jkasset.COMPARE_PARSEFILE = True + except: + pass + + + jas = jkasset.JKasset(args.ifile,'decode',args.skey) + +if __name__ == "__main__": + random.seed() + process(sys.argv[1:]) + + + diff --git a/ruby/lib/py/jam_tracks/jkzify.rb b/ruby/lib/py/jam_tracks/jkzify.rb new file mode 100755 index 000000000..c5ef594fd --- /dev/null +++ b/ruby/lib/py/jam_tracks/jkzify.rb @@ -0,0 +1,98 @@ +#!/usr/bin/env ruby +# +# Usage: jkzify [path to JamTrack WAVs] [destination for jkz and key files] +# +# Please review the comments, this script needs improvement -- I just threw it together to help me validate jkcreate.py +# + +wd = Dir.getwd +tracks_location = ARGV.shift +output_location = ARGV.shift +script_location = File.dirname(File.expand_path(__FILE__)) + + + +# first try to catch any issues with input arguments +def halt msg; warn msg; exit; end +halt "usage: jkzify path/to/wavs/ path/to/output" unless tracks_location && output_location +halt "path not found" unless File.exist?(tracks_location) && File.exist?(output_location) +# end + + + +# step 1: gather metadata from the track/folder names (and user input) +title = tracks_location.split('/').last.gsub(/'/,'') +puts "i think this track is called \"#{title}\"..." +shortname = title.gsub(/[aeiou\s]/,'').squeeze[0...8].downcase +puts "i'll call it #{shortname} for now" +puts "what sku do you want for this JamTrack? (press ENTER for '#{shortname}')" +input = STDIN.gets.chomp.strip +sku = input.empty? ? shortname : input + + + +# step 2: move WAVs to our own workspace +def renamed(t); t.gsub(/\s|'/,'_'); end #little helper + +Dir.chdir wd +`rm -rf #{shortname}` if File.exist? shortname +`mkdir #{shortname}` + +Dir.chdir tracks_location +Dir.glob('*.wav') do |track| + new_name = renamed track + `cp "#{track}" "#{wd}/#{shortname}/#{new_name}"` + puts "Copied from " + track + " to #{wd}/#{shortname}/" + new_name +end +Dir.chdir "#{wd}/#{shortname}" + + + +# step 3: generate OGGs from WAVs +oggs_location = title.downcase.gsub('\'','').split.join('_') + '_oggs/' +puts "generating OGGs in a separate folder #{oggs_location} (this may take some time)" +`mkdir #{oggs_location}` unless File.exist?(oggs_location) +Dir.glob('*.wav') do |track| + input = track + output = "#{oggs_location}#{File.basename(track, '.wav')}.ogg" + #puts "sox #{input} #{output}" + `sox #{input} #{output}` +end + + +# step 4: get all track parameters (names and instruments) +track_params = {} +track_files = [] +suffix = '\.wav' +Dir.foreach tracks_location do |track| + next if track =~ /Master Mix/i + next unless track =~ /#{suffix}$/i + print '.' + track.gsub!(/#{suffix}$/i, '') + instrument = track.split('-').drop(1).map(&:strip).join(' ') + parameter = "#{shortname}/#{oggs_location}#{renamed track}.ogg+#{instrument}" + #parameter.gsub!(/ /, '\ ') #spaces in filenames + track_params[instrument] = "-i '#{parameter}'" + track_files << parameter +end +puts "OK, #{track_params.length} tracks found" + + + +# step 5: create list of track titles +Dir.chdir wd +tracks_filename = "#{shortname}.txt" +File.open(tracks_filename, 'w+') { |f| f.puts track_files } + + + +# step 6: run jkcreate.py given all these parameters +public_key = "#{output_location}#{shortname}-pub.pem" +private_key = "#{output_location}#{shortname}-priv.pem" +output_jkz = "#{output_location}#{shortname}.jkz" +#puts "python jkcreate.py -D -k #{sku} -c art.png -p #{public_key} -s #{private_key} -I #{tracks_filename} -o #{output_jkz} -t '#{title}'" +`python jkcreate.py -D -k #{sku} -c art.png -p #{public_key} -s #{private_key} -I #{tracks_filename} -o #{output_jkz} -t '#{title}'` + + + +Dir.chdir wd \ No newline at end of file From 2206ba85939c96cca285e50bee7c846fb6ed78b8 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Sun, 14 Dec 2014 20:16:24 -0600 Subject: [PATCH 29/72] VRFS-2481 : Use relative path to include jam_tracks. --- ruby/bin/jamtrack.rb | 0 ruby/lib/jam_ruby/jam_tracks_manager.rb | 5 +- ruby/lib/py/jam_tracks/jamtrack.info | 1 - ruby/lib/py/jam_tracks/jamtracklist.txt | 8 - ruby/lib/py/jam_tracks/jkaes.py | 212 ------------ ruby/lib/py/jam_tracks/jkaes.pyc | Bin 9156 -> 0 bytes ruby/lib/py/jam_tracks/jkasset.py | 431 ------------------------ ruby/lib/py/jam_tracks/jkasset.pyc | Bin 11420 -> 0 bytes ruby/lib/py/jam_tracks/jkcreate.py | 249 -------------- ruby/lib/py/jam_tracks/jkmedia.py | 44 --- ruby/lib/py/jam_tracks/jkrsa.py | 194 ----------- ruby/lib/py/jam_tracks/jktrack.py | 104 ------ ruby/lib/py/jam_tracks/jktrack.pyc | Bin 5712 -> 0 bytes ruby/lib/py/jam_tracks/jkunpack.py | 56 --- ruby/lib/py/jam_tracks/jkzify.rb | 98 ------ 15 files changed, 2 insertions(+), 1400 deletions(-) mode change 100644 => 100755 ruby/bin/jamtrack.rb delete mode 100644 ruby/lib/py/jam_tracks/jamtrack.info delete mode 100644 ruby/lib/py/jam_tracks/jamtracklist.txt delete mode 100755 ruby/lib/py/jam_tracks/jkaes.py delete mode 100644 ruby/lib/py/jam_tracks/jkaes.pyc delete mode 100755 ruby/lib/py/jam_tracks/jkasset.py delete mode 100644 ruby/lib/py/jam_tracks/jkasset.pyc delete mode 100755 ruby/lib/py/jam_tracks/jkcreate.py delete mode 100644 ruby/lib/py/jam_tracks/jkmedia.py delete mode 100755 ruby/lib/py/jam_tracks/jkrsa.py delete mode 100755 ruby/lib/py/jam_tracks/jktrack.py delete mode 100644 ruby/lib/py/jam_tracks/jktrack.pyc delete mode 100755 ruby/lib/py/jam_tracks/jkunpack.py delete mode 100755 ruby/lib/py/jam_tracks/jkzify.rb diff --git a/ruby/bin/jamtrack.rb b/ruby/bin/jamtrack.rb old mode 100644 new mode 100755 diff --git a/ruby/lib/jam_ruby/jam_tracks_manager.rb b/ruby/lib/jam_ruby/jam_tracks_manager.rb index 07b264df0..29116aaf7 100644 --- a/ruby/lib/jam_ruby/jam_tracks_manager.rb +++ b/ruby/lib/jam_ruby/jam_tracks_manager.rb @@ -4,8 +4,7 @@ module JamRuby class JamTracksManager class << self def save_jam_track(jam_track, user) - spec = Gem::Specification.find_by_name("jam_ruby") - py_root = spec.gem_dir + "/lib/py/jam_tracks/" + py_root = File.expand_path(File.join("..", "..", "jamtracks")) puts "Executing python in #{py_root}" sku=""#jam_track.sku @@ -14,7 +13,7 @@ module JamRuby tracks_filename="" output_jkz="" title="" - `python #{py_root}jkcreate.py -D -k #{sku} -c art.png -p #{public_key} -s #{private_key} -I #{tracks_filename} -o #{output_jkz} -t '#{title}'` + `python #{py_root}/jkcreate.py -D -k #{sku} -c art.png -p #{public_key} -s #{private_key} -I #{tracks_filename} -o #{output_jkz} -t '#{title}'` end end end diff --git a/ruby/lib/py/jam_tracks/jamtrack.info b/ruby/lib/py/jam_tracks/jamtrack.info deleted file mode 100644 index 2152352db..000000000 --- a/ruby/lib/py/jam_tracks/jamtrack.info +++ /dev/null @@ -1 +0,0 @@ -{"SKU": "0044454545454", "METADATA_VER": 1, "TrackData": {"track_2": {"length": 0.1, "bitrate": 96000}, "track_1": {"length": 264.5681632653061, "bitrate": 128003}, "track_0": {"length": 405.054693877551, "bitrate": 128003}}, "TrackInstrument": {"track_1": "Alto Sax", "track_0": "Bass Guitar"}, "Title": "SmFtS2F6YW0gcm9sbGluZyBpbiB0aGUgZG91Z2g="} \ No newline at end of file diff --git a/ruby/lib/py/jam_tracks/jamtracklist.txt b/ruby/lib/py/jam_tracks/jamtracklist.txt deleted file mode 100644 index 6fa0a517d..000000000 --- a/ruby/lib/py/jam_tracks/jamtracklist.txt +++ /dev/null @@ -1,8 +0,0 @@ -You Shook Me All Night Long/You Shook Me All Night Long Stem - Bass.ogg+Bass -You Shook Me All Night Long/You Shook Me All Night Long Stem - Drums.ogg+Drums -You Shook Me All Night Long/You Shook Me All Night Long Stem - Guitar - Main.ogg+Guitar Main -You Shook Me All Night Long/You Shook Me All Night Long Stem - Guitar - Rhythm 2.ogg+Guitar Rhythm 2 -You Shook Me All Night Long/You Shook Me All Night Long Stem - Guitar - Rhythm.ogg+Guitar Rhythm -You Shook Me All Night Long/You Shook Me All Night Long Stem - Guitar - Solo.ogg+Guitar Solo -You Shook Me All Night Long/You Shook Me All Night Long Stem - Vocals - Background.ogg+Vocals Background -You Shook Me All Night Long/You Shook Me All Night Long Stem - Vocals - Lead.ogg+Vocals Lead diff --git a/ruby/lib/py/jam_tracks/jkaes.py b/ruby/lib/py/jam_tracks/jkaes.py deleted file mode 100755 index 49966001f..000000000 --- a/ruby/lib/py/jam_tracks/jkaes.py +++ /dev/null @@ -1,212 +0,0 @@ -#!/usr/bin/env python - -from Crypto.Cipher import AES -from Crypto import Random -import base64 -import os -import binascii -import json -import sys - -class JKaes: - - # the key size for the cipher object; must be 16, 24, or 32 for AES - KEY_SIZE = 16 - #always 16 bytes for aes - BLOCK_SIZE = 16 - - # the character used for padding--with a block cipher such as AES, the value - # you encrypt must be a multiple of BLOCK_SIZE in length. This character is - # used to ensure that your value is always a multiple of BLOCK_SIZE - #PADDING_CHARACTER = '{' - PADDING_CHARACTER = b'0' - PADDING = 0 - op = 'encode' - musicFile = '' - - KEY='' - IV='' - - CIPHER_MODE = AES.MODE_CBC - # CIPHER_MODE = AES.MODE_CFB - - def __init__(self, fileName, keyFile='',operation='encode'): - self.op = operation - self.musicFile = fileName - if len(keyFile) != 0: - self.initKeyFromFile(keyFile) - else: - if operation == 'encode': - self.generateNewKeys() - - # one-liner to sufficiently pad the text to be encrypted - def updatePadSize(self,dlen): - self.PADDING = (self.BLOCK_SIZE - dlen % self.BLOCK_SIZE) - - def getPadding(self): - return self.PADDING - - def pad(self,s): - self.updatePadSize(len(s)) - pdata = self.PADDING * self.PADDING_CHARACTER - if len(pdata) != self.PADDING : - sys.exit('Pad data length %d does match %d. Check python pad character size!' % - (len(pdata), self.PADDING)) - s = s + pdata - return s - - def EncodeAEStoB64(self,c, s): - return base64.b64encode(c.encrypt(self.pad(s))) - - def b64DecodeAES(self,c, e): - return c.decrypt(base64.b64decode(e)).rstrip(self.PADDING_CHARACTER) - - - def initKeyFromFile(self,fname): - (self.KEY,self.IV)= self.readAesKeyFile(fname) - - def generateNewKeys(self): - self.KEY = self.generateAesKey(); - self.IV = self.generateAesIv() - - def generateAesKey(self,keySize=KEY_SIZE): - # generate a random secret key - #secret = Random.get_random_bytes(keySize) - secret = Random.new().read(keySize) - return secret - - def generateAesIv(self): - secret = Random.new().read(AES.block_size) - return secret - - def getKeyStr(self): - str = '%s_%s_%s' % (len(self.KEY),base64.b64encode(self.IV),base64.b64encode(self.KEY)) - #print 'okey=',str - return str - def getKeyModeStr(self): - if self.CIPHER_MODE == AES.MODE_CBC: - return 'CBC' - elif self.CIPHER_MODE == AES.MODE_CFB: - return 'CFB' - else: - sys.exit('Bad AES key mode') - - def setKey(self, content): - (keySize, ivstr, keystr) = content.split('_') - #print 'ikey=',content - self.IV = base64.b64decode(ivstr) - self.KEY = base64.b64decode(keystr) - - def writeAesKeytoFile(self, iv,key,Keyfilename): - #initialization vector - iv - #key - must be the same size as the iv - #output filename - str = '%s_%s_%s' % (len(key),base64.b64encode(iv),base64.b64encode(key)) - #print 'okey=',str - fo = open(Keyfilename, 'w') - fo.write(str) - fo.close() - - def writeAesKeytoFile(self,Keyfilename): - self.writeAesKeytoFile(self.IV,self.KEY,Keyfilename) - - def readAesKeyFile(self,keyFilename): - # Given the filename of a file that contains a public or private key, - # return the key as a (n,e) or (n,d) tuple value. - fo = open(keyFilename) - content = fo.read() - fo.close() - (keySize, ivstr, keystr) = content.split('_') - - #print 'ikey=',content - iv = base64.b64decode(ivstr) - key = base64.b64decode(keystr) - return (int(keySize), iv, key) - - - def WriteFileData(self,outfile,data): - #ddata = base64.b64decode(data) - fi = open(outfile,'wb') - fi.write(data); - - def LoadFileData(self, infile): - fi = open(infile,'rb') - data = fi.read(); - return data - #edata = base64.b64encode(data) - #return edata - - - def WriteAesFileData(self, outfile,data): - # These files are assumed to be in cypher text - so we won't b64 encode/decode them - fo = open(outfile,'wb') - fo.write(data) - - def LoadAesFileData(self, infile): - # These files are assumed to be in cypher text - so we won't b64 encode/decode them - fi = open(infile,'rb') - data = fi.read(); - return data - - def EncodeFileToMsgWithKey(self, key, iv, infile): - infileData = self.LoadFileData(infile) - pdata = self.pad(infileData) - cipher = AES.new(key, self.CIPHER_MODE, iv) - msg = cipher.encrypt(pdata) - return msg - - - def EncodeFileWithKeyAndWrite(self, key, iv, infile,outFile): - msg = self.EncodeFileToMsgWithKey(key, iv, infile) - self.WriteAesFileData(outFile,msg) - return msg - - def EncodeFileToMsg(self, infile): - infileData = self.LoadFileData(infile) - # file data must be in plain text - pdata = self.pad(infileData) - cipher = AES.new(self.KEY, self.CIPHER_MODE, self.IV) - msg = cipher.encrypt(pdata) - return msg - - - def EncodeFileAndWrite(self, infile,outFile): - edata = self.EncodeFileToMsg(infile) - self.WriteAesFileData(outFile,edata) - #return edata - - def DecodeFileToMsgWithKey(self,key, iv, infile): - infileData = self.LoadAesFileData(infile) - cipher = AES.new(key, self.CIPHER_MODE, iv) - #expects the file to have the right number of blocks - data = cipher.decrypt(infileData) - return data - - def DecodeFileWithKeyAndWrite(self,key, iv, infile,outFile): - msg = self.DecodeFileToMsgWithKey(key, iv, infile) - self.WriteFileData(outFile,msg) - - def DecodeFileToMsg(self, infile): - msg = self.DecodeFileToMsgWithKey(self.KEY, self.IV, infile) - return msg - - def DecodeFileAndWrite(self, infile,outFile): - data = self.DecodeFileToMsg(infile) - self.WriteFileData(outFile,data) - #return data - - def DecodeMsg(self, pdata): - cipher = AES.new(self.KEY,self.CIPHER_MODE, self.IV) - data = cipher.decrypt(pdata) - return data - - def DecodeU64Msg(self, pdata): - cipher = AES.new(self.KEY, self.CIPHER_MODE, self.IV) - data = self.b64DecodeAES(cipher, pdata) - return data - - - - - - diff --git a/ruby/lib/py/jam_tracks/jkaes.pyc b/ruby/lib/py/jam_tracks/jkaes.pyc deleted file mode 100644 index ea98827c8205f5665c145f09a6d2e490d3e4f7e3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9156 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',tname - # write the content to tmp file - outTmpFile = tempfile.NamedTemporaryFile() - outTmpFile.write(fdata) - - track = JKtrack('dec_'+fname) - track.setKeyStr(aesKey) - track.DecodeBufferToFile(fdata,tname,padding) - if COMPARE_PARSEFILE: - # write the encrypted files out - fi = open('enc_'+tname,'wb') - fi.write(fdata); - fi.close() - - if filecmp.cmp(tname,fname): - print 'OK: file %s match %s' % (fname,tname) - else: - print 'BAD: file %s does not match %s' % (fname,tname) - - def parseContainer(self, rsaKeyFile): - # file must be zip file - zf = zipfile.ZipFile(self.packageFileInfo) - - #now decrypt data with rsa public key - rsa = JKrsa() - rsa.loadKey(rsaKeyFile) - - # get the jam package info file - pinfo = self.extractMediaMetaData(JAMTRACK_PACKAGE_INFO_FILE,rsa,zf) - fi = open(JAMTRACK_PACKAGE_INFO_FILE,'wb') - fi.write(pinfo); - fi.close() - - #get the media details - estr = self.extractMediaMetaData(JAMTRACK_MEDIA_META_FILE,rsa,zf) - data = json.loads(estr) - if DEBUG_ENABLE: - pprint(data) - - try: - coverFileInfoArray = data[JAMTRACK_COVER_ART_TAG] - try: - coverFileInfo= coverFileInfoArray[0] - cfile = coverFileInfo['datafile'] - aesKey = coverFileInfo['key'] - fname = coverFileInfo['filename'] - fsize = coverFileInfo['size'] - padding = coverFileInfo['padding'] - try: - fdata = zf.read(cfile) - except KeyError: - print 'ERROR: Did not find %s in zip file' % cfile - sys.exit() - else: - self.decryptAesFile(aesKey,fdata,fname,cfile,padding) - except: - print 'no cover art data' - except: - print 'no cover art' - - #now extract the file - mediaFilesInfo = data["tracks"] - - for mediaFileInfo in mediaFilesInfo: - aesKey = mediaFileInfo['key'] - fname = mediaFileInfo['filename'] - fsize = mediaFileInfo['size'] - tname = mediaFileInfo['trackname'] - padding = mediaFileInfo['padding'] - fdata = '' - try: - fdata = zf.read(tname) - except KeyError: - print 'ERROR: Did not find %s in zip file' % tname - sys.exit() - else: - self.decryptAesFile(aesKey,fdata,fname,tname,padding) - - diff --git a/ruby/lib/py/jam_tracks/jkasset.pyc b/ruby/lib/py/jam_tracks/jkasset.pyc deleted file mode 100644 index efd5ed263d50a59b13574b884d3cc839c725297c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11420 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 YFM9uiZ@(}x0jx5j0wnWS6hXoN4;vafAOHXW diff --git a/ruby/lib/py/jam_tracks/jkcreate.py b/ruby/lib/py/jam_tracks/jkcreate.py deleted file mode 100755 index e2272e17b..000000000 --- a/ruby/lib/py/jam_tracks/jkcreate.py +++ /dev/null @@ -1,249 +0,0 @@ -import jkasset -import json -import jkasset -from pprint import pprint -import random -import sys, getopt -import jkmedia -import base64 -import tempfile -import argparse -from types import * -import unicodedata - -parser ='' -inputfiles = [] - -def help(): - parser.print_help() - - - -def media_arg(string): - #print 'media_arg:', string - try: - fname,ext = string.split('+') - #print fname - if fname == '': - print "### No music file in command line: ", string - raise argparse.ArgumentTypeError("Track file name empty") - - inputfiles.append(string) - except: - if string: - inputfiles.append(string) - pass - - return string - -def loadTracksFromList(fileName): - try: - lines = [line.strip() for line in open(fileName)] - #print lines - global inputfiles - inputfiles = lines - except: - print "Unable to open media filelist", fileName - help() - sys.exit(-1) - -def process(argv): - #print argv - global parser - parser = argparse.ArgumentParser( - prog='jkcreate', - description='JamTrack Packaging Tool', - epilog="Note: Meta file values may be update with title,sku,etc") - - parser.add_argument("-D", "--verbosity", - help="increase output verbosity", - action="store_true") - - parser.add_argument("-t", "--title", metavar='title', type=str, - help="Title of JamTrack. If not specified, the title in the metafile is used.") - - parser.add_argument("-k", "--sku", type=str, - help="The product SKU to use. If not specified, the SKU in the metafile is used.",required=True,) - - parser.add_argument("-c", "--cover", metavar='coverArtFile', type=str, - help="The cover art file. File should be JPEG or PNG.") - - parser.add_argument("-p", "--pkey", metavar='pkey.pem',type=str, - help="The output public key file. File should be .pem",required=True,) - - parser.add_argument("-s", "--skey", metavar='skey.pem', type=str, - help="The output private key file. File should be .pem",required=True,) - - - parser.add_argument("-m", "--meta",metavar='trackMetaFile', type=str, - help="The input JamTrack JSON metafile file. Optional") - - parser.add_argument("-o", "--ofile", metavar='JamTrackPage', type=str, - help="The output JamTrack file (.jkz)",required=True,) - - parser.add_argument("-I", "--infilesList", metavar='musicTrackFileList', type=str, - help=" -I 'file containing tracks'") - - parser.add_argument("-i", "--infiles", metavar='musictrack', type=media_arg, nargs='*', - help=" -i 'Lumme.ogg+Bass Guitar' -i 'Hydrate-Kenny_Beltrey.ogg+Bass Guitar'",) - - - #parser.print_help() - args = parser.parse_args(argv) - #print args - - try: - if args.verbosity: - jkasset.DEBUG_ENABLE = 1 - jkasset.COMPARE_PARSEFILE = True - #args.sku = random.randint(1, 100000000) - except: - pass - - - try: - if args.infilesList: - loadTracksFromList(args.infilesList) - except: - pass - #create json struct from info - metaJson={} - try: - if args.meta: - #try to load json data - try: - json_data=open(args.meta).read() - - metaJson = json.loads(json_data) - if jkasset.DEBUG_ENABLE : - pprint(metaJson) - except: - print 'Error processing json from file: ', args.meta - help() - sys.exit(-1) - except: - print 'No meta file specified' - pass - - jdata={} - mdata=[] - instdata={} - - if any(metaJson): - try: - #pickup the instrument data passed in - if any - instdata = metaJson["TrackInstrument"] - except: - pass - - i = 0 - trackLength = {} - trackLenStats=[] - - #print inputfiles - - if not any(inputfiles) : - print 'No track files specified ' - help() - sys.exit(-1) - - for mediaFileInfo in inputfiles: - #update track name and instrument name - #print mediaFileInfo - mediaFileInfo.strip(); - mlist = mediaFileInfo.split("+") - mediaFileInfo = mlist[0] - - trackName = 'track_%02d' % (i) - i = i + 1 - mdata.append({"name" : mediaFileInfo, "trackName": trackName}) - - try: - minfo = jkmedia.JKTrackInfo(mediaFileInfo) ; - tinfo={}; - tinfo['bitrate']=minfo.bitrate - tinfo['length'] = minfo.trackLength - trackLenStats.append(minfo.trackLength) - trackLength[trackName]= tinfo - except: - print("Cannot find file or read media information for '%s'"% mediaFileInfo) - #help() - sys.exit(-1) - - try: - instdata[trackName]=mlist[1] - except: - if instdata.has_key(trackName) == False: - print "No instrument for track ",trackName - pass - - try: - if args.title: - try: - title = base64.b64encode(args.title) - metaJson['Title']=title - except: - print 'Error processing title : ', title - help() - sys.exit(-1) - - if args.sku: - try: - metaJson['SKU']=args.sku - except: - print 'Error sku' - help() - sys.exit(-1) - - metaJson['METADATA_VER'] = 1 - - except: - #These are both optional.. - pass - - if any(instdata): - metaJson['TrackInstrument'] = instdata - if len (instdata) > len(mdata): - print "Number of instruments ", len (instdata)," in meta data greater than number of tracks ",len(mdata) - help() - sys.exit(-1) - - if any(trackLength): - metaJson['TrackData'] = trackLength - - #dump the meta data to temp file - - metaTmp = tempfile.NamedTemporaryFile(); - metaTmp.write(json.dumps(metaJson)) - metaTmp.seek(0) - - jdata["tracks"] = mdata - jdata["rsa_priv_file"]= args.skey - jdata["rsa_pub_file"]= args.pkey - jdata["container_file"]= args.ofile - - #jdata["jamktrack_info"]= trackMetaFile - jdata["jamktrack_info"]= metaTmp.name - - try: - jdata["coverart"]= args.cover - except: - print 'No cover art specified' - - data = json.dumps(jdata) - #pprint(data) - - fname = 'jt_metadata.json' - fo = open(fname, 'w') - fo.write(data) - fo.close() - - jas = jkasset.JKasset(fname) - - -if __name__ == "__main__": - random.seed() - process(sys.argv[1:]) - - - diff --git a/ruby/lib/py/jam_tracks/jkmedia.py b/ruby/lib/py/jam_tracks/jkmedia.py deleted file mode 100644 index 035abc3a4..000000000 --- a/ruby/lib/py/jam_tracks/jkmedia.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env python -import base64 -import os -import binascii -import json -import sys -from optparse import OptionParser -#from import mutagen.ogg -#from import mutagen.oggvorbis - -class JKTrackInfo: - trackLength =0 - bitrate = 0 - mediaFile=''; - - - def __init__(self, fileName): - #get the file extension - self.mediaFile = fileName - fileName, fileExtension = os.path.splitext(self.mediaFile) - audio = '' - if fileExtension == '.ogg' : - from mutagen.oggvorbis import OggVorbis - audio = OggVorbis(self.mediaFile) - elif fileExtension == '.wav' : - from mutagen.wavpack import WavPack - audio = WavPack(self.mediaFile) - elif fileExtension == '.mp3' : - from mutagen.mp3 import MP3 - audio = MP3(self.mediaFile) - elif fileExtension == '.flac' : - from mutagen.flac import FLAC - audio = FLAC(self.mediaFile) - else: - print "Unsupported filetype '",fileExtension, "' for file ", self.mediaFile - raise Exception("Unsupported filetype") - - print audio.info.length, audio.info.bitrate - self.bitrate = audio.info.bitrate - self.trackLength = audio.info.length - - - - diff --git a/ruby/lib/py/jam_tracks/jkrsa.py b/ruby/lib/py/jam_tracks/jkrsa.py deleted file mode 100755 index 4d0e38600..000000000 --- a/ruby/lib/py/jam_tracks/jkrsa.py +++ /dev/null @@ -1,194 +0,0 @@ -# RSA Cipher -# http://inventwithpython.com/hacking (BSD Licensed) - -from Crypto.Cipher import PKCS1_OAEP -from Crypto.PublicKey import RSA -from Crypto import Random -import base64 -import sys - -class JKrsa: - # IMPORTANT: The block size MUST be less than or equal to the key size! - # (Note: The block size is in bytes, the key size is in bits. There - # are 8 bits in 1 byte.) - #DEFAULT_BLOCK_SIZE = 128-2 # 128 bytes - DEFAULT_BLOCK_SIZE = 64 # 128 bytes - - - RSA_KEYSIZE = 1024 - BYTE_SIZE = 256 # One byte has 256 different values. - RSAKey=''; - PADDING = 0 - PADDING_CHARACTER =b'\0' - encipher=''; - decipher='' - - def __init__(self, operation='encode'): - self.op = operation - - def updatePadSize(self,dlen): - self.PADDING = (self.DEFAULT_BLOCK_SIZE - dlen % self.DEFAULT_BLOCK_SIZE) - - def getPadding(self): - return self.PADDING - - def pad(self,s): - self.updatePadSize(len(s)) - pdata = self.PADDING * self.PADDING_CHARACTER - if len(pdata) != self.PADDING : - sys.exit('Pad data length %d does match %d. Check python pad character size!', - (len(pdata), self.PADDING)) - s = s + pdata - return s - - def EncodeRSA(self,c, s): - #print 'len=',len(s) - estr = self.encipher.encrypt(s) - #print 'elen=',len(estr) - #print estr - return estr - - def DecodeRSA(self,c, e): - #print 'len=',len(e) - str = self.decipher.decrypt(e) - return str - - def generateKey(self, privateFileName='pri_Key.pem', publicFileName='pub_Key.pem'): - self.RSAKey = RSA.generate(self.RSA_KEYSIZE) - - public_key = self.RSAKey.publickey().exportKey("PEM") - private_key = self.RSAKey.exportKey("PEM") - - if privateFileName: - f = open(privateFileName,'w') - f.write(self.RSAKey.exportKey("PEM")) - f.close() - - f = open(privateFileName,'r') - skey = RSA.importKey(f.read()) - self.decipher = PKCS1_OAEP.new(skey) - f.close() - - if publicFileName: - f = open(publicFileName,'w') - f.write(self.RSAKey.publickey().exportKey("PEM")) - f.close() - - f = open(publicFileName,'r') - pkey = RSA.importKey(f.read()) - self.encipher = PKCS1_OAEP.new(pkey) - f.close() - - return private_key, public_key - - def loadKey(self, fileName='pri_Key.pem'): - f = open(fileName,'r') - self.RSAKey = RSA.importKey(f.read()) - self.decipher = PKCS1_OAEP.new(self.RSAKey) - f.close() - - def WriteFileData(self,outfile,data): - #ddata = base64.b64decode(data) - fi = open(outfile,'wb') - fi.write(data); - - def LoadFileData(self, infile): - fi = open(infile,'rb') - data = fi.read(); - #edata = base64.b64encode(data) - return data - - def WriteRSAFileData(self, outfile,data): - # These files are assumed to be in cypher text - so we won't b64 encode/decode them - fo = open(outfile,'wb') - #edata = base64.b64encode(data) - fo.write(data) - - - def LoadRSAFileData(self, infile): - # These files are assumed to be in cypher text - so we won't b64 encode/decode them - fi = open(infile,'rb') - data = fi.read(); - return data - - def getBlocksFromText(self,message, blockSize=DEFAULT_BLOCK_SIZE): - # Converts a string message to a list of block integers. Each integer - # represents 128 (or whatever blockSize is set to) string characters. - # messageBytes = message.encode('ascii') # convert the string to bytes - blockInts = [] - blockInts = [message[i:i+blockSize] for i in range(0, len(message), blockSize)] - return blockInts - - - def encryptMesssage(self, message, blockSize=DEFAULT_BLOCK_SIZE): - keySize = self.RSAKey.size() + 1; - - # Check that key size is greater than block size. - if keySize < blockSize * 8: # * 8 to convert bytes to bits - sys.exit('ERROR: Block size is %d bits and key size is %d bits.' - ' The RSA cipher requires the block size to be equal to' - ' or less than the key size. Either decrease the' - ' block size or use different keys.' % (blockSize * 8, keySize)) - - if len(message) % blockSize : # * 8 to convert bytes to bits - sys.exit('ERROR: Block size is %d and data size is %d .' - ' The RSA cipher requires the block size to be equal to' - ' or less than the key size. Either decrease the' - ' block size or use different keys.' % (blockSize, len(message))) - - - encryptedContent = [] - for block in self.getBlocksFromText(message, blockSize): - encryptedContent_ = self.EncodeRSA(self.RSAKey,block); - encryptedContent.extend(encryptedContent_) - - return encryptedContent - - def decryptMesssage(self, message): - #print message - #split in equal chunks - decryptedContent = [] - for block in self.getBlocksFromText(message, self.DEFAULT_BLOCK_SIZE*2): - decryptedContent_ = self.DecodeRSA(self.RSAKey,block); - decryptedContent.extend(decryptedContent_) - - return decryptedContent - - - def encryptAndWriteToFile(self, keyFilename, inFile,outFilename, blockSize=DEFAULT_BLOCK_SIZE): - # Using a key from a key file, encrypt the message and save it to a - # file. Returns the encrypted message string. - self.loadKey(keyFilename) - keySize = self.RSAKey.size()+1; - - # Check that key size is greater than block size. - if keySize < blockSize * 8: # * 8 to convert bytes to bits - sys.exit('ERROR: Block size is %s bits and key size is %s bits.' - ' The RSA cipher requires the block size to be equal to' - ' or less than the key size. Either decrease the' - ' block size or use different keys.' % (blockSize * 8, keySize)) - - # Encrypt the message - message = self.LoadFileData(inFile); - message = self.pad(message) - - encryptedContent = self.encryptMesssage(message); - self.WriteRSAFileData(outFilename,encryptedContent) - # Also return the encrypted string. - #return encryptedContent - - - def readFromFileAndDecrypt(self, keyFilename, inFile, outFilename): - # Using a key from a key file, read an encrypted message from a file - # and then decrypt it. Returns the decrypted message string. - self.loadKey(keyFilename) - - # Read in the message length and the encrypted message from the file. - message = self.LoadRSAFileData(inFile) - - decryptMessage = self.decryptMesssage(message) - self.WriteFileData(outFilename,decryptMessage) - - - - diff --git a/ruby/lib/py/jam_tracks/jktrack.py b/ruby/lib/py/jam_tracks/jktrack.py deleted file mode 100755 index 3e0dcd4b3..000000000 --- a/ruby/lib/py/jam_tracks/jktrack.py +++ /dev/null @@ -1,104 +0,0 @@ -import jkaes -import tempfile -from jkaes import JKaes -import os -class JKtrack: - mediaFile = '' - Name = '' - aesInKeyFile='' - aesCipher = '' - outTmpFile ='' - outPutFile = '' - def __init__(self, fileName, title='Unknown', inKeyFile=''): - self.mediaFile = fileName - self.Name = title - self.aesInKeyFile = inKeyFile - self.aesCipher = JKaes(self.mediaFile, self.aesInKeyFile) - - def getOutputFileName(self): - return self.outPutFile; - - def getKeyStr(self): - return self.aesCipher.getKeyStr(); - - def getEncryptPadding(self): - return self.aesCipher.getPadding() - - def setKeyStr(self,key): - self.aesCipher.setKey(key); - - def getKeyModeStr(self): - return self.aesCipher.getKeyModeStr() - - def getCoverEncryptFilePackageName(self,instance=-1): - #preserve the extentsion on the original name - fileName, fileExtension = os.path.splitext(self.mediaFile) - if instance != -1: - nm = "cover_%02d%s" % (instance,fileExtension) - return nm - else: - nm = os.path.basename(self.mediaFile) - return nm - - def getEncryptFilePackageName(self,instance=-1): - #preserve the extentsion on the original name - fileName, fileExtension = os.path.splitext(self.mediaFile) - if instance != -1: - nm = "track_%02d%s" % (instance,fileExtension) - return nm - else: - nm = os.path.basename(self.mediaFile) - return nm - - def Encode(self, outFile): - self.outPutFile = outFile - self.aesCipher.EncodeFileAndWrite( self.mediaFile, outFile) - - def EncodeToTmpFile(self): - self.outTmpFile = tempfile.NamedTemporaryFile() - self.Encode(self.outTmpFile.name) - - def EncodeWithKeyToFile(self,keyFile,outFile): - self.aesCipher.readAesKeyFile(keyFile) - self.Encode(outFile) - - def Decode(self, outFile): - self.outPutFile = outFile - self.aesCipher.DecodeFileAndWrite( self.mediaFile, outFile) - - def DecodeToTmpFile(self): - self.outTmpFile = tempfile.NamedTemporaryFile() - self.Decode(self.outTmpFile.name) - - def DecodeWithKeyToFile(self,keyFile,outFile): - self.aesCipher.readAesKeyFile(keyFile) - self.Decode(outFile) - - - def DecodeBufferToFile(self,msg,outfile,padding=0): - dmsg = self.aesCipher.DecodeMsg(msg) - fi = open(outfile,'wb') - fi.write(dmsg); - if padding > 0: - #trunckage the file by - fi.seek(-padding, os.SEEK_END) - fi.truncate() - fi.close() - return dmsg - - def DecodeU64Buffer(self,msg): - return self.aesCipher.DecodeU64Msg(msg) - - def setKey(self, keyFile): - self.aesCipher.readAesKeyFile(keyFile) - - def saveKey(self,keyFile): - self.aesCipher.writeAesKeytoFile(keyFile) - - def saveEncFileCopy(self,outfile): - fi = open(self.outPutFile,'rb') - fi.seek(0) - dmsg = fi.read(); - fo = open(outfile,'wb') - fo.write(dmsg); - return dmsg \ No newline at end of file diff --git a/ruby/lib/py/jam_tracks/jktrack.pyc b/ruby/lib/py/jam_tracks/jktrack.pyc deleted file mode 100644 index d065cbbe6aee5c45bc364f9d9dba2207c51113bd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5712 zcmc&&>uwuG6h3P^cc)3yB)tJDl!6hoE)^6(xs^0XrD~`qsY*j7Yvb9Z>&x2OowN-~ zeJGux2C{o}8Dk4FTbI}78`cdu2hzUbmkbMv~PFnV{I+2sJ zK1tFzGbMIZfwI^!1*XMf|Ag#=!BYxPDm){0N;k}kEh}(Z?6d-NVoxb>M(m6N^I~Te zSP+YCb9%y}*fTnLR_we2=fo~3a9-@90vE)rR?dP|hbAhKheGsA>wH0qvyE~k%sg-2aMpJ=n-5o_= ztHnv|YBds{ksb*q^r_R-dQGKAW20mQCdq}|8YcmEw%vnmN2Ybf@Sq_Qej3MbMefIK z*Y#K_z&T821#}X;O8YdXJ?)KvzSZM^`wbaWq2o;GZq=nH!G)Ot+6A=#Ks03!q926T z#>v)6Q0;QrFDLbMr`yAP?f@GUX|Ji>%wwW2d$`|TpkeP-6)?_as)CB@g;oTg6lq2~ zL$oHkn@0=HU*v$;|D6@l&b$@f@7U<1EQwb17FtfY=88I*U`(L^RS!P_K#+t3Ah=N> zMXKUSL0;mA(1!#NNV6#EEv5m+$)ao)WbtK5c1yCFzluTc)qKIyorq;c5oZh~_0F>> ztzEf(!(PdJ&?GsXOk*FTmIj}!+lpQE!Wj*VRQE=hMG2f)-_bCl8{;HH#H&Y!x^fgU z!{+h}7bRKTNgNMP+C#JvN*8)vB3rrXGuk~s;tjVne!q0ih|5phb z=npEvEP_bGWNGNJDt2=o&_LYg0v9M|Lsu|wa20JxDTv4+*Vl{mLoB{tq||rct}kOS z$m)j;oZ-e-S-o&WQ!}(6ANH9wfE~_Cecn#&qZB5ok~t8Bm?4nkG{FHJk}w$mJT0FB z%19}*aX*`pYxU&!0C;!Y)6iP$`7>R!AdS+|7^}4f{mVt|E?viwZ)Q!qhM7(prdqPN zJejYxyX4i-sMTfFCz2N%3cJvS<2H?4E+2wGPpKh3TB} zSszM{*Y58`e`ELNQjcY2{@4pp8oVds_JGx6GkEh34mFqAy~~c_#9TplB6SiC+4I&n z{qt^<}WzB17Y z;rWIVRG9$&LDBD-xSwrlpzCy_L@(ISnFsQ#TGZ6csiny3^76gf^2!|@xO6+IhuFse zi)&XF>1Evq5c1o`v3c^)uuHh>t93owas$13Gah1mVX*gXez(238KpkFtY9{qMJK^r zFcVA%<-$Dr$w8>;$5`$*CA!>3UAcc;*P%x}K$y!F;jRpsw znbC=2?40pTM -s ' - - -def process(argv): - - global parser - parser = argparse.ArgumentParser( - prog='jkunpack', - description='JamTrack UnPackaging Tool', - epilog="Note: Files may be overwritten during unpacking") - - parser.add_argument("-D", "--verbosity", - help="increase output verbosity", - action="store_true") - - parser.add_argument("-s", "--skey", metavar='skey.pem', type=str, - help="The private key file. File should be .pem",required=True) - - parser.add_argument("-i", "--ifile", metavar='JamTrackPage', type=str, - help="The input JamTrack file (.jkz)",required=True) - - parser.print_help() - args = parser.parse_args(argv) - print args - - try: - if args.verbosity: - jkasset.DEBUG_ENABLE = 1 - jkasset.COMPARE_PARSEFILE = True - except: - pass - - - jas = jkasset.JKasset(args.ifile,'decode',args.skey) - -if __name__ == "__main__": - random.seed() - process(sys.argv[1:]) - - - diff --git a/ruby/lib/py/jam_tracks/jkzify.rb b/ruby/lib/py/jam_tracks/jkzify.rb deleted file mode 100755 index c5ef594fd..000000000 --- a/ruby/lib/py/jam_tracks/jkzify.rb +++ /dev/null @@ -1,98 +0,0 @@ -#!/usr/bin/env ruby -# -# Usage: jkzify [path to JamTrack WAVs] [destination for jkz and key files] -# -# Please review the comments, this script needs improvement -- I just threw it together to help me validate jkcreate.py -# - -wd = Dir.getwd -tracks_location = ARGV.shift -output_location = ARGV.shift -script_location = File.dirname(File.expand_path(__FILE__)) - - - -# first try to catch any issues with input arguments -def halt msg; warn msg; exit; end -halt "usage: jkzify path/to/wavs/ path/to/output" unless tracks_location && output_location -halt "path not found" unless File.exist?(tracks_location) && File.exist?(output_location) -# end - - - -# step 1: gather metadata from the track/folder names (and user input) -title = tracks_location.split('/').last.gsub(/'/,'') -puts "i think this track is called \"#{title}\"..." -shortname = title.gsub(/[aeiou\s]/,'').squeeze[0...8].downcase -puts "i'll call it #{shortname} for now" -puts "what sku do you want for this JamTrack? (press ENTER for '#{shortname}')" -input = STDIN.gets.chomp.strip -sku = input.empty? ? shortname : input - - - -# step 2: move WAVs to our own workspace -def renamed(t); t.gsub(/\s|'/,'_'); end #little helper - -Dir.chdir wd -`rm -rf #{shortname}` if File.exist? shortname -`mkdir #{shortname}` - -Dir.chdir tracks_location -Dir.glob('*.wav') do |track| - new_name = renamed track - `cp "#{track}" "#{wd}/#{shortname}/#{new_name}"` - puts "Copied from " + track + " to #{wd}/#{shortname}/" + new_name -end -Dir.chdir "#{wd}/#{shortname}" - - - -# step 3: generate OGGs from WAVs -oggs_location = title.downcase.gsub('\'','').split.join('_') + '_oggs/' -puts "generating OGGs in a separate folder #{oggs_location} (this may take some time)" -`mkdir #{oggs_location}` unless File.exist?(oggs_location) -Dir.glob('*.wav') do |track| - input = track - output = "#{oggs_location}#{File.basename(track, '.wav')}.ogg" - #puts "sox #{input} #{output}" - `sox #{input} #{output}` -end - - -# step 4: get all track parameters (names and instruments) -track_params = {} -track_files = [] -suffix = '\.wav' -Dir.foreach tracks_location do |track| - next if track =~ /Master Mix/i - next unless track =~ /#{suffix}$/i - print '.' - track.gsub!(/#{suffix}$/i, '') - instrument = track.split('-').drop(1).map(&:strip).join(' ') - parameter = "#{shortname}/#{oggs_location}#{renamed track}.ogg+#{instrument}" - #parameter.gsub!(/ /, '\ ') #spaces in filenames - track_params[instrument] = "-i '#{parameter}'" - track_files << parameter -end -puts "OK, #{track_params.length} tracks found" - - - -# step 5: create list of track titles -Dir.chdir wd -tracks_filename = "#{shortname}.txt" -File.open(tracks_filename, 'w+') { |f| f.puts track_files } - - - -# step 6: run jkcreate.py given all these parameters -public_key = "#{output_location}#{shortname}-pub.pem" -private_key = "#{output_location}#{shortname}-priv.pem" -output_jkz = "#{output_location}#{shortname}.jkz" -#puts "python jkcreate.py -D -k #{sku} -c art.png -p #{public_key} -s #{private_key} -I #{tracks_filename} -o #{output_jkz} -t '#{title}'" -`python jkcreate.py -D -k #{sku} -c art.png -p #{public_key} -s #{private_key} -I #{tracks_filename} -o #{output_jkz} -t '#{title}'` - - - -Dir.chdir wd \ No newline at end of file From aae589944db781672af7b931632b919e6e10ef9c Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Sun, 14 Dec 2014 20:20:40 -0600 Subject: [PATCH 30/72] VRFS-2481 : Create test for JKZ method. Some cleanup. --- ruby/lib/jam_ruby/jam_tracks_manager.rb | 18 ++++++++++++++---- .../jam_ruby/models/jam_track_right_spec.rb | 9 +++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/ruby/lib/jam_ruby/jam_tracks_manager.rb b/ruby/lib/jam_ruby/jam_tracks_manager.rb index 29116aaf7..4bd6c7c78 100644 --- a/ruby/lib/jam_ruby/jam_tracks_manager.rb +++ b/ruby/lib/jam_ruby/jam_tracks_manager.rb @@ -1,11 +1,16 @@ +require 'json' +require 'tempfile' + module JamRuby - # describes an audio track (like the drums, or guitar) that comprises a JamTrack + # Interact with external python tools to create the JKZ class JamTracksManager class << self - def save_jam_track(jam_track, user) + def save_jam_track_jkz(jam_track, user) py_root = File.expand_path(File.join("..", "..", "jamtracks")) - puts "Executing python in #{py_root}" + tmp_dir = Dir::Tmpname.make_tmpname("/tmp/jamtrack", nil) + puts "Executing python in #{py_root}, outputting to #{tmp_dir}" + sku=""#jam_track.sku public_key="" @@ -13,7 +18,12 @@ module JamRuby tracks_filename="" output_jkz="" title="" - `python #{py_root}/jkcreate.py -D -k #{sku} -c art.png -p #{public_key} -s #{private_key} -I #{tracks_filename} -o #{output_jkz} -t '#{title}'` + + output = `python #{py_root}/jkcreate.py -D -k #{sku} -c art.png -p #{public_key} -s #{private_key} -I #{tracks_filename} -o #{output_jkz} -t '#{title}'` + puts "#{output} :: #{!!output.index(/Error/i)}" + raise ArgumentError, "Error calling python script: #{output}" if !!output.index(/Error.*:/i) + + tmp_dir end end end diff --git a/ruby/spec/jam_ruby/models/jam_track_right_spec.rb b/ruby/spec/jam_ruby/models/jam_track_right_spec.rb index 5d136d62a..d7aadd34d 100644 --- a/ruby/spec/jam_ruby/models/jam_track_right_spec.rb +++ b/ruby/spec/jam_ruby/models/jam_track_right_spec.rb @@ -27,5 +27,14 @@ describe JamTrackRight do right_2.errors[:user_id].should == ['has already been taken'] end end + + it "saves JKZ" do + user = FactoryGirl.create(:user) + jam_track = FactoryGirl.create(:jam_track) + right = JamTrackRight.create(:user=>user, :jam_track=>jam_track) + + JamRuby::JamTracksManager.save_jam_track_jkz(user, jam_track) + end + end From 4f161c44e3368b4bf2101eed0640d8ed950ac2a4 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Sun, 14 Dec 2014 21:40:03 -0600 Subject: [PATCH 31/72] VRFS-2481 : Better implementation of external script. Getting proper stdout and stderr, as well as a clean implicit shutdown. --- ruby/lib/jam_ruby/jam_tracks_manager.rb | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/ruby/lib/jam_ruby/jam_tracks_manager.rb b/ruby/lib/jam_ruby/jam_tracks_manager.rb index 4bd6c7c78..38a4c5eab 100644 --- a/ruby/lib/jam_ruby/jam_tracks_manager.rb +++ b/ruby/lib/jam_ruby/jam_tracks_manager.rb @@ -1,5 +1,6 @@ require 'json' require 'tempfile' +require 'open3' module JamRuby @@ -19,10 +20,15 @@ module JamRuby output_jkz="" title="" - output = `python #{py_root}/jkcreate.py -D -k #{sku} -c art.png -p #{public_key} -s #{private_key} -I #{tracks_filename} -o #{output_jkz} -t '#{title}'` - puts "#{output} :: #{!!output.index(/Error/i)}" - raise ArgumentError, "Error calling python script: #{output}" if !!output.index(/Error.*:/i) - + # From http://stackoverflow.com/questions/690151/getting-output-of-system-calls-in-ruby/5970819#5970819: + #Open3.popen3("ls") do |stdin, stdout, stderr, wait_thr| + Open3.popen3("python", "#{py_root}/jkcreate.py -D -k #{sku} -c art.png -p #{public_key} -s #{private_key} -I #{tracks_filename} -o #{output_jkz} -t '#{title}'") do |stdin, stdout, stderr, wait_thr| + pid = wait_thr.pid + exit_status = wait_thr.value + err = stderr.read(1000) + raise ArgumentError, "Error calling python script: #{err}" if err.present? + end + tmp_dir end end From 8be471fc99e81281afc86134f65765567c9b8090 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Mon, 15 Dec 2014 11:59:07 -0600 Subject: [PATCH 32/72] Better error reporting. Test now currently fails as expected. --- ruby/lib/jam_ruby/jam_tracks_manager.rb | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/ruby/lib/jam_ruby/jam_tracks_manager.rb b/ruby/lib/jam_ruby/jam_tracks_manager.rb index 38a4c5eab..416516a48 100644 --- a/ruby/lib/jam_ruby/jam_tracks_manager.rb +++ b/ruby/lib/jam_ruby/jam_tracks_manager.rb @@ -7,22 +7,20 @@ module JamRuby # Interact with external python tools to create the JKZ class JamTracksManager class << self - def save_jam_track_jkz(jam_track, user) + def save_jam_track_jkz(user, jam_track) py_root = File.expand_path(File.join("..", "..", "jamtracks")) tmp_dir = Dir::Tmpname.make_tmpname("/tmp/jamtrack", nil) puts "Executing python in #{py_root}, outputting to #{tmp_dir}" - sku=""#jam_track.sku - public_key="" - private_key="" - tracks_filename="" - output_jkz="" - title="" + sku=jam_track.id + tracks_filename=tmp_dir + "/jamtrack.info" + output_jkz="tmp_dir/#{jam_track.name.tableize}.jkz" + title=jam_track.name # From http://stackoverflow.com/questions/690151/getting-output-of-system-calls-in-ruby/5970819#5970819: #Open3.popen3("ls") do |stdin, stdout, stderr, wait_thr| - Open3.popen3("python", "#{py_root}/jkcreate.py -D -k #{sku} -c art.png -p #{public_key} -s #{private_key} -I #{tracks_filename} -o #{output_jkz} -t '#{title}'") do |stdin, stdout, stderr, wait_thr| + Open3.popen3("python", "#{py_root}/jkcreate.py", "-D -k #{sku} -c art.png -p #{tmp_dir}/pkey.pem -s #{tmp_dir}/skey.pem -I #{tracks_filename} -o #{output_jkz} ") do |stdin, stdout, stderr, wait_thr| pid = wait_thr.pid exit_status = wait_thr.value err = stderr.read(1000) From 6a758532dc0329e9ed9866fbab28819bdf1be22d Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Mon, 15 Dec 2014 12:08:37 -0600 Subject: [PATCH 33/72] VRFS-2481 : Code and spec cleanup --- ruby/lib/jam_ruby/jam_tracks_manager.rb | 5 ++++- ruby/spec/jam_ruby/models/jam_track_right_spec.rb | 5 +++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/ruby/lib/jam_ruby/jam_tracks_manager.rb b/ruby/lib/jam_ruby/jam_tracks_manager.rb index 416516a48..1c4b481aa 100644 --- a/ruby/lib/jam_ruby/jam_tracks_manager.rb +++ b/ruby/lib/jam_ruby/jam_tracks_manager.rb @@ -20,7 +20,10 @@ module JamRuby # From http://stackoverflow.com/questions/690151/getting-output-of-system-calls-in-ruby/5970819#5970819: #Open3.popen3("ls") do |stdin, stdout, stderr, wait_thr| - Open3.popen3("python", "#{py_root}/jkcreate.py", "-D -k #{sku} -c art.png -p #{tmp_dir}/pkey.pem -s #{tmp_dir}/skey.pem -I #{tracks_filename} -o #{output_jkz} ") do |stdin, stdout, stderr, wait_thr| + Open3.popen3("python", + "#{py_root}/jkcreate.py", + "-D -k #{sku} -c art.png -p #{tmp_dir}/pkey.pem -s #{tmp_dir}/skey.pem -I #{tracks_filename} -o #{output_jkz} -t '#{title}'" + ) do |stdin, stdout, stderr, wait_thr| pid = wait_thr.pid exit_status = wait_thr.value err = stderr.read(1000) diff --git a/ruby/spec/jam_ruby/models/jam_track_right_spec.rb b/ruby/spec/jam_ruby/models/jam_track_right_spec.rb index d7aadd34d..1df21358f 100644 --- a/ruby/spec/jam_ruby/models/jam_track_right_spec.rb +++ b/ruby/spec/jam_ruby/models/jam_track_right_spec.rb @@ -32,8 +32,9 @@ describe JamTrackRight do user = FactoryGirl.create(:user) jam_track = FactoryGirl.create(:jam_track) right = JamTrackRight.create(:user=>user, :jam_track=>jam_track) - - JamRuby::JamTracksManager.save_jam_track_jkz(user, jam_track) + expect { + JamRuby::JamTracksManager.save_jam_track_jkz(user, jam_track) + }.to_not raise_error end end From f6fef00ea55741fb54c60aab15bd369b1b07fb42 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Sun, 14 Dec 2014 18:18:04 -0600 Subject: [PATCH 34/72] VRFS-1952 : Schema changes for proper type and incremental code changes. WIP. --- db/up/jam_track_updates.sql | 13 ++++++++++--- ruby/lib/jam_ruby/models/jam_track.rb | 13 +++++++++++++ ruby/lib/jam_ruby/models/jam_track_right.rb | 8 ++++++++ ...s_controller.rb => api_jam_tracks_controller.rb} | 0 4 files changed, 31 insertions(+), 3 deletions(-) rename web/app/controllers/{api_jamtracks_controller.rb => api_jam_tracks_controller.rb} (100%) diff --git a/db/up/jam_track_updates.sql b/db/up/jam_track_updates.sql index 1c42254ed..8eaaf1bd6 100644 --- a/db/up/jam_track_updates.sql +++ b/db/up/jam_track_updates.sql @@ -8,10 +8,11 @@ ALTER TABLE jam_track_rights DROP CONSTRAINT jam_track_rights_jam_track_id_fkey; -- Change Jam Tracks ID type to BIGINT so it can work like the other downloadable items: +CREATE SEQUENCE jam_tracks_next_seq; ALTER TABLE jam_tracks ALTER COLUMN id DROP DEFAULT, - ALTER COLUMN id TYPE BIGINT USING nextval('tracks_next_tracker_seq'), - ALTER COLUMN id SET DEFAULT nextval('tracks_next_tracker_seq'); + ALTER COLUMN id TYPE BIGINT USING nextval('jam_tracks_next_seq'), + ALTER COLUMN id SET DEFAULT nextval('jam_tracks_next_seq'); -- Change referencing ID type and re-add constraints: ALTER TABLE jam_track_tracks @@ -22,7 +23,13 @@ ALTER TABLE jam_track_tracks -- Change referencing ID type and re-add constraints. Also -- add S3 URL for user-specific downloads: ALTER TABLE jam_track_rights - ADD COLUMN s3_url VARCHAR(2048), + ALTER COLUMN id DROP DEFAULT, + ALTER COLUMN id TYPE BIGINT USING nextval('tracks_next_tracker_seq'), + ALTER COLUMN id SET DEFAULT nextval('tracks_next_tracker_seq'), + ADD COLUMN url VARCHAR(2048), + ADD COLUMN md5 VARCHAR, + ADD COLUMN length INTEGER NOT NULL DEFAULT 0, + ADD COLUMN download_count INTEGER NOT NULL DEFAULT 0, ALTER COLUMN jam_track_id TYPE BIGINT USING 0, ALTER COLUMN jam_track_id SET NOT NULL, ADD CONSTRAINT jam_track_rights_user_id_fkey FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, diff --git a/ruby/lib/jam_ruby/models/jam_track.rb b/ruby/lib/jam_ruby/models/jam_track.rb index 38b1a9e5d..f93087046 100644 --- a/ruby/lib/jam_ruby/models/jam_track.rb +++ b/ruby/lib/jam_ruby/models/jam_track.rb @@ -96,6 +96,19 @@ module JamRuby end end + def self.list_downloads(user, limit = 100, since = 0) + since = 0 unless since || since == '' # guard against nil + downloads = [] + + user.jam_track_rights.limit(limit).collect do |jt_right| + downloads << jt_right + end + + #JamTrack.joins(:recording).joins(:recording => :claimed_recordings) + end + + + private def sanitize_active_admin diff --git a/ruby/lib/jam_ruby/models/jam_track_right.rb b/ruby/lib/jam_ruby/models/jam_track_right.rb index f06c823e6..57c0a444f 100644 --- a/ruby/lib/jam_ruby/models/jam_track_right.rb +++ b/ruby/lib/jam_ruby/models/jam_track_right.rb @@ -8,7 +8,15 @@ module JamRuby validates :user, presence:true validates :jam_track, presence:true + validate :verify_download_count validates_uniqueness_of :user_id, scope: :jam_track_id + MAX_JAM_TRACK_DOWNLOADS = 1000 + + def verify_download_count + if (self.download_count < 0 || self.download_count > MAX_JAM_TRACK_DOWNLOADS) && !@current_user.admin + errors.add(:download_count, "must be less than or equal to #{MAX_JAM_TRACK_DOWNLOADS}") + end + end end end diff --git a/web/app/controllers/api_jamtracks_controller.rb b/web/app/controllers/api_jam_tracks_controller.rb similarity index 100% rename from web/app/controllers/api_jamtracks_controller.rb rename to web/app/controllers/api_jam_tracks_controller.rb From ac2daed4daac86b85db53af82ea6eadb47b4c941 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Sun, 14 Dec 2014 19:42:38 -0600 Subject: [PATCH 35/72] VRFS-2481 : Jam Tracks python layer and source. --- ruby/bin/jamtrack.rb | 1 + ruby/lib/jam_ruby.rb | 2 + .../app/uploaders/jam_track_right_uploader.rb | 28 ++ ruby/lib/jam_ruby/jam_tracks_manager.rb | 22 + ruby/lib/jam_ruby/models/jam_track_right.rb | 12 +- ruby/lib/py/jam_tracks/jamtrack.info | 1 + ruby/lib/py/jam_tracks/jamtracklist.txt | 8 + ruby/lib/py/jam_tracks/jkaes.py | 212 +++++++++ ruby/lib/py/jam_tracks/jkaes.pyc | Bin 0 -> 9156 bytes ruby/lib/py/jam_tracks/jkasset.py | 431 ++++++++++++++++++ ruby/lib/py/jam_tracks/jkasset.pyc | Bin 0 -> 11420 bytes ruby/lib/py/jam_tracks/jkcreate.py | 249 ++++++++++ ruby/lib/py/jam_tracks/jkmedia.py | 44 ++ ruby/lib/py/jam_tracks/jkrsa.py | 194 ++++++++ ruby/lib/py/jam_tracks/jktrack.py | 104 +++++ ruby/lib/py/jam_tracks/jktrack.pyc | Bin 0 -> 5712 bytes ruby/lib/py/jam_tracks/jkunpack.py | 56 +++ ruby/lib/py/jam_tracks/jkzify.rb | 98 ++++ 18 files changed, 1461 insertions(+), 1 deletion(-) create mode 100644 ruby/bin/jamtrack.rb create mode 100644 ruby/lib/jam_ruby/app/uploaders/jam_track_right_uploader.rb create mode 100644 ruby/lib/jam_ruby/jam_tracks_manager.rb create mode 100644 ruby/lib/py/jam_tracks/jamtrack.info create mode 100644 ruby/lib/py/jam_tracks/jamtracklist.txt create mode 100755 ruby/lib/py/jam_tracks/jkaes.py create mode 100644 ruby/lib/py/jam_tracks/jkaes.pyc create mode 100755 ruby/lib/py/jam_tracks/jkasset.py create mode 100644 ruby/lib/py/jam_tracks/jkasset.pyc create mode 100755 ruby/lib/py/jam_tracks/jkcreate.py create mode 100644 ruby/lib/py/jam_tracks/jkmedia.py create mode 100755 ruby/lib/py/jam_tracks/jkrsa.py create mode 100755 ruby/lib/py/jam_tracks/jktrack.py create mode 100644 ruby/lib/py/jam_tracks/jktrack.pyc create mode 100755 ruby/lib/py/jam_tracks/jkunpack.py create mode 100755 ruby/lib/py/jam_tracks/jkzify.rb diff --git a/ruby/bin/jamtrack.rb b/ruby/bin/jamtrack.rb new file mode 100644 index 000000000..2152352db --- /dev/null +++ b/ruby/bin/jamtrack.rb @@ -0,0 +1 @@ +{"SKU": "0044454545454", "METADATA_VER": 1, "TrackData": {"track_2": {"length": 0.1, "bitrate": 96000}, "track_1": {"length": 264.5681632653061, "bitrate": 128003}, "track_0": {"length": 405.054693877551, "bitrate": 128003}}, "TrackInstrument": {"track_1": "Alto Sax", "track_0": "Bass Guitar"}, "Title": "SmFtS2F6YW0gcm9sbGluZyBpbiB0aGUgZG91Z2g="} \ No newline at end of file diff --git a/ruby/lib/jam_ruby.rb b/ruby/lib/jam_ruby.rb index 07234042f..b3b1a3ee1 100755 --- a/ruby/lib/jam_ruby.rb +++ b/ruby/lib/jam_ruby.rb @@ -74,6 +74,7 @@ require "jam_ruby/app/uploaders/mix_uploader" require "jam_ruby/app/uploaders/music_notation_uploader" require "jam_ruby/app/uploaders/jam_track_uploader" require "jam_ruby/app/uploaders/jam_track_track_uploader" +require "jam_ruby/app/uploaders/jam_track_right_uploader" require "jam_ruby/app/uploaders/max_mind_release_uploader" require "jam_ruby/lib/desk_multipass" require "jam_ruby/lib/ip" @@ -189,6 +190,7 @@ require "jam_ruby/models/jam_company" require "jam_ruby/models/user_sync" require "jam_ruby/models/video_source" require "jam_ruby/models/recorded_video" +require "jam_ruby/jam_tracks_manager" include Jampb diff --git a/ruby/lib/jam_ruby/app/uploaders/jam_track_right_uploader.rb b/ruby/lib/jam_ruby/app/uploaders/jam_track_right_uploader.rb new file mode 100644 index 000000000..e33494063 --- /dev/null +++ b/ruby/lib/jam_ruby/app/uploaders/jam_track_right_uploader.rb @@ -0,0 +1,28 @@ +class JamTrackRightUploader < CarrierWave::Uploader::Base + # include CarrierWaveDirect::Uploader + include CarrierWave::MimeTypes + + process :set_content_type + + def initialize(*) + super + JamRuby::UploaderConfiguration.set_aws_private_configuration(self) + end + + # Add a white list of extensions which are allowed to be uploaded. + def extension_white_list + %w(jkz) + end + + def store_dir + nil + end + + def md5 + @md5 ||= ::Digest::MD5.file(current_path).hexdigest + end + + def filename + "#{model.store_dir}/#{model.filename}" if model.id + end +end diff --git a/ruby/lib/jam_ruby/jam_tracks_manager.rb b/ruby/lib/jam_ruby/jam_tracks_manager.rb new file mode 100644 index 000000000..07b264df0 --- /dev/null +++ b/ruby/lib/jam_ruby/jam_tracks_manager.rb @@ -0,0 +1,22 @@ +module JamRuby + + # describes an audio track (like the drums, or guitar) that comprises a JamTrack + class JamTracksManager + class << self + def save_jam_track(jam_track, user) + spec = Gem::Specification.find_by_name("jam_ruby") + py_root = spec.gem_dir + "/lib/py/jam_tracks/" + puts "Executing python in #{py_root}" + + sku=""#jam_track.sku + public_key="" + private_key="" + tracks_filename="" + output_jkz="" + title="" + `python #{py_root}jkcreate.py -D -k #{sku} -c art.png -p #{public_key} -s #{private_key} -I #{tracks_filename} -o #{output_jkz} -t '#{title}'` + end + end + end + +end diff --git a/ruby/lib/jam_ruby/models/jam_track_right.rb b/ruby/lib/jam_ruby/models/jam_track_right.rb index 57c0a444f..33a036a05 100644 --- a/ruby/lib/jam_ruby/models/jam_track_right.rb +++ b/ruby/lib/jam_ruby/models/jam_track_right.rb @@ -2,7 +2,7 @@ module JamRuby # describes what users have rights to which tracks class JamTrackRight < ActiveRecord::Base - attr_accessible :user, :jam_track, :user_id, :jam_track_id + attr_accessible :user, :jam_track, :user_id, :jam_track_id, :url, :md5, :length, :download_count belongs_to :user, class_name: "JamRuby::User" # the owner, or purchaser of the jam_track belongs_to :jam_track, class_name: "JamRuby::JamTrack" @@ -11,6 +11,10 @@ module JamRuby validate :verify_download_count validates_uniqueness_of :user_id, scope: :jam_track_id + + # Uploads the JKZ: + mount_uploader :url, JamTrackRightUploader + MAX_JAM_TRACK_DOWNLOADS = 1000 def verify_download_count @@ -18,5 +22,11 @@ module JamRuby errors.add(:download_count, "must be less than or equal to #{MAX_JAM_TRACK_DOWNLOADS}") end end + + # Create user-specific JKZ for the associated jam_track: + def create_jkz + + end + end end diff --git a/ruby/lib/py/jam_tracks/jamtrack.info b/ruby/lib/py/jam_tracks/jamtrack.info new file mode 100644 index 000000000..2152352db --- /dev/null +++ b/ruby/lib/py/jam_tracks/jamtrack.info @@ -0,0 +1 @@ +{"SKU": "0044454545454", "METADATA_VER": 1, "TrackData": {"track_2": {"length": 0.1, "bitrate": 96000}, "track_1": {"length": 264.5681632653061, "bitrate": 128003}, "track_0": {"length": 405.054693877551, "bitrate": 128003}}, "TrackInstrument": {"track_1": "Alto Sax", "track_0": "Bass Guitar"}, "Title": "SmFtS2F6YW0gcm9sbGluZyBpbiB0aGUgZG91Z2g="} \ No newline at end of file diff --git a/ruby/lib/py/jam_tracks/jamtracklist.txt b/ruby/lib/py/jam_tracks/jamtracklist.txt new file mode 100644 index 000000000..6fa0a517d --- /dev/null +++ b/ruby/lib/py/jam_tracks/jamtracklist.txt @@ -0,0 +1,8 @@ +You Shook Me All Night Long/You Shook Me All Night Long Stem - Bass.ogg+Bass +You Shook Me All Night Long/You Shook Me All Night Long Stem - Drums.ogg+Drums +You Shook Me All Night Long/You Shook Me All Night Long Stem - Guitar - Main.ogg+Guitar Main +You Shook Me All Night Long/You Shook Me All Night Long Stem - Guitar - Rhythm 2.ogg+Guitar Rhythm 2 +You Shook Me All Night Long/You Shook Me All Night Long Stem - Guitar - Rhythm.ogg+Guitar Rhythm +You Shook Me All Night Long/You Shook Me All Night Long Stem - Guitar - Solo.ogg+Guitar Solo +You Shook Me All Night Long/You Shook Me All Night Long Stem - Vocals - Background.ogg+Vocals Background +You Shook Me All Night Long/You Shook Me All Night Long Stem - Vocals - Lead.ogg+Vocals Lead diff --git a/ruby/lib/py/jam_tracks/jkaes.py b/ruby/lib/py/jam_tracks/jkaes.py new file mode 100755 index 000000000..49966001f --- /dev/null +++ b/ruby/lib/py/jam_tracks/jkaes.py @@ -0,0 +1,212 @@ +#!/usr/bin/env python + +from Crypto.Cipher import AES +from Crypto import Random +import base64 +import os +import binascii +import json +import sys + +class JKaes: + + # the key size for the cipher object; must be 16, 24, or 32 for AES + KEY_SIZE = 16 + #always 16 bytes for aes + BLOCK_SIZE = 16 + + # the character used for padding--with a block cipher such as AES, the value + # you encrypt must be a multiple of BLOCK_SIZE in length. This character is + # used to ensure that your value is always a multiple of BLOCK_SIZE + #PADDING_CHARACTER = '{' + PADDING_CHARACTER = b'0' + PADDING = 0 + op = 'encode' + musicFile = '' + + KEY='' + IV='' + + CIPHER_MODE = AES.MODE_CBC + # CIPHER_MODE = AES.MODE_CFB + + def __init__(self, fileName, keyFile='',operation='encode'): + self.op = operation + self.musicFile = fileName + if len(keyFile) != 0: + self.initKeyFromFile(keyFile) + else: + if operation == 'encode': + self.generateNewKeys() + + # one-liner to sufficiently pad the text to be encrypted + def updatePadSize(self,dlen): + self.PADDING = (self.BLOCK_SIZE - dlen % self.BLOCK_SIZE) + + def getPadding(self): + return self.PADDING + + def pad(self,s): + self.updatePadSize(len(s)) + pdata = self.PADDING * self.PADDING_CHARACTER + if len(pdata) != self.PADDING : + sys.exit('Pad data length %d does match %d. Check python pad character size!' % + (len(pdata), self.PADDING)) + s = s + pdata + return s + + def EncodeAEStoB64(self,c, s): + return base64.b64encode(c.encrypt(self.pad(s))) + + def b64DecodeAES(self,c, e): + return c.decrypt(base64.b64decode(e)).rstrip(self.PADDING_CHARACTER) + + + def initKeyFromFile(self,fname): + (self.KEY,self.IV)= self.readAesKeyFile(fname) + + def generateNewKeys(self): + self.KEY = self.generateAesKey(); + self.IV = self.generateAesIv() + + def generateAesKey(self,keySize=KEY_SIZE): + # generate a random secret key + #secret = Random.get_random_bytes(keySize) + secret = Random.new().read(keySize) + return secret + + def generateAesIv(self): + secret = Random.new().read(AES.block_size) + return secret + + def getKeyStr(self): + str = '%s_%s_%s' % (len(self.KEY),base64.b64encode(self.IV),base64.b64encode(self.KEY)) + #print 'okey=',str + return str + def getKeyModeStr(self): + if self.CIPHER_MODE == AES.MODE_CBC: + return 'CBC' + elif self.CIPHER_MODE == AES.MODE_CFB: + return 'CFB' + else: + sys.exit('Bad AES key mode') + + def setKey(self, content): + (keySize, ivstr, keystr) = content.split('_') + #print 'ikey=',content + self.IV = base64.b64decode(ivstr) + self.KEY = base64.b64decode(keystr) + + def writeAesKeytoFile(self, iv,key,Keyfilename): + #initialization vector - iv + #key - must be the same size as the iv + #output filename + str = '%s_%s_%s' % (len(key),base64.b64encode(iv),base64.b64encode(key)) + #print 'okey=',str + fo = open(Keyfilename, 'w') + fo.write(str) + fo.close() + + def writeAesKeytoFile(self,Keyfilename): + self.writeAesKeytoFile(self.IV,self.KEY,Keyfilename) + + def readAesKeyFile(self,keyFilename): + # Given the filename of a file that contains a public or private key, + # return the key as a (n,e) or (n,d) tuple value. + fo = open(keyFilename) + content = fo.read() + fo.close() + (keySize, ivstr, keystr) = content.split('_') + + #print 'ikey=',content + iv = base64.b64decode(ivstr) + key = base64.b64decode(keystr) + return (int(keySize), iv, key) + + + def WriteFileData(self,outfile,data): + #ddata = base64.b64decode(data) + fi = open(outfile,'wb') + fi.write(data); + + def LoadFileData(self, infile): + fi = open(infile,'rb') + data = fi.read(); + return data + #edata = base64.b64encode(data) + #return edata + + + def WriteAesFileData(self, outfile,data): + # These files are assumed to be in cypher text - so we won't b64 encode/decode them + fo = open(outfile,'wb') + fo.write(data) + + def LoadAesFileData(self, infile): + # These files are assumed to be in cypher text - so we won't b64 encode/decode them + fi = open(infile,'rb') + data = fi.read(); + return data + + def EncodeFileToMsgWithKey(self, key, iv, infile): + infileData = self.LoadFileData(infile) + pdata = self.pad(infileData) + cipher = AES.new(key, self.CIPHER_MODE, iv) + msg = cipher.encrypt(pdata) + return msg + + + def EncodeFileWithKeyAndWrite(self, key, iv, infile,outFile): + msg = self.EncodeFileToMsgWithKey(key, iv, infile) + self.WriteAesFileData(outFile,msg) + return msg + + def EncodeFileToMsg(self, infile): + infileData = self.LoadFileData(infile) + # file data must be in plain text + pdata = self.pad(infileData) + cipher = AES.new(self.KEY, self.CIPHER_MODE, self.IV) + msg = cipher.encrypt(pdata) + return msg + + + def EncodeFileAndWrite(self, infile,outFile): + edata = self.EncodeFileToMsg(infile) + self.WriteAesFileData(outFile,edata) + #return edata + + def DecodeFileToMsgWithKey(self,key, iv, infile): + infileData = self.LoadAesFileData(infile) + cipher = AES.new(key, self.CIPHER_MODE, iv) + #expects the file to have the right number of blocks + data = cipher.decrypt(infileData) + return data + + def DecodeFileWithKeyAndWrite(self,key, iv, infile,outFile): + msg = self.DecodeFileToMsgWithKey(key, iv, infile) + self.WriteFileData(outFile,msg) + + def DecodeFileToMsg(self, infile): + msg = self.DecodeFileToMsgWithKey(self.KEY, self.IV, infile) + return msg + + def DecodeFileAndWrite(self, infile,outFile): + data = self.DecodeFileToMsg(infile) + self.WriteFileData(outFile,data) + #return data + + def DecodeMsg(self, pdata): + cipher = AES.new(self.KEY,self.CIPHER_MODE, self.IV) + data = cipher.decrypt(pdata) + return data + + def DecodeU64Msg(self, pdata): + cipher = AES.new(self.KEY, self.CIPHER_MODE, self.IV) + data = self.b64DecodeAES(cipher, pdata) + return data + + + + + + diff --git a/ruby/lib/py/jam_tracks/jkaes.pyc b/ruby/lib/py/jam_tracks/jkaes.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ea98827c8205f5665c145f09a6d2e490d3e4f7e3 GIT binary patch literal 9156 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',tname + # write the content to tmp file + outTmpFile = tempfile.NamedTemporaryFile() + outTmpFile.write(fdata) + + track = JKtrack('dec_'+fname) + track.setKeyStr(aesKey) + track.DecodeBufferToFile(fdata,tname,padding) + if COMPARE_PARSEFILE: + # write the encrypted files out + fi = open('enc_'+tname,'wb') + fi.write(fdata); + fi.close() + + if filecmp.cmp(tname,fname): + print 'OK: file %s match %s' % (fname,tname) + else: + print 'BAD: file %s does not match %s' % (fname,tname) + + def parseContainer(self, rsaKeyFile): + # file must be zip file + zf = zipfile.ZipFile(self.packageFileInfo) + + #now decrypt data with rsa public key + rsa = JKrsa() + rsa.loadKey(rsaKeyFile) + + # get the jam package info file + pinfo = self.extractMediaMetaData(JAMTRACK_PACKAGE_INFO_FILE,rsa,zf) + fi = open(JAMTRACK_PACKAGE_INFO_FILE,'wb') + fi.write(pinfo); + fi.close() + + #get the media details + estr = self.extractMediaMetaData(JAMTRACK_MEDIA_META_FILE,rsa,zf) + data = json.loads(estr) + if DEBUG_ENABLE: + pprint(data) + + try: + coverFileInfoArray = data[JAMTRACK_COVER_ART_TAG] + try: + coverFileInfo= coverFileInfoArray[0] + cfile = coverFileInfo['datafile'] + aesKey = coverFileInfo['key'] + fname = coverFileInfo['filename'] + fsize = coverFileInfo['size'] + padding = coverFileInfo['padding'] + try: + fdata = zf.read(cfile) + except KeyError: + print 'ERROR: Did not find %s in zip file' % cfile + sys.exit() + else: + self.decryptAesFile(aesKey,fdata,fname,cfile,padding) + except: + print 'no cover art data' + except: + print 'no cover art' + + #now extract the file + mediaFilesInfo = data["tracks"] + + for mediaFileInfo in mediaFilesInfo: + aesKey = mediaFileInfo['key'] + fname = mediaFileInfo['filename'] + fsize = mediaFileInfo['size'] + tname = mediaFileInfo['trackname'] + padding = mediaFileInfo['padding'] + fdata = '' + try: + fdata = zf.read(tname) + except KeyError: + print 'ERROR: Did not find %s in zip file' % tname + sys.exit() + else: + self.decryptAesFile(aesKey,fdata,fname,tname,padding) + + diff --git a/ruby/lib/py/jam_tracks/jkasset.pyc b/ruby/lib/py/jam_tracks/jkasset.pyc new file mode 100644 index 0000000000000000000000000000000000000000..efd5ed263d50a59b13574b884d3cc839c725297c GIT binary patch literal 11420 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 YFM9uiZ@(}x0jx5j0wnWS6hXoN4;vafAOHXW literal 0 HcmV?d00001 diff --git a/ruby/lib/py/jam_tracks/jkcreate.py b/ruby/lib/py/jam_tracks/jkcreate.py new file mode 100755 index 000000000..e2272e17b --- /dev/null +++ b/ruby/lib/py/jam_tracks/jkcreate.py @@ -0,0 +1,249 @@ +import jkasset +import json +import jkasset +from pprint import pprint +import random +import sys, getopt +import jkmedia +import base64 +import tempfile +import argparse +from types import * +import unicodedata + +parser ='' +inputfiles = [] + +def help(): + parser.print_help() + + + +def media_arg(string): + #print 'media_arg:', string + try: + fname,ext = string.split('+') + #print fname + if fname == '': + print "### No music file in command line: ", string + raise argparse.ArgumentTypeError("Track file name empty") + + inputfiles.append(string) + except: + if string: + inputfiles.append(string) + pass + + return string + +def loadTracksFromList(fileName): + try: + lines = [line.strip() for line in open(fileName)] + #print lines + global inputfiles + inputfiles = lines + except: + print "Unable to open media filelist", fileName + help() + sys.exit(-1) + +def process(argv): + #print argv + global parser + parser = argparse.ArgumentParser( + prog='jkcreate', + description='JamTrack Packaging Tool', + epilog="Note: Meta file values may be update with title,sku,etc") + + parser.add_argument("-D", "--verbosity", + help="increase output verbosity", + action="store_true") + + parser.add_argument("-t", "--title", metavar='title', type=str, + help="Title of JamTrack. If not specified, the title in the metafile is used.") + + parser.add_argument("-k", "--sku", type=str, + help="The product SKU to use. If not specified, the SKU in the metafile is used.",required=True,) + + parser.add_argument("-c", "--cover", metavar='coverArtFile', type=str, + help="The cover art file. File should be JPEG or PNG.") + + parser.add_argument("-p", "--pkey", metavar='pkey.pem',type=str, + help="The output public key file. File should be .pem",required=True,) + + parser.add_argument("-s", "--skey", metavar='skey.pem', type=str, + help="The output private key file. File should be .pem",required=True,) + + + parser.add_argument("-m", "--meta",metavar='trackMetaFile', type=str, + help="The input JamTrack JSON metafile file. Optional") + + parser.add_argument("-o", "--ofile", metavar='JamTrackPage', type=str, + help="The output JamTrack file (.jkz)",required=True,) + + parser.add_argument("-I", "--infilesList", metavar='musicTrackFileList', type=str, + help=" -I 'file containing tracks'") + + parser.add_argument("-i", "--infiles", metavar='musictrack', type=media_arg, nargs='*', + help=" -i 'Lumme.ogg+Bass Guitar' -i 'Hydrate-Kenny_Beltrey.ogg+Bass Guitar'",) + + + #parser.print_help() + args = parser.parse_args(argv) + #print args + + try: + if args.verbosity: + jkasset.DEBUG_ENABLE = 1 + jkasset.COMPARE_PARSEFILE = True + #args.sku = random.randint(1, 100000000) + except: + pass + + + try: + if args.infilesList: + loadTracksFromList(args.infilesList) + except: + pass + #create json struct from info + metaJson={} + try: + if args.meta: + #try to load json data + try: + json_data=open(args.meta).read() + + metaJson = json.loads(json_data) + if jkasset.DEBUG_ENABLE : + pprint(metaJson) + except: + print 'Error processing json from file: ', args.meta + help() + sys.exit(-1) + except: + print 'No meta file specified' + pass + + jdata={} + mdata=[] + instdata={} + + if any(metaJson): + try: + #pickup the instrument data passed in - if any + instdata = metaJson["TrackInstrument"] + except: + pass + + i = 0 + trackLength = {} + trackLenStats=[] + + #print inputfiles + + if not any(inputfiles) : + print 'No track files specified ' + help() + sys.exit(-1) + + for mediaFileInfo in inputfiles: + #update track name and instrument name + #print mediaFileInfo + mediaFileInfo.strip(); + mlist = mediaFileInfo.split("+") + mediaFileInfo = mlist[0] + + trackName = 'track_%02d' % (i) + i = i + 1 + mdata.append({"name" : mediaFileInfo, "trackName": trackName}) + + try: + minfo = jkmedia.JKTrackInfo(mediaFileInfo) ; + tinfo={}; + tinfo['bitrate']=minfo.bitrate + tinfo['length'] = minfo.trackLength + trackLenStats.append(minfo.trackLength) + trackLength[trackName]= tinfo + except: + print("Cannot find file or read media information for '%s'"% mediaFileInfo) + #help() + sys.exit(-1) + + try: + instdata[trackName]=mlist[1] + except: + if instdata.has_key(trackName) == False: + print "No instrument for track ",trackName + pass + + try: + if args.title: + try: + title = base64.b64encode(args.title) + metaJson['Title']=title + except: + print 'Error processing title : ', title + help() + sys.exit(-1) + + if args.sku: + try: + metaJson['SKU']=args.sku + except: + print 'Error sku' + help() + sys.exit(-1) + + metaJson['METADATA_VER'] = 1 + + except: + #These are both optional.. + pass + + if any(instdata): + metaJson['TrackInstrument'] = instdata + if len (instdata) > len(mdata): + print "Number of instruments ", len (instdata)," in meta data greater than number of tracks ",len(mdata) + help() + sys.exit(-1) + + if any(trackLength): + metaJson['TrackData'] = trackLength + + #dump the meta data to temp file + + metaTmp = tempfile.NamedTemporaryFile(); + metaTmp.write(json.dumps(metaJson)) + metaTmp.seek(0) + + jdata["tracks"] = mdata + jdata["rsa_priv_file"]= args.skey + jdata["rsa_pub_file"]= args.pkey + jdata["container_file"]= args.ofile + + #jdata["jamktrack_info"]= trackMetaFile + jdata["jamktrack_info"]= metaTmp.name + + try: + jdata["coverart"]= args.cover + except: + print 'No cover art specified' + + data = json.dumps(jdata) + #pprint(data) + + fname = 'jt_metadata.json' + fo = open(fname, 'w') + fo.write(data) + fo.close() + + jas = jkasset.JKasset(fname) + + +if __name__ == "__main__": + random.seed() + process(sys.argv[1:]) + + + diff --git a/ruby/lib/py/jam_tracks/jkmedia.py b/ruby/lib/py/jam_tracks/jkmedia.py new file mode 100644 index 000000000..035abc3a4 --- /dev/null +++ b/ruby/lib/py/jam_tracks/jkmedia.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +import base64 +import os +import binascii +import json +import sys +from optparse import OptionParser +#from import mutagen.ogg +#from import mutagen.oggvorbis + +class JKTrackInfo: + trackLength =0 + bitrate = 0 + mediaFile=''; + + + def __init__(self, fileName): + #get the file extension + self.mediaFile = fileName + fileName, fileExtension = os.path.splitext(self.mediaFile) + audio = '' + if fileExtension == '.ogg' : + from mutagen.oggvorbis import OggVorbis + audio = OggVorbis(self.mediaFile) + elif fileExtension == '.wav' : + from mutagen.wavpack import WavPack + audio = WavPack(self.mediaFile) + elif fileExtension == '.mp3' : + from mutagen.mp3 import MP3 + audio = MP3(self.mediaFile) + elif fileExtension == '.flac' : + from mutagen.flac import FLAC + audio = FLAC(self.mediaFile) + else: + print "Unsupported filetype '",fileExtension, "' for file ", self.mediaFile + raise Exception("Unsupported filetype") + + print audio.info.length, audio.info.bitrate + self.bitrate = audio.info.bitrate + self.trackLength = audio.info.length + + + + diff --git a/ruby/lib/py/jam_tracks/jkrsa.py b/ruby/lib/py/jam_tracks/jkrsa.py new file mode 100755 index 000000000..4d0e38600 --- /dev/null +++ b/ruby/lib/py/jam_tracks/jkrsa.py @@ -0,0 +1,194 @@ +# RSA Cipher +# http://inventwithpython.com/hacking (BSD Licensed) + +from Crypto.Cipher import PKCS1_OAEP +from Crypto.PublicKey import RSA +from Crypto import Random +import base64 +import sys + +class JKrsa: + # IMPORTANT: The block size MUST be less than or equal to the key size! + # (Note: The block size is in bytes, the key size is in bits. There + # are 8 bits in 1 byte.) + #DEFAULT_BLOCK_SIZE = 128-2 # 128 bytes + DEFAULT_BLOCK_SIZE = 64 # 128 bytes + + + RSA_KEYSIZE = 1024 + BYTE_SIZE = 256 # One byte has 256 different values. + RSAKey=''; + PADDING = 0 + PADDING_CHARACTER =b'\0' + encipher=''; + decipher='' + + def __init__(self, operation='encode'): + self.op = operation + + def updatePadSize(self,dlen): + self.PADDING = (self.DEFAULT_BLOCK_SIZE - dlen % self.DEFAULT_BLOCK_SIZE) + + def getPadding(self): + return self.PADDING + + def pad(self,s): + self.updatePadSize(len(s)) + pdata = self.PADDING * self.PADDING_CHARACTER + if len(pdata) != self.PADDING : + sys.exit('Pad data length %d does match %d. Check python pad character size!', + (len(pdata), self.PADDING)) + s = s + pdata + return s + + def EncodeRSA(self,c, s): + #print 'len=',len(s) + estr = self.encipher.encrypt(s) + #print 'elen=',len(estr) + #print estr + return estr + + def DecodeRSA(self,c, e): + #print 'len=',len(e) + str = self.decipher.decrypt(e) + return str + + def generateKey(self, privateFileName='pri_Key.pem', publicFileName='pub_Key.pem'): + self.RSAKey = RSA.generate(self.RSA_KEYSIZE) + + public_key = self.RSAKey.publickey().exportKey("PEM") + private_key = self.RSAKey.exportKey("PEM") + + if privateFileName: + f = open(privateFileName,'w') + f.write(self.RSAKey.exportKey("PEM")) + f.close() + + f = open(privateFileName,'r') + skey = RSA.importKey(f.read()) + self.decipher = PKCS1_OAEP.new(skey) + f.close() + + if publicFileName: + f = open(publicFileName,'w') + f.write(self.RSAKey.publickey().exportKey("PEM")) + f.close() + + f = open(publicFileName,'r') + pkey = RSA.importKey(f.read()) + self.encipher = PKCS1_OAEP.new(pkey) + f.close() + + return private_key, public_key + + def loadKey(self, fileName='pri_Key.pem'): + f = open(fileName,'r') + self.RSAKey = RSA.importKey(f.read()) + self.decipher = PKCS1_OAEP.new(self.RSAKey) + f.close() + + def WriteFileData(self,outfile,data): + #ddata = base64.b64decode(data) + fi = open(outfile,'wb') + fi.write(data); + + def LoadFileData(self, infile): + fi = open(infile,'rb') + data = fi.read(); + #edata = base64.b64encode(data) + return data + + def WriteRSAFileData(self, outfile,data): + # These files are assumed to be in cypher text - so we won't b64 encode/decode them + fo = open(outfile,'wb') + #edata = base64.b64encode(data) + fo.write(data) + + + def LoadRSAFileData(self, infile): + # These files are assumed to be in cypher text - so we won't b64 encode/decode them + fi = open(infile,'rb') + data = fi.read(); + return data + + def getBlocksFromText(self,message, blockSize=DEFAULT_BLOCK_SIZE): + # Converts a string message to a list of block integers. Each integer + # represents 128 (or whatever blockSize is set to) string characters. + # messageBytes = message.encode('ascii') # convert the string to bytes + blockInts = [] + blockInts = [message[i:i+blockSize] for i in range(0, len(message), blockSize)] + return blockInts + + + def encryptMesssage(self, message, blockSize=DEFAULT_BLOCK_SIZE): + keySize = self.RSAKey.size() + 1; + + # Check that key size is greater than block size. + if keySize < blockSize * 8: # * 8 to convert bytes to bits + sys.exit('ERROR: Block size is %d bits and key size is %d bits.' + ' The RSA cipher requires the block size to be equal to' + ' or less than the key size. Either decrease the' + ' block size or use different keys.' % (blockSize * 8, keySize)) + + if len(message) % blockSize : # * 8 to convert bytes to bits + sys.exit('ERROR: Block size is %d and data size is %d .' + ' The RSA cipher requires the block size to be equal to' + ' or less than the key size. Either decrease the' + ' block size or use different keys.' % (blockSize, len(message))) + + + encryptedContent = [] + for block in self.getBlocksFromText(message, blockSize): + encryptedContent_ = self.EncodeRSA(self.RSAKey,block); + encryptedContent.extend(encryptedContent_) + + return encryptedContent + + def decryptMesssage(self, message): + #print message + #split in equal chunks + decryptedContent = [] + for block in self.getBlocksFromText(message, self.DEFAULT_BLOCK_SIZE*2): + decryptedContent_ = self.DecodeRSA(self.RSAKey,block); + decryptedContent.extend(decryptedContent_) + + return decryptedContent + + + def encryptAndWriteToFile(self, keyFilename, inFile,outFilename, blockSize=DEFAULT_BLOCK_SIZE): + # Using a key from a key file, encrypt the message and save it to a + # file. Returns the encrypted message string. + self.loadKey(keyFilename) + keySize = self.RSAKey.size()+1; + + # Check that key size is greater than block size. + if keySize < blockSize * 8: # * 8 to convert bytes to bits + sys.exit('ERROR: Block size is %s bits and key size is %s bits.' + ' The RSA cipher requires the block size to be equal to' + ' or less than the key size. Either decrease the' + ' block size or use different keys.' % (blockSize * 8, keySize)) + + # Encrypt the message + message = self.LoadFileData(inFile); + message = self.pad(message) + + encryptedContent = self.encryptMesssage(message); + self.WriteRSAFileData(outFilename,encryptedContent) + # Also return the encrypted string. + #return encryptedContent + + + def readFromFileAndDecrypt(self, keyFilename, inFile, outFilename): + # Using a key from a key file, read an encrypted message from a file + # and then decrypt it. Returns the decrypted message string. + self.loadKey(keyFilename) + + # Read in the message length and the encrypted message from the file. + message = self.LoadRSAFileData(inFile) + + decryptMessage = self.decryptMesssage(message) + self.WriteFileData(outFilename,decryptMessage) + + + + diff --git a/ruby/lib/py/jam_tracks/jktrack.py b/ruby/lib/py/jam_tracks/jktrack.py new file mode 100755 index 000000000..3e0dcd4b3 --- /dev/null +++ b/ruby/lib/py/jam_tracks/jktrack.py @@ -0,0 +1,104 @@ +import jkaes +import tempfile +from jkaes import JKaes +import os +class JKtrack: + mediaFile = '' + Name = '' + aesInKeyFile='' + aesCipher = '' + outTmpFile ='' + outPutFile = '' + def __init__(self, fileName, title='Unknown', inKeyFile=''): + self.mediaFile = fileName + self.Name = title + self.aesInKeyFile = inKeyFile + self.aesCipher = JKaes(self.mediaFile, self.aesInKeyFile) + + def getOutputFileName(self): + return self.outPutFile; + + def getKeyStr(self): + return self.aesCipher.getKeyStr(); + + def getEncryptPadding(self): + return self.aesCipher.getPadding() + + def setKeyStr(self,key): + self.aesCipher.setKey(key); + + def getKeyModeStr(self): + return self.aesCipher.getKeyModeStr() + + def getCoverEncryptFilePackageName(self,instance=-1): + #preserve the extentsion on the original name + fileName, fileExtension = os.path.splitext(self.mediaFile) + if instance != -1: + nm = "cover_%02d%s" % (instance,fileExtension) + return nm + else: + nm = os.path.basename(self.mediaFile) + return nm + + def getEncryptFilePackageName(self,instance=-1): + #preserve the extentsion on the original name + fileName, fileExtension = os.path.splitext(self.mediaFile) + if instance != -1: + nm = "track_%02d%s" % (instance,fileExtension) + return nm + else: + nm = os.path.basename(self.mediaFile) + return nm + + def Encode(self, outFile): + self.outPutFile = outFile + self.aesCipher.EncodeFileAndWrite( self.mediaFile, outFile) + + def EncodeToTmpFile(self): + self.outTmpFile = tempfile.NamedTemporaryFile() + self.Encode(self.outTmpFile.name) + + def EncodeWithKeyToFile(self,keyFile,outFile): + self.aesCipher.readAesKeyFile(keyFile) + self.Encode(outFile) + + def Decode(self, outFile): + self.outPutFile = outFile + self.aesCipher.DecodeFileAndWrite( self.mediaFile, outFile) + + def DecodeToTmpFile(self): + self.outTmpFile = tempfile.NamedTemporaryFile() + self.Decode(self.outTmpFile.name) + + def DecodeWithKeyToFile(self,keyFile,outFile): + self.aesCipher.readAesKeyFile(keyFile) + self.Decode(outFile) + + + def DecodeBufferToFile(self,msg,outfile,padding=0): + dmsg = self.aesCipher.DecodeMsg(msg) + fi = open(outfile,'wb') + fi.write(dmsg); + if padding > 0: + #trunckage the file by + fi.seek(-padding, os.SEEK_END) + fi.truncate() + fi.close() + return dmsg + + def DecodeU64Buffer(self,msg): + return self.aesCipher.DecodeU64Msg(msg) + + def setKey(self, keyFile): + self.aesCipher.readAesKeyFile(keyFile) + + def saveKey(self,keyFile): + self.aesCipher.writeAesKeytoFile(keyFile) + + def saveEncFileCopy(self,outfile): + fi = open(self.outPutFile,'rb') + fi.seek(0) + dmsg = fi.read(); + fo = open(outfile,'wb') + fo.write(dmsg); + return dmsg \ No newline at end of file diff --git a/ruby/lib/py/jam_tracks/jktrack.pyc b/ruby/lib/py/jam_tracks/jktrack.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d065cbbe6aee5c45bc364f9d9dba2207c51113bd GIT binary patch literal 5712 zcmc&&>uwuG6h3P^cc)3yB)tJDl!6hoE)^6(xs^0XrD~`qsY*j7Yvb9Z>&x2OowN-~ zeJGux2C{o}8Dk4FTbI}78`cdu2hzUbmkbMv~PFnV{I+2sJ zK1tFzGbMIZfwI^!1*XMf|Ag#=!BYxPDm){0N;k}kEh}(Z?6d-NVoxb>M(m6N^I~Te zSP+YCb9%y}*fTnLR_we2=fo~3a9-@90vE)rR?dP|hbAhKheGsA>wH0qvyE~k%sg-2aMpJ=n-5o_= ztHnv|YBds{ksb*q^r_R-dQGKAW20mQCdq}|8YcmEw%vnmN2Ybf@Sq_Qej3MbMefIK z*Y#K_z&T821#}X;O8YdXJ?)KvzSZM^`wbaWq2o;GZq=nH!G)Ot+6A=#Ks03!q926T z#>v)6Q0;QrFDLbMr`yAP?f@GUX|Ji>%wwW2d$`|TpkeP-6)?_as)CB@g;oTg6lq2~ zL$oHkn@0=HU*v$;|D6@l&b$@f@7U<1EQwb17FtfY=88I*U`(L^RS!P_K#+t3Ah=N> zMXKUSL0;mA(1!#NNV6#EEv5m+$)ao)WbtK5c1yCFzluTc)qKIyorq;c5oZh~_0F>> ztzEf(!(PdJ&?GsXOk*FTmIj}!+lpQE!Wj*VRQE=hMG2f)-_bCl8{;HH#H&Y!x^fgU z!{+h}7bRKTNgNMP+C#JvN*8)vB3rrXGuk~s;tjVne!q0ih|5phb z=npEvEP_bGWNGNJDt2=o&_LYg0v9M|Lsu|wa20JxDTv4+*Vl{mLoB{tq||rct}kOS z$m)j;oZ-e-S-o&WQ!}(6ANH9wfE~_Cecn#&qZB5ok~t8Bm?4nkG{FHJk}w$mJT0FB z%19}*aX*`pYxU&!0C;!Y)6iP$`7>R!AdS+|7^}4f{mVt|E?viwZ)Q!qhM7(prdqPN zJejYxyX4i-sMTfFCz2N%3cJvS<2H?4E+2wGPpKh3TB} zSszM{*Y58`e`ELNQjcY2{@4pp8oVds_JGx6GkEh34mFqAy~~c_#9TplB6SiC+4I&n z{qt^<}WzB17Y z;rWIVRG9$&LDBD-xSwrlpzCy_L@(ISnFsQ#TGZ6csiny3^76gf^2!|@xO6+IhuFse zi)&XF>1Evq5c1o`v3c^)uuHh>t93owas$13Gah1mVX*gXez(238KpkFtY9{qMJK^r zFcVA%<-$Dr$w8>;$5`$*CA!>3UAcc;*P%x}K$y!F;jRpsw znbC=2?40pTM -s ' + + +def process(argv): + + global parser + parser = argparse.ArgumentParser( + prog='jkunpack', + description='JamTrack UnPackaging Tool', + epilog="Note: Files may be overwritten during unpacking") + + parser.add_argument("-D", "--verbosity", + help="increase output verbosity", + action="store_true") + + parser.add_argument("-s", "--skey", metavar='skey.pem', type=str, + help="The private key file. File should be .pem",required=True) + + parser.add_argument("-i", "--ifile", metavar='JamTrackPage', type=str, + help="The input JamTrack file (.jkz)",required=True) + + parser.print_help() + args = parser.parse_args(argv) + print args + + try: + if args.verbosity: + jkasset.DEBUG_ENABLE = 1 + jkasset.COMPARE_PARSEFILE = True + except: + pass + + + jas = jkasset.JKasset(args.ifile,'decode',args.skey) + +if __name__ == "__main__": + random.seed() + process(sys.argv[1:]) + + + diff --git a/ruby/lib/py/jam_tracks/jkzify.rb b/ruby/lib/py/jam_tracks/jkzify.rb new file mode 100755 index 000000000..c5ef594fd --- /dev/null +++ b/ruby/lib/py/jam_tracks/jkzify.rb @@ -0,0 +1,98 @@ +#!/usr/bin/env ruby +# +# Usage: jkzify [path to JamTrack WAVs] [destination for jkz and key files] +# +# Please review the comments, this script needs improvement -- I just threw it together to help me validate jkcreate.py +# + +wd = Dir.getwd +tracks_location = ARGV.shift +output_location = ARGV.shift +script_location = File.dirname(File.expand_path(__FILE__)) + + + +# first try to catch any issues with input arguments +def halt msg; warn msg; exit; end +halt "usage: jkzify path/to/wavs/ path/to/output" unless tracks_location && output_location +halt "path not found" unless File.exist?(tracks_location) && File.exist?(output_location) +# end + + + +# step 1: gather metadata from the track/folder names (and user input) +title = tracks_location.split('/').last.gsub(/'/,'') +puts "i think this track is called \"#{title}\"..." +shortname = title.gsub(/[aeiou\s]/,'').squeeze[0...8].downcase +puts "i'll call it #{shortname} for now" +puts "what sku do you want for this JamTrack? (press ENTER for '#{shortname}')" +input = STDIN.gets.chomp.strip +sku = input.empty? ? shortname : input + + + +# step 2: move WAVs to our own workspace +def renamed(t); t.gsub(/\s|'/,'_'); end #little helper + +Dir.chdir wd +`rm -rf #{shortname}` if File.exist? shortname +`mkdir #{shortname}` + +Dir.chdir tracks_location +Dir.glob('*.wav') do |track| + new_name = renamed track + `cp "#{track}" "#{wd}/#{shortname}/#{new_name}"` + puts "Copied from " + track + " to #{wd}/#{shortname}/" + new_name +end +Dir.chdir "#{wd}/#{shortname}" + + + +# step 3: generate OGGs from WAVs +oggs_location = title.downcase.gsub('\'','').split.join('_') + '_oggs/' +puts "generating OGGs in a separate folder #{oggs_location} (this may take some time)" +`mkdir #{oggs_location}` unless File.exist?(oggs_location) +Dir.glob('*.wav') do |track| + input = track + output = "#{oggs_location}#{File.basename(track, '.wav')}.ogg" + #puts "sox #{input} #{output}" + `sox #{input} #{output}` +end + + +# step 4: get all track parameters (names and instruments) +track_params = {} +track_files = [] +suffix = '\.wav' +Dir.foreach tracks_location do |track| + next if track =~ /Master Mix/i + next unless track =~ /#{suffix}$/i + print '.' + track.gsub!(/#{suffix}$/i, '') + instrument = track.split('-').drop(1).map(&:strip).join(' ') + parameter = "#{shortname}/#{oggs_location}#{renamed track}.ogg+#{instrument}" + #parameter.gsub!(/ /, '\ ') #spaces in filenames + track_params[instrument] = "-i '#{parameter}'" + track_files << parameter +end +puts "OK, #{track_params.length} tracks found" + + + +# step 5: create list of track titles +Dir.chdir wd +tracks_filename = "#{shortname}.txt" +File.open(tracks_filename, 'w+') { |f| f.puts track_files } + + + +# step 6: run jkcreate.py given all these parameters +public_key = "#{output_location}#{shortname}-pub.pem" +private_key = "#{output_location}#{shortname}-priv.pem" +output_jkz = "#{output_location}#{shortname}.jkz" +#puts "python jkcreate.py -D -k #{sku} -c art.png -p #{public_key} -s #{private_key} -I #{tracks_filename} -o #{output_jkz} -t '#{title}'" +`python jkcreate.py -D -k #{sku} -c art.png -p #{public_key} -s #{private_key} -I #{tracks_filename} -o #{output_jkz} -t '#{title}'` + + + +Dir.chdir wd \ No newline at end of file From 6407d2f22a1164dd9e7067e66578e09da81aa282 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Mon, 15 Dec 2014 12:15:44 -0600 Subject: [PATCH 36/72] Merge from working branch --- ruby/bin/jamtrack.rb | 0 ruby/lib/jam_ruby/jam_tracks_manager.rb | 40 +++++++++++++------ .../jam_ruby/models/jam_track_right_spec.rb | 10 +++++ 3 files changed, 38 insertions(+), 12 deletions(-) mode change 100644 => 100755 ruby/bin/jamtrack.rb diff --git a/ruby/bin/jamtrack.rb b/ruby/bin/jamtrack.rb old mode 100644 new mode 100755 diff --git a/ruby/lib/jam_ruby/jam_tracks_manager.rb b/ruby/lib/jam_ruby/jam_tracks_manager.rb index 07b264df0..1c4b481aa 100644 --- a/ruby/lib/jam_ruby/jam_tracks_manager.rb +++ b/ruby/lib/jam_ruby/jam_tracks_manager.rb @@ -1,20 +1,36 @@ +require 'json' +require 'tempfile' +require 'open3' + module JamRuby - # describes an audio track (like the drums, or guitar) that comprises a JamTrack + # Interact with external python tools to create the JKZ class JamTracksManager class << self - def save_jam_track(jam_track, user) - spec = Gem::Specification.find_by_name("jam_ruby") - py_root = spec.gem_dir + "/lib/py/jam_tracks/" - puts "Executing python in #{py_root}" + def save_jam_track_jkz(user, jam_track) + py_root = File.expand_path(File.join("..", "..", "jamtracks")) + tmp_dir = Dir::Tmpname.make_tmpname("/tmp/jamtrack", nil) + puts "Executing python in #{py_root}, outputting to #{tmp_dir}" + - sku=""#jam_track.sku - public_key="" - private_key="" - tracks_filename="" - output_jkz="" - title="" - `python #{py_root}jkcreate.py -D -k #{sku} -c art.png -p #{public_key} -s #{private_key} -I #{tracks_filename} -o #{output_jkz} -t '#{title}'` + sku=jam_track.id + tracks_filename=tmp_dir + "/jamtrack.info" + output_jkz="tmp_dir/#{jam_track.name.tableize}.jkz" + title=jam_track.name + + # From http://stackoverflow.com/questions/690151/getting-output-of-system-calls-in-ruby/5970819#5970819: + #Open3.popen3("ls") do |stdin, stdout, stderr, wait_thr| + Open3.popen3("python", + "#{py_root}/jkcreate.py", + "-D -k #{sku} -c art.png -p #{tmp_dir}/pkey.pem -s #{tmp_dir}/skey.pem -I #{tracks_filename} -o #{output_jkz} -t '#{title}'" + ) do |stdin, stdout, stderr, wait_thr| + pid = wait_thr.pid + exit_status = wait_thr.value + err = stderr.read(1000) + raise ArgumentError, "Error calling python script: #{err}" if err.present? + end + + tmp_dir end end end diff --git a/ruby/spec/jam_ruby/models/jam_track_right_spec.rb b/ruby/spec/jam_ruby/models/jam_track_right_spec.rb index 5d136d62a..1df21358f 100644 --- a/ruby/spec/jam_ruby/models/jam_track_right_spec.rb +++ b/ruby/spec/jam_ruby/models/jam_track_right_spec.rb @@ -27,5 +27,15 @@ describe JamTrackRight do right_2.errors[:user_id].should == ['has already been taken'] end end + + it "saves JKZ" do + user = FactoryGirl.create(:user) + jam_track = FactoryGirl.create(:jam_track) + right = JamTrackRight.create(:user=>user, :jam_track=>jam_track) + expect { + JamRuby::JamTracksManager.save_jam_track_jkz(user, jam_track) + }.to_not raise_error + end + end From 49ada397be63786713c9749c604bcea5be528400 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Mon, 15 Dec 2014 12:20:30 -0600 Subject: [PATCH 37/72] Remove silly file. --- ruby/bin/jamtrack.rb | 1 - 1 file changed, 1 deletion(-) delete mode 100755 ruby/bin/jamtrack.rb diff --git a/ruby/bin/jamtrack.rb b/ruby/bin/jamtrack.rb deleted file mode 100755 index 2152352db..000000000 --- a/ruby/bin/jamtrack.rb +++ /dev/null @@ -1 +0,0 @@ -{"SKU": "0044454545454", "METADATA_VER": 1, "TrackData": {"track_2": {"length": 0.1, "bitrate": 96000}, "track_1": {"length": 264.5681632653061, "bitrate": 128003}, "track_0": {"length": 405.054693877551, "bitrate": 128003}}, "TrackInstrument": {"track_1": "Alto Sax", "track_0": "Bass Guitar"}, "Title": "SmFtS2F6YW0gcm9sbGluZyBpbiB0aGUgZG91Z2g="} \ No newline at end of file From 1a7420ffb1f91a0e2277694b28ccecf93353bc77 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Mon, 15 Dec 2014 12:26:38 -0600 Subject: [PATCH 38/72] Whack files no longer needed. --- ruby/bin/jamtrack.rb | 1 - ruby/lib/py/jam_tracks/jamtrack.info | 1 - ruby/lib/py/jam_tracks/jamtracklist.txt | 8 - ruby/lib/py/jam_tracks/jkaes.py | 212 ------------ ruby/lib/py/jam_tracks/jkaes.pyc | Bin 9156 -> 0 bytes ruby/lib/py/jam_tracks/jkasset.py | 431 ------------------------ ruby/lib/py/jam_tracks/jkasset.pyc | Bin 11420 -> 0 bytes ruby/lib/py/jam_tracks/jkcreate.py | 249 -------------- ruby/lib/py/jam_tracks/jkmedia.py | 44 --- ruby/lib/py/jam_tracks/jkrsa.py | 194 ----------- ruby/lib/py/jam_tracks/jktrack.py | 104 ------ ruby/lib/py/jam_tracks/jktrack.pyc | Bin 5712 -> 0 bytes ruby/lib/py/jam_tracks/jkunpack.py | 56 --- ruby/lib/py/jam_tracks/jkzify.rb | 98 ------ 14 files changed, 1398 deletions(-) delete mode 100755 ruby/bin/jamtrack.rb delete mode 100644 ruby/lib/py/jam_tracks/jamtrack.info delete mode 100644 ruby/lib/py/jam_tracks/jamtracklist.txt delete mode 100755 ruby/lib/py/jam_tracks/jkaes.py delete mode 100644 ruby/lib/py/jam_tracks/jkaes.pyc delete mode 100755 ruby/lib/py/jam_tracks/jkasset.py delete mode 100644 ruby/lib/py/jam_tracks/jkasset.pyc delete mode 100755 ruby/lib/py/jam_tracks/jkcreate.py delete mode 100644 ruby/lib/py/jam_tracks/jkmedia.py delete mode 100755 ruby/lib/py/jam_tracks/jkrsa.py delete mode 100755 ruby/lib/py/jam_tracks/jktrack.py delete mode 100644 ruby/lib/py/jam_tracks/jktrack.pyc delete mode 100755 ruby/lib/py/jam_tracks/jkunpack.py delete mode 100755 ruby/lib/py/jam_tracks/jkzify.rb diff --git a/ruby/bin/jamtrack.rb b/ruby/bin/jamtrack.rb deleted file mode 100755 index 2152352db..000000000 --- a/ruby/bin/jamtrack.rb +++ /dev/null @@ -1 +0,0 @@ -{"SKU": "0044454545454", "METADATA_VER": 1, "TrackData": {"track_2": {"length": 0.1, "bitrate": 96000}, "track_1": {"length": 264.5681632653061, "bitrate": 128003}, "track_0": {"length": 405.054693877551, "bitrate": 128003}}, "TrackInstrument": {"track_1": "Alto Sax", "track_0": "Bass Guitar"}, "Title": "SmFtS2F6YW0gcm9sbGluZyBpbiB0aGUgZG91Z2g="} \ No newline at end of file diff --git a/ruby/lib/py/jam_tracks/jamtrack.info b/ruby/lib/py/jam_tracks/jamtrack.info deleted file mode 100644 index 2152352db..000000000 --- a/ruby/lib/py/jam_tracks/jamtrack.info +++ /dev/null @@ -1 +0,0 @@ -{"SKU": "0044454545454", "METADATA_VER": 1, "TrackData": {"track_2": {"length": 0.1, "bitrate": 96000}, "track_1": {"length": 264.5681632653061, "bitrate": 128003}, "track_0": {"length": 405.054693877551, "bitrate": 128003}}, "TrackInstrument": {"track_1": "Alto Sax", "track_0": "Bass Guitar"}, "Title": "SmFtS2F6YW0gcm9sbGluZyBpbiB0aGUgZG91Z2g="} \ No newline at end of file diff --git a/ruby/lib/py/jam_tracks/jamtracklist.txt b/ruby/lib/py/jam_tracks/jamtracklist.txt deleted file mode 100644 index 6fa0a517d..000000000 --- a/ruby/lib/py/jam_tracks/jamtracklist.txt +++ /dev/null @@ -1,8 +0,0 @@ -You Shook Me All Night Long/You Shook Me All Night Long Stem - Bass.ogg+Bass -You Shook Me All Night Long/You Shook Me All Night Long Stem - Drums.ogg+Drums -You Shook Me All Night Long/You Shook Me All Night Long Stem - Guitar - Main.ogg+Guitar Main -You Shook Me All Night Long/You Shook Me All Night Long Stem - Guitar - Rhythm 2.ogg+Guitar Rhythm 2 -You Shook Me All Night Long/You Shook Me All Night Long Stem - Guitar - Rhythm.ogg+Guitar Rhythm -You Shook Me All Night Long/You Shook Me All Night Long Stem - Guitar - Solo.ogg+Guitar Solo -You Shook Me All Night Long/You Shook Me All Night Long Stem - Vocals - Background.ogg+Vocals Background -You Shook Me All Night Long/You Shook Me All Night Long Stem - Vocals - Lead.ogg+Vocals Lead diff --git a/ruby/lib/py/jam_tracks/jkaes.py b/ruby/lib/py/jam_tracks/jkaes.py deleted file mode 100755 index 49966001f..000000000 --- a/ruby/lib/py/jam_tracks/jkaes.py +++ /dev/null @@ -1,212 +0,0 @@ -#!/usr/bin/env python - -from Crypto.Cipher import AES -from Crypto import Random -import base64 -import os -import binascii -import json -import sys - -class JKaes: - - # the key size for the cipher object; must be 16, 24, or 32 for AES - KEY_SIZE = 16 - #always 16 bytes for aes - BLOCK_SIZE = 16 - - # the character used for padding--with a block cipher such as AES, the value - # you encrypt must be a multiple of BLOCK_SIZE in length. This character is - # used to ensure that your value is always a multiple of BLOCK_SIZE - #PADDING_CHARACTER = '{' - PADDING_CHARACTER = b'0' - PADDING = 0 - op = 'encode' - musicFile = '' - - KEY='' - IV='' - - CIPHER_MODE = AES.MODE_CBC - # CIPHER_MODE = AES.MODE_CFB - - def __init__(self, fileName, keyFile='',operation='encode'): - self.op = operation - self.musicFile = fileName - if len(keyFile) != 0: - self.initKeyFromFile(keyFile) - else: - if operation == 'encode': - self.generateNewKeys() - - # one-liner to sufficiently pad the text to be encrypted - def updatePadSize(self,dlen): - self.PADDING = (self.BLOCK_SIZE - dlen % self.BLOCK_SIZE) - - def getPadding(self): - return self.PADDING - - def pad(self,s): - self.updatePadSize(len(s)) - pdata = self.PADDING * self.PADDING_CHARACTER - if len(pdata) != self.PADDING : - sys.exit('Pad data length %d does match %d. Check python pad character size!' % - (len(pdata), self.PADDING)) - s = s + pdata - return s - - def EncodeAEStoB64(self,c, s): - return base64.b64encode(c.encrypt(self.pad(s))) - - def b64DecodeAES(self,c, e): - return c.decrypt(base64.b64decode(e)).rstrip(self.PADDING_CHARACTER) - - - def initKeyFromFile(self,fname): - (self.KEY,self.IV)= self.readAesKeyFile(fname) - - def generateNewKeys(self): - self.KEY = self.generateAesKey(); - self.IV = self.generateAesIv() - - def generateAesKey(self,keySize=KEY_SIZE): - # generate a random secret key - #secret = Random.get_random_bytes(keySize) - secret = Random.new().read(keySize) - return secret - - def generateAesIv(self): - secret = Random.new().read(AES.block_size) - return secret - - def getKeyStr(self): - str = '%s_%s_%s' % (len(self.KEY),base64.b64encode(self.IV),base64.b64encode(self.KEY)) - #print 'okey=',str - return str - def getKeyModeStr(self): - if self.CIPHER_MODE == AES.MODE_CBC: - return 'CBC' - elif self.CIPHER_MODE == AES.MODE_CFB: - return 'CFB' - else: - sys.exit('Bad AES key mode') - - def setKey(self, content): - (keySize, ivstr, keystr) = content.split('_') - #print 'ikey=',content - self.IV = base64.b64decode(ivstr) - self.KEY = base64.b64decode(keystr) - - def writeAesKeytoFile(self, iv,key,Keyfilename): - #initialization vector - iv - #key - must be the same size as the iv - #output filename - str = '%s_%s_%s' % (len(key),base64.b64encode(iv),base64.b64encode(key)) - #print 'okey=',str - fo = open(Keyfilename, 'w') - fo.write(str) - fo.close() - - def writeAesKeytoFile(self,Keyfilename): - self.writeAesKeytoFile(self.IV,self.KEY,Keyfilename) - - def readAesKeyFile(self,keyFilename): - # Given the filename of a file that contains a public or private key, - # return the key as a (n,e) or (n,d) tuple value. - fo = open(keyFilename) - content = fo.read() - fo.close() - (keySize, ivstr, keystr) = content.split('_') - - #print 'ikey=',content - iv = base64.b64decode(ivstr) - key = base64.b64decode(keystr) - return (int(keySize), iv, key) - - - def WriteFileData(self,outfile,data): - #ddata = base64.b64decode(data) - fi = open(outfile,'wb') - fi.write(data); - - def LoadFileData(self, infile): - fi = open(infile,'rb') - data = fi.read(); - return data - #edata = base64.b64encode(data) - #return edata - - - def WriteAesFileData(self, outfile,data): - # These files are assumed to be in cypher text - so we won't b64 encode/decode them - fo = open(outfile,'wb') - fo.write(data) - - def LoadAesFileData(self, infile): - # These files are assumed to be in cypher text - so we won't b64 encode/decode them - fi = open(infile,'rb') - data = fi.read(); - return data - - def EncodeFileToMsgWithKey(self, key, iv, infile): - infileData = self.LoadFileData(infile) - pdata = self.pad(infileData) - cipher = AES.new(key, self.CIPHER_MODE, iv) - msg = cipher.encrypt(pdata) - return msg - - - def EncodeFileWithKeyAndWrite(self, key, iv, infile,outFile): - msg = self.EncodeFileToMsgWithKey(key, iv, infile) - self.WriteAesFileData(outFile,msg) - return msg - - def EncodeFileToMsg(self, infile): - infileData = self.LoadFileData(infile) - # file data must be in plain text - pdata = self.pad(infileData) - cipher = AES.new(self.KEY, self.CIPHER_MODE, self.IV) - msg = cipher.encrypt(pdata) - return msg - - - def EncodeFileAndWrite(self, infile,outFile): - edata = self.EncodeFileToMsg(infile) - self.WriteAesFileData(outFile,edata) - #return edata - - def DecodeFileToMsgWithKey(self,key, iv, infile): - infileData = self.LoadAesFileData(infile) - cipher = AES.new(key, self.CIPHER_MODE, iv) - #expects the file to have the right number of blocks - data = cipher.decrypt(infileData) - return data - - def DecodeFileWithKeyAndWrite(self,key, iv, infile,outFile): - msg = self.DecodeFileToMsgWithKey(key, iv, infile) - self.WriteFileData(outFile,msg) - - def DecodeFileToMsg(self, infile): - msg = self.DecodeFileToMsgWithKey(self.KEY, self.IV, infile) - return msg - - def DecodeFileAndWrite(self, infile,outFile): - data = self.DecodeFileToMsg(infile) - self.WriteFileData(outFile,data) - #return data - - def DecodeMsg(self, pdata): - cipher = AES.new(self.KEY,self.CIPHER_MODE, self.IV) - data = cipher.decrypt(pdata) - return data - - def DecodeU64Msg(self, pdata): - cipher = AES.new(self.KEY, self.CIPHER_MODE, self.IV) - data = self.b64DecodeAES(cipher, pdata) - return data - - - - - - diff --git a/ruby/lib/py/jam_tracks/jkaes.pyc b/ruby/lib/py/jam_tracks/jkaes.pyc deleted file mode 100644 index ea98827c8205f5665c145f09a6d2e490d3e4f7e3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9156 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',tname - # write the content to tmp file - outTmpFile = tempfile.NamedTemporaryFile() - outTmpFile.write(fdata) - - track = JKtrack('dec_'+fname) - track.setKeyStr(aesKey) - track.DecodeBufferToFile(fdata,tname,padding) - if COMPARE_PARSEFILE: - # write the encrypted files out - fi = open('enc_'+tname,'wb') - fi.write(fdata); - fi.close() - - if filecmp.cmp(tname,fname): - print 'OK: file %s match %s' % (fname,tname) - else: - print 'BAD: file %s does not match %s' % (fname,tname) - - def parseContainer(self, rsaKeyFile): - # file must be zip file - zf = zipfile.ZipFile(self.packageFileInfo) - - #now decrypt data with rsa public key - rsa = JKrsa() - rsa.loadKey(rsaKeyFile) - - # get the jam package info file - pinfo = self.extractMediaMetaData(JAMTRACK_PACKAGE_INFO_FILE,rsa,zf) - fi = open(JAMTRACK_PACKAGE_INFO_FILE,'wb') - fi.write(pinfo); - fi.close() - - #get the media details - estr = self.extractMediaMetaData(JAMTRACK_MEDIA_META_FILE,rsa,zf) - data = json.loads(estr) - if DEBUG_ENABLE: - pprint(data) - - try: - coverFileInfoArray = data[JAMTRACK_COVER_ART_TAG] - try: - coverFileInfo= coverFileInfoArray[0] - cfile = coverFileInfo['datafile'] - aesKey = coverFileInfo['key'] - fname = coverFileInfo['filename'] - fsize = coverFileInfo['size'] - padding = coverFileInfo['padding'] - try: - fdata = zf.read(cfile) - except KeyError: - print 'ERROR: Did not find %s in zip file' % cfile - sys.exit() - else: - self.decryptAesFile(aesKey,fdata,fname,cfile,padding) - except: - print 'no cover art data' - except: - print 'no cover art' - - #now extract the file - mediaFilesInfo = data["tracks"] - - for mediaFileInfo in mediaFilesInfo: - aesKey = mediaFileInfo['key'] - fname = mediaFileInfo['filename'] - fsize = mediaFileInfo['size'] - tname = mediaFileInfo['trackname'] - padding = mediaFileInfo['padding'] - fdata = '' - try: - fdata = zf.read(tname) - except KeyError: - print 'ERROR: Did not find %s in zip file' % tname - sys.exit() - else: - self.decryptAesFile(aesKey,fdata,fname,tname,padding) - - diff --git a/ruby/lib/py/jam_tracks/jkasset.pyc b/ruby/lib/py/jam_tracks/jkasset.pyc deleted file mode 100644 index efd5ed263d50a59b13574b884d3cc839c725297c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11420 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 YFM9uiZ@(}x0jx5j0wnWS6hXoN4;vafAOHXW diff --git a/ruby/lib/py/jam_tracks/jkcreate.py b/ruby/lib/py/jam_tracks/jkcreate.py deleted file mode 100755 index e2272e17b..000000000 --- a/ruby/lib/py/jam_tracks/jkcreate.py +++ /dev/null @@ -1,249 +0,0 @@ -import jkasset -import json -import jkasset -from pprint import pprint -import random -import sys, getopt -import jkmedia -import base64 -import tempfile -import argparse -from types import * -import unicodedata - -parser ='' -inputfiles = [] - -def help(): - parser.print_help() - - - -def media_arg(string): - #print 'media_arg:', string - try: - fname,ext = string.split('+') - #print fname - if fname == '': - print "### No music file in command line: ", string - raise argparse.ArgumentTypeError("Track file name empty") - - inputfiles.append(string) - except: - if string: - inputfiles.append(string) - pass - - return string - -def loadTracksFromList(fileName): - try: - lines = [line.strip() for line in open(fileName)] - #print lines - global inputfiles - inputfiles = lines - except: - print "Unable to open media filelist", fileName - help() - sys.exit(-1) - -def process(argv): - #print argv - global parser - parser = argparse.ArgumentParser( - prog='jkcreate', - description='JamTrack Packaging Tool', - epilog="Note: Meta file values may be update with title,sku,etc") - - parser.add_argument("-D", "--verbosity", - help="increase output verbosity", - action="store_true") - - parser.add_argument("-t", "--title", metavar='title', type=str, - help="Title of JamTrack. If not specified, the title in the metafile is used.") - - parser.add_argument("-k", "--sku", type=str, - help="The product SKU to use. If not specified, the SKU in the metafile is used.",required=True,) - - parser.add_argument("-c", "--cover", metavar='coverArtFile', type=str, - help="The cover art file. File should be JPEG or PNG.") - - parser.add_argument("-p", "--pkey", metavar='pkey.pem',type=str, - help="The output public key file. File should be .pem",required=True,) - - parser.add_argument("-s", "--skey", metavar='skey.pem', type=str, - help="The output private key file. File should be .pem",required=True,) - - - parser.add_argument("-m", "--meta",metavar='trackMetaFile', type=str, - help="The input JamTrack JSON metafile file. Optional") - - parser.add_argument("-o", "--ofile", metavar='JamTrackPage', type=str, - help="The output JamTrack file (.jkz)",required=True,) - - parser.add_argument("-I", "--infilesList", metavar='musicTrackFileList', type=str, - help=" -I 'file containing tracks'") - - parser.add_argument("-i", "--infiles", metavar='musictrack', type=media_arg, nargs='*', - help=" -i 'Lumme.ogg+Bass Guitar' -i 'Hydrate-Kenny_Beltrey.ogg+Bass Guitar'",) - - - #parser.print_help() - args = parser.parse_args(argv) - #print args - - try: - if args.verbosity: - jkasset.DEBUG_ENABLE = 1 - jkasset.COMPARE_PARSEFILE = True - #args.sku = random.randint(1, 100000000) - except: - pass - - - try: - if args.infilesList: - loadTracksFromList(args.infilesList) - except: - pass - #create json struct from info - metaJson={} - try: - if args.meta: - #try to load json data - try: - json_data=open(args.meta).read() - - metaJson = json.loads(json_data) - if jkasset.DEBUG_ENABLE : - pprint(metaJson) - except: - print 'Error processing json from file: ', args.meta - help() - sys.exit(-1) - except: - print 'No meta file specified' - pass - - jdata={} - mdata=[] - instdata={} - - if any(metaJson): - try: - #pickup the instrument data passed in - if any - instdata = metaJson["TrackInstrument"] - except: - pass - - i = 0 - trackLength = {} - trackLenStats=[] - - #print inputfiles - - if not any(inputfiles) : - print 'No track files specified ' - help() - sys.exit(-1) - - for mediaFileInfo in inputfiles: - #update track name and instrument name - #print mediaFileInfo - mediaFileInfo.strip(); - mlist = mediaFileInfo.split("+") - mediaFileInfo = mlist[0] - - trackName = 'track_%02d' % (i) - i = i + 1 - mdata.append({"name" : mediaFileInfo, "trackName": trackName}) - - try: - minfo = jkmedia.JKTrackInfo(mediaFileInfo) ; - tinfo={}; - tinfo['bitrate']=minfo.bitrate - tinfo['length'] = minfo.trackLength - trackLenStats.append(minfo.trackLength) - trackLength[trackName]= tinfo - except: - print("Cannot find file or read media information for '%s'"% mediaFileInfo) - #help() - sys.exit(-1) - - try: - instdata[trackName]=mlist[1] - except: - if instdata.has_key(trackName) == False: - print "No instrument for track ",trackName - pass - - try: - if args.title: - try: - title = base64.b64encode(args.title) - metaJson['Title']=title - except: - print 'Error processing title : ', title - help() - sys.exit(-1) - - if args.sku: - try: - metaJson['SKU']=args.sku - except: - print 'Error sku' - help() - sys.exit(-1) - - metaJson['METADATA_VER'] = 1 - - except: - #These are both optional.. - pass - - if any(instdata): - metaJson['TrackInstrument'] = instdata - if len (instdata) > len(mdata): - print "Number of instruments ", len (instdata)," in meta data greater than number of tracks ",len(mdata) - help() - sys.exit(-1) - - if any(trackLength): - metaJson['TrackData'] = trackLength - - #dump the meta data to temp file - - metaTmp = tempfile.NamedTemporaryFile(); - metaTmp.write(json.dumps(metaJson)) - metaTmp.seek(0) - - jdata["tracks"] = mdata - jdata["rsa_priv_file"]= args.skey - jdata["rsa_pub_file"]= args.pkey - jdata["container_file"]= args.ofile - - #jdata["jamktrack_info"]= trackMetaFile - jdata["jamktrack_info"]= metaTmp.name - - try: - jdata["coverart"]= args.cover - except: - print 'No cover art specified' - - data = json.dumps(jdata) - #pprint(data) - - fname = 'jt_metadata.json' - fo = open(fname, 'w') - fo.write(data) - fo.close() - - jas = jkasset.JKasset(fname) - - -if __name__ == "__main__": - random.seed() - process(sys.argv[1:]) - - - diff --git a/ruby/lib/py/jam_tracks/jkmedia.py b/ruby/lib/py/jam_tracks/jkmedia.py deleted file mode 100644 index 035abc3a4..000000000 --- a/ruby/lib/py/jam_tracks/jkmedia.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env python -import base64 -import os -import binascii -import json -import sys -from optparse import OptionParser -#from import mutagen.ogg -#from import mutagen.oggvorbis - -class JKTrackInfo: - trackLength =0 - bitrate = 0 - mediaFile=''; - - - def __init__(self, fileName): - #get the file extension - self.mediaFile = fileName - fileName, fileExtension = os.path.splitext(self.mediaFile) - audio = '' - if fileExtension == '.ogg' : - from mutagen.oggvorbis import OggVorbis - audio = OggVorbis(self.mediaFile) - elif fileExtension == '.wav' : - from mutagen.wavpack import WavPack - audio = WavPack(self.mediaFile) - elif fileExtension == '.mp3' : - from mutagen.mp3 import MP3 - audio = MP3(self.mediaFile) - elif fileExtension == '.flac' : - from mutagen.flac import FLAC - audio = FLAC(self.mediaFile) - else: - print "Unsupported filetype '",fileExtension, "' for file ", self.mediaFile - raise Exception("Unsupported filetype") - - print audio.info.length, audio.info.bitrate - self.bitrate = audio.info.bitrate - self.trackLength = audio.info.length - - - - diff --git a/ruby/lib/py/jam_tracks/jkrsa.py b/ruby/lib/py/jam_tracks/jkrsa.py deleted file mode 100755 index 4d0e38600..000000000 --- a/ruby/lib/py/jam_tracks/jkrsa.py +++ /dev/null @@ -1,194 +0,0 @@ -# RSA Cipher -# http://inventwithpython.com/hacking (BSD Licensed) - -from Crypto.Cipher import PKCS1_OAEP -from Crypto.PublicKey import RSA -from Crypto import Random -import base64 -import sys - -class JKrsa: - # IMPORTANT: The block size MUST be less than or equal to the key size! - # (Note: The block size is in bytes, the key size is in bits. There - # are 8 bits in 1 byte.) - #DEFAULT_BLOCK_SIZE = 128-2 # 128 bytes - DEFAULT_BLOCK_SIZE = 64 # 128 bytes - - - RSA_KEYSIZE = 1024 - BYTE_SIZE = 256 # One byte has 256 different values. - RSAKey=''; - PADDING = 0 - PADDING_CHARACTER =b'\0' - encipher=''; - decipher='' - - def __init__(self, operation='encode'): - self.op = operation - - def updatePadSize(self,dlen): - self.PADDING = (self.DEFAULT_BLOCK_SIZE - dlen % self.DEFAULT_BLOCK_SIZE) - - def getPadding(self): - return self.PADDING - - def pad(self,s): - self.updatePadSize(len(s)) - pdata = self.PADDING * self.PADDING_CHARACTER - if len(pdata) != self.PADDING : - sys.exit('Pad data length %d does match %d. Check python pad character size!', - (len(pdata), self.PADDING)) - s = s + pdata - return s - - def EncodeRSA(self,c, s): - #print 'len=',len(s) - estr = self.encipher.encrypt(s) - #print 'elen=',len(estr) - #print estr - return estr - - def DecodeRSA(self,c, e): - #print 'len=',len(e) - str = self.decipher.decrypt(e) - return str - - def generateKey(self, privateFileName='pri_Key.pem', publicFileName='pub_Key.pem'): - self.RSAKey = RSA.generate(self.RSA_KEYSIZE) - - public_key = self.RSAKey.publickey().exportKey("PEM") - private_key = self.RSAKey.exportKey("PEM") - - if privateFileName: - f = open(privateFileName,'w') - f.write(self.RSAKey.exportKey("PEM")) - f.close() - - f = open(privateFileName,'r') - skey = RSA.importKey(f.read()) - self.decipher = PKCS1_OAEP.new(skey) - f.close() - - if publicFileName: - f = open(publicFileName,'w') - f.write(self.RSAKey.publickey().exportKey("PEM")) - f.close() - - f = open(publicFileName,'r') - pkey = RSA.importKey(f.read()) - self.encipher = PKCS1_OAEP.new(pkey) - f.close() - - return private_key, public_key - - def loadKey(self, fileName='pri_Key.pem'): - f = open(fileName,'r') - self.RSAKey = RSA.importKey(f.read()) - self.decipher = PKCS1_OAEP.new(self.RSAKey) - f.close() - - def WriteFileData(self,outfile,data): - #ddata = base64.b64decode(data) - fi = open(outfile,'wb') - fi.write(data); - - def LoadFileData(self, infile): - fi = open(infile,'rb') - data = fi.read(); - #edata = base64.b64encode(data) - return data - - def WriteRSAFileData(self, outfile,data): - # These files are assumed to be in cypher text - so we won't b64 encode/decode them - fo = open(outfile,'wb') - #edata = base64.b64encode(data) - fo.write(data) - - - def LoadRSAFileData(self, infile): - # These files are assumed to be in cypher text - so we won't b64 encode/decode them - fi = open(infile,'rb') - data = fi.read(); - return data - - def getBlocksFromText(self,message, blockSize=DEFAULT_BLOCK_SIZE): - # Converts a string message to a list of block integers. Each integer - # represents 128 (or whatever blockSize is set to) string characters. - # messageBytes = message.encode('ascii') # convert the string to bytes - blockInts = [] - blockInts = [message[i:i+blockSize] for i in range(0, len(message), blockSize)] - return blockInts - - - def encryptMesssage(self, message, blockSize=DEFAULT_BLOCK_SIZE): - keySize = self.RSAKey.size() + 1; - - # Check that key size is greater than block size. - if keySize < blockSize * 8: # * 8 to convert bytes to bits - sys.exit('ERROR: Block size is %d bits and key size is %d bits.' - ' The RSA cipher requires the block size to be equal to' - ' or less than the key size. Either decrease the' - ' block size or use different keys.' % (blockSize * 8, keySize)) - - if len(message) % blockSize : # * 8 to convert bytes to bits - sys.exit('ERROR: Block size is %d and data size is %d .' - ' The RSA cipher requires the block size to be equal to' - ' or less than the key size. Either decrease the' - ' block size or use different keys.' % (blockSize, len(message))) - - - encryptedContent = [] - for block in self.getBlocksFromText(message, blockSize): - encryptedContent_ = self.EncodeRSA(self.RSAKey,block); - encryptedContent.extend(encryptedContent_) - - return encryptedContent - - def decryptMesssage(self, message): - #print message - #split in equal chunks - decryptedContent = [] - for block in self.getBlocksFromText(message, self.DEFAULT_BLOCK_SIZE*2): - decryptedContent_ = self.DecodeRSA(self.RSAKey,block); - decryptedContent.extend(decryptedContent_) - - return decryptedContent - - - def encryptAndWriteToFile(self, keyFilename, inFile,outFilename, blockSize=DEFAULT_BLOCK_SIZE): - # Using a key from a key file, encrypt the message and save it to a - # file. Returns the encrypted message string. - self.loadKey(keyFilename) - keySize = self.RSAKey.size()+1; - - # Check that key size is greater than block size. - if keySize < blockSize * 8: # * 8 to convert bytes to bits - sys.exit('ERROR: Block size is %s bits and key size is %s bits.' - ' The RSA cipher requires the block size to be equal to' - ' or less than the key size. Either decrease the' - ' block size or use different keys.' % (blockSize * 8, keySize)) - - # Encrypt the message - message = self.LoadFileData(inFile); - message = self.pad(message) - - encryptedContent = self.encryptMesssage(message); - self.WriteRSAFileData(outFilename,encryptedContent) - # Also return the encrypted string. - #return encryptedContent - - - def readFromFileAndDecrypt(self, keyFilename, inFile, outFilename): - # Using a key from a key file, read an encrypted message from a file - # and then decrypt it. Returns the decrypted message string. - self.loadKey(keyFilename) - - # Read in the message length and the encrypted message from the file. - message = self.LoadRSAFileData(inFile) - - decryptMessage = self.decryptMesssage(message) - self.WriteFileData(outFilename,decryptMessage) - - - - diff --git a/ruby/lib/py/jam_tracks/jktrack.py b/ruby/lib/py/jam_tracks/jktrack.py deleted file mode 100755 index 3e0dcd4b3..000000000 --- a/ruby/lib/py/jam_tracks/jktrack.py +++ /dev/null @@ -1,104 +0,0 @@ -import jkaes -import tempfile -from jkaes import JKaes -import os -class JKtrack: - mediaFile = '' - Name = '' - aesInKeyFile='' - aesCipher = '' - outTmpFile ='' - outPutFile = '' - def __init__(self, fileName, title='Unknown', inKeyFile=''): - self.mediaFile = fileName - self.Name = title - self.aesInKeyFile = inKeyFile - self.aesCipher = JKaes(self.mediaFile, self.aesInKeyFile) - - def getOutputFileName(self): - return self.outPutFile; - - def getKeyStr(self): - return self.aesCipher.getKeyStr(); - - def getEncryptPadding(self): - return self.aesCipher.getPadding() - - def setKeyStr(self,key): - self.aesCipher.setKey(key); - - def getKeyModeStr(self): - return self.aesCipher.getKeyModeStr() - - def getCoverEncryptFilePackageName(self,instance=-1): - #preserve the extentsion on the original name - fileName, fileExtension = os.path.splitext(self.mediaFile) - if instance != -1: - nm = "cover_%02d%s" % (instance,fileExtension) - return nm - else: - nm = os.path.basename(self.mediaFile) - return nm - - def getEncryptFilePackageName(self,instance=-1): - #preserve the extentsion on the original name - fileName, fileExtension = os.path.splitext(self.mediaFile) - if instance != -1: - nm = "track_%02d%s" % (instance,fileExtension) - return nm - else: - nm = os.path.basename(self.mediaFile) - return nm - - def Encode(self, outFile): - self.outPutFile = outFile - self.aesCipher.EncodeFileAndWrite( self.mediaFile, outFile) - - def EncodeToTmpFile(self): - self.outTmpFile = tempfile.NamedTemporaryFile() - self.Encode(self.outTmpFile.name) - - def EncodeWithKeyToFile(self,keyFile,outFile): - self.aesCipher.readAesKeyFile(keyFile) - self.Encode(outFile) - - def Decode(self, outFile): - self.outPutFile = outFile - self.aesCipher.DecodeFileAndWrite( self.mediaFile, outFile) - - def DecodeToTmpFile(self): - self.outTmpFile = tempfile.NamedTemporaryFile() - self.Decode(self.outTmpFile.name) - - def DecodeWithKeyToFile(self,keyFile,outFile): - self.aesCipher.readAesKeyFile(keyFile) - self.Decode(outFile) - - - def DecodeBufferToFile(self,msg,outfile,padding=0): - dmsg = self.aesCipher.DecodeMsg(msg) - fi = open(outfile,'wb') - fi.write(dmsg); - if padding > 0: - #trunckage the file by - fi.seek(-padding, os.SEEK_END) - fi.truncate() - fi.close() - return dmsg - - def DecodeU64Buffer(self,msg): - return self.aesCipher.DecodeU64Msg(msg) - - def setKey(self, keyFile): - self.aesCipher.readAesKeyFile(keyFile) - - def saveKey(self,keyFile): - self.aesCipher.writeAesKeytoFile(keyFile) - - def saveEncFileCopy(self,outfile): - fi = open(self.outPutFile,'rb') - fi.seek(0) - dmsg = fi.read(); - fo = open(outfile,'wb') - fo.write(dmsg); - return dmsg \ No newline at end of file diff --git a/ruby/lib/py/jam_tracks/jktrack.pyc b/ruby/lib/py/jam_tracks/jktrack.pyc deleted file mode 100644 index d065cbbe6aee5c45bc364f9d9dba2207c51113bd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5712 zcmc&&>uwuG6h3P^cc)3yB)tJDl!6hoE)^6(xs^0XrD~`qsY*j7Yvb9Z>&x2OowN-~ zeJGux2C{o}8Dk4FTbI}78`cdu2hzUbmkbMv~PFnV{I+2sJ zK1tFzGbMIZfwI^!1*XMf|Ag#=!BYxPDm){0N;k}kEh}(Z?6d-NVoxb>M(m6N^I~Te zSP+YCb9%y}*fTnLR_we2=fo~3a9-@90vE)rR?dP|hbAhKheGsA>wH0qvyE~k%sg-2aMpJ=n-5o_= ztHnv|YBds{ksb*q^r_R-dQGKAW20mQCdq}|8YcmEw%vnmN2Ybf@Sq_Qej3MbMefIK z*Y#K_z&T821#}X;O8YdXJ?)KvzSZM^`wbaWq2o;GZq=nH!G)Ot+6A=#Ks03!q926T z#>v)6Q0;QrFDLbMr`yAP?f@GUX|Ji>%wwW2d$`|TpkeP-6)?_as)CB@g;oTg6lq2~ zL$oHkn@0=HU*v$;|D6@l&b$@f@7U<1EQwb17FtfY=88I*U`(L^RS!P_K#+t3Ah=N> zMXKUSL0;mA(1!#NNV6#EEv5m+$)ao)WbtK5c1yCFzluTc)qKIyorq;c5oZh~_0F>> ztzEf(!(PdJ&?GsXOk*FTmIj}!+lpQE!Wj*VRQE=hMG2f)-_bCl8{;HH#H&Y!x^fgU z!{+h}7bRKTNgNMP+C#JvN*8)vB3rrXGuk~s;tjVne!q0ih|5phb z=npEvEP_bGWNGNJDt2=o&_LYg0v9M|Lsu|wa20JxDTv4+*Vl{mLoB{tq||rct}kOS z$m)j;oZ-e-S-o&WQ!}(6ANH9wfE~_Cecn#&qZB5ok~t8Bm?4nkG{FHJk}w$mJT0FB z%19}*aX*`pYxU&!0C;!Y)6iP$`7>R!AdS+|7^}4f{mVt|E?viwZ)Q!qhM7(prdqPN zJejYxyX4i-sMTfFCz2N%3cJvS<2H?4E+2wGPpKh3TB} zSszM{*Y58`e`ELNQjcY2{@4pp8oVds_JGx6GkEh34mFqAy~~c_#9TplB6SiC+4I&n z{qt^<}WzB17Y z;rWIVRG9$&LDBD-xSwrlpzCy_L@(ISnFsQ#TGZ6csiny3^76gf^2!|@xO6+IhuFse zi)&XF>1Evq5c1o`v3c^)uuHh>t93owas$13Gah1mVX*gXez(238KpkFtY9{qMJK^r zFcVA%<-$Dr$w8>;$5`$*CA!>3UAcc;*P%x}K$y!F;jRpsw znbC=2?40pTM -s ' - - -def process(argv): - - global parser - parser = argparse.ArgumentParser( - prog='jkunpack', - description='JamTrack UnPackaging Tool', - epilog="Note: Files may be overwritten during unpacking") - - parser.add_argument("-D", "--verbosity", - help="increase output verbosity", - action="store_true") - - parser.add_argument("-s", "--skey", metavar='skey.pem', type=str, - help="The private key file. File should be .pem",required=True) - - parser.add_argument("-i", "--ifile", metavar='JamTrackPage', type=str, - help="The input JamTrack file (.jkz)",required=True) - - parser.print_help() - args = parser.parse_args(argv) - print args - - try: - if args.verbosity: - jkasset.DEBUG_ENABLE = 1 - jkasset.COMPARE_PARSEFILE = True - except: - pass - - - jas = jkasset.JKasset(args.ifile,'decode',args.skey) - -if __name__ == "__main__": - random.seed() - process(sys.argv[1:]) - - - diff --git a/ruby/lib/py/jam_tracks/jkzify.rb b/ruby/lib/py/jam_tracks/jkzify.rb deleted file mode 100755 index c5ef594fd..000000000 --- a/ruby/lib/py/jam_tracks/jkzify.rb +++ /dev/null @@ -1,98 +0,0 @@ -#!/usr/bin/env ruby -# -# Usage: jkzify [path to JamTrack WAVs] [destination for jkz and key files] -# -# Please review the comments, this script needs improvement -- I just threw it together to help me validate jkcreate.py -# - -wd = Dir.getwd -tracks_location = ARGV.shift -output_location = ARGV.shift -script_location = File.dirname(File.expand_path(__FILE__)) - - - -# first try to catch any issues with input arguments -def halt msg; warn msg; exit; end -halt "usage: jkzify path/to/wavs/ path/to/output" unless tracks_location && output_location -halt "path not found" unless File.exist?(tracks_location) && File.exist?(output_location) -# end - - - -# step 1: gather metadata from the track/folder names (and user input) -title = tracks_location.split('/').last.gsub(/'/,'') -puts "i think this track is called \"#{title}\"..." -shortname = title.gsub(/[aeiou\s]/,'').squeeze[0...8].downcase -puts "i'll call it #{shortname} for now" -puts "what sku do you want for this JamTrack? (press ENTER for '#{shortname}')" -input = STDIN.gets.chomp.strip -sku = input.empty? ? shortname : input - - - -# step 2: move WAVs to our own workspace -def renamed(t); t.gsub(/\s|'/,'_'); end #little helper - -Dir.chdir wd -`rm -rf #{shortname}` if File.exist? shortname -`mkdir #{shortname}` - -Dir.chdir tracks_location -Dir.glob('*.wav') do |track| - new_name = renamed track - `cp "#{track}" "#{wd}/#{shortname}/#{new_name}"` - puts "Copied from " + track + " to #{wd}/#{shortname}/" + new_name -end -Dir.chdir "#{wd}/#{shortname}" - - - -# step 3: generate OGGs from WAVs -oggs_location = title.downcase.gsub('\'','').split.join('_') + '_oggs/' -puts "generating OGGs in a separate folder #{oggs_location} (this may take some time)" -`mkdir #{oggs_location}` unless File.exist?(oggs_location) -Dir.glob('*.wav') do |track| - input = track - output = "#{oggs_location}#{File.basename(track, '.wav')}.ogg" - #puts "sox #{input} #{output}" - `sox #{input} #{output}` -end - - -# step 4: get all track parameters (names and instruments) -track_params = {} -track_files = [] -suffix = '\.wav' -Dir.foreach tracks_location do |track| - next if track =~ /Master Mix/i - next unless track =~ /#{suffix}$/i - print '.' - track.gsub!(/#{suffix}$/i, '') - instrument = track.split('-').drop(1).map(&:strip).join(' ') - parameter = "#{shortname}/#{oggs_location}#{renamed track}.ogg+#{instrument}" - #parameter.gsub!(/ /, '\ ') #spaces in filenames - track_params[instrument] = "-i '#{parameter}'" - track_files << parameter -end -puts "OK, #{track_params.length} tracks found" - - - -# step 5: create list of track titles -Dir.chdir wd -tracks_filename = "#{shortname}.txt" -File.open(tracks_filename, 'w+') { |f| f.puts track_files } - - - -# step 6: run jkcreate.py given all these parameters -public_key = "#{output_location}#{shortname}-pub.pem" -private_key = "#{output_location}#{shortname}-priv.pem" -output_jkz = "#{output_location}#{shortname}.jkz" -#puts "python jkcreate.py -D -k #{sku} -c art.png -p #{public_key} -s #{private_key} -I #{tracks_filename} -o #{output_jkz} -t '#{title}'" -`python jkcreate.py -D -k #{sku} -c art.png -p #{public_key} -s #{private_key} -I #{tracks_filename} -o #{output_jkz} -t '#{title}'` - - - -Dir.chdir wd \ No newline at end of file From 8ac9d19e73737c6591be851cb8790f57e280ad16 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Mon, 15 Dec 2014 18:02:51 -0600 Subject: [PATCH 39/72] VRFS-2481 : Use jam_tracks object hierarchy to create jkz file, and upload to AWS. Meshed out spec to verify creation and upload, including a real ogg file (required by the python tool). --- ruby/lib/jam_ruby/jam_tracks_manager.rb | 92 +++++++++++++----- ruby/lib/jam_ruby/models/jam_track.rb | 6 +- ruby/lib/jam_ruby/models/jam_track_right.rb | 19 ++++ ruby/spec/files/on.ogg | Bin 0 -> 4303 bytes .../jam_ruby/models/jam_track_right_spec.rb | 54 +++++++++- 5 files changed, 141 insertions(+), 30 deletions(-) create mode 100644 ruby/spec/files/on.ogg diff --git a/ruby/lib/jam_ruby/jam_tracks_manager.rb b/ruby/lib/jam_ruby/jam_tracks_manager.rb index 1c4b481aa..5f63365f1 100644 --- a/ruby/lib/jam_ruby/jam_tracks_manager.rb +++ b/ruby/lib/jam_ruby/jam_tracks_manager.rb @@ -1,6 +1,8 @@ require 'json' require 'tempfile' require 'open3' +require 'fileutils' +require 'open-uri' module JamRuby @@ -8,31 +10,69 @@ module JamRuby class JamTracksManager class << self def save_jam_track_jkz(user, jam_track) - py_root = File.expand_path(File.join("..", "..", "jamtracks")) - tmp_dir = Dir::Tmpname.make_tmpname("/tmp/jamtrack", nil) - puts "Executing python in #{py_root}, outputting to #{tmp_dir}" - - - sku=jam_track.id - tracks_filename=tmp_dir + "/jamtrack.info" - output_jkz="tmp_dir/#{jam_track.name.tableize}.jkz" - title=jam_track.name - - # From http://stackoverflow.com/questions/690151/getting-output-of-system-calls-in-ruby/5970819#5970819: - #Open3.popen3("ls") do |stdin, stdout, stderr, wait_thr| - Open3.popen3("python", - "#{py_root}/jkcreate.py", - "-D -k #{sku} -c art.png -p #{tmp_dir}/pkey.pem -s #{tmp_dir}/skey.pem -I #{tracks_filename} -o #{output_jkz} -t '#{title}'" - ) do |stdin, stdout, stderr, wait_thr| - pid = wait_thr.pid - exit_status = wait_thr.value - err = stderr.read(1000) - raise ArgumentError, "Error calling python script: #{err}" if err.present? - end - - tmp_dir + jam_track_right = jam_track.right_for_user(user) + raise ArgumentError if jam_track_right.nil? + save_jam_track_right_jkz(jam_track_right) end - end - end -end + def save_jam_track_right_jkz(jam_track_right) + jam_track = jam_track_right.jam_track + py_root = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "..", "jamtracks")) + + Dir.mktmpdir do |tmp_dir| + jam_file_opts="" + jam_track.jam_track_tracks.each do |jam_track_track| + nm=jam_track_track.filename + nm.gsub!(" ", "_") + track_filename = File.join(tmp_dir, nm) + track_url = jam_track_track.sign_url + copy_url_to_file(track_url, track_filename) + jam_file_opts << " -i '#{track_filename}+#{jam_track_track.part}'" + end + + sku=jam_track.id + title=jam_track.name + output_jkz=File.join(tmp_dir, "#{title.parameterize}.jkz") + py_file = File.join(py_root, "jkcreate.py") + puts "Executing python source in #{py_file}, outputting to #{tmp_dir} (#{output_jkz})" + + # From http://stackoverflow.com/questions/690151/getting-output-of-system-calls-in-ruby/5970819#5970819: + cli = "python #{py_file} -D -k #{sku} -p #{tmp_dir}/pkey.pem -s #{tmp_dir}/skey.pem #{jam_file_opts} -o #{output_jkz} -t '#{title}'" + Open3.popen3(cli) do |stdin, stdout, stderr, wait_thr| + pid = wait_thr.pid + exit_status = wait_thr.value + err = stderr.read(1000) + out = stdout.read(1000) + #puts "stdout: #{out}, stderr: #{err}" + raise ArgumentError, "Error calling python script: #{out}" if out && (out.index("No track files specified") || out.index("Cannot find file")) + raise ArgumentError, "Error calling python script: #{err}" if err.present? + jam_track_right[:url] + + jam_track_right.url.store!(File.open(output_jkz)) + jam_track_right.save! + end + end # mktmpdir + jam_track_right + end # save_jam_track_jkz + + def copy_url_to_file(url, filename) + uri = URI(url) + open(filename, 'wb') do |io| + Net::HTTP.start(uri.host, uri.port) do |http| + request = Net::HTTP::Get.new uri + http.request request do |response| + response_code = response.code.to_i + unless response_code >= 200 && response_code <= 299 + raise "bad status code: #{response_code}. body: #{response.body}" + end + response.read_body do |chunk| + io.write chunk + end + end + end + end + end # copy_url_to_file + + end # self + end # class +end # module diff --git a/ruby/lib/jam_ruby/models/jam_track.rb b/ruby/lib/jam_ruby/models/jam_track.rb index f93087046..3658a2fc1 100644 --- a/ruby/lib/jam_ruby/models/jam_track.rb +++ b/ruby/lib/jam_ruby/models/jam_track.rb @@ -43,7 +43,7 @@ module JamRuby has_many :jam_track_tracks, :class_name => "JamRuby::JamTrackTrack", order: 'position ASC' - has_many :jam_track_rights, :class_name => "JamRuby::JamTrackRight", inverse_of: 'jam_track', :foreign_key => "jam_track_id" + has_many :jam_track_rights, :class_name => "JamRuby::JamTrackRight" #, inverse_of: 'jam_track', :foreign_key => "jam_track_id" has_many :owners, :through => :jam_track_rights, :class_name => "JamRuby::User", :source => :user accepts_nested_attributes_for :jam_track_tracks, allow_destroy: true @@ -107,6 +107,10 @@ module JamRuby #JamTrack.joins(:recording).joins(:recording => :claimed_recordings) end + def right_for_user(user) + jam_track_rights.where("user_id=?", user).first + end + private diff --git a/ruby/lib/jam_ruby/models/jam_track_right.rb b/ruby/lib/jam_ruby/models/jam_track_right.rb index 33a036a05..81b6cbc10 100644 --- a/ruby/lib/jam_ruby/models/jam_track_right.rb +++ b/ruby/lib/jam_ruby/models/jam_track_right.rb @@ -2,6 +2,7 @@ module JamRuby # describes what users have rights to which tracks class JamTrackRight < ActiveRecord::Base + include JamRuby::S3ManagerMixin attr_accessible :user, :jam_track, :user_id, :jam_track_id, :url, :md5, :length, :download_count belongs_to :user, class_name: "JamRuby::User" # the owner, or purchaser of the jam_track belongs_to :jam_track, class_name: "JamRuby::JamTrack" @@ -16,6 +17,15 @@ module JamRuby mount_uploader :url, JamTrackRightUploader MAX_JAM_TRACK_DOWNLOADS = 1000 + + def store_dir + "#{jam_track.store_dir}/rights" + end + + # create name of the file + def filename + "#{jam_track.name}.jkz" + end def verify_download_count if (self.download_count < 0 || self.download_count > MAX_JAM_TRACK_DOWNLOADS) && !@current_user.admin @@ -28,5 +38,14 @@ module JamRuby end + # creates a short-lived URL that has access to the object. + # the idea is that this is used when a user who has the rights to this tries to download this JamTrack + # we would verify their rights (can_download?), and generates a URL in response to the click so that they can download + # but the url is short lived enough so that it wouldn't be easily shared + def sign_url(expiration_time = 120) + s3_manager.sign_url(self[:url], {:expires => expiration_time, :response_content_type => 'jkz', :secure => false}) + end + + end end diff --git a/ruby/spec/files/on.ogg b/ruby/spec/files/on.ogg new file mode 100644 index 0000000000000000000000000000000000000000..743d6e3aa6ca187978d95bb768ef449e0ccc6071 GIT binary patch literal 4303 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 literal 0 HcmV?d00001 diff --git a/ruby/spec/jam_ruby/models/jam_track_right_spec.rb b/ruby/spec/jam_ruby/models/jam_track_right_spec.rb index 1df21358f..5dea3c4fe 100644 --- a/ruby/spec/jam_ruby/models/jam_track_right_spec.rb +++ b/ruby/spec/jam_ruby/models/jam_track_right_spec.rb @@ -1,7 +1,8 @@ require 'spec_helper' describe JamTrackRight do - + include UsesTempFiles + include CarrierWave::Test::Matchers it "created" do jam_track_right = FactoryGirl.create(:jam_track_right) @@ -28,13 +29,60 @@ describe JamTrackRight do end end - it "saves JKZ" do + describe "JKZ" do + before(:all) do + original_storage = JamTrackTrackUploader.storage = :fog + original_storage = JamTrackRightUploader.storage = :fog + end + + after(:all) do + JamTrackTrackUploader.storage = @original_storage + JamTrackRightUploader.storage = @original_storage + end + + before(:each) do + #content_for_file('abc') + end + + it "should fail if no tracks" do user = FactoryGirl.create(:user) jam_track = FactoryGirl.create(:jam_track) right = JamTrackRight.create(:user=>user, :jam_track=>jam_track) expect { JamRuby::JamTracksManager.save_jam_track_jkz(user, jam_track) - }.to_not raise_error + }.to raise_error(ArgumentError) + end + + it "should create" do + ogg_path = File.join('spec', 'files', 'on.ogg') + user = FactoryGirl.create(:user) + #jam_track = FactoryGirl.create(:jam_track) + jam_track_track = FactoryGirl.create(:jam_track_track) + jam_track = jam_track_track.jam_track + + uploader = JamTrackTrackUploader.new(jam_track_track, :url) + uploader.store!(File.open(ogg_path)) + jam_track_track.save! + + jam_track_track[:url].should == jam_track_track.store_dir + '/' + jam_track_track.filename + + # verify it's on S3 + s3 = S3Manager.new(APP_CONFIG.aws_bucket, APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key) + s3.exists?(jam_track_track[:url]).should be_true + s3.length(jam_track_track[:url]).should == File.size?(ogg_path) + + jam_track_right = JamTrackRight.create(:user=>user, :jam_track=>jam_track) + expect { + JamRuby::JamTracksManager.save_jam_track_jkz(user, jam_track) + }.to_not raise_error(ArgumentError) + jam_track_right.reload + jam_track_right[:url].should == jam_track_right.store_dir + '/' + jam_track_right.filename + + # verify it's on S3 + s3 = S3Manager.new(APP_CONFIG.aws_bucket, APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key) + s3.exists?(jam_track_right[:url]).should be_true + s3.length(jam_track_right[:url]).should > File.size?(ogg_path) + end end end From 361d88a0fa29baba4c555ac7874dbf091913efa0 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Mon, 15 Dec 2014 19:21:27 -0600 Subject: [PATCH 40/72] VRFS-1959 : jam_tracks#list_downloads API and model methods, as well as associated tests to verify. --- db/up/jam_track_updates.sql | 2 ++ ruby/lib/jam_ruby/models/jam_track.rb | 26 +++++++++++++++---- .../jam_ruby/models/jam_track_right_spec.rb | 8 ++++++ .../controllers/api_jam_tracks_controller.rb | 14 +++++----- web/config/routes.rb | 4 +-- .../api_jam_tracks_controller_spec.rb | 22 ++++++++++++++++ 6 files changed, 62 insertions(+), 14 deletions(-) create mode 100644 web/spec/controllers/api_jam_tracks_controller_spec.rb diff --git a/db/up/jam_track_updates.sql b/db/up/jam_track_updates.sql index 8eaaf1bd6..4b4a1b3b3 100644 --- a/db/up/jam_track_updates.sql +++ b/db/up/jam_track_updates.sql @@ -30,6 +30,8 @@ ALTER TABLE jam_track_rights ADD COLUMN md5 VARCHAR, ADD COLUMN length INTEGER NOT NULL DEFAULT 0, ADD COLUMN download_count INTEGER NOT NULL DEFAULT 0, + ADD COLUMN created_at timestamp without time zone NOT NULL, + ADD COLUMN updated_at timestamp without time zone NOT NULL, ALTER COLUMN jam_track_id TYPE BIGINT USING 0, ALTER COLUMN jam_track_id SET NOT NULL, ADD CONSTRAINT jam_track_rights_user_id_fkey FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, diff --git a/ruby/lib/jam_ruby/models/jam_track.rb b/ruby/lib/jam_ruby/models/jam_track.rb index 3658a2fc1..1badc209e 100644 --- a/ruby/lib/jam_ruby/models/jam_track.rb +++ b/ruby/lib/jam_ruby/models/jam_track.rb @@ -100,19 +100,35 @@ module JamRuby since = 0 unless since || since == '' # guard against nil downloads = [] - user.jam_track_rights.limit(limit).collect do |jt_right| - downloads << jt_right + user.jam_track_rights + .limit(limit) + .where('jam_track_rights.id > ?', since) + .each do |jam_track_right| + downloads << { + :type => "jam_track", + :id => jam_track_right.id.to_s, + :jam_track_id => jam_track_right.jam_track_id, + :length => jam_track_right.length, + :md5 => jam_track_right.md5, + :url => jam_track_right.url, + :created_at => jam_track_right.created_at, + :next => jam_track_right.id + } end - #JamTrack.joins(:recording).joins(:recording => :claimed_recordings) + next_date = downloads[-1][:next] if downloads.length > 0 + next_date = since if next_date.nil? # echo back to the client the same value they passed in, if there are no results + + { + 'downloads' => downloads, + 'next' => next_date.to_s + } end def right_for_user(user) jam_track_rights.where("user_id=?", user).first end - - private def sanitize_active_admin diff --git a/ruby/spec/jam_ruby/models/jam_track_right_spec.rb b/ruby/spec/jam_ruby/models/jam_track_right_spec.rb index 5dea3c4fe..c93c86947 100644 --- a/ruby/spec/jam_ruby/models/jam_track_right_spec.rb +++ b/ruby/spec/jam_ruby/models/jam_track_right_spec.rb @@ -17,6 +17,14 @@ describe JamTrackRight do end + it "lists" do + jam_track_right = FactoryGirl.create(:jam_track_right) + jam_tracks = JamTrack.list_downloads(jam_track_right.user) + jam_tracks.should have_key('downloads') + jam_tracks.should have_key('next') + jam_tracks['downloads'].should have(1).items + end + describe "validations" do it "one purchase per user/jam_track combo" do user = FactoryGirl.create(:user) diff --git a/web/app/controllers/api_jam_tracks_controller.rb b/web/app/controllers/api_jam_tracks_controller.rb index 8bbc2c179..67081b365 100644 --- a/web/app/controllers/api_jam_tracks_controller.rb +++ b/web/app/controllers/api_jam_tracks_controller.rb @@ -1,15 +1,15 @@ -class ApiJamtracksController < ApiController +class ApiJamTracksController < ApiController # have to be signed in currently to see this screen before_filter :api_signed_in_user respond_to :json - def index - data = JamTrack.index current_user, params - - @jamtracks = data[0] - @next = data[1] + def list_downloads + begin + render :json => JamTrack.list_downloads(current_user, params[:limit], params[:since]), :status => 200 + rescue + render :json => { :message => "could not produce list of files" }, :status => 403 + end end - end diff --git a/web/config/routes.rb b/web/config/routes.rb index 38cc43398..e6ec314e0 100644 --- a/web/config/routes.rb +++ b/web/config/routes.rb @@ -190,8 +190,8 @@ SampleApp::Application.routes.draw do match '/music_notations/:id' => 'api_music_notations#download', :via => :get, :as => :download_music_notation # Jamtracks - match '/jamtracks' => 'api_jamtracks#index', :via => :get - + match '/jamtracks/downloads' => 'api_jam_tracks#list_downloads', :via => :get, :as => 'api_jam_tracks_list_downloads' + # Shopping carts match '/shopping_carts/add_jamtrack' => 'api_shopping_carts#add_jamtrack', :via => :post match '/shopping_carts' => 'api_shopping_carts#index', :via => :get diff --git a/web/spec/controllers/api_jam_tracks_controller_spec.rb b/web/spec/controllers/api_jam_tracks_controller_spec.rb new file mode 100644 index 000000000..dc72d910a --- /dev/null +++ b/web/spec/controllers/api_jam_tracks_controller_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe ApiJamTracksController do + + before(:each) do + @user = FactoryGirl.create(:user) + @jam_track = FactoryGirl.create(:jam_track) + controller.current_user = @user + end + + describe "download" do + let(:mix) { FactoryGirl.create(:mix) } + + it "list download" do + right = JamTrackRight.create(:user=>@user, :jam_track=>@jam_track) + get :list_downloads + response.should be_success + json = JSON.parse(response.body) + json['downloads'].should have(1).items + end + end +end From ff42a84598c1a993eb16d8bc3c2e217df653f2eb Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Mon, 15 Dec 2014 20:30:46 -0600 Subject: [PATCH 41/72] VRFS-2482 : Jam Tracks cleanup and initial spec. --- db/up/jam_track_updates.sql | 2 ++ ruby/lib/jam_ruby.rb | 2 ++ ruby/lib/jam_ruby/jam_tracks_manager.rb | 1 + ruby/lib/jam_ruby/models/jam_track.rb | 2 ++ ruby/lib/jam_ruby/models/jam_track_right.rb | 13 +++++--- .../resque/scheduled/jam_tracks_cleaner.rb | 30 +++++++++++++++++++ .../jam_ruby/models/jam_track_right_spec.rb | 13 +++++--- web/config/scheduler.yml | 5 ++++ 8 files changed, 60 insertions(+), 8 deletions(-) create mode 100644 ruby/lib/jam_ruby/resque/scheduled/jam_tracks_cleaner.rb diff --git a/db/up/jam_track_updates.sql b/db/up/jam_track_updates.sql index 4b4a1b3b3..f084bfd53 100644 --- a/db/up/jam_track_updates.sql +++ b/db/up/jam_track_updates.sql @@ -30,6 +30,8 @@ ALTER TABLE jam_track_rights ADD COLUMN md5 VARCHAR, ADD COLUMN length INTEGER NOT NULL DEFAULT 0, ADD COLUMN download_count INTEGER NOT NULL DEFAULT 0, + ADD COLUMN signed BOOLEAN NOT NULL DEFAULT FALSE, + ADD COLUMN downloaded BOOLEAN NOT NULL DEFAULT FALSE, ADD COLUMN created_at timestamp without time zone NOT NULL, ADD COLUMN updated_at timestamp without time zone NOT NULL, ALTER COLUMN jam_track_id TYPE BIGINT USING 0, diff --git a/ruby/lib/jam_ruby.rb b/ruby/lib/jam_ruby.rb index b3b1a3ee1..f4cd8da0f 100755 --- a/ruby/lib/jam_ruby.rb +++ b/ruby/lib/jam_ruby.rb @@ -55,6 +55,8 @@ require "jam_ruby/resque/scheduled/active_music_session_cleaner" require "jam_ruby/resque/scheduled/score_history_sweeper" require "jam_ruby/resque/scheduled/scheduled_music_session_cleaner" require "jam_ruby/resque/scheduled/recordings_cleaner" +require "jam_ruby/resque/scheduled/jam_tracks_cleaner" +require "jam_ruby/resque/jam_tracks_builder" require "jam_ruby/resque/google_analytics_event" require "jam_ruby/resque/batch_email_job" require "jam_ruby/mq_router" diff --git a/ruby/lib/jam_ruby/jam_tracks_manager.rb b/ruby/lib/jam_ruby/jam_tracks_manager.rb index 5f63365f1..4ea820d5a 100644 --- a/ruby/lib/jam_ruby/jam_tracks_manager.rb +++ b/ruby/lib/jam_ruby/jam_tracks_manager.rb @@ -49,6 +49,7 @@ module JamRuby jam_track_right[:url] jam_track_right.url.store!(File.open(output_jkz)) + jam_track_right.signed=true jam_track_right.save! end end # mktmpdir diff --git a/ruby/lib/jam_ruby/models/jam_track.rb b/ruby/lib/jam_ruby/models/jam_track.rb index 1badc209e..06cb59851 100644 --- a/ruby/lib/jam_ruby/models/jam_track.rb +++ b/ruby/lib/jam_ruby/models/jam_track.rb @@ -129,6 +129,8 @@ module JamRuby jam_track_rights.where("user_id=?", user).first end + + private def sanitize_active_admin diff --git a/ruby/lib/jam_ruby/models/jam_track_right.rb b/ruby/lib/jam_ruby/models/jam_track_right.rb index 81b6cbc10..672bae8a8 100644 --- a/ruby/lib/jam_ruby/models/jam_track_right.rb +++ b/ruby/lib/jam_ruby/models/jam_track_right.rb @@ -12,9 +12,11 @@ module JamRuby validate :verify_download_count validates_uniqueness_of :user_id, scope: :jam_track_id + # Uploads the JKZ: mount_uploader :url, JamTrackRightUploader + before_destroy :delete_s3_files MAX_JAM_TRACK_DOWNLOADS = 1000 @@ -33,11 +35,11 @@ module JamRuby end end - # Create user-specific JKZ for the associated jam_track: - def create_jkz - + def self.ready_to_clean + JamTrackRight.where("downloaded=TRUE AND updated_at <= ?", 5.minutes.ago).limit(1000) end + # creates a short-lived URL that has access to the object. # the idea is that this is used when a user who has the rights to this tries to download this JamTrack # we would verify their rights (can_download?), and generates a URL in response to the click so that they can download @@ -46,6 +48,9 @@ module JamRuby s3_manager.sign_url(self[:url], {:expires => expiration_time, :response_content_type => 'jkz', :secure => false}) end - + def delete_s3_files + remove_url! + end + end end diff --git a/ruby/lib/jam_ruby/resque/scheduled/jam_tracks_cleaner.rb b/ruby/lib/jam_ruby/resque/scheduled/jam_tracks_cleaner.rb new file mode 100644 index 000000000..b8fe904bc --- /dev/null +++ b/ruby/lib/jam_ruby/resque/scheduled/jam_tracks_cleaner.rb @@ -0,0 +1,30 @@ +require 'json' +require 'resque' +require 'resque-retry' +require 'net/http' +require 'digest/md5' + +module JamRuby + + # periodically scheduled to find jam_tracks to cleanup + class JamTracksCleaner + extend Resque::Plugins::LonelyJob + + @queue = :jam_tracks_cleaner + + @@log = Logging.logger[JamTracksCleaner] + + def self.lock_timeout + # this should be enough time to make sure the job has finished, but not so long that the system isn't recovering from a abandoned job + 1200 + end + + def self.perform + JamTrackRight.downloaded.each do |jam_track_right| + @@log.debug("deleting files for jam_track_right #{jam_track_right.id}") + jam_track_right.delete_s3_files + end + end + end + +end \ No newline at end of file diff --git a/ruby/spec/jam_ruby/models/jam_track_right_spec.rb b/ruby/spec/jam_ruby/models/jam_track_right_spec.rb index c93c86947..911540b98 100644 --- a/ruby/spec/jam_ruby/models/jam_track_right_spec.rb +++ b/ruby/spec/jam_ruby/models/jam_track_right_spec.rb @@ -80,16 +80,21 @@ describe JamTrackRight do s3.length(jam_track_track[:url]).should == File.size?(ogg_path) jam_track_right = JamTrackRight.create(:user=>user, :jam_track=>jam_track) - expect { + #expect { JamRuby::JamTracksManager.save_jam_track_jkz(user, jam_track) - }.to_not raise_error(ArgumentError) + #}.to_not raise_error(ArgumentError) jam_track_right.reload jam_track_right[:url].should == jam_track_right.store_dir + '/' + jam_track_right.filename # verify it's on S3 + url = jam_track_right[:url] s3 = S3Manager.new(APP_CONFIG.aws_bucket, APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key) - s3.exists?(jam_track_right[:url]).should be_true - s3.length(jam_track_right[:url]).should > File.size?(ogg_path) + s3.exists?(url).should be_true + s3.length(url).should > File.size?(ogg_path) + + JamTrackRight.ready_to_clean.count.should == 0 + jam_track_right.destroy + s3.exists?(url).should be_false end end diff --git a/web/config/scheduler.yml b/web/config/scheduler.yml index 9108a382d..ed15a7365 100644 --- a/web/config/scheduler.yml +++ b/web/config/scheduler.yml @@ -14,6 +14,11 @@ IcecastSourceCheck: class: "JamRuby::IcecastSourceCheck" description: "Finds icecast mounts that need their 'sourced' state to change, but haven't in some time" +JamTracksCleaner: + cron: "0 5 * * *" + class: "JamRuby::UnusedMusicNotationCleaner" + description: "Remove unused music notations" + CleanupFacebookSignup: cron: "30 2 * * *" class: "JamRuby::CleanupFacebookSignup" From 795cb6f536f350ef3b8d0c8d3c7febdb0ac27132 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Mon, 15 Dec 2014 23:57:57 -0600 Subject: [PATCH 42/72] Dedicated spec for testing cleaner. Some cleanup/renaming. --- db/up/jam_track_updates.sql | 2 +- ruby/lib/jam_ruby/jam_tracks_manager.rb | 1 + ruby/lib/jam_ruby/models/jam_track_right.rb | 4 +- .../resque/scheduled/jam_tracks_cleaner.rb | 2 +- .../resque/jam_tracks_cleaner_spec.rb | 53 +++++++++++++++++++ 5 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 ruby/spec/jam_ruby/resque/jam_tracks_cleaner_spec.rb diff --git a/db/up/jam_track_updates.sql b/db/up/jam_track_updates.sql index f084bfd53..4b6b609f7 100644 --- a/db/up/jam_track_updates.sql +++ b/db/up/jam_track_updates.sql @@ -31,7 +31,7 @@ ALTER TABLE jam_track_rights ADD COLUMN length INTEGER NOT NULL DEFAULT 0, ADD COLUMN download_count INTEGER NOT NULL DEFAULT 0, ADD COLUMN signed BOOLEAN NOT NULL DEFAULT FALSE, - ADD COLUMN downloaded BOOLEAN NOT NULL DEFAULT FALSE, + ADD COLUMN downloaded_since_sign BOOLEAN NOT NULL DEFAULT FALSE, ADD COLUMN created_at timestamp without time zone NOT NULL, ADD COLUMN updated_at timestamp without time zone NOT NULL, ALTER COLUMN jam_track_id TYPE BIGINT USING 0, diff --git a/ruby/lib/jam_ruby/jam_tracks_manager.rb b/ruby/lib/jam_ruby/jam_tracks_manager.rb index 4ea820d5a..7055ddb57 100644 --- a/ruby/lib/jam_ruby/jam_tracks_manager.rb +++ b/ruby/lib/jam_ruby/jam_tracks_manager.rb @@ -50,6 +50,7 @@ module JamRuby jam_track_right.url.store!(File.open(output_jkz)) jam_track_right.signed=true + jam_track_right.downloaded_since_sign=false jam_track_right.save! end end # mktmpdir diff --git a/ruby/lib/jam_ruby/models/jam_track_right.rb b/ruby/lib/jam_ruby/models/jam_track_right.rb index 672bae8a8..1a22ace5e 100644 --- a/ruby/lib/jam_ruby/models/jam_track_right.rb +++ b/ruby/lib/jam_ruby/models/jam_track_right.rb @@ -36,7 +36,7 @@ module JamRuby end def self.ready_to_clean - JamTrackRight.where("downloaded=TRUE AND updated_at <= ?", 5.minutes.ago).limit(1000) + JamTrackRight.where("downloaded_since_sign=? AND updated_at <= ?", true, 5.minutes.ago).limit(1000) end @@ -45,7 +45,7 @@ module JamRuby # we would verify their rights (can_download?), and generates a URL in response to the click so that they can download # but the url is short lived enough so that it wouldn't be easily shared def sign_url(expiration_time = 120) - s3_manager.sign_url(self[:url], {:expires => expiration_time, :response_content_type => 'jkz', :secure => false}) + s3_manager.sign_url(self[:url], {:expires => expiration_time, :secure => false}) end def delete_s3_files diff --git a/ruby/lib/jam_ruby/resque/scheduled/jam_tracks_cleaner.rb b/ruby/lib/jam_ruby/resque/scheduled/jam_tracks_cleaner.rb index b8fe904bc..3ef073b01 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/jam_tracks_cleaner.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/jam_tracks_cleaner.rb @@ -20,7 +20,7 @@ module JamRuby end def self.perform - JamTrackRight.downloaded.each do |jam_track_right| + JamTrackRight.ready_to_clean.each do |jam_track_right| @@log.debug("deleting files for jam_track_right #{jam_track_right.id}") jam_track_right.delete_s3_files end diff --git a/ruby/spec/jam_ruby/resque/jam_tracks_cleaner_spec.rb b/ruby/spec/jam_ruby/resque/jam_tracks_cleaner_spec.rb new file mode 100644 index 000000000..36cb5a067 --- /dev/null +++ b/ruby/spec/jam_ruby/resque/jam_tracks_cleaner_spec.rb @@ -0,0 +1,53 @@ +require 'spec_helper' + +describe JamTracksCleaner do + include UsesTempFiles + include CarrierWave::Test::Matchers + RIGHT_NAME = 'abc.jkz' + in_directory_with_file(RIGHT_NAME) + + before (:all) do + @user = FactoryGirl.create(:user) + @jam_track = FactoryGirl.create(:jam_track) + original_storage = JamTrackRightUploader.storage = :fog + end + + after(:all) do + JamTrackRightUploader.storage = @original_storage + end + + + before(:each) do + content_for_file('abc') + end + + it "should clean" do + jam_track_right = JamTrackRight.create(:user=>@user, :jam_track=>@jam_track) + jam_track_right.signed=true + jam_track_right + + jam_track_right.url.store!(File.open(RIGHT_NAME)) + jam_track_right.downloaded_since_sign=true + jam_track_right.save! + + jam_track_right[:url].should == jam_track_right.store_dir + '/' + jam_track_right.filename + jam_track_right.reload + + # Should exist after uploading: + url = jam_track_right[:url] + s3 = S3Manager.new(APP_CONFIG.aws_bucket, APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key) + + url.should_not be_nil + s3 = S3Manager.new(APP_CONFIG.aws_bucket, APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key) + s3.exists?(jam_track_right[:url]).should be_true + + JamRuby::JamTracksCleaner.perform + s3.exists?(url).should be_true + s3 = S3Manager.new(APP_CONFIG.aws_bucket, APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key) + jam_track_right.update_attribute("updated_at", 6.minutes.ago) + + # But not after running cleaner job: + JamRuby::JamTracksCleaner.perform + s3.exists?(url).should be_false + end +end \ No newline at end of file From 34cb617f5fc4ee5ce5af2f6d5cb6fbaa81922023 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Wed, 17 Dec 2014 17:19:36 -0600 Subject: [PATCH 43/72] VRFS-1960 : download API for JamTracks, including a new message type, rescue job that builds the JKZ, and specs that exercise it all. --- db/up/jam_track_updates.sql | 3 + pb/src/client_container.proto | 10 +++ ruby/lib/jam_ruby/jam_tracks_manager.rb | 12 ++- ruby/lib/jam_ruby/message_factory.rb | 11 +++ ruby/lib/jam_ruby/models/jam_track.rb | 38 ++-------- ruby/lib/jam_ruby/models/jam_track_right.rb | 21 +++++- ruby/lib/jam_ruby/models/jam_track_track.rb | 3 +- ruby/lib/jam_ruby/models/notification.rb | 6 ++ .../lib/jam_ruby/resque/jam_tracks_builder.rb | 26 +++++++ .../jam_ruby/models/jam_track_right_spec.rb | 3 +- .../controllers/api_jam_tracks_controller.rb | 29 +++++++- web/config/routes.rb | 3 +- .../api_jam_tracks_controller_spec.rb | 70 +++++++++++++++++- web/spec/files/on.ogg | Bin 0 -> 4303 bytes 14 files changed, 190 insertions(+), 45 deletions(-) create mode 100644 ruby/lib/jam_ruby/resque/jam_tracks_builder.rb create mode 100644 web/spec/files/on.ogg diff --git a/db/up/jam_track_updates.sql b/db/up/jam_track_updates.sql index 4b6b609f7..257b3b7aa 100644 --- a/db/up/jam_track_updates.sql +++ b/db/up/jam_track_updates.sql @@ -32,9 +32,12 @@ ALTER TABLE jam_track_rights ADD COLUMN download_count INTEGER NOT NULL DEFAULT 0, ADD COLUMN signed BOOLEAN NOT NULL DEFAULT FALSE, ADD COLUMN downloaded_since_sign BOOLEAN NOT NULL DEFAULT FALSE, + ADD COLUMN last_downloaded_at timestamp without time zone NULL, ADD COLUMN created_at timestamp without time zone NOT NULL, ADD COLUMN updated_at timestamp without time zone NOT NULL, ALTER COLUMN jam_track_id TYPE BIGINT USING 0, ALTER COLUMN jam_track_id SET NOT NULL, ADD CONSTRAINT jam_track_rights_user_id_fkey FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, ADD CONSTRAINT jam_track_rights_jam_track_id_fkey FOREIGN KEY(jam_track_id) REFERENCES jam_tracks(id) ON DELETE CASCADE; + +ALTER TABLE notifications ADD COLUMN jam_track_right_id BIGINT REFERENCES jam_track_rights(id); \ No newline at end of file diff --git a/pb/src/client_container.proto b/pb/src/client_container.proto index 901797515..3835e8239 100644 --- a/pb/src/client_container.proto +++ b/pb/src/client_container.proto @@ -73,6 +73,9 @@ message ClientMessage { SOURCE_DOWN_REQUESTED = 251; SOURCE_UP = 252; SOURCE_DOWN = 253; + + // jamtracks notifications + JAM_TRACK_SIGN_COMPLETE = 260; TEST_SESSION_MESSAGE = 295; @@ -172,6 +175,9 @@ message ClientMessage { optional SourceUp source_up = 252; optional SourceDown source_down = 253; + // jamtracks notification + optional JamTrackSignComplete jam_track_sign_complete=260; + // Client-Session messages (to/from) optional TestSessionMessage test_session_message = 295; @@ -586,6 +592,10 @@ message SourceDown { optional string music_session = 1; // music session id } +message JamTrackSignComplete { + required string jam_track_right = 1; // jam track right id +} + // route_to: session // a test message used by ruby-client currently. just gives way to send out to rest of session message TestSessionMessage { diff --git a/ruby/lib/jam_ruby/jam_tracks_manager.rb b/ruby/lib/jam_ruby/jam_tracks_manager.rb index 7055ddb57..6594ffa9f 100644 --- a/ruby/lib/jam_ruby/jam_tracks_manager.rb +++ b/ruby/lib/jam_ruby/jam_tracks_manager.rb @@ -26,10 +26,13 @@ module JamRuby nm.gsub!(" ", "_") track_filename = File.join(tmp_dir, nm) track_url = jam_track_track.sign_url + puts "track_url: #{track_url}" copy_url_to_file(track_url, track_filename) + copy_url_to_file(track_url, File.join(".", nm)) jam_file_opts << " -i '#{track_filename}+#{jam_track_track.part}'" end - + puts "LS + " + `ls -la '#{tmp_dir}'` + sku=jam_track.id title=jam_track.name output_jkz=File.join(tmp_dir, "#{title.parameterize}.jkz") @@ -44,11 +47,11 @@ module JamRuby err = stderr.read(1000) out = stdout.read(1000) #puts "stdout: #{out}, stderr: #{err}" - raise ArgumentError, "Error calling python script: #{out}" if out && (out.index("No track files specified") || out.index("Cannot find file")) raise ArgumentError, "Error calling python script: #{err}" if err.present? + raise ArgumentError, "Error calling python script: #{out}" if out && (out.index("No track files specified") || out.index("Cannot find file")) jam_track_right[:url] - jam_track_right.url.store!(File.open(output_jkz)) + jam_track_right.url.store!(File.open(output_jkz, "rb")) jam_track_right.signed=true jam_track_right.downloaded_since_sign=false jam_track_right.save! @@ -59,11 +62,12 @@ module JamRuby def copy_url_to_file(url, filename) uri = URI(url) - open(filename, 'wb') do |io| + open(filename, 'w+b') do |io| Net::HTTP.start(uri.host, uri.port) do |http| request = Net::HTTP::Get.new uri http.request request do |response| response_code = response.code.to_i + puts "Response from server was #{response_code} / #{response.message}" unless response_code >= 200 && response_code <= 299 raise "bad status code: #{response_code}. body: #{response.body}" end diff --git a/ruby/lib/jam_ruby/message_factory.rb b/ruby/lib/jam_ruby/message_factory.rb index 6d1d34fb7..9587b2c03 100644 --- a/ruby/lib/jam_ruby/message_factory.rb +++ b/ruby/lib/jam_ruby/message_factory.rb @@ -712,6 +712,17 @@ module JamRuby ) end + def jam_track_sign_complete(jam_track_right_id) + signed = Jampb::JamTrackSignComplete.new() + + Jampb::ClientMessage.new( + :type => ClientMessage::Type::JAM_TRACK_SIGN_COMPLETE, + :route_to => USER_TARGET_PREFIX + client_id, + :jam_track_sign_complete => signed + ) + end + + def recording_master_mix_complete(receiver_id, recording_id, claimed_recording_id, band_id, msg, notification_id, created_at) recording_master_mix_complete = Jampb::RecordingMasterMixComplete.new( :recording_id => recording_id, diff --git a/ruby/lib/jam_ruby/models/jam_track.rb b/ruby/lib/jam_ruby/models/jam_track.rb index 06cb59851..1951a6949 100644 --- a/ruby/lib/jam_ruby/models/jam_track.rb +++ b/ruby/lib/jam_ruby/models/jam_track.rb @@ -50,7 +50,7 @@ module JamRuby # create storage directory that will house this jam_track, as well as def store_dir - "jam_tracks/#{created_at.strftime('%m-%d-%Y')}/#{id}" + "jam_tracks/#{id}" end # create name of the file @@ -66,35 +66,14 @@ module JamRuby s3_manager.sign_url(self[:url], {:expires => expiration_time, :response_content_type => 'audio/jka', :secure => false}) end + def can_download?(user) owners.include?(user) end - def self.index user, options = {} - limit = options[:limit] - limit ||= 20 - limit = limit.to_i - - start = options[:start].presence - start = start.to_i || 0 - - query = JamTrack.joins(:jam_track_tracks) - .offset(start) - .limit(limit) - - query = query.where("jam_tracks.genre_id = '#{options[:genre]}'") unless options[:genre].blank? - query = query.where("jam_track_tracks.instrument_id = '#{options[:instrument]}'") unless options[:instrument].blank? - query = query.where("jam_tracks.sales_region = '#{options[:availability]}'") unless options[:availability].blank? - query = query.group("jam_tracks.id") - - if query.length == 0 - [query, nil] - elsif query.length < limit - [query, nil] - else - [query, start + limit] - end - end + def right_for_user(user) + jam_track_rights.where("user_id=?", user).first + end def self.list_downloads(user, limit = 100, since = 0) since = 0 unless since || since == '' # guard against nil @@ -125,13 +104,8 @@ module JamRuby } end - def right_for_user(user) - jam_track_rights.where("user_id=?", user).first - end - - - private + private def sanitize_active_admin self.genre_id = nil if self.genre_id == '' diff --git a/ruby/lib/jam_ruby/models/jam_track_right.rb b/ruby/lib/jam_ruby/models/jam_track_right.rb index 1a22ace5e..796ce89d2 100644 --- a/ruby/lib/jam_ruby/models/jam_track_right.rb +++ b/ruby/lib/jam_ruby/models/jam_track_right.rb @@ -12,7 +12,6 @@ module JamRuby validate :verify_download_count validates_uniqueness_of :user_id, scope: :jam_track_id - # Uploads the JKZ: mount_uploader :url, JamTrackRightUploader @@ -39,7 +38,6 @@ module JamRuby JamTrackRight.where("downloaded_since_sign=? AND updated_at <= ?", true, 5.minutes.ago).limit(1000) end - # creates a short-lived URL that has access to the object. # the idea is that this is used when a user who has the rights to this tries to download this JamTrack # we would verify their rights (can_download?), and generates a URL in response to the click so that they can download @@ -51,6 +49,25 @@ module JamRuby def delete_s3_files remove_url! end + + def enqueue + begin + Resque.enqueue(JamTracksBuilder, self.id) + rescue Exception => e + # implies redis is down. we don't update started_at by bailing out here + false + end + + # avoid db validations + JamTrackRight.where(:id => self.id).update_all(:last_downloaded_at => Time.now) + + true + end + + def update_download_count(count=1) + self.download_count = self.download_count + count + self.last_downloaded_at = Time.now + end end end diff --git a/ruby/lib/jam_ruby/models/jam_track_track.rb b/ruby/lib/jam_ruby/models/jam_track_track.rb index fd527b9eb..9328d7570 100644 --- a/ruby/lib/jam_ruby/models/jam_track_track.rb +++ b/ruby/lib/jam_ruby/models/jam_track_track.rb @@ -9,7 +9,7 @@ module JamRuby mount_uploader :url, JamTrackTrackUploader - attr_accessible :track_type, :instrument, :instrument_id, :position, :part, :url, as: :admin + attr_accessible :jam_track_id, :track_type, :instrument, :instrument_id, :position, :part, :url, as: :admin validates :position, presence: true, numericality: {only_integer: true}, length: {in: 1..1000} validates :part, length: {maximum: 20} @@ -36,6 +36,7 @@ module JamRuby # we would verify their rights (can_download?), and generates a URL in response to the click so that they can download # but the url is short lived enough so that it wouldn't be easily shared def sign_url(expiration_time = 120) + puts "Signing: #{self[:url]}" s3_manager.sign_url(self[:url], {:expires => expiration_time, :response_content_type => 'audio/ogg', :secure => false}) end diff --git a/ruby/lib/jam_ruby/models/notification.rb b/ruby/lib/jam_ruby/models/notification.rb index f66d9e8ef..a908cfe74 100644 --- a/ruby/lib/jam_ruby/models/notification.rb +++ b/ruby/lib/jam_ruby/models/notification.rb @@ -13,6 +13,7 @@ module JamRuby belongs_to :band, :class_name => "JamRuby::Band", :foreign_key => "band_id" belongs_to :music_session, :class_name => "JamRuby::MusicSession", :foreign_key => "music_session_id" belongs_to :recording, :class_name => "JamRuby::Recording", :foreign_key => "recording_id" + belongs_to :jam_track_right, :class_name => "JamRuby::JamTrackRight", :foreign_key => "jam_track_right_id" validates :target_user, :presence => true validates :message, length: {minimum: 1, maximum: 400}, no_profanity: true, if: :text_message? @@ -1186,6 +1187,11 @@ module JamRuby end end + def send_jam_track_signed(jam_track_right) + msg = @@message_factory.jam_track_signed(jam_track_right) + @@mq_router.publish_to_all_clients(msg) + end + def send_client_update(product, version, uri, size) msg = @@message_factory.client_update( product, version, uri, size) diff --git a/ruby/lib/jam_ruby/resque/jam_tracks_builder.rb b/ruby/lib/jam_ruby/resque/jam_tracks_builder.rb new file mode 100644 index 000000000..310755fce --- /dev/null +++ b/ruby/lib/jam_ruby/resque/jam_tracks_builder.rb @@ -0,0 +1,26 @@ +require 'json' +require 'resque' +require 'resque-retry' +require 'net/http' +require 'digest/md5' + +module JamRuby + class JamTracksBuilder + @queue = :jam_tracks_builder + @@log = Logging.logger[JamTracksBuilder] + attr_accessor :jam_track_right_id + + def self.perform(jam_track_right_id) + jam_track_builder = JamTracksBuilder.new() + jam_track_builder.jam_track_right_id = jam_track_right_id + jam_track_builder.run + end + + def run + @@log.info("jam_track_builder job starting. jam_track_right_id #{jam_track_right_id}") + @jam_track_right = JamTrackRight.find(jam_track_right_id) + JamRuby::JamTracksManager.save_jam_track_right_jkz(@jam_track_right) + puts "Signed jamtrack to #{@jam_track_right[:url]}" + end + end +end \ No newline at end of file diff --git a/ruby/spec/jam_ruby/models/jam_track_right_spec.rb b/ruby/spec/jam_ruby/models/jam_track_right_spec.rb index 911540b98..6670ba996 100644 --- a/ruby/spec/jam_ruby/models/jam_track_right_spec.rb +++ b/ruby/spec/jam_ruby/models/jam_track_right_spec.rb @@ -64,12 +64,11 @@ describe JamTrackRight do it "should create" do ogg_path = File.join('spec', 'files', 'on.ogg') user = FactoryGirl.create(:user) - #jam_track = FactoryGirl.create(:jam_track) jam_track_track = FactoryGirl.create(:jam_track_track) jam_track = jam_track_track.jam_track uploader = JamTrackTrackUploader.new(jam_track_track, :url) - uploader.store!(File.open(ogg_path)) + uploader.store!(File.open(ogg_path, 'rb')) jam_track_track.save! jam_track_track[:url].should == jam_track_track.store_dir + '/' + jam_track_track.filename diff --git a/web/app/controllers/api_jam_tracks_controller.rb b/web/app/controllers/api_jam_tracks_controller.rb index 67081b365..2107aec6b 100644 --- a/web/app/controllers/api_jam_tracks_controller.rb +++ b/web/app/controllers/api_jam_tracks_controller.rb @@ -2,7 +2,8 @@ class ApiJamTracksController < ApiController # have to be signed in currently to see this screen before_filter :api_signed_in_user - + before_filter :lookup_jam_track, :only => [ :download ] + respond_to :json def list_downloads @@ -12,4 +13,30 @@ class ApiJamTracksController < ApiController render :json => { :message => "could not produce list of files" }, :status => 403 end end + + def download + if @jam_track_right.valid? + puts "Success" + + if (@jam_track_right && @jam_track_right.signed && @jam_track_right.url.present? &&@jam_track_right.url.file.exists?) + @jam_track_right.update_download_count + @jam_track_right.save! + redirect_to @jam_track_right.sign_url + else + @jam_track_right.enqueue + render :json => { :message => "not available, digitally signing Jam Track offline." }, :status => 202 + end + else + puts "#@jam_track_right.errors: #{@jam_track_right.errors.inspect}" + render :json => { :message => "download limit surpassed" }, :status => 403 + end + end + +private + def lookup_jam_track + @jam_track_right = JamTrackRight.where("jam_track_id=? AND user_id=?", params[:id], current_user).first + puts "@jam_track_right: #{@jam_track_right.nil?}" + raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR unless @jam_track_right + end + end diff --git a/web/config/routes.rb b/web/config/routes.rb index e6ec314e0..b0730ffef 100644 --- a/web/config/routes.rb +++ b/web/config/routes.rb @@ -191,7 +191,8 @@ SampleApp::Application.routes.draw do # Jamtracks match '/jamtracks/downloads' => 'api_jam_tracks#list_downloads', :via => :get, :as => 'api_jam_tracks_list_downloads' - + match '/jamtracks/:id/download' => 'api_jam_tracks#download', :via => :get, :as => 'api_jam_tracks_download' + # Shopping carts match '/shopping_carts/add_jamtrack' => 'api_shopping_carts#add_jamtrack', :via => :post match '/shopping_carts' => 'api_shopping_carts#index', :via => :get diff --git a/web/spec/controllers/api_jam_tracks_controller_spec.rb b/web/spec/controllers/api_jam_tracks_controller_spec.rb index dc72d910a..35da57118 100644 --- a/web/spec/controllers/api_jam_tracks_controller_spec.rb +++ b/web/spec/controllers/api_jam_tracks_controller_spec.rb @@ -1,7 +1,18 @@ require 'spec_helper' describe ApiJamTracksController do + include CarrierWave::Test::Matchers + before(:all) do + original_storage = JamTrackTrackUploader.storage = :fog + original_storage = JamTrackRightUploader.storage = :fog + end + + after(:all) do + JamTrackTrackUploader.storage = @original_storage + JamTrackRightUploader.storage = @original_storage + end + before(:each) do @user = FactoryGirl.create(:user) @jam_track = FactoryGirl.create(:jam_track) @@ -9,8 +20,6 @@ describe ApiJamTracksController do end describe "download" do - let(:mix) { FactoryGirl.create(:mix) } - it "list download" do right = JamTrackRight.create(:user=>@user, :jam_track=>@jam_track) get :list_downloads @@ -19,4 +28,61 @@ describe ApiJamTracksController do json['downloads'].should have(1).items end end + + describe "with a JamTrack" do + before(:each) do + JamTrackRight.destroy_all + # Create a working JamTrack for these tests. The integrity + # of this process is checked in other tests: + @ogg_path = File.join('spec', 'files', 'on.ogg') + @jam_track = FactoryGirl.create(:jam_track) #jam_track_track.jam_track + jam_track_track = @jam_track.jam_track_tracks.first + + uploader = JamTrackTrackUploader.new(jam_track_track, :url) + uploader.store!(File.open(@ogg_path, 'rb')) + #jam_track_track.url.store!(File.open(ogg_path, "rb")) + jam_track_track.save! + jam_track_track.reload + ResqueSpec.reset! + end + + it "download depends on rights" do + s3 = S3Manager.new(APP_CONFIG.aws_bucket, APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key) + get :download, :id => @jam_track.id + response.status.should == 403 + + right = JamTrackRight.create(:user=>@user, :jam_track=>@jam_track) + get :download, :id => @jam_track.id + response.status.should == 202 + right.download_count.should eq(0) + + JamTracksBuilder.should have_queued(right.id).in(:jam_tracks_builder) + + qname = "#{ResqueSpec.queue_name(JamRuby::JamTracksBuilder)}" + expect(ResqueSpec.peek(qname).present?).to eq(true) + ResqueSpec.perform_next(qname) + + JamTracksBuilder.should_not have_queued(right.id).in(:jam_tracks_builder) + right.reload + right.download_count.should eq(0) + + get :download, :id => @jam_track.id + response.status.should == 302 + response.location.should =~ /.*#{Regexp.escape(right.filename)}.*/ + #response.should redirect_to(/.*#{Regexp.escape(right.filename)}.*/) + #response.should redirect_to("%r{.*#{Regexp.escape(right.filename)}.*}") + + #right.reload + #s3.exists?(response.location).should be_true + #puts "s3.length (response.location): #{s3.length (response.location)}" + #s3.length (response.location).should > File.size?(@ogg_path) + + right.reload + right.download_count.should eq(1) + + notifications = Notification.where(:jam_track_right_id => right.id) + notifications.count.should == 1 + puts "notifications.first.inspect: #{notifications.first.inspect}" + end + end end diff --git a/web/spec/files/on.ogg b/web/spec/files/on.ogg new file mode 100644 index 0000000000000000000000000000000000000000..743d6e3aa6ca187978d95bb768ef449e0ccc6071 GIT binary patch literal 4303 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 literal 0 HcmV?d00001 From bbee617c4b5693d6aab9afb7159720602c3cbfb3 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Mon, 22 Dec 2014 14:23:05 -0600 Subject: [PATCH 44/72] VRFS-1960 : Jam Track Signed Notification, usage in notificationPanel, and spec. --- db/up/jam_track_updates.sql | 1 + pb/src/client_container.proto | 2 +- .../jam_ruby/constants/notification_types.rb | 2 ++ ruby/lib/jam_ruby/message_factory.rb | 10 ++++++---- ruby/lib/jam_ruby/models/jam_track_right.rb | 12 ++++++++++++ ruby/lib/jam_ruby/models/notification.rb | 17 ++++++++++++++--- ruby/lib/jam_ruby/resque/jam_tracks_builder.rb | 14 +++++++++++--- web/app/assets/javascripts/notificationPanel.js | 6 ++++++ .../api_jam_tracks_controller_spec.rb | 4 +--- 9 files changed, 54 insertions(+), 14 deletions(-) diff --git a/db/up/jam_track_updates.sql b/db/up/jam_track_updates.sql index 257b3b7aa..3ee39e190 100644 --- a/db/up/jam_track_updates.sql +++ b/db/up/jam_track_updates.sql @@ -32,6 +32,7 @@ ALTER TABLE jam_track_rights ADD COLUMN download_count INTEGER NOT NULL DEFAULT 0, ADD COLUMN signed BOOLEAN NOT NULL DEFAULT FALSE, ADD COLUMN downloaded_since_sign BOOLEAN NOT NULL DEFAULT FALSE, + ADD COLUMN last_signed_at timestamp without time zone NULL, ADD COLUMN last_downloaded_at timestamp without time zone NULL, ADD COLUMN created_at timestamp without time zone NOT NULL, ADD COLUMN updated_at timestamp without time zone NOT NULL, diff --git a/pb/src/client_container.proto b/pb/src/client_container.proto index 3835e8239..bac794c88 100644 --- a/pb/src/client_container.proto +++ b/pb/src/client_container.proto @@ -593,7 +593,7 @@ message SourceDown { } message JamTrackSignComplete { - required string jam_track_right = 1; // jam track right id + required int32 jam_track_right_id = 1; // jam track right id } // route_to: session diff --git a/ruby/lib/jam_ruby/constants/notification_types.rb b/ruby/lib/jam_ruby/constants/notification_types.rb index 515f56d04..87dbe14e5 100644 --- a/ruby/lib/jam_ruby/constants/notification_types.rb +++ b/ruby/lib/jam_ruby/constants/notification_types.rb @@ -45,5 +45,7 @@ module NotificationTypes # general purpose text message TEXT_MESSAGE = "TEXT_MESSAGE" + # Jam Tracks: + JAM_TRACK_SIGN_COMPLETE = "JAM_TRACK_SIGN_COMPLETE" end \ No newline at end of file diff --git a/ruby/lib/jam_ruby/message_factory.rb b/ruby/lib/jam_ruby/message_factory.rb index 9587b2c03..e85401065 100644 --- a/ruby/lib/jam_ruby/message_factory.rb +++ b/ruby/lib/jam_ruby/message_factory.rb @@ -712,13 +712,15 @@ module JamRuby ) end - def jam_track_sign_complete(jam_track_right_id) - signed = Jampb::JamTrackSignComplete.new() + def jam_track_sign_complete(receiver_id, jam_track_right_id) + signed = Jampb::JamTrackSignComplete.new( + :jam_track_right_id => jam_track_right_id + ) Jampb::ClientMessage.new( :type => ClientMessage::Type::JAM_TRACK_SIGN_COMPLETE, - :route_to => USER_TARGET_PREFIX + client_id, - :jam_track_sign_complete => signed + :route_to => USER_TARGET_PREFIX + receiver_id, #:route_to => CLIENT_TARGET, + :jam_track_sign_complete => signed ) end diff --git a/ruby/lib/jam_ruby/models/jam_track_right.rb b/ruby/lib/jam_ruby/models/jam_track_right.rb index 796ce89d2..1593971a8 100644 --- a/ruby/lib/jam_ruby/models/jam_track_right.rb +++ b/ruby/lib/jam_ruby/models/jam_track_right.rb @@ -38,6 +38,18 @@ module JamRuby JamTrackRight.where("downloaded_since_sign=? AND updated_at <= ?", true, 5.minutes.ago).limit(1000) end + def finish_sign(length, md5) + self.last_signed_at = Time.now + self.length = length + self.md5 = md5 + self.signed = true + if save + Notification.send_jam_track_sign_complete(self) + else + raise "Error sending notification #{self.errors}" + end + end + # creates a short-lived URL that has access to the object. # the idea is that this is used when a user who has the rights to this tries to download this JamTrack # we would verify their rights (can_download?), and generates a URL in response to the click so that they can download diff --git a/ruby/lib/jam_ruby/models/notification.rb b/ruby/lib/jam_ruby/models/notification.rb index a908cfe74..ccd4198e7 100644 --- a/ruby/lib/jam_ruby/models/notification.rb +++ b/ruby/lib/jam_ruby/models/notification.rb @@ -204,6 +204,9 @@ module JamRuby when NotificationTypes::SCHEDULED_SESSION_COMMENT return "New message about session." + when NotificationTypes::JAM_TRACK_SIGN_COMPLETE + return "Jam Track is ready for download." + # recording notifications when NotificationTypes::MUSICIAN_RECORDING_SAVED return "#{name} has made a new recording." @@ -1187,9 +1190,17 @@ module JamRuby end end - def send_jam_track_signed(jam_track_right) - msg = @@message_factory.jam_track_signed(jam_track_right) - @@mq_router.publish_to_all_clients(msg) + def send_jam_track_sign_complete(jam_track_right) + + notification = Notification.new + notification.jam_track_right_id = jam_track_right.id + notification.description = NotificationTypes::JAM_TRACK_SIGN_COMPLETE + notification.target_user_id = jam_track_right.user_id + notification.save! + + msg = @@message_factory.jam_track_sign_complete(jam_track_right.user_id, jam_track_right.id) + @@mq_router.publish_to_user(jam_track_right.user_id, msg) + #@@mq_router.publish_to_all_clients(msg) end def send_client_update(product, version, uri, size) diff --git a/ruby/lib/jam_ruby/resque/jam_tracks_builder.rb b/ruby/lib/jam_ruby/resque/jam_tracks_builder.rb index 310755fce..1dcf80367 100644 --- a/ruby/lib/jam_ruby/resque/jam_tracks_builder.rb +++ b/ruby/lib/jam_ruby/resque/jam_tracks_builder.rb @@ -11,15 +11,23 @@ module JamRuby attr_accessor :jam_track_right_id def self.perform(jam_track_right_id) - jam_track_builder = JamTracksBuilder.new() - jam_track_builder.jam_track_right_id = jam_track_right_id - jam_track_builder.run + JamWebEventMachine.run_wait_stop do + jam_track_builder = JamTracksBuilder.new() + jam_track_builder.jam_track_right_id = jam_track_right_id + jam_track_builder.run + end end def run @@log.info("jam_track_builder job starting. jam_track_right_id #{jam_track_right_id}") @jam_track_right = JamTrackRight.find(jam_track_right_id) JamRuby::JamTracksManager.save_jam_track_right_jkz(@jam_track_right) + + length = @jam_track_right.url.size() + md5 = Digest::MD5.new + + @jam_track_right.finish_sign(length, md5.to_s) + puts "Signed jamtrack to #{@jam_track_right[:url]}" end end diff --git a/web/app/assets/javascripts/notificationPanel.js b/web/app/assets/javascripts/notificationPanel.js index df6e23b48..41fa918c4 100644 --- a/web/app/assets/javascripts/notificationPanel.js +++ b/web/app/assets/javascripts/notificationPanel.js @@ -390,6 +390,12 @@ context.jamClient.OnDownloadAvailable(); // poke backend, letting it know a download is available } + else if (type === context.JK.MessageType.JAM_TRACK_SIGN_COMPLETE) { + $notification.find('#div-actions').hide(); + logger.debug("context.jamClient.OnDownloadAvailable!") + context.jamClient.OnDownloadAvailable(); // poke backend, letting it know a download is available + } + else if (type === context.JK.MessageType.BAND_INVITATION) { var $action_btn = $notification.find($btnNotificationAction); $action_btn.text('ACCEPT'); diff --git a/web/spec/controllers/api_jam_tracks_controller_spec.rb b/web/spec/controllers/api_jam_tracks_controller_spec.rb index 35da57118..e64130f3e 100644 --- a/web/spec/controllers/api_jam_tracks_controller_spec.rb +++ b/web/spec/controllers/api_jam_tracks_controller_spec.rb @@ -1,5 +1,4 @@ require 'spec_helper' - describe ApiJamTracksController do include CarrierWave::Test::Matchers @@ -81,8 +80,7 @@ describe ApiJamTracksController do right.download_count.should eq(1) notifications = Notification.where(:jam_track_right_id => right.id) - notifications.count.should == 1 - puts "notifications.first.inspect: #{notifications.first.inspect}" + notifications.count.should == 1 end end end From bb298dd6b96e14b52834c64d7f0ef7a9e7cd1e6b Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Mon, 29 Dec 2014 15:05:19 -0600 Subject: [PATCH 45/72] VRFS-2548 : Config-ize location of jamtracks python tool --- ruby/lib/jam_ruby/jam_tracks_manager.rb | 9 ++++----- ruby/spec/support/utilities.rb | 4 ++++ web/config/application.rb | 3 +++ web/jt_metadata.json | 1 + 4 files changed, 12 insertions(+), 5 deletions(-) create mode 100644 web/jt_metadata.json diff --git a/ruby/lib/jam_ruby/jam_tracks_manager.rb b/ruby/lib/jam_ruby/jam_tracks_manager.rb index 6594ffa9f..239601ada 100644 --- a/ruby/lib/jam_ruby/jam_tracks_manager.rb +++ b/ruby/lib/jam_ruby/jam_tracks_manager.rb @@ -17,8 +17,8 @@ module JamRuby def save_jam_track_right_jkz(jam_track_right) jam_track = jam_track_right.jam_track - py_root = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "..", "jamtracks")) - + #py_root = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "..", "jamtracks")) + py_root = APP_CONFIG.jamtracks_dir Dir.mktmpdir do |tmp_dir| jam_file_opts="" jam_track.jam_track_tracks.each do |jam_track_track| @@ -26,12 +26,11 @@ module JamRuby nm.gsub!(" ", "_") track_filename = File.join(tmp_dir, nm) track_url = jam_track_track.sign_url - puts "track_url: #{track_url}" copy_url_to_file(track_url, track_filename) copy_url_to_file(track_url, File.join(".", nm)) jam_file_opts << " -i '#{track_filename}+#{jam_track_track.part}'" end - puts "LS + " + `ls -la '#{tmp_dir}'` + #puts "LS + " + `ls -la '#{tmp_dir}'` sku=jam_track.id title=jam_track.name @@ -67,8 +66,8 @@ module JamRuby request = Net::HTTP::Get.new uri http.request request do |response| response_code = response.code.to_i - puts "Response from server was #{response_code} / #{response.message}" unless response_code >= 200 && response_code <= 299 + puts "Response from server was #{response_code} / #{response.message}" raise "bad status code: #{response_code}. body: #{response.body}" end response.read_body do |chunk| diff --git a/ruby/spec/support/utilities.rb b/ruby/spec/support/utilities.rb index 48a525fe0..380e63d0c 100644 --- a/ruby/spec/support/utilities.rb +++ b/ruby/spec/support/utilities.rb @@ -74,6 +74,10 @@ def app_config 0 # 0 seconds end + def jamtracks_dir + ENV['jamtracks_dir'] || File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "..", "jamtracks")) + end + def rabbitmq_host "localhost" end diff --git a/web/config/application.rb b/web/config/application.rb index 08d42e215..1aa82a05c 100644 --- a/web/config/application.rb +++ b/web/config/application.rb @@ -201,6 +201,9 @@ if defined?(Bundler) config.icecast_hardcoded_source_password = nil # generate a new password everytim. production should always use this value config.icecast_wait_after_reload = 5 # 5 seconds. a hack needed until VRFS-1043 + # Location of jamtracks python tool: + config.jamtracks_dir = ENV['JAMTRACKS_DIR'] || File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "jamtracks")) + config.email_alerts_alias = 'nobody@jamkazam.com' # should be used for 'oh no' server down/service down sorts of emails config.email_generic_from = 'nobody@jamkazam.com' config.email_smtp_address = 'smtp.sendgrid.net' diff --git a/web/jt_metadata.json b/web/jt_metadata.json new file mode 100644 index 000000000..252165875 --- /dev/null +++ b/web/jt_metadata.json @@ -0,0 +1 @@ +{"container_file": "/var/folders/fk/0ckzmddd4tq28kxbb09vckbr0000gn/T/d20141229-34425-8fuxu7/jam-track-3.jkz", "coverart": null, "rsa_priv_file": "/var/folders/fk/0ckzmddd4tq28kxbb09vckbr0000gn/T/d20141229-34425-8fuxu7/skey.pem", "tracks": [{"name": "/var/folders/fk/0ckzmddd4tq28kxbb09vckbr0000gn/T/d20141229-34425-8fuxu7/lead_guitar.ogg", "trackName": "track_00"}], "rsa_pub_file": "/var/folders/fk/0ckzmddd4tq28kxbb09vckbr0000gn/T/d20141229-34425-8fuxu7/pkey.pem", "jamktrack_info": "/var/folders/fk/0ckzmddd4tq28kxbb09vckbr0000gn/T/tmpJWp0T6"} \ No newline at end of file From 83bd5b6c6e45992ddc6309f727b9ba1f60b93558 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Wed, 31 Dec 2014 19:35:20 -0600 Subject: [PATCH 46/72] VRFS-2588 : YouTube client, remove logging Also, do some misc cleanup. --- .../{youtube_client.rb => you_tube_client.rb} | 79 +++++-------------- web/spec/features/oauth_spec.rb | 2 +- web/spec/features/youtube_spec.rb | 2 +- 3 files changed, 21 insertions(+), 62 deletions(-) rename web/lib/{youtube_client.rb => you_tube_client.rb} (81%) diff --git a/web/lib/youtube_client.rb b/web/lib/you_tube_client.rb similarity index 81% rename from web/lib/youtube_client.rb rename to web/lib/you_tube_client.rb index 635aa1a63..cba813844 100644 --- a/web/lib/youtube_client.rb +++ b/web/lib/you_tube_client.rb @@ -1,14 +1,13 @@ 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: +require 'socket' + +# Youtube OAuth and API functionality: module JamRuby class YouTubeClient attr_accessor :client @@ -28,13 +27,7 @@ module JamRuby :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 => '', :part => 'snippet'} - # result = JSON.parse(result.data.to_json) + youtube = client.discovered_api('youtube', 'v3') end # Return a login URL that will show a web page with @@ -42,7 +35,6 @@ module JamRuby 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" @@ -62,6 +54,7 @@ module JamRuby # 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 @@ -104,38 +97,24 @@ module JamRuby } 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 + faraday.request :url_encoded + 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 + # 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}: " @@ -158,8 +137,6 @@ module JamRuby "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 @@ -178,7 +155,6 @@ module JamRuby '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 @@ -216,6 +192,8 @@ module JamRuby (status_hash['status']>=200 && status_hash['status']<300) end + # Set fully_uploaded if the upload can be verified. + # @return true if verified; false otherwise: def complete_upload(recorded_video) if (verify_upload(recorded_video.user, recorded_video.url, recorded_video.length)) recorded_video.update_attribute(:fully_uploaded, true) @@ -245,12 +223,10 @@ module JamRuby 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.request :url_encoded faraday.adapter Faraday.default_adapter end @@ -258,7 +234,6 @@ module JamRuby 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, @@ -270,17 +245,15 @@ module JamRuby 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.request :url_encoded faraday.adapter Faraday.default_adapter end wait_for_callback do |access_token| - Rails.logger.info("The access_token is #{access_token}") - #self.server.stop() + Rails.logger.info("The access_token is #{access_token}") end result = conn.post '/o/oauth2/token', nil, { @@ -288,9 +261,7 @@ module JamRuby '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 } @@ -334,19 +305,16 @@ module JamRuby def exchange_for_token(access_code) - #puts "EXCHANGING token for code: [#{access_code}] #{access_code.class}" - + Rails.logger.info("Exchanging token for code: [#{access_code}]") 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_id'=>(config.google_email), 'client_secret'=>(config.google_secret), 'redirect_uri'=>(redirect_uri), } @@ -357,16 +325,12 @@ module JamRuby 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 @@ -376,11 +340,6 @@ module JamRuby end self.socket = nil end - - # if (self.server) - # Thread.kill(self.server) - # self.server = nil - # end end end # class end # module diff --git a/web/spec/features/oauth_spec.rb b/web/spec/features/oauth_spec.rb index e6c5af5a2..024418400 100644 --- a/web/spec/features/oauth_spec.rb +++ b/web/spec/features/oauth_spec.rb @@ -1,5 +1,5 @@ require 'spec_helper' -require 'youtube_client' +require 'you_tube_client' describe "OAuth", :slow=>true, :js=>true, :type=>:feature, :capybara_feature=>true do diff --git a/web/spec/features/youtube_spec.rb b/web/spec/features/youtube_spec.rb index 41883cd80..48d905df3 100644 --- a/web/spec/features/youtube_spec.rb +++ b/web/spec/features/youtube_spec.rb @@ -1,5 +1,5 @@ require 'spec_helper' -require 'youtube_client' +require 'you_tube_client' require 'rest_client' describe "YouTube", :slow=>true, :js=>true, :type => :feature, :capybara_feature => true do From 71399dcaa70fbedf40eab684f73da1068f61fba8 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Wed, 31 Dec 2014 20:39:22 -0600 Subject: [PATCH 47/72] * fixes for multiple issues: VRFS-2587 - no forking in resque, VRFS-2586 - fix influxdb client's shutdown behavior, VRFS-2585 - firefox cache busting --- ruby/lib/jam_ruby/lib/em_helper.rb | 4 ++ ruby/lib/jam_ruby/lib/stats.rb | 37 +++++++++++++++ ruby/lib/jam_ruby/resque/audiomixer.rb | 12 +++-- ruby/lib/jam_ruby/resque/quick_mixer.rb | 10 ++--- ruby/lib/jam_ruby/resque/resque_hooks.rb | 45 +++++++++++++++---- .../scheduled/active_music_session_cleaner.rb | 8 ++-- .../resque/scheduled/icecast_source_check.rb | 2 - .../scheduled/music_session_scheduler.rb | 4 +- .../unused_music_notation_cleaner.rb | 4 +- ruby/spec/spec_helper.rb | 4 ++ .../javascripts/jquery.listenbroadcast.js | 45 ++++++++++++++++--- web/app/assets/javascripts/sessionList.js | 7 +-- web/app/assets/javascripts/web/sessions.js | 2 +- web/config/environment.rb | 4 ++ web/lib/tasks/start.rake | 4 ++ web/spec/factories.rb | 1 - web/spec/features/create_session_spec.rb | 2 +- web/spec/spec_helper.rb | 3 ++ 18 files changed, 152 insertions(+), 46 deletions(-) diff --git a/ruby/lib/jam_ruby/lib/em_helper.rb b/ruby/lib/jam_ruby/lib/em_helper.rb index ba6e582e8..5867424ef 100644 --- a/ruby/lib/jam_ruby/lib/em_helper.rb +++ b/ruby/lib/jam_ruby/lib/em_helper.rb @@ -13,11 +13,15 @@ module JamWebEventMachine @@log = Logging.logger[JamWebEventMachine] + + # THIS WAS USED BY resque jobs needing EventMachine/AMQP, but it's no longer needed. It's useful code though + # starts amqp & eventmachine up first. # and then calls your block. # After the supplied block is done, # waits until all EM tasks scheduled in the supplied block are done, or timeout def self.run_wait_stop(timeout = 30, &blk) + JamWebEventMachine.run thread = Thread.current diff --git a/ruby/lib/jam_ruby/lib/stats.rb b/ruby/lib/jam_ruby/lib/stats.rb index bb73931f5..c51a14ae1 100644 --- a/ruby/lib/jam_ruby/lib/stats.rb +++ b/ruby/lib/jam_ruby/lib/stats.rb @@ -1,5 +1,41 @@ + require 'influxdb' +# monkey patch InfluxDB client to clear the queue when asked to stop +module InfluxDB + class Client + def stop! + @queue.clear if @queue + @stopped = true + end + end +end + +module InfluxDB + class Worker + def spawn_threads! + NUM_WORKER_THREADS.times do |thread_num| + log :debug, "Spawning background worker thread #{thread_num}." + + Thread.new do + Thread.current[:influxdb] = self.object_id + + at_exit do + log :debug, "Thread exiting, bailing out (not flushing queue)" + end + + while !client.stopped? + self.check_background_queue(thread_num) + sleep rand(SLEEP_INTERVAL) + end + end + end + + end + + end +end + module JamRuby class Stats @@ -10,6 +46,7 @@ module JamRuby def self.destroy! if @client + @client.queue.clear if @client.queue @client.stop! end end diff --git a/ruby/lib/jam_ruby/resque/audiomixer.rb b/ruby/lib/jam_ruby/resque/audiomixer.rb index 7ea7e7e55..cbe62d92e 100644 --- a/ruby/lib/jam_ruby/resque/audiomixer.rb +++ b/ruby/lib/jam_ruby/resque/audiomixer.rb @@ -20,13 +20,11 @@ module JamRuby def self.perform(mix_id, postback_ogg_url, postback_mp3_url) - JamWebEventMachine.run_wait_stop do - audiomixer = AudioMixer.new() - audiomixer.postback_ogg_url = postback_ogg_url - audiomixer.postback_mp3_url = postback_mp3_url - audiomixer.mix_id = mix_id - audiomixer.run - end + audiomixer = AudioMixer.new() + audiomixer.postback_ogg_url = postback_ogg_url + audiomixer.postback_mp3_url = postback_mp3_url + audiomixer.mix_id = mix_id + audiomixer.run end diff --git a/ruby/lib/jam_ruby/resque/quick_mixer.rb b/ruby/lib/jam_ruby/resque/quick_mixer.rb index daddc1802..93704173f 100644 --- a/ruby/lib/jam_ruby/resque/quick_mixer.rb +++ b/ruby/lib/jam_ruby/resque/quick_mixer.rb @@ -19,12 +19,10 @@ module JamRuby def self.perform(quick_mix_id, postback_mp3_url) - JamWebEventMachine.run_wait_stop do - audiomixer = QuickMixer.new - audiomixer.postback_mp3_url = postback_mp3_url - audiomixer.quick_mix_id = quick_mix_id - audiomixer.run - end + audiomixer = QuickMixer.new + audiomixer.postback_mp3_url = postback_mp3_url + audiomixer.quick_mix_id = quick_mix_id + audiomixer.run end diff --git a/ruby/lib/jam_ruby/resque/resque_hooks.rb b/ruby/lib/jam_ruby/resque/resque_hooks.rb index 37685f0d8..9b27e7097 100644 --- a/ruby/lib/jam_ruby/resque/resque_hooks.rb +++ b/ruby/lib/jam_ruby/resque/resque_hooks.rb @@ -1,26 +1,53 @@ require 'resque' +require 'resque-lonely_job' -# https://devcenter.heroku.com/articles/forked-pg-connections -Resque.before_fork do - defined?(ActiveRecord::Base) and - ActiveRecord::Base.connection.disconnect! +ENV['FORK_PER_JOB'] = 'false' - JamRuby::Stats.destroy! +def shutdown + puts "Cleaning up resources..." + Stats.destroy! + EventMachine.stop_event_loop + puts "Terminated!" + exit! end -Resque.after_fork do - defined?(ActiveRecord::Base) and - ActiveRecord::Base.establish_connection +Resque.before_first_fork do + JamWebEventMachine.start + #ActiveRecord::Base.establish_connection config = { influxdb_database: APP_CONFIG.influxdb_database, influxdb_username: APP_CONFIG.influxdb_username, influxdb_password: APP_CONFIG.influxdb_password, influxdb_hosts: APP_CONFIG.influxdb_hosts, influxdb_port: APP_CONFIG.influxdb_port, - influxdb_async: false # if we use async=true, the forked job will die before the stat is sent + influxdb_async: true # if we use async=true, the forked job will die before the stat is sent } + + # handle these events and force a shutdown. this is required I think due to influxdb-client. + Signal.trap("TERM") do + shutdown + end + Signal.trap("INT") do + shutdown + end + JamRuby::Stats.init(config) + +end +# https://devcenter.heroku.com/articles/forked-pg-connections +Resque.before_fork do + + #defined?(ActiveRecord::Base) and + # ActiveRecord::Base.connection.disconnect! + + #JamRuby::Stats.destroy! +end + +Resque.after_fork do + #defined?(ActiveRecord::Base) and + # ActiveRecord::Base.establish_connection + end # for jobs that do not extend lonely job, just extend this module and get stats diff --git a/ruby/lib/jam_ruby/resque/scheduled/active_music_session_cleaner.rb b/ruby/lib/jam_ruby/resque/scheduled/active_music_session_cleaner.rb index e700a0427..0b12cc0ce 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/active_music_session_cleaner.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/active_music_session_cleaner.rb @@ -22,11 +22,9 @@ module JamRuby def self.perform @@log.debug("ActiveMusicSessionCleaner waking up") - JamWebEventMachine.run_wait_stop do - cleaner = ActiveMusicSessionCleaner.new - cleaner.interval = "INTERVAL '1 minute'" - cleaner.run - end + cleaner = ActiveMusicSessionCleaner.new + cleaner.interval = "INTERVAL '1 minute'" + cleaner.run @@log.debug("ActiveMusicSessionCleaner done") end diff --git a/ruby/lib/jam_ruby/resque/scheduled/icecast_source_check.rb b/ruby/lib/jam_ruby/resque/scheduled/icecast_source_check.rb index b81c5b992..6b18cb633 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/icecast_source_check.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/icecast_source_check.rb @@ -23,9 +23,7 @@ module JamRuby def self.perform @@log.debug("waking up") - JamWebEventMachine.run_wait_stop do IcecastSourceCheck.new.run - end @@log.debug("done") end diff --git a/ruby/lib/jam_ruby/resque/scheduled/music_session_scheduler.rb b/ruby/lib/jam_ruby/resque/scheduled/music_session_scheduler.rb index cd706285a..6fa751ef9 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/music_session_scheduler.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/music_session_scheduler.rb @@ -19,9 +19,7 @@ module JamRuby def self.perform @@log.debug("MusicSessionScheduler waking up") - JamWebEventMachine.run_wait_stop do - MusicSessionScheduler.new.run - end + MusicSessionScheduler.new.run @@log.debug("MusicSessionScheduler done") end diff --git a/ruby/lib/jam_ruby/resque/scheduled/unused_music_notation_cleaner.rb b/ruby/lib/jam_ruby/resque/scheduled/unused_music_notation_cleaner.rb index 9fb3e307f..5955b1ec3 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/unused_music_notation_cleaner.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/unused_music_notation_cleaner.rb @@ -20,9 +20,7 @@ module JamRuby def self.perform @@log.debug("waking up") - JamWebEventMachine.run_wait_stop do - UnusedMusicNotationCleaner.new.run - end + UnusedMusicNotationCleaner.new.run @@log.debug("done") end diff --git a/ruby/spec/spec_helper.rb b/ruby/spec/spec_helper.rb index 73e9a3384..890a195c0 100644 --- a/ruby/spec/spec_helper.rb +++ b/ruby/spec/spec_helper.rb @@ -12,6 +12,10 @@ require 'uses_temp_files' require 'resque_spec' require 'resque_failed_job_mailer' +# to prevent embedded resque code from forking +ENV['FORK_PER_JOB'] = 'false' + + # recreate test database and migrate it SpecDb::recreate_database diff --git a/web/app/assets/javascripts/jquery.listenbroadcast.js b/web/app/assets/javascripts/jquery.listenbroadcast.js index 349305b57..66c20d850 100644 --- a/web/app/assets/javascripts/jquery.listenbroadcast.js +++ b/web/app/assets/javascripts/jquery.listenbroadcast.js @@ -81,7 +81,7 @@ if($audio.length == 0) { $audio = $('') $parent.append($audio) audioDomElement = $audio.get(0); @@ -105,7 +105,7 @@ if(!response.mount) { transition(PlayStateSessionOver); destroy(); - } + } else { audioDomElement.play(); @@ -164,9 +164,15 @@ audioDomElement.load(); var $parent = $audio.parent(); $audio.remove(); - $parent.append(''); - $audio = $('audio', $parent); + $audio = $('') $audio.append(originalSource); + var $sources = $audio.find('source') + $.each($sources, function(i, source) { + var $source = $(source); + var bustedSource = cacheBustedSrc($source.attr('data-audio-src')) + $source.attr('src', bustedSource) + }) + $parent.append($audio); audioDomElement = $audio.get(0); audioBind(); logger.log("recreated audio element ") @@ -225,6 +231,9 @@ else { // tell audio to stop/start, in attempt to retry //audioDomElement.pause(); + + recreateAudioElement(); + audioDomElement.load(); if(needsCanPlayGuard()) { $audio.bind('canplay', function() { @@ -443,7 +452,12 @@ // we have cause to believe the session is done; check against the server if(refresh) { - checkServer(); + checkServer() + .done(function(response) { + if(!response.mount) { + transition(PlayStateSessionOver); + destroy(); + }}) } } @@ -634,6 +648,7 @@ function openBubble() { checkServer().done(function(response) { + var mountId = response.mount ? response.mount.id : null if(mountId) { @@ -648,6 +663,10 @@ } else { mountInfo = null; + + transition(PlayStateSessionOver); + destroy(); + context.JK.app.layout.notify('This session can not currently broadcast') } }) @@ -679,6 +698,11 @@ }) } } + + function cacheBustedSrc(src) { + return src + '?cache-buster=' + new Date().getTime(); + } + function initialize() { @@ -690,6 +714,7 @@ fanAccess = $parent.attr('fan-access') === 'true' // coerce to boolean if(lazyAudioInit) { + // save the original src element (without any cache bust) audioSrc = $parent.attr('data-audio-src'); if(audioSrc === null) throw 'data-audio-src must be specified in $parentElement'; audioType = $parent.attr('data-audio-type'); @@ -709,7 +734,17 @@ } if(!lazyAudioInit) { + var $sources = $audio.find('source') + $.each($sources, function(i, source) { + var $source = $(source); + // save original src value (before cache bust) + $source.attr('data-audio-src', $source.attr('src')) + + var bustedSource = cacheBustedSrc($source.attr('data-audio-src')) + $source.attr('src', bustedSource) + }) audioDomElement = $audio.get(0); + audioBind(); } diff --git a/web/app/assets/javascripts/sessionList.js b/web/app/assets/javascripts/sessionList.js index 80507b011..46f32fcad 100644 --- a/web/app/assets/javascripts/sessionList.js +++ b/web/app/assets/javascripts/sessionList.js @@ -40,7 +40,7 @@ if(data.isEnd) { $listenText.text('Listen').removeClass('statusing') - stopPlay(); + stopPlay($listenLink); } if(data.isSessionOver) { @@ -60,8 +60,9 @@ function togglePlay() { var $listenLink = $(this) - var $listenText = $('.listen-link-text', $listenLink); - var $listenDetails = $('.listen-link-details', $listenLink); + var $parent = $listenLink.closest('.action-links'); + var $listenText = $('.listen-link-text', $parent); + var $listenDetails = $('.listen-link-details', $parent); if($listenLink.data('listenbroadcast-playstate') == 'playing') { $listenText.text('Listen') $listenDetails.removeClass('statusing') diff --git a/web/app/assets/javascripts/web/sessions.js b/web/app/assets/javascripts/web/sessions.js index 397795585..b39e16686 100644 --- a/web/app/assets/javascripts/web/sessions.js +++ b/web/app/assets/javascripts/web/sessions.js @@ -106,7 +106,7 @@ $controls.bind('statechange.listenBroadcast', stateChange); context.JK.prettyPrintElements($('time.duration').show()); context.JK.TickDuration(null); - $playButton.click(togglePlay); + $playButton.click(startPlay); sessionId = musicSessionId; diff --git a/web/config/environment.rb b/web/config/environment.rb index 3807f21a1..3463d46ae 100644 --- a/web/config/environment.rb +++ b/web/config/environment.rb @@ -3,10 +3,14 @@ require File.expand_path('../application', __FILE__) Mime::Type.register "audio/ogg", :audio_ogg +# to prevent embedded resque code from forking +ENV['FORK_PER_JOB'] = 'false' + # assign globals APP_CONFIG = Rails.application.config Stats.client = InfluxDB::Rails.client + # Initialize the rails application SampleApp::Application.initialize! diff --git a/web/lib/tasks/start.rake b/web/lib/tasks/start.rake index b53b25f6c..2efd63076 100644 --- a/web/lib/tasks/start.rake +++ b/web/lib/tasks/start.rake @@ -4,6 +4,7 @@ task :all_jobs do Rake::Task['environment'].invoke + ENV['FORK_PER_JOB'] = 'false' ENV['QUEUE'] = ENV['QUEUE'] || '*' Rake::Task['resque:work'].invoke end @@ -12,6 +13,7 @@ end task :audiomixer do Rake::Task['environment'].invoke + ENV['FORK_PER_JOB'] = 'false' ENV['QUEUE'] = 'audiomixer' Rake::Task['resque:work'].invoke end @@ -20,6 +22,7 @@ end task :icecast do Rake::Task['environment'].invoke + ENV['FORK_PER_JOB'] = 'false' ENV['QUEUE'] = 'icecast' Rake::Task['resque:work'].invoke end @@ -29,6 +32,7 @@ end task :odd_jobs do Rake::Task['environment'].invoke + ENV['FORK_PER_JOB'] = 'false' ENV['QUEUE'] = '*,!icecast,!audiomixer' Rake::Task['resque:work'].invoke end diff --git a/web/spec/factories.rb b/web/spec/factories.rb index a1d3a1ef8..26756e206 100644 --- a/web/spec/factories.rb +++ b/web/spec/factories.rb @@ -86,7 +86,6 @@ FactoryGirl.define do before(:create) do |user, evaluator| if evaluator.specific_instruments evaluator.specific_instruments.each do |instrument| - puts "burp: " user.musician_instruments << FactoryGirl.build(:musician_instrument, user: user, instrument: instrument) end else diff --git a/web/spec/features/create_session_spec.rb b/web/spec/features/create_session_spec.rb index b431d8d25..8bcd553a7 100644 --- a/web/spec/features/create_session_spec.rb +++ b/web/spec/features/create_session_spec.rb @@ -149,7 +149,7 @@ describe "Create Session", :js => true, :type => :feature, :capybara_feature => btn = first('#btn-alert-ok')# accept the 'If you start this session now, the scheduled start time...' btn.trigger(:click) if btn - + expect(page).to have_selector('h2', text: 'my tracks') find('#session-screen .session-mytracks .session-track') end diff --git a/web/spec/spec_helper.rb b/web/spec/spec_helper.rb index 936471e11..09606e087 100644 --- a/web/spec/spec_helper.rb +++ b/web/spec/spec_helper.rb @@ -67,6 +67,9 @@ Thread.new { end } +# to prevent embedded resque code from forking +ENV['FORK_PER_JOB'] = 'false' + bputs "before load websocket server" current = Thread.current From 83396675e6f7d3a60e4a14395f3fdaf0838ebe44 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Wed, 31 Dec 2014 20:44:51 -0600 Subject: [PATCH 48/72] * remove a diagnostic from javascript --- web/app/assets/javascripts/JamServer.js | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/web/app/assets/javascripts/JamServer.js b/web/app/assets/javascripts/JamServer.js index f60ea07d0..73997c0a7 100644 --- a/web/app/assets/javascripts/JamServer.js +++ b/web/app/assets/javascripts/JamServer.js @@ -321,23 +321,15 @@ lastDisconnectedReason = 'WEBSOCKET_CLOSED_LOCALLY' } - rest.createDiagnostic({ - type: lastDisconnectedReason, - data: {logs: logger.logCache, client_type: clientType, client_id: server.clientID, channel_id: channelId} - }) - .always(function() { - if ($currentDisplay.is('.no-websocket-connection')) { - // this path is the 'not in session path'; so there is nothing else to do - $currentDisplay.removeClass('active'); - - // TODO: tell certain elements that we've reconnected - } - else { - window.location.reload(); - } - }); - + if ($currentDisplay.is('.no-websocket-connection')) { + // this path is the 'not in session path'; so there is nothing else to do + $currentDisplay.removeClass('active'); + // TODO: tell certain elements that we've reconnected + } + else { + window.location.reload(); + } } function buildOptions() { From 9ad521fa71a9a6bded597c721775d1245942abae Mon Sep 17 00:00:00 2001 From: Seth Call Date: Wed, 31 Dec 2014 20:47:36 -0600 Subject: [PATCH 49/72] * comment two other noisy diagnostic reports --- websocket-gateway/lib/jam_websockets/router.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/websocket-gateway/lib/jam_websockets/router.rb b/websocket-gateway/lib/jam_websockets/router.rb index 7f2ad4be1..e9f185fcd 100644 --- a/websocket-gateway/lib/jam_websockets/router.rb +++ b/websocket-gateway/lib/jam_websockets/router.rb @@ -809,7 +809,7 @@ module JamWebsockets def handle_heartbeat(heartbeat, heartbeat_message_id, client) unless context = @clients[client] @log.warn "*** WARNING: unable to find context when handling heartbeat. client_id=#{client.client_id}; killing session" - Diagnostic.missing_client_state(client.user_id, client.context) + #Diagnostic.missing_client_state(client.user_id, client.context) raise SessionError, 'context state is gone. please reconnect.' else connection = Connection.find_by_client_id(context.client.client_id) @@ -1127,7 +1127,7 @@ module JamWebsockets client_context = @client_lookup[cid] if client_context - Diagnostic.expired_stale_connection(client_context.user.id, client_context) + #Diagnostic.expired_stale_connection(client_context.user.id, client_context) cleanup_client(client_context.client) end From 8a84ff5609bc2ffa025d9976aff72a0e2650d37a Mon Sep 17 00:00:00 2001 From: Seth Call Date: Wed, 31 Dec 2014 23:17:47 -0600 Subject: [PATCH 50/72] * fix broken tests because of removal of diagnostics, and fix bad logging pattern --- ruby/lib/jam_ruby/resque/batch_email_job.rb | 2 +- .../jam_ruby/resque/icecast_config_writer.rb | 2 +- ruby/lib/jam_ruby/resque/resque_hooks.rb | 15 ++++++++------- .../scheduled/active_music_session_cleaner.rb | 2 +- .../resque/scheduled/audiomixer_retry.rb | 2 +- .../scheduled/cleanup_facebook_signup.rb | 3 --- .../resque/scheduled/daily_session_emailer.rb | 2 +- .../resque/scheduled/icecast_config_retry.rb | 2 +- .../resque/scheduled/icecast_source_check.rb | 16 +++++++++------- .../scheduled/music_session_scheduler.rb | 2 +- .../resque/scheduled/new_musician_emailer.rb | 2 +- .../resque/scheduled/recordings_cleaner.rb | 2 +- .../scheduled_music_session_cleaner.rb | 2 +- .../resque/scheduled/score_history_sweeper.rb | 2 +- .../jam_ruby/resque/scheduled/stats_maker.rb | 18 +++++++++++++++--- .../scheduled/unused_music_notation_cleaner.rb | 2 +- .../resque/scheduled/user_progress_emailer.rb | 2 +- web/app/assets/javascripts/sessionList.js | 3 ++- web/app/controllers/api_controller.rb | 9 ++++++--- .../controllers/api_recordings_controller.rb | 4 +++- .../controllers/api_user_syncs_controller.rb | 5 +++-- .../controllers/vanilla_forums_controller.rb | 9 ++++++--- web/config/initializers/email.rb | 4 +--- .../middlewares/clear_duplicated_session.rb | 6 ++++-- web/spec/features/reconnect_spec.rb | 5 ----- 25 files changed, 70 insertions(+), 53 deletions(-) diff --git a/ruby/lib/jam_ruby/resque/batch_email_job.rb b/ruby/lib/jam_ruby/resque/batch_email_job.rb index 6d82d420e..fbfcde05f 100644 --- a/ruby/lib/jam_ruby/resque/batch_email_job.rb +++ b/ruby/lib/jam_ruby/resque/batch_email_job.rb @@ -3,7 +3,7 @@ require 'resque-lonely_job' module JamRuby class BatchEmailJob - extend Resque::Plugins::LonelyJob + extend Resque::Plugins::JamLonelyJob @@log = Logging.logger[BatchEmailJob] diff --git a/ruby/lib/jam_ruby/resque/icecast_config_writer.rb b/ruby/lib/jam_ruby/resque/icecast_config_writer.rb index 05ce40a26..50b41f230 100644 --- a/ruby/lib/jam_ruby/resque/icecast_config_writer.rb +++ b/ruby/lib/jam_ruby/resque/icecast_config_writer.rb @@ -9,7 +9,7 @@ module JamRuby # executes a mix of tracks, creating a final output mix class IcecastConfigWriter - extend Resque::Plugins::LonelyJob + extend Resque::Plugins::JamLonelyJob @@log = Logging.logger[IcecastConfigWriter] diff --git a/ruby/lib/jam_ruby/resque/resque_hooks.rb b/ruby/lib/jam_ruby/resque/resque_hooks.rb index 9b27e7097..fcd344bf6 100644 --- a/ruby/lib/jam_ruby/resque/resque_hooks.rb +++ b/ruby/lib/jam_ruby/resque/resque_hooks.rb @@ -1,5 +1,4 @@ require 'resque' -require 'resque-lonely_job' ENV['FORK_PER_JOB'] = 'false' @@ -63,19 +62,21 @@ module JamRuby end end + +require 'resque-lonely_job' + # for jobs that extend lonely job, we override around_perform already implemented in LonelyJob, and call into it module Resque module Plugins - module LonelyJob + module JamLonelyJob def around_perform(*args) Stats.timer('job.stats') do - begin - yield - ensure - unlock_queue(*args) - end + super end end end end end + +Resque::Plugins::JamLonelyJob.module_eval { include Resque::Plugins::LonelyJob } + diff --git a/ruby/lib/jam_ruby/resque/scheduled/active_music_session_cleaner.rb b/ruby/lib/jam_ruby/resque/scheduled/active_music_session_cleaner.rb index 0b12cc0ce..899f3dd67 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/active_music_session_cleaner.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/active_music_session_cleaner.rb @@ -7,7 +7,7 @@ require 'digest/md5' module JamRuby class ActiveMusicSessionCleaner - extend Resque::Plugins::LonelyJob + extend Resque::Plugins::JamLonelyJob attr_accessor :interval diff --git a/ruby/lib/jam_ruby/resque/scheduled/audiomixer_retry.rb b/ruby/lib/jam_ruby/resque/scheduled/audiomixer_retry.rb index 6c2d8fe90..782b8a8d9 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/audiomixer_retry.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/audiomixer_retry.rb @@ -8,7 +8,7 @@ module JamRuby # periodically scheduled to find jobs that need retrying, and cleanup activities class AudioMixerRetry - extend Resque::Plugins::LonelyJob + extend Resque::Plugins::JamLonelyJob @queue = :scheduled_audiomixer_retry diff --git a/ruby/lib/jam_ruby/resque/scheduled/cleanup_facebook_signup.rb b/ruby/lib/jam_ruby/resque/scheduled/cleanup_facebook_signup.rb index 25b643187..c8f67120b 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/cleanup_facebook_signup.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/cleanup_facebook_signup.rb @@ -2,9 +2,6 @@ module JamRuby class CleanupFacebookSignup - - - @queue = :scheduled_cleanup_facebook_signup @@log = Logging.logger[CleanupFacebookSignup] diff --git a/ruby/lib/jam_ruby/resque/scheduled/daily_session_emailer.rb b/ruby/lib/jam_ruby/resque/scheduled/daily_session_emailer.rb index e5faa2d51..6d233f165 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/daily_session_emailer.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/daily_session_emailer.rb @@ -1,6 +1,6 @@ module JamRuby class DailySessionEmailer - extend Resque::Plugins::LonelyJob + extend Resque::Plugins::JamLonelyJob @queue = :scheduled_daily_session_emailer @@log = Logging.logger[DailySessionEmailer] diff --git a/ruby/lib/jam_ruby/resque/scheduled/icecast_config_retry.rb b/ruby/lib/jam_ruby/resque/scheduled/icecast_config_retry.rb index 1e2f6fcc3..7048c9be4 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/icecast_config_retry.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/icecast_config_retry.rb @@ -8,7 +8,7 @@ module JamRuby # periodically scheduled to find jobs that need retrying class IcecastConfigRetry - extend Resque::Plugins::LonelyJob + extend Resque::Plugins::JamLonelyJob @queue = :scheduled_icecast_config_retry diff --git a/ruby/lib/jam_ruby/resque/scheduled/icecast_source_check.rb b/ruby/lib/jam_ruby/resque/scheduled/icecast_source_check.rb index 6b18cb633..1519ed6f3 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/icecast_source_check.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/icecast_source_check.rb @@ -9,11 +9,13 @@ module JamRuby # http://blog.bignerdranch.com/1643-never-use-resque-for-serial-jobs/ # periodically scheduled to find sources that need to be brought down, or alternatively, it seems the client failed to start sourcing class IcecastSourceCheck - extend Resque::Plugins::LonelyJob + extend Resque::Plugins::JamLonelyJob @queue = :scheduled_icecast_source_check - @@log = Logging.logger[IcecastSourceCheck] + def log + @log || Logging.logger[IcecastSourceCheck] + end def self.lock_timeout # this should be enough time to make sure the job has finished, but not so long that the system isn't recovering from a abandoned job @@ -21,11 +23,7 @@ module JamRuby end def self.perform - @@log.debug("waking up") - - IcecastSourceCheck.new.run - - @@log.debug("done") + IcecastSourceCheck.new.run end @@ -35,6 +33,8 @@ module JamRuby # ** listeners > 0 and sourced is DOWN (false) # ** listeners == 0 and sourced is UP (true) + log.debug("waking up") + IcecastMount.find_each(lock: true, :conditions => "( (listeners > 0 AND sourced = FALSE) OR (listeners = 0 AND sourced = TRUE) ) AND ( sourced_needs_changing_at IS NULL OR sourced_needs_changing_at < (NOW() - interval '#{APP_CONFIG.icecast_max_sourced_changed} second') ) ", :batch_size => 100) do |mount| if mount.music_session_id mount.with_lock do @@ -42,6 +42,8 @@ module JamRuby end end end + + log.debug("done") end def handle_notifications(mount) diff --git a/ruby/lib/jam_ruby/resque/scheduled/music_session_scheduler.rb b/ruby/lib/jam_ruby/resque/scheduled/music_session_scheduler.rb index 6fa751ef9..2ed6b53fc 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/music_session_scheduler.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/music_session_scheduler.rb @@ -6,7 +6,7 @@ require 'digest/md5' module JamRuby class MusicSessionScheduler - extend Resque::Plugins::LonelyJob + extend Resque::Plugins::JamLonelyJob @queue = :music_session_scheduler diff --git a/ruby/lib/jam_ruby/resque/scheduled/new_musician_emailer.rb b/ruby/lib/jam_ruby/resque/scheduled/new_musician_emailer.rb index 2a2c861a4..8012e197a 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/new_musician_emailer.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/new_musician_emailer.rb @@ -1,6 +1,6 @@ module JamRuby class NewMusicianEmailer - extend Resque::Plugins::LonelyJob + extend Resque::Plugins::JamLonelyJob @queue = :scheduled_new_musician_emailer @@log = Logging.logger[NewMusicianEmailer] diff --git a/ruby/lib/jam_ruby/resque/scheduled/recordings_cleaner.rb b/ruby/lib/jam_ruby/resque/scheduled/recordings_cleaner.rb index 1e4b41769..7903edc0a 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/recordings_cleaner.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/recordings_cleaner.rb @@ -8,7 +8,7 @@ module JamRuby # periodically scheduled to find recordings to cleanup class RecordingsCleaner - extend Resque::Plugins::LonelyJob + extend Resque::Plugins::JamLonelyJob @queue = :recordings_cleaner diff --git a/ruby/lib/jam_ruby/resque/scheduled/scheduled_music_session_cleaner.rb b/ruby/lib/jam_ruby/resque/scheduled/scheduled_music_session_cleaner.rb index 8dfeca8e0..8fddeccfb 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/scheduled_music_session_cleaner.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/scheduled_music_session_cleaner.rb @@ -1,6 +1,6 @@ module JamRuby class ScheduledMusicSessionCleaner - extend Resque::Plugins::LonelyJob + extend Resque::Plugins::JamLonelyJob @queue = :scheduled_music_session_cleaner @@log = Logging.logger[ScheduledMusicSessionCleaner] diff --git a/ruby/lib/jam_ruby/resque/scheduled/score_history_sweeper.rb b/ruby/lib/jam_ruby/resque/scheduled/score_history_sweeper.rb index 76c759ef8..52819600b 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/score_history_sweeper.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/score_history_sweeper.rb @@ -8,7 +8,7 @@ module JamRuby # periodically scheduled to find jobs that need retrying class ScoreHistorySweeper - extend Resque::Plugins::LonelyJob + extend Resque::Plugins::JamLonelyJob @queue = :score_history_sweeper diff --git a/ruby/lib/jam_ruby/resque/scheduled/stats_maker.rb b/ruby/lib/jam_ruby/resque/scheduled/stats_maker.rb index d84535b3e..f22dd4bad 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/stats_maker.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/stats_maker.rb @@ -8,13 +8,25 @@ module JamRuby # creates stats to send to influx periodically class StatsMaker - extend Resque::Plugins::LonelyJob + extend Resque::Plugins::JamLonelyJob - @queue = :stats_maker + @queue = :scheduled_db_metrics + + def log + @log || Logging.logger[StatsMaker] + end + + def self.lock_timeout + 120 + end - @@log = Logging.logger['StatsMaker'] def self.perform + StatsMaker.new.run + end + + def run + log.debug("starting...") Stats.write('connection', Connection.stats) Stats.write('users', User.stats) end diff --git a/ruby/lib/jam_ruby/resque/scheduled/unused_music_notation_cleaner.rb b/ruby/lib/jam_ruby/resque/scheduled/unused_music_notation_cleaner.rb index 5955b1ec3..3d3ca8e76 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/unused_music_notation_cleaner.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/unused_music_notation_cleaner.rb @@ -6,7 +6,7 @@ require 'digest/md5' module JamRuby class UnusedMusicNotationCleaner - extend Resque::Plugins::LonelyJob + extend Resque::Plugins::JamLonelyJob @queue = :unused_music_notation_cleaner diff --git a/ruby/lib/jam_ruby/resque/scheduled/user_progress_emailer.rb b/ruby/lib/jam_ruby/resque/scheduled/user_progress_emailer.rb index 99835146f..84e1c5081 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/user_progress_emailer.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/user_progress_emailer.rb @@ -1,6 +1,6 @@ module JamRuby class UserProgressEmailer - extend Resque::Plugins::LonelyJob + extend Resque::Plugins::JamLonelyJob @queue = :scheduled_user_progress_emailer @@log = Logging.logger[UserProgressEmailer] diff --git a/web/app/assets/javascripts/sessionList.js b/web/app/assets/javascripts/sessionList.js index 46f32fcad..61db4744e 100644 --- a/web/app/assets/javascripts/sessionList.js +++ b/web/app/assets/javascripts/sessionList.js @@ -23,7 +23,8 @@ // related to listen function stateChange(e, data) { - var $listenLink = e.element; + var $listenLink = e.element; // TODO: this is empty + console.log(e,$(this)) var $listenText = $('.listen-link-text', $listenLink); var $listenDetails = $('.listen-link-details', $listenLink); diff --git a/web/app/controllers/api_controller.rb b/web/app/controllers/api_controller.rb index d05413842..c42f138b1 100644 --- a/web/app/controllers/api_controller.rb +++ b/web/app/controllers/api_controller.rb @@ -1,6 +1,9 @@ class ApiController < ApplicationController - @@log = Logging.logger[ApiController] + def log + @log || Logging.logger[ApiController] + end + @@html_encoder = HTMLEntities.new # define common error handlers @@ -21,11 +24,11 @@ class ApiController < ApplicationController render "errors/conflict_error", :status => 409 end rescue_from 'ActiveRecord::RecordNotFound' do |exception| - @@log.debug(exception) + log.debug(exception) render :json => { :errors => { :resource => ["record not found"] } }, :status => 404 end rescue_from 'PG::Error' do |exception| - @@log.debug(exception) + log.debug(exception) if exception.to_s.include? "duplicate key value violates unique constraint" render :json => { :errors => { :resource => ["resource already exists"] } }, :status => 409 # 409 = conflict else diff --git a/web/app/controllers/api_recordings_controller.rb b/web/app/controllers/api_recordings_controller.rb index b286bc8a2..695e3a4b4 100644 --- a/web/app/controllers/api_recordings_controller.rb +++ b/web/app/controllers/api_recordings_controller.rb @@ -8,7 +8,9 @@ class ApiRecordingsController < ApiController respond_to :json - @@log = Logging.logger[ApiRecordingsController] + def log + @log || Logging.logger[ApiRecordingsController] + end def index # lists recordings created by for the current user diff --git a/web/app/controllers/api_user_syncs_controller.rb b/web/app/controllers/api_user_syncs_controller.rb index d511b34af..54a0554bc 100644 --- a/web/app/controllers/api_user_syncs_controller.rb +++ b/web/app/controllers/api_user_syncs_controller.rb @@ -8,8 +8,9 @@ class ApiUserSyncsController < ApiController respond_to :json - @@log = Logging.logger[ApiUserSyncsController] - + def log + @log || Logging.logger[ApiUserSyncsController] + end def show @user_sync = UserSync.show(params[:user_sync_id], current_user.id) diff --git a/web/app/controllers/vanilla_forums_controller.rb b/web/app/controllers/vanilla_forums_controller.rb index 86ac2bf48..b7772880d 100644 --- a/web/app/controllers/vanilla_forums_controller.rb +++ b/web/app/controllers/vanilla_forums_controller.rb @@ -3,7 +3,10 @@ require 'js_connect' class VanillaForumsController < ApplicationController - @@log = Logging.logger[VanillaForumsController] + + def log + @log || Logging.logger[VanillaForumsController ] + end # displays the embedded forum # see http://vanillaforums.com/blog/jsconnect-technical-documentation-for-embedded-sso/ @@ -43,9 +46,9 @@ class VanillaForumsController < ApplicationController 'photourl' => current_user.resolved_photo_url, 'uniqueid' => current_user.id} - @@log.debug("user is logged in: #{user}") + log.debug("user is logged in: #{user}") else - @@log.debug("user is not logged in") + log.debug("user is not logged in") end diff --git a/web/config/initializers/email.rb b/web/config/initializers/email.rb index 8d9daf5a5..2710d5659 100644 --- a/web/config/initializers/email.rb +++ b/web/config/initializers/email.rb @@ -1,5 +1,3 @@ -@@log = Logging.logger['EmailInitializer'] - ActionMailer::Base.raise_delivery_errors = true ActionMailer::Base.delivery_method = GenericState.allow_emails? ? :smtp : :test ActionMailer::Base.smtp_settings = { @@ -12,4 +10,4 @@ ActionMailer::Base.smtp_settings = { :enable_starttls_auto => Rails.application.config.email_smtp_starttls_auto } -@@log.debug("ActionMailer.delivery_method = #{ActionMailer::Base.delivery_method}") +Rails.logger.debug("ActionMailer.delivery_method = #{ActionMailer::Base.delivery_method}") diff --git a/web/lib/middlewares/clear_duplicated_session.rb b/web/lib/middlewares/clear_duplicated_session.rb index e91a09298..766bfff71 100644 --- a/web/lib/middlewares/clear_duplicated_session.rb +++ b/web/lib/middlewares/clear_duplicated_session.rb @@ -5,7 +5,9 @@ module Middlewares class ClearDuplicatedSession - @@log = Logging.logger[ClearDuplicatedSession] + def log + @log || Logging.logger[ClearDuplicatedSession] + end def initialize(app) @app = app @@ -53,7 +55,7 @@ module Middlewares # Sets expiration date = 1970-01-01 to the cookie, this way browser will # note the cookie is expired and will delete it def delete_session_cookie_for_current_domain(env, headers) - @@log.debug "deleting default domain session cookie" + log.debug "deleting default domain session cookie" ::Rack::Utils.set_cookie_header!( headers, # contains response headers get_session_key(env), # gets the cookie session name, '_session_cookie' - for this example diff --git a/web/spec/features/reconnect_spec.rb b/web/spec/features/reconnect_spec.rb index ba386e278..adc8e982b 100644 --- a/web/spec/features/reconnect_spec.rb +++ b/web/spec/features/reconnect_spec.rb @@ -74,11 +74,6 @@ describe "Reconnect", :js => true, :type => :feature, :capybara_feature => true # but.. after a few seconds, it should reconnect on it's own page.should_not have_selector('.no-websocket-connection') - - # confirm that a diagnostic was written - Diagnostic.count.should == i + 1 - diagnostic = Diagnostic.first - diagnostic.type.should == Diagnostic::WEBSOCKET_CLOSED_LOCALLY end # then verify we can create a session From e8fd6360aa92f72b251c16cb42b7c8d5546469dd Mon Sep 17 00:00:00 2001 From: Jonathan Kolyer Date: Thu, 1 Jan 2015 18:00:43 +0000 Subject: [PATCH 51/72] VRFS-2552 removed config.json_engine due to this error: undefined method key? JSON::Ext::Generator::State --- web/config/initializers/rabl_init.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/config/initializers/rabl_init.rb b/web/config/initializers/rabl_init.rb index f03d6261c..7b8877eba 100644 --- a/web/config/initializers/rabl_init.rb +++ b/web/config/initializers/rabl_init.rb @@ -11,7 +11,7 @@ Rabl.configure do |config| # config.cache_engine = Rabl::CacheEngine.new # Defaults to Rails cache # config.escape_all_output = false # config.json_engine = nil # Any multi_json engines or a Class with #encode method - config.json_engine = PrettyJson if Rails.env.development? + # config.json_engine = PrettyJson if Rails.env.development? # config.msgpack_engine = nil # Defaults to ::MessagePack # config.bson_engine = nil # Defaults to ::BSON # config.plist_engine = nil # Defaults to ::Plist::Emit @@ -24,4 +24,4 @@ Rabl.configure do |config| # config.enable_json_callbacks = false # config.xml_options = { :dasherize => true, :skip_types => false } config.view_paths << Rails.root.join('app/views') -end \ No newline at end of file +end From 559d2d720d91744807c376ba78b0a02a050c9cee Mon Sep 17 00:00:00 2001 From: Jonathan Kolyer Date: Thu, 1 Jan 2015 19:29:18 +0000 Subject: [PATCH 52/72] VRFS-2552 update query to include users started scheduled sessions not yet finished --- ruby/lib/jam_ruby/models/music_session.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ruby/lib/jam_ruby/models/music_session.rb b/ruby/lib/jam_ruby/models/music_session.rb index e1cbea90e..ff4b2e3ea 100644 --- a/ruby/lib/jam_ruby/models/music_session.rb +++ b/ruby/lib/jam_ruby/models/music_session.rb @@ -286,12 +286,15 @@ module JamRuby # keep unstarted sessions around for 12 hours after scheduled_start session_not_started = "(music_sessions.scheduled_start > NOW() - '12 hour'::INTERVAL AND music_sessions.started_at IS NULL)" + # keep started sessions that are not finished yet + session_started_not_finished = "(music_sessions.started_at IS NOT NULL AND music_sessions.session_removed_at IS NULL)" + # let session be restarted for up to 2 hours after finishing session_finished = "(music_sessions.session_removed_at > NOW() - '2 hour'::INTERVAL)" query = MusicSession.where("music_sessions.canceled = FALSE") query = query.where("music_sessions.user_id = '#{user.id}'") - query = query.where("music_sessions.scheduled_start IS NULL OR #{session_not_started} OR #{session_finished}") + query = query.where("music_sessions.scheduled_start IS NULL OR #{session_not_started} OR #{session_finished} OR #{session_started_not_finished}") query = query.where("music_sessions.create_type IS NULL OR music_sessions.create_type != '#{CREATE_TYPE_QUICK_START}'") query = query.order("music_sessions.scheduled_start ASC") From 9b1525715e54966f1a609418a67af4fcf4b594a8 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Thu, 1 Jan 2015 14:22:42 -0600 Subject: [PATCH 53/72] * fix a bug in broadcasting and also fix bad log refernce --- .../jam_ruby/resque/scheduled/icecast_source_check.rb | 4 ++-- web/app/assets/javascripts/sessionList.js | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/ruby/lib/jam_ruby/resque/scheduled/icecast_source_check.rb b/ruby/lib/jam_ruby/resque/scheduled/icecast_source_check.rb index 1519ed6f3..8accb3c43 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/icecast_source_check.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/icecast_source_check.rb @@ -49,13 +49,13 @@ module JamRuby def handle_notifications(mount) if mount.listeners == 0 && mount.sourced # if no listeners, but we are sourced, then ask it to stop sourcing - @@log.debug("SOURCE_DOWN_REQUEST called on mount #{mount.name}") + log.debug("SOURCE_DOWN_REQUEST called on mount #{mount.name}") mount.notify_source_down_requested elsif mount.listeners > 0 && !mount.sourced # if we have some listeners, and still are not sourced, then ask to start sourcing again - @@log.debug("SOURCE_UP_REQUEST called on mount #{mount.name}") + log.debug("SOURCE_UP_REQUEST called on mount #{mount.name}") mount.notify_source_up_requested end diff --git a/web/app/assets/javascripts/sessionList.js b/web/app/assets/javascripts/sessionList.js index 61db4744e..78bffdaf3 100644 --- a/web/app/assets/javascripts/sessionList.js +++ b/web/app/assets/javascripts/sessionList.js @@ -23,10 +23,11 @@ // related to listen function stateChange(e, data) { - var $listenLink = e.element; // TODO: this is empty - console.log(e,$(this)) - var $listenText = $('.listen-link-text', $listenLink); - var $listenDetails = $('.listen-link-details', $listenLink); + var $listenLink = data.element; + var $parent = $listenLink.closest('.action-links') + //console.log(e,$(this)) + var $listenText = $('.listen-link-text', $parent); + var $listenDetails = $('.listen-link-details', $parent); if(data.displayText) { From 5758096f335d0a97f93e9d643bee494e38346772 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Thu, 1 Jan 2015 21:45:07 -0600 Subject: [PATCH 54/72] * fix bug in connection stats --- ruby/lib/jam_ruby/models/connection.rb | 7 ++++--- ruby/spec/jam_ruby/models/connection_spec.rb | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/ruby/lib/jam_ruby/models/connection.rb b/ruby/lib/jam_ruby/models/connection.rb index 53714d6b8..8a813f9ce 100644 --- a/ruby/lib/jam_ruby/models/connection.rb +++ b/ruby/lib/jam_ruby/models/connection.rb @@ -229,11 +229,12 @@ module JamRuby CLIENT_TYPES.each do |type| stats[type] = 0 end - Connection.select('count(client_type) AS client_type_count, client_type') do |result| - stats[result['client_type']] = result['client_type_count'] + + Connection.select('count(client_type) AS client_type_count, client_type').group('client_type').all.each do |result| + stats[result['client_type']] = result['client_type_count'].to_i end - result = Connection.select('count(id) AS total, count(scoring_timeout) AS scoring_timeout_count, count(music_session_id) AS in_session, count(as_musician) AS musicians, count(udp_reachable) AS udp_reachable_count, count(is_network_testing) AS is_network_testing_count').first + result = Connection.select('count(id) AS total, count(CASE WHEN scoring_timeout > NOW() THEN 1 ELSE null END) AS scoring_timeout_count, count(music_session_id) AS in_session, count(as_musician) AS musicians, count(CASE WHEN udp_reachable THEN 1 ELSE null END) AS udp_reachable_count, count(CASE WHEN is_network_testing THEN 1 ELSE null END) AS is_network_testing_count').first stats['count'] = result['total'].to_i stats['scoring_timeout'] = result['scoring_timeout_count'].to_i diff --git a/ruby/spec/jam_ruby/models/connection_spec.rb b/ruby/spec/jam_ruby/models/connection_spec.rb index 97784b838..f93e46bf7 100644 --- a/ruby/spec/jam_ruby/models/connection_spec.rb +++ b/ruby/spec/jam_ruby/models/connection_spec.rb @@ -193,5 +193,20 @@ describe JamRuby::Connection do stats['udp_reachable'].should eq(0) stats['networking_testing'].should eq(0) end + + it "1 connection" do + conn.touch + + stats = Connection.stats + stats[Connection::TYPE_CLIENT].should eq(1) + stats[Connection::TYPE_BROWSER].should eq(0) + stats[Connection::TYPE_LATENCY_TESTER].should eq(0) + stats['count'].should eq(1) + stats['scoring_timeout'].should eq(0) + stats['in_session'].should eq(1) + stats['musicians'].should eq(1) + stats['udp_reachable'].should eq(1) + stats['networking_testing'].should eq(0) + end end end From 82b7c005ec210d15b2761614ff58b923b78ea173 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Fri, 2 Jan 2015 11:18:39 -0600 Subject: [PATCH 55/72] * if promos is selected, then change text to 'Selected Sessions and Recordings' VRFS-2591 --- ruby/lib/jam_ruby/models/user.rb | 1 - web/app/views/users/_latest.html.haml | 6 ------ web/app/views/users/_latest.html.slim | 11 +++++++++++ 3 files changed, 11 insertions(+), 7 deletions(-) delete mode 100644 web/app/views/users/_latest.html.haml create mode 100644 web/app/views/users/_latest.html.slim diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb index 1df38f5bb..4956d1958 100644 --- a/ruby/lib/jam_ruby/models/user.rb +++ b/ruby/lib/jam_ruby/models/user.rb @@ -1470,7 +1470,6 @@ module JamRuby def self.stats stats = {} result = User.select('count(CASE WHEN musician THEN 1 ELSE null END) as musician_count, count(CASE WHEN musician = FALSE THEN 1 ELSE null END) as fan_count, count(first_downloaded_client_at) first_downloaded_client_at_count, count(first_ran_client_at) first_ran_client_at_count, count(first_certified_gear_at) first_certified_gear_at_count, count(first_music_session_at) as first_music_session_at_count, count(first_invited_at) first_invited_at_count, count(first_friended_at) as first_friended_at_count, count(first_social_promoted_at) first_social_promoted_at_count, avg(last_jam_audio_latency) last_jam_audio_latency_avg').first - puts "result #{result['musician_count']}" stats['musicians'] = result['musician_count'].to_i stats['fans'] = result['fan_count'].to_i stats['downloaded_client'] = result['first_downloaded_client_at_count'].to_i diff --git a/web/app/views/users/_latest.html.haml b/web/app/views/users/_latest.html.haml deleted file mode 100644 index 470f3a1f3..000000000 --- a/web/app/views/users/_latest.html.haml +++ /dev/null @@ -1,6 +0,0 @@ -.latest - .home-session-list - %h2.latest-head Latest Sessions & Recordings - .latest-body - .session-list-wrapper.content-scroller - = render :partial => "feed_item", :collection => @promo_latest \ No newline at end of file diff --git a/web/app/views/users/_latest.html.slim b/web/app/views/users/_latest.html.slim new file mode 100644 index 000000000..e0ee0ff51 --- /dev/null +++ b/web/app/views/users/_latest.html.slim @@ -0,0 +1,11 @@ +.latest + .home-session-list + - if Rails.application.config.use_promos_on_homepage + h2.latest-head + | Selected Sessions & Recordings + - else + h2.latest-head + | Latest Sessions & Recordings + .latest-body + .session-list-wrapper.content-scroller + = render :partial => "feed_item", :collection => @promo_latest \ No newline at end of file From 5f14f73322d3304b9818d06d1af64b6c44725f5a Mon Sep 17 00:00:00 2001 From: Seth Call Date: Fri, 2 Jan 2015 15:29:31 -0600 Subject: [PATCH 56/72] * some more fixes for broadcasting - VRFS-2523 --- .../javascripts/jquery.listenbroadcast.js | 152 +++++++++--------- web/app/assets/javascripts/sessionList.js | 3 +- web/app/assets/javascripts/web/sessions.js | 13 +- .../stylesheets/web/recordings.css.scss | 4 +- web/app/views/music_sessions/show.html.erb | 2 +- 5 files changed, 91 insertions(+), 83 deletions(-) diff --git a/web/app/assets/javascripts/jquery.listenbroadcast.js b/web/app/assets/javascripts/jquery.listenbroadcast.js index 66c20d850..95c806af2 100644 --- a/web/app/assets/javascripts/jquery.listenbroadcast.js +++ b/web/app/assets/javascripts/jquery.listenbroadcast.js @@ -33,6 +33,7 @@ context.JK.ListenBroadcast = function($parentElement, options){ var WAIT_FOR_BUFFER_TIMEOUT = 5000; + var WAIT_FOR_PLAYING_TIMEOUT = 7000; var RETRY_ATTEMPTS = 5; // we try 4 times, so the user will wait up until RETRY_ATTEMPTS * WAIT_FOR_BUFFER_TIMEOUTS var logger = context.JK.logger; @@ -42,6 +43,7 @@ var audioDomElement = null; var musicSessionId = null; var waitForBufferingTimeout = null; + var waitForPlayingTimeout = null; var fanAccess = null; var audioSrc = null; var audioType = null; @@ -49,6 +51,7 @@ var self = this; var mountInfo = null; var $mountState = null; + var sessionInfo = null; // stored so we can access .mount, mostly var lazyAudioInit = options && options.lazyAudioInit; var hoverOptions = (options && options.hoverOptions) ? options.hoverOptions : {} var $detailHelper = options && options.detailHelper; @@ -77,21 +80,7 @@ e.stopPropagation(); } - if(lazyAudioInit) { - if($audio.length == 0) { - $audio = - $('') - $parent.append($audio) - audioDomElement = $audio.get(0); - audioBind(); - } - } - - if(destroyed) return; - - if(!audioDomElement) throw "no audio element supplied; the user should not be able to attempt a play" + //if(destroyed) return; if(context.JK.ListenBroadcastCurrentlyPlaying) { context.JK.ListenBroadcastCurrentlyPlaying.forcedPause(); @@ -102,20 +91,33 @@ checkServer() .done(function(response) { - if(!response.mount) { + if(!sessionInfo.mount) { transition(PlayStateSessionOver); destroy(); } else { - audioDomElement.play(); + recreateAudioElement(); - retryAttempts = 0; + audioDomElement.load(); - transition(PlayStateInitializing); + retryAttempts = 0; + + transition(PlayStateInitializing); + + // keep this after transition, because any transition clears this timer + waitForBufferingTimeout = setTimeout(noBuffer, WAIT_FOR_BUFFER_TIMEOUT); + logger.debug("setting buffering timeout"); + rest.addPlayablePlay(musicSessionId, 'JamRuby::MusicSession', null, context.JK.currentUserId); + + if(needsCanPlayGuard()) { + $audio.bind('canplay', function() { + audioDomElement.play(); + }) + } + else { + audioDomElement.play(); + } - // keep this after transition, because any transition clears this timer - waitForBufferingTimeout = setTimeout(noBuffer, WAIT_FOR_BUFFER_TIMEOUT); - rest.addPlayablePlay(musicSessionId, 'JamRuby::MusicSession', null, context.JK.currentUserId); } }) } @@ -131,9 +133,7 @@ e.stopPropagation(); } - if(destroyed) return; - - if(!lazyAudioInit && !audioDomElement) throw "no audio element supplied; the user should not be able to attempt a pause" + //if(destroyed) return; transition(PlayStateNone); @@ -141,12 +141,13 @@ } function destroy() { - if(!destroyed) { - $audio.remove(); - $audio = null; - audioDomElement = null; - destroyed = true; - } + // if(!destroyed) { + //$audio.remove(); + //$audio = null; + //audioDomElement = null; + recreateAudioElement() + // destroyed = true; + //} } function onScreenChanged(e, data) { @@ -155,36 +156,49 @@ } } + function createAudioElementHtml() { + if (sessionInfo == null) throw "no session info"; + if (sessionInfo.mount == null) throw "no session mount info"; + + $audio = + $('') + $parent.append($audio) + audioDomElement = $audio.get(0); + audioBind(); + + } + // this is the only way to make audio stop buffering after the user hits pause function recreateAudioElement() { // jeez: http://stackoverflow.com/questions/4071872/html5-video-force-abort-of-buffering/13302599#13302599 - var originalSource = $audio.html() - audioDomElement.pause(); - audioDomElement.src = ''; - audioDomElement.load(); - var $parent = $audio.parent(); + if(audioDomElement) { + audioDomElement.pause(); + audioDomElement.src = ''; + audioDomElement.load(); + } $audio.remove(); - $audio = $('') - $audio.append(originalSource); - var $sources = $audio.find('source') - $.each($sources, function(i, source) { - var $source = $(source); - var bustedSource = cacheBustedSrc($source.attr('data-audio-src')) - $source.attr('src', bustedSource) - }) - $parent.append($audio); - audioDomElement = $audio.get(0); - audioBind(); + createAudioElementHtml(); logger.log("recreated audio element ") } function clearBufferTimeout() { if(waitForBufferingTimeout) { + logger.debug("clearing buffering timeout"); clearTimeout (waitForBufferingTimeout); waitForBufferingTimeout = null; } } + function clearPlayingTimeout() { + if(waitForPlayingTimeout) { + logger.debug("clearing playing timeout"); + clearTimeout (waitForPlayingTimeout); + waitForPlayingTimeout = null; + } + } + function transition(newState) { logger.log("transitioning from " + playState + " to " + newState); @@ -192,8 +206,15 @@ if(newState != PlayStateStalled) { clearBufferTimeout(); + clearPlayingTimeout(); } + if(newState == PlayStateBuffering) { + // give some time after buffering is seen to let play start + waitForPlayingTimeout = setTimeout(noPlay, WAIT_FOR_PLAYING_TIMEOUT) + } + + if( playState == PlayStateNone || playState == PlayStateEnded || playState == PlayStateFailedStart || @@ -208,6 +229,9 @@ triggerStateChange(); } + function noPlay() { + noBuffer(); + } function noBuffer() { waitForBufferingTimeout = null; @@ -224,7 +248,7 @@ checkServer() .done(function(response) { - if(!response.mount) { + if(!sessionInfo.mount) { transition(PlayStateSessionOver); destroy(); } @@ -353,6 +377,10 @@ function checkServer() { return rest.getSession(musicSessionId) + .done(function(response) { + console.log("assigning sessionInfo") + sessionInfo = response; + }) .fail(function(jqXHR) { if(jqXHR.status == 404 || jqXHR.status == 403) { transition(PlayStateSessionOver); @@ -454,7 +482,7 @@ if(refresh) { checkServer() .done(function(response) { - if(!response.mount) { + if(!sessionInfo.mount) { transition(PlayStateSessionOver); destroy(); }}) @@ -649,7 +677,7 @@ function openBubble() { checkServer().done(function(response) { - var mountId = response.mount ? response.mount.id : null + var mountId = sessionInfo.mount ? sessionInfo.mount.id : null if(mountId) { rest.getMount({id: mountId}) @@ -705,7 +733,6 @@ function initialize() { - musicSessionId = $parent.attr('data-music-session'); if(!musicSessionId) throw "data-music-session must be specified on $parentElement"; @@ -713,14 +740,6 @@ if(fanAccess === null) throw 'fan-access must be specified in $parentElement'; fanAccess = $parent.attr('fan-access') === 'true' // coerce to boolean - if(lazyAudioInit) { - // save the original src element (without any cache bust) - audioSrc = $parent.attr('data-audio-src'); - if(audioSrc === null) throw 'data-audio-src must be specified in $parentElement'; - audioType = $parent.attr('data-audio-type'); - if(audioType === null) throw 'data-audio-type must be specified in $parentElement'; - } - bindHoverDetail(); $audio = $('audio', $parent); @@ -733,21 +752,6 @@ throw "more than one