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 000000000..743d6e3aa Binary files /dev/null and b/web/spec/files/on.ogg differ