groove-dl 0.1.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/.gitignore +4 -0
- data/.rubocop.yml +8 -0
- data/.travis.yml +7 -0
- data/Gemfile +4 -0
- data/LICENSE +674 -0
- data/MANIFEST +34 -0
- data/README.md +87 -0
- data/Rakefile +10 -0
- data/bin/groove-dl +18 -0
- data/bin/groove-dl-cli +15 -0
- data/groove-dl.gemspec +32 -0
- data/lib/groove-dl.rb +13 -0
- data/lib/groove-dl/app.rb +54 -0
- data/lib/groove-dl/cli.rb +58 -0
- data/lib/groove-dl/cli/search.rb +30 -0
- data/lib/groove-dl/configuration.rb +14 -0
- data/lib/groove-dl/displayer.rb +56 -0
- data/lib/groove-dl/downloader.rb +178 -0
- data/lib/groove-dl/errors.rb +8 -0
- data/lib/groove-dl/version.rb +5 -0
- data/lib/groove-dl/widgets/download/bar.rb +87 -0
- data/lib/groove-dl/widgets/download/book.rb +73 -0
- data/lib/groove-dl/widgets/download/list/failed.rb +75 -0
- data/lib/groove-dl/widgets/download/list/queue.rb +171 -0
- data/lib/groove-dl/widgets/download/list/success.rb +76 -0
- data/lib/groove-dl/widgets/search/bar.rb +67 -0
- data/lib/groove-dl/widgets/search/list.rb +146 -0
- data/spec/groove-dl/cli_spec.rb +49 -0
- data/spec/groove-dl/displayer_spec.rb +63 -0
- data/spec/groove-dl/downloader_spec.rb +188 -0
- data/spec/spec_helper.rb +8 -0
- data/task/manifest.rake +9 -0
- data/task/rubocop.rake +5 -0
- data/task/spec.rake +6 -0
- metadata +233 -0
@@ -0,0 +1,87 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
module GrooveDl
|
3
|
+
# Widgets components
|
4
|
+
module Widgets
|
5
|
+
# Download section
|
6
|
+
module Download
|
7
|
+
# Download bar
|
8
|
+
class Bar < Gtk::Box
|
9
|
+
attr_reader :type, :query
|
10
|
+
|
11
|
+
##
|
12
|
+
# Initialize widgets
|
13
|
+
#
|
14
|
+
# @param [Grooveshark::Client] client Grooveshark client
|
15
|
+
# @param [Gtk::Window] window Gtk app
|
16
|
+
#
|
17
|
+
def load(_client, window)
|
18
|
+
set_name('download_bar')
|
19
|
+
|
20
|
+
download_box = Gtk::Box.new(:horizontal, 6)
|
21
|
+
|
22
|
+
add_button = Gtk::Button.new(label: 'Add to queue',
|
23
|
+
stock_id: Gtk::Stock::SAVE)
|
24
|
+
|
25
|
+
add_button.signal_connect('released') do
|
26
|
+
l = window.find_by_name('download_list')
|
27
|
+
l.store.clear
|
28
|
+
l.create_model(window.find_by_name('search_list').selection)
|
29
|
+
end
|
30
|
+
|
31
|
+
download_box.pack_start(add_button,
|
32
|
+
expand: false,
|
33
|
+
fill: true,
|
34
|
+
padding: 5)
|
35
|
+
|
36
|
+
directory_chooser =
|
37
|
+
Gtk::FileChooserButton.new('Select directory',
|
38
|
+
Gtk::FileChooser::Action::SELECT_FOLDER)
|
39
|
+
directory_chooser.filename = Dir.tmpdir
|
40
|
+
directory_chooser.set_name('directory_chooser')
|
41
|
+
download_box.pack_start(directory_chooser,
|
42
|
+
expand: true,
|
43
|
+
fill: true,
|
44
|
+
padding: 5)
|
45
|
+
|
46
|
+
concurrency_entry = Gtk::Entry.new
|
47
|
+
concurrency_entry.set_name('concurrency_entry')
|
48
|
+
concurrency_entry.text = '5'
|
49
|
+
|
50
|
+
concurrency_label = Gtk::Label.new('Concurrency', true)
|
51
|
+
concurrency_label.mnemonic_widget = concurrency_entry
|
52
|
+
|
53
|
+
download_box.pack_start(concurrency_label,
|
54
|
+
expand: false,
|
55
|
+
fill: false,
|
56
|
+
padding: 5)
|
57
|
+
download_box.pack_start(concurrency_entry,
|
58
|
+
expand: false,
|
59
|
+
fill: false,
|
60
|
+
padding: 5)
|
61
|
+
|
62
|
+
concurrency_entry.signal_connect('changed') do
|
63
|
+
value = concurrency_entry.text.to_i
|
64
|
+
concurrency_entry.text = value.to_s unless value == 0
|
65
|
+
end
|
66
|
+
|
67
|
+
download_button = Gtk::Button.new(label: 'Download',
|
68
|
+
stock_id: Gtk::Stock::SAVE)
|
69
|
+
|
70
|
+
download_button.signal_connect('released') do
|
71
|
+
download_button.sensitive = false
|
72
|
+
window.find_by_name('download_list').download
|
73
|
+
end
|
74
|
+
|
75
|
+
download_box.pack_start(download_button,
|
76
|
+
expand: false,
|
77
|
+
fill: true,
|
78
|
+
padding: 5)
|
79
|
+
|
80
|
+
pack_start(download_box,
|
81
|
+
expand: false,
|
82
|
+
padding: 10)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
module GrooveDl
|
3
|
+
# Widgets components
|
4
|
+
module Widgets
|
5
|
+
# Download section
|
6
|
+
module Download
|
7
|
+
# Download book
|
8
|
+
class Book < Gtk::Notebook
|
9
|
+
attr_reader :window
|
10
|
+
|
11
|
+
QUEUE = 'Queue (%d)'
|
12
|
+
SUCCESS = 'Success (%d)'
|
13
|
+
FAILED = 'Failed (%d)'
|
14
|
+
|
15
|
+
##
|
16
|
+
# Initialize widgets
|
17
|
+
#
|
18
|
+
# @param [Grooveshark::Client] client Grooveshark client
|
19
|
+
# @param [Gtk::Window] window Gtk app
|
20
|
+
#
|
21
|
+
def load(client, window)
|
22
|
+
@window = window
|
23
|
+
set_name('download_book')
|
24
|
+
set_tab_pos(Gtk::PositionType::TOP)
|
25
|
+
|
26
|
+
# Download list
|
27
|
+
download_list = Widgets::Download::List::Queue.new(:vertical, 6)
|
28
|
+
download_list.set_name('download_list')
|
29
|
+
download_list.load(client, window)
|
30
|
+
|
31
|
+
@download_label = Gtk::Label.new(QUEUE % 0)
|
32
|
+
@download_label.set_name('download_label')
|
33
|
+
append_page(download_list, @download_label)
|
34
|
+
|
35
|
+
# Success list
|
36
|
+
download_success_list =
|
37
|
+
Widgets::Download::List::Success.new(:vertical, 6)
|
38
|
+
download_success_list.set_name('download_success_list')
|
39
|
+
download_success_list.load(client, window)
|
40
|
+
|
41
|
+
@download_success_label = Gtk::Label.new(SUCCESS % 0)
|
42
|
+
@download_success_label.set_name('download_success_label')
|
43
|
+
append_page(download_success_list, @download_success_label)
|
44
|
+
|
45
|
+
# Failed list
|
46
|
+
download_failed_list =
|
47
|
+
Widgets::Download::List::Failed.new(:vertical, 6)
|
48
|
+
download_failed_list.set_name('download_failed_list')
|
49
|
+
download_failed_list.load(client, window)
|
50
|
+
|
51
|
+
@download_failed_label = Gtk::Label.new(FAILED % 0)
|
52
|
+
@download_failed_label.set_name('download_failed_label')
|
53
|
+
append_page(download_failed_list, @download_failed_label)
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# Set label for page in notebook element
|
58
|
+
#
|
59
|
+
# @param [String] type Page type
|
60
|
+
# @param [Integer] nb Number of element in this page
|
61
|
+
#
|
62
|
+
def set_label(type, nb)
|
63
|
+
element = @download_label if type == 'QUEUE'
|
64
|
+
element = @download_success_label if type == 'SUCCESS'
|
65
|
+
element = @download_failed_label if type == 'FAILED'
|
66
|
+
|
67
|
+
return if element.nil?
|
68
|
+
element.set_text(Book.const_get(type.upcase) % nb)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
module GrooveDl
|
3
|
+
# Widgets components
|
4
|
+
module Widgets
|
5
|
+
# Download section
|
6
|
+
module Download
|
7
|
+
# List page
|
8
|
+
module List
|
9
|
+
# Failed tree
|
10
|
+
class Failed < Gtk::Box
|
11
|
+
attr_reader :store, :data
|
12
|
+
|
13
|
+
COLUMN_PATH,
|
14
|
+
COLUMN_REASON = *(0..2).to_a
|
15
|
+
|
16
|
+
##
|
17
|
+
# Initialize widgets
|
18
|
+
#
|
19
|
+
# @param [Grooveshark::Client] client Grooveshark client
|
20
|
+
# @param [Gtk::Window] window Gtk app
|
21
|
+
#
|
22
|
+
def load(_client, _window)
|
23
|
+
@data = {}
|
24
|
+
sw = Gtk::ScrolledWindow.new
|
25
|
+
sw.shadow_type = Gtk::ShadowType::ETCHED_IN
|
26
|
+
sw.set_policy(Gtk::PolicyType::AUTOMATIC,
|
27
|
+
Gtk::PolicyType::AUTOMATIC)
|
28
|
+
pack_start(sw, expand: true, fill: true, padding: 0)
|
29
|
+
|
30
|
+
@store = Gtk::ListStore.new(String, String)
|
31
|
+
treeview = Gtk::TreeView.new(@store)
|
32
|
+
treeview.rules_hint = true
|
33
|
+
|
34
|
+
sw.add(treeview)
|
35
|
+
|
36
|
+
add_columns(treeview)
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# Append row in list store
|
41
|
+
#
|
42
|
+
# @param [Gtk::TreeIter] i Iterable element
|
43
|
+
# @param [String] reason Why this download have failed
|
44
|
+
#
|
45
|
+
def create_model(i, reason)
|
46
|
+
iter = @store.append
|
47
|
+
path = i[COLUMN_PATH]
|
48
|
+
iter[COLUMN_PATH] = path
|
49
|
+
iter[COLUMN_REASON] = reason
|
50
|
+
end
|
51
|
+
|
52
|
+
##
|
53
|
+
# Add columns on the treeview element
|
54
|
+
#
|
55
|
+
# @param [Gtk::Treeview] treeview Treeview
|
56
|
+
#
|
57
|
+
def add_columns(treeview)
|
58
|
+
renderer = Gtk::CellRendererText.new
|
59
|
+
column = Gtk::TreeViewColumn.new('Path',
|
60
|
+
renderer,
|
61
|
+
'text' => COLUMN_PATH)
|
62
|
+
column.fixed_width = 650
|
63
|
+
treeview.append_column(column)
|
64
|
+
|
65
|
+
renderer = Gtk::CellRendererText.new
|
66
|
+
column = Gtk::TreeViewColumn.new('Reason',
|
67
|
+
renderer,
|
68
|
+
'text' => COLUMN_REASON)
|
69
|
+
treeview.append_column(column)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
module GrooveDl
|
3
|
+
# Widgets components
|
4
|
+
module Widgets
|
5
|
+
# Download section
|
6
|
+
module Download
|
7
|
+
# List page
|
8
|
+
module List
|
9
|
+
# Queue tree
|
10
|
+
class Queue < Gtk::Box
|
11
|
+
attr_reader :store, :data
|
12
|
+
attr_writer :downloader, :client
|
13
|
+
attr_accessor :failed, :success, :queue
|
14
|
+
|
15
|
+
COLUMN_PATH,
|
16
|
+
COLUMN_PGBAR_VALUE,
|
17
|
+
COLUMN_PGBAR_TEXT = *(0..2).to_a
|
18
|
+
|
19
|
+
##
|
20
|
+
# Initialize widgets
|
21
|
+
#
|
22
|
+
# @param [Grooveshark::Client] client Grooveshark client
|
23
|
+
# @param [Gtk::Window] window Gtk app
|
24
|
+
#
|
25
|
+
def load(client, window)
|
26
|
+
@success = 0
|
27
|
+
@failed = 0
|
28
|
+
@queue = 0
|
29
|
+
@client = client
|
30
|
+
@window = window
|
31
|
+
@data = {}
|
32
|
+
@downloader = GrooveDl::Downloader.new(@client)
|
33
|
+
@downloader.type = 'gui'
|
34
|
+
|
35
|
+
sw = Gtk::ScrolledWindow.new
|
36
|
+
sw.shadow_type = Gtk::ShadowType::ETCHED_IN
|
37
|
+
sw.set_policy(Gtk::PolicyType::AUTOMATIC,
|
38
|
+
Gtk::PolicyType::AUTOMATIC)
|
39
|
+
pack_start(sw, expand: true, fill: true, padding: 0)
|
40
|
+
|
41
|
+
@store = Gtk::ListStore.new(String, Integer, String)
|
42
|
+
create_model
|
43
|
+
treeview = Gtk::TreeView.new(@store)
|
44
|
+
treeview.rules_hint = true
|
45
|
+
|
46
|
+
sw.add(treeview)
|
47
|
+
|
48
|
+
add_columns(treeview)
|
49
|
+
end
|
50
|
+
|
51
|
+
def create_model(data = {})
|
52
|
+
data.each do |id, element|
|
53
|
+
if element.is_a?(Grooveshark::Song)
|
54
|
+
iter = @store.append
|
55
|
+
iter[COLUMN_PATH] =
|
56
|
+
@downloader.build_path(@window
|
57
|
+
.find_by_name('directory_chooser')
|
58
|
+
.filename,
|
59
|
+
element)
|
60
|
+
iter[COLUMN_PGBAR_VALUE] = 0
|
61
|
+
iter[COLUMN_PGBAR_TEXT] = nil
|
62
|
+
@data[element.id] = { iter: iter, song: element }
|
63
|
+
else
|
64
|
+
playlist = Grooveshark::Playlist.new(@client,
|
65
|
+
'playlist_id' => id)
|
66
|
+
result = {}
|
67
|
+
playlist.load_songs.each do |song|
|
68
|
+
result[song.id] = song
|
69
|
+
end
|
70
|
+
|
71
|
+
create_model(result)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
return if @data.empty?
|
76
|
+
@queue = @data.count
|
77
|
+
@window.find_by_name('download_book').set_label('QUEUE', @queue)
|
78
|
+
end
|
79
|
+
|
80
|
+
##
|
81
|
+
# Add columns on the treeview element
|
82
|
+
#
|
83
|
+
# @param [Gtk::Treeview] treeview Treeview
|
84
|
+
#
|
85
|
+
def add_columns(treeview)
|
86
|
+
renderer = Gtk::CellRendererText.new
|
87
|
+
column = Gtk::TreeViewColumn.new('Path',
|
88
|
+
renderer,
|
89
|
+
'text' => COLUMN_PATH)
|
90
|
+
column.fixed_width = 650
|
91
|
+
treeview.append_column(column)
|
92
|
+
|
93
|
+
renderer = Gtk::CellRendererProgress.new
|
94
|
+
column = Gtk::TreeViewColumn.new('Progress',
|
95
|
+
renderer,
|
96
|
+
value: COLUMN_PGBAR_VALUE,
|
97
|
+
text: COLUMN_PGBAR_TEXT)
|
98
|
+
column.fixed_width = 100
|
99
|
+
treeview.append_column(column)
|
100
|
+
end
|
101
|
+
|
102
|
+
##
|
103
|
+
# Download files in queue
|
104
|
+
#
|
105
|
+
def download
|
106
|
+
concurrency = @window.find_by_name('concurrency_entry').text.to_i
|
107
|
+
concurrency = 5 if concurrency == 0
|
108
|
+
Thread.abort_on_exception = true
|
109
|
+
Thread.new do
|
110
|
+
nb = 0
|
111
|
+
@data.each do |_id, s|
|
112
|
+
nb += 1
|
113
|
+
Thread.new do
|
114
|
+
download_file(s)
|
115
|
+
nb -= 1
|
116
|
+
end
|
117
|
+
sleep(0.5) until nb < concurrency
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
##
|
123
|
+
# Download song
|
124
|
+
#
|
125
|
+
# @param [Grooveshark::Song] song Song to download
|
126
|
+
#
|
127
|
+
def download_file(song)
|
128
|
+
begin
|
129
|
+
@downloader.download(song[:song], song[:iter])
|
130
|
+
notify_success(song)
|
131
|
+
rescue Errors::AlreadyDownloaded => e
|
132
|
+
GrooveDl.configuration.logger.info(e.message)
|
133
|
+
notify_success(song)
|
134
|
+
rescue Grooveshark::GeneralError => e
|
135
|
+
GrooveDl.configuration.logger.error(e)
|
136
|
+
notify_error(song, e)
|
137
|
+
end
|
138
|
+
|
139
|
+
@window.find_by_name('download_book')
|
140
|
+
.set_label('QUEUE', @queue -= 1)
|
141
|
+
@store.remove(song[:iter])
|
142
|
+
end
|
143
|
+
|
144
|
+
##
|
145
|
+
# Notify success
|
146
|
+
#
|
147
|
+
# @param [Grooveshark::Song] song Song displayed in success page
|
148
|
+
#
|
149
|
+
def notify_success(song)
|
150
|
+
@window.find_by_name('download_success_list')
|
151
|
+
.create_model(song[:iter])
|
152
|
+
@window.find_by_name('download_book')
|
153
|
+
.set_label('SUCCESS', @success += 1)
|
154
|
+
end
|
155
|
+
|
156
|
+
##
|
157
|
+
# Notify erro
|
158
|
+
#
|
159
|
+
# @param [Grooveshark::Song] song Song displayed in failed page
|
160
|
+
#
|
161
|
+
def notify_error(song, e)
|
162
|
+
@window.find_by_name('download_failed_list')
|
163
|
+
.create_model(song[:iter], e.message)
|
164
|
+
@window.find_by_name('download_book')
|
165
|
+
.set_label('FAILED', @failed += 1)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
module GrooveDl
|
3
|
+
# Widgets components
|
4
|
+
module Widgets
|
5
|
+
# Download section
|
6
|
+
module Download
|
7
|
+
# List page
|
8
|
+
module List
|
9
|
+
# Success tree
|
10
|
+
class Success < Gtk::Box
|
11
|
+
attr_reader :store, :data
|
12
|
+
|
13
|
+
COLUMN_PATH,
|
14
|
+
COLUMN_SIZE = *(0..1).to_a
|
15
|
+
|
16
|
+
##
|
17
|
+
# Initialize widgets
|
18
|
+
#
|
19
|
+
# @param [Grooveshark::Client] client Grooveshark client
|
20
|
+
# @param [Gtk::Window] window Gtk app
|
21
|
+
#
|
22
|
+
def load(_client, _window)
|
23
|
+
@data = {}
|
24
|
+
sw = Gtk::ScrolledWindow.new
|
25
|
+
sw.shadow_type = Gtk::ShadowType::ETCHED_IN
|
26
|
+
sw.set_policy(Gtk::PolicyType::AUTOMATIC,
|
27
|
+
Gtk::PolicyType::AUTOMATIC)
|
28
|
+
pack_start(sw, expand: true, fill: true, padding: 0)
|
29
|
+
|
30
|
+
@store = Gtk::ListStore.new(String, String)
|
31
|
+
treeview = Gtk::TreeView.new(@store)
|
32
|
+
treeview.rules_hint = true
|
33
|
+
|
34
|
+
sw.add(treeview)
|
35
|
+
|
36
|
+
add_columns(treeview)
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# Append row in the list store
|
41
|
+
#
|
42
|
+
# @param [Gtk::TreeIter] i Iterable element
|
43
|
+
#
|
44
|
+
def create_model(i)
|
45
|
+
iter = @store.append
|
46
|
+
path = i[COLUMN_PATH]
|
47
|
+
iter[COLUMN_PATH] = path
|
48
|
+
size = (File.size?(path).to_f / (1024 * 1024)).round(2)
|
49
|
+
iter[COLUMN_SIZE] = "#{size} MB"
|
50
|
+
end
|
51
|
+
|
52
|
+
##
|
53
|
+
# Add columns on the treeview element
|
54
|
+
#
|
55
|
+
# @param [Gtk::Treeview] treeview Treeview
|
56
|
+
#
|
57
|
+
def add_columns(treeview)
|
58
|
+
renderer = Gtk::CellRendererText.new
|
59
|
+
column = Gtk::TreeViewColumn.new('Path',
|
60
|
+
renderer,
|
61
|
+
'text' => COLUMN_PATH)
|
62
|
+
column.fixed_width = 650
|
63
|
+
treeview.append_column(column)
|
64
|
+
|
65
|
+
renderer = Gtk::CellRendererText.new
|
66
|
+
column = Gtk::TreeViewColumn.new('Size',
|
67
|
+
renderer,
|
68
|
+
'text' => COLUMN_SIZE)
|
69
|
+
column.fixed_width = 100
|
70
|
+
treeview.append_column(column)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|