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.
@@ -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 = 'Download grooveshark songs.'
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.2'
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'
@@ -1,55 +1,28 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  module GrooveDl
3
3
  # Bootstraper for the application
4
- class App < Gtk::Window
5
- def initialize
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
- search_list = Widgets::Search::List.new(:vertical, 6)
16
- search_list.load(client, self)
7
+ def initialize(path)
8
+ super()
17
9
 
18
- download_bar = Widgets::Download::Bar.new(:vertical, 6)
19
- download_bar.load(client, self)
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
- add(box)
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
- init_default
32
- end
19
+ client = Grooveshark::Client.new
20
+ search_list = Widgets::Search.new(client, self)
21
+ Widgets::Download.new(client, self, search_list)
33
22
 
34
- def init_default
35
- signal_connect('destroy') do
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
@@ -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.request('getResultsFromSearch',
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
@@ -7,8 +7,8 @@ module GrooveDl
7
7
  ##
8
8
  # Initialize Displayer
9
9
  #
10
- # @params [Array] result The result from the search
11
- # @params [String] type The search type
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 Nam Author NumSongs) if @type == 'Playlists'
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
- # @params [Terminal::Table] table Table in which row will be added
41
- # @params [Array] result The result from the search
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['playlist_id'],
47
- data['name'],
48
- data['f_name'],
49
- data['num_songs']]) if @type == 'Playlists'
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,
@@ -25,7 +25,7 @@ module GrooveDl
25
25
  ##
26
26
  # Download song
27
27
  #
28
- # @params [String] song_id Song id
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
- # @params [String] playlist_id Playlist id
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
- sprintf('%s/%s/%s/%s.mp3',
100
- output_directory,
101
- song.artist,
102
- song.album,
103
- song.name)
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::List::Queue::COLUMN_PGBAR_VALUE
150
- pgbar_text = Widgets::Download::List::Queue::COLUMN_PGBAR_TEXT
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::List::Queue::COLUMN_PATH]
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
@@ -1,5 +1,5 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  # GrooveDl version
3
3
  module GrooveDl
4
- VERSION = '0.2.0'
4
+ VERSION = '0.3.0'
5
5
  end
@@ -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