ektoplayer 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 (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