groove-dl 0.2.0 → 0.3.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 +4 -4
- data/.gitignore +1 -0
- data/MANIFEST +4 -8
- data/bin/groove-dl +4 -9
- data/glade/groove-dl.glade +539 -0
- data/groove-dl.gemspec +4 -4
- data/lib/groove-dl/app.rb +16 -43
- data/lib/groove-dl/cli/search.rb +1 -7
- data/lib/groove-dl/displayer.rb +9 -9
- data/lib/groove-dl/downloader.rb +10 -10
- data/lib/groove-dl/version.rb +1 -1
- data/lib/groove-dl/widgets/download.rb +231 -0
- data/lib/groove-dl/widgets/events.rb +23 -0
- data/lib/groove-dl/widgets/search.rb +77 -0
- data/spec/groove-dl/displayer_spec.rb +6 -5
- data/spec/groove-dl/downloader_spec.rb +9 -9
- metadata +10 -14
- data/lib/groove-dl/widgets/download/bar.rb +0 -96
- data/lib/groove-dl/widgets/download/book.rb +0 -77
- data/lib/groove-dl/widgets/download/list/failed.rb +0 -75
- data/lib/groove-dl/widgets/download/list/queue.rb +0 -171
- data/lib/groove-dl/widgets/download/list/success.rb +0 -83
- data/lib/groove-dl/widgets/menu/success.rb +0 -32
- data/lib/groove-dl/widgets/search/bar.rb +0 -74
- data/lib/groove-dl/widgets/search/list.rb +0 -151
data/groove-dl.gemspec
CHANGED
@@ -7,15 +7,15 @@ Gem::Specification.new do |s|
|
|
7
7
|
s.authors = ['Pierre Rambaud']
|
8
8
|
s.email = 'pierre.rambaud86@gmail.com'
|
9
9
|
s.license = 'GPL-3.0'
|
10
|
-
s.summary = '
|
10
|
+
s.summary = 'Grooveshark songs downloader.'
|
11
11
|
s.homepage = 'http://github.com/PierreRambaud/groove-dl'
|
12
|
-
s.description = 'Grooveshark downloader allow you to choose' \
|
13
|
-
'songs and download them.'
|
12
|
+
s.description = 'Grooveshark downloader allow you to search, choose ' \
|
13
|
+
'playlists and songs and download them.'
|
14
14
|
s.executables = ['groove-dl', 'groove-dl-cli']
|
15
15
|
|
16
16
|
s.files = File.read(File.expand_path('../MANIFEST', __FILE__)).split("\n")
|
17
17
|
|
18
|
-
s.required_ruby_version = '>= 1.9.
|
18
|
+
s.required_ruby_version = '>= 1.9.3'
|
19
19
|
|
20
20
|
s.add_dependency 'slop', '~>3.6'
|
21
21
|
s.add_dependency 'grooveshark', '~>0.2.12'
|
data/lib/groove-dl/app.rb
CHANGED
@@ -1,55 +1,28 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
2
|
module GrooveDl
|
3
3
|
# Bootstraper for the application
|
4
|
-
class App < Gtk::
|
5
|
-
|
6
|
-
Gtk::Settings.default.gtk_button_images = true
|
7
|
-
super
|
8
|
-
|
9
|
-
client = Grooveshark::Client.new
|
10
|
-
box = Gtk::Box.new(:vertical)
|
11
|
-
|
12
|
-
search_bar = Widgets::Search::Bar.new(:vertical, 6)
|
13
|
-
search_bar.load(client, self)
|
4
|
+
class App < Gtk::Builder
|
5
|
+
attr_accessor :signals_list
|
14
6
|
|
15
|
-
|
16
|
-
|
7
|
+
def initialize(path)
|
8
|
+
super()
|
17
9
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
download_book = Widgets::Download::Book.new
|
22
|
-
download_book.load(client, self)
|
23
|
-
|
24
|
-
box.pack_start(search_bar, expand: false, fill: true, padding: 10)
|
25
|
-
box.pack_start(search_list, expand: true, fill: true, padding: 5)
|
26
|
-
box.pack_start(download_bar, expand: false, fill: true, padding: 10)
|
27
|
-
box.pack_start(download_book, expand: true, fill: true, padding: 5)
|
10
|
+
Gtk::Settings.default.gtk_button_images = true
|
11
|
+
add_from_file(path)
|
28
12
|
|
29
|
-
|
13
|
+
@signals_list = {}
|
14
|
+
@main_window = get_object('main_window')
|
15
|
+
@main_window.set_window_position(Gtk::Window::Position::CENTER)
|
16
|
+
@main_window.signal_connect('destroy') { Gtk.main_quit }
|
17
|
+
@main_window.show_all
|
30
18
|
|
31
|
-
|
32
|
-
|
19
|
+
client = Grooveshark::Client.new
|
20
|
+
search_list = Widgets::Search.new(client, self)
|
21
|
+
Widgets::Download.new(client, self, search_list)
|
33
22
|
|
34
|
-
|
35
|
-
|
36
|
-
Gtk.main_quit
|
23
|
+
connect_signals do |handler|
|
24
|
+
@signals_list[handler] if @signals_list.key?(handler)
|
37
25
|
end
|
38
|
-
|
39
|
-
set_title('Grooveshark Downloader')
|
40
|
-
set_default_size(1024, 768)
|
41
|
-
set_window_position(:center)
|
42
|
-
show_all
|
43
|
-
end
|
44
|
-
|
45
|
-
def find_by_name(element = self, name)
|
46
|
-
return element if element.name == name
|
47
|
-
element.children.each do |child|
|
48
|
-
result = find_by_name(child, name)
|
49
|
-
return result unless result.nil?
|
50
|
-
end if element.respond_to?(:children)
|
51
|
-
|
52
|
-
nil
|
53
26
|
end
|
54
27
|
end
|
55
28
|
end
|
data/lib/groove-dl/cli/search.rb
CHANGED
@@ -16,13 +16,7 @@ GrooveDl::CLI.options.command 'search' do
|
|
16
16
|
query = opts[:s] if opts[:s]
|
17
17
|
query = opts[:p] if opts[:p]
|
18
18
|
|
19
|
-
results = client.
|
20
|
-
type: type,
|
21
|
-
query: query)['result']
|
22
|
-
results.map! do |data|
|
23
|
-
next Grooveshark::Song.new data if type == 'Songs'
|
24
|
-
data
|
25
|
-
end
|
19
|
+
results = client.search(type, query)
|
26
20
|
|
27
21
|
displayer = GrooveDl::Displayer.new(results, type)
|
28
22
|
displayer.render
|
data/lib/groove-dl/displayer.rb
CHANGED
@@ -7,8 +7,8 @@ module GrooveDl
|
|
7
7
|
##
|
8
8
|
# Initialize Displayer
|
9
9
|
#
|
10
|
-
# @
|
11
|
-
# @
|
10
|
+
# @param [Array] result The result from the search
|
11
|
+
# @param [String] type The search type
|
12
12
|
#
|
13
13
|
# @return [Nil]
|
14
14
|
#
|
@@ -31,22 +31,22 @@ module GrooveDl
|
|
31
31
|
|
32
32
|
def headers
|
33
33
|
return %w(Id Album Artist Song) if @type == 'Songs'
|
34
|
-
return %w(Id
|
34
|
+
return %w(Id Name Author NumSongs) if @type == 'Playlists'
|
35
35
|
end
|
36
36
|
|
37
37
|
##
|
38
38
|
# Add row into table
|
39
39
|
#
|
40
|
-
# @
|
41
|
-
# @
|
40
|
+
# @param [Terminal::Table] table Table in which row will be added
|
41
|
+
# @param [Array] result The result from the search
|
42
42
|
#
|
43
43
|
# @return [Nil]
|
44
44
|
#
|
45
45
|
def add_row(table, data)
|
46
|
-
table.add_row([data
|
47
|
-
data
|
48
|
-
data
|
49
|
-
data
|
46
|
+
table.add_row([data.id,
|
47
|
+
data.name,
|
48
|
+
data.username,
|
49
|
+
data.num_songs]) if @type == 'Playlists'
|
50
50
|
table.add_row([data.id,
|
51
51
|
data.album,
|
52
52
|
data.artist,
|
data/lib/groove-dl/downloader.rb
CHANGED
@@ -25,7 +25,7 @@ module GrooveDl
|
|
25
25
|
##
|
26
26
|
# Download song
|
27
27
|
#
|
28
|
-
# @
|
28
|
+
# @param [String] song_id Song id
|
29
29
|
#
|
30
30
|
# @return [Array]
|
31
31
|
#
|
@@ -39,7 +39,7 @@ module GrooveDl
|
|
39
39
|
##
|
40
40
|
# Download playlist
|
41
41
|
#
|
42
|
-
# @
|
42
|
+
# @param [String] playlist_id Playlist id
|
43
43
|
#
|
44
44
|
# @return [Array]
|
45
45
|
#
|
@@ -96,11 +96,11 @@ module GrooveDl
|
|
96
96
|
# @param [Grooveshark::Song] song Song
|
97
97
|
#
|
98
98
|
def build_path(output_directory, song)
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
99
|
+
format('%s/%s/%s/%s.mp3',
|
100
|
+
output_directory,
|
101
|
+
song.artist,
|
102
|
+
song.album,
|
103
|
+
song.name)
|
104
104
|
end
|
105
105
|
|
106
106
|
##
|
@@ -146,11 +146,11 @@ module GrooveDl
|
|
146
146
|
#
|
147
147
|
def process_gui_response(object)
|
148
148
|
proc do |response|
|
149
|
-
pgbar_value = Widgets::Download::
|
150
|
-
pgbar_text = Widgets::Download::
|
149
|
+
pgbar_value = Widgets::Download::QUEUE_COLUMN_PGBAR_VALUE
|
150
|
+
pgbar_text = Widgets::Download::QUEUE_COLUMN_PGBAR_TEXT
|
151
151
|
|
152
152
|
total_size = response['content-length'].to_i
|
153
|
-
path = object[Widgets::Download::
|
153
|
+
path = object[Widgets::Download::QUEUE_COLUMN_PATH]
|
154
154
|
if File.exist?(path) &&
|
155
155
|
File.size?(path) == total_size
|
156
156
|
object[pgbar_value] = 100
|
data/lib/groove-dl/version.rb
CHANGED
@@ -0,0 +1,231 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
module GrooveDl
|
3
|
+
# Widgets components
|
4
|
+
module Widgets
|
5
|
+
# Download bar section
|
6
|
+
class Download < Events
|
7
|
+
attr_accessor :search_list, :songs, :downloader
|
8
|
+
attr_accessor :failed, :success, :queue
|
9
|
+
|
10
|
+
RIGHT_CLICK = 3
|
11
|
+
|
12
|
+
SUCCESS_COLUMN_PATH,
|
13
|
+
SUCCESS_COLUMN_SIZE = *(0..1).to_a
|
14
|
+
|
15
|
+
FAILED_COLUMN_PATH,
|
16
|
+
FAILED_COLUMN_REASON = *(0..1).to_a
|
17
|
+
|
18
|
+
QUEUE_COLUMN_PATH,
|
19
|
+
QUEUE_COLUMN_PGBAR_VALUE,
|
20
|
+
QUEUE_COLUMN_PGBAR_TEXT = *(0..2).to_a
|
21
|
+
|
22
|
+
##
|
23
|
+
# Initialize download list and download button
|
24
|
+
#
|
25
|
+
# @param [Grooveshark::Client] client Grooveshark client
|
26
|
+
# @param [Gtk::Builder] app Application created by Gtk builder
|
27
|
+
# @param [Search] search_list Search class for getting list
|
28
|
+
#
|
29
|
+
# @return [Download]
|
30
|
+
#
|
31
|
+
def initialize(client, app, search_list)
|
32
|
+
super(client, app)
|
33
|
+
@songs = {}
|
34
|
+
@queue = 0
|
35
|
+
@success = 0
|
36
|
+
@failed = 0
|
37
|
+
@search_list = search_list
|
38
|
+
@downloader = GrooveDl::Downloader.new(@client)
|
39
|
+
@downloader.type = 'gui'
|
40
|
+
@app.get_object('directory_chooser').filename = Dir.tmpdir
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# Event when button download is clicked
|
45
|
+
#
|
46
|
+
def on_btn_download_clicked
|
47
|
+
return if @queue.zero?
|
48
|
+
@app.get_object('btn_clear_queue').sensitive = false
|
49
|
+
@app.get_object('btn_add_to_queue').sensitive = false
|
50
|
+
|
51
|
+
download_songs
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# Event when button clear queue is clicked
|
56
|
+
#
|
57
|
+
def on_btn_clear_queue_clicked
|
58
|
+
@app.get_object('download_queue_list_store').clear
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
# Event when button add to queue is clicked
|
63
|
+
#
|
64
|
+
def on_btn_add_to_queue_clicked
|
65
|
+
selected = {}
|
66
|
+
column_id = GrooveDl::Widgets::Search::COLUMN_ID
|
67
|
+
column_checkbox = GrooveDl::Widgets::Search::COLUMN_CHECKBOX
|
68
|
+
search_list_store = @app.get_object('search_list_store')
|
69
|
+
search_list_store.each do |_model, _path, iter|
|
70
|
+
next unless iter[column_checkbox]
|
71
|
+
selected[iter[column_id]] = @search_list.data[iter[column_id]]
|
72
|
+
end
|
73
|
+
|
74
|
+
@queue_store = @app.get_object('download_queue_list_store')
|
75
|
+
@failed_store = @app.get_object('download_failed_list_store')
|
76
|
+
@success_store = @app.get_object('download_success_list_store')
|
77
|
+
create_queue_item(selected)
|
78
|
+
|
79
|
+
@queue = @songs.count
|
80
|
+
@app.get_object('download_label_queue')
|
81
|
+
.set_text(format('Queue (%d)', @queue))
|
82
|
+
end
|
83
|
+
|
84
|
+
##
|
85
|
+
# Append row in queue list store
|
86
|
+
#
|
87
|
+
# @param [Hash] data Data parsed
|
88
|
+
#
|
89
|
+
def create_queue_item(data)
|
90
|
+
data.each do |id, element|
|
91
|
+
if element.is_a?(Grooveshark::Song)
|
92
|
+
iter = @queue_store.append
|
93
|
+
iter[QUEUE_COLUMN_PATH] =
|
94
|
+
@downloader.build_path(@app.get_object('directory_chooser')
|
95
|
+
.filename,
|
96
|
+
element)
|
97
|
+
iter[QUEUE_COLUMN_PGBAR_VALUE] = 0
|
98
|
+
iter[QUEUE_COLUMN_PGBAR_TEXT] = nil
|
99
|
+
@songs[element.id] = { iter: iter, song: element }
|
100
|
+
else
|
101
|
+
playlist = Grooveshark::Playlist.new(@client,
|
102
|
+
'playlist_id' => id)
|
103
|
+
result = {}
|
104
|
+
playlist.load_songs.each do |song|
|
105
|
+
result[song.id] = song
|
106
|
+
end
|
107
|
+
|
108
|
+
create_queue_item(result)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
return if @songs.empty?
|
113
|
+
end
|
114
|
+
|
115
|
+
##
|
116
|
+
# Append row in failed list store
|
117
|
+
#
|
118
|
+
# @param [Gtk::TreeIter] i Iterable element
|
119
|
+
# @param [String] reason Why this download have failed
|
120
|
+
#
|
121
|
+
def create_failed_item(i, reason)
|
122
|
+
iter = @failed_store.append
|
123
|
+
path = i[FAILED_COLUMN_PATH]
|
124
|
+
iter[FAILED_COLUMN_PATH] = path
|
125
|
+
iter[FAILED_COLUMN_REASON] = reason
|
126
|
+
end
|
127
|
+
|
128
|
+
##
|
129
|
+
# Append row in the success list store
|
130
|
+
#
|
131
|
+
# @param [Gtk::TreeIter] i Iterable element
|
132
|
+
#
|
133
|
+
def create_success_item(i)
|
134
|
+
iter = @success_store.append
|
135
|
+
path = i[SUCCESS_COLUMN_PATH]
|
136
|
+
iter[SUCCESS_COLUMN_PATH] = path
|
137
|
+
size = (File.size?(path).to_f / (1024 * 1024)).round(2)
|
138
|
+
iter[SUCCESS_COLUMN_SIZE] = "#{size} MB"
|
139
|
+
end
|
140
|
+
|
141
|
+
##
|
142
|
+
# Download songs in queue
|
143
|
+
#
|
144
|
+
def download_songs
|
145
|
+
concurrency = @app.get_object('concurrency_entry').text.to_i
|
146
|
+
concurrency = 5 if concurrency.zero?
|
147
|
+
Thread.abort_on_exception = true
|
148
|
+
Thread.new do
|
149
|
+
nb = 0
|
150
|
+
@songs.each do |_id, s|
|
151
|
+
nb += 1
|
152
|
+
Thread.new do
|
153
|
+
download_file(s)
|
154
|
+
nb -= 1
|
155
|
+
end
|
156
|
+
sleep(0.5) until nb < concurrency
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
##
|
162
|
+
# Download song
|
163
|
+
#
|
164
|
+
# @param [Grooveshark::Song] song Song to download
|
165
|
+
#
|
166
|
+
def download_file(song)
|
167
|
+
begin
|
168
|
+
@downloader.download(song[:song], song[:iter])
|
169
|
+
notify_success(song)
|
170
|
+
rescue Errors::AlreadyDownloaded => e
|
171
|
+
GrooveDl.configuration.logger.info(e.message)
|
172
|
+
notify_success(song)
|
173
|
+
rescue Grooveshark::GeneralError => e
|
174
|
+
GrooveDl.configuration.logger.error(e)
|
175
|
+
notify_error(song, e)
|
176
|
+
end
|
177
|
+
@app.get_object('download_label_queue')
|
178
|
+
.set_text(format('Queue (%d)', @queue -= 1))
|
179
|
+
@queue_store.remove(song[:iter])
|
180
|
+
end
|
181
|
+
|
182
|
+
##
|
183
|
+
# Notify success
|
184
|
+
#
|
185
|
+
# @param [Grooveshark::Song] song Song displayed in success page
|
186
|
+
#
|
187
|
+
def notify_success(song)
|
188
|
+
create_success_item(song[:iter])
|
189
|
+
@app.get_object('download_label_success')
|
190
|
+
.set_text(format('Success (%d)', @success += 1))
|
191
|
+
end
|
192
|
+
|
193
|
+
##
|
194
|
+
# Notify erro
|
195
|
+
#
|
196
|
+
# @param [Grooveshark::Song] song Song displayed in failed page
|
197
|
+
# @param [StandardError] e Exception to retrieve message
|
198
|
+
#
|
199
|
+
def notify_error(song, e)
|
200
|
+
create_failed_item(song[:iter], e.message)
|
201
|
+
@app.get_object('download_label_failed')
|
202
|
+
.set_text(format('Failed (%d)', @failed += 1))
|
203
|
+
end
|
204
|
+
|
205
|
+
##
|
206
|
+
# Open downloaded song
|
207
|
+
#
|
208
|
+
def on_menu_open_activate
|
209
|
+
treeview = @app.get_object('download_success')
|
210
|
+
iter = treeview.selection.selected
|
211
|
+
Thread.new do
|
212
|
+
path = iter[Download::QUEUE_COLUMN_PATH]
|
213
|
+
system("xdg-open #{Shellwords.escape(path)}")
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
##
|
218
|
+
# Open menu on right click
|
219
|
+
#
|
220
|
+
def on_download_success_button_press_event(widget, event)
|
221
|
+
return unless event.is_a?(Gdk::EventButton) &&
|
222
|
+
event.button == RIGHT_CLICK
|
223
|
+
|
224
|
+
path, _model = widget.get_path_at_pos(event.x, event.y)
|
225
|
+
widget.selection.select_path(path)
|
226
|
+
@app.get_object('success_menu')
|
227
|
+
.popup(nil, nil, event.button, event.time)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
module GrooveDl
|
3
|
+
# Widgets components
|
4
|
+
module Widgets
|
5
|
+
# Search section
|
6
|
+
class Events
|
7
|
+
attr_reader :client, :app
|
8
|
+
|
9
|
+
##
|
10
|
+
# Initialize events for signals
|
11
|
+
#
|
12
|
+
def initialize(client, app)
|
13
|
+
@client = client
|
14
|
+
@app = app
|
15
|
+
|
16
|
+
methods.each do |name|
|
17
|
+
next unless name.match(/^on_/)
|
18
|
+
@app.signals_list[name.to_s] = method(name)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|