soundcloud9000 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.
Files changed (36) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +7 -0
  5. data/README.md +65 -0
  6. data/Rakefile +46 -0
  7. data/bin/soundcloud9000 +9 -0
  8. data/lib/soundcloud9000.rb +22 -0
  9. data/lib/soundcloud9000/application.rb +93 -0
  10. data/lib/soundcloud9000/client.rb +51 -0
  11. data/lib/soundcloud9000/controllers/controller.rb +19 -0
  12. data/lib/soundcloud9000/controllers/player_controller.rb +75 -0
  13. data/lib/soundcloud9000/controllers/track_controller.rb +90 -0
  14. data/lib/soundcloud9000/download_thread.rb +51 -0
  15. data/lib/soundcloud9000/events.rb +17 -0
  16. data/lib/soundcloud9000/models/collection.rb +42 -0
  17. data/lib/soundcloud9000/models/player.rb +138 -0
  18. data/lib/soundcloud9000/models/playlist.rb +22 -0
  19. data/lib/soundcloud9000/models/track.rb +56 -0
  20. data/lib/soundcloud9000/models/track_collection.rb +71 -0
  21. data/lib/soundcloud9000/models/user.rb +26 -0
  22. data/lib/soundcloud9000/time_helper.rb +23 -0
  23. data/lib/soundcloud9000/ui/canvas.rb +21 -0
  24. data/lib/soundcloud9000/ui/color.rb +46 -0
  25. data/lib/soundcloud9000/ui/input.rb +56 -0
  26. data/lib/soundcloud9000/ui/rect.rb +14 -0
  27. data/lib/soundcloud9000/ui/table.rb +132 -0
  28. data/lib/soundcloud9000/ui/view.rb +73 -0
  29. data/lib/soundcloud9000/views/player_view.rb +63 -0
  30. data/lib/soundcloud9000/views/splash.rb +69 -0
  31. data/lib/soundcloud9000/views/tracks_table.rb +14 -0
  32. data/soundcloud9000 +8 -0
  33. data/soundcloud9000.gemspec +27 -0
  34. data/spec/controllers/track_controller_spec.rb +59 -0
  35. data/spec/spec_helper.rb +2 -0
  36. metadata +165 -0
