groove-dl 0.1.0

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