ektoplayer 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +49 -0
  3. data/bin/ektoplayer +7 -0
  4. data/lib/ektoplayer.rb +10 -0
  5. data/lib/ektoplayer/application.rb +148 -0
  6. data/lib/ektoplayer/bindings.rb +230 -0
  7. data/lib/ektoplayer/browsepage.rb +138 -0
  8. data/lib/ektoplayer/client.rb +18 -0
  9. data/lib/ektoplayer/common.rb +91 -0
  10. data/lib/ektoplayer/config.rb +247 -0
  11. data/lib/ektoplayer/controllers/browser.rb +47 -0
  12. data/lib/ektoplayer/controllers/controller.rb +9 -0
  13. data/lib/ektoplayer/controllers/help.rb +21 -0
  14. data/lib/ektoplayer/controllers/info.rb +22 -0
  15. data/lib/ektoplayer/controllers/mainwindow.rb +40 -0
  16. data/lib/ektoplayer/controllers/playlist.rb +60 -0
  17. data/lib/ektoplayer/database.rb +199 -0
  18. data/lib/ektoplayer/events.rb +56 -0
  19. data/lib/ektoplayer/models/browser.rb +127 -0
  20. data/lib/ektoplayer/models/database.rb +49 -0
  21. data/lib/ektoplayer/models/model.rb +15 -0
  22. data/lib/ektoplayer/models/player.rb +28 -0
  23. data/lib/ektoplayer/models/playlist.rb +72 -0
  24. data/lib/ektoplayer/models/search.rb +42 -0
  25. data/lib/ektoplayer/models/trackloader.rb +17 -0
  26. data/lib/ektoplayer/mp3player.rb +151 -0
  27. data/lib/ektoplayer/operations/browser.rb +19 -0
  28. data/lib/ektoplayer/operations/operations.rb +26 -0
  29. data/lib/ektoplayer/operations/player.rb +11 -0
  30. data/lib/ektoplayer/operations/playlist.rb +67 -0
  31. data/lib/ektoplayer/theme.rb +102 -0
  32. data/lib/ektoplayer/trackloader.rb +146 -0
  33. data/lib/ektoplayer/ui.rb +404 -0
  34. data/lib/ektoplayer/ui/colors.rb +105 -0
  35. data/lib/ektoplayer/ui/widgets.rb +195 -0
  36. data/lib/ektoplayer/ui/widgets/container.rb +125 -0
  37. data/lib/ektoplayer/ui/widgets/labelwidget.rb +43 -0
  38. data/lib/ektoplayer/ui/widgets/listwidget.rb +332 -0
  39. data/lib/ektoplayer/ui/widgets/tabbedcontainer.rb +110 -0
  40. data/lib/ektoplayer/updater.rb +77 -0
  41. data/lib/ektoplayer/views/browser.rb +25 -0
  42. data/lib/ektoplayer/views/help.rb +46 -0
  43. data/lib/ektoplayer/views/info.rb +208 -0
  44. data/lib/ektoplayer/views/mainwindow.rb +64 -0
  45. data/lib/ektoplayer/views/playinginfo.rb +135 -0
  46. data/lib/ektoplayer/views/playlist.rb +39 -0
  47. data/lib/ektoplayer/views/progressbar.rb +51 -0
  48. data/lib/ektoplayer/views/splash.rb +99 -0
  49. data/lib/ektoplayer/views/trackrenderer.rb +137 -0
  50. data/lib/ektoplayer/views/volumemeter.rb +74 -0
  51. metadata +164 -0