@@ -0,0 +1,21 @@
1
+ require 'curses'
2
+
3
+ require_relative 'color'
4
+
5
+ module Soundcloud9000
6
+ module UI
7
+ # initializes our Curses canvas for drawing on
8
+ class Canvas
9
+ def initialize
10
+ Curses.noecho # do not show typed keys
11
+ Curses.stdscr.keypad(true) # enable arrow keys
12
+ Curses.init_screen
13
+ Color.init
14
+ end
15
+
16
+ def close
17
+ Curses.close_screen
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,46 @@
1
+ require 'curses'
2
+
3
+ module Soundcloud9000
4
+ module UI
5
+ # this class stores our text color configurations
6
+ class Color
7
+ PAIRS = {
8
+ white: 0,
9
+ red: 1,
10
+ blue: 2,
11
+ green: 3,
12
+ cyan: 4
13
+ }
14
+
15
+ DEFINITION = {
16
+ PAIRS[:white] => [Curses::COLOR_WHITE, Curses::COLOR_BLACK],
17
+ PAIRS[:red] => [Curses::COLOR_RED, Curses::COLOR_BLACK],
18
+ PAIRS[:blue] => [Curses::COLOR_BLUE, Curses::COLOR_WHITE],
19
+ PAIRS[:green] => [Curses::COLOR_GREEN, Curses::COLOR_BLACK],
20
+ PAIRS[:cyan] => [Curses::COLOR_BLACK, Curses::COLOR_CYAN]
21
+ }
22
+
23
+ COLORS = {
24
+ white: Curses.color_pair(PAIRS[:white]),
25
+ black: Curses.color_pair(PAIRS[:white]) | Curses::A_REVERSE,
26
+ red: Curses.color_pair(PAIRS[:red]),
27
+ blue: Curses.color_pair(PAIRS[:blue]),
28
+ green: Curses.color_pair(PAIRS[:green]),
29
+ green_reverse: Curses.color_pair(PAIRS[:green]) | Curses::A_REVERSE,
30
+ cyan: Curses.color_pair(PAIRS[:cyan])
31
+ }
32
+
33
+ def self.init
34
+ Curses.start_color
35
+
36
+ DEFINITION.each do |definition, (color, background)|
37
+ Curses.init_pair(definition, color, background)
38
+ end
39
+ end
40
+
41
+ def self.get(name)
42
+ COLORS[name]
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,56 @@
1
+ require 'curses'
2
+ require_relative 'color'
3
+
4
+ module Soundcloud9000
5
+ module UI
6
+ # handles getting input from the user
7
+ class Input
8
+ MAPPING = {
9
+ Curses::KEY_LEFT => :left,
10
+ Curses::KEY_RIGHT => :right,
11
+ Curses::KEY_DOWN => :down,
12
+ Curses::KEY_UP => :up,
13
+ Curses::KEY_CTRL_J => :enter,
14
+ Curses::KEY_ENTER => :enter,
15
+ ' ' => :space,
16
+ 'j' => :j,
17
+ 'k' => :k,
18
+ 's' => :s,
19
+ 'u' => :u,
20
+ '1' => :one,
21
+ '2' => :two,
22
+ '3' => :three,
23
+ '4' => :four,
24
+ '5' => :five,
25
+ '6' => :six,
26
+ '7' => :seven,
27
+ '8' => :eight,
28
+ '9' => :nine,
29
+ 'f' => :f
30
+ }
31
+
32
+ def self.get(delay = 0)
33
+ Curses.timeout = delay
34
+ MAPPING[Curses.getch]
35
+ end
36
+
37
+ def self.getstr(prompt)
38
+ Curses.setpos(Curses.lines - 1, 0)
39
+ Curses.clrtoeol
40
+ Curses.addstr(prompt)
41
+ Curses.echo
42
+ result = Curses.getstr
43
+ Curses.noecho
44
+ Curses.setpos(Curses.lines - 1, 0)
45
+ Curses.addstr(''.ljust(Curses.cols))
46
+ result
47
+ end
48
+
49
+ def self.error(output)
50
+ Curses.setpos(Curses.lines - 1, 0)
51
+ Curses.clrtoeol
52
+ Curses.attron(Color.get(:red)) { Curses.addstr(output) }
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,14 @@
1
+ module Soundcloud9000
2
+ module UI
3
+ class Rect
4
+ attr_reader :x, :y, :width, :height
5
+
6
+ def initialize(x, y, width, height)
7
+ @x = x
8
+ @y = y
9
+ @width = width
10
+ @height = height
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,132 @@
1
+ require_relative 'view'
2
+
3
+ module Soundcloud9000
4
+ module UI
5
+ # responsible for drawing our table of tracks
6
+ class Table < View
7
+ SEPARATOR = ' | '
8
+
9
+ attr_reader :current, :collection
10
+ attr_accessor :header, :keys
11
+
12
+ def initialize(*args)
13
+ super
14
+
15
+ @sizes = []
16
+ @rows = []
17
+ @current = 0
18
+ @top = 0
19
+ @selected = nil
20
+
21
+ reset
22
+ end
23
+
24
+ def bind_to(collection)
25
+ fail ArgumentError if @collection
26
+
27
+ @collection = collection
28
+ @collection.events.on(:append) { render }
29
+ @collection.events.on(:replace) { clear; render }
30
+ end
31
+
32
+ def length
33
+ @collection.size
34
+ end
35
+
36
+ def body_height
37
+ rect.height - @header.size
38
+ end
39
+
40
+ def bottom?
41
+ current + 1 >= length
42
+ end
43
+
44
+ def up
45
+ if @current > 0
46
+ @current -= 1
47
+ @top -= 1 if @current < @top
48
+ render
49
+ end
50
+ end
51
+
52
+ def down
53
+ if (@current + 1) < length
54
+ @current += 1
55
+ @top += 1 if @current > body_height
56
+ render
57
+ end
58
+ end
59
+
60
+ def select
61
+ @selected = @current
62
+ render
63
+ end
64
+
65
+ def deselect
66
+ @selected = nil
67
+ render
68
+ end
69
+
70
+ protected
71
+
72
+ def rows(start = 0, size = collection.size)
73
+ collection[start, size].map do |record|
74
+ keys.map { |key| record.send(key).to_s }
75
+ end
76
+ end
77
+
78
+ def rest_width(elements)
79
+ rect.width - elements.size * SEPARATOR.size -
80
+ elements.inject(0) { |_a, e| + e }
81
+ end
82
+
83
+ def perform_layout
84
+ @sizes = []
85
+ (rows + [header]).each do |row|
86
+ row.each_with_index do |value, index|
87
+ current, max = value.to_s.length, @sizes[index] || 0
88
+ @sizes[index] = current if max < current
89
+ end
90
+ end
91
+
92
+ @sizes[-1] = rest_width(@sizes[0...-1])
93
+ end
94
+
95
+ def draw
96
+ draw_header
97
+ draw_body
98
+ end
99
+
100
+ def draw_header
101
+ with_color(:green_reverse) do
102
+ draw_values(header)
103
+ end
104
+ end
105
+
106
+ def color_for(index)
107
+ if @top + index == @current
108
+ :cyan
109
+ elsif @top + index == @selected
110
+ :black
111
+ else
112
+ :white
113
+ end
114
+ end
115
+
116
+ def draw_body
117
+ rows(@top, body_height + 1).each_with_index do |row, index|
118
+ with_color(color_for(index)) do
119
+ draw_values(row)
120
+ end
121
+ end
122
+ end
123
+
124
+ def draw_values(values)
125
+ i = -1
126
+ content = values.map { |value| value.ljust(@sizes[i += 1]) }.join(SEPARATOR)
127
+
128
+ line content
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,73 @@
1
+ require 'curses'
2
+
3
+ require_relative 'color'
4
+
5
+ module Soundcloud9000
6
+ module UI
7
+ # class responsible for helping keep our app tidy and populated
8
+ class View
9
+ ROW_SEPARATOR = ?|
10
+ LINE_SEPARATOR = ?-
11
+ INTERSECTION = ?+
12
+
13
+ attr_reader :rect
14
+
15
+ def initialize(rect)
16
+ @rect = rect
17
+ @window = Curses::Window.new(rect.height, rect.width, rect.y, rect.x)
18
+ @line = 0
19
+ @padding = 0
20
+ end
21
+
22
+ def padding(value = nil)
23
+ value.nil? ? @padding : @padding = value
24
+ end
25
+
26
+ def render
27
+ perform_layout
28
+ reset
29
+ draw
30
+ refresh
31
+ end
32
+
33
+ def body_width
34
+ rect.width - 2 * padding
35
+ end
36
+
37
+ def with_color(name, &block)
38
+ @window.attron(Color.get(name), &block)
39
+ end
40
+
41
+ def clear
42
+ @window.clear
43
+ end
44
+
45
+ protected
46
+
47
+ def lines_left
48
+ rect.height - @line - 1
49
+ end
50
+
51
+ def line(content)
52
+ @window.setpos(@line, padding)
53
+ @window.addstr(content.ljust(body_width).slice(0, body_width))
54
+ @line += 1
55
+ end
56
+
57
+ def reset
58
+ @line = 0
59
+ end
60
+
61
+ def refresh
62
+ @window.refresh
63
+ end
64
+
65
+ def perform_layout
66
+ end
67
+
68
+ def draw
69
+ fail NotImplementedError
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,63 @@
1
+ require_relative '../time_helper'
2
+ require_relative '../ui/view'
3
+
4
+ module Soundcloud9000
5
+ module Views
6
+ # draws and manages the top section of sc9000, the player
7
+ class PlayerView < UI::View
8
+ attr_accessor :player
9
+
10
+ def initialize(*attrs)
11
+ super
12
+
13
+ @spectrum = true
14
+ padding 2
15
+ end
16
+
17
+ def toggle_spectrum
18
+ @spectrum = !@spectrum
19
+ end
20
+
21
+ protected
22
+
23
+ def draw
24
+ line progress + download_progress
25
+ with_color(:green) do
26
+ line((duration + ' - ' + status).ljust(16) + @player.title)
27
+ end
28
+ line track_info
29
+ line '>' * (@player.level.to_f * body_width).ceil
30
+ end
31
+
32
+ def status
33
+ @player.playing? ? 'playing' : 'paused'
34
+ end
35
+
36
+ def progress
37
+ '#' * (@player.play_progress * body_width).ceil
38
+ end
39
+
40
+ def download_progress
41
+ progress = @player.download_progress - @player.play_progress
42
+
43
+ if progress > 0
44
+ '.' * (progress * body_width).ceil
45
+ else
46
+ ''
47
+ end
48
+ end
49
+
50
+ def track
51
+ @player.track
52
+ end
53
+
54
+ def track_info
55
+ "#{track.plays} Plays | #{track.likes} Likes | #{track.comments} Comments | #{track.url}"
56
+ end
57
+
58
+ def duration
59
+ TimeHelper.duration(@player.seconds_played.to_i * 1000)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,69 @@
1
+ require_relative '../ui/view'
2
+
3
+ module Soundcloud9000
4
+ module Views
5
+ # is responsible for drawing the wonderful splash screen
6
+ class Splash < UI::View
7
+ CONTENT = %q{
8
+ ohmmNNmmdyoo.
9
+ dhMMMMMMMMMMMMMMMms-
10
+ :m .MMMMMMMMMMMMMMMMMMMMd-
11
+ .o`d: o++./dMy .MMMMMMMMMMMMMMMMMMMMMMd-
12
+ /y/M.Mo MmdhMoMy -MMMMMMMMMMMMMMMMMMMMMMMN-
13
+ . +N+M-Ms MNddMoMh /:MMMMMMMMMMMMMMMMMMMMMMMMN-
14
+ hy yMoM:My MMddMsMd :oMMMMMMMMMMMMMMMMMMMMMMMMMMd
15
+ _ dh hMsM/Mh .MMdmMyMd /oMMMMMMMMMMMMMMMMMMMMMMMMMMMMMdhoo
16
+ . m N h:Nm dMyM+Md DMMdNMyMm /MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMms.
17
+ .m M..M NoMN mMhMsMm dMMdMMhMN +MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMN/
18
+ d/MM M/MM /oMhMM /MMdMyMN MMMdMMhMN /MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM-
19
+ :MsMM MsMM +MMNMM +MMmMhMM MMMdMMdMM +MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMy
20
+ /MyMM MyMM +dMNMM dMMmMyMN dMMdMMdMM +oMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMs
21
+ N+MM M+MM \+MdMM \NMhMoMm +MMdNMyMm \/MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMm.
22
+ \+-N M-.M MsNm dMsM/Mh \MMdmMsMd :MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMh.
23
+ + N M m+dy +N+M-Ms MmdhMoMy .MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMNy:
24
+ `:- -/.+ +. +: -+.o- +/+oooooooooooooooooooooooooooo+/-
25
+
26
+
27
+ _ _ _ ___ ___ ___ ___
28
+ | | | | | |/ _ \ / _ \ / _ \ / _ \
29
+ ___ ___ _ _ _ __ __| | ___| | ___ _ _ __| | (_) | | | | | | | | | |
30
+ / __|/ _ \| | | | '_ \ / _` |/ __| |/ _ \| | | |/ _` |\__, | | | | | | | | | |
31
+ \__ \ (_) | |_| | | | | (_| | (__| | (_) | |_| | (_| | / /| |_| | |_| | |_| |
32
+ |___/\___/ \__,_|_| |_|\__,_|\___|_|\___/ \__,_|\__,_| /_/ \___/ \___/ \___/
33
+
34
+ Originally Developed by Matthias Georgi and Tobias Schmidt
35
+ Currently Maintained by Sumanth Ratna
36
+ }
37
+
38
+ protected
39
+
40
+ def left
41
+ (rect.width - lines.map(&:length).max) / 2
42
+ end
43
+
44
+ def top
45
+ (rect.height - lines.size) / 2
46
+ end
47
+
48
+ def lines
49
+ CONTENT.split("\n")
50
+ end
51
+
52
+ def draw
53
+ 0.upto(top) { line '' }
54
+ lines.each do |row|
55
+ with_color(:green) do
56
+ line ' ' * left + row
57
+ end
58
+ end
59
+ end
60
+
61
+ def refresh
62
+ super
63
+
64
+ # show until any keypress
65
+ @window.getch
66
+ end
67
+ end
68
+ end
69
+ end