ektoplayer 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +49 -0
- data/bin/ektoplayer +7 -0
- data/lib/ektoplayer.rb +10 -0
- data/lib/ektoplayer/application.rb +148 -0
- data/lib/ektoplayer/bindings.rb +230 -0
- data/lib/ektoplayer/browsepage.rb +138 -0
- data/lib/ektoplayer/client.rb +18 -0
- data/lib/ektoplayer/common.rb +91 -0
- data/lib/ektoplayer/config.rb +247 -0
- data/lib/ektoplayer/controllers/browser.rb +47 -0
- data/lib/ektoplayer/controllers/controller.rb +9 -0
- data/lib/ektoplayer/controllers/help.rb +21 -0
- data/lib/ektoplayer/controllers/info.rb +22 -0
- data/lib/ektoplayer/controllers/mainwindow.rb +40 -0
- data/lib/ektoplayer/controllers/playlist.rb +60 -0
- data/lib/ektoplayer/database.rb +199 -0
- data/lib/ektoplayer/events.rb +56 -0
- data/lib/ektoplayer/models/browser.rb +127 -0
- data/lib/ektoplayer/models/database.rb +49 -0
- data/lib/ektoplayer/models/model.rb +15 -0
- data/lib/ektoplayer/models/player.rb +28 -0
- data/lib/ektoplayer/models/playlist.rb +72 -0
- data/lib/ektoplayer/models/search.rb +42 -0
- data/lib/ektoplayer/models/trackloader.rb +17 -0
- data/lib/ektoplayer/mp3player.rb +151 -0
- data/lib/ektoplayer/operations/browser.rb +19 -0
- data/lib/ektoplayer/operations/operations.rb +26 -0
- data/lib/ektoplayer/operations/player.rb +11 -0
- data/lib/ektoplayer/operations/playlist.rb +67 -0
- data/lib/ektoplayer/theme.rb +102 -0
- data/lib/ektoplayer/trackloader.rb +146 -0
- data/lib/ektoplayer/ui.rb +404 -0
- data/lib/ektoplayer/ui/colors.rb +105 -0
- data/lib/ektoplayer/ui/widgets.rb +195 -0
- data/lib/ektoplayer/ui/widgets/container.rb +125 -0
- data/lib/ektoplayer/ui/widgets/labelwidget.rb +43 -0
- data/lib/ektoplayer/ui/widgets/listwidget.rb +332 -0
- data/lib/ektoplayer/ui/widgets/tabbedcontainer.rb +110 -0
- data/lib/ektoplayer/updater.rb +77 -0
- data/lib/ektoplayer/views/browser.rb +25 -0
- data/lib/ektoplayer/views/help.rb +46 -0
- data/lib/ektoplayer/views/info.rb +208 -0
- data/lib/ektoplayer/views/mainwindow.rb +64 -0
- data/lib/ektoplayer/views/playinginfo.rb +135 -0
- data/lib/ektoplayer/views/playlist.rb +39 -0
- data/lib/ektoplayer/views/progressbar.rb +51 -0
- data/lib/ektoplayer/views/splash.rb +99 -0
- data/lib/ektoplayer/views/trackrenderer.rb +137 -0
- data/lib/ektoplayer/views/volumemeter.rb +74 -0
- 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,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
|