soundcloud9000 0.1.0

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