sync_songs 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +8 -0
- data/CONTRIBUTING.org +14 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +51 -0
- data/LICENSE.org +2 -0
- data/README.org +117 -0
- data/Rakefile +19 -0
- data/bin/sync_songs +69 -0
- data/development.org +53 -0
- data/lib/sync_songs/cli.rb +280 -0
- data/lib/sync_songs/controller.rb +435 -0
- data/lib/sync_songs/services/csv_cli.rb +26 -0
- data/lib/sync_songs/services/csv_controller.rb +64 -0
- data/lib/sync_songs/services/csv_set.rb +65 -0
- data/lib/sync_songs/services/grooveshark_cli.rb +26 -0
- data/lib/sync_songs/services/grooveshark_controller.rb +77 -0
- data/lib/sync_songs/services/grooveshark_set.rb +107 -0
- data/lib/sync_songs/services/lastfm_cli.rb +46 -0
- data/lib/sync_songs/services/lastfm_controller.rb +78 -0
- data/lib/sync_songs/services/lastfm_set.rb +177 -0
- data/lib/sync_songs/services/service_controller.rb +55 -0
- data/lib/sync_songs/song.rb +99 -0
- data/lib/sync_songs/song_set.rb +30 -0
- data/lib/sync_songs/version.rb +6 -0
- data/lib/sync_songs.rb +27 -0
- data/plan.org +17 -0
- data/sync_songs.gemspec +25 -0
- data/test/unit/sample_data/sample_data.rb +23 -0
- data/test/unit/services/test_csv_set.rb +54 -0
- data/test/unit/services/test_service_controller.rb +30 -0
- data/test/unit/suite_data.rb +12 -0
- data/test/unit/test_song.rb +129 -0
- data/test/unit/test_song_set.rb +134 -0
- metadata +165 -0
data/.gitignore
ADDED
data/CONTRIBUTING.org
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# -*- mode:org; indent-tabs-mode:nil; tab-width:2 -*-
|
2
|
+
|
3
|
+
* Contributing
|
4
|
+
|
5
|
+
** Issues
|
6
|
+
|
7
|
+
If you have a question, found a bug or want to make a suggestion please go ahead and [[https://github.com/Sleft/sync_songs/issues/new][post it as an issue]] but make sure it is not already reported. Please be as clear as possible and provide details. If the issue has to do with the program failing please include output from the program with the =--debug= and the =-v= options activated.
|
8
|
+
|
9
|
+
** Development
|
10
|
+
|
11
|
+
If you want to contribute please:
|
12
|
+
|
13
|
+
- Follow the guidelines in development.org.
|
14
|
+
- Fork the project and commit changes to an aptly named topic branch which you then make a pull request for.
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
sync_songs (0.0.0)
|
5
|
+
grooveshark (>= 0.2.7)
|
6
|
+
highline (>= 1.6.16)
|
7
|
+
lastfm (>= 1.17.0)
|
8
|
+
launchy (>= 2.2.0)
|
9
|
+
thor (>= 0.18.1)
|
10
|
+
|
11
|
+
GEM
|
12
|
+
remote: https://rubygems.org/
|
13
|
+
specs:
|
14
|
+
activesupport (3.2.13)
|
15
|
+
i18n (= 0.6.1)
|
16
|
+
multi_json (~> 1.0)
|
17
|
+
addressable (2.3.3)
|
18
|
+
grooveshark (0.2.7)
|
19
|
+
json (>= 1.4.6)
|
20
|
+
rest-client (>= 1.5.1)
|
21
|
+
uuid (~> 2.0)
|
22
|
+
highline (1.6.16)
|
23
|
+
httparty (0.10.2)
|
24
|
+
multi_json (~> 1.0)
|
25
|
+
multi_xml (>= 0.5.2)
|
26
|
+
i18n (0.6.1)
|
27
|
+
json (1.7.7)
|
28
|
+
lastfm (1.17.0)
|
29
|
+
activesupport (>= 3.0.3)
|
30
|
+
httparty
|
31
|
+
xml-simple
|
32
|
+
launchy (2.2.0)
|
33
|
+
addressable (~> 2.3)
|
34
|
+
macaddr (1.6.1)
|
35
|
+
systemu (~> 2.5.0)
|
36
|
+
mime-types (1.21)
|
37
|
+
multi_json (1.7.2)
|
38
|
+
multi_xml (0.5.3)
|
39
|
+
rest-client (1.6.7)
|
40
|
+
mime-types (>= 1.16)
|
41
|
+
systemu (2.5.2)
|
42
|
+
thor (0.18.1)
|
43
|
+
uuid (2.3.7)
|
44
|
+
macaddr (~> 1.0)
|
45
|
+
xml-simple (1.1.2)
|
46
|
+
|
47
|
+
PLATFORMS
|
48
|
+
ruby
|
49
|
+
|
50
|
+
DEPENDENCIES
|
51
|
+
sync_songs!
|
data/LICENSE.org
ADDED
data/README.org
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
# -*- mode:org; indent-tabs-mode:nil; tab-width:2 -*-
|
2
|
+
|
3
|
+
* sync_songs
|
4
|
+
|
5
|
+
With sync_songs you can sync sets of songs between services. If you have one set of song at one service and another song set at another service you can use sync_songs to merge the song sets. sync_songs can also be used to backup song sets by the ability to spread them across several services. Additionaly sync_songs can be used to diff song sets.
|
6
|
+
|
7
|
+
Currently sync_songs supports the following services:
|
8
|
+
- csv (on the form =name, artist, album, duration, id= where only the first two fields are required)
|
9
|
+
- Grooveshark
|
10
|
+
- Last.fm
|
11
|
+
|
12
|
+
sync_songs can be used as standalone but also as a library.
|
13
|
+
|
14
|
+
** Installation
|
15
|
+
|
16
|
+
To use sync_songs one has to have [[http://www.ruby-lang.org][Ruby]] installed. The easiest way to install Ruby is to use a package management system. If you are on a Debian-based distribution you can issue the following terminal command to install Ruby:
|
17
|
+
#+BEGIN_EXAMPLE
|
18
|
+
sudo apt-get install ruby1.9.1
|
19
|
+
#+END_EXAMPLE
|
20
|
+
|
21
|
+
The following describes three ways of obtaining and installing. The first way is recommended for users and the second way is recommended for developers.
|
22
|
+
|
23
|
+
*** Gem
|
24
|
+
|
25
|
+
This is the best method to install for most purposes. It requires RubyGems which on Debian-based distributions can be installed via the following command:
|
26
|
+
#+BEGIN_EXAMPLE
|
27
|
+
sudo apt-get install rubygems1.9.1
|
28
|
+
#+END_EXAMPLE
|
29
|
+
|
30
|
+
Then you can install sync_songs and its dependencies via the following command:
|
31
|
+
#+BEGIN_EXAMPLE
|
32
|
+
sudo gem install sync_songs
|
33
|
+
#+END_EXAMPLE
|
34
|
+
|
35
|
+
*** Git
|
36
|
+
|
37
|
+
This method is good if you want to help develop sync_songs. It requires Git which on Debian-based distributions can be installed via the following command:
|
38
|
+
#+BEGIN_EXAMPLE
|
39
|
+
sudo apt-get install git
|
40
|
+
#+END_EXAMPLE
|
41
|
+
|
42
|
+
To get the dependencies for sync_songs one can use bundler which can be installed via RubyGems (see above for installation instructions) in the following way:
|
43
|
+
#+BEGIN_EXAMPLE
|
44
|
+
sudo gem install bundler
|
45
|
+
#+END_EXAMPLE
|
46
|
+
|
47
|
+
To install sync_songs =cd= to an empty directory and do
|
48
|
+
#+BEGIN_EXAMPLE
|
49
|
+
git clone https://github.com/Sleft/sync_songs.git .
|
50
|
+
#+END_EXAMPLE
|
51
|
+
to clone the git repository into that directory. You can use the same command when you want to update it. To install the dependencies issue the following the same directory:
|
52
|
+
#+BEGIN_EXAMPLE
|
53
|
+
bundle
|
54
|
+
#+END_EXAMPLE
|
55
|
+
|
56
|
+
*** Archive
|
57
|
+
|
58
|
+
This method is not recommended but good if you for some reason cannot use RubyGems or Git. [[https://github.com/Sleft/sync_songs/archive/master.zip][Download]] an archive and extract to the directory you want to install in. Install the dependencies listed in the [[https://github.com/Sleft/sync_songs/blob/master/sync_songs.gemspec][gemspec]].
|
59
|
+
|
60
|
+
** Usage
|
61
|
+
|
62
|
+
If you want to use sync_songs simply to sync songs between different services you probably want to use it as standalone.
|
63
|
+
|
64
|
+
*** Standalone
|
65
|
+
|
66
|
+
Issue the following command to learn about how to use sync_songs:
|
67
|
+
#+BEGIN_EXAMPLE
|
68
|
+
sync_songs help
|
69
|
+
#+END_EXAMPLE
|
70
|
+
|
71
|
+
The most common way of using sync_songs is probably to sync between two services by issuing a command of the following form:
|
72
|
+
#+BEGIN_EXAMPLE
|
73
|
+
sync_songs sync --color -vs user1:service1:favorites user2:service2:favorites
|
74
|
+
#+END_EXAMPLE
|
75
|
+
The =--color= option is recommended as it contributes to legibility. The =-v= option is recommended as it explains what is being done. Note that fetching song data from services may take some time due to limitations of bandwidth and due to limitations of particular services.
|
76
|
+
|
77
|
+
The above example does not work as it uses placeholder services. For a list of supported services one can issue
|
78
|
+
#+BEGIN_EXAMPLE
|
79
|
+
sync_songs supp
|
80
|
+
#+END_EXAMPLE
|
81
|
+
If one has a user named mary at Grooveshark and a user named smith at Last.fm one can use the following to sync between them:
|
82
|
+
#+BEGIN_EXAMPLE
|
83
|
+
sync_songs sync --color -vs mary:grooveshark:favorites smith:lastfm:loved
|
84
|
+
#+END_EXAMPLE
|
85
|
+
|
86
|
+
To sync between more than two services just add additional services as arguments to the =-s= option. For example, to also sync to a csv file one can add it as an argument:
|
87
|
+
#+BEGIN_EXAMPLE
|
88
|
+
sync_songs sync --color -vs user1:service1:favorites user2:service2:favorites file_path:csv:library
|
89
|
+
#+END_EXAMPLE
|
90
|
+
Note that syncing to a csv is a way of backing up songs from services.
|
91
|
+
|
92
|
+
To diff songs one can proceed as above but by replacing the =sync= command with =diff=, e.g.
|
93
|
+
#+BEGIN_EXAMPLE
|
94
|
+
sync_songs diff --color -vs user1:service1:favorites user2:service2:favorites
|
95
|
+
#+END_EXAMPLE
|
96
|
+
|
97
|
+
*** Library
|
98
|
+
|
99
|
+
If you want to integrate sync_songs in a project add the following line to the project's gemspec:
|
100
|
+
#+BEGIN_EXAMPLE
|
101
|
+
gem.add_runtime_dependency 'sync_songs'
|
102
|
+
#+END_EXAMPLE
|
103
|
+
Alternatively add the following line to your Gemfile:
|
104
|
+
#+BEGIN_EXAMPLE
|
105
|
+
gem 'sync_songs'
|
106
|
+
#+END_EXAMPLE
|
107
|
+
Now you should be able to =require sync_songs=.
|
108
|
+
|
109
|
+
Note that you can use bundler to get dependencies for sync_songs, see installation via Git above.
|
110
|
+
|
111
|
+
* License
|
112
|
+
|
113
|
+
See LICENSE.org.
|
114
|
+
|
115
|
+
* Contributing and development
|
116
|
+
|
117
|
+
See CONTRIBUTING.org.
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- coding: utf-8; mode: ruby -*-
|
2
|
+
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
require 'rake/testtask'
|
5
|
+
|
6
|
+
Rake::TestTask.new do |t|
|
7
|
+
t.libs << 'test'
|
8
|
+
t.test_files = FileList['test/unit/test*.rb',
|
9
|
+
'test/unit/services/test*.rb']
|
10
|
+
end
|
11
|
+
|
12
|
+
desc 'Run tests'
|
13
|
+
task default: :test
|
14
|
+
|
15
|
+
# task test: :rubocop
|
16
|
+
|
17
|
+
task :style do
|
18
|
+
sh 'rubocop'
|
19
|
+
end
|
data/bin/sync_songs
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- coding: utf-8; mode: ruby -*-
|
3
|
+
|
4
|
+
require 'thor'
|
5
|
+
require_relative '../lib/sync_songs.rb'
|
6
|
+
|
7
|
+
# Public: Classes for syncing sets of songs.
|
8
|
+
module SyncSongs
|
9
|
+
class Cli < Thor
|
10
|
+
class_option :verbose, type: :boolean, aliases: '-v',
|
11
|
+
desc: 'Explain what is being done'
|
12
|
+
class_option :debug, type: :boolean, desc: 'Debug mode'
|
13
|
+
class_option :color, type: :boolean, desc: 'Color mode'
|
14
|
+
services_option = [:services, {type: :array, required: true,
|
15
|
+
banner: Controller::INPUT_FORM,
|
16
|
+
desc: 'At least two users or file paths '\
|
17
|
+
'each paired with a service and a type, '\
|
18
|
+
'e.g. -s user1:grooveshark:favorites '\
|
19
|
+
'songs.csv:csv:library',
|
20
|
+
aliases: '-s'}]
|
21
|
+
|
22
|
+
desc 'sync', 'Sync sets of songs'
|
23
|
+
long_desc 'Syncs sets of songs between the given services.'
|
24
|
+
method_option(*services_option)
|
25
|
+
def sync
|
26
|
+
setupController
|
27
|
+
@controller.sync
|
28
|
+
end
|
29
|
+
|
30
|
+
desc 'diff', 'Diff sets of songs'
|
31
|
+
long_desc 'Diffs sets of songs between the given services.'
|
32
|
+
method_option(*services_option)
|
33
|
+
def diff
|
34
|
+
setupController
|
35
|
+
@controller.diff
|
36
|
+
end
|
37
|
+
|
38
|
+
desc 'supp', 'List supported services'
|
39
|
+
long_desc <<-LONGDESC
|
40
|
+
Prints a list of the supported services.
|
41
|
+
LONGDESC
|
42
|
+
def supp
|
43
|
+
@controller = Controller.new(CLI.new(options[:verbose],
|
44
|
+
options[:debug],
|
45
|
+
options[:color]),
|
46
|
+
nil)
|
47
|
+
@controller.showSupportedServices
|
48
|
+
end
|
49
|
+
|
50
|
+
desc 'version', 'Shows the version number'
|
51
|
+
long_desc 'Shows the version number. Please include the version'\
|
52
|
+
'in bug reports. If there is an error please produce it with the'\
|
53
|
+
'debug option and include the output from it in the bug report.'
|
54
|
+
def version
|
55
|
+
puts "#{$PROGRAM_NAME} #{VERSION}"
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def setupController
|
61
|
+
@controller = Controller.new(CLI.new(options[:verbose],
|
62
|
+
options[:debug],
|
63
|
+
options[:color]),
|
64
|
+
options[:services])
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
Cli.start(ARGV)
|
69
|
+
end
|
data/development.org
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# -*- mode:org; indent-tabs-mode:nil; tab-width:2 -*-
|
2
|
+
|
3
|
+
* Development
|
4
|
+
|
5
|
+
sync_songs is designed to be used as standalone and also to provide a useful library for syncing sets of songs between services. One goal is for sync_songs to handle any number of services. Another goal is for it to be easy to add support for new services.
|
6
|
+
|
7
|
+
** General guidelines
|
8
|
+
|
9
|
+
- Document code with [[http://tomdoc.org/][TomDoc]].
|
10
|
+
- If possible, include tests for every feature.
|
11
|
+
- Follow [[https://github.com/bbatsov/ruby-style-guide][The Ruby Style Guide]] as much as possible. This means that [[https://github.com/bbatsov/rubocop][rubocop]] can be used to check style (get it via =sudo gem install rubocop=).
|
12
|
+
|
13
|
+
** Project structure
|
14
|
+
|
15
|
+
*** File structure
|
16
|
+
|
17
|
+
The directory structure is as follows:
|
18
|
+
- ./ :: Main project files, e.g. Rakefile, gem files and readme.
|
19
|
+
- bin :: Scripts meant to be executed by the user are placed here.
|
20
|
+
- lib :: Contains a file that loads the library.
|
21
|
+
- sync_songs :: The main code.
|
22
|
+
- services :: The code relating to particular services.
|
23
|
+
- test ::
|
24
|
+
- unit :: Tests of the =Test::Unit= framework. This directory should have a structure that corresponds to lib/sync_songs. Thus, this directory contains tests for the main code and also test suites.
|
25
|
+
- sample_data :: Sample data for tests of the parent directory.
|
26
|
+
- services :: Tests relating to particular services.
|
27
|
+
- sample_data :: Sample data for tests of the parent directory.
|
28
|
+
|
29
|
+
*** Code structure
|
30
|
+
|
31
|
+
The code is structured along MVC pattern. In lib/sync_songs there is a controller, controller.rb, which handles the main logic. The controller interacts with the user via a user interface, for example cli.rb -- a command line interface. Entity classes of the program are based on the classes =Song= and =SongSet=.
|
32
|
+
|
33
|
+
In .lib/sync_songs/services the classes relating to particular services are found. Each service has a controller which the main controller communicates with. If a particular service needs to interact with the user it should have its own user interface which is called by its controller. Each service has an entity class for getting and setting songs.
|
34
|
+
|
35
|
+
** How to add a service
|
36
|
+
|
37
|
+
For a service to be meaningful it needs to be possible to get songs from it or set songs to it. Obviously it is best if both getting and setting is possible as it makes possible for sync in both directions. This mean that it needs to be possible to get and/or set songs to the service in question via Ruby.
|
38
|
+
|
39
|
+
As described under the heading "Code structure" above each service should have a controller, a user interface (if it needs to interact with the user) and an entity class. When adding a service it is probably best to start by designing the entity class.
|
40
|
+
|
41
|
+
The entity class for a particular service, which I will call a service set, should be a subclass of =SongSet= and named ServicenameSet. This provides it with the ability to store objects of the class =Song= via the methods =<<= and =add=. Every service set needs to have at least two methods. It needs to have a constructor that calls the constructor of its super class and does any setup that is necessary for the particular service. It also needs a method for getting or setting songs. The method for getting songs should be named after which kind of songs it gets, e.g. if it gets favorite songs it should be called =favorites= (if there is no given name for the type of song please call the method =library=), and it should get those songs from the service and add them to the =SongSet=. The method for setting songs should be named =addTo= followed by the type of songs it adds, e.g. =addToFavorites= if it adds favorites, and it should take a =SongSet= as a parameter and add those to the given type of songs of the service. When there is a method for setting songs there also needs to be a method for searching songs named =search= which takes a =SongSet= and an optional boolean value defaulting to true for whether to do strict search as arguments. The search method should search for the songs given as argument and return any matches. A service set is not responsible for handling exceptions but any exceptions thrown by any of its methods should be documented in TomDoc style. For an example of a simple service set see lib/sync_songs/services/csv_set.rb.
|
42
|
+
|
43
|
+
The controller for a particular service should be a subclass of =ServiceController= and it should be named ServicenameController. The controller is responsible for all communication with the main controller and it should setup the service set via its constructor. It should also have wrappers for the getters, setters and search methods of the service set and if any of them throws an exception it should be handled. For an example of a controller for a service see lib/sync_songs/services/csv_controller.rb.
|
44
|
+
|
45
|
+
A user interface for a particular service is only necessary if it needs to interact with the user. The service user interface should be named ServicenameInterfacetype and if possible it should use methods of the main user interface of the same type. Note that the service user interface should be called by the service controller only. For an example of a service user interface see lib/sync_songs/services/csv_cli.rb.
|
46
|
+
|
47
|
+
** Adding a user interface
|
48
|
+
|
49
|
+
As mentioned sync_songs is designed to have a replaceable user interface. If one wants to make a new user interface one needs to construct a main user interface and a user interface for every service that needs one. If the main controller needs to be changed to support other user interface that is a flaw in the main controller and fixes for such flaws are encouraged.
|
50
|
+
|
51
|
+
** Plan
|
52
|
+
|
53
|
+
The plan is for sync_songs to work as expected, have as few bugs as possible and support more services. Specific plans are documented in plan.org. Also see [[https://github.com/Sleft/sync_songs/issues][issues]].
|
@@ -0,0 +1,280 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'highline/import'
|
4
|
+
|
5
|
+
# Public: Classes for syncing sets of songs.
|
6
|
+
module SyncSongs
|
7
|
+
# Public: Command-line interface.
|
8
|
+
class CLI
|
9
|
+
# Public: A character for answering yes to a question.
|
10
|
+
YES_ANSWER = 'y'
|
11
|
+
# Public: A character that the user can input to quit what is
|
12
|
+
# currently happening. Sometimes it means to quit to program
|
13
|
+
# altogether, sometimes it means to merely to quit the current
|
14
|
+
# dialog.
|
15
|
+
QUIT_ANSWER = 'q'
|
16
|
+
# Public: Message asking for yes, no or quit.
|
17
|
+
YN_OPTIONS_MSG = 'Enter y for yes, n for no or q to quit'
|
18
|
+
# Public: Validator for yes, no or quit questions.
|
19
|
+
YN_VALIDATOR = /\A[yn#{QUIT_ANSWER}]/i
|
20
|
+
|
21
|
+
attr_reader :color
|
22
|
+
|
23
|
+
# Public: Creates a command-line interface.
|
24
|
+
#
|
25
|
+
# verbose - True if interface is verbose (default:
|
26
|
+
# nil).
|
27
|
+
# debug - True if interface is in debug mode
|
28
|
+
# (default: nil), this means e.g. that
|
29
|
+
# stack traces for exceptions are printed.
|
30
|
+
# color - True if color formatted output should be
|
31
|
+
# used (default: nil).
|
32
|
+
# possible_directions - A hash of possible sync directions
|
33
|
+
# between two given services mapped to
|
34
|
+
# descriptions of those directions (default:
|
35
|
+
# nil).
|
36
|
+
def initialize(verbose = nil, debug = nil, color = nil,
|
37
|
+
possible_directions = nil)
|
38
|
+
@verbose = verbose
|
39
|
+
@debug = debug
|
40
|
+
@possible_directions = possible_directions
|
41
|
+
HighLine::use_color = color
|
42
|
+
|
43
|
+
directionsMessage if @possible_directions
|
44
|
+
|
45
|
+
HighLine.color_scheme = HighLine::ColorScheme.new do |cs|
|
46
|
+
cs[:em] = [:bold]
|
47
|
+
cs[:verbose] = [:blue]
|
48
|
+
cs[:verbose_direction] = [:cyan, :bold]
|
49
|
+
cs[:error] = [:red, :bold]
|
50
|
+
cs[:even_row] = [:green]
|
51
|
+
cs[:odd_row] = [:magenta]
|
52
|
+
end
|
53
|
+
|
54
|
+
@row = true # To track even and odd rows for colorizing.
|
55
|
+
end
|
56
|
+
|
57
|
+
# Public: Asks for directions to write in and return them.
|
58
|
+
#
|
59
|
+
# directions - A two dimensional array where each element is of
|
60
|
+
# the form ['service1', '?', 'service2'].
|
61
|
+
#
|
62
|
+
# Returns an two dimensional array where each element is of the
|
63
|
+
# form ['service1', '</=/>', 'service2'].
|
64
|
+
def askDirections(directions)
|
65
|
+
directions.each do |d|
|
66
|
+
d[1] = askDirection("#{d.join(' ')} ")
|
67
|
+
|
68
|
+
exitOption(d[1])
|
69
|
+
|
70
|
+
if @verbose
|
71
|
+
say("<%= color(%q(#{d.first.join(' ')}), :verbose) %> "\
|
72
|
+
"<%= color(%q(#{d[1]}), :verbose_direction) %> "\
|
73
|
+
"<%= color(%q(#{d.last.join(' ')}), :verbose) %>")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
directions
|
78
|
+
end
|
79
|
+
|
80
|
+
# Public: Asks if strict search should be used for the given
|
81
|
+
# service and returns the answer.
|
82
|
+
#
|
83
|
+
# s - A String describing a service
|
84
|
+
#
|
85
|
+
# Returns true if the user answer that strict search should be
|
86
|
+
# used for the given service.
|
87
|
+
def strict_search(s)
|
88
|
+
input = ask("<%= color(%q(Strict search), :em) %> for #{s}? ") do |q|
|
89
|
+
q.responses[:not_valid] = 'A strict search is recommended '\
|
90
|
+
'as a wide search may generate too many hits. '\
|
91
|
+
"#{YN_OPTIONS_MSG}"
|
92
|
+
q.default = YES_ANSWER
|
93
|
+
q.validate = YN_VALIDATOR
|
94
|
+
end
|
95
|
+
|
96
|
+
exitOption(input)
|
97
|
+
|
98
|
+
input.casecmp(YES_ANSWER) == 0
|
99
|
+
end
|
100
|
+
|
101
|
+
# Public: Asks if interactive mode should be used for the given
|
102
|
+
# service and returns the answer.
|
103
|
+
#
|
104
|
+
# s - A String describing a service
|
105
|
+
#
|
106
|
+
# Returns true if the user answer that interactive mode should be
|
107
|
+
# used for the given service.
|
108
|
+
def interactive(s)
|
109
|
+
input = ask("<%= color(%q(Interactive mode), :em) %> for #{s}? ") do |q|
|
110
|
+
q.responses[:not_valid] = 'In interactive mode you will for '\
|
111
|
+
'every found song be asked whether to add it. Interactive '\
|
112
|
+
'mode is recommended for everything but services you have '\
|
113
|
+
"direct access to, such as text files. #{YN_OPTIONS_MSG}"
|
114
|
+
q.default = YES_ANSWER
|
115
|
+
q.validate = YN_VALIDATOR
|
116
|
+
end
|
117
|
+
|
118
|
+
exitOption(input)
|
119
|
+
|
120
|
+
input.casecmp(YES_ANSWER) == 0
|
121
|
+
end
|
122
|
+
|
123
|
+
# Public: For every of the given songs, ask whether to add it and
|
124
|
+
# return an array of songs to add.
|
125
|
+
#
|
126
|
+
# service - A String naming a service.
|
127
|
+
# songs - An Array of songs to ask about.
|
128
|
+
#
|
129
|
+
# Return an Array of songs to add.
|
130
|
+
def askAddSongs(service, songs)
|
131
|
+
songs_to_add = []
|
132
|
+
|
133
|
+
songs.each do |s|
|
134
|
+
add = askAddSong(s, service)
|
135
|
+
|
136
|
+
# Stop asking if the user press quit
|
137
|
+
break if add.casecmp(QUIT_ANSWER) == 0
|
138
|
+
|
139
|
+
songs_to_add << s if add.casecmp(YES_ANSWER) == 0
|
140
|
+
end
|
141
|
+
|
142
|
+
songs_to_add
|
143
|
+
end
|
144
|
+
|
145
|
+
# Public: Shows the given message and exits with the given exit
|
146
|
+
# code.
|
147
|
+
#
|
148
|
+
# msg - A String naming a failure message.
|
149
|
+
# exit_code - Exit code to use, see
|
150
|
+
# http://tldp.org/LDP/abs/html/exitcodes.html for
|
151
|
+
# details (default: 1).
|
152
|
+
# exception - The Exception causing the failure (default: nil).
|
153
|
+
def fail(msg, exit_code = 1, exception = nil)
|
154
|
+
failMessage(msg)
|
155
|
+
|
156
|
+
if @debug && exception
|
157
|
+
p exception
|
158
|
+
puts exception.backtrace
|
159
|
+
end
|
160
|
+
|
161
|
+
exit(exit_code)
|
162
|
+
end
|
163
|
+
|
164
|
+
# Public: Shows the given message.
|
165
|
+
#
|
166
|
+
# msg - A String or an Enumerable naming a message.
|
167
|
+
def message(msg)
|
168
|
+
puts msg
|
169
|
+
end
|
170
|
+
|
171
|
+
# Public: Shows the given message.
|
172
|
+
#
|
173
|
+
# msg - A String or an Enumerable naming a message.
|
174
|
+
def emMessage(msg)
|
175
|
+
styleMessage(msg, :em)
|
176
|
+
end
|
177
|
+
|
178
|
+
# Public: Prints the given message if in verbose mode.
|
179
|
+
#
|
180
|
+
# msg - A String or an an Enumerable of Strings naming a verbose
|
181
|
+
# message.
|
182
|
+
def verboseMessage(msg)
|
183
|
+
styleMessage(msg, :verbose) if @verbose
|
184
|
+
end
|
185
|
+
|
186
|
+
# Public: Shows the given fail message.
|
187
|
+
#
|
188
|
+
# msg - A String or an Enumerable naming a message.
|
189
|
+
def failMessage(msg)
|
190
|
+
styleMessage(msg, :error)
|
191
|
+
end
|
192
|
+
|
193
|
+
# Public: Shows the supported services.
|
194
|
+
def supportedServices
|
195
|
+
msg = []
|
196
|
+
|
197
|
+
Controller.supportedServices.each do |service, type_action|
|
198
|
+
type_msg = []
|
199
|
+
type_action.each do |type, action|
|
200
|
+
type_msg << "#{type} <%= color(%q(#{action}), :even_row) %>"
|
201
|
+
end
|
202
|
+
msg << "<%= color(%q(#{service}), :em) %>: #{type_msg.join(', ')}"
|
203
|
+
end
|
204
|
+
|
205
|
+
say(msg.join("\n"))
|
206
|
+
end
|
207
|
+
|
208
|
+
# Public: Sets the possible directions.
|
209
|
+
#
|
210
|
+
# val - A hash of possible sync directions between two given
|
211
|
+
# services mapped to descriptions of those directions.
|
212
|
+
def possible_directions=(val)
|
213
|
+
@possible_directions = val
|
214
|
+
directionsMessage
|
215
|
+
end
|
216
|
+
|
217
|
+
private
|
218
|
+
|
219
|
+
# Internal: Prints the given message with the given style.
|
220
|
+
#
|
221
|
+
# msg - A String or an an Enumerable of Strings naming a
|
222
|
+
# message.
|
223
|
+
# color - A Symbol representing an ERB style.
|
224
|
+
def styleMessage(msg, style)
|
225
|
+
if @verbose
|
226
|
+
if msg.respond_to? :each
|
227
|
+
msg.each { |m| styleMessage(m, style) }
|
228
|
+
else
|
229
|
+
say("<%= color(%q(#{msg}), '#{style}') %>")
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
# Internal: Asks whether to add the given song to the given
|
235
|
+
# service.
|
236
|
+
#
|
237
|
+
# song - A String naming a song.
|
238
|
+
# service - A String naming a service.
|
239
|
+
def askAddSong(song, service)
|
240
|
+
question = "Add <%= color(%q(#{song} to #{service}), "
|
241
|
+
question << (@row ? ':even_row' : ':odd_row')
|
242
|
+
@row = !@row
|
243
|
+
|
244
|
+
ask("#{question}) %>? ") do |q|
|
245
|
+
q.responses[:not_valid] = YN_OPTIONS_MSG
|
246
|
+
q.default = YES_ANSWER
|
247
|
+
q.validate = YN_VALIDATOR
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
# Internal: Ask which direction to sync for the given services.
|
252
|
+
#
|
253
|
+
# question - A String naming a question asking for which direction
|
254
|
+
# to sync in between to services.
|
255
|
+
#
|
256
|
+
# Returns a String naming the direction to sync in.
|
257
|
+
def askDirection(question)
|
258
|
+
ask(question) do |q|
|
259
|
+
q.responses[:not_valid] = @DIRECTIONS_MSG
|
260
|
+
q.default = '='
|
261
|
+
q.validate = lambda { |a| @possible_directions.key?(a.to_sym) || a == QUIT_ANSWER }
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
# Internal: Exits if input is a character for quitting.
|
266
|
+
#
|
267
|
+
# input - Input String from user.
|
268
|
+
def exitOption(input)
|
269
|
+
exit if input.casecmp(QUIT_ANSWER) == 0
|
270
|
+
end
|
271
|
+
|
272
|
+
# Internal: Create a message describing what sync directions the
|
273
|
+
# user can choose for any two services.
|
274
|
+
def directionsMessage
|
275
|
+
@DIRECTIONS_MSG = @possible_directions.map { |k, v| "<%= color(%q(#{k}), :verbose_direction) %> for #{v}" }
|
276
|
+
@DIRECTIONS_MSG = "Enter #{@DIRECTIONS_MSG.join(", ")}"
|
277
|
+
@DIRECTIONS_MSG << " or <%= color(%q(#{QUIT_ANSWER}), :verbose_direction) %> to quit"
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|