play 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +2 -0
- data/Gemfile.lock +87 -0
- data/README.md +139 -0
- data/Rakefile +185 -0
- data/bin/play +49 -0
- data/config.ru +10 -0
- data/db/migrate/01_create_schema.rb +55 -0
- data/lib/play.rb +73 -0
- data/lib/play/album.rb +6 -0
- data/lib/play/app.rb +118 -0
- data/lib/play/app/api.rb +110 -0
- data/lib/play/artist.rb +21 -0
- data/lib/play/client.rb +67 -0
- data/lib/play/core_ext/hash.rb +6 -0
- data/lib/play/history.rb +7 -0
- data/lib/play/library.rb +58 -0
- data/lib/play/office.rb +34 -0
- data/lib/play/song.rb +97 -0
- data/lib/play/templates/album_songs.mustache +5 -0
- data/lib/play/templates/artist_songs.mustache +5 -0
- data/lib/play/templates/index.mustache +18 -0
- data/lib/play/templates/layout.mustache +23 -0
- data/lib/play/templates/now_playing.mustache +5 -0
- data/lib/play/templates/play_history.mustache +3 -0
- data/lib/play/templates/profile.mustache +24 -0
- data/lib/play/templates/search.mustache +3 -0
- data/lib/play/templates/show_song.mustache +9 -0
- data/lib/play/templates/song.mustache +21 -0
- data/lib/play/user.rb +59 -0
- data/lib/play/views/album_songs.rb +9 -0
- data/lib/play/views/artist_songs.rb +9 -0
- data/lib/play/views/index.rb +9 -0
- data/lib/play/views/layout.rb +6 -0
- data/lib/play/views/now_playing.rb +17 -0
- data/lib/play/views/play_history.rb +9 -0
- data/lib/play/views/profile.rb +9 -0
- data/lib/play/views/search.rb +9 -0
- data/lib/play/views/show_song.rb +19 -0
- data/lib/play/vote.rb +7 -0
- data/play.gemspec +129 -0
- data/play.yml.example +22 -0
- data/public/css/base.css +129 -0
- data/test/helper.rb +33 -0
- data/test/spec/mini.rb +24 -0
- data/test/test_api.rb +118 -0
- data/test/test_app.rb +57 -0
- data/test/test_artist.rb +15 -0
- data/test/test_client.rb +11 -0
- data/test/test_library.rb +19 -0
- data/test/test_office.rb +26 -0
- data/test/test_play.rb +17 -0
- data/test/test_song.rb +78 -0
- data/test/test_user.rb +21 -0
- metadata +299 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
play (0.0.1)
|
5
|
+
SystemTimer
|
6
|
+
activerecord
|
7
|
+
mustache
|
8
|
+
mysql2
|
9
|
+
oa-oauth
|
10
|
+
rack (~> 1.2.2)
|
11
|
+
ruby-audioinfo
|
12
|
+
sinatra
|
13
|
+
sqlite3
|
14
|
+
yajl-ruby
|
15
|
+
|
16
|
+
GEM
|
17
|
+
remote: http://rubygems.org/
|
18
|
+
specs:
|
19
|
+
SystemTimer (1.2.3)
|
20
|
+
activemodel (3.0.7)
|
21
|
+
activesupport (= 3.0.7)
|
22
|
+
builder (~> 2.1.2)
|
23
|
+
i18n (~> 0.5.0)
|
24
|
+
activerecord (3.0.7)
|
25
|
+
activemodel (= 3.0.7)
|
26
|
+
activesupport (= 3.0.7)
|
27
|
+
arel (~> 2.0.2)
|
28
|
+
tzinfo (~> 0.3.23)
|
29
|
+
activesupport (3.0.7)
|
30
|
+
addressable (2.2.5)
|
31
|
+
apetag (1.1.2)
|
32
|
+
cicphash (>= 1.0.0)
|
33
|
+
arel (2.0.9)
|
34
|
+
builder (2.1.2)
|
35
|
+
cicphash (1.0.0)
|
36
|
+
faraday (0.6.1)
|
37
|
+
addressable (~> 2.2.4)
|
38
|
+
multipart-post (~> 1.1.0)
|
39
|
+
rack (< 2, >= 1.1.0)
|
40
|
+
flacinfo-rb (0.4)
|
41
|
+
i18n (0.5.0)
|
42
|
+
mocha (0.9.12)
|
43
|
+
mp4info (1.7.3)
|
44
|
+
multi_json (1.0.1)
|
45
|
+
multipart-post (1.1.0)
|
46
|
+
mustache (0.99.3)
|
47
|
+
mysql2 (0.2.7)
|
48
|
+
nokogiri (1.4.4)
|
49
|
+
oa-core (0.2.4)
|
50
|
+
oa-oauth (0.2.4)
|
51
|
+
faraday (~> 0.6.1)
|
52
|
+
multi_json (>= 0.0.5)
|
53
|
+
nokogiri (~> 1.4.2)
|
54
|
+
oa-core (= 0.2.4)
|
55
|
+
oauth (~> 0.4.0)
|
56
|
+
oauth2 (~> 0.4.1)
|
57
|
+
oauth (0.4.4)
|
58
|
+
oauth2 (0.4.1)
|
59
|
+
faraday (~> 0.6.1)
|
60
|
+
multi_json (>= 0.0.5)
|
61
|
+
rack (1.2.2)
|
62
|
+
ruby-audioinfo (0.1.7)
|
63
|
+
apetag (= 1.1.2)
|
64
|
+
flacinfo-rb (>= 0.4)
|
65
|
+
mp4info (>= 1.7.3)
|
66
|
+
ruby-mp3info (>= 0.6.3)
|
67
|
+
ruby-ogginfo (>= 0.3.1)
|
68
|
+
wmainfo-rb (>= 0.5)
|
69
|
+
ruby-mp3info (0.6.13)
|
70
|
+
ruby-ogginfo (0.6.5)
|
71
|
+
running_man (0.2.1)
|
72
|
+
sinatra (1.2.6)
|
73
|
+
rack (~> 1.1)
|
74
|
+
tilt (< 2.0, >= 1.2.2)
|
75
|
+
sqlite3 (1.3.3)
|
76
|
+
tilt (1.3)
|
77
|
+
tzinfo (0.3.27)
|
78
|
+
wmainfo-rb (0.6)
|
79
|
+
yajl-ruby (0.8.2)
|
80
|
+
|
81
|
+
PLATFORMS
|
82
|
+
ruby
|
83
|
+
|
84
|
+
DEPENDENCIES
|
85
|
+
mocha
|
86
|
+
play!
|
87
|
+
running_man
|
data/README.md
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
# Play
|
2
|
+
Play is your company's dj.
|
3
|
+
|
4
|
+
## Background
|
5
|
+
|
6
|
+
We want to play music at our office. Everyone has their own library on their
|
7
|
+
own machines, and everyone except for me plays shitty music. Play is designed
|
8
|
+
to make office music more palatable.
|
9
|
+
|
10
|
+
Play is **api-driven** and **web-driven**. All music is dropped on a central
|
11
|
+
Mac system. Once it's available to Play, users can control what's being played.
|
12
|
+
Users can either use a nice web view or the API, which lends itself for use on
|
13
|
+
the command line or through Campfire.
|
14
|
+
|
15
|
+
Play will play all the songs that are added to its Queue. Play will play the
|
16
|
+
crap out of that Queue. And you know what? If there's nothing left in the
|
17
|
+
Queue, Play will figure out who's in the office and play something that they'll
|
18
|
+
like.
|
19
|
+
|
20
|
+
No shit.
|
21
|
+
|
22
|
+
## Install
|
23
|
+
|
24
|
+
The underlying tech of Play uses `afplay` to control music (for now), so you'll
|
25
|
+
need a Mac. `afplay` is just a simple command-line wrapper to OS X's underlying
|
26
|
+
music libraries, so it should come preinstalled with your Mac, and it should
|
27
|
+
play anything iTunes can play.
|
28
|
+
|
29
|
+
Play also expects MySQL to be installed.
|
30
|
+
|
31
|
+
### Install the gem
|
32
|
+
|
33
|
+
Play itself is installed with a gem.
|
34
|
+
|
35
|
+
gem install play
|
36
|
+
|
37
|
+
### Fill out ~/.play.yml
|
38
|
+
|
39
|
+
You'll need to set up your configuration values, which we store in
|
40
|
+
`~/.play.yml`. You can view the [example file on
|
41
|
+
GitHub](https://github.com/holman/play/blob/master/play.yml.example).
|
42
|
+
|
43
|
+
### Set up your database
|
44
|
+
|
45
|
+
This is a bit of a pain that we'll correct eventually. For now, create your
|
46
|
+
MySQL database by hand. We expect the database to be called `play`, but it's
|
47
|
+
really pulled from whatever you have in `~/.play.yml`. When that's set up, run:
|
48
|
+
|
49
|
+
bin/play --migrate
|
50
|
+
|
51
|
+
### Set up your GitHub application
|
52
|
+
|
53
|
+
Next, go to GitHub and [register a new OAuth
|
54
|
+
application](https://github.com/account/applications/new). Users sign in with
|
55
|
+
their GitHub account. Copy the Client ID and Client Secret into Play's
|
56
|
+
`~/.play.yml` file.
|
57
|
+
|
58
|
+
### Set up your music folder
|
59
|
+
|
60
|
+
Next, tell Play where to look for music. It's the `path` attribute in
|
61
|
+
`~/.play.yml`. We'll then look at your path and import everything
|
62
|
+
recursively when you run:
|
63
|
+
|
64
|
+
play -i
|
65
|
+
|
66
|
+
## Play
|
67
|
+
|
68
|
+
Once you're all set up, you can spin up the web app with:
|
69
|
+
|
70
|
+
play -w
|
71
|
+
|
72
|
+
You can hit the server at [localhost:5050](http://localhost:5050). Queue some
|
73
|
+
hawt, hawt music up. We'll wait.
|
74
|
+
|
75
|
+
Ready? Cool. The only thing left to do is actually start the music server.
|
76
|
+
That's done with:
|
77
|
+
|
78
|
+
play -d
|
79
|
+
|
80
|
+
You'll detach it and put it in the background, where it will sit waiting for
|
81
|
+
salacious music to play for you. When you want to kill it for reals, run:
|
82
|
+
|
83
|
+
play -s
|
84
|
+
|
85
|
+
For all the fun commands and stuff you can do, just run:
|
86
|
+
|
87
|
+
play -h
|
88
|
+
|
89
|
+
|
90
|
+
## Set up your office (optional)
|
91
|
+
|
92
|
+
This isn't a required step. If nothing's in the queue and Play has still been
|
93
|
+
told to play something, it'll just play random music. But you can set it up so
|
94
|
+
it will play a suitable artist for someone who's currently in the office.
|
95
|
+
|
96
|
+
That particular step is left to the reader's imagination — here at GitHub we
|
97
|
+
poll our router's ARP tables and update an internal application with MAC
|
98
|
+
addresses — but all Play cares about is a URL that returns comma-separated
|
99
|
+
string identifiers. We get that string by hitting the `office_url` in
|
100
|
+
`~/.play.yml`. The string that's returned from that URL should look
|
101
|
+
something like this:
|
102
|
+
|
103
|
+
holman,kneath,defunkt
|
104
|
+
|
105
|
+
That means those three handsome lads are in the office right now. Once we get
|
106
|
+
that, we'll compare each of those with the users we have in our database. We do
|
107
|
+
that by checking a user attribute called `office_string`, which is just a
|
108
|
+
unique identifier to associate back to Play's user accounts. In this example,
|
109
|
+
I'd log into my account and change my `office_string` to be "holman" so I could
|
110
|
+
match up. It could be anything, though; we actually use MAC addresses here.
|
111
|
+
|
112
|
+
## Local development
|
113
|
+
|
114
|
+
If you're going to hack on this locally, you can use the normal process for
|
115
|
+
writing a Sintara app (`rackup`, `shotgun`, et cetera). The only thing we kind
|
116
|
+
of do differently in Play is we disable the GitHub OAuth callback (since your
|
117
|
+
development URL is different than it would be in production).
|
118
|
+
|
119
|
+
To actually use it locally, we'll automatically create a user called `user` for
|
120
|
+
you when you first access the app. That way you can actually mess around
|
121
|
+
without having to hit GitHub or create a user manually.
|
122
|
+
|
123
|
+
None of this happens if you launch Play with `bin/play -d`.
|
124
|
+
|
125
|
+
## Current Status
|
126
|
+
|
127
|
+
This is pretty rough. For the most part it should run pretty reliably for you,
|
128
|
+
but there's a bit of setup and configuration that I'd like to refine and do
|
129
|
+
away with until it's "ready" for prime time.
|
130
|
+
|
131
|
+
Once it's ready, pretty sure I'm going to make the most awesome screencast, and
|
132
|
+
then it's balls-out from there.
|
133
|
+
|
134
|
+
## ♬ ★★★
|
135
|
+
|
136
|
+
This was created by [Zach Holman](http://zachholman.com). You can follow me on
|
137
|
+
Twitter as [@holman](http://twitter.com).
|
138
|
+
|
139
|
+
I usually find myself playing Justice, Kanye West, and Muse at the office.
|
data/Rakefile
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
#############################################################################
|
6
|
+
#
|
7
|
+
# Helper functions
|
8
|
+
#
|
9
|
+
#############################################################################
|
10
|
+
|
11
|
+
def name
|
12
|
+
@name ||= Dir['*.gemspec'].first.split('.').first
|
13
|
+
end
|
14
|
+
|
15
|
+
def version
|
16
|
+
line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/]
|
17
|
+
line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
|
18
|
+
end
|
19
|
+
|
20
|
+
def date
|
21
|
+
Date.today.to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
def rubyforge_project
|
25
|
+
name
|
26
|
+
end
|
27
|
+
|
28
|
+
def gemspec_file
|
29
|
+
"#{name}.gemspec"
|
30
|
+
end
|
31
|
+
|
32
|
+
def gem_file
|
33
|
+
"#{name}-#{version}.gem"
|
34
|
+
end
|
35
|
+
|
36
|
+
def replace_header(head, header_name)
|
37
|
+
head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
|
38
|
+
end
|
39
|
+
|
40
|
+
#############################################################################
|
41
|
+
#
|
42
|
+
# Standard tasks
|
43
|
+
#
|
44
|
+
#############################################################################
|
45
|
+
|
46
|
+
task :default => :test
|
47
|
+
|
48
|
+
require 'rake/testtask'
|
49
|
+
Rake::TestTask.new(:test) do |test|
|
50
|
+
test.libs << 'lib' << 'test'
|
51
|
+
test.pattern = 'test/**/test_*.rb'
|
52
|
+
test.verbose = true
|
53
|
+
end
|
54
|
+
|
55
|
+
desc "Generate RCov test coverage and open in your browser"
|
56
|
+
task :coverage do
|
57
|
+
require 'rcov'
|
58
|
+
sh "rm -fr coverage"
|
59
|
+
sh "rcov test/test_*.rb"
|
60
|
+
sh "open coverage/index.html"
|
61
|
+
end
|
62
|
+
|
63
|
+
require 'rake/rdoctask'
|
64
|
+
Rake::RDocTask.new do |rdoc|
|
65
|
+
rdoc.rdoc_dir = 'rdoc'
|
66
|
+
rdoc.title = "#{name} #{version}"
|
67
|
+
rdoc.rdoc_files.include('README*')
|
68
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
69
|
+
end
|
70
|
+
|
71
|
+
desc "Open an irb session preloaded with this library"
|
72
|
+
task :console do
|
73
|
+
sh "irb -rubygems -r ./lib/#{name}.rb"
|
74
|
+
end
|
75
|
+
|
76
|
+
#############################################################################
|
77
|
+
#
|
78
|
+
# Custom tasks (add your own tasks here)
|
79
|
+
#
|
80
|
+
#############################################################################
|
81
|
+
|
82
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/lib')
|
83
|
+
|
84
|
+
require 'yaml'
|
85
|
+
|
86
|
+
task :environment do
|
87
|
+
ENV['RACK_ENV'] ||= 'development'
|
88
|
+
require 'lib/play'
|
89
|
+
require "bundler/setup"
|
90
|
+
end
|
91
|
+
|
92
|
+
desc "Open an irb session preloaded with this library"
|
93
|
+
task :console do
|
94
|
+
sh "irb -rubygems -r ./lib/play"
|
95
|
+
end
|
96
|
+
|
97
|
+
namespace :db do
|
98
|
+
task :create do
|
99
|
+
config = YAML::load(File.open("#{ENV['HOME']}/.play.yml"))
|
100
|
+
`mysql -u#{config['db']['user']} \
|
101
|
+
--password='#{config['db']['password']}' \
|
102
|
+
--execute=\'CREATE DATABASE #{config['db']['database']} CHARACTER SET utf8 COLLATE utf8_unicode_ci;'`
|
103
|
+
end
|
104
|
+
|
105
|
+
task :drop do
|
106
|
+
config = YAML::load(File.open("#{ENV['HOME']}/.play.yml"))
|
107
|
+
`mysql --user=#{config['db']['user']} \
|
108
|
+
--password='#{config['db']['password']}' \
|
109
|
+
--execute='DROP DATABASE #{config['db']['database']};'`
|
110
|
+
end
|
111
|
+
|
112
|
+
desc "Migrate the database through scripts in db/migrate."
|
113
|
+
task :migrate => :environment do
|
114
|
+
ActiveRecord::Base.establish_connection(Play.config['db'])
|
115
|
+
ActiveRecord::Migrator.migrate("db/migrate")
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
#############################################################################
|
120
|
+
#
|
121
|
+
# Packaging tasks
|
122
|
+
#
|
123
|
+
#############################################################################
|
124
|
+
|
125
|
+
desc "Create tag v#{version} and build and push #{gem_file} to Rubygems"
|
126
|
+
task :release => :build do
|
127
|
+
unless `git branch` =~ /^\* master$/
|
128
|
+
puts "You must be on the master branch to release!"
|
129
|
+
exit!
|
130
|
+
end
|
131
|
+
sh "git commit --allow-empty -a -m 'Release #{version}'"
|
132
|
+
sh "git tag v#{version}"
|
133
|
+
sh "git push origin master"
|
134
|
+
sh "git push origin v#{version}"
|
135
|
+
sh "gem push pkg/#{name}-#{version}.gem"
|
136
|
+
end
|
137
|
+
|
138
|
+
desc "Build #{gem_file} into the pkg directory"
|
139
|
+
task :build => :gemspec do
|
140
|
+
sh "mkdir -p pkg"
|
141
|
+
sh "gem build #{gemspec_file}"
|
142
|
+
sh "mv #{gem_file} pkg"
|
143
|
+
end
|
144
|
+
|
145
|
+
desc "Generate #{gemspec_file}"
|
146
|
+
task :gemspec => :validate do
|
147
|
+
# read spec file and split out manifest section
|
148
|
+
spec = File.read(gemspec_file)
|
149
|
+
head, manifest, tail = spec.split(" # = MANIFEST =\n")
|
150
|
+
|
151
|
+
# replace name version and date
|
152
|
+
replace_header(head, :name)
|
153
|
+
replace_header(head, :version)
|
154
|
+
replace_header(head, :date)
|
155
|
+
#comment this out if your rubyforge_project has a different name
|
156
|
+
replace_header(head, :rubyforge_project)
|
157
|
+
|
158
|
+
# determine file list from git ls-files
|
159
|
+
files = `git ls-files`.
|
160
|
+
split("\n").
|
161
|
+
sort.
|
162
|
+
reject { |file| file =~ /^\./ }.
|
163
|
+
reject { |file| file =~ /^(rdoc|pkg)/ }.
|
164
|
+
map { |file| " #{file}" }.
|
165
|
+
join("\n")
|
166
|
+
|
167
|
+
# piece file back together and write
|
168
|
+
manifest = " s.files = %w[\n#{files}\n ]\n"
|
169
|
+
spec = [head, manifest, tail].join(" # = MANIFEST =\n")
|
170
|
+
File.open(gemspec_file, 'w') { |io| io.write(spec) }
|
171
|
+
puts "Updated #{gemspec_file}"
|
172
|
+
end
|
173
|
+
|
174
|
+
desc "Validate #{gemspec_file}"
|
175
|
+
task :validate do
|
176
|
+
libfiles = Dir['lib/*'] - ["lib/#{name}.rb", "lib/#{name}"]
|
177
|
+
unless libfiles.empty?
|
178
|
+
puts "Directory `lib` should only contain a `#{name}.rb` file and `#{name}` dir."
|
179
|
+
exit!
|
180
|
+
end
|
181
|
+
unless Dir['VERSION*'].empty?
|
182
|
+
puts "A `VERSION` file at root level violates Gem best practices."
|
183
|
+
exit!
|
184
|
+
end
|
185
|
+
end
|
data/bin/play
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
|
4
|
+
|
5
|
+
require 'play'
|
6
|
+
require 'optparse'
|
7
|
+
|
8
|
+
parser = OptionParser.new do |opts|
|
9
|
+
opts.banner = "Usage: play [options] COMMAND"
|
10
|
+
|
11
|
+
opts.separator ""
|
12
|
+
opts.separator "Options:"
|
13
|
+
|
14
|
+
opts.on("--migrate", "Setup the database") do
|
15
|
+
ActiveRecord::Base.establish_connection(Play.config['db'])
|
16
|
+
ActiveRecord::Migrator.migrate("#{File.dirname(__FILE__)}/db/migrate")
|
17
|
+
end
|
18
|
+
|
19
|
+
opts.on("-d", "--detach", "Start the music server") do
|
20
|
+
ENV['RACK_ENV'] = 'production'
|
21
|
+
pid = fork { Play::Client.loop }
|
22
|
+
Process.detach(pid)
|
23
|
+
end
|
24
|
+
|
25
|
+
opts.on("-s", "--stop", "Stop the music server") do
|
26
|
+
Play::Client.stop
|
27
|
+
end
|
28
|
+
|
29
|
+
opts.on("-w", "--web", "Run the web instance") do
|
30
|
+
system("rackup -p 5050")
|
31
|
+
end
|
32
|
+
|
33
|
+
opts.on("-p", "--path", "Pause the music server") do |path|
|
34
|
+
Play::Client.pause
|
35
|
+
end
|
36
|
+
|
37
|
+
opts.on("-i", "--import", "Import new songs") do |import|
|
38
|
+
Play::Library.import_songs
|
39
|
+
exit
|
40
|
+
end
|
41
|
+
|
42
|
+
opts.on("-h", "--help", "Show this message") do
|
43
|
+
puts opts
|
44
|
+
exit
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
parser.parse!
|