groove-dl 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|