@@ -0,0 +1,47 @@
1
+ require 'curses'
2
+
3
+ require_relative 'controller'
4
+
5
+ module Ektoplayer
6
+ module Controllers
7
+ class Browser < Controller
8
+ def initialize(view, browser, view_operations, operations)
9
+ super(view)
10
+ view.attach(browser)
11
+
12
+ register = view_operations.with_register('browser.')
13
+
14
+ %w(up down page_up page_down top bottom
15
+ search_up search_down search_next search_prev).
16
+ each { |op| register.(op, &view.method(op)) }
17
+
18
+ register.(:enter) do
19
+ operations.send(:'browser.enter', view.selected)
20
+ end
21
+
22
+ register.(:add_to_playlist) do
23
+ #if tracks = browser.tracks(view.selected)
24
+ operations.send(:'browser.add_to_playlist', view.selected)
25
+ #end
26
+ end
27
+
28
+ # TODO: mouse?
29
+ view.mouse.on(65536) do view.scroll_up(3) end
30
+ view.mouse.on(2097152) do view.scroll_down(3) end
31
+
32
+ view.mouse.on(Curses::BUTTON1_DOUBLE_CLICKED) do |mevent|
33
+ view.select_from_cursorpos(mevent.y)
34
+ view_operations.send(:'browser.enter')
35
+ end
36
+
37
+ [Curses::BUTTON1_CLICKED, Curses::BUTTON2_CLICKED, Curses::BUTTON3_CLICKED].
38
+ each do |button|
39
+ view.mouse.on(button) do |mevent|
40
+ view.select_from_cursorpos(mevent.y)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+
@@ -0,0 +1,9 @@
1
+ module Ektoplayer
2
+ module Controllers
3
+ class Controller
4
+ def initialize(view)
5
+ @view = view
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,21 @@
1
+ require 'curses'
2
+
3
+ require_relative 'controller'
4
+
5
+ module Ektoplayer
6
+ module Controllers
7
+ class Help < Controller
8
+ def initialize(view, view_operations)
9
+ super(view)
10
+ register = view_operations.with_register('help.')
11
+ %w(up down page_up page_down top bottom).
12
+ each { |op| register.(op, &view.method(op)) }
13
+
14
+ # TODO: mouse?
15
+ view.mouse.on(65536) { view.page_up }
16
+ view.mouse.on(2097152) { view.page_down }
17
+ end
18
+ end
19
+ end
20
+ end
21
+
@@ -0,0 +1,22 @@
1
+ require 'curses'
2
+
3
+ require_relative 'controller'
4
+
5
+ module Ektoplayer
6
+ module Controllers
7
+ class Info < Controller
8
+ def initialize(view, playlist, trackloader, database, view_operations)
9
+ super(view)
10
+ view.attach(playlist, trackloader, database)
11
+ register = view_operations.with_register('info.')
12
+ %w(up down page_up page_down top bottom).
13
+ each { |op| register.(op, &view.method(op)) }
14
+
15
+ # TODO: mouse?
16
+ view.mouse.on(65536) { view.page_up }
17
+ view.mouse.on(2097152) { view.page_down }
18
+ end
19
+ end
20
+ end
21
+ end
22
+
@@ -0,0 +1,40 @@
1
+ require_relative 'controller'
2
+
3
+ module Ektoplayer
4
+ module Controllers
5
+ class MainWindow < Controller
6
+ def initialize(view, view_operations)
7
+ super(view)
8
+ ops = view_operations
9
+ ops.reg('splash.show') { view.tabs.selected=(view.splash) }
10
+ ops.reg('playlist.show') { view.tabs.selected=(view.playlist) }
11
+ ops.reg('browser.show') { view.tabs.selected=(view.browser) }
12
+ ops.reg('info.show') { view.tabs.selected=(view.info) }
13
+ ops.reg('help.show') { view.tabs.selected=(view.help) }
14
+ ops.reg('tabs.next') { view.tabs.next }
15
+ ops.reg('tabs.prev') { view.tabs.prev }
16
+
17
+ ops.reg('playinginfo.toggle') do
18
+ view.with_lock do
19
+ view.playinginfo.visible=(!view.playinginfo.visible?)
20
+ view.want_layout
21
+ end
22
+ end
23
+
24
+ ops.reg('progressbar.toggle') do
25
+ view.with_lock do
26
+ view.progressbar.visible=(!view.progressbar.visible?)
27
+ view.want_layout
28
+ end
29
+ end
30
+
31
+ ops.reg('volumemeter.toggle') do
32
+ view.with_lock do
33
+ view.volumemeter.visible=(!view.volumemeter.visible?)
34
+ view.want_layout
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,60 @@
1
+ require_relative 'controller'
2
+ require_relative '../bindings'
3
+
4
+ module Ektoplayer
5
+ module Controllers
6
+ class Playlist < Controller
7
+ def initialize(view, playlist, view_operations, operations)
8
+ super(view)
9
+ view.attach(playlist)
10
+
11
+ register = view_operations.with_register('playlist.')
12
+ %w(up down page_up page_down top bottom
13
+ search_up search_down search_next search_prev).
14
+ each { |op| register.(op, &@view.method(op)) }
15
+
16
+ register.(:play) do
17
+ operations.send(:'playlist.play', view.selected)
18
+ end
19
+
20
+ register.(:reload) do
21
+ operations.send(:'playlist.reload', view.selected)
22
+ end
23
+
24
+ register.(:download_album) do
25
+ operations.send(:'playlist.download_album', view.selected)
26
+ end
27
+
28
+ register.(:delete) do
29
+ old_cursor, old_selected = view.cursor, view.selected
30
+ operations.send(:'playlist.delete', old_selected)
31
+ view.selected=(old_selected)
32
+ view.force_cursorpos(old_cursor)
33
+ end
34
+
35
+ register.(:goto_current) do
36
+ if index = playlist.current_playing
37
+ view.selected=(index)
38
+ view.center()
39
+ end
40
+ end
41
+
42
+ # TODO: mouse?
43
+ view.mouse.on(65536) do view.scroll_up(3) end
44
+ view.mouse.on(2097152) do view.scroll_down(3) end
45
+
46
+ [Curses::BUTTON1_CLICKED, Curses::BUTTON2_CLICKED, Curses::BUTTON3_CLICKED].
47
+ each do |button|
48
+ view.mouse.on(button) do |mevent|
49
+ view.select_from_cursorpos(mevent.y)
50
+ end
51
+ end
52
+
53
+ view.mouse.on(Curses::BUTTON1_DOUBLE_CLICKED) do |mevent|
54
+ view.select_from_cursorpos(mevent.y)
55
+ view_operations.send('playlist.play')
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,199 @@
1
+ require 'sqlite3'
2
+ require_relative 'events'
3
+
4
+ module Ektoplayer
5
+ class Database
6
+ SELECT_ARCHIVES = %q[
7
+ SELECT archive_url, archive_type
8
+ FROM archive_urls
9
+ JOIN tracks AS t ON t.album_url = archive_urls.album_url
10
+ WHERE t.url = ?
11
+ ].squeeze(' ').freeze
12
+
13
+ SELECT = %q[
14
+ SELECT
15
+ %{SELECT_COLUMNS}
16
+ FROM (
17
+ SELECT DISTINCT
18
+ t.url AS url,
19
+ t.album_url AS album_url,
20
+ t.title AS title,
21
+ t.artist AS artist,
22
+ t.remix AS remix,
23
+ t.number AS number,
24
+ t.bpm AS bpm,
25
+
26
+ a.artist AS album_artist,
27
+ a.title AS album,
28
+ a.released_by AS released_by,
29
+ a.released_by_url AS released_by_url,
30
+ a.posted_by AS posted_by,
31
+ a.posted_by_url AS posted_by_url,
32
+ a.cover_url AS cover_url,
33
+ a.category AS category,
34
+ a.description AS description,
35
+ a.date AS date,
36
+ a.rating AS rating,
37
+ a.votes AS votes,
38
+ a.download_count AS download_count,
39
+
40
+ a_s.style AS style,
41
+
42
+ (
43
+ SELECT GROUP_CONCAT(style)
44
+ FROM albums_styles
45
+ WHERE albums_styles.album_url = t.album_url
46
+ ) AS styles
47
+ FROM
48
+ tracks AS t
49
+
50
+ JOIN albums AS a ON a.url = t.album_url
51
+ JOIN albums_styles AS a_s ON a.url = a_s.album_url
52
+ )
53
+
54
+ WHERE 1 %{WHERE}
55
+ GROUP BY %{GROUP_BY}
56
+ ORDER BY %{ORDER_BY}
57
+ %{LIMIT} ].squeeze(' ').freeze
58
+
59
+ attr_reader :db, :events
60
+
61
+ def initialize(db_file)
62
+ @db = SQLite3::Database.new db_file
63
+ @db.results_as_hash = true
64
+ @events = Events.new(:changed)
65
+ create_tables
66
+ end
67
+
68
+ private def create_tables
69
+ @db.execute_batch %q{
70
+ CREATE TABLE IF NOT EXISTS albums (
71
+ url TEXT NOT NULL,
72
+ title TEXT NOT NULL,
73
+ artist TEXT,
74
+ released_by TEXT,
75
+ released_by_url TEXT,
76
+ posted_by TEXT,
77
+ posted_by_url TEXT,
78
+ cover_url TEXT,
79
+ category TEXT,
80
+ description TEXT,
81
+ date DATE,
82
+ rating FLOAT NOT NULL DEFAULT -1,
83
+ votes INT NOT NULL DEFAULT 0,
84
+ download_count INT NOT NULL DEFAULT 0,
85
+ PRIMARY KEY (url)
86
+ );
87
+ CREATE TABLE IF NOT EXISTS tracks (
88
+ url TEXT NOT NULL,
89
+ album_url TEXT NOT NULL REFERENCES albums(url),
90
+ title TEXT NOT NULL,
91
+ artist TEXT NOT NULL,
92
+ remix TEXT,
93
+ number INT NOT NULL,
94
+ bpm INT,
95
+ PRIMARY KEY (url)
96
+ );
97
+ CREATE TABLE IF NOT EXISTS styles (
98
+ style TEXT NOT NULL,
99
+ url TEXT NOT NULL,
100
+ PRIMARY KEY (style)
101
+ );
102
+ CREATE TABLE IF NOT EXISTS archive_urls (
103
+ album_url TEXT NOT NULL REFERENCES albums(url),
104
+ archive_url TEXT NOT NULL,
105
+ archive_type TEXT NOT NULL,
106
+ PRIMARY KEY (album_url, archive_url)
107
+ );
108
+ CREATE TABLE IF NOT EXISTS albums_styles (
109
+ album_url TEXT NOT NULL REFERENCES albums(url),
110
+ style TEXT NOT NULL REFERENCES styles(style),
111
+ PRIMARY KEY (album_url, style)
112
+ );}
113
+ end
114
+
115
+ private def drop_tables
116
+ %w(albums_styles archive_urls styles tracks albums).
117
+ each { |t| @db.execute("DROP TABLE IF EXISTS #{t}") }
118
+ end
119
+
120
+ def insert_into(table, hash, mode: :insert)
121
+ cols = ?( + (hash.keys * ?,) + ?)
122
+ values = ?( + (([??] * hash.size) * ?,) + ?)
123
+ q = @db.prepare "#{mode} INTO #{table} #{cols} VALUES #{values}"
124
+ q.bind_params(hash.values)
125
+ q.execute
126
+ @events.trigger(:changed)
127
+ rescue
128
+ Application.log(self, hash, $!)
129
+ end
130
+
131
+ def replace_into(table, hash)
132
+ insert_into(table, hash, mode: :replace)
133
+ end
134
+
135
+ def execute(query, params=nil)
136
+ stm = @db.prepare query
137
+ stm.bind_params(*params) if params
138
+ stm.execute.to_a
139
+ rescue
140
+ Application.log(self, $!)
141
+ end
142
+
143
+ def select(
144
+ columns: ?*,
145
+ filters: [],
146
+ group_by: 'url',
147
+ order_by: 'album, number',
148
+ limit: nil
149
+ )
150
+ where_clauses, where_params = [], []
151
+
152
+ filters.each do |filter|
153
+ where_clauses << "AND #{filter[:tag]} #{filter[:operator]} ?"
154
+ where_params << filter[:value]
155
+ end
156
+
157
+ if order_by.is_a?Array
158
+ fail ArgumentError, 'order_by is empty' if order_by.empty?
159
+ order_by = order_by.join(?,)
160
+ else
161
+ fail ArgumentError, 'order_by is empty' if order_by.empty?
162
+ end
163
+
164
+ if group_by.is_a?Array
165
+ fail ArgumentError, 'group_by is empty' if group_by.empty?
166
+ group_by = group_by.join(?,)
167
+ else
168
+ fail ArgumentError, 'group_by is empty' if group_by.empty?
169
+ end
170
+
171
+ limit = "LIMIT #{limit}" if limit
172
+
173
+ stm = @db.prepare SELECT % {
174
+ SELECT_COLUMNS: columns,
175
+ WHERE: where_clauses.join(' '),
176
+ GROUP_BY: group_by,
177
+ ORDER_BY: order_by,
178
+ LIMIT: limit
179
+ }
180
+
181
+ stm.bind_params(*where_params)
182
+ stm.execute.to_a
183
+ rescue
184
+ Application.log(self, $!)
185
+ end
186
+
187
+ def get_archives(url)
188
+ execute(SELECT_ARCHIVES, [url])
189
+ end
190
+
191
+ def track_count
192
+ @db.execute('SELECT COUNT(*) FROM tracks')[0][0]
193
+ end
194
+
195
+ def album_count
196
+ @db.execute('SELECT COUNT(*) FROM albums')[0][0]
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,56 @@
1
+ # Event class
2
+ class Events
3
+ # Create a new events object
4
+ def initialize(*known_events)
5
+ @on_all = []
6
+
7
+ if known_events.empty?
8
+ @map = {}
9
+ auto_create
10
+ else
11
+ @map = known_events.map { |e| [e, []] }.to_h
12
+ no_auto_create
13
+ end
14
+ end
15
+
16
+ # Disables auto creation of non existent events
17
+ def no_auto_create
18
+ @map.default_proc = proc { |h,k| fail KeyError, "Unknown event: #{k}" }
19
+ end
20
+
21
+ # Enables auto creation of non existent events
22
+ def auto_create
23
+ @map.default_proc = proc { |h,k| h[k] = [] }
24
+ end
25
+
26
+ # Registers a new event
27
+ def register(*events)
28
+ events.each { |event| @map[event] = [] }
29
+ end
30
+ alias :reg :register
31
+
32
+ # Register hook for event
33
+ def on(event, &block)
34
+ fail ArgumentError unless block
35
+ @map[event] << block
36
+ end
37
+
38
+ def on_multi(*events, &block)
39
+ events.each { |event| on(event, &block) }
40
+ end
41
+
42
+ # Forward all events to +block(event, *args)+
43
+ def on_all(&block)
44
+ fail ArgumentError unless block
45
+ @on_all << block
46
+ end
47
+
48
+ # Trigger event
49
+ def trigger(event, *args)
50
+ @on_all.each { |callback| callback.(event, *args) }
51
+
52
+ if @map.key?(event)
53
+ @map[event].each { |callback| callback.call(*args) }
54
+ end
55
+ end
56
+ end