spinna 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ade338315b39f54cf3ba9a615c6dfa12cc361cbe
4
+ data.tar.gz: a0b6a23bd1b0716c67bed3445561d7686c5c86f0
5
+ SHA512:
6
+ metadata.gz: 039b7c87100b437bc5fd2d02aa769a9315637a5b6d4beb15aa1e6e2f339c66791cf9ec5f4f0b3d1f6655b4b87fce3ea58fa8e725a023b16a1b9915b3e9de025d
7
+ data.tar.gz: 843f620f9984395abb340b6e63074bb380abf5f478a4d15ea7c8037f54688efcd03496d6ab558327227e50dd7c32410bc1f33be9fcc766742e1f4cf3b028fad3
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.9.2"
4
+ - "1.9.3"
5
+ - "2.1.1"
6
+ - jruby-19mode # JRuby in 1.9 mode
7
+
8
+ script: bundle exec rake test
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in spinna.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Patrick Paul-Hus
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,67 @@
1
+ # Spinna
2
+
3
+ I have a *lot* of music. This little application helps me make sure that I don’t
4
+ always listen to the same stuff because I seem to be really bad at randomizing
5
+ my album selection.
6
+
7
+ Spinna will simply select some random albums from a given location and copy them
8
+ to my current working directory. It will also keep track of the selections made
9
+ in order to improve the rotation by not allowing a recently selected album to be
10
+ selected again.
11
+
12
+ ## Requirements
13
+
14
+ Spinna requires Ruby 1.9+.
15
+
16
+ ## Installation
17
+
18
+ `gem install spinna`
19
+
20
+ ## Configuration
21
+
22
+ First, spinna needs to be configured. Create a folder named `.spinna` in
23
+ your home directory and create a file named `config.yml`. If you run spinna
24
+ before creating the folder in your home directory, it will be created but
25
+ you will have to create the config file yourself.
26
+
27
+ The configuration file should look like this:
28
+
29
+ ```yaml
30
+ source_dir: '/foo'
31
+ history_size: 50
32
+ number_of_picks: 10
33
+ ```
34
+
35
+ The `source_dir` represents the location of your music library on your filesystem.
36
+ It is assumed that this directory contains a flat hierarchy of folders, your albums.
37
+ It is required.
38
+
39
+ The `history_size` is how many picks do we keep in the history log before we start
40
+ removing the older picks. If you do not supply it, the default value is 50.
41
+
42
+ The `number_of_picks` represents the default number of album picks you want Spinna
43
+ to make when you run it. If you do not supply it, the default value is 10.
44
+
45
+ ## Usage
46
+
47
+ To run, simply do:
48
+
49
+ `spinna get`
50
+
51
+ and it will download picks in the current working directory.
52
+
53
+ You can also request a given number of picks if you want:
54
+
55
+ `spinna get -n 10`
56
+
57
+ and it will pick 10 albums from your music library and download them
58
+ in the current working directory.
59
+
60
+
61
+ ## Contributing
62
+
63
+ 1. Fork it ( https://github.com/hydrozen/spinna/fork )
64
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
65
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
66
+ 4. Push to the branch (`git push origin my-new-feature`)
67
+ 5. Create a new Pull Request
@@ -0,0 +1,16 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ task :default => [:test]
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.libs << 'spec'
8
+ t.pattern = "spec/**/*_spec.rb"
9
+ end
10
+
11
+ task :console do
12
+ require 'pry'
13
+ require 'spinna'
14
+ ARGV.clear
15
+ Pry.start
16
+ end
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.expand_path("../../lib", __FILE__)
4
+
5
+ require 'spinna/cli'
6
+
7
+ Spinna::CLI.start(ARGV)
@@ -0,0 +1,12 @@
1
+ require "spinna/version"
2
+
3
+ module Spinna
4
+ # Base class for all errors.
5
+ class Error < StandardError; end
6
+
7
+ # Raised when the configuration file was not found.
8
+ class ConfigFileNotFoundError < Error; end
9
+
10
+ # Raised when the configuration file is not valid.
11
+ class InvalidConfigFileError < Error; end
12
+ end
@@ -0,0 +1,23 @@
1
+ require 'thor'
2
+ require 'yaml'
3
+ require 'spinna'
4
+ require 'spinna/client'
5
+
6
+ module Spinna
7
+ class CLI < Thor
8
+ desc 'get', 'Downloads some fresh music'
9
+ method_option :number_of_picks, :aliases => '-n', :type => :numeric
10
+ method_option :pattern, :aliases => '-p', :type => :string
11
+
12
+ def get
13
+ begin
14
+ client = Client.new
15
+ client.get(options)
16
+ exit 0
17
+ rescue StandardError => e
18
+ puts e.message
19
+ exit 1
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,44 @@
1
+ require 'fileutils'
2
+ require 'spinna/history'
3
+ require 'spinna/config'
4
+ require 'spinna/picker'
5
+ require 'spinna/music_library'
6
+
7
+ module Spinna
8
+ # The client takes care of initializing the application by reading
9
+ # the configuration and the history. It exposes the various commands
10
+ # that spinna can do.
11
+ class Client
12
+ attr_accessor :library
13
+ attr_accessor :config
14
+ attr_accessor :history
15
+
16
+ # Boots the application.
17
+ def initialize
18
+ init_configuration
19
+ init_history
20
+ init_library
21
+ end
22
+
23
+ # Get a certain number of random albums from the music collection.
24
+ def get(opts = {})
25
+ number_of_picks = opts[:number_of_picks] || config.number_of_picks
26
+ pattern = opts[:pattern] || nil
27
+ Picker.new(config, history, library).pick(number_of_picks, pattern)
28
+ end
29
+
30
+ private
31
+
32
+ def init_configuration
33
+ @config = Config.new
34
+ end
35
+
36
+ def init_history
37
+ @history = Spinna::History.new(config)
38
+ end
39
+
40
+ def init_library
41
+ @library = Spinna::MusicLibrary.new(config, history)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,86 @@
1
+ require 'yaml'
2
+ require 'spinna'
3
+
4
+ module Spinna
5
+ # Takes care of reading the configuration file and exposing
6
+ # the settings.
7
+ class Config
8
+ # Default number of albums to keep in the history.
9
+ DEFAULT_HISTORY_SIZE = 50
10
+
11
+ # Default number of albums to pick when spinna is run.
12
+ DEFAULT_NUMBER_OF_PICKS = 10
13
+
14
+ # The directory where spinna stores its data.
15
+ attr_accessor :data_dir
16
+
17
+ # The directory where all the albums are stored.
18
+ attr_accessor :source_dir
19
+
20
+ # The number previously picked albums to keep in the
21
+ # history. Defaults to DEFAULT_NUMBER_OF_PICKS.
22
+ attr_accessor :history_size
23
+
24
+ # The number of picks to fetch if not explicitly given.
25
+ attr_accessor :number_of_picks
26
+
27
+ # The name of the config file to load. (ex: config.yml)
28
+ attr_accessor :config_file
29
+
30
+ def initialize(opts = {})
31
+ @data_dir = opts[:data_dir] || ENV['SPINNA_DATA_DIR'] || File.join(Dir.home, '.spinna')
32
+ @config_file = opts[:config_file] || 'config.yml'
33
+ ensure_data_dir_exists
34
+ ensure_configuration_exists
35
+ parse_configuration
36
+ end
37
+
38
+ private
39
+
40
+ def parse_configuration
41
+ config = read_configuration_file
42
+ @source_dir = config['source_dir']
43
+ @history_size = config['history_size'] || DEFAULT_HISTORY_SIZE
44
+ @number_of_picks = config['number_of_picks'] || DEFAULT_NUMBER_OF_PICKS
45
+ validate_config
46
+ end
47
+
48
+ def validate_config
49
+ unless source_dir
50
+ raise Spinna::InvalidConfigFileError, "You must set a source_dir in your configuration file."
51
+ end
52
+ end
53
+
54
+ def ensure_data_dir_exists
55
+ if !File.exists?(data_dir)
56
+ Dir.mkdir(data_dir, 0700)
57
+ raise Spinna::ConfigFileNotFoundError, "Could not find the configuration file. Please create it at #{config_path}."
58
+ end
59
+ end
60
+
61
+ def ensure_configuration_exists
62
+ unless config_file_exists?
63
+ raise Spinna::ConfigFileNotFoundError, "Could not find the configuration file. Please create it at #{config_path}."
64
+ end
65
+ end
66
+
67
+ def read_configuration_file
68
+ begin
69
+ config = YAML.load_file(File.join(data_dir, config_file))
70
+ raise StandardError unless config.is_a?(Hash)
71
+ rescue StandardError
72
+ raise Spinna::InvalidConfigFileError, "Could not parse the configuration file. Please make sure it is valid YAML."
73
+ end
74
+ config
75
+ end
76
+
77
+ def config_path
78
+ File.join(data_dir, config_file)
79
+ end
80
+
81
+ def config_file_exists?
82
+ File.exists?(config_path)
83
+ end
84
+ end
85
+ end
86
+
@@ -0,0 +1,76 @@
1
+ require 'spinna/config'
2
+
3
+ module Spinna
4
+ # Takes care of tracking the picks that have been recently made
5
+ # so that they are not picked again too soon. The log is persisted
6
+ # in ~/.spinna/history.log by default.
7
+ class History
8
+ # The configuration of the application.
9
+ attr_reader :config
10
+
11
+ # An array that represents the log entries.
12
+ # The oldest entries are first.
13
+ attr_reader :log
14
+
15
+ def initialize(config)
16
+ @config = config
17
+ read_history_log
18
+ end
19
+
20
+ # Add album at the end of the history log unless it’s
21
+ # already in there.
22
+ def append(album)
23
+ @log << album unless @log.include?(album)
24
+ trim if full?
25
+ end
26
+
27
+ # Is the given album in the history already?
28
+ def include?(album)
29
+ @log.include?(album)
30
+ end
31
+
32
+ # Saves the history to file after removing old picks
33
+ # from the log if needed.
34
+ def save
35
+ write_history_to_disk
36
+ end
37
+
38
+ # Returns the path to the history log file on the filesystem.
39
+ def history_path
40
+ File.join(config.data_dir, 'history.log')
41
+ end
42
+
43
+ private
44
+
45
+ # Are there more albums in the log than there should be?
46
+ def full?
47
+ @log.size > config.history_size
48
+ end
49
+
50
+ # Deletes the old albums that are now outdated depending
51
+ # on the history_size.
52
+ def trim
53
+ @log = @log.last(config.history_size)
54
+ end
55
+
56
+ def write_history_to_disk
57
+ File.open(history_path, 'w+') do |f|
58
+ @log.each { |album| f.puts album }
59
+ end
60
+ end
61
+
62
+ # Reads the history.log file and stores all the lines
63
+ # in an array.
64
+ def read_history_log
65
+ if !history_file_exists?
66
+ @log = [] and return
67
+ end
68
+
69
+ @log = File.read(history_path).split("\n")
70
+ end
71
+
72
+ def history_file_exists?
73
+ File.exists?(history_path)
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,36 @@
1
+ module Spinna
2
+ # This class encapsulates the access to the music library.
3
+ class MusicLibrary
4
+ def initialize(config, history)
5
+ @config = config
6
+ @history = history
7
+ end
8
+
9
+ # Returns the albums that are pickable. An album is pickable
10
+ # if it isn’t present in the history log. If a pattern is given,
11
+ # then only the albums that match the pattern will be returned.
12
+ def pickable_albums(pattern = nil)
13
+ return available_albums unless pattern
14
+ available_albums.select! { |a| a =~ Regexp.new(pattern)}
15
+ end
16
+
17
+ private
18
+
19
+ attr_reader :config
20
+ attr_reader :history
21
+
22
+ def albums
23
+ Dir.entries(config.source_dir).reject! { |i| i =~ /\A\./ }
24
+ end
25
+
26
+ # Returns the list of previously selected albums.
27
+ def previously_selected_albums
28
+ history.log
29
+ end
30
+
31
+ # Returns only the albums that are available for selection.
32
+ def available_albums
33
+ albums - previously_selected_albums
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,52 @@
1
+ module Spinna
2
+ # The picker is responsible for reading the media library, selecting
3
+ # albums from it and updating the history log. Basically, it does the
4
+ # grunt work.
5
+ class Picker
6
+ def initialize(config, history, library)
7
+ @config = config
8
+ @history = history
9
+ @library = library
10
+ end
11
+
12
+ # Picks the requested number of picks from the available albums.
13
+ def pick(number_of_picks, pattern = nil)
14
+ picks = pick_albums(number_of_picks, pattern)
15
+ copy_picks(picks)
16
+ update_history(picks)
17
+ picks
18
+ end
19
+
20
+ private
21
+
22
+ attr_accessor :config
23
+ attr_accessor :history
24
+ attr_accessor :library
25
+
26
+ def pick_albums(number_of_picks, pattern = nil)
27
+ albums = library.pickable_albums(pattern)
28
+ return albums if number_of_picks >= albums.size
29
+ choose_random_picks(number_of_picks, albums)
30
+ end
31
+
32
+ # Does the actual picking of random albums.
33
+ def choose_random_picks(number_of_picks, albums)
34
+ return albums if albums.size < number_of_picks
35
+ albums.sample(number_of_picks)
36
+ end
37
+
38
+ # Copies the picks to the current working directory.
39
+ def copy_picks(picks)
40
+ picks.each do |pick|
41
+ puts "Copying #{pick}..."
42
+ FileUtils.cp_r("#{config.source_dir}/#{pick}", Dir.pwd)
43
+ end
44
+ end
45
+
46
+ # Updates the history.
47
+ def update_history(picks)
48
+ picks.each { |pick| history.append(pick) }
49
+ history.save
50
+ end
51
+ end
52
+ end