play 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/Gemfile +2 -0
  2. data/Gemfile.lock +87 -0
  3. data/README.md +139 -0
  4. data/Rakefile +185 -0
  5. data/bin/play +49 -0
  6. data/config.ru +10 -0
  7. data/db/migrate/01_create_schema.rb +55 -0
  8. data/lib/play.rb +73 -0
  9. data/lib/play/album.rb +6 -0
  10. data/lib/play/app.rb +118 -0
  11. data/lib/play/app/api.rb +110 -0
  12. data/lib/play/artist.rb +21 -0
  13. data/lib/play/client.rb +67 -0
  14. data/lib/play/core_ext/hash.rb +6 -0
  15. data/lib/play/history.rb +7 -0
  16. data/lib/play/library.rb +58 -0
  17. data/lib/play/office.rb +34 -0
  18. data/lib/play/song.rb +97 -0
  19. data/lib/play/templates/album_songs.mustache +5 -0
  20. data/lib/play/templates/artist_songs.mustache +5 -0
  21. data/lib/play/templates/index.mustache +18 -0
  22. data/lib/play/templates/layout.mustache +23 -0
  23. data/lib/play/templates/now_playing.mustache +5 -0
  24. data/lib/play/templates/play_history.mustache +3 -0
  25. data/lib/play/templates/profile.mustache +24 -0
  26. data/lib/play/templates/search.mustache +3 -0
  27. data/lib/play/templates/show_song.mustache +9 -0
  28. data/lib/play/templates/song.mustache +21 -0
  29. data/lib/play/user.rb +59 -0
  30. data/lib/play/views/album_songs.rb +9 -0
  31. data/lib/play/views/artist_songs.rb +9 -0
  32. data/lib/play/views/index.rb +9 -0
  33. data/lib/play/views/layout.rb +6 -0
  34. data/lib/play/views/now_playing.rb +17 -0
  35. data/lib/play/views/play_history.rb +9 -0
  36. data/lib/play/views/profile.rb +9 -0
  37. data/lib/play/views/search.rb +9 -0
  38. data/lib/play/views/show_song.rb +19 -0
  39. data/lib/play/vote.rb +7 -0
  40. data/play.gemspec +129 -0
  41. data/play.yml.example +22 -0
  42. data/public/css/base.css +129 -0
  43. data/test/helper.rb +33 -0
  44. data/test/spec/mini.rb +24 -0
  45. data/test/test_api.rb +118 -0
  46. data/test/test_app.rb +57 -0
  47. data/test/test_artist.rb +15 -0
  48. data/test/test_client.rb +11 -0
  49. data/test/test_library.rb +19 -0
  50. data/test/test_office.rb +26 -0
  51. data/test/test_play.rb +17 -0
  52. data/test/test_song.rb +78 -0
  53. data/test/test_user.rb +21 -0
  54. metadata +299 -0
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'http://rubygems.org'
2
+ gemspec
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!