groove-dl 0.2.0 → 0.3.0

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