Merge branch 'develop' into feature/nocaptcha

This commit is contained in:
Steven Miers 2015-01-05 17:04:13 -06:00
commit 6cfe5decbf
111 changed files with 1932 additions and 593 deletions

View File

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

View File

@ -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(
$("<tr id=\"jam_track_tracks_detail\"></tr>").html(
$("<td colspan=\"1\"></td>")
).append(
$("<td colspan=\"4\"></td>").html(
"No Tracks"
)
)
);
}
else {
rowJamTrack.after(
$("<tr id=\"jam_track_tracks_detail\"></tr>").html(
$("<td/><td colspan=\"1\"><em><strong>Tracks in '" + name + "':</strong></em></td>")
).append(
$("<td colspan=\"4\"></td>").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(
$("<tr id=\"jam_track_tracks_detail\"></tr>").html(
$("<td colspan=\"18\"></td>")
).append(
$("<td colspan=\"4\"></td>").html(
"No Tracks"
)
)
);
}
else {
$rowJamTrack.after(
$("<tr id=\"jam_track_tracks_detail\"></tr>").html(
$("<td colspan=\"18\"></td>")
).append(
$("<td colspan=\"4\"></td>").html(
$jamTrackTracks.html()
)
)
);
}
}
showTracks($rowJamTrack)
})
});

View File

@ -2,25 +2,26 @@
= 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
= f.input :status, collection: JamRuby::JamTrack::STATUS
= f.input :recording_type, collection: JamRuby::JamTrack::RECORDING_TYPE
= 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 :price
= 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, :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
= f.input :sales_region, collection: JamRuby::JamTrack::SALES_REGION, include_blank: false
= 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|

View File

@ -1,9 +1,10 @@
= 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 :part
= 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, :required=>true, :input_html => { :rows=>1, :maxlength=>20, :type=>'numeric' }
= f.input :position
- if f.object.new_record?

View File

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

View File

@ -233,4 +233,6 @@ add_track_resource_id.sql
user_genres.sql
user_online.sql
icecast_source_changes.sql
diagnostics_user_id_index.sql
diagnostics_user_id_index.sql
jam_track_updates.sql
private_key_in_jam_track_rights.sql

View File

@ -0,0 +1,44 @@
-- 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:
CREATE SEQUENCE jam_tracks_next_seq;
ALTER TABLE jam_tracks
ALTER COLUMN id DROP DEFAULT,
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
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
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,
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,
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

@ -0,0 +1 @@
ALTER TABLE jam_track_rights ADD COLUMN private_key VARCHAR;

View File

@ -78,6 +78,9 @@ message ClientMessage {
SOURCE_UP = 252;
SOURCE_DOWN = 253;
// jamtracks notifications
JAM_TRACK_SIGN_COMPLETE = 260;
TEST_SESSION_MESSAGE = 295;
PING_REQUEST = 300;
@ -180,6 +183,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;
@ -594,6 +600,11 @@ message SourceDown {
optional string music_session = 1; // music session id
}
message JamTrackSignComplete {
required int32 jam_track_right_id = 1; // jam track right id
}
message SubscriptionMessage {
optional string type = 1; // the type of the subscription
optional string id = 2; // data about what to subscribe to, specifically

View File

@ -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/scheduled/stats_maker"
require "jam_ruby/resque/google_analytics_event"
require "jam_ruby/resque/batch_email_job"
@ -75,6 +77,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"
@ -193,6 +196,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

View File

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

View File

@ -11,7 +11,7 @@ class JamTrackUploader < CarrierWave::Uploader::Base
# Add a white list of extensions which are allowed to be uploaded.
def extension_white_list
%w(jka)
%w(jkz)
end
def store_dir

View File

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

View File

@ -0,0 +1,86 @@
require 'json'
require 'tempfile'
require 'open3'
require 'fileutils'
require 'open-uri'
module JamRuby
# Interact with external python tools to create the JKZ
class JamTracksManager
class << self
def save_jam_track_jkz(user, jam_track)
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
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 = APP_CONFIG.jamtracks_dir
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)
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")
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: #{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]
#raise ArgumentError, "output_jkz is empty #{output_jkz}" unless File.exists?(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.private_key=File.read("#{tmp_dir}/skey.pem")
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, '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
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|
io.write chunk
end
end
end
end
end # copy_url_to_file
end # self
end # class
end # module

View File

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

View File

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

View File

@ -712,6 +712,19 @@ module JamRuby
)
end
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 + receiver_id, #:route_to => CLIENT_TARGET,
: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

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

