cultome_player 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +24 -0
- data/.rspec +2 -0
- data/.travis.yml +7 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +325 -0
- data/Rakefile +8 -0
- data/bin/cultome_player +39 -0
- data/config/environment.yml +28 -0
- data/cultome_player.gemspec +35 -0
- data/db/001_create_schema.rb +58 -0
- data/lib/cultome_player.rb +107 -0
- data/lib/cultome_player/command.rb +11 -0
- data/lib/cultome_player/command/language.rb +61 -0
- data/lib/cultome_player/command/processor.rb +165 -0
- data/lib/cultome_player/command/reader.rb +86 -0
- data/lib/cultome_player/environment.rb +130 -0
- data/lib/cultome_player/events.rb +29 -0
- data/lib/cultome_player/media.rb +47 -0
- data/lib/cultome_player/objects.rb +15 -0
- data/lib/cultome_player/objects/album.rb +21 -0
- data/lib/cultome_player/objects/artist.rb +18 -0
- data/lib/cultome_player/objects/command.rb +37 -0
- data/lib/cultome_player/objects/drive.rb +26 -0
- data/lib/cultome_player/objects/genre.rb +16 -0
- data/lib/cultome_player/objects/parameter.rb +37 -0
- data/lib/cultome_player/objects/response.rb +42 -0
- data/lib/cultome_player/objects/song.rb +38 -0
- data/lib/cultome_player/player.rb +13 -0
- data/lib/cultome_player/player/adapter.rb +14 -0
- data/lib/cultome_player/player/adapter/mpg123.rb +143 -0
- data/lib/cultome_player/player/interactive.rb +56 -0
- data/lib/cultome_player/player/interface.rb +13 -0
- data/lib/cultome_player/player/interface/basic.rb +96 -0
- data/lib/cultome_player/player/interface/builtin_help.rb +368 -0
- data/lib/cultome_player/player/interface/extended.rb +199 -0
- data/lib/cultome_player/player/interface/helper.rb +300 -0
- data/lib/cultome_player/player/playlist.rb +280 -0
- data/lib/cultome_player/plugins.rb +23 -0
- data/lib/cultome_player/plugins/help.rb +58 -0
- data/lib/cultome_player/state_checker.rb +74 -0
- data/lib/cultome_player/utils.rb +95 -0
- data/lib/cultome_player/version.rb +3 -0
- data/spec/config.yml +0 -0
- data/spec/cultome_player/command/processor_spec.rb +168 -0
- data/spec/cultome_player/command/reader_spec.rb +45 -0
- data/spec/cultome_player/cultome_player_spec.rb +17 -0
- data/spec/cultome_player/environment_spec.rb +65 -0
- data/spec/cultome_player/events_spec.rb +22 -0
- data/spec/cultome_player/media_spec.rb +41 -0
- data/spec/cultome_player/player/adapter/mpg123_spec.rb +82 -0
- data/spec/cultome_player/player/interface/basic_spec.rb +168 -0
- data/spec/cultome_player/player/interface/extended/connect_spec.rb +117 -0
- data/spec/cultome_player/player/interface/extended/search_spec.rb +90 -0
- data/spec/cultome_player/player/interface/extended/show_spec.rb +36 -0
- data/spec/cultome_player/player/interface/extended/shuffle_spec.rb +26 -0
- data/spec/cultome_player/player/interface/extended_spec.rb +136 -0
- data/spec/cultome_player/player/interface/helper_spec.rb +63 -0
- data/spec/cultome_player/player/interface_spec.rb +17 -0
- data/spec/cultome_player/player/playlist_spec.rb +301 -0
- data/spec/cultome_player/plugins/help_spec.rb +21 -0
- data/spec/cultome_player/plugins_spec.rb +19 -0
- data/spec/cultome_player/utils_spec.rb +15 -0
- data/spec/spec_helper.rb +108 -0
- data/spec/test/uno/dos/dos.mp3 +0 -0
- data/spec/test/uno/dos/tres/tres.mp3 +0 -0
- data/spec/test/uno/uno.mp3 +0 -0
- data/tasks/console.rake +19 -0
- data/tasks/db.rake +19 -0
- data/tasks/run.rake +7 -0
- metadata +322 -0
@@ -0,0 +1,199 @@
|
|
1
|
+
|
2
|
+
module CultomePlayer::Player::Interface
|
3
|
+
module Extended
|
4
|
+
|
5
|
+
# For more information on this command refer to user manual or inline help in interactive mode.
|
6
|
+
def search(cmd)
|
7
|
+
songs = select_songs_with cmd
|
8
|
+
|
9
|
+
if songs.empty?
|
10
|
+
failure('It matches not even one')
|
11
|
+
else
|
12
|
+
playlists[:focus] <= songs
|
13
|
+
success(songs: songs, response_type: :songs)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# For more information on this command refer to user manual or inline help in interactive mode.
|
18
|
+
def show(cmd)
|
19
|
+
if cmd.params.empty?
|
20
|
+
if playing?
|
21
|
+
#mostramos la cancion actual
|
22
|
+
msg = get_progress_bar_with_labels(playback_position, playback_length, 20, format_secs(playback_position), format_secs(playback_length))
|
23
|
+
return success(message: "#{current_song.to_s}\n#{msg}", song: current_song)
|
24
|
+
else
|
25
|
+
return failure("Nothing to show yet. Try with 'play' first.")
|
26
|
+
end
|
27
|
+
|
28
|
+
# with parameters
|
29
|
+
else
|
30
|
+
list_to_show = cmd.params(:object).reduce([]) do |acc, p|
|
31
|
+
acc + case p.value
|
32
|
+
when :playlist then current_playlist.to_a
|
33
|
+
when :current then playlists[:current].to_a
|
34
|
+
when :history then playlists[:history].to_a
|
35
|
+
when :queue then playlists[:queue].to_a
|
36
|
+
when :focus then playlists[:focus].to_a
|
37
|
+
when :search then playlists[:search].to_a
|
38
|
+
|
39
|
+
when :song then return success(message: current_song.to_s, song: current_song)
|
40
|
+
when :artist then return success(message: current_artist.to_s, artist: current_song)
|
41
|
+
when :album then return success(message: current_album.to_s, album: current_song)
|
42
|
+
|
43
|
+
when :drives then Drive.all
|
44
|
+
when :artists then return Artist.all
|
45
|
+
when :albums then return Album.all
|
46
|
+
when :genres then return Genre.all
|
47
|
+
|
48
|
+
when :library then whole_library.to_a
|
49
|
+
|
50
|
+
when :recently_added then [] # TODO implement
|
51
|
+
when :recently_played then [] # TODO implement
|
52
|
+
when :more_played then [] # TODO implement
|
53
|
+
when :less_played then [] # TODO implement
|
54
|
+
when :populars then [] # TODO implement
|
55
|
+
|
56
|
+
else []
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
if list_to_show.empty?
|
61
|
+
return failure("I checked and there is nothing there.")
|
62
|
+
else
|
63
|
+
playlists[:focus] <= list_to_show
|
64
|
+
return success(list: list_to_show, response_type: :list)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# For more information on this command refer to user manual or inline help in interactive mode.
|
70
|
+
def enqueue(cmd)
|
71
|
+
songs = select_songs_with cmd
|
72
|
+
if songs.empty?
|
73
|
+
failure("No songs found with this criteria. Sorry, nothing was enqueued.")
|
74
|
+
else
|
75
|
+
playlists[:queue] << songs
|
76
|
+
msg = "These songs were enqueued:\n"
|
77
|
+
songs.each {|s,idx| msg << " #{s.to_s}\n"}
|
78
|
+
|
79
|
+
success(message: msg, enqueued: songs)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# For more information on this command refer to user manual or inline help in interactive mode.
|
84
|
+
def shuffle(cmd)
|
85
|
+
if cmd.params.empty?
|
86
|
+
if playlists[:current].shuffling?
|
87
|
+
return success(message: "Everyday I'm shuffling!", shuffling: true)
|
88
|
+
else
|
89
|
+
return success(message: "No shuffling", shuffling: false)
|
90
|
+
end
|
91
|
+
else
|
92
|
+
turn_on = cmd.params(:boolean).first.value
|
93
|
+
turn_on ? playlists[:current].shuffle : playlists[:current].order
|
94
|
+
return success(message: turn_on ? "Now we're shuffling!" : "Shuffle is now off")
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# For more information on this command refer to user manual or inline help in interactive mode.
|
99
|
+
def connect(cmd)
|
100
|
+
path = cmd.params(:path).first
|
101
|
+
name = cmd.params(:literal).first
|
102
|
+
|
103
|
+
if path.nil?
|
104
|
+
# with only literal parameter
|
105
|
+
raise 'invalid parameter:missing parameters' if name.nil?
|
106
|
+
|
107
|
+
# es una reconexion...
|
108
|
+
drive = Drive.find_by(name: name.value)
|
109
|
+
raise 'invalid name:the named drive doesnt exists' if drive.nil?
|
110
|
+
|
111
|
+
if drive.connected
|
112
|
+
failure("What you mean? Drive 'name.value' is connected.")
|
113
|
+
else
|
114
|
+
if drive.update_attributes({connected: true})
|
115
|
+
success(message: "Drive '#{name.value}' was reconnected.")
|
116
|
+
else
|
117
|
+
failure("Something went wrong and I couldnt reconnect drive '#{name.value}'. Try again later please.")
|
118
|
+
end
|
119
|
+
end
|
120
|
+
else
|
121
|
+
# with path and literal parameter
|
122
|
+
raise 'invalid path:the directory is invalid' unless Dir.exist?(path.value)
|
123
|
+
raise 'invalid name:name required' if name.nil?
|
124
|
+
|
125
|
+
# es una creacion o actualizacion...
|
126
|
+
# checamos si la unidad existe
|
127
|
+
root_path = File.expand_path(path.value)
|
128
|
+
drive = Drive.find_by(path: root_path)
|
129
|
+
# la creamos si no existe...
|
130
|
+
is_update = !drive.nil?
|
131
|
+
drive = Drive.create!(name: name.value, path: root_path) unless is_update
|
132
|
+
|
133
|
+
track_info = get_files_in_tree(root_path, file_types).each_with_object([]) do |filepath, acc|
|
134
|
+
acc << extract_from_mp3(filepath, library_path: root_path)
|
135
|
+
end
|
136
|
+
|
137
|
+
# insertamos las nuevas y actualizamos las existentes
|
138
|
+
updated = update_song(track_info)
|
139
|
+
imported = insert_song(track_info)
|
140
|
+
|
141
|
+
success(message: connect_response_msg(imported, updated),
|
142
|
+
files_detected: track_info.size,
|
143
|
+
files_imported: imported,
|
144
|
+
files_updated: updated,
|
145
|
+
drive_updated: is_update)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# For more information on this command refer to user manual or inline help in interactive mode.
|
150
|
+
def disconnect(cmd)
|
151
|
+
name = cmd.params(:literal).first.value
|
152
|
+
drive = Drive.find_by(name: name)
|
153
|
+
raise "Drive '#{name}' dont exist." if drive.nil?
|
154
|
+
|
155
|
+
if drive.connected
|
156
|
+
if drive.update(connected: false)
|
157
|
+
success(message: "Drive '#{name}' is now disconnected.")
|
158
|
+
else
|
159
|
+
failure("I cant disconnect drive '#{name}', something weird happened. Maybe if you again later works.")
|
160
|
+
end
|
161
|
+
else
|
162
|
+
failure("The drive '#{name}' is already disconnected.")
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# For more information on this command refer to user manual or inline help in interactive mode.
|
167
|
+
def ff(cmd)
|
168
|
+
ff_in_secs = 10
|
169
|
+
|
170
|
+
unless cmd.params(:number).empty?
|
171
|
+
ff_in_secs = cmd.params(:number).first.value
|
172
|
+
end
|
173
|
+
|
174
|
+
ff_in_player ff_in_secs
|
175
|
+
|
176
|
+
return success(message: "Fast Forwarded by #{ff_in_secs} secs")
|
177
|
+
end
|
178
|
+
|
179
|
+
# For more information on this command refer to user manual or inline help in interactive mode.
|
180
|
+
def fb(cmd)
|
181
|
+
fb_in_secs = 10
|
182
|
+
|
183
|
+
unless cmd.params(:number).empty?
|
184
|
+
fb_in_secs = cmd.params(:number).first.value
|
185
|
+
end
|
186
|
+
|
187
|
+
fb_in_player fb_in_secs
|
188
|
+
|
189
|
+
return success(message: "Fast Backwarded by #{fb_in_secs} secs")
|
190
|
+
end
|
191
|
+
|
192
|
+
# For more information on this command refer to user manual or inline help in interactive mode.
|
193
|
+
def repeat(cmd)
|
194
|
+
repeat_in_player
|
195
|
+
return success(message: "Repeating " + current_song.to_s)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
@@ -0,0 +1,300 @@
|
|
1
|
+
module CultomePlayer::Player::Interface
|
2
|
+
module Helper
|
3
|
+
include CultomePlayer::Objects
|
4
|
+
|
5
|
+
VALID_SONG_ATTR = [:name, :year, :track, :duration, :relative_path, :artist_id, :album_id, :drive_id]
|
6
|
+
|
7
|
+
# Returns a string representation of a number of seconds in the format: mm:ss
|
8
|
+
#
|
9
|
+
# @param secs [Integer] Number of seconds to be represented.
|
10
|
+
# @return [String] The number of seconds formatted as mm:ss.
|
11
|
+
def format_secs(secs)
|
12
|
+
mins = secs.to_i / 60
|
13
|
+
secs_left = secs.to_i % 60
|
14
|
+
return "#{mins.to_s.rjust(2, "0")}:#{secs_left.to_s.rjust(2, "0")}"
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns a representation of a progress bar.
|
18
|
+
#
|
19
|
+
# @param current [Integer] The actual progress.
|
20
|
+
# @param total [Integer] The total progress to achive.
|
21
|
+
# @param total [Integer] Optional, the total progress to achive. Default 100.
|
22
|
+
# @param size [Integer] Optional, the width of the bar. Default 10.
|
23
|
+
# @param left [String] Optional, prefix to append. Default ''.
|
24
|
+
# @param right [String] Optional, postfix to append. Default ''.
|
25
|
+
# @return [String] The string representation of a progress bar.
|
26
|
+
def get_progress_bar_with_labels(current, total=100, size=10, left='', right='')
|
27
|
+
bar = get_progress_bar(current, total, size)
|
28
|
+
return "#{left} #{bar} #{right}".strip
|
29
|
+
end
|
30
|
+
|
31
|
+
# (see #get_progress_bar_with_labels)
|
32
|
+
def get_progress_bar(current, total=100, size=10)
|
33
|
+
factor = total > 0 ? current / total.to_f : 0
|
34
|
+
bars = ( factor * size ).floor
|
35
|
+
total = "_" * size
|
36
|
+
total[0,bars] = "#" * bars
|
37
|
+
return "|#{total}|"
|
38
|
+
end
|
39
|
+
|
40
|
+
# Generate a query and a array of values to replace into the query for a given set of parameters.
|
41
|
+
#
|
42
|
+
# @param params [List<Parameter>] The list of serach parameter to prepare.
|
43
|
+
# @return [(List<String>,List<String>)] The query and the values set to use.
|
44
|
+
def process_for_search(params)
|
45
|
+
return nil, [] if params.empty?
|
46
|
+
|
47
|
+
query, values = case params.first.type
|
48
|
+
when :object then process_object_for_search(params)
|
49
|
+
when :criteria then process_criteria_for_search(params)
|
50
|
+
when :literal then process_literal_for_search(params)
|
51
|
+
end
|
52
|
+
|
53
|
+
raise 'invalid command:invalid search criteria' if query =~ /^[\(\)]+$/
|
54
|
+
return query, values
|
55
|
+
end
|
56
|
+
|
57
|
+
# Return a list of absolute path of files in the path which has extension.
|
58
|
+
#
|
59
|
+
# @param path [String] The path to searlook for files.
|
60
|
+
# @param extension [List<String>] The list of extension to filter the files with.
|
61
|
+
# @return [List<String>] The absolute paths to the files found.
|
62
|
+
def get_files_in_tree(path, *extensions)
|
63
|
+
return extensions.each_with_object([]) do |ext, files|
|
64
|
+
files << Dir.glob("#{path}/**/*.#{ext}")
|
65
|
+
end.flatten
|
66
|
+
end
|
67
|
+
|
68
|
+
# Insert a new song into database. Except if its already present by path.
|
69
|
+
#
|
70
|
+
# @param new_info [List<Hash>] Has contains the keys :artist_id, :album_id, :drive_id, :relative_path, :library_path (optional).
|
71
|
+
# @return [Integer] The number of songs writed.
|
72
|
+
def insert_song(new_info)
|
73
|
+
existing_paths = get_unique_paths
|
74
|
+
to_be_processed = new_info.select{|s| !existing_paths.include?(s[:file_path]) }
|
75
|
+
return to_be_processed.count do |info|
|
76
|
+
write_song(info)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Updates a song into database
|
81
|
+
#
|
82
|
+
# @param new_info [List<Hash>] Has contains the keys :artist_id, :album_id, :drive_id, :relative_path, :library_path (optional).
|
83
|
+
# @return [Integer] The number of songs updated.
|
84
|
+
def update_song(new_info)
|
85
|
+
existing_paths = get_unique_paths
|
86
|
+
to_be_processed = new_info.select{|s| existing_paths.include?(s[:file_path]) }
|
87
|
+
return to_be_processed.count do |info|
|
88
|
+
# extraemos la cancion almacenada.. si existe
|
89
|
+
song = Song.includes(:drive).where("drives.path||'/'||songs.relative_path = ?", info[:file_path]).references(:drives).first
|
90
|
+
|
91
|
+
song.nil? ? false : write_song(info, song)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Extract the full list of songs connected.
|
96
|
+
#
|
97
|
+
# @return [List<Song>] The full list of songs connected in library.
|
98
|
+
def whole_library
|
99
|
+
Song.connected.to_a
|
100
|
+
end
|
101
|
+
|
102
|
+
# Play the next song in queue playlist.
|
103
|
+
#
|
104
|
+
# @return [Song] The song programed.
|
105
|
+
def play_queue
|
106
|
+
song = playlists[:queue].remove_next
|
107
|
+
play_in_player song
|
108
|
+
return song
|
109
|
+
end
|
110
|
+
|
111
|
+
# Select songs from the library and current and focus playlist.
|
112
|
+
#
|
113
|
+
# @param cmd [Command] The user command.
|
114
|
+
# @return [List<Song>] The list of songs picked.
|
115
|
+
def select_songs_with(cmd)
|
116
|
+
found_songs = search_songs_with(cmd)
|
117
|
+
from_focus = get_from_focus(cmd.params(:number))
|
118
|
+
from_playlists = get_from_playlists(cmd.params_values(:object))
|
119
|
+
results = found_songs + from_focus + from_playlists
|
120
|
+
return results
|
121
|
+
end
|
122
|
+
|
123
|
+
# Search in library for songs that fullfil the command parameters.
|
124
|
+
#
|
125
|
+
# @param cmd [Command] The user command.
|
126
|
+
# @return [List<Song>] The list of songs found.
|
127
|
+
def search_songs_with(cmd)
|
128
|
+
criteria_query, criteria_values = process_for_search(cmd.params(:criteria))
|
129
|
+
literal_query, literal_values = process_for_search(cmd.params(:literal))
|
130
|
+
object_query, object_values = process_for_search(cmd.params(:object))
|
131
|
+
# preparamos la query completa con sus parametros
|
132
|
+
search_query = [criteria_query, object_query, literal_query].compact.collect{|q| "(#{q})" }.join(" or ")
|
133
|
+
search_values = [criteria_values, object_values, literal_values].flatten.compact
|
134
|
+
# hacemos la query!
|
135
|
+
return search_query.empty? ? [] : Song.includes(:artist, :album).connected.where(search_query, *search_values).references(:artist, :album).to_a
|
136
|
+
end
|
137
|
+
|
138
|
+
# Get a list of songs from selected playlists, only if the playlist exist.
|
139
|
+
#
|
140
|
+
# @param lists [List<Symbol>] The names of the playlists to check.
|
141
|
+
# @return [List<Song>] The songs in the valid playlists.
|
142
|
+
def get_from_playlists(lists)
|
143
|
+
valid_lists = lists.select{|list_name| playlist?(list_name) }
|
144
|
+
return playlists[*valid_lists].songs
|
145
|
+
end
|
146
|
+
|
147
|
+
# Try to find the player object by name.
|
148
|
+
#
|
149
|
+
# @param name [Symbol] The name of the player object
|
150
|
+
# @return [Object] The player object found, if any.
|
151
|
+
def player_object(name)
|
152
|
+
case name
|
153
|
+
when :song then playlists[:current].current
|
154
|
+
else raise 'unknown player object:unknown player object'
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Check if a command has the format to be considered a ply inline (dont create a playlist).
|
159
|
+
#
|
160
|
+
# @param cmd [Command] The command to check.
|
161
|
+
# @return [Boolean] True if is considered to be played inline. False otherwise.
|
162
|
+
def play_inline?(cmd)
|
163
|
+
if cmd.action == "play"
|
164
|
+
return true if cmd.params.all?{|p| p.type == :number }
|
165
|
+
if cmd.params.size == 1
|
166
|
+
p = cmd.params.first
|
167
|
+
return p.type == :object && p.value == :song
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
return false
|
172
|
+
end
|
173
|
+
|
174
|
+
# Select the connect action message depending on the imported and updated parameters.
|
175
|
+
#
|
176
|
+
# @param imported [Integer] The number of imported songs.
|
177
|
+
# @param updated [Integer] The number of updated songs.
|
178
|
+
# @return [String] The appropiated message to show to user.
|
179
|
+
def connect_response_msg(imported, updated)
|
180
|
+
message = ""
|
181
|
+
if imported > 0
|
182
|
+
message += "Songs imported: #{imported}."
|
183
|
+
end
|
184
|
+
|
185
|
+
if updated > 0
|
186
|
+
message += "Songs updated: #{updated}."
|
187
|
+
end
|
188
|
+
|
189
|
+
return message
|
190
|
+
end
|
191
|
+
|
192
|
+
private
|
193
|
+
|
194
|
+
def get_from_focus(params)
|
195
|
+
params.map do |p|
|
196
|
+
playlists[:focus].at p.value - 1
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def get_unique_paths
|
201
|
+
Song.all.collect{|m| m.path }.uniq
|
202
|
+
end
|
203
|
+
|
204
|
+
def artist_id(artist_name)
|
205
|
+
return 0 if artist_name.blank?
|
206
|
+
artist = Artist.where(name: artist_name).first_or_create
|
207
|
+
return artist.id
|
208
|
+
end
|
209
|
+
|
210
|
+
def album_id(album_name)
|
211
|
+
return 0 if album_name.blank?
|
212
|
+
album = Album.where(name: album_name).first_or_create
|
213
|
+
return album.id
|
214
|
+
end
|
215
|
+
|
216
|
+
def drive_id(library_path)
|
217
|
+
return 0 if library_path.blank?
|
218
|
+
drive = Drive.where(path: library_path).first_or_create
|
219
|
+
return drive.id
|
220
|
+
end
|
221
|
+
|
222
|
+
def add_genre_to(song, genre)
|
223
|
+
unless genre.blank?
|
224
|
+
song.genres << Genre.where(name: genre).first_or_create
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def write_song(info, song=nil)
|
229
|
+
info[:artist_id] = artist_id(info[:artist])
|
230
|
+
info[:album_id] = album_id(info[:album])
|
231
|
+
info[:drive_id] = drive_id(info[:library_path])
|
232
|
+
info[:relative_path] = info[:file_path].gsub("#{info[:library_path]}/", '')
|
233
|
+
|
234
|
+
song_attr = info.select{|k,v| VALID_SONG_ATTR.include?(k) }
|
235
|
+
|
236
|
+
if song.nil?
|
237
|
+
song = Song.create!(song_attr)
|
238
|
+
else
|
239
|
+
song.update_attributes(song_attr)
|
240
|
+
end
|
241
|
+
|
242
|
+
add_genre_to(song, info[:genre])
|
243
|
+
|
244
|
+
return song.persisted?
|
245
|
+
end
|
246
|
+
|
247
|
+
def process_literal_for_search(params)
|
248
|
+
literals = params.collect do |p|
|
249
|
+
{query: 'artists.name like ? or albums.name like ? or songs.name like ?', value: ["%#{p.value}%", "%#{p.value}%", "%#{p.value}%"] }
|
250
|
+
end
|
251
|
+
|
252
|
+
query = literals.collect{|o| o[:query] }.join(" or ")
|
253
|
+
values = literals.collect{|o| o[:value]}
|
254
|
+
|
255
|
+
return query, values
|
256
|
+
end
|
257
|
+
|
258
|
+
def process_object_for_search(params)
|
259
|
+
objs = params.collect do |p|
|
260
|
+
case p.value
|
261
|
+
when :artist then {query: 'artists.name = ?', value: current_artist.name }
|
262
|
+
when :album then {query: 'albums.name = ?', value: current_album.name }
|
263
|
+
when :song then {query: 'songs.name = ?', value: current_song.name }
|
264
|
+
when :library then {query: 'songs.id > 0'}
|
265
|
+
else raise 'invalid search:unknown type'
|
266
|
+
end unless playlist?(p.value)
|
267
|
+
end.compact
|
268
|
+
|
269
|
+
return nil, [] if objs.empty?
|
270
|
+
|
271
|
+
query = objs.collect{|o| o[:query] }.join(" or ")
|
272
|
+
values = objs.collect{|o| o[:value]}.compact
|
273
|
+
|
274
|
+
return query, values
|
275
|
+
end
|
276
|
+
|
277
|
+
def process_criteria_for_search(params)
|
278
|
+
default = Hash.new{|h,k| h[k] = {count: 0, query: "", values: []} }
|
279
|
+
# analizamos los criterios
|
280
|
+
criterios = params.each_with_object(default) do |p, acc|
|
281
|
+
info = acc[p.criteria] # creamos nuevo mapa o sacamos el existente
|
282
|
+
|
283
|
+
info[:count] += 1
|
284
|
+
info[:query] << " or " if info[:count] > 1
|
285
|
+
case p.criteria
|
286
|
+
when :a then info[:query] << "artists.name like ?"
|
287
|
+
when :b then info[:query] << "albums.name like ?"
|
288
|
+
when :t then info[:query] << "songs.name like ?"
|
289
|
+
end
|
290
|
+
info[:values] << "%#{p.value}%"
|
291
|
+
end
|
292
|
+
|
293
|
+
query = criterios.values.collect{|c| "(#{c[:query]})" }.join(" and ")
|
294
|
+
values = criterios.values.collect{|c| c[:values] }.flatten
|
295
|
+
return query, values
|
296
|
+
end
|
297
|
+
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|