VRFS-1960 : download API for JamTracks, including a new message type, rescue job that builds the JKZ, and specs that exercise it all.

This commit is contained in:
Steven Miers 2014-12-17 17:19:36 -06:00
parent 795cb6f536
commit 34cb617f5f
14 changed files with 190 additions and 45 deletions

View File

@ -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);

View File

@ -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 {

View File

@ -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

View File

@ -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,

View File

@ -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 == ''

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

BIN
web/spec/files/on.ogg Normal file

Binary file not shown.