View File

@ -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}
@ -43,19 +43,47 @@ 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
class << self
def index(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
end
# 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
def filename
"#{name}.jka"
"#{name}.jkz"
end
# creates a short-lived URL that has access to the object.
@ -63,40 +91,49 @@ 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 => 'audio/jka', :secure => false})
s3_manager.sign_url(self[:url], {:expires => expiration_time, :response_content_type => 'audio/jkz', :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
def right_for_user(user)
jam_track_rights.where("user_id=?", user).first
end
start = options[:start].presence
start = start.to_i || 0
def self.list_downloads(user, limit = 100, since = 0)
since = 0 unless since || since == '' # guard against nil
downloads = []
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]
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
next_id = downloads[-1][:next] if downloads.length > 0
next_id = since if next_id.nil? # echo back to the client the same value they passed in, if there are no results
{
'downloads' => downloads,
'next' => next_id.to_s
}
end
private
private
def sanitize_active_admin
self.genre_id = nil if self.genre_id == ''

View File

@ -2,13 +2,94 @@ 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"
validates :user, presence:true
validates :jam_track, presence:true
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
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
errors.add(:download_count, "must be less than or equal to #{MAX_JAM_TRACK_DOWNLOADS}")
end
end
def self.ready_to_clean
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
# 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, :secure => false})
end
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
def self.list_keys(user, jamtracks)
if jamtracks.nil?
return []
end
JamTrack.select('jam_tracks.id, jam_track_rights.private_key AS private_key, jam_track_rights.id AS jam_track_right_id')
.joins("LEFT OUTER JOIN jam_track_rights ON jam_tracks.id = jam_track_rights.jam_track_id AND jam_track_rights.user_id = '#{user.id}'")
.where('jam_tracks.id IN (?)', jamtracks)
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}
@ -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

View File

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

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?
@ -203,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."
@ -1186,6 +1190,19 @@ module JamRuby
end
end
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)
msg = @@message_factory.client_update( product, version, uri, size)

View File

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

View File

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

View File

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

View File

@ -3,7 +3,7 @@ require 'resque-lonely_job'
module JamRuby
class BatchEmailJob
extend Resque::Plugins::LonelyJob
extend Resque::Plugins::JamLonelyJob
@@log = Logging.logger[BatchEmailJob]

View File

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

View File

@ -0,0 +1,38 @@
require 'json'
require 'resque'
require 'resque-retry'
require 'net/http'
require 'digest/md5'
module JamRuby
class JamTracksBuilder
extend JamRuby::ResqueStats
@queue = :jam_tracks_builder
def log
@log || Logging.logger[JamTracksBuilder]
end
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)
length = @jam_track_right.url.size()
md5 = Digest::MD5.new
@jam_track_right.finish_sign(length, md5.to_s)
log.info "Signed jamtrack to #{@jam_track_right[:url]}"
end
end
end

View File

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

View File

@ -1,26 +1,52 @@
require 'resque'
# 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
@ -36,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 }

View File

@ -7,7 +7,7 @@ require 'digest/md5'
module JamRuby
class ActiveMusicSessionCleaner
extend Resque::Plugins::LonelyJob
extend Resque::Plugins::JamLonelyJob
attr_accessor :interval
@ -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

View File

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

View File

@ -2,9 +2,6 @@
module JamRuby
class CleanupFacebookSignup
@queue = :scheduled_cleanup_facebook_signup
@@log = Logging.logger[CleanupFacebookSignup]

View File

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

View File

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

View File

@ -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,13 +23,7 @@ module JamRuby
end
def self.perform
@@log.debug("waking up")
JamWebEventMachine.run_wait_stop do
IcecastSourceCheck.new.run
end
@@log.debug("done")
IcecastSourceCheck.new.run
end
@ -37,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
@ -44,18 +42,20 @@ module JamRuby
end
end
end
log.debug("done")
end
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

View File

@ -0,0 +1,34 @@
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::JamLonelyJob
@queue = :jam_tracks_cleaner
class << self
def log
@log || Logging.logger[JamTracksCleaner]
end
def 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 perform
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
end
end
end
end

View File

@ -6,7 +6,7 @@ require 'digest/md5'
module JamRuby
class MusicSessionScheduler
extend Resque::Plugins::LonelyJob
extend Resque::Plugins::JamLonelyJob
@queue = :music_session_scheduler
@ -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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,7 +6,7 @@ require 'digest/md5'
module JamRuby
class UnusedMusicNotationCleaner
extend Resque::Plugins::LonelyJob
extend Resque::Plugins::JamLonelyJob
@queue = :unused_music_notation_cleaner
@ -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

View File

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

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

Binary file not shown.

View File

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

View File

@ -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)
@ -16,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)
@ -27,5 +36,92 @@ describe JamTrackRight do
right_2.errors[:user_id].should == ['has already been taken']
end
end
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 raise_error(ArgumentError)
end
it "should create" do
ogg_path = File.join('spec', 'files', 'on.ogg')
user = FactoryGirl.create(:user)
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, 'rb'))
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
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?(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
describe "list_keys" do
let(:user) {FactoryGirl.create(:user)}
it "empty" do
JamTrackRight.list_keys(user, nil).should eq([])
end
it "bogus key" do
JamTrackRight.list_keys(user, ['a']).should eq([])
end
it "valid track with no rights to it by querying user" do
jam_track = FactoryGirl.create(:jam_track)
keys = JamTrackRight.list_keys(user, [jam_track.id])
keys.length.should == 0
end
it "valid track with rights to it by querying user" do
jam_track_right = FactoryGirl.create(:jam_track_right, private_key: 'keyabc')
keys = JamTrackRight.list_keys(jam_track_right.user, [jam_track_right.jam_track.id])
keys.length.should == 1
keys[0].id.should == jam_track_right.jam_track.id
keys[0]['private_key'].should eq('keyabc')
end
end
end

View File

@ -97,7 +97,7 @@ describe JamTrack do
end
describe "upload/download" do
JKA_NAME = 'blah.jka'
JKA_NAME = 'blah.jkz'
in_directory_with_file(JKA_NAME)

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
* TODO
== TODO:
Jasmine Javascript Unit Tests
=============================
@ -11,3 +11,4 @@ $ rake jasmine
Open browser to localhost:8888

View File

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

View File

@ -1344,6 +1344,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",
@ -1374,15 +1383,16 @@
function updateBillingInfo(options) {
return $.ajax({
type: "PUT",
url: '/api/recurly/update_billing_info?' + $.param(options),
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",
url: '/api/recurly/place_order?' + $.param(options),
dataType: "json",
contentType: 'application/json'
@ -1534,6 +1544,7 @@
this.addJamtrackToShoppingCart = addJamtrackToShoppingCart;
this.getShoppingCarts = getShoppingCarts;
this.removeShoppingCart = removeShoppingCart;
this.clearShoppingCart = clearShoppingCart;
this.getRecurlyAccount = getRecurlyAccount;
this.createRecurlyAccount = createRecurlyAccount;
this.getBillingInfo = getBillingInfo;

View File

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

View File

@ -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 =
$('<audio preload="none">' +
'<source src="' + audioSrc + '" type="' + audioType + '" />' +
'</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,30 +156,49 @@
}
}
function createAudioElementHtml() {
if (sessionInfo == null) throw "no session info";
if (sessionInfo.mount == null) throw "no session mount info";
$audio =
$('<audio preload="none">' +
'<source src="' + cacheBustedSrc(sessionInfo.mount.url) + '" type="' + sessionInfo.mount.mime_type + '"/>' +
'</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();
$parent.append('<audio preload="none"></audio>');
$audio = $('audio', $parent);
$audio.append(originalSource);
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);
@ -186,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 ||
@ -202,6 +229,9 @@
triggerStateChange();
}
function noPlay() {
noBuffer();
}
function noBuffer() {
waitForBufferingTimeout = null;
@ -218,13 +248,16 @@
checkServer()
.done(function(response) {
if(!response.mount) {
if(!sessionInfo.mount) {
transition(PlayStateSessionOver);
destroy();
}
else {
// tell audio to stop/start, in attempt to retry
//audioDomElement.pause();
recreateAudioElement();
audioDomElement.load();
if(needsCanPlayGuard()) {
$audio.bind('canplay', function() {
@ -344,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);
@ -443,7 +480,12 @@
// we have cause to believe the session is done; check against the server
if(refresh) {
checkServer();
checkServer()
.done(function(response) {
if(!sessionInfo.mount) {
transition(PlayStateSessionOver);
destroy();
}})
}
}
@ -634,7 +676,8 @@
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})
@ -648,6 +691,10 @@
}
else {
mountInfo = null;
transition(PlayStateSessionOver);
destroy();
context.JK.app.layout.notify('This session can not currently broadcast')
}
})
@ -679,8 +726,12 @@
})
}
}
function initialize() {
function cacheBustedSrc(src) {
return src + '?cache-buster=' + new Date().getTime();
}
function initialize() {
musicSessionId = $parent.attr('data-music-session');
if(!musicSessionId) throw "data-music-session must be specified on $parentElement";
@ -689,13 +740,6 @@
if(fanAccess === null) throw 'fan-access must be specified in $parentElement';
fanAccess = $parent.attr('fan-access') === 'true' // coerce to boolean
if(lazyAudioInit) {
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);
@ -708,11 +752,6 @@
throw "more than one <audio> element found";
}
if(!lazyAudioInit) {
audioDomElement = $audio.get(0);
audioBind();
}
$parent.bind('play.listenBroadcast', play);
$parent.bind('pause.listenBroadcast', pause);
$parent.bind('destroy.listenBroadcast', destroy);

View File

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

View File

@ -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();
@ -267,7 +268,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();
@ -317,16 +318,15 @@
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 = 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;
@ -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");

View File

@ -23,9 +23,11 @@
// related to listen
function stateChange(e, data) {
var $listenLink = e.element;
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)
{
@ -39,8 +41,9 @@
}
if(data.isEnd) {
$listenText.text('Listen').removeClass('statusing')
stopPlay();
//$listenDetails.removeClass('statusing')
//$listenText.text('Listen')
stopPlay($listenLink);
}
if(data.isSessionOver) {
@ -60,8 +63,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')

View File

@ -73,14 +73,17 @@
}
function startPlay() {
var img = $('.play-icon');
logger.debug("user initiated play")
var img = $('.play-icon', $playButton);
img.attr('src', '/assets/content/icon_pausebutton.png');
$controls.trigger('play.listenBroadcast');
playing = true;
}
function stopPlay() {
var img = $('.play-icon');
logger.debug("user initiated pause")
var img = $('.play-icon', $playButton);
$status.text('LIVE SESSION IN PROGRESS')
img.attr('src', '/assets/content/icon_playbutton.png');
$controls.trigger('pause.listenBroadcast');
playing = false;
@ -98,7 +101,7 @@
}
function initialize(musicSessionId) {
$controls = $('.sessions-page .recording-controls');
$controls = $('.sessions-page .session-controls');
$status = $('.sessions-page .session-status')
$('.timeago').timeago();
@ -130,7 +133,7 @@
$("#btnLike").click(like);
$playButton.trigger('click');
startPlay();
pollForUpdates(musicSessionId);
}

View File

@ -166,6 +166,10 @@
}
}
.thanks-panel {
padding: 30px;
}
.order-panel {
padding: 30px;

View File

@ -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;
}
@ -73,6 +79,9 @@
.no-cart-items {
margin-top: 30px;
text-align: center;
font-style: italic;
font-weight: bold;
font-size: 15px;
}
}
}

View File

@ -1,6 +1,6 @@
@import "client/common.css.scss";
.feed-entry .recording-controls, .feed-entry .session-controls, .landing-details .recording-controls {
.feed-entry .recording-controls, .feed-entry .session-controls, .landing-details .recording-controls, .landing-details .session-controls {
margin-top:0px;
margin-bottom:5px;
padding:8px 5px 8px 10px;
@ -8,7 +8,7 @@
position:relative;
}
.landing-details .recording-controls, .landing-details .recording-controls {
.landing-details .recording-controls, .landing-details .session-controls {
background-color:#242323;

View File

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

View File

@ -0,0 +1,58 @@
class ApiJamTracksController < ApiController
# have to be signed in currently to see this screen
before_filter :api_signed_in_user
before_filter :lookup_jam_track_right, :only => :download
respond_to :json
def index
data = JamTrack.index(params)
@jam_tracks, @next = data[0], data[1]
render "api_jam_tracks/index", :layout => nil
end
def 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
def download
if @jam_track_right.valid?
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
render :json => { :message => "download limit surpassed", :errors=>@jam_track_right.errors }, :status => 403
end
end
def keys
jamtrack_ids = params[:jamtracks]
unless jamtrack_ids.kind_of?(Array)
render :json => {message: 'jamtracks parameter must be an array'}, :status => 200
return
end
@jam_tracks = JamTrackRight.list_keys(current_user, params[:jamtracks])
render "api_jam_tracks/list_keys", :layout => nil
end
private
def lookup_jam_track_right
@jam_track_right = JamTrackRight.where("jam_track_id=? AND user_id=?", params[:id], current_user.id).first
raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR unless @jam_track_right
end
end # class ApiJamTracksController

View File

@ -1,15 +0,0 @@
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]
end
end

View File

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

View File

@ -1,155 +1,98 @@
require 'recurly_client'
class ApiRecurlyController < ApiController
before_filter :api_signed_in_user
before_filter :create_client
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
@account = @client.find_or_create_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
if @account.errors.any?
response.status = 404
else
current_user.recurly_code = @account.account_code
current_user.save
@account.billing_info = params[:billing_info]
@account.billing_info.save
logger.debug @account
end
respond_with @account
rescue Recurly::Error, NoMethodError => e
render :json => { :message => e.inspect }, :status => 404
def delete_account
@client.delete_account(current_user)
render json: {}, status: 200
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.reculry_code)
respond_with @account
rescue Recurly::Error, NoMethodError => e
render :json => { message: ValidationMessages::RECURLY_ERROR}, :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
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
@account.first_name = current_user.first_name
@account.last_name = current_user.last_name
@account.email = current_user.email
@account.update
if @account.errors.any?
response.status = 404
else
current_user.recurly_code = @account.account_code
current_user.save
end
respond_with @account
rescue Recurly::Error, NoMethodError => e
render :json => { message: ValidationMessages::RECURLY_ERROR}, :status => 404
end
# get subscription
def get_subscription
@account = Recurly::Acount.find(current_user.reculry_code)
respond_with @account.subscriptions.last
rescue Recurly::Error, NoMethodError => e
render :json => { message: ValidationMessages::RECURLY_GET_ACCOUNT_ERROR}, :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
if current_user.recurly_code.nil?
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
end
rescue Recurly::Error, NoMethodError => e
render :json => { message: ValidationMessages::RECURLY_ERROR}, :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
if current_user.recurly_code.nil?
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
end
@account = Recurly::Acount.find(current_user.reculry_code)
@account.billing_info = params
@account.billing_info.save
if @account.erros.any?
response.status = :unprocessable_entity
end
respond_with @account
end
rescue Recurly::Error, NoMethodError => e
render :json => { message: ValidationMessages::RECURLY_ERROR}, :status => 404
@account=@client.update_billing_info(current_user, params[:billing_info])
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 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
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
@account = Recurly::Account.find(current_user.recurly_code)
@account.billing_info = params
@account.billing_info.save
# create subscription.
if @account.erros.any?
response.status = :unprocessable_entity
end
respond_with @account
end
rescue Recurly::Error, NoMethodError => e
render :json => { message: ValidationMessages::RECURLY_ERROR}, :status => 404
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
end
private
def create_client
@client = RecurlyClient.new
end
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

View File

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

View File

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

View File

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

View File

@ -3,5 +3,5 @@ node :next do |page|
end
node :jamtracks do |page|
partial "api_jamtracks/show", object: @jamtracks
partial "api_jam_tracks/show", object: @jam_tracks
end

View File

@ -0,0 +1,9 @@
object @jam_tracks
node do |jam_track|
{
id: jam_track['id'],
private: jam_track['private_key'],
error: jam_track['private_key'] ? nil : ( jam_track['jam_track_right_id'] ? 'no_key' : 'not_purchased' )
}
end

View File

@ -1,4 +1,4 @@
object @jamtrack
object @jam_track
attributes :id, :name, :description, :recording_type, :original_artist, :songwriter, :publisher, :sales_region, :price
@ -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) {

View File

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

View File

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

View File

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

View File

@ -53,7 +53,7 @@
<% end %>
<br clear="all" />
<div class="w100">
<div class="recording-controls <%= @music_session.is_over? ? 'ended' : 'inprogress' %>" data-music-session="<%=@music_session.id %>">
<div class="session-controls <%= @music_session.is_over? ? 'ended' : 'inprogress' %>" data-music-session="<%=@music_session.id %>">
<a class="left play-button" href="#">
<%= image_tag 'content/icon_pausebutton.png', width:20, height:20, class:'play-icon' %>
<% if @music_session.active_music_session && @music_session.active_music_session.mount %>

View File

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

View File

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

View File

@ -207,6 +207,9 @@ if defined?(Bundler)
config.icecast_wait_after_reload = 0 # 0 seconds. a hack needed until VRFS-1043... maybe
config.source_changes_missing_secs = 2 # amount of time before we think it's odd that there are no source_change notifications
# 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'

View File

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

View File

@ -89,4 +89,5 @@ SampleApp::Application.configure do
# For product = JamKazamDev
config.youtube_developer_key = "AI39si5bPqiNc5GQHscWJh9Wl1WTAr9aZqr_YncUvaR7Kz0rgPdBVWVubHZ94xZ3KLIBqtE9mu3VZe-UpMU80QxXoC66kBNp7A"
config.youtube_app_name = "JamKazamDev"
config.jam_tracks_available=true
end

View File

@ -101,7 +101,8 @@ SampleApp::Application.configure do
config.recurly_private_api_key = '4631527f203b41848523125b3ae51341'
# 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.recurly_subdomain = 'jamkazam-test'
config.log_level = :debug
config.jam_tracks_available = true
end

View File

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

View File

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

View File

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

View File

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

View File

@ -191,13 +191,17 @@ 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' => 'api_jam_tracks#index', :via => :get, :as => 'api_jam_tracks_list'
match '/jamtracks/downloads' => 'api_jam_tracks#downloads', :via => :get, :as => 'api_jam_tracks_downloads'
match '/jamtracks/download/:id' => 'api_jam_tracks#download', :via => :get, :as => 'api_jam_tracks_download'
match '/jamtracks/keys' => 'api_jam_tracks#keys', :via => :post, :as => 'api_jam_tracks_keys'
# Shopping carts
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
@ -233,11 +237,14 @@ 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 => :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 => :put
match '/recurly/place_order' => 'api_recurly#place_order', :via => :post
# login/logout
match '/auth_session' => 'api_users#auth_session_create', :via => :post

View File

@ -15,6 +15,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"

View File

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

149
web/lib/recurly_client.rb Normal file
View File

@ -0,0 +1,149 @@
require 'recurly'
module JamRuby
class RecurlyClient
def initialize()
end
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.new(account.errors) if account.errors.any?
rescue Recurly::Error, NoMethodError => x
raise RecurlyClientError, x.to_s
else
if account
current_user.update_attribute(:recurly_code, account.account_code)
end
end
account
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.new(account.errors) if account.errors.any?
else
raise RecurlyClientError, "Could not find account to update billing info."
end
account
end
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_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
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)
account = get_account(current_user)
if(account.nil?)
account = create_account(current_user, billing_info)
else
update_billing_info(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
attr_accessor :errors
def initialize(data)
if data.respond_to?('has_key?')
self.errors = data
else
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

View File

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

View File

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

View File

@ -0,0 +1,167 @@
require 'spec_helper'
describe ApiJamTracksController do
render_views
include CarrierWave::Test::Matchers
before(:all) do
@original_storage = JamTrackTrackUploader.storage = :fog
@original_storage_right = JamTrackRightUploader.storage = :fog
end
after(:all) do
JamTrackTrackUploader.storage = @original_storage
JamTrackRightUploader.storage = @original_storage_right
end
before(:each) do
@user = FactoryGirl.create(:user)
@jam_track = FactoryGirl.create(:jam_track)
controller.current_user = @user
end
describe "download functionality" do
it "lists available tracks" do
get :index
response.should be_success
json = JSON.parse(response.body)#, :symbolize_names => true)
json["next"].should be_nil
json["jamtracks"].length.should == 1
jam_track2 = FactoryGirl.create(:jam_track)
get :index
response.should be_success
json = JSON.parse(response.body)#, :symbolize_names => true)
json["next"].should be_nil
json["jamtracks"].length.should == 2
end
it "lists owned tracks" do
get :downloads
response.should be_success
json = JSON.parse(response.body)
json['downloads'].should have(0).items
right = JamTrackRight.create(:user=>@user, :jam_track=>@jam_track)
get :downloads
response.should be_success
json = JSON.parse(response.body)
json['downloads'].should have(1).items
end
it "finds a download" do
#get "/download/#{right.id}/"
right = JamTrackRight.create(:user=>@user, :jam_track=>@jam_track)
get :download, { :format => 'json', :id => @jam_track.id }
response.should be_success
response.status.should == 202
response.body.should =~ /not available.*/
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)
right.private_key.should be_nil
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.private_key.should_not be_nil
right.download_count.should eq(0)
get :download, :id => @jam_track.id
response.status.should == 302
response.location.should =~ /.*#{Regexp.escape(right.filename)}.*/
right.reload
right.download_count.should eq(1)
notifications = Notification.where(:jam_track_right_id => right.id)
notifications.count.should == 1
end
end
describe "keys" do
it "empty" do
get :keys, jamtracks: []
response.status.should == 200
json = JSON.parse(response.body)
json.length == 0
end
it "track with no rights" do
get :keys, jamtracks: [@jam_track.id]
response.status.should == 200
json = JSON.parse(response.body)
json.length.should == 1
puts json.inspect
json[0]['id'].should == @jam_track.id
json[0]['private'].should be_nil
json[0]['error'].should == 'not_purchased'
end
it "track with no key" do
right = FactoryGirl.create(:jam_track_right, user: @user, private_key: nil, jam_track: @jam_track)
get :keys, jamtracks: [@jam_track.id]
response.status.should == 200
json = JSON.parse(response.body)
json.length.should == 1
json[0]['id'].should == @jam_track.id
json[0]['private'].should be_nil
json[0]['error'].should == 'no_key'
end
it "track with key" do
right = FactoryGirl.create(:jam_track_right, user: @user, private_key: 'abc', jam_track: @jam_track)
get :keys, jamtracks: [@jam_track.id]
response.status.should == 200
json = JSON.parse(response.body)
json.length.should == 1
json[0]['id'].should == @jam_track.id
json[0]['private'].should eq('abc')
json[0]['error'].should be_nil
end
it "non-owning user asking for a real track" do
right = FactoryGirl.create(:jam_track_right, user: FactoryGirl.create(:user), private_key: 'abc', jam_track: @jam_track)
get :keys, jamtracks: [@jam_track.id]
response.status.should == 200
json = JSON.parse(response.body)
json[0]['id'].should == @jam_track.id
json[0]['private'].should be_nil
json[0]['error'].should == 'not_purchased'
end
end
end

View File

@ -1,34 +1,118 @@
# 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_client'
#require 'recurly/account'
describe ApiRecurlyController, :type=>:controller do
render_views
# 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[: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'
@billing_info[:vat_number] = ''
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 send correct error" do
@billing_info[:number]='121'
post :create_account, {:format => 'json', :billing_info=>@billing_info}
response.status.should == 404
body = JSON.parse(response.body)
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'}
response.should be_success
get :get_account
body = JSON.parse(response.body)
response.should be_success
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['first_name'].should eq("Person")
@user.update_attribute(:first_name, "Thing")
controller.current_user = @user
put :update_account
body = JSON.parse(response.body)
body['first_name'].should eq("Thing")
get :get_account, { :format => 'json'}
response.should be_success
body = JSON.parse(response.body)
body['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)
end
it "should update billing info" do
# $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
response.should be_success
body = JSON.parse(response.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
body = JSON.parse(response.body)
end
end # spec

View File

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

View File

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

Some files were not shown because too many files have changed in this diff Show More