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.
@@ -0,0 +1,67 @@
1
+ # -*- coding: utf-8 -*-
2
+ module GrooveDl
3
+ # Widgets components
4
+ module Widgets
5
+ # Search section
6
+ module Search
7
+ # Search 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
+ search_box = Gtk::Box.new(:horizontal, 6)
19
+
20
+ search_bar = Gtk::Entry.new
21
+ search_bar.set_name('search_bar')
22
+ search_bar.text = 'CruciAGoT'
23
+ search_box.pack_start(search_bar,
24
+ expand: true,
25
+ fill: true,
26
+ padding: 10)
27
+
28
+ search_type = Gtk::ComboBoxText.new
29
+ search_type.set_name('search_type')
30
+ search_type.append_text 'Playlists'
31
+ search_type.append_text 'Songs'
32
+ search_type.active = 0
33
+
34
+ search_box.pack_start(search_type,
35
+ expand: false,
36
+ fill: true,
37
+ padding: 5)
38
+
39
+ button = Gtk::Button.new(label: 'Search', stock_id: Gtk::Stock::FIND)
40
+ button.signal_connect('released') do
41
+ @type = search_type.active_text
42
+ @query = search_bar.text
43
+ next if @type.empty? || @query.empty?
44
+ search = client.request('getResultsFromSearch',
45
+ type: @type,
46
+ query: @query)
47
+ results = search['result'].map do |data|
48
+ next Grooveshark::Song.new data if type == 'Songs'
49
+ data
50
+ end if search.key?('result')
51
+
52
+ window.find_by_name('search_list').create_model(results)
53
+ end
54
+
55
+ search_box.pack_start(button,
56
+ expand: false,
57
+ fill: false,
58
+ padding: 10)
59
+
60
+ pack_start(search_box,
61
+ expand: false,
62
+ padding: 10)
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,146 @@
1
+ # -*- coding: utf-8 -*-
2
+ module GrooveDl
3
+ # Widgets components
4
+ module Widgets
5
+ # Search section
6
+ module Search
7
+ # Search list tree
8
+ class List < Gtk::Box
9
+ attr_reader :data
10
+ attr_reader :store
11
+ attr_reader :selection
12
+
13
+ COLUMN_FIXED,
14
+ COLUMN_ID,
15
+ COLUMN_NAME,
16
+ COLUMN_AUTHOR,
17
+ COLUMN_SONG = *(0..5).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
+ set_name('search_list')
27
+
28
+ @selection = {}
29
+ sw = Gtk::ScrolledWindow.new
30
+ sw.shadow_type = Gtk::ShadowType::ETCHED_IN
31
+ sw.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC)
32
+ pack_start(sw, expand: true, fill: true, padding: 0)
33
+
34
+ @store = Gtk::ListStore.new(TrueClass,
35
+ Integer,
36
+ String,
37
+ String,
38
+ String)
39
+ create_model
40
+ treeview = Gtk::TreeView.new(@store)
41
+ treeview.rules_hint = true
42
+ treeview.search_column = COLUMN_SONG
43
+
44
+ sw.add(treeview)
45
+
46
+ add_columns(treeview)
47
+ end
48
+
49
+ ##
50
+ # Create line in the list store
51
+ #
52
+ # @param [Array] data Data stored
53
+ #
54
+ def create_model(data = [])
55
+ @store.clear
56
+ @data = {}
57
+ data.each do |element|
58
+ iter = @store.append
59
+ iter[COLUMN_FIXED] = false
60
+ if element.is_a?(Grooveshark::Song)
61
+ @data[element.id.to_i] = element
62
+ iter[COLUMN_ID] = element.id.to_i
63
+ iter[COLUMN_NAME] = element.name
64
+ iter[COLUMN_AUTHOR] = element.artist
65
+ iter[COLUMN_SONG] = element.album
66
+ else
67
+ @data[element['playlist_id'].to_i] = element
68
+ iter[COLUMN_ID] = element['playlist_id'].to_i
69
+ iter[COLUMN_NAME] = element['name']
70
+ iter[COLUMN_AUTHOR] = element['f_name']
71
+ iter[COLUMN_SONG] = element['num_songs']
72
+ end
73
+ end
74
+ end
75
+
76
+ ##
77
+ # Add columns on the treeview element
78
+ #
79
+ # @param [Gtk::Treeview] treeview Treeview
80
+ #
81
+ def add_columns(treeview)
82
+ renderer = Gtk::CellRendererToggle.new
83
+ renderer.signal_connect('toggled') do |_cell, path|
84
+ fixed_toggled(treeview.model, path)
85
+ end
86
+
87
+ column = Gtk::TreeViewColumn.new('X',
88
+ renderer,
89
+ 'active' => COLUMN_FIXED)
90
+ column.sizing = Gtk::TreeViewColumn::Sizing::FIXED
91
+ column.fixed_width = 30
92
+ treeview.append_column(column)
93
+
94
+ renderer = Gtk::CellRendererText.new
95
+ column = Gtk::TreeViewColumn.new('Id',
96
+ renderer,
97
+ 'text' => COLUMN_ID)
98
+ column.set_sort_column_id(COLUMN_ID)
99
+ column.sizing = Gtk::TreeViewColumn::Sizing::FIXED
100
+ column.fixed_width = 70
101
+ treeview.append_column(column)
102
+
103
+ renderer = Gtk::CellRendererText.new
104
+ column = Gtk::TreeViewColumn.new('Name',
105
+ renderer,
106
+ 'text' => COLUMN_NAME)
107
+ column.set_sort_column_id(COLUMN_NAME)
108
+ column.fixed_width = 400
109
+ treeview.append_column(column)
110
+
111
+ renderer = Gtk::CellRendererText.new
112
+ column = Gtk::TreeViewColumn.new('Author',
113
+ renderer,
114
+ 'text' => COLUMN_AUTHOR)
115
+ column.set_sort_column_id(COLUMN_AUTHOR)
116
+ column.fixed_width = 200
117
+ treeview.append_column(column)
118
+
119
+ renderer = Gtk::CellRendererText.new
120
+ column = Gtk::TreeViewColumn.new('Other data',
121
+ renderer,
122
+ 'text' => COLUMN_SONG)
123
+ column.set_sort_column_id(COLUMN_SONG)
124
+ column.fixed_width = 100
125
+ treeview.append_column(column)
126
+ end
127
+
128
+ ##
129
+ # Fixed toggle button
130
+ #
131
+ # @param [Gtk::ListStore] model List store
132
+ # @param [String] path_str Path to row
133
+ #
134
+ def fixed_toggled(model, path_str)
135
+ path = Gtk::TreePath.new(path_str)
136
+ iter = model.get_iter(path)
137
+ fixed = iter[COLUMN_FIXED]
138
+ fixed ^= 1
139
+ iter[COLUMN_FIXED] = fixed
140
+ @selection[iter[COLUMN_ID]] = @data[iter[COLUMN_ID]] if fixed
141
+ @selection.delete(iter[COLUMN_ID]) unless fixed
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,49 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'spec_helper'
3
+ require 'groove-dl/cli'
4
+ require 'slop'
5
+
6
+ # Groove Dl tests
7
+ module GrooveDl
8
+ # CLI tests
9
+ module CLI
10
+ describe 'CLI' do
11
+ it 'should return options' do
12
+ options = CLI.options
13
+ expect(options).to be_a(::Slop)
14
+ expect(options.config[:strict]).to be_truthy
15
+ expect(options.config[:banner])
16
+ .to eq('Usage: groove-dl [COMMAND] [OPTIONS]')
17
+ expect(options.to_s)
18
+ .to match(/-v, --version(\s+)Shows the current version/)
19
+ expect(options.to_s)
20
+ .to match(/-h, --help(\s+)Display this help message./)
21
+
22
+ version = options.fetch_option(:v)
23
+ expect(version.short).to eq('v')
24
+ expect(version.long).to eq('version')
25
+ expect { version.call }.to output(/Groove-dl v.* on ruby/).to_stdout
26
+ end
27
+
28
+ it 'should retrieve version information' do
29
+ expect(CLI.version_information).to eq(
30
+ "Groove-dl v#{VERSION} on #{RUBY_DESCRIPTION}"
31
+ )
32
+ end
33
+
34
+ it 'should download playlist' do
35
+ allow(Grooveshark::Client).to receive(:new).and_return(true)
36
+ downloader = double
37
+ allow(downloader).to receive(:playlist).with(1).and_return(true)
38
+ allow(Downloader).to receive(:new).and_return(downloader)
39
+ expect(CLI.options.parse %w( -p 1)).to eq(['-p', '1'])
40
+ end
41
+
42
+ it 'should do nothing if v option is passed' do
43
+ expect(CLI.options).to receive(:puts).with(/Groove-dl v.* on ruby/)
44
+ .and_return(nil)
45
+ expect(CLI.options.parse %w( -v)).to eq(['-v'])
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,63 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'spec_helper'
3
+ require 'grooveshark'
4
+ require 'terminal-table'
5
+ require 'fakefs/spec_helpers'
6
+ require 'groove-dl/displayer'
7
+
8
+ # Groove Dl tests
9
+ module GrooveDl
10
+ # Downloader test
11
+ describe 'Displayer' do
12
+ include FakeFS::SpecHelpers
13
+
14
+ before(:each) do
15
+ end
16
+
17
+ it 'should initialize' do
18
+ song = Grooveshark::Song.new('song_id' => 1,
19
+ 'name' => 'test',
20
+ 'artist_name' => 'got')
21
+ displayer = Displayer.new([song], 'Songs')
22
+ expect(displayer.type).to eq('Songs')
23
+ expect(displayer.result.first).to be_a(Grooveshark::Song)
24
+ end
25
+
26
+ it 'should render songs' do
27
+ song = Grooveshark::Song.new('song_id' => 1,
28
+ 'name' => 'test',
29
+ 'artist_name' => 'got')
30
+ displayer = Displayer.new([song], 'Songs')
31
+
32
+ str = '+----+-------+--------+------+
33
+ | Songs |
34
+ +----+-------+--------+------+
35
+ | Id | Album | Artist | Song |
36
+ +----+-------+--------+------+
37
+ | 1 | | got | test |
38
+ +----+-------+--------+------+'
39
+ allow(displayer).to receive(:puts)
40
+ .with(str).and_return(nil)
41
+ expect(displayer.render).to be_nil
42
+ end
43
+
44
+ it 'should render songs' do
45
+ playlist = { 'playlist_id' => 1,
46
+ 'name' => 'Someting',
47
+ 'f_name' => 'GoT',
48
+ 'num_songs' => 1 }
49
+ displayer = Displayer.new([playlist], 'Playlists')
50
+
51
+ str = '+----+----------+--------+----------+
52
+ | Playlists |
53
+ +----+----------+--------+----------+
54
+ | Id | Nam | Author | NumSongs |
55
+ +----+----------+--------+----------+
56
+ | 1 | Someting | GoT | 1 |
57
+ +----+----------+--------+----------+'
58
+ allow(displayer).to receive(:puts)
59
+ .with(str).and_return(nil)
60
+ expect(displayer.render).to be_nil
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,188 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'spec_helper'
3
+ require 'grooveshark'
4
+ require 'ruby-progressbar'
5
+ require 'fakefs/spec_helpers'
6
+ require 'groove-dl/downloader'
7
+ require 'groove-dl/errors'
8
+ require 'slop'
9
+
10
+ # Groove Dl tests
11
+ module GrooveDl
12
+ # Downloader test
13
+ describe 'Downloader' do
14
+ include FakeFS::SpecHelpers
15
+
16
+ before(:each) do
17
+ @client = double
18
+ @downloader = Downloader.new(@client, {})
19
+ end
20
+
21
+ it 'should do nothing if playlist not found' do
22
+ allow(@client).to receive(:request).and_return({})
23
+ expect(@downloader.playlist(1)).to be_falsy
24
+ end
25
+
26
+ it 'should do nothing if queue is empty' do
27
+ expect(@downloader.download_queue).to be_falsy
28
+ end
29
+
30
+ it 'should download playlist' do
31
+ allow(@client).to receive(:request)
32
+ .and_return('songs' => [{ 'song_id' => 1,
33
+ 'name' => 'test',
34
+ 'artist_name' => 'got',
35
+ 'album_name' => 'ruby' }])
36
+ allow(@client).to receive(:get_stream_auth_by_songid)
37
+ .with(1).and_return({})
38
+ allow(@client).to receive(:get_song_url_by_id)
39
+ .with(1).and_return('http://test/stream?key=something')
40
+
41
+ allow(RestClient::Request).to receive(:execute)
42
+ .and_return(true)
43
+
44
+ expect(@downloader.playlist('1'))
45
+ .to eq(skipped: 0, downloaded: 0)
46
+ end
47
+
48
+ it 'should download song' do
49
+ allow(@client).to receive(:get_stream_auth_by_songid)
50
+ .with(1).and_return({})
51
+ allow(@client).to receive(:get_song_url_by_id)
52
+ .with(1).and_return('http://test/stream?key=something')
53
+
54
+ allow(RestClient::Request).to receive(:execute)
55
+ .and_return(true)
56
+
57
+ expect(@downloader.song(1))
58
+ .to eq(skipped: 0, downloaded: 0)
59
+ end
60
+
61
+ it 'should process response in cli mode' do
62
+ Dir.mkdir('/tmp')
63
+ pbar = double
64
+ allow(pbar).to receive(:progress).and_return(pbar)
65
+ allow(pbar).to receive(:+).and_return(0)
66
+ allow(pbar).to receive(:progress=).and_return(0)
67
+ allow(pbar).to receive(:finish).and_return(true)
68
+ allow(ProgressBar).to receive(:create)
69
+ .with(title: 'got-test.mp3', format: '%a |%b>>%i| %p%% %t', total: 1)
70
+ .and_return(pbar)
71
+ response = double
72
+ allow(response).to receive(:[])
73
+ .with('content-length').and_return('1')
74
+ allow(response).to receive(:read_body)
75
+ .and_yield('something')
76
+ .and_yield('nested')
77
+
78
+ expect(@downloader.process_cli_response('/tmp/got-test.mp3')
79
+ .call(response))
80
+ .to eq(1)
81
+
82
+ expect(File.read('/tmp/got-test.mp3')).to eq('somethingnested')
83
+ end
84
+
85
+ it 'should process response in cli mode and does not download twice' do
86
+ Dir.mkdir('/tmp')
87
+ response = double
88
+ allow(response).to receive(:[])
89
+ .with('content-length').and_return('15')
90
+ allow(response).to receive(:read_body)
91
+ .and_yield('something')
92
+ .and_yield('nested')
93
+
94
+ File.open('/tmp/got-test.mp3', 'w') do |f|
95
+ f.write('somethingnested')
96
+ end
97
+
98
+ expect do
99
+ @downloader.process_cli_response('/tmp/got-test.mp3')
100
+ .call(response)
101
+ end.to raise_error(Errors::AlreadyDownloaded,
102
+ '/tmp/got-test.mp3 already downloaded')
103
+ end
104
+
105
+ it 'should process response in gui mode' do
106
+ Dir.mkdir('/tmp')
107
+ stub_const('Widgets::Download::List::Queue::COLUMN_PATH', 0)
108
+ stub_const('Widgets::Download::List::Queue::COLUMN_PGBAR_VALUE', 1)
109
+ stub_const('Widgets::Download::List::Queue::COLUMN_PGBAR_TEXT', 2)
110
+ iter = []
111
+ iter[0] = '/tmp/got-test.mp3'
112
+ response = double
113
+ allow(response).to receive(:[])
114
+ .with('content-length').and_return('15')
115
+ allow(response).to receive(:read_body)
116
+ .and_yield('something')
117
+ .and_yield('nested')
118
+
119
+ expect(@downloader.process_gui_response(iter)
120
+ .call(response))
121
+ .to eq('Complete')
122
+
123
+ expect(iter[1]).to eq(100)
124
+ expect(iter[2]).to eq('Complete')
125
+
126
+ expect(File.read('/tmp/got-test.mp3')).to eq('somethingnested')
127
+ end
128
+
129
+ it 'should process response in gui mode and does not download twice' do
130
+ Dir.mkdir('/tmp')
131
+ stub_const('Widgets::Download::List::Queue::COLUMN_PATH', 0)
132
+ stub_const('Widgets::Download::List::Queue::COLUMN_PGBAR_VALUE', 1)
133
+ stub_const('Widgets::Download::List::Queue::COLUMN_PGBAR_TEXT', 2)
134
+ iter = []
135
+ iter[0] = '/tmp/got-test.mp3'
136
+ response = double
137
+ allow(response).to receive(:[])
138
+ .with('content-length').and_return('15')
139
+ allow(response).to receive(:read_body)
140
+ .and_yield('something')
141
+ .and_yield('nested')
142
+
143
+ File.open('/tmp/got-test.mp3', 'w') do |f|
144
+ f.write('somethingnested')
145
+ end
146
+
147
+ expect do
148
+ @downloader.process_gui_response(iter)
149
+ .call(response)
150
+ end.to raise_error(Errors::AlreadyDownloaded,
151
+ '/tmp/got-test.mp3 already downloaded')
152
+
153
+ expect(iter[1]).to eq(100)
154
+ expect(iter[2]).to eq('Complete')
155
+
156
+ expect(File.read('/tmp/got-test.mp3')).to eq('somethingnested')
157
+ end
158
+
159
+ it 'should download in gui mode' do
160
+ @downloader.type = 'gui'
161
+ Dir.mkdir('/tmp')
162
+ stub_const('Widgets::Download::List::Queue::COLUMN_PATH', 0)
163
+ stub_const('Widgets::Download::List::Queue::COLUMN_PGBAR_VALUE', 1)
164
+ stub_const('Widgets::Download::List::Queue::COLUMN_PGBAR_TEXT', 2)
165
+ iter = []
166
+ iter[0] = '/tmp/got-test.mp3'
167
+
168
+ allow(@client).to receive(:get_stream_auth_by_songid)
169
+ .with(1).and_return({})
170
+ allow(@client).to receive(:get_song_url_by_id)
171
+ .with(1).and_return('http://test/stream?key=something')
172
+
173
+ head_return = double
174
+ allow(head_return).to receive(:headers)
175
+ .and_return(content_length: nil)
176
+
177
+ allow(RestClient).to receive(:head)
178
+ .and_return(head_return)
179
+
180
+ allow(RestClient::Request).to receive(:execute)
181
+ .and_return(true)
182
+
183
+ song = Grooveshark::Song.new('song_id' => 1)
184
+ expect(@downloader.download(song, iter))
185
+ .to eq(true)
186
+ end
187
+ end
188
+ end