Remove obsolete Emmental 2.x code
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This commit is contained in:
parent
78ea2904a1
commit
6e4e83cb40
6
Makefile
6
Makefile
|
@ -12,8 +12,6 @@ clean:
|
|||
find . -type f -name "*gresource*" -exec rm {} \+
|
||||
find . -type d -name __pycache__ -exec rm -r {} \+
|
||||
find emmental/mpris2/ -type f -name "*.xml" -exec rm {} \+
|
||||
find data/ -type d -name "Test Album" -exec rm -r {} \+
|
||||
find data/ -type d -name "Test Library" -exec rm -r {} \+
|
||||
|
||||
.PHONY:flake8
|
||||
flake8:
|
||||
|
@ -53,8 +51,8 @@ uninstall:
|
|||
|
||||
.PHONY: pkgbuild
|
||||
pkgbuild:
|
||||
$(eval MAJOR := $(shell grep \^MAJOR lib/version.py | awk -F= '{ gsub(/ /,""); print $$2}'))
|
||||
$(eval MINOR := $(shell grep \^MINOR lib/version.py | awk -F= '{ gsub(/ /,""); print $$2}'))
|
||||
$(eval MAJOR := $(shell grep \^MAJOR_VERSION emmental/__init__.py | awk -F= '{ gsub(/ /,""); print $$2}'))
|
||||
$(eval MINOR := $(shell grep \^MINOR_VERSION emmental/__init__.py | awk -F= '{ gsub(/ /,""); print $$2}'))
|
||||
$(eval TAG := $(shell git describe --tags --abbrev=0))
|
||||
$(eval CSUM := $(shell git archive --format tar.gz $(TAG) | sha256sum | awk '{print $$1}'))
|
||||
cp data/PKGBUILD aur/
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
from gi.repository import Gtk
|
||||
from . import artwork
|
||||
from . import controls
|
||||
from . import nowplaying
|
||||
from . import player
|
||||
from . import scale
|
||||
|
||||
Player = player.Player()
|
||||
|
||||
def Artwork(): return artwork.Artwork(Player)
|
||||
|
||||
class Header(Gtk.HeaderBar):
|
||||
def __init__(self):
|
||||
Gtk.HeaderBar.__init__(self)
|
||||
self.pack_start(controls.AudioControls(Player, Player.Autopause))
|
||||
self.pack_end(scale.ScaleButtonBox(scale.SeekScale(Player)))
|
||||
self.set_title_widget(nowplaying.NowPlaying(Player))
|
||||
|
||||
|
||||
def play_track(track):
|
||||
if track == Player.track:
|
||||
return False
|
||||
Player.play_track(track)
|
||||
return True
|
|
@ -1,46 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
from gi.repository import Gtk, GdkPixbuf, Gst
|
||||
|
||||
class Artwork(Gtk.AspectFrame):
|
||||
def __init__(self, player):
|
||||
Gtk.AspectFrame.__init__(self)
|
||||
self.picture = Gtk.Picture()
|
||||
self.frame = Gtk.Frame()
|
||||
self.frame.set_child(self.picture)
|
||||
|
||||
self.set_child(self.frame)
|
||||
self.set_obey_child(False)
|
||||
self.set_margin_start(5)
|
||||
self.set_margin_end(5)
|
||||
self.set_margin_top(5)
|
||||
self.set_margin_bottom(5)
|
||||
self.set_ratio(1.0)
|
||||
|
||||
self.player = player
|
||||
self.player.connect("artwork", self.on_artwork)
|
||||
self.player.connect("track-changed", self.on_track_changed)
|
||||
self.on_track_changed(player, None, player.track)
|
||||
|
||||
def __set_from_cover_jpg__(self, track):
|
||||
cover = track.path.parent / "cover.jpg"
|
||||
if cover.exists():
|
||||
self.picture.set_filename(str(cover))
|
||||
return True
|
||||
return False
|
||||
|
||||
def on_artwork(self, player, sample):
|
||||
buffer = sample.get_buffer()
|
||||
(res, map) = buffer.map(Gst.MapFlags.READ)
|
||||
if res:
|
||||
loader = GdkPixbuf.PixbufLoader()
|
||||
loader.write(map.data)
|
||||
self.picture.set_pixbuf(loader.get_pixbuf())
|
||||
loader.close()
|
||||
buffer.unmap(map)
|
||||
|
||||
def on_track_changed(self, player, prev, new):
|
||||
if not (new and self.__set_from_cover_jpg__(new)):
|
||||
display = self.picture.get_display()
|
||||
theme = Gtk.IconTheme.get_for_display(display)
|
||||
icon = theme.lookup_icon("emmental", [ ], 1024, 1, 0, 0)
|
||||
self.picture.set_file(icon.get_file())
|
163
audio/bass.py
163
audio/bass.py
|
@ -1,163 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import gi
|
||||
gi.require_version("Gst", "1.0")
|
||||
|
||||
import lib
|
||||
import sys
|
||||
from gi.repository import GObject
|
||||
from gi.repository import GLib
|
||||
from gi.repository import Gst
|
||||
Gst.init(sys.argv)
|
||||
|
||||
from . import replaygain
|
||||
|
||||
TIMEOUT = 100
|
||||
|
||||
|
||||
class BassPlayer(GObject.GObject):
|
||||
def __init__(self):
|
||||
GObject.GObject.__init__(self)
|
||||
lib.settings.initialize("audio.replaygain", "disabled")
|
||||
lib.settings.initialize("audio.volume", 1.0)
|
||||
|
||||
self.audio = replaygain.ReplayGainSink()
|
||||
self.video = Gst.ElementFactory.make("fakesink")
|
||||
|
||||
self.playbin = Gst.ElementFactory.make("playbin")
|
||||
self.playbin.set_property("audio-sink", self.audio)
|
||||
self.playbin.set_property("video-sink", self.video)
|
||||
self.playbin.set_property("volume", lib.settings.get_float("audio.volume"))
|
||||
self.playbin.set_state(Gst.State.READY)
|
||||
self.set_property("replaygain", lib.settings.get("audio.replaygain"))
|
||||
|
||||
self.bus.add_signal_watch()
|
||||
self.bus.connect("message::eos", self.__eos__)
|
||||
self.bus.connect("message::state-changed", self.state_changed)
|
||||
self.bus.connect("message::stream-start", self.stream_start)
|
||||
self.bus.connect("message::state-changed", self.state_changed)
|
||||
self.bus.connect("message::tag", self.tag)
|
||||
|
||||
self.timeout = None
|
||||
|
||||
def __eos__(self, bus, message):
|
||||
self.emit("eos")
|
||||
|
||||
@GObject.Property
|
||||
def bus(self):
|
||||
return self.playbin.get_bus()
|
||||
|
||||
@GObject.Property
|
||||
def duration(self):
|
||||
(res, dur) = self.playbin.query_duration(Gst.Format.TIME)
|
||||
return dur if res == True else 0
|
||||
|
||||
@GObject.Property
|
||||
def playing(self):
|
||||
(ret, state, pending) = self.playbin.get_state(Gst.CLOCK_TIME_NONE)
|
||||
return state == Gst.State.PLAYING
|
||||
|
||||
@playing.setter
|
||||
def playing(self, playing):
|
||||
state = Gst.State.PLAYING if playing else Gst.State.PAUSED
|
||||
self.playbin.set_state(state)
|
||||
|
||||
@GObject.Property
|
||||
def play_percent(self):
|
||||
if self.playbin.clock == None or self.duration == 0:
|
||||
return 0
|
||||
runtime = self.playbin.clock.get_time() - self.playbin.base_time
|
||||
return runtime / self.duration
|
||||
|
||||
@GObject.Property
|
||||
def position(self):
|
||||
(res, pos) = self.playbin.query_position(Gst.Format.TIME)
|
||||
return pos if res == True else 0
|
||||
|
||||
@position.setter
|
||||
def position(self, pos):
|
||||
self.playbin.seek_simple(Gst.Format.TIME,
|
||||
Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT,
|
||||
pos)
|
||||
|
||||
@GObject.Property
|
||||
def replaygain(self):
|
||||
return self.audio.get_property("mode")
|
||||
|
||||
@replaygain.setter
|
||||
def replaygain(self, mode):
|
||||
lib.settings.set("audio.replaygain", mode)
|
||||
self.audio.set_property("mode", mode)
|
||||
|
||||
@GObject.Property
|
||||
def uri(self):
|
||||
return self.playbin.get_property("uri")
|
||||
|
||||
@uri.setter
|
||||
def uri(self, uri):
|
||||
if uri:
|
||||
self.playbin.set_property("uri", uri)
|
||||
else:
|
||||
self.playbin.set_state(Gst.State.READY)
|
||||
|
||||
@GObject.Property
|
||||
def volume(self):
|
||||
return self.playbin.get_property("volume")
|
||||
|
||||
@volume.setter
|
||||
def volume(self, vol):
|
||||
self.playbin.set_property("volume", vol)
|
||||
lib.settings.set("audio.volume", vol)
|
||||
|
||||
def state_changed(self, bus, message):
|
||||
if message.src == self.playbin:
|
||||
(old, new, pending) = message.parse_state_changed()
|
||||
if new == Gst.State.PLAYING:
|
||||
self.emit("playback-start")
|
||||
else:
|
||||
self.emit("playback-paused")
|
||||
|
||||
def stream_start(self, bus, message):
|
||||
self.emit("duration-changed")
|
||||
|
||||
def tag(self, bus, message):
|
||||
(res, sample) = message.parse_tag().get_sample("image")
|
||||
if res:
|
||||
self.emit("artwork", sample)
|
||||
|
||||
def timeout_function(self):
|
||||
self.emit("position-changed")
|
||||
return GLib.SOURCE_CONTINUE
|
||||
|
||||
@GObject.Signal
|
||||
def about_to_finish(self):
|
||||
pass
|
||||
|
||||
@GObject.Signal(arg_types=(Gst.Sample,))
|
||||
def artwork(self, sample):
|
||||
pass
|
||||
|
||||
@GObject.Signal
|
||||
def duration_changed(self):
|
||||
pass
|
||||
|
||||
@GObject.Signal
|
||||
def eos(self):
|
||||
pass
|
||||
|
||||
@GObject.Signal
|
||||
def playback_start(self):
|
||||
if not self.timeout:
|
||||
self.timeout = GLib.timeout_add(TIMEOUT, self.timeout_function)
|
||||
|
||||
@GObject.Signal
|
||||
def playback_paused(self):
|
||||
if self.timeout:
|
||||
GLib.source_remove(self.timeout)
|
||||
self.timeout = None
|
||||
|
||||
@GObject.Signal
|
||||
def position_changed(self):
|
||||
remaining = self.duration - self.position
|
||||
if remaining < 2 * Gst.SECOND:
|
||||
if remaining + (TIMEOUT * Gst.MSECOND) >= 2 * Gst.SECOND:
|
||||
self.emit("about-to-finish")
|
|
@ -1,152 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
from gi.repository import Gtk
|
||||
from . import scale
|
||||
|
||||
class ControlButton(Gtk.Button):
|
||||
def __init__(self, player, icon):
|
||||
Gtk.Button.__init__(self)
|
||||
self.add_css_class("large-icons")
|
||||
self.set_icon_name(icon)
|
||||
self.player = player
|
||||
|
||||
|
||||
class PreviousButton(ControlButton):
|
||||
def __init__(self, player):
|
||||
ControlButton.__init__(self, player, "media-skip-backward")
|
||||
|
||||
def do_clicked(self):
|
||||
self.player.previous()
|
||||
|
||||
|
||||
class NextButton(ControlButton):
|
||||
def __init__(self, player):
|
||||
ControlButton.__init__(self, player, "media-skip-forward")
|
||||
|
||||
def do_clicked(self):
|
||||
self.player.next()
|
||||
|
||||
|
||||
class PlayPauseButton(ControlButton):
|
||||
def __init__(self, player):
|
||||
ControlButton.__init__(self, player, "media-playback-start")
|
||||
self.player.connect("playback-start", self.playback_start)
|
||||
self.player.connect("playback-paused", self.playback_paused)
|
||||
|
||||
def do_clicked(self):
|
||||
self.player.playpause()
|
||||
|
||||
def playback_start(self, player):
|
||||
self.set_icon_name("media-playback-pause")
|
||||
|
||||
def playback_paused(self, player):
|
||||
self.set_icon_name("media-playback-start")
|
||||
|
||||
|
||||
class ControlScaleBox(scale.ScaleButtonBox):
|
||||
def __init__(self, scalectrl):
|
||||
icon = Gtk.Image()
|
||||
icon.add_css_class("large-icons")
|
||||
scalectrl.connect("value-changed", self.on_value_changed, icon)
|
||||
scale.ScaleButtonBox.__init__(self, scalectrl)
|
||||
self.on_value_changed(scalectrl, icon)
|
||||
self.prepend(icon)
|
||||
|
||||
def on_value_changed(self, scale, icon):
|
||||
pass
|
||||
|
||||
|
||||
class AutoPauseControlBox(ControlScaleBox):
|
||||
def __init__(self, apscale):
|
||||
apscale.unparent()
|
||||
ControlScaleBox.__init__(self, apscale)
|
||||
|
||||
def on_value_changed(self, scale, icon):
|
||||
name = "start" if scale.get_value() == -1 else "pause"
|
||||
icon.set_from_icon_name(f"media-playback-{name}")
|
||||
|
||||
|
||||
class VolumeControlBox(ControlScaleBox):
|
||||
def __init__(self, player):
|
||||
ControlScaleBox.__init__(self, scale.VolumeScale(player))
|
||||
|
||||
def on_value_changed(self, scale, icon):
|
||||
value = scale.get_value()
|
||||
if value == 0: name = "muted"
|
||||
elif value <= 1/3: name = "low"
|
||||
elif value <= 2/3: name = "medium"
|
||||
else: name = "high"
|
||||
icon.set_from_icon_name(f"audio-volume-{name}-symbolic")
|
||||
|
||||
|
||||
class ReplayGainComboBox(Gtk.ComboBoxText):
|
||||
def __init__(self, player):
|
||||
Gtk.ComboBoxText.__init__(self)
|
||||
self.modes = [ "disabled", "track", "album" ]
|
||||
self.player = player
|
||||
|
||||
self.append_text("ReplayGain Disabled")
|
||||
self.append_text("ReplayGain Track Mode")
|
||||
self.append_text("ReplayGain Album Mode")
|
||||
self.set_active(self.modes.index(player.replaygain))
|
||||
self.set_can_focus(False)
|
||||
|
||||
def do_changed(self):
|
||||
self.player.set_property("replaygain", self.modes[self.get_active()])
|
||||
|
||||
|
||||
class ReplayGainControl(Gtk.Box):
|
||||
def __init__(self, player):
|
||||
Gtk.Box.__init__(self)
|
||||
self.icon = Gtk.Image.new_from_icon_name("audio-headphones")
|
||||
self.icon.add_css_class("large-icons")
|
||||
self.rgcombo = ReplayGainComboBox(player)
|
||||
|
||||
self.append(self.icon)
|
||||
self.append(self.rgcombo)
|
||||
|
||||
|
||||
class ControlsPopover(Gtk.Popover):
|
||||
def __init__(self, player, apscale):
|
||||
Gtk.Popover.__init__(self)
|
||||
self.box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
|
||||
self.box.append(AutoPauseControlBox(apscale))
|
||||
self.box.append(VolumeControlBox(player))
|
||||
self.box.append(ReplayGainControl(player))
|
||||
self.set_child(self.box)
|
||||
|
||||
|
||||
class MenuIcon(Gtk.Overlay):
|
||||
def __init__(self, apscale):
|
||||
Gtk.Overlay.__init__(self)
|
||||
self.icon = Gtk.Image.new_from_icon_name("pan-down-symbolic")
|
||||
self.icon.set_margin_top(5)
|
||||
|
||||
self.label = Gtk.Label()
|
||||
self.label.set_markup("<small> </small>")
|
||||
self.label.set_yalign(0)
|
||||
|
||||
apscale.connect("value-changed", self.on_value_changed)
|
||||
self.add_overlay(self.icon)
|
||||
self.add_overlay(self.label)
|
||||
|
||||
def on_value_changed(self, scale):
|
||||
value = int(scale.get_value())
|
||||
text = str(value) if value > -1 else " "
|
||||
self.label.set_markup(f"<small>{text}</small>")
|
||||
|
||||
|
||||
class MenuButton(Gtk.MenuButton):
|
||||
def __init__(self, player, apscale):
|
||||
Gtk.MenuButton.__init__(self)
|
||||
self.set_popover(ControlsPopover(player, apscale))
|
||||
self.get_first_child().set_child(MenuIcon(apscale))
|
||||
|
||||
|
||||
class AudioControls(Gtk.Box):
|
||||
def __init__(self, player, apscale):
|
||||
Gtk.Box.__init__(self)
|
||||
self.add_css_class("linked")
|
||||
self.append(PreviousButton(player))
|
||||
self.append(PlayPauseButton(player))
|
||||
self.append(NextButton(player))
|
||||
self.append(MenuButton(player, apscale))
|
|
@ -1,39 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
from gi.repository import GLib
|
||||
from gi.repository import Gtk
|
||||
|
||||
class TrackTitle(Gtk.Label):
|
||||
def __init__(self, player):
|
||||
Gtk.Label.__init__(self)
|
||||
player.connect("track-changed", self.on_track_changed)
|
||||
self.on_track_changed(player, None, player.track)
|
||||
self.add_css_class("title")
|
||||
|
||||
def on_track_changed(self, player, old, new):
|
||||
text = new.title if new else "Emmental"
|
||||
self.set_markup(f"<big>{GLib.markup_escape_text(text)}</big>")
|
||||
|
||||
|
||||
class TrackArtist(Gtk.Label):
|
||||
def __init__(self, player):
|
||||
Gtk.Label.__init__(self)
|
||||
player.connect("track-changed", self.on_track_changed)
|
||||
self.on_track_changed(player, None, player.track)
|
||||
self.add_css_class("subtitle")
|
||||
|
||||
def on_track_changed(self, player, old, new):
|
||||
text = f"by {new.artist.name}" if new else "The Cheesy Music Player"
|
||||
self.set_markup(f"<big>{GLib.markup_escape_text(text)}</big>")
|
||||
|
||||
|
||||
class NowPlaying(Gtk.ScrolledWindow):
|
||||
def __init__(self, player):
|
||||
Gtk.ScrolledWindow.__init__(self)
|
||||
self.box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
|
||||
self.box.append(TrackTitle(player))
|
||||
self.box.append(TrackArtist(player))
|
||||
|
||||
self.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.NEVER)
|
||||
self.set_valign(Gtk.Align.CENTER)
|
||||
self.set_hexpand(True)
|
||||
self.set_child(self.box)
|
|
@ -1,78 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import db
|
||||
import lib
|
||||
from gi.repository import GObject
|
||||
from . import bass
|
||||
from . import scale
|
||||
|
||||
|
||||
class Player(bass.BassPlayer):
|
||||
def __init__(self):
|
||||
bass.BassPlayer.__init__(self)
|
||||
self.Autopause = scale.AutoPauseScale()
|
||||
self.playlist = None
|
||||
self.track = None
|
||||
|
||||
self.set_playlist(db.find_playlist(lib.settings.get_int("audio.plstateid")))
|
||||
self.change_track(db.track.Table.get(lib.settings.get_int("audio.trackid")))
|
||||
|
||||
def change_track(self, track, reset=False, add_prev=True):
|
||||
if self.track and self.play_percent > (2 / 3):
|
||||
self.track.played()
|
||||
if reset:
|
||||
self.uri = None
|
||||
self.emit("track-changed", self.track, track)
|
||||
if track and add_prev:
|
||||
db.user.Table.find("Previous").add_track(track)
|
||||
|
||||
def do_about_to_finish(self):
|
||||
if self.Autopause.get_value() != 0:
|
||||
self.Autopause.decrement()
|
||||
self.change_track(self.playlist.next_track())
|
||||
|
||||
def do_eos(self):
|
||||
self.Autopause.decrement()
|
||||
self.change_track(self.playlist.next_track(), reset=True)
|
||||
self.playing = self.Autopause.keep_playing
|
||||
|
||||
def play(self): self.playing = True
|
||||
def pause(self): self.playing = False
|
||||
def playpause(self): self.playing = not self.playing
|
||||
|
||||
def play_track(self, track, add_prev=True):
|
||||
self.change_track(track, reset=True, add_prev=add_prev)
|
||||
self.play()
|
||||
|
||||
def next(self):
|
||||
if track := db.user.Table.find("Previous").next_track():
|
||||
self.play_track(track, add_prev=False)
|
||||
else:
|
||||
if (track := self.playlist.next_track()) == None:
|
||||
self.set_playlist(db.user.Table.find("Collection"))
|
||||
track = self.playlist.next_track()
|
||||
self.play_track(track)
|
||||
|
||||
def previous(self):
|
||||
if track := db.user.Table.find("Previous").previous_track():
|
||||
self.play_track(track, add_prev=False)
|
||||
|
||||
def set_playlist(self, plist):
|
||||
if plist is None:
|
||||
plist = db.user.Table.find("Collection")
|
||||
if plist != self.playlist:
|
||||
self.emit("playlist-changed", self.playlist, plist)
|
||||
|
||||
@GObject.Signal(arg_types=(db.track.Track, db.track.Track))
|
||||
def track_changed(self, prev, new):
|
||||
self.track = new
|
||||
if self.track:
|
||||
lib.settings.set("audio.trackid", new.rowid)
|
||||
self.uri = new.path.absolute().as_uri()
|
||||
|
||||
@GObject.Signal(arg_types=(db.playlist.Playlist, db.playlist.Playlist))
|
||||
def playlist_changed(self, prev, new):
|
||||
self.playlist = new
|
||||
if self.playlist:
|
||||
if new.current >= new.get_n_tracks() - 1:
|
||||
new.current = -1
|
||||
lib.settings.set("audio.plstateid", new.plstateid)
|
|
@ -1,52 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gst
|
||||
|
||||
|
||||
class ReplayGainSink(Gst.Bin):
|
||||
def __init__(self):
|
||||
Gst.Bin.__init__(self)
|
||||
self.selector = Gst.ElementFactory.make("output-selector")
|
||||
self.funnel = Gst.ElementFactory.make("funnel")
|
||||
self.audiosink = Gst.ElementFactory.make("autoaudiosink")
|
||||
self.rgvolume = Gst.ElementFactory.make("rgvolume")
|
||||
self.rglimiter = Gst.ElementFactory.make("rglimiter")
|
||||
|
||||
for element in [ self.selector, self.funnel, self.audiosink,
|
||||
self.rgvolume, self.rglimiter ]:
|
||||
self.add(element)
|
||||
|
||||
# No ReplayGain: selector -> funnel -> audiosink
|
||||
self.shortcut = self.selector.get_request_pad("src_%u")
|
||||
self.shortcut.link(self.funnel.get_request_pad("sink_%u"))
|
||||
self.funnel.link(self.audiosink)
|
||||
|
||||
# Replaygain: selector -> rgvolume -> rglimiter -> funnel -> audiosink
|
||||
self.replaygain = self.selector.get_request_pad("src_%u")
|
||||
self.replaygain.link(self.rgvolume.get_static_pad("sink"))
|
||||
self.rgvolume.link(self.rglimiter)
|
||||
self.rglimiter.get_static_pad("src").link(
|
||||
self.funnel.get_request_pad("sink_%u"))
|
||||
|
||||
self.selector.set_property("pad-negotiation-mode", 1)
|
||||
self.selector.set_property("active-pad", self.shortcut)
|
||||
|
||||
pad = self.selector.get_static_pad("sink")
|
||||
ghost = Gst.GhostPad.new("sink", pad)
|
||||
ghost.set_active(True)
|
||||
self.add_pad(ghost)
|
||||
|
||||
@GObject.Property
|
||||
def mode(self):
|
||||
if self.selector.get_property("active-pad") == self.shortcut:
|
||||
return "disabled"
|
||||
album_mode = self.rgvolume.get_property("album-mode")
|
||||
return "album" if album_mode else "track"
|
||||
|
||||
@mode.setter
|
||||
def mode(self, mode):
|
||||
if mode == "disabled":
|
||||
self.selector.set_property("active-pad", self.shortcut)
|
||||
else:
|
||||
self.rgvolume.set_property("album-mode", mode == "album")
|
||||
self.selector.set_property("active-pad", self.replaygain)
|
132
audio/scale.py
132
audio/scale.py
|
@ -1,132 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
from gi.repository import GLib
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Gst
|
||||
|
||||
class ScalePlus(Gtk.Scale):
|
||||
def __init__(self, min, max, step, page):
|
||||
Gtk.Scale.__init__(self)
|
||||
self.set_range(min, max)
|
||||
self.set_increments(step, page)
|
||||
self.set_value(min)
|
||||
self.set_draw_value(True)
|
||||
self.set_hexpand(True)
|
||||
self.set_format_value_func(self.format_value)
|
||||
|
||||
def __change_value__(self, n, scroll):
|
||||
value = self.get_value()
|
||||
self.set_value(value + n)
|
||||
new = self.get_value()
|
||||
if value == new:
|
||||
return None
|
||||
self.emit("change-value", scroll, new)
|
||||
return new
|
||||
|
||||
def decrement(self):
|
||||
adjustment = self.get_adjustment()
|
||||
return self.__change_value__(-adjustment.get_step_increment(),
|
||||
Gtk.ScrollType.STEP_BACKWARD)
|
||||
|
||||
def increment(self):
|
||||
adjustment = self.get_adjustment()
|
||||
return self.__change_value__(adjustment.get_step_increment(),
|
||||
Gtk.ScrollType.STEP_FORWARD)
|
||||
|
||||
def format_value(self, scale, value):
|
||||
return str(value)
|
||||
|
||||
|
||||
class SeekScale(ScalePlus):
|
||||
def __init__(self, player):
|
||||
ScalePlus.__init__(self, 0, player.duration,
|
||||
5 * Gst.SECOND, 30 * Gst.SECOND)
|
||||
self.set_size_request(200, -1)
|
||||
self.player = player
|
||||
self.player.connect("duration-changed", self.duration_changed)
|
||||
self.player.connect("position-changed", self.position_changed)
|
||||
|
||||
def do_change_value(self, scroll, value):
|
||||
self.player.position = value
|
||||
|
||||
def duration_changed(self, player):
|
||||
self.set_range(0, player.duration)
|
||||
|
||||
def position_changed(self, player):
|
||||
self.set_value(player.position)
|
||||
|
||||
def format_value(self, scale, value):
|
||||
position = int(value / Gst.SECOND)
|
||||
duration = int(self.get_adjustment().get_upper() / Gst.SECOND)
|
||||
(p_m, p_s) = divmod(position, 60)
|
||||
(r_m, r_s) = divmod(duration - position, 60)
|
||||
return f"{p_m:02}:{p_s:02} / {r_m:02}:{r_s:02}"
|
||||
|
||||
|
||||
class AutoPauseScale(ScalePlus):
|
||||
def __init__(self):
|
||||
ScalePlus.__init__(self, -1, 99, 1, 5)
|
||||
self.keep_playing = True
|
||||
self.set_digits(0)
|
||||
|
||||
def about_to_pause(self):
|
||||
return self.get_value() == 0
|
||||
|
||||
def format_value(self, scale, value):
|
||||
match int(value):
|
||||
case -1: return "Keep Playing"
|
||||
case 0: return "This Track"
|
||||
case 1: return "Next Track"
|
||||
case _: return f"{int(value)} Tracks"
|
||||
|
||||
def decrement(self):
|
||||
self.keep_playing = not self.about_to_pause()
|
||||
super().decrement()
|
||||
|
||||
|
||||
class VolumeScale(ScalePlus):
|
||||
def __init__(self, player):
|
||||
ScalePlus.__init__(self, 0.0, 1.0, 0.05, 0.25)
|
||||
self.player = player
|
||||
self.set_value(player.volume)
|
||||
|
||||
def do_change_value(self, scroll, value):
|
||||
self.set_value(value)
|
||||
self.player.volume = value
|
||||
|
||||
def format_value(self, scale, value):
|
||||
return f"{int(value * 100)}%"
|
||||
|
||||
|
||||
class ScaleButton(Gtk.Button):
|
||||
def __init__(self, scale, icon):
|
||||
Gtk.Button.__init__(self)
|
||||
self.add_css_class("normal-icons")
|
||||
self.add_css_class("flat")
|
||||
self.set_valign(Gtk.Align.END)
|
||||
self.set_icon_name(icon)
|
||||
self.scale = scale
|
||||
|
||||
|
||||
class DecrementButton(ScaleButton):
|
||||
def __init__(self, scale):
|
||||
ScaleButton.__init__(self, scale, "list-remove-symbolic")
|
||||
|
||||
def do_clicked(self):
|
||||
self.scale.decrement()
|
||||
|
||||
|
||||
class IncrementButton(ScaleButton):
|
||||
def __init__(self, scale):
|
||||
ScaleButton.__init__(self, scale, "list-add-symbolic")
|
||||
|
||||
def do_clicked(self):
|
||||
self.scale.increment()
|
||||
|
||||
|
||||
class ScaleButtonBox(Gtk.Box):
|
||||
def __init__(self, scale):
|
||||
Gtk.Box.__init__(self)
|
||||
self.set_orientation(Gtk.Orientation.HORIZONTAL)
|
||||
self.append(DecrementButton(scale))
|
||||
self.append(scale)
|
||||
self.append(IncrementButton(scale))
|
|
@ -1,29 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import unittest
|
||||
from gi.repository import Gtk
|
||||
from . import artwork
|
||||
|
||||
class FakePlayer:
|
||||
def __init__(self):
|
||||
self.track = None
|
||||
def connect(self, name, cb): pass
|
||||
|
||||
class TestAudioArtwork(unittest.TestCase):
|
||||
def test_audio_artwork_init(self):
|
||||
fake = FakePlayer()
|
||||
art = artwork.Artwork(fake)
|
||||
|
||||
self.assertIsInstance(art, Gtk.AspectFrame)
|
||||
self.assertIsInstance(art.frame, Gtk.Frame)
|
||||
self.assertIsInstance(art.picture, Gtk.Picture)
|
||||
|
||||
self.assertEqual(art.player, fake)
|
||||
self.assertEqual(art.get_child(), art.frame)
|
||||
self.assertEqual(art.frame.get_child(), art.picture)
|
||||
self.assertEqual(art.get_obey_child(), False)
|
||||
self.assertEqual(art.get_ratio(), 1.0)
|
||||
|
||||
self.assertEqual(art.get_margin_start(), 5)
|
||||
self.assertEqual(art.get_margin_end(), 5)
|
||||
self.assertEqual(art.get_margin_top(), 5)
|
||||
self.assertEqual(art.get_margin_bottom(), 5)
|
|
@ -1,32 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import audio
|
||||
import db
|
||||
import pathlib
|
||||
import unittest
|
||||
from gi.repository import Gtk
|
||||
|
||||
test_album = pathlib.Path("./data/Test Album/")
|
||||
test_track = test_album / "01 - Test Track.ogg"
|
||||
|
||||
class TestAudio(unittest.TestCase):
|
||||
def test_init(self):
|
||||
self.assertIsInstance(audio.Player, audio.player.Player)
|
||||
|
||||
def test_play_track(self):
|
||||
db.reset()
|
||||
track = db.make_fake_track(1, 10, "Test Track", test_track, test_album)
|
||||
|
||||
self.assertTrue(audio.play_track(track))
|
||||
self.assertTrue(audio.Player.playing)
|
||||
self.assertFalse(audio.play_track(track))
|
||||
audio.Player.playing = False
|
||||
|
||||
def test_header(self):
|
||||
header = audio.Header()
|
||||
self.assertIsInstance(header, Gtk.HeaderBar)
|
||||
self.assertIsInstance(header.get_title_widget(),
|
||||
audio.nowplaying.NowPlaying)
|
||||
|
||||
def test_widgets(self):
|
||||
self.assertIsInstance(audio.Artwork(),
|
||||
audio.artwork.Artwork)
|
|
@ -1,137 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import lib
|
||||
import pathlib
|
||||
import time
|
||||
import unittest
|
||||
from gi.repository import GObject
|
||||
from gi.repository import GLib
|
||||
from gi.repository import Gst
|
||||
from . import bass
|
||||
from . import replaygain
|
||||
|
||||
main_context = GLib.main_context_default()
|
||||
test_album = pathlib.Path("./data/Test Album/")
|
||||
test_track = test_album / "01 - Test Track.ogg"
|
||||
test_uri = test_track.absolute().as_uri()
|
||||
|
||||
class TestBassPlayer(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.duration_changed = None
|
||||
|
||||
def on_duration_changed(self, player): self.duration_changed = player.duration
|
||||
def on_playback_start(self, player): self.playing = True
|
||||
def on_playback_paused(self, player): self.playing = False
|
||||
|
||||
def test_bass_player_init(self):
|
||||
base = bass.BassPlayer()
|
||||
self.assertIsInstance(base, GObject.GObject)
|
||||
self.assertIsInstance(base.audio, replaygain.ReplayGainSink)
|
||||
self.assertIsInstance(base.video, Gst.Element)
|
||||
self.assertIsInstance(base.playbin, Gst.Element)
|
||||
self.assertEqual(base.playbin.get_property("audio-sink"), base.audio)
|
||||
self.assertEqual(base.playbin.get_property("video-sink"), base.video)
|
||||
self.assertEqual(base.playbin.get_state(Gst.CLOCK_TIME_NONE)[1],
|
||||
Gst.State.READY)
|
||||
self.assertIsNone(base.timeout)
|
||||
|
||||
def test_bass_player_bus(self):
|
||||
base = bass.BassPlayer()
|
||||
self.assertIsInstance(base.bus, Gst.Bus)
|
||||
|
||||
def test_bass_player_duration(self):
|
||||
base = bass.BassPlayer()
|
||||
base.connect("duration-changed", self.on_duration_changed)
|
||||
self.assertEqual(base.get_property("duration"), 0)
|
||||
|
||||
base.set_property("uri", test_uri)
|
||||
base.set_property("playing", False)
|
||||
|
||||
iterations = 0
|
||||
while self.duration_changed == None:
|
||||
main_context.iteration(may_block=False)
|
||||
if (iterations := iterations +1) == 100000:
|
||||
break
|
||||
|
||||
self.assertEqual(base.get_property("duration"), 10 * Gst.SECOND)
|
||||
self.assertEqual(self.duration_changed, 10 * Gst.SECOND)
|
||||
|
||||
def test_bass_player_playing(self):
|
||||
base = bass.BassPlayer()
|
||||
base.connect("playback-start", self.on_playback_start)
|
||||
base.connect("playback-paused", self.on_playback_paused)
|
||||
base.set_property("uri", test_uri)
|
||||
self.assertFalse(base.get_property("playing"))
|
||||
|
||||
base.set_property("playing", True)
|
||||
(ret, state, pending) = base.playbin.get_state(Gst.CLOCK_TIME_NONE)
|
||||
self.assertEqual(state, Gst.State.PLAYING)
|
||||
self.assertTrue(base.get_property("playing"))
|
||||
while main_context.iteration(may_block=False): pass
|
||||
self.assertIsNotNone(base.timeout)
|
||||
|
||||
base.set_property("playing", False)
|
||||
(ret, state, pending) = base.playbin.get_state(Gst.CLOCK_TIME_NONE)
|
||||
self.assertEqual(state, Gst.State.PAUSED)
|
||||
self.assertFalse(base.get_property("playing"))
|
||||
while main_context.iteration(may_block=False): pass
|
||||
self.assertIsNone(base.timeout)
|
||||
|
||||
def test_basic_player_position(self):
|
||||
base = bass.BassPlayer()
|
||||
self.assertEqual(base.get_property("position"), 0)
|
||||
|
||||
base.set_property("uri", test_uri)
|
||||
base.set_property("playing", False)
|
||||
time.sleep(0.1)
|
||||
while main_context.iteration(may_block=False): time.sleep(0.005)
|
||||
|
||||
base.set_property("position", 5 * Gst.SECOND)
|
||||
time.sleep(0.2)
|
||||
while main_context.iteration(may_block=False): time.sleep(0.005)
|
||||
self.assertGreater(base.get_property("position"), 0)
|
||||
|
||||
def test_bass_player_replaygain(self):
|
||||
lib.settings.reset()
|
||||
base = bass.BassPlayer()
|
||||
self.assertEqual(lib.settings.get("audio.replaygain"), "disabled")
|
||||
self.assertEqual(base.get_property("replaygain"), "disabled")
|
||||
|
||||
base.set_property("replaygain", "track")
|
||||
self.assertEqual(base.audio.get_property("mode"), "track")
|
||||
self.assertEqual(base.get_property("replaygain"), "track")
|
||||
self.assertEqual(lib.settings.get("audio.replaygain"), "track")
|
||||
|
||||
base.set_property("replaygain", "disabled")
|
||||
self.assertEqual(base.audio.get_property("mode"), "disabled")
|
||||
self.assertEqual(base.get_property("replaygain"), "disabled")
|
||||
self.assertEqual(lib.settings.get("audio.replaygain"), "disabled")
|
||||
|
||||
base.set_property("replaygain", "album")
|
||||
self.assertEqual(base.audio.get_property("mode"), "album")
|
||||
self.assertEqual(base.get_property("replaygain"), "album")
|
||||
self.assertEqual(lib.settings.get("audio.replaygain"), "album")
|
||||
|
||||
def test_bass_player_uri(self):
|
||||
base = bass.BassPlayer()
|
||||
|
||||
self.assertIsNone(base.get_property("uri"))
|
||||
base.set_property("uri", test_uri)
|
||||
self.assertEqual(base.get_property("uri"), test_uri)
|
||||
|
||||
base.playbin.set_state(Gst.State.PAUSED)
|
||||
base.set_property("uri", None)
|
||||
self.assertEqual(base.playbin.get_state(Gst.CLOCK_TIME_NONE)[1],
|
||||
Gst.State.READY)
|
||||
|
||||
def test_bass_player_volume(self):
|
||||
lib.settings.reset()
|
||||
base = bass.BassPlayer()
|
||||
|
||||
self.assertEqual(lib.settings.get_float("audio.volume"), 1.0)
|
||||
self.assertEqual(base.get_property("volume"), 1.0)
|
||||
base.set_property("volume", 0.5)
|
||||
self.assertEqual(base.get_property("volume"), 0.5)
|
||||
self.assertEqual(lib.settings.get_float("audio.volume"), 0.5)
|
||||
|
||||
base2 = bass.BassPlayer()
|
||||
self.assertEqual(base2.get_property("volume"), 0.5)
|
|
@ -1,241 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import unittest
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Gst
|
||||
from . import controls
|
||||
from . import scale
|
||||
|
||||
class FakePlayer(GObject.GObject):
|
||||
def __init__(self):
|
||||
GObject.GObject.__init__(self)
|
||||
self.prev = False
|
||||
self.nxt = False
|
||||
self.play = False
|
||||
self.vol = 1.0
|
||||
self.rgain = "disabled"
|
||||
|
||||
@GObject.Property
|
||||
def playing(self): return self.play
|
||||
@playing.setter
|
||||
def playing(self, newval):
|
||||
self.play = newval
|
||||
self.emit("playback-start" if newval else "playback-paused")
|
||||
|
||||
def previous(self): self.prev = True
|
||||
def next(self): self.nxt = True
|
||||
|
||||
@GObject.Property
|
||||
def volume(self): return self.vol
|
||||
def playpause(self):
|
||||
self.playing = not self.playing
|
||||
|
||||
@GObject.Property
|
||||
def replaygain(self): return self.rgain
|
||||
@replaygain.setter
|
||||
def replaygain(self, newval): self.rgain = newval
|
||||
|
||||
@GObject.Signal
|
||||
def playback_start(self): pass
|
||||
@GObject.Signal
|
||||
def playback_paused(self): pass
|
||||
|
||||
|
||||
class TestControlButton(unittest.TestCase):
|
||||
def test_control_button(self):
|
||||
fake = FakePlayer()
|
||||
ctrl = controls.ControlButton(fake, "missing-icon")
|
||||
self.assertIsInstance(ctrl, Gtk.Button)
|
||||
self.assertTrue(ctrl.has_css_class("large-icons"))
|
||||
self.assertEqual(ctrl.get_icon_name(), "missing-icon")
|
||||
self.assertEqual(ctrl.player, fake)
|
||||
|
||||
|
||||
class TestPreviousButton(unittest.TestCase):
|
||||
def test_previous_button(self):
|
||||
fake = FakePlayer()
|
||||
prev = controls.PreviousButton(fake)
|
||||
|
||||
self.assertIsInstance(prev, controls.ControlButton)
|
||||
self.assertEqual(prev.get_icon_name(), "media-skip-backward")
|
||||
prev.emit("clicked")
|
||||
self.assertTrue(fake.prev)
|
||||
|
||||
|
||||
class TestNextButton(unittest.TestCase):
|
||||
def test_next_button(self):
|
||||
fake = FakePlayer()
|
||||
next = controls.NextButton(fake)
|
||||
|
||||
self.assertIsInstance(next, controls.ControlButton)
|
||||
self.assertEqual(next.get_icon_name(), "media-skip-forward")
|
||||
next.emit("clicked")
|
||||
self.assertTrue(fake.nxt)
|
||||
|
||||
|
||||
class TestPlayPauseButton(unittest.TestCase):
|
||||
def test_play_pause_button(self):
|
||||
fake = FakePlayer()
|
||||
play = controls.PlayPauseButton(fake)
|
||||
|
||||
self.assertIsInstance(play, controls.ControlButton)
|
||||
self.assertEqual(play.get_icon_name(), "media-playback-start")
|
||||
|
||||
play.emit("clicked")
|
||||
self.assertTrue(fake.play)
|
||||
self.assertEqual(play.get_icon_name(), "media-playback-pause")
|
||||
|
||||
play.emit("clicked")
|
||||
self.assertFalse(fake.play)
|
||||
self.assertEqual(play.get_icon_name(), "media-playback-start")
|
||||
|
||||
|
||||
class TestControlScaleBox(unittest.TestCase):
|
||||
def test_control_scale_box(self):
|
||||
splus = scale.ScalePlus(1, 10, 1, 5)
|
||||
ctrlbox = controls.ControlScaleBox(splus)
|
||||
self.assertIsInstance(ctrlbox, scale.ScaleButtonBox)
|
||||
self.assertIsInstance(ctrlbox.get_first_child(), Gtk.Image)
|
||||
self.assertTrue(ctrlbox.get_first_child().has_css_class("large-icons"))
|
||||
|
||||
|
||||
class TestAutoPauseControlBox(unittest.TestCase):
|
||||
def test_autopause_control_box(self):
|
||||
apscale = scale.AutoPauseScale()
|
||||
apbox = controls.AutoPauseControlBox(apscale)
|
||||
icon = apbox.get_first_child()
|
||||
|
||||
self.assertIsInstance(apbox, controls.ControlScaleBox)
|
||||
apscale.set_value(-1)
|
||||
self.assertEqual(icon.get_icon_name(), "media-playback-start")
|
||||
apscale.set_value(0)
|
||||
self.assertEqual(icon.get_icon_name(), "media-playback-pause")
|
||||
|
||||
|
||||
class TestVolumeControlbox(unittest.TestCase):
|
||||
def test_volume_control_box(self):
|
||||
fake = FakePlayer()
|
||||
vcb = controls.VolumeControlBox(fake)
|
||||
icon = vcb.get_first_child()
|
||||
volume = icon.get_next_sibling().scale
|
||||
|
||||
self.assertIsInstance(vcb, controls.ControlScaleBox)
|
||||
self.assertIsInstance(volume, scale.VolumeScale)
|
||||
|
||||
volume.set_value(0)
|
||||
self.assertEqual(icon.get_icon_name(), "audio-volume-muted-symbolic")
|
||||
volume.set_value(0.3)
|
||||
self.assertEqual(icon.get_icon_name(), "audio-volume-low-symbolic")
|
||||
volume.set_value(0.6)
|
||||
self.assertEqual(icon.get_icon_name(), "audio-volume-medium-symbolic")
|
||||
volume.set_value(0.9)
|
||||
self.assertEqual(icon.get_icon_name(), "audio-volume-high-symbolic")
|
||||
|
||||
|
||||
class TestReplayGainComboBox(unittest.TestCase):
|
||||
def test_replay_gain_combobox(self):
|
||||
fake = FakePlayer()
|
||||
combo = controls.ReplayGainComboBox(fake)
|
||||
|
||||
self.assertIsInstance(combo, Gtk.ComboBoxText)
|
||||
self.assertEqual(combo.player, fake)
|
||||
self.assertEqual(combo.modes, [ "disabled", "track", "album" ])
|
||||
self.assertEqual(combo.get_active_text(), "ReplayGain Disabled")
|
||||
|
||||
combo.set_active(1)
|
||||
self.assertEqual(combo.get_active_text(), "ReplayGain Track Mode")
|
||||
self.assertEqual(fake.replaygain, "track")
|
||||
|
||||
combo.set_active(2)
|
||||
self.assertEqual(combo.get_active_text(), "ReplayGain Album Mode")
|
||||
self.assertEqual(fake.replaygain, "album")
|
||||
|
||||
|
||||
class TestReplayGainControl(unittest.TestCase):
|
||||
def test_replay_gain_control(self):
|
||||
fake = FakePlayer()
|
||||
rgc = controls.ReplayGainControl(fake)
|
||||
|
||||
self.assertIsInstance(rgc, Gtk.Box)
|
||||
self.assertIsInstance(rgc.icon, Gtk.Image)
|
||||
self.assertIsInstance(rgc.rgcombo, controls.ReplayGainComboBox)
|
||||
|
||||
self.assertEqual(rgc.get_orientation(), Gtk.Orientation.HORIZONTAL)
|
||||
self.assertEqual(rgc.icon.get_icon_name(), "audio-headphones")
|
||||
self.assertEqual(rgc.get_first_child(), rgc.icon)
|
||||
self.assertEqual(rgc.icon.get_next_sibling(), rgc.rgcombo)
|
||||
self.assertTrue(rgc.icon.has_css_class("large-icons"))
|
||||
|
||||
|
||||
class TestControlsPopover(unittest.TestCase):
|
||||
def test_controls_popover(self):
|
||||
fake = FakePlayer()
|
||||
apscale = scale.AutoPauseScale()
|
||||
pop = controls.ControlsPopover(fake, apscale)
|
||||
|
||||
self.assertIsInstance(pop, Gtk.Popover)
|
||||
self.assertIsInstance(pop.box, Gtk.Box)
|
||||
|
||||
child = pop.box.get_first_child()
|
||||
self.assertIsInstance(child, controls.AutoPauseControlBox)
|
||||
child = child.get_next_sibling()
|
||||
self.assertIsInstance(child, controls.VolumeControlBox)
|
||||
child = child.get_next_sibling()
|
||||
self.assertIsInstance(child, controls.ReplayGainControl)
|
||||
|
||||
self.assertEqual(pop.get_child(), pop.box)
|
||||
self.assertEqual(pop.box.get_orientation(), Gtk.Orientation.VERTICAL)
|
||||
|
||||
|
||||
class TestControlsMenuIcon(unittest.TestCase):
|
||||
def test_controls_menu_icon(self):
|
||||
apscale = scale.AutoPauseScale()
|
||||
icon = controls.MenuIcon(apscale)
|
||||
|
||||
self.assertIsInstance(icon, Gtk.Overlay)
|
||||
self.assertIsInstance(icon.icon, Gtk.Image)
|
||||
self.assertIsInstance(icon.label, Gtk.Label)
|
||||
|
||||
self.assertEqual(icon.icon.get_icon_name(), "pan-down-symbolic")
|
||||
self.assertEqual(icon.icon.get_margin_top(), 5)
|
||||
self.assertEqual(icon.label.get_text(), " ")
|
||||
self.assertEqual(icon.label.get_yalign(), 0)
|
||||
|
||||
apscale.set_value(0)
|
||||
self.assertEqual(icon.label.get_text(), "0")
|
||||
|
||||
self.assertIn(icon.icon, icon)
|
||||
self.assertIn(icon.label, icon)
|
||||
|
||||
|
||||
class TestControlsMenuButton(unittest.TestCase):
|
||||
def test_controls_menu_button(self):
|
||||
fake = FakePlayer()
|
||||
apscale = scale.AutoPauseScale()
|
||||
menu = controls.MenuButton(fake, apscale)
|
||||
|
||||
self.assertIsInstance(menu, Gtk.MenuButton)
|
||||
self.assertIsInstance(menu.get_popover(),
|
||||
controls.ControlsPopover)
|
||||
self.assertIsInstance(menu.get_first_child().get_child(),
|
||||
controls.MenuIcon)
|
||||
|
||||
|
||||
class TestAudioControls(unittest.TestCase):
|
||||
def test_audio_controls(self):
|
||||
fake = FakePlayer()
|
||||
apscale = scale.AutoPauseScale()
|
||||
ctrl = controls.AudioControls(fake, apscale)
|
||||
|
||||
self.assertIsInstance(ctrl, Gtk.Box)
|
||||
self.assertEqual(ctrl.get_orientation(), Gtk.Orientation.HORIZONTAL)
|
||||
self.assertTrue(ctrl.has_css_class("linked"))
|
||||
|
||||
child = ctrl.get_first_child()
|
||||
self.assertIsInstance(child, controls.PreviousButton)
|
||||
child = child.get_next_sibling()
|
||||
self.assertIsInstance(child, controls.PlayPauseButton)
|
||||
child = child.get_next_sibling()
|
||||
self.assertIsInstance(child, controls.NextButton)
|
||||
child = child.get_next_sibling()
|
||||
self.assertIsInstance(child, controls.MenuButton)
|
|
@ -1,70 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import db
|
||||
import unittest
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gtk
|
||||
from . import nowplaying
|
||||
|
||||
class FakeArtist(GObject.GObject):
|
||||
def __init__(self):
|
||||
self.name = "Test Artist"
|
||||
|
||||
class FakeTrack(GObject.GObject):
|
||||
def __init__(self):
|
||||
GObject.GObject.__init__(self)
|
||||
self.title = "Test Title"
|
||||
self.artist = FakeArtist()
|
||||
|
||||
class FakePlayer(GObject.GObject):
|
||||
def __init__(self):
|
||||
GObject.GObject.__init__(self)
|
||||
self.track = None
|
||||
|
||||
@GObject.Signal(arg_types=(FakeTrack,FakeTrack))
|
||||
def track_changed(self, prev, new): pass
|
||||
|
||||
|
||||
class TestAudioTrackTitle(unittest.TestCase):
|
||||
def test_track_title(self):
|
||||
fake = FakePlayer()
|
||||
title = nowplaying.TrackTitle(fake)
|
||||
|
||||
self.assertIsInstance(title, Gtk.Label)
|
||||
self.assertTrue(title.has_css_class("title"))
|
||||
self.assertEqual(title.get_text(), "Emmental")
|
||||
|
||||
fake.emit("track-changed", None, FakeTrack())
|
||||
self.assertEqual(title.get_text(), "Test Title")
|
||||
|
||||
|
||||
class TestAudioTrackArtist(unittest.TestCase):
|
||||
def test_track_artist(self):
|
||||
fake = FakePlayer()
|
||||
artist = nowplaying.TrackArtist(fake)
|
||||
|
||||
self.assertIsInstance(artist, Gtk.Label)
|
||||
self.assertTrue(artist.has_css_class("subtitle"))
|
||||
self.assertEqual(artist.get_text(), "The Cheesy Music Player")
|
||||
|
||||
fake.emit("track-changed", None, FakeTrack())
|
||||
self.assertEqual(artist.get_text(), "by Test Artist")
|
||||
|
||||
|
||||
class TestNowPlaying(unittest.TestCase):
|
||||
def test_now_playing(self):
|
||||
fake = FakePlayer()
|
||||
now = nowplaying.NowPlaying(fake)
|
||||
child = now.box.get_first_child()
|
||||
viewport = now.get_child()
|
||||
|
||||
self.assertIsInstance(now, Gtk.ScrolledWindow)
|
||||
self.assertIsInstance(now.box, Gtk.Box)
|
||||
self.assertIsInstance(child, nowplaying.TrackTitle)
|
||||
self.assertIsInstance(child.get_next_sibling(), nowplaying.TrackArtist)
|
||||
|
||||
self.assertEqual(viewport.get_child(), now.box)
|
||||
self.assertEqual(now.get_valign(), Gtk.Align.CENTER)
|
||||
self.assertEqual(now.get_policy(), (Gtk.PolicyType.AUTOMATIC,
|
||||
Gtk.PolicyType.NEVER))
|
||||
|
||||
self.assertTrue(now.get_hexpand())
|
|
@ -1,146 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import db
|
||||
import lib
|
||||
import pathlib
|
||||
import scanner
|
||||
import unittest
|
||||
from gi.repository import GLib
|
||||
from gi.repository import Gst
|
||||
from . import bass
|
||||
from . import player
|
||||
from . import scale
|
||||
|
||||
main_context = GLib.main_context_default()
|
||||
test_album = pathlib.Path("./data/Test Album/")
|
||||
test_track = test_album / "01 - Test Track.ogg"
|
||||
test_uri = test_track.absolute().as_uri()
|
||||
|
||||
|
||||
class TestPlayer(unittest.TestCase):
|
||||
def setUpClass():
|
||||
db.reset()
|
||||
lib = db.library.Table.find(test_album)
|
||||
scanner.Queue.push(scanner.task.DirectoryTask(lib, test_album))
|
||||
while scanner.Queue.run() == GLib.SOURCE_CONTINUE: pass
|
||||
|
||||
def setUp(self):
|
||||
self.changed = None
|
||||
lib.settings.reset()
|
||||
self.library = db.library.Table.lookup(test_album)
|
||||
self.track = db.track.Table.lookup(test_track)
|
||||
|
||||
def on_track_changed(self, player, prev, new):
|
||||
self.changed = (prev, new)
|
||||
|
||||
def on_playlist_changed(self, player, prev, new):
|
||||
self.changed = (prev, new)
|
||||
|
||||
def test_init(self):
|
||||
play = player.Player()
|
||||
self.assertIsInstance(play, bass.BassPlayer)
|
||||
self.assertIsInstance(play.bus, Gst.Bus)
|
||||
self.assertIsInstance(play.Autopause, scale.AutoPauseScale)
|
||||
self.assertIsNone(play.track)
|
||||
|
||||
self.assertEqual(play.playlist, db.user.Table.find("Collection"))
|
||||
self.assertEqual(lib.settings.get_int("audio.plstateid"),
|
||||
db.user.Table.find("Collection").plstateid)
|
||||
|
||||
def test_set_playlist(self):
|
||||
collection = db.user.Table.find("Collection")
|
||||
plist = db.user.Table.find("Test Playlist")
|
||||
play = player.Player()
|
||||
play.connect("playlist-changed", self.on_playlist_changed)
|
||||
|
||||
play.set_playlist(plist)
|
||||
self.assertEqual(play.playlist, plist)
|
||||
self.assertEqual(self.changed, (collection, plist))
|
||||
self.assertEqual(lib.settings.get_int("audio.plstateid"), plist.plstateid)
|
||||
|
||||
self.changed = None
|
||||
play.set_playlist(plist)
|
||||
self.assertIsNone(self.changed)
|
||||
|
||||
play2 = player.Player()
|
||||
self.assertEqual(play2.playlist, plist)
|
||||
|
||||
play2.set_playlist(None)
|
||||
self.assertEqual(play2.playlist, collection)
|
||||
|
||||
def test_set_playlist_reset(self):
|
||||
plist = db.user.Table.find("Test Playlist")
|
||||
plist.add_track(db.make_fake_track(1, 1, "Test 1", "/a/b/c/1.ogg"))
|
||||
plist.current = 0
|
||||
|
||||
play = player.Player()
|
||||
play.set_playlist(plist)
|
||||
self.assertEqual(plist.current, -1)
|
||||
|
||||
def test_change_track(self):
|
||||
play = player.Player()
|
||||
play.connect("track-changed", self.on_track_changed)
|
||||
|
||||
self.assertEqual(play.get_property("uri"), None)
|
||||
|
||||
play.change_track(self.track, reset=True)
|
||||
self.assertEqual(play.track, self.track)
|
||||
self.assertEqual(lib.settings.get_int("audio.trackid"), self.track.rowid)
|
||||
self.assertEqual(self.changed, (None, self.track) )
|
||||
|
||||
db.sql.execute("DELETE FROM temp_playlist_map")
|
||||
play2 = player.Player()
|
||||
self.assertEqual(play2.track, self.track)
|
||||
self.assertEqual(db.user.Table.find("Previous").get_track(0), self.track)
|
||||
|
||||
def test_play_track(self):
|
||||
play = player.Player()
|
||||
play.play_track(self.track)
|
||||
self.assertEqual(play.track, self.track)
|
||||
self.assertTrue(play.playing)
|
||||
play.pause()
|
||||
|
||||
def test_play_pause(self):
|
||||
play = player.Player()
|
||||
|
||||
play.play_track(self.track)
|
||||
self.assertEqual(play.track, self.track)
|
||||
|
||||
play.pause()
|
||||
self.assertFalse(play.playing)
|
||||
|
||||
play.play()
|
||||
self.assertTrue(play.playing)
|
||||
|
||||
play.playpause()
|
||||
self.assertFalse(play.playing)
|
||||
|
||||
play.playpause()
|
||||
self.assertTrue(play.playing)
|
||||
|
||||
play.pause()
|
||||
self.assertFalse(play.playing)
|
||||
|
||||
def test_next_previous(self):
|
||||
collection = db.user.Table.find("Collection")
|
||||
play = player.Player()
|
||||
play.set_playlist(collection)
|
||||
play.connect("track-changed", self.on_track_changed)
|
||||
|
||||
track0 = collection.get_track(0)
|
||||
track1 = collection.get_track(1)
|
||||
|
||||
play.next()
|
||||
self.assertEqual(play.track, track0)
|
||||
self.assertEqual(self.changed, (None, track0))
|
||||
|
||||
play.next()
|
||||
self.assertEqual(play.track, track1)
|
||||
self.assertEqual(self.changed, (track0, track1))
|
||||
|
||||
play.previous()
|
||||
self.assertEqual(play.track, track0)
|
||||
self.assertEqual(self.changed, (track1, track0))
|
||||
|
||||
play.next()
|
||||
self.assertEqual(play.track, track1)
|
||||
self.assertEqual(self.changed, (track0, track1))
|
|
@ -1,38 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import unittest
|
||||
from gi.repository import Gst
|
||||
from . import replaygain
|
||||
|
||||
class TestReplayGainSink(unittest.TestCase):
|
||||
def test_replay_gain_sink_init(self):
|
||||
rgsink = replaygain.ReplayGainSink()
|
||||
|
||||
self.assertIsInstance(rgsink, Gst.Bin)
|
||||
self.assertIsInstance(rgsink.selector, Gst.Element)
|
||||
self.assertIsInstance(rgsink.funnel, Gst.Element)
|
||||
self.assertIsInstance(rgsink.audiosink, Gst.Element)
|
||||
self.assertIsInstance(rgsink.rgvolume, Gst.Element)
|
||||
self.assertIsInstance(rgsink.rglimiter, Gst.Element)
|
||||
|
||||
self.assertIsInstance(rgsink.shortcut, Gst.Pad)
|
||||
self.assertIsInstance(rgsink.replaygain, Gst.Pad)
|
||||
self.assertIsInstance(rgsink.get_static_pad("sink"), Gst.GhostPad)
|
||||
|
||||
def test_replay_gain_sink_mode(self):
|
||||
rgsink = replaygain.ReplayGainSink()
|
||||
self.assertEqual(rgsink.get_property("mode"), "disabled")
|
||||
|
||||
rgsink.set_property("mode", "album")
|
||||
self.assertEqual(rgsink.get_property("mode"), "album")
|
||||
self.assertEqual(rgsink.selector.get_property("active-pad"),
|
||||
rgsink.replaygain)
|
||||
|
||||
rgsink.set_property("mode", "track")
|
||||
self.assertEqual(rgsink.get_property("mode"), "track")
|
||||
self.assertEqual(rgsink.selector.get_property("active-pad"),
|
||||
rgsink.replaygain)
|
||||
|
||||
rgsink.set_property("mode", "disabled")
|
||||
self.assertEqual(rgsink.get_property("mode"), "disabled")
|
||||
self.assertEqual(rgsink.selector.get_property("active-pad"),
|
||||
rgsink.shortcut)
|
|
@ -1,256 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import unittest
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Gst
|
||||
from . import scale
|
||||
|
||||
class FakePlayer(GObject.GObject):
|
||||
def __init__(self, position, duration, volume):
|
||||
GObject.GObject.__init__(self)
|
||||
self.pos = position
|
||||
self.dur = duration
|
||||
self.vol = volume
|
||||
|
||||
@GObject.Property
|
||||
def position(self): return self.pos
|
||||
@position.setter
|
||||
def position(self, newval):
|
||||
self.pos = newval
|
||||
self.emit("position-changed")
|
||||
|
||||
@GObject.Signal
|
||||
def position_changed(self): pass
|
||||
|
||||
@GObject.Property
|
||||
def duration(self): return self.dur
|
||||
@GObject.Signal
|
||||
def duration_changed(self): pass
|
||||
|
||||
def seek(self, value): self.seek_val = value
|
||||
|
||||
@GObject.Property
|
||||
def volume(self): return self.vol
|
||||
@volume.setter
|
||||
def volume(self, volume): self.vol = volume
|
||||
|
||||
|
||||
class TestScalePlus(unittest.TestCase):
|
||||
def on_change_value(self, scale, scroll, value):
|
||||
self.change_value = (scroll, value)
|
||||
|
||||
def test_scale_plus_init(self):
|
||||
splus = scale.ScalePlus(1, 10, 2, 5)
|
||||
adj = splus.get_adjustment()
|
||||
|
||||
self.assertIsInstance(splus, Gtk.Scale)
|
||||
self.assertTrue(splus.get_draw_value())
|
||||
self.assertTrue(splus.get_hexpand())
|
||||
self.assertEqual(splus.get_value_pos(), Gtk.PositionType.TOP)
|
||||
|
||||
self.assertEqual(adj.get_value(), 1)
|
||||
self.assertEqual(adj.get_lower(), 1)
|
||||
self.assertEqual(adj.get_upper(), 10)
|
||||
self.assertEqual(adj.get_step_increment(), 2)
|
||||
self.assertEqual(adj.get_page_increment(), 5)
|
||||
|
||||
def test_scale_plus_decrement(self):
|
||||
splus = scale.ScalePlus(1, 10, 2, 5)
|
||||
splus.set_value(10)
|
||||
splus.connect("change-value", self.on_change_value)
|
||||
|
||||
for i in [ 8, 6, 4, 2, 1 ]:
|
||||
self.assertEqual(splus.decrement(), i)
|
||||
self.assertEqual(splus.format_value(splus, i), str(i))
|
||||
self.assertEqual(self.change_value,
|
||||
(Gtk.ScrollType.STEP_BACKWARD, i))
|
||||
|
||||
self.change_value = None
|
||||
self.assertIsNone(splus.decrement())
|
||||
self.assertIsNone(self.change_value)
|
||||
|
||||
def test_scale_plus_increment(self):
|
||||
splus = scale.ScalePlus(1, 10, 2, 5)
|
||||
splus.connect("change-value", self.on_change_value)
|
||||
|
||||
for i in [ 3, 5, 7, 9, 10 ]:
|
||||
self.assertEqual(splus.increment(), i)
|
||||
self.assertEqual(splus.format_value(splus, i), str(i))
|
||||
self.assertEqual(self.change_value,
|
||||
(Gtk.ScrollType.STEP_FORWARD, i))
|
||||
|
||||
self.change_value = None
|
||||
self.assertIsNone(splus.increment())
|
||||
self.assertIsNone(self.change_value)
|
||||
|
||||
|
||||
class TestSeekScale(unittest.TestCase):
|
||||
def test_seek_scale_init(self):
|
||||
fake = FakePlayer(0, 5 * Gst.SECOND, 1)
|
||||
seek = scale.SeekScale(fake)
|
||||
adj = seek.get_adjustment()
|
||||
|
||||
self.assertIsInstance(seek, scale.ScalePlus)
|
||||
|
||||
self.assertEqual(seek.get_size_request(), (200, -1))
|
||||
self.assertEqual(seek.player, fake)
|
||||
self.assertEqual(adj.get_value(), 0)
|
||||
self.assertEqual(adj.get_lower(), 0)
|
||||
self.assertEqual(adj.get_upper(), 5 * Gst.SECOND)
|
||||
self.assertEqual(adj.get_step_increment(), 5 * Gst.SECOND)
|
||||
self.assertEqual(adj.get_page_increment(), 30 * Gst.SECOND)
|
||||
|
||||
def test_seek_scale_duration(self):
|
||||
fake = FakePlayer(0, 2 * Gst.SECOND, 1)
|
||||
seek = scale.SeekScale(fake)
|
||||
adj = seek.get_adjustment()
|
||||
|
||||
self.assertEqual(adj.get_upper(), 2 * Gst.SECOND)
|
||||
fake.dur = 3 * Gst.SECOND
|
||||
fake.emit("duration-changed")
|
||||
self.assertEqual(adj.get_upper(), 3 * Gst.SECOND)
|
||||
|
||||
def test_seek_scale_position(self):
|
||||
fake = FakePlayer(0, 15 * Gst.SECOND, 1)
|
||||
seek = scale.SeekScale(fake)
|
||||
adj = seek.get_adjustment()
|
||||
|
||||
fake.position = 3 * Gst.SECOND
|
||||
self.assertEqual(seek.player, fake)
|
||||
self.assertEqual(adj.get_value(), 3 * Gst.SECOND)
|
||||
self.assertEqual(adj.get_lower(), 0)
|
||||
|
||||
def test_seek_scale_values(self):
|
||||
fake = FakePlayer(0, 15 * Gst.SECOND, 1)
|
||||
seek = scale.SeekScale(fake)
|
||||
|
||||
seek.increment()
|
||||
self.assertEqual(fake.pos, 5 * Gst.SECOND)
|
||||
self.assertEqual(seek.format_value(seek, 5 * Gst.SECOND),
|
||||
"00:05 / 00:10")
|
||||
|
||||
seek.decrement()
|
||||
self.assertEqual(fake.pos, 0)
|
||||
|
||||
|
||||
class TestAutoPauseScale(unittest.TestCase):
|
||||
def test_autopause_scale_init(self):
|
||||
pause = scale.AutoPauseScale()
|
||||
adj = pause.get_adjustment()
|
||||
|
||||
self.assertIsInstance(pause, scale.ScalePlus)
|
||||
self.assertEqual(pause.get_digits(), 0)
|
||||
self.assertEqual(adj.get_value(), -1)
|
||||
self.assertEqual(adj.get_lower(), -1)
|
||||
self.assertEqual(adj.get_upper(), 99)
|
||||
self.assertEqual(adj.get_step_increment(), 1)
|
||||
self.assertEqual(adj.get_page_increment(), 5)
|
||||
self.assertTrue(pause.keep_playing)
|
||||
|
||||
def test_autopause_scale_values(self):
|
||||
pause = scale.AutoPauseScale()
|
||||
self.assertEqual(pause.format_value(pause, -1), "Keep Playing")
|
||||
self.assertEqual(pause.format_value(pause, 0), "This Track")
|
||||
self.assertEqual(pause.format_value(pause, 1), "Next Track")
|
||||
self.assertEqual(pause.format_value(pause, 2), "2 Tracks")
|
||||
|
||||
def test_keep_playing(self):
|
||||
pause = scale.AutoPauseScale()
|
||||
pause.set_value(2)
|
||||
|
||||
pause.decrement()
|
||||
self.assertEqual(pause.get_value(), 1)
|
||||
self.assertFalse(pause.about_to_pause())
|
||||
self.assertTrue(pause.keep_playing)
|
||||
|
||||
pause.decrement()
|
||||
self.assertEqual(pause.get_value(), 0)
|
||||
self.assertTrue(pause.about_to_pause())
|
||||
self.assertTrue(pause.keep_playing)
|
||||
|
||||
pause.decrement()
|
||||
self.assertEqual(pause.get_value(), -1)
|
||||
self.assertFalse(pause.about_to_pause())
|
||||
self.assertFalse(pause.keep_playing)
|
||||
|
||||
pause.decrement()
|
||||
self.assertEqual(pause.get_value(), -1)
|
||||
self.assertFalse(pause.about_to_pause())
|
||||
self.assertTrue(pause.keep_playing)
|
||||
|
||||
|
||||
class TestVolumeScale(unittest.TestCase):
|
||||
def test_volume_scale_init(self):
|
||||
fake = FakePlayer(0, 5 * Gst.SECOND, 1.0)
|
||||
volume = scale.VolumeScale(fake)
|
||||
adj = volume.get_adjustment()
|
||||
|
||||
self.assertIsInstance(volume, scale.ScalePlus)
|
||||
self.assertEqual(volume.player, fake)
|
||||
self.assertEqual(adj.get_value(), 1.0)
|
||||
self.assertEqual(adj.get_lower(), 0.0)
|
||||
self.assertEqual(adj.get_upper(), 1.0)
|
||||
self.assertEqual(adj.get_step_increment(), 0.05)
|
||||
self.assertEqual(adj.get_page_increment(), 0.25)
|
||||
|
||||
fake.volume = 0.5
|
||||
vol2 = scale.VolumeScale(fake)
|
||||
self.assertEqual(vol2.get_value(), 0.5)
|
||||
|
||||
def test_volume_scale_values(self):
|
||||
fake = FakePlayer(0, 15 * Gst.SECOND, 0.5)
|
||||
volume = scale.VolumeScale(fake)
|
||||
|
||||
volume.increment()
|
||||
self.assertEqual(fake.volume, 0.55)
|
||||
self.assertEqual(volume.format_value(volume, 0.55), "55%")
|
||||
|
||||
volume.decrement()
|
||||
self.assertEqual(fake.volume, 0.50)
|
||||
self.assertEqual(volume.format_value(volume, 0.50), "50%")
|
||||
|
||||
|
||||
class TestScaleButton(unittest.TestCase):
|
||||
def test_scale_button(self):
|
||||
splus = scale.ScalePlus(0, 5, 1, 1)
|
||||
sbutt = scale.ScaleButton(splus, "missing-icon")
|
||||
|
||||
self.assertIsInstance(sbutt, Gtk.Button)
|
||||
self.assertEqual(sbutt.get_valign(), Gtk.Align.END)
|
||||
self.assertTrue(sbutt.has_css_class("normal-icons"))
|
||||
self.assertTrue(sbutt.has_css_class("flat"))
|
||||
|
||||
|
||||
class TestDecrementButton(unittest.TestCase):
|
||||
def test_decrement_button(self):
|
||||
splus = scale.ScalePlus(0, 5, 1, 1)
|
||||
dec = scale.DecrementButton(splus)
|
||||
splus.set_value(1)
|
||||
|
||||
self.assertIsInstance(dec, scale.ScaleButton)
|
||||
self.assertEqual(dec.get_icon_name(), "list-remove-symbolic")
|
||||
dec.emit("clicked")
|
||||
self.assertEqual(splus.get_value(), 0)
|
||||
|
||||
|
||||
class TestIncrementButton(unittest.TestCase):
|
||||
def test_increment_button(self):
|
||||
splus = scale.ScalePlus(0, 5, 1, 1)
|
||||
inc = scale.IncrementButton(splus)
|
||||
|
||||
self.assertIsInstance(inc, scale.ScaleButton)
|
||||
self.assertEqual(inc.get_icon_name(), "list-add-symbolic")
|
||||
inc.emit("clicked")
|
||||
self.assertEqual(splus.get_value(), 1)
|
||||
|
||||
|
||||
class TestScaleButtonBox(unittest.TestCase):
|
||||
def test_scale_button_box(self):
|
||||
splus = scale.ScalePlus(0, 5, 1, 1)
|
||||
sbox = scale.ScaleButtonBox(splus)
|
||||
|
||||
self.assertIsInstance(sbox, Gtk.Box)
|
||||
self.assertEqual(sbox.get_orientation(), Gtk.Orientation.HORIZONTAL)
|
||||
self.assertIsInstance(sbox.get_first_child(), scale.DecrementButton)
|
||||
self.assertIsInstance(sbox.get_last_child(), scale.IncrementButton)
|
||||
self.assertIsInstance(splus.get_next_sibling(), scale.IncrementButton)
|
|
@ -1,66 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import datetime
|
||||
import lib
|
||||
import pathlib
|
||||
|
||||
def new_db():
|
||||
from . import sql
|
||||
try:
|
||||
return sql.execute("SELECT COUNT(*) from tracks").fetchone()[0] == 0
|
||||
except:
|
||||
return True
|
||||
|
||||
|
||||
from . import artist
|
||||
from . import album
|
||||
from . import disc
|
||||
from . import genre
|
||||
from . import decade
|
||||
from . import year
|
||||
from . import library
|
||||
from . import track
|
||||
from . import state
|
||||
from . import user
|
||||
|
||||
from . import sql
|
||||
def _search_table(table, key, search):
|
||||
if row := sql.execute(f"SELECT {key} FROM {table} "
|
||||
f"WHERE plstateid=?", [ search ]).fetchone():
|
||||
return row[0]
|
||||
|
||||
def find_playlist(plstateid):
|
||||
if playlistid := _search_table("playlists", "playlistid", plstateid):
|
||||
return user.Table.get(playlistid)
|
||||
if artistid := _search_table("artists", "artistid", plstateid):
|
||||
return artist.Table.get(artistid)
|
||||
if albumid := _search_table("albums", "albumid", plstateid):
|
||||
return album.Table.get(albumid)
|
||||
if discid := _search_table("discs", "discid", plstateid):
|
||||
return disc.Table.get(discid)
|
||||
if genreid := _search_table("genres", "genreid", plstateid):
|
||||
return genre.Table.get(genreid)
|
||||
if decadeid := _search_table("decades", "decadeid", plstateid):
|
||||
return decade.Table.get(decadeid)
|
||||
if yearid := _search_table("years", "yearid", plstateid):
|
||||
return year.Table.get(yearid)
|
||||
if libraryid := _search_table("libraries", "libraryid", plstateid):
|
||||
return library.Table.get(libraryid)
|
||||
return None
|
||||
|
||||
def make_fake_track(trackno, length, title, path, lib="/a/b/c", art="Test Artist",
|
||||
alb="Test Album", disk=1, subtitle=None, yeer=2021, mnth=3, dy=18):
|
||||
lib = library.Table.find(pathlib.Path(lib))
|
||||
art = artist.Table.find(art, art)
|
||||
alb = art.find_album(alb, datetime.date(yeer, mnth, dy))
|
||||
disk = alb.find_disc(disk, subtitle)
|
||||
dec = decade.Table.find((yeer // 10) * 10)
|
||||
yeer = dec.find_year(yeer)
|
||||
return track.Table.insert(lib, art, alb, disk, dec, yeer, trackno,
|
||||
length, title, pathlib.Path(path))
|
||||
|
||||
def reset():
|
||||
mods = [ track, state, user, artist, album,
|
||||
disc, genre, decade, year, library ]
|
||||
for mod in mods: mod.Table.reset()
|
||||
|
||||
if lib.version.TESTING: reset()
|
64
db/album.py
64
db/album.py
|
@ -1,64 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
#
|
||||
# Table: albums
|
||||
# +---------+----------+-----------+------+------+
|
||||
# | albumid | artistid | plstateid | name | sort |
|
||||
# +---------+----------+-----------+------+------+
|
||||
from gi.repository import GObject
|
||||
from . import disc
|
||||
from . import playlist
|
||||
from . import sql
|
||||
|
||||
class Album(playlist.ParentPlaylist):
|
||||
def __init__(self, row):
|
||||
playlist.ParentPlaylist.__init__(self, row, "media-optical-cd-audio")
|
||||
self._name = row["name"]
|
||||
self._release = row["release"]
|
||||
|
||||
@GObject.Property
|
||||
def name(self): return self._name
|
||||
|
||||
@GObject.Property
|
||||
def release(self): return self._release
|
||||
|
||||
def delete(self): Table.delete(self)
|
||||
|
||||
def find_disc(self, number, subtitle):
|
||||
return self.find_child(number, subtitle)
|
||||
|
||||
def get_child_table(self): return disc.Table
|
||||
def lookup_child(self, number, subtitle):
|
||||
return disc.Table.lookup(self, number)
|
||||
|
||||
|
||||
class AlbumTable(playlist.ChildModel):
|
||||
def __init__(self):
|
||||
playlist.ChildModel.__init__(self, "albums", "artistid", "sort")
|
||||
|
||||
def do_create(self):
|
||||
sql.execute("CREATE TABLE IF NOT EXISTS albums "
|
||||
"(albumid INTEGER PRIMARY KEY, "
|
||||
" artistid INTEGER, "
|
||||
" plstateid INTEGER NOT NULL, "
|
||||
" release DATE NOT NULL, "
|
||||
" name TEXT, "
|
||||
" sort TEXT, "
|
||||
" FOREIGN KEY(artistid) REFERENCES artists(artistid), "
|
||||
" FOREIGN KEY(plstateid) REFERENCES playlist_states(plstateid), "
|
||||
" UNIQUE(artistid, release, name))")
|
||||
|
||||
def do_factory(self, row):
|
||||
return Album(row)
|
||||
|
||||
def do_insert(self, plstate, artist, name, release):
|
||||
return sql.execute("INSERT INTO albums (artistid, plstateid, release, name, sort) "
|
||||
"VALUES (?, ?, ?, ?, ?)",
|
||||
[ artist.rowid, plstate.rowid, release, name, name.casefold() ])
|
||||
|
||||
def do_lookup(self, artist, name, release):
|
||||
return sql.execute("SELECT * FROM albums "
|
||||
"WHERE (artistid=? AND name=? AND release=?)",
|
||||
[ artist.rowid, name, release ])
|
||||
|
||||
|
||||
Table = AlbumTable()
|
54
db/artist.py
54
db/artist.py
|
@ -1,54 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
#
|
||||
# Table: artists
|
||||
# +----------+-----------+------+------+
|
||||
# | artistid | plstateid | name | sort |
|
||||
# +----------+-----------+------+------+
|
||||
from gi.repository import GObject
|
||||
from . import album
|
||||
from . import playlist
|
||||
from . import sql
|
||||
|
||||
class Artist(playlist.ParentPlaylist):
|
||||
def __init__(self, row):
|
||||
playlist.ParentPlaylist.__init__(self, row, "avatar-default-symbolic")
|
||||
self._name = row["name"]
|
||||
|
||||
@GObject.Property
|
||||
def name(self): return self._name
|
||||
|
||||
def delete(self): Table.delete(self)
|
||||
|
||||
def find_album(self, name, release): return self.find_child(name, release)
|
||||
|
||||
def get_child_table(self): return album.Table
|
||||
|
||||
|
||||
class ArtistTable(playlist.Model):
|
||||
def __init__(self):
|
||||
playlist.Model.__init__(self, "artists", "sort")
|
||||
|
||||
def do_create(self):
|
||||
sql.execute("CREATE TABLE IF NOT EXISTS artists "
|
||||
"(artistid INTEGER PRIMARY KEY, "
|
||||
" plstateid INTEGER NOT NULL, "
|
||||
" name TEXT UNIQUE, "
|
||||
" sort TEXT, "
|
||||
" FOREIGN KEY(plstateid) REFERENCES playlist_states(plstateid))")
|
||||
|
||||
def do_factory(self, row):
|
||||
return Artist(row)
|
||||
|
||||
def do_insert(self, plstate, name, sort):
|
||||
return sql.execute("INSERT INTO artists (plstateid, name, sort) "
|
||||
"VALUES (?, ?, ?)",
|
||||
[ plstate.rowid, name, sort.casefold() ])
|
||||
|
||||
def do_lookup(self, name):
|
||||
return sql.execute("SELECT * FROM artists WHERE name=?", [ name ])
|
||||
|
||||
def find(self, name, sort):
|
||||
return res if (res := self.lookup(name)) else self.insert(name, sort)
|
||||
|
||||
|
||||
Table = ArtistTable()
|
51
db/decade.py
51
db/decade.py
|
@ -1,51 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
#
|
||||
# Table: decades
|
||||
# +----------+-----------+--------+
|
||||
# | decadeid | plstateid | decade |
|
||||
# +----------+-----------+--------+
|
||||
from gi.repository import GObject
|
||||
from . import playlist
|
||||
from . import sql
|
||||
from . import year
|
||||
|
||||
class Decade(playlist.ParentPlaylist):
|
||||
def __init__(self, row):
|
||||
playlist.ParentPlaylist.__init__(self, row, "x-office-calendar")
|
||||
self._decade = row["decade"]
|
||||
|
||||
@GObject.Property
|
||||
def decade(self): return self._decade
|
||||
|
||||
@GObject.Property
|
||||
def name(self): return f"{self._decade}s"
|
||||
|
||||
def delete(self): Table.delete(self)
|
||||
def find_year(self, yr): return self.find_child(yr)
|
||||
def get_child_table(self): return year.Table
|
||||
def lookup_child(self, yr): return year.Table.lookup(yr)
|
||||
|
||||
|
||||
class DecadeTable(playlist.Model):
|
||||
def __init__(self):
|
||||
playlist.Model.__init__(self, "decades", "decade")
|
||||
|
||||
def do_create(self):
|
||||
sql.execute("CREATE TABLE IF NOT EXISTS decades "
|
||||
"(decadeid INTEGER PRIMARY KEY, "
|
||||
" plstateid INTEGER NOT NULL, "
|
||||
" decade INTEGER UNIQUE, "
|
||||
" FOREIGN KEY(plstateid) REFERENCES playlist_states(plstateid))")
|
||||
|
||||
def do_factory(self, row):
|
||||
return Decade(row)
|
||||
|
||||
def do_insert(self, plstate, decade):
|
||||
return sql.execute("INSERT INTO decades (plstateid,decade) "
|
||||
"VALUES (?, ?)", [ plstate.rowid, decade ])
|
||||
|
||||
def do_lookup(self, decade):
|
||||
return sql.execute("SELECT * FROM decades WHERE decade=?", [ decade ])
|
||||
|
||||
|
||||
Table = DecadeTable()
|
61
db/disc.py
61
db/disc.py
|
@ -1,61 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
#
|
||||
# Table: discs
|
||||
# +--------+---------+--------+----------+
|
||||
# | discid | albumid | number | subtitle |
|
||||
# +--------+---------+--------+----------+
|
||||
from gi.repository import GObject
|
||||
from . import playlist
|
||||
from . import sql
|
||||
|
||||
class Disc(playlist.Playlist):
|
||||
def __init__(self, row):
|
||||
playlist.Playlist.__init__(self, row, "media-optical")
|
||||
self._number = row["number"]
|
||||
self._subtitle = row["subtitle"]
|
||||
|
||||
def delete(self): Table.delete(self)
|
||||
|
||||
@GObject.Property
|
||||
def name(self):
|
||||
if self._subtitle:
|
||||
return f"{self._number}: {self._subtitle}"
|
||||
return f"Disc {self._number}"
|
||||
|
||||
@GObject.Property
|
||||
def number(self): return self._number
|
||||
|
||||
@GObject.Property
|
||||
def subtitle(self):
|
||||
return self._subtitle if self._subtitle else ""
|
||||
|
||||
|
||||
class DiscTable(playlist.ChildModel):
|
||||
def __init__(self):
|
||||
playlist.ChildModel.__init__(self, "discs", "albumid", "number")
|
||||
|
||||
def do_create(self):
|
||||
sql.execute("CREATE TABLE IF NOT EXISTS discs "
|
||||
"(discid INTEGER PRIMARY KEY, "
|
||||
" albumid INTEGER, "
|
||||
" plstateid INTEGER NOT NULL, "
|
||||
" number INTEGER, "
|
||||
" subtitle TEXT, "
|
||||
" FOREIGN KEY(albumid) REFERENCES albums(albumid), "
|
||||
" FOREIGN KEY(plstateid) REFERENCES playlist_states(plstateid), "
|
||||
" UNIQUE(albumid, number))")
|
||||
|
||||
def do_factory(self, row):
|
||||
return Disc(row)
|
||||
|
||||
def do_insert(self, plstate, album, number, subtitle):
|
||||
return sql.execute("INSERT INTO discs (albumid, plstateid, number, subtitle) "
|
||||
"VALUES (?, ?, ?, ?)",
|
||||
[ album.rowid, plstate.rowid, number, subtitle ])
|
||||
|
||||
def do_lookup(self, album, number):
|
||||
return sql.execute("SELECT * FROM discs WHERE (albumid=? AND number=?)",
|
||||
[ album.rowid, number ])
|
||||
|
||||
|
||||
Table = DiscTable()
|
64
db/genre.py
64
db/genre.py
|
@ -1,64 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
#
|
||||
# Table: genres
|
||||
# +---------+-----------+------+------+
|
||||
# | genreid | plstateid | name | sort |
|
||||
# +---------+-----------+------+------+
|
||||
#
|
||||
# Index: genre_index
|
||||
# +-----------------+
|
||||
# | name -> genreid |
|
||||
# +-----------------+
|
||||
from gi.repository import GObject
|
||||
from . import playlist
|
||||
from . import track
|
||||
from . import sql
|
||||
|
||||
class Genre(playlist.MappedPlaylist):
|
||||
def __init__(self, row):
|
||||
playlist.MappedPlaylist.__init__(self, row, "emblem-generic", "genre_map")
|
||||
self._name = row["name"]
|
||||
|
||||
def delete(self):
|
||||
self.clear()
|
||||
Table.delete(self)
|
||||
|
||||
@GObject.Property
|
||||
def name(self): return self._name
|
||||
|
||||
|
||||
class GenreTable(playlist.Model):
|
||||
def __init__(self):
|
||||
playlist.Model.__init__(self, "genres", "sort")
|
||||
|
||||
def do_create(self):
|
||||
sql.execute("CREATE TABLE IF NOT EXISTS genres "
|
||||
"(genreid INTEGER PRIMARY KEY, "
|
||||
" plstateid INTEGER NOT NULL, "
|
||||
" name TEXT UNIQUE, "
|
||||
" sort TEXT, "
|
||||
" FOREIGN KEY(plstateid) REFERENCES playlist_states(plstateid))")
|
||||
sql.execute("CREATE TABLE IF NOT EXISTS genre_map "
|
||||
"(genreid INTEGER, "
|
||||
" trackid INTEGER, "
|
||||
" FOREIGN KEY(genreid) REFERENCES genres(genreid), "
|
||||
" FOREIGN KEY(trackid) REFERENCES tracks(trackid), "
|
||||
" UNIQUE(genreid, trackid))")
|
||||
|
||||
def do_drop(self):
|
||||
sql.execute("DROP TABLE genres")
|
||||
sql.execute("DROP TABLE genre_map")
|
||||
|
||||
def do_factory(self, row):
|
||||
return Genre(row)
|
||||
|
||||
def do_insert(self, plstate, name):
|
||||
return sql.execute("INSERT INTO genres (plstateid, name, sort) "
|
||||
"VALUES (?, ?, ?)",
|
||||
[ plstate.rowid, name, name.casefold() ])
|
||||
|
||||
def do_lookup(self, name):
|
||||
return sql.execute("SELECT * FROM genres WHERE name=?", [ name ])
|
||||
|
||||
|
||||
Table = GenreTable()
|
|
@ -1,69 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
#
|
||||
# Table: libraries
|
||||
# +-----------+-----------+---------+------+
|
||||
# | libraryid | plstateid | enabled | path |
|
||||
# +-----------+-----------+---------+------+
|
||||
import pathlib
|
||||
from gi.repository import GObject
|
||||
from . import sql
|
||||
from . import playlist
|
||||
from . import track
|
||||
from . import user
|
||||
|
||||
class Library(playlist.Playlist):
|
||||
def __init__(self, row):
|
||||
playlist.Playlist.__init__(self, row, "folder-music")
|
||||
self._path = pathlib.Path(row["path"])
|
||||
self._enabled = row["enabled"]
|
||||
|
||||
@GObject.Property
|
||||
def name(self): return str(self._path)
|
||||
|
||||
@GObject.Property
|
||||
def path(self): return self._path
|
||||
|
||||
@GObject.Property(type=bool,default=True)
|
||||
def enabled(self): return self._enabled
|
||||
|
||||
@enabled.setter
|
||||
def enabled(self, newval):
|
||||
sql.execute("UPDATE libraries SET enabled=? WHERE libraryid=?",
|
||||
[ newval, self.rowid ])
|
||||
sql.commit()
|
||||
self._enabled = newval
|
||||
user.Table.find("Collection").refresh()
|
||||
|
||||
def delete(self): Table.delete(self)
|
||||
|
||||
def tracks(self):
|
||||
cursor = sql.execute(f"SELECT trackid FROM tracks "
|
||||
"WHERE libraryid=?", [ self.rowid ])
|
||||
return [ track.Table.get(row["trackid"]) for row in cursor.fetchall() ]
|
||||
|
||||
|
||||
|
||||
class LibraryTable(playlist.Model):
|
||||
def __init__(self):
|
||||
playlist.Model.__init__(self, "libraries", "path")
|
||||
|
||||
def do_create(self):
|
||||
sql.execute("CREATE TABLE IF NOT EXISTS libraries "
|
||||
"(libraryid INTEGER PRIMARY KEY, "
|
||||
" plstateid INTEGER NOT NULL, "
|
||||
" enabled INTEGER DEFAULT 1, "
|
||||
" path TEXT UNIQUE, "
|
||||
" FOREIGN KEY (plstateid) REFERENCES playlist_states(plstateid))")
|
||||
|
||||
def do_factory(self, row):
|
||||
return Library(row)
|
||||
|
||||
def do_insert(self, plstate, path):
|
||||
return sql.execute("INSERT INTO libraries (plstateid, path) "
|
||||
"VALUES (?, ?)", [ plstate.rowid, str(path) ])
|
||||
|
||||
def do_lookup(self, path):
|
||||
return sql.execute("SELECT * FROM libraries WHERE path=?", [ str(path) ])
|
||||
|
||||
|
||||
Table = LibraryTable()
|
273
db/playlist.py
273
db/playlist.py
|
@ -1,273 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import random
|
||||
from gi.repository import GObject
|
||||
from . import sql
|
||||
from . import state
|
||||
from . import table
|
||||
|
||||
class Playlist(GObject.GObject):
|
||||
def __init__(self, row, icon_name):
|
||||
GObject.GObject.__init__(self)
|
||||
self._rowkey = row.keys()[0]
|
||||
self._rowid = row[self._rowkey]
|
||||
self._plstate = state.Table.get(row["plstateid"])
|
||||
self._icon_name = icon_name
|
||||
|
||||
def delete(self): raise NotImplementedError
|
||||
|
||||
def has_children(self): return False
|
||||
def can_add_remove_tracks(self): return False
|
||||
|
||||
def get_n_tracks(self):
|
||||
cur = sql.execute(f"SELECT COUNT(*) FROM tracks "
|
||||
f"WHERE {self._rowkey}=?", [ self._rowid ])
|
||||
return cur.fetchone()[0]
|
||||
|
||||
def get_track(self, n):
|
||||
order = ', '.join(self.plist_state.sort)
|
||||
row = sql.execute(f"SELECT * FROM tracks "
|
||||
f"INNER JOIN artists USING(artistid) "
|
||||
f"INNER JOIN albums USING(albumid) "
|
||||
f"INNER JOIN discs USING(discid) "
|
||||
f"WHERE tracks.{self._rowkey}=? "
|
||||
f"ORDER BY {order} LIMIT 1 OFFSET ?",
|
||||
[ self.rowid, n ]).fetchone()
|
||||
return track.Table.factory(row)
|
||||
|
||||
def get_tracks(self):
|
||||
order = ', '.join(self.plist_state.sort)
|
||||
rows = sql.execute(f"SELECT * FROM tracks "
|
||||
f"INNER JOIN artists USING(artistid) "
|
||||
f"INNER JOIN albums USING(albumid) "
|
||||
f"INNER JOIN discs USING(discid) "
|
||||
f"WHERE tracks.{self._rowkey}=? "
|
||||
f"ORDER BY {order}", [ self.rowid ]).fetchall()
|
||||
return [ track.Table.factory(row) for row in rows ]
|
||||
|
||||
def get_current_track(self):
|
||||
return self.get_track(self.current) if self.current > -1 else None
|
||||
|
||||
def get_track_index(self, track):
|
||||
order = ', '.join(self.plist_state.sort)
|
||||
cur = sql.execute(f"SELECT * FROM (SELECT trackid,ROW_NUMBER() "
|
||||
f"OVER (ORDER BY {order}) "
|
||||
f"FROM tracks "
|
||||
f"INNER JOIN artists USING(artistid) "
|
||||
f"INNER JOIN albums USING(albumid) "
|
||||
f"INNER JOIN discs USING(discid) "
|
||||
f"WHERE tracks.{self._rowkey}=?) "
|
||||
f"WHERE trackid=?", [ self.rowid, track.rowid ])
|
||||
return cur.fetchone()[1] - 1
|
||||
|
||||
def track_adjusts_current(self, track):
|
||||
if self.current > -1:
|
||||
if (index := self.get_track_index(track)) != None:
|
||||
return index <= self.current
|
||||
return False
|
||||
|
||||
def add_track(self, track):
|
||||
self.emit("track-added", track)
|
||||
|
||||
def remove_track(self, track, adjust_current):
|
||||
self.emit("track-removed", track, adjust_current)
|
||||
|
||||
def next_track(self):
|
||||
n = self.get_n_tracks()
|
||||
if self.random and n > 1:
|
||||
self.current += random.randint(1, int((3*n)/4))
|
||||
else:
|
||||
self.current += 1
|
||||
return self.get_current_track()
|
||||
|
||||
def refresh(self):
|
||||
self.emit("refreshed")
|
||||
|
||||
@GObject.Property
|
||||
def name(self): raise NotImplementedError
|
||||
|
||||
@GObject.Property
|
||||
def icon_name(self): return self._icon_name
|
||||
|
||||
@GObject.Property
|
||||
def plist_state(self): return self._plstate
|
||||
|
||||
@GObject.Property
|
||||
def current(self): return self._plstate.get_property("current")
|
||||
|
||||
@current.setter
|
||||
def current(self, newval):
|
||||
n_tracks = self.get_n_tracks()
|
||||
if newval >= n_tracks:
|
||||
if n_tracks == 0: newval = -1
|
||||
elif self.random: newval = newval % n_tracks
|
||||
elif self.loop: newval = 0
|
||||
else: newval = n_tracks
|
||||
self._plstate.set_property("current", newval)
|
||||
|
||||
@GObject.Property(type=bool,default=False)
|
||||
def loop(self): return self._plstate.get_property("loop")
|
||||
|
||||
@loop.setter
|
||||
def loop(self, newval): self._plstate.set_property("loop", newval)
|
||||
|
||||
@GObject.Property(type=bool,default=False)
|
||||
def random(self): return self._plstate.get_property("random")
|
||||
|
||||
@random.setter
|
||||
def random(self, newval): self._plstate.set_property("random", newval)
|
||||
|
||||
@GObject.Property(type=GObject.TYPE_PYOBJECT)
|
||||
def sort(self): return self._plstate.get_property("sort")
|
||||
|
||||
@sort.setter
|
||||
def sort(self, newval):
|
||||
track = self.get_current_track()
|
||||
self._plstate.set_property("sort", newval)
|
||||
if track:
|
||||
self.current = self.get_track_index(track)
|
||||
self.refresh()
|
||||
|
||||
@GObject.Property
|
||||
def rowid(self): return self._rowid
|
||||
|
||||
@GObject.Property
|
||||
def rowkey(self): return self._rowkey
|
||||
|
||||
@GObject.Property
|
||||
def plstateid(self): return self._plstate.rowid
|
||||
|
||||
@GObject.Signal
|
||||
def refreshed(self): pass
|
||||
|
||||
@GObject.Signal(arg_types=(GObject.TYPE_PYOBJECT,))
|
||||
def track_added(self, track):
|
||||
if self.track_adjusts_current(track):
|
||||
self.current += 1
|
||||
|
||||
@GObject.Signal(arg_types=(GObject.TYPE_PYOBJECT,bool))
|
||||
def track_removed(self, track, adjust_current):
|
||||
if adjust_current:
|
||||
self.current -= 1
|
||||
|
||||
|
||||
class MappedPlaylist(Playlist):
|
||||
def __init__(self, row, icon_name, map_table):
|
||||
Playlist.__init__(self, row, icon_name)
|
||||
self._map_table = map_table
|
||||
|
||||
@GObject.Property
|
||||
def map_table(self): return self._map_table
|
||||
|
||||
def can_add_remove_tracks(self): return self._map_table == "playlist_map"
|
||||
|
||||
def add_track(self, track):
|
||||
res = sql.execute(f"INSERT OR IGNORE INTO {self.map_table} "
|
||||
f"({self.rowkey}, trackid) VALUES (?, ?)",
|
||||
[ self.rowid, track.rowid ]).rowcount == 1
|
||||
if res:
|
||||
super().add_track(track)
|
||||
return res
|
||||
|
||||
def clear(self):
|
||||
sql.execute(f"DELETE FROM {self.map_table} "
|
||||
f"WHERE {self.rowkey}=?", [ self.rowid ])
|
||||
|
||||
def get_n_tracks(self):
|
||||
cur = sql.execute(f"SELECT COUNT(*) FROM {self.map_table} "
|
||||
f"WHERE {self._rowkey}=?", [ self._rowid ])
|
||||
return cur.fetchone()[0]
|
||||
|
||||
def get_track(self, n):
|
||||
order = ', '.join(self.plist_state.sort)
|
||||
row = sql.execute(f"SELECT * FROM tracks "
|
||||
f"INNER JOIN {self.map_table} USING(trackid) "
|
||||
f"INNER JOIN artists USING(artistid) "
|
||||
f"INNER JOIN albums USING(albumid) "
|
||||
f"INNER JOIN discs USING(discid) "
|
||||
f"WHERE {self._rowkey}=? "
|
||||
f"ORDER BY {order} LIMIT 1 OFFSET ?",
|
||||
[ self._rowid, n ]).fetchone()
|
||||
return track.Table.factory(row)
|
||||
|
||||
def get_tracks(self):
|
||||
order = ', '.join(self.plist_state.sort)
|
||||
rows = sql.execute(f"SELECT * FROM tracks "
|
||||
f"INNER JOIN {self.map_table} USING(trackid) "
|
||||
f"INNER JOIN artists USING(artistid) "
|
||||
f"INNER JOIN albums USING(albumid) "
|
||||
f"INNER JOIN discs USING(discid) "
|
||||
f"WHERE {self._rowkey}=? ORDER BY {order}",
|
||||
[ self._rowid ]).fetchall()
|
||||
return [ track.Table.factory(row) for row in rows ]
|
||||
|
||||
|
||||
def get_track_index(self, track):
|
||||
order = ', '.join(self.plist_state.sort)
|
||||
row = sql.execute(f"SELECT * FROM (SELECT trackid,{self._rowkey},ROW_NUMBER() "
|
||||
f"OVER (ORDER BY {order}) "
|
||||
f"FROM tracks "
|
||||
f"INNER JOIN {self.map_table} USING (trackid) "
|
||||
f"INNER JOIN artists USING(artistid) "
|
||||
f"INNER JOIN albums USING(albumid) "
|
||||
f"INNER JOIN discs USING(discid)) "
|
||||
f"WHERE {self._rowkey}=? AND trackid=?",
|
||||
[ self.rowid, track.rowid ]).fetchone()
|
||||
return row[2] - 1 if row else None
|
||||
|
||||
def remove_track(self, track):
|
||||
adjust_current = self.track_adjusts_current(track)
|
||||
res = sql.execute(f"DELETE FROM {self.map_table} "
|
||||
f"WHERE {self.rowkey}=? AND trackid=?",
|
||||
[ self.rowid, track.rowid ]).rowcount == 1
|
||||
if res:
|
||||
super().remove_track(track, adjust_current)
|
||||
return res
|
||||
|
||||
|
||||
class ParentPlaylist(Playlist):
|
||||
def has_children(self): return True
|
||||
def get_child_table(self): raise NotImplementedError
|
||||
|
||||
def get_n_children(self):
|
||||
return self.get_child_table().get_n_children(self)
|
||||
|
||||
def get_child(self, n):
|
||||
return self.get_child_table().get_child(self, n)
|
||||
|
||||
def get_child_index(self, child):
|
||||
return self.get_child_table().get_child_index(self, child)
|
||||
|
||||
def find_child(self, *args):
|
||||
if (res := self.lookup_child(*args)) == None:
|
||||
res = self.get_child_table().insert(self, *args)
|
||||
self.emit("children-changed", self.get_child_index(res), 0, 1)
|
||||
return res
|
||||
|
||||
def lookup_child(self, *args):
|
||||
return self.get_child_table().lookup(self, *args)
|
||||
|
||||
@GObject.Signal(arg_types=(int,int,int))
|
||||
def children_changed(self, pos, rm, add): pass
|
||||
|
||||
|
||||
class Model(table.Model):
|
||||
def insert(self, *args, **kwargs):
|
||||
loop = kwargs.pop("loop", False)
|
||||
sort = kwargs.pop("sort", state.DefaultSort)
|
||||
return super().insert(state.Table.insert(loop=loop,sort=sort), *args)
|
||||
|
||||
def delete(self, plist):
|
||||
state.Table.delete(plist.plist_state)
|
||||
return super().delete(plist)
|
||||
|
||||
|
||||
class ChildModel(table.Child):
|
||||
def insert(self, *args, **kwargs):
|
||||
return super().insert(state.Table.insert(), *args)
|
||||
|
||||
def delete(self, plist):
|
||||
state.Table.delete(plist.plist_state)
|
||||
return super().delete(plist)
|
||||
|
||||
|
||||
from . import track
|
15
db/sql.py
15
db/sql.py
|
@ -1,15 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import lib
|
||||
import sqlite3
|
||||
|
||||
File = lib.data.emmental_data / "emmental.sqlite"
|
||||
|
||||
Connection = sqlite3.connect(File, detect_types=sqlite3.PARSE_DECLTYPES, check_same_thread=False)
|
||||
Connection.row_factory = sqlite3.Row
|
||||
|
||||
commit = Connection.commit
|
||||
execute = Connection.execute
|
||||
|
||||
def optimize():
|
||||
Connection.execute("PRAGMA analysis_limit=1000")
|
||||
Connection.execute("PRAGMA optimize")
|
80
db/state.py
80
db/state.py
|
@ -1,80 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
#
|
||||
# Table: playlist_states
|
||||
# +-----------+--------+------+---------+------+
|
||||
# | plstateid | random | loop | current | sort |
|
||||
# +-----------+--------+------+---------+------+
|
||||
from gi.repository import GObject
|
||||
from . import sql
|
||||
from . import table
|
||||
|
||||
DefaultSort = [ "artists.sort ASC", "albums.release ASC", "albums.sort ASC",
|
||||
"discs.number ASC", "tracks.number ASC", "tracks.trackid ASC" ]
|
||||
|
||||
class PlaylistState(GObject.GObject):
|
||||
def __init__(self, row):
|
||||
GObject.GObject.__init__(self)
|
||||
self._plstateid = row["plstateid"]
|
||||
self._random = row["random"]
|
||||
self._loop = row["loop"]
|
||||
self._current = row["current"]
|
||||
self._sort = row["sort"]
|
||||
|
||||
@GObject.Property
|
||||
def rowid(self): return self._plstateid
|
||||
|
||||
@GObject.Property(type=bool,default=False)
|
||||
def random(self): return self._random
|
||||
|
||||
@random.setter
|
||||
def random(self, newval): self._random = self.update("random", newval)
|
||||
|
||||
@GObject.Property(type=bool,default=False)
|
||||
def loop(self): return self._loop
|
||||
|
||||
@loop.setter
|
||||
def loop(self, newval): self._loop = self.update("loop", newval)
|
||||
|
||||
@GObject.Property(type=int)
|
||||
def current(self): return self._current
|
||||
|
||||
@current.setter
|
||||
def current(self, newval): self._current = self.update("current", newval)
|
||||
|
||||
@GObject.Property(type=GObject.TYPE_PYOBJECT)
|
||||
def sort(self): return self._sort.split(",")
|
||||
|
||||
@sort.setter
|
||||
def sort(self, newval):
|
||||
if "tracks.trackid ASC" not in newval:
|
||||
newval.append("tracks.trackid ASC")
|
||||
self._sort = self.update("sort", ",".join(newval))
|
||||
|
||||
def update(self, column, newval):
|
||||
sql.execute(f"UPDATE playlist_states SET {column}=? WHERE plstateid=?",
|
||||
[ newval, self.rowid ])
|
||||
sql.commit()
|
||||
return newval
|
||||
|
||||
|
||||
class PlaylistStateTable(table.Table):
|
||||
def __init__(self):
|
||||
table.Table.__init__(self, "playlist_states")
|
||||
|
||||
def do_create(self):
|
||||
sql.execute("CREATE TABLE IF NOT EXISTS playlist_states "
|
||||
"(plstateid INTEGER PRIMARY KEY, "
|
||||
" random INTEGER DEFAULT 0, "
|
||||
" loop INTEGER DEFAULT 0, "
|
||||
" current INTEGER DEFAULT -1, "
|
||||
" sort TEXT)")
|
||||
|
||||
def do_factory(self, row):
|
||||
return PlaylistState(row)
|
||||
|
||||
def do_insert(self, random=False, loop=False, sort=DefaultSort):
|
||||
return sql.execute("INSERT INTO playlist_states (random, loop, sort) "
|
||||
"VALUES (?, ?, ?)", (random, loop, ",".join(sort)))
|
||||
|
||||
|
||||
Table = PlaylistStateTable()
|
115
db/table.py
115
db/table.py
|
@ -1,115 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gio
|
||||
from . import sql
|
||||
|
||||
class Table:
|
||||
def __init__(self, table):
|
||||
self.cache = dict()
|
||||
self.table = table
|
||||
self.do_create()
|
||||
|
||||
def do_create(self): raise NotImplementedError
|
||||
def do_factory(self, row): raise NotImplementedError
|
||||
def do_insert(self, *args): raise NotImplementedError
|
||||
def do_lookup(self, *args): raise NotImplementedError
|
||||
|
||||
def do_drop(self):
|
||||
sql.execute(f"DROP TABLE {self.table}")
|
||||
|
||||
def delete(self, obj):
|
||||
del self.cache[obj.rowid]
|
||||
sql.execute(f"DELETE FROM {self.table} WHERE rowid=?", [ obj.rowid ])
|
||||
|
||||
def factory(self, row):
|
||||
if row:
|
||||
return self.cache.setdefault(row[0], self.do_factory(row))
|
||||
|
||||
def find(self, *args):
|
||||
return ret if (ret := self.lookup(*args)) else self.insert(*args)
|
||||
|
||||
def get(self, rowid):
|
||||
if (row := self.cache.get(rowid)):
|
||||
return row
|
||||
cur = sql.execute(f"SELECT * FROM {self.table} WHERE rowid=?", [ rowid ])
|
||||
return self.factory(cur.fetchone())
|
||||
|
||||
def insert(self, *args, **kwargs):
|
||||
return self.get(self.do_insert(*args, **kwargs).lastrowid)
|
||||
|
||||
def lookup(self, *args):
|
||||
return self.factory(self.do_lookup(*args).fetchone())
|
||||
|
||||
def reset(self):
|
||||
self.do_drop()
|
||||
self.cache.clear()
|
||||
self.do_create()
|
||||
|
||||
|
||||
class Model(GObject.GObject, Gio.ListModel, Table):
|
||||
def __init__(self, table, order):
|
||||
GObject.GObject.__init__(self)
|
||||
self.order = order
|
||||
Table.__init__(self, table)
|
||||
|
||||
def do_get_item_type(self):
|
||||
return GObject.TYPE_PYOBJECT
|
||||
|
||||
def do_get_n_items(self):
|
||||
return sql.execute(f"SELECT COUNT(*) FROM {self.table}").fetchone()[0]
|
||||
|
||||
def do_get_item(self, n):
|
||||
cur = sql.execute(f"SELECT * FROM {self.table} ORDER BY {self.order} "
|
||||
"LIMIT 1 OFFSET ?", [ n ])
|
||||
return self.factory(cur.fetchone())
|
||||
|
||||
def get_item_index(self, item):
|
||||
cur = sql.execute("SELECT * FROM (SELECT rowid,ROW_NUMBER() "
|
||||
f"OVER (ORDER BY {self.order}) "
|
||||
f"FROM {self.table}) "
|
||||
"WHERE rowid=?", [ item.rowid ])
|
||||
return cur.fetchone()[1] - 1
|
||||
|
||||
def delete(self, item):
|
||||
pos = self.get_item_index(item)
|
||||
super().delete(item)
|
||||
self.emit("items-changed", pos, 1, 0)
|
||||
|
||||
def insert(self, *args):
|
||||
row = super().insert(*args)
|
||||
pos = self.get_item_index(row)
|
||||
self.emit("items-changed", pos, 0, 1)
|
||||
return row
|
||||
|
||||
def reset(self):
|
||||
n = self.get_n_items()
|
||||
super().reset()
|
||||
self.emit("items-changed", 0, n, self.get_n_items())
|
||||
|
||||
|
||||
class Child(Table):
|
||||
def __init__(self, table, parent, order):
|
||||
Table.__init__(self, table)
|
||||
self.parent = parent
|
||||
self.order = order
|
||||
|
||||
def get_n_children(self, parent):
|
||||
cur = sql.execute(f"SELECT COUNT(*) FROM {self.table} "
|
||||
f"WHERE {self.parent}=?", [ parent.rowid ])
|
||||
return cur.fetchone()[0]
|
||||
|
||||
def get_child(self, parent, n):
|
||||
cur = sql.execute(f"SELECT * FROM {self.table} WHERE {self.parent}=? "
|
||||
f"ORDER BY {self.order} LIMIT 1 OFFSET ?",
|
||||
[ parent.rowid, n ])
|
||||
return self.factory(cur.fetchone())
|
||||
|
||||
def get_child_index(self, parent, child):
|
||||
cur = sql.execute(f"SELECT * FROM (SELECT rowid,ROW_NUMBER() "
|
||||
f"OVER (ORDER BY {self.order}) "
|
||||
f"FROM {self.table} "
|
||||
f"WHERE {self.parent}=?)"
|
||||
f"WHERE rowid=?", [ parent.rowid, child.rowid ])
|
||||
return cur.fetchone()[1] - 1
|
||||
|
||||
def find(self, *args): raise NotImplementedError
|
|
@ -1,90 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import datetime
|
||||
import db
|
||||
import sqlite3
|
||||
import unittest
|
||||
from gi.repository import GObject
|
||||
|
||||
class TestAlbum(unittest.TestCase):
|
||||
def track_added(self, album, added):
|
||||
self.added = added
|
||||
|
||||
def track_removed(self, album, removed, adjusted_current):
|
||||
self.removed = (removed, False)
|
||||
|
||||
def setUp(self):
|
||||
db.reset()
|
||||
|
||||
def test_init(self):
|
||||
artist = db.artist.Table.find("Test Artist", "Test Sort")
|
||||
album = artist.find_album("Test Album", datetime.date(2021, 3, 18))
|
||||
self.assertIsInstance(album, db.playlist.ParentPlaylist)
|
||||
self.assertEqual(album.get_property("name"), "Test Album")
|
||||
self.assertEqual(album.get_property("icon-name"), "media-optical-cd-audio")
|
||||
self.assertEqual(album.get_property("release"), datetime.date(2021, 3, 18))
|
||||
self.assertEqual(album.get_child_table(), db.disc.Table)
|
||||
|
||||
def test_delete(self):
|
||||
artist = db.artist.Table.find("Test Artist", "Test Sort")
|
||||
album = artist.find_album("Test Album", datetime.date(2021, 3, 18))
|
||||
album.delete()
|
||||
self.assertIsNone(db.album.Table.lookup(artist, "Test Album",
|
||||
datetime.date(2021, 3, 18)))
|
||||
|
||||
def test_find_disc(self):
|
||||
artist = db.artist.Table.find("Test Artist", "Test Sort")
|
||||
album = artist.find_album("Test Album", datetime.date(2021, 3, 18))
|
||||
disc = album.find_disc(1, None)
|
||||
self.assertIsInstance(disc, db.disc.Disc)
|
||||
|
||||
def test_tracks(self):
|
||||
artist = db.artist.Table.find("Test Artist", "Test Sort")
|
||||
album = artist.find_album("Test Album", datetime.date(2021, 3, 18))
|
||||
album.connect("track-added", self.track_added)
|
||||
self.assertEqual(album.get_n_tracks(), 0)
|
||||
self.assertEqual(album.get_tracks(), [ ])
|
||||
|
||||
track = db.make_fake_track(1, 1, "Test Track", "/a/b/c/1.ogg")
|
||||
self.assertEqual(album.get_n_tracks(), 1)
|
||||
self.assertEqual(album.get_track(0), track)
|
||||
self.assertEqual(album.get_tracks(), [ track ])
|
||||
self.assertEqual(album.get_track_index(track), 0)
|
||||
self.assertEqual(self.added, track)
|
||||
|
||||
album.connect("track-removed", self.track_removed)
|
||||
db.track.Table.delete(track)
|
||||
self.assertEqual(album.get_tracks(), [ ])
|
||||
self.assertEqual(self.removed, (track, False))
|
||||
|
||||
|
||||
class TestAlbumTable(unittest.TestCase):
|
||||
def setUp(self):
|
||||
db.reset()
|
||||
|
||||
def test_init(self):
|
||||
table = db.album.AlbumTable()
|
||||
self.assertIsInstance(table, db.playlist.ChildModel)
|
||||
self.assertEqual(table.table, "albums")
|
||||
self.assertEqual(table.parent, "artistid")
|
||||
self.assertEqual(table.order, "sort")
|
||||
|
||||
self.assertIsInstance(db.album.Table, db.album.AlbumTable)
|
||||
db.sql.execute("SELECT albumid,artistid,plstateid,release,name,sort FROM albums")
|
||||
|
||||
def test_insert(self):
|
||||
artist = db.artist.Table.insert("Test Artist", "Test Sort")
|
||||
album = artist.find_album("Test Album", datetime.date(2021, 3, 18))
|
||||
|
||||
self.assertIsInstance(album, db.album.Album)
|
||||
self.assertEqual(album._name, "Test Album")
|
||||
self.assertEqual(album._rowkey, "albumid")
|
||||
|
||||
with self.assertRaises(sqlite3.IntegrityError):
|
||||
db.album.Table.insert(artist, "Test Album",
|
||||
datetime.date(2021, 3, 18))
|
||||
|
||||
def test_lookup(self):
|
||||
artist = db.artist.Table.insert("Test Artist", "Test Sort")
|
||||
album = artist.find_album("Test Album", datetime.date(2021, 3, 18))
|
||||
self.assertEqual(db.album.Table.lookup(artist, "Test Album", datetime.date(2021, 3, 18)), album)
|
||||
self.assertIsNone(db.album.Table.lookup(artist, "none", datetime.date(1, 1, 1)))
|
|
@ -1,82 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import datetime
|
||||
import db
|
||||
import sqlite3
|
||||
import unittest
|
||||
from gi.repository import GObject
|
||||
|
||||
class TestArtist(unittest.TestCase):
|
||||
def track_added(self, artist, added):
|
||||
self.added = added
|
||||
|
||||
def track_removed(self, artist, removed, adjusted_current):
|
||||
self.removed = (removed, adjusted_current)
|
||||
|
||||
def setUp(self):
|
||||
db.reset()
|
||||
|
||||
def test_init(self):
|
||||
artist = db.artist.Table.find("Test Artist", "Test Sort")
|
||||
self.assertIsInstance(artist, db.playlist.ParentPlaylist)
|
||||
self.assertEqual(artist.get_property("name"), "Test Artist")
|
||||
self.assertEqual(artist.get_property("icon-name"), "avatar-default-symbolic")
|
||||
self.assertEqual(artist.get_child_table(), db.album.Table)
|
||||
|
||||
def test_delete(self):
|
||||
artist = db.artist.Table.find("Test Artist", "Test Sort")
|
||||
artist.delete()
|
||||
self.assertIsNone(db.artist.Table.lookup("Test Artist"))
|
||||
|
||||
def test_find_album(self):
|
||||
artist = db.artist.Table.find("Test Artist", "Test Sort")
|
||||
album = artist.find_album("Test Album", datetime.date(2021, 3, 18))
|
||||
self.assertIsInstance(album, db.album.Album)
|
||||
|
||||
def test_tracks(self):
|
||||
artist = db.artist.Table.find("Test Artist", "Test Sort")
|
||||
artist.connect("track-added", self.track_added)
|
||||
self.assertEqual(artist.get_n_tracks(), 0)
|
||||
self.assertEqual(artist.get_tracks(), [ ])
|
||||
|
||||
track = db.make_fake_track(1, 1, "Test Track", "/a/b/c/1.ogg")
|
||||
self.assertEqual(artist.get_n_tracks(), 1)
|
||||
self.assertEqual(artist.get_track(0), track)
|
||||
self.assertEqual(artist.get_tracks(), [ track ])
|
||||
self.assertEqual(artist.get_track_index(track), 0)
|
||||
self.assertEqual(self.added, track)
|
||||
|
||||
artist.connect("track-removed", self.track_removed)
|
||||
db.track.Table.delete(track)
|
||||
self.assertEqual(artist.get_tracks(), [ ])
|
||||
self.assertEqual(self.removed, (track, False))
|
||||
|
||||
|
||||
class TestArtistTable(unittest.TestCase):
|
||||
def setUp(self):
|
||||
db.reset()
|
||||
|
||||
def test_init(self):
|
||||
table = db.artist.ArtistTable()
|
||||
self.assertIsInstance(table, db.playlist.Model)
|
||||
self.assertEqual(table.table, "artists")
|
||||
self.assertEqual(table.order, "sort")
|
||||
|
||||
self.assertIsInstance(db.artist.Table, db.artist.ArtistTable)
|
||||
db.sql.execute("SELECT artistid,plstateid,name,sort FROM artists")
|
||||
|
||||
def test_insert(self):
|
||||
table = db.artist.ArtistTable()
|
||||
artist = table.insert("Test Artist", "Test Sort")
|
||||
|
||||
self.assertIsInstance(artist, db.artist.Artist)
|
||||
self.assertEqual(artist._name, "Test Artist")
|
||||
self.assertEqual(artist._rowkey, "artistid")
|
||||
|
||||
with self.assertRaises(sqlite3.IntegrityError):
|
||||
db.artist.Table.insert("Test Artist", "Test Sort")
|
||||
|
||||
def test_lookup(self):
|
||||
table = db.artist.ArtistTable()
|
||||
artist = table.insert("Test Artist", "Test Sort")
|
||||
self.assertEqual(table.lookup("Test Artist"), artist)
|
||||
self.assertIsNone(table.lookup("none"))
|
|
@ -1,20 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import db
|
||||
import unittest
|
||||
|
||||
class TestDB(unittest.TestCase):
|
||||
def test_new_db(self):
|
||||
db.reset()
|
||||
self.assertTrue(db.new_db())
|
||||
db.make_fake_track(1, 1, "Test Track", "/a/b/c/1.ogg")
|
||||
self.assertFalse(db.new_db())
|
||||
|
||||
def test_find_playlist(self):
|
||||
track = db.make_fake_track(1, 1, "Test Track", "/a/b/c/1.ogg")
|
||||
collection = db.user.Table.find("Collection")
|
||||
genre = db.genre.Table.find("Test Genre")
|
||||
|
||||
self.assertIsNone(db.find_playlist(123456))
|
||||
for plist in [ collection, track.artist, track.album, track.disc,
|
||||
genre, track.decade, track.year, track.library ]:
|
||||
self.assertEqual(db.find_playlist(plist.plist_state.rowid), plist)
|
|
@ -1,93 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import db
|
||||
import sqlite3
|
||||
import unittest
|
||||
from gi.repository import GObject
|
||||
|
||||
class TestDecade(unittest.TestCase):
|
||||
def children_changed(self, decade, pos, rm, add):
|
||||
self.changed = (pos, rm, add)
|
||||
|
||||
def track_added(self, decade, added):
|
||||
self.added = added
|
||||
|
||||
def track_removed(self, decade, removed, adjusted_current):
|
||||
self.removed = (removed, adjusted_current)
|
||||
|
||||
def setUp(self):
|
||||
db.reset()
|
||||
|
||||
def test_init(self):
|
||||
decade = db.decade.Table.insert(2020)
|
||||
self.assertIsInstance(decade, db.playlist.ParentPlaylist)
|
||||
self.assertEqual(decade.get_property("name"), "2020s")
|
||||
self.assertEqual(decade.get_property("icon-name"), "x-office-calendar")
|
||||
self.assertEqual(decade.get_child_table(), db.year.Table)
|
||||
|
||||
def test_decade(self):
|
||||
decade = db.decade.Table.insert(2020)
|
||||
self.assertEqual(decade._decade, 2020)
|
||||
self.assertEqual(decade.get_property("decade"), 2020)
|
||||
|
||||
def test_delete(self):
|
||||
decade = db.decade.Table.find(2020)
|
||||
decade.delete()
|
||||
self.assertIsNone(db.decade.Table.lookup(2020))
|
||||
|
||||
def test_find_year(self):
|
||||
decade = db.decade.Table.insert(2020)
|
||||
decade.connect("children-changed", self.children_changed)
|
||||
|
||||
year = decade.find_year(2021)
|
||||
self.assertIsInstance(year, db.year.Year)
|
||||
self.assertEqual(self.changed, (0, 0, 1))
|
||||
|
||||
def test_tracks(self):
|
||||
decade = db.decade.Table.insert(2020)
|
||||
decade.connect("track-added", self.track_added)
|
||||
self.assertEqual(decade.get_n_tracks(), 0)
|
||||
self.assertEqual(decade.get_tracks(), [ ])
|
||||
|
||||
track = db.make_fake_track(1, 1, "Test Track", "/a/b/c/1.ogg")
|
||||
self.assertEqual(decade.get_n_tracks(), 1)
|
||||
self.assertEqual(decade.get_track(0), track)
|
||||
self.assertEqual(decade.get_tracks(), [ track ])
|
||||
self.assertEqual(decade.get_track_index(track), 0)
|
||||
self.assertEqual(self.added, track)
|
||||
|
||||
decade.connect("track-removed", self.track_removed)
|
||||
db.track.Table.delete(track)
|
||||
self.assertEqual(decade.get_tracks(), [ ])
|
||||
self.assertEqual(self.removed, (track, False))
|
||||
|
||||
|
||||
class TestDecadeTable(unittest.TestCase):
|
||||
def setUp(self):
|
||||
db.reset()
|
||||
|
||||
def test_init(self):
|
||||
table = db.decade.DecadeTable()
|
||||
self.assertIsInstance(table, db.playlist.Model)
|
||||
self.assertEqual(table.table, "decades")
|
||||
self.assertEqual(table.order, "decade")
|
||||
|
||||
self.assertIsInstance(db.decade.Table, db.decade.DecadeTable)
|
||||
db.sql.execute("SELECT decadeid,plstateid,decade FROM decades")
|
||||
|
||||
def test_insert(self):
|
||||
table = db.decade.DecadeTable()
|
||||
decade = table.insert(2020)
|
||||
|
||||
self.assertIsInstance(decade, db.decade.Decade)
|
||||
self.assertEqual(decade._decade, 2020)
|
||||
self.assertEqual(decade._rowkey, "decadeid")
|
||||
|
||||
with self.assertRaises(sqlite3.IntegrityError):
|
||||
db.decade.Table.insert(2020)
|
||||
|
||||
def test_lookup(self):
|
||||
table = db.decade.DecadeTable()
|
||||
decade = table.insert(2020)
|
||||
|
||||
self.assertEqual(table.lookup(2020), decade)
|
||||
self.assertIsNone(table.lookup(2021))
|
105
db/test_disc.py
105
db/test_disc.py
|
@ -1,105 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import datetime
|
||||
import db
|
||||
import sqlite3
|
||||
import unittest
|
||||
from gi.repository import GObject
|
||||
|
||||
class TestDisc(unittest.TestCase):
|
||||
def track_added(self, disc, added):
|
||||
self.added = added
|
||||
|
||||
def track_removed(self, disc, removed, adjusted_current):
|
||||
self. removed = (removed, adjusted_current)
|
||||
|
||||
def setUp(self):
|
||||
db.reset()
|
||||
|
||||
def make_disc(self, artist, album, discno, subtitle):
|
||||
artist = db.artist.Table.find(artist, artist)
|
||||
album = artist.find_album(album, datetime.date(2021, 3, 18))
|
||||
return album.find_disc(discno, subtitle)
|
||||
|
||||
def test_init(self):
|
||||
disc = self.make_disc("Test Artist", "Test Album", 1, "")
|
||||
self.assertIsInstance(disc, db.playlist.Playlist)
|
||||
self.assertEqual(disc.get_property("icon-name"), "media-optical")
|
||||
self.assertEqual(disc.get_property("number"), 1)
|
||||
|
||||
def test_delete(self):
|
||||
artist = db.artist.Table.find("Test Artist", "Test Artist")
|
||||
album = artist.find_album("Test Album", datetime.date(2021, 3, 18))
|
||||
disc = album.find_disc(1, "")
|
||||
disc.delete()
|
||||
self.assertIsNone(db.disc.Table.lookup(album, 1))
|
||||
|
||||
def test_subtitle(self):
|
||||
disc = self.make_disc("Test Artist", "Test Album", 1, "Test Subtitle")
|
||||
self.assertEqual(disc._subtitle, "Test Subtitle")
|
||||
self.assertEqual(disc.get_property("subtitle"), "Test Subtitle")
|
||||
self.assertEqual(disc.get_property("name"), "1: Test Subtitle")
|
||||
|
||||
def test_subtitle_none(self):
|
||||
disc = self.make_disc("Test Artist", "Test Album", 1, None)
|
||||
self.assertEqual(disc._subtitle, None)
|
||||
self.assertEqual(disc.get_property("subtitle"), "")
|
||||
self.assertEqual(disc.get_property("name"), "Disc 1")
|
||||
|
||||
def test_subtitle_len_0(self):
|
||||
disc = self.make_disc("Test Artist", "Test Album", 1, "")
|
||||
self.assertEqual(disc._subtitle, "")
|
||||
self.assertEqual(disc.get_property("subtitle"), "")
|
||||
self.assertEqual(disc.get_property("name"), "Disc 1")
|
||||
|
||||
def test_tracks(self):
|
||||
disc = self.make_disc("Test Artist", "Test Album", 1, "Test Subtitle")
|
||||
disc.connect("track-added", self.track_added)
|
||||
self.assertEqual(disc.get_n_tracks(), 0)
|
||||
self.assertEqual(disc.get_tracks(), [ ])
|
||||
|
||||
track = db.make_fake_track(1, 1, "Test Track", "/a/b/c/1.ogg")
|
||||
self.assertEqual(disc.get_n_tracks(), 1)
|
||||
self.assertEqual(disc.get_track(0), track)
|
||||
self.assertEqual(disc.get_tracks(), [ track ])
|
||||
self.assertEqual(disc.get_track_index(track), 0)
|
||||
self.assertEqual(self.added, track)
|
||||
|
||||
disc.connect("track-removed", self.track_removed)
|
||||
db.track.Table.delete(track)
|
||||
self.assertEqual(disc.get_tracks(), [ ])
|
||||
self.assertEqual(self.removed, (track, False))
|
||||
|
||||
|
||||
class TestDiscTable(unittest.TestCase):
|
||||
def setUp(self):
|
||||
db.reset()
|
||||
|
||||
def test_init(self):
|
||||
table = db.disc.DiscTable()
|
||||
self.assertIsInstance(table, db.playlist.ChildModel)
|
||||
self.assertEqual(table.table, "discs")
|
||||
self.assertEqual(table.parent, "albumid")
|
||||
self.assertEqual(table.order, "number")
|
||||
|
||||
self.assertIsInstance(db.disc.Table, db.disc.DiscTable)
|
||||
db.sql.execute("SELECT discid,albumid,plstateid,number,subtitle FROM discs")
|
||||
|
||||
def test_insert(self):
|
||||
artist = db.artist.Table.insert("Test Artist", "Test Sort")
|
||||
album = artist.find_album("Test Album", datetime.date(2021, 3, 18))
|
||||
disc = db.disc.Table.insert(album, 1, "subtitle")
|
||||
|
||||
self.assertIsInstance(disc, db.disc.Disc)
|
||||
self.assertEqual(disc._number, 1)
|
||||
self.assertEqual(disc._subtitle, "subtitle")
|
||||
self.assertEqual(disc._rowkey, "discid")
|
||||
|
||||
with self.assertRaises(sqlite3.IntegrityError):
|
||||
db.disc.Table.insert(album, 1, "subtitle")
|
||||
|
||||
def test_lookup(self):
|
||||
artist = db.artist.Table.insert("Test Artist", "Test Sort")
|
||||
album = artist.find_album("Test Album", datetime.date(2021, 3, 18))
|
||||
disc = album.find_disc(1, None)
|
||||
self.assertEqual(db.disc.Table.lookup(album, 1), disc)
|
||||
self.assertIsNone(db.disc.Table.lookup(album, "none"))
|
|
@ -1,85 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import db
|
||||
import sqlite3
|
||||
import unittest
|
||||
from gi.repository import GObject
|
||||
from . import sql
|
||||
|
||||
|
||||
class TestGenre(unittest.TestCase):
|
||||
def track_added(self, genre, added):
|
||||
self.added = added
|
||||
|
||||
def track_removed(self, genre, removed, adjusted_current):
|
||||
self.removed = (removed, adjusted_current)
|
||||
|
||||
def setUp(self):
|
||||
db.reset()
|
||||
|
||||
def test_init(self):
|
||||
genre = db.genre.Table.find("Test Genre")
|
||||
self.assertIsInstance(genre, db.playlist.MappedPlaylist)
|
||||
self.assertEqual(genre._name, "Test Genre")
|
||||
self.assertEqual(genre.get_property("name"), "Test Genre")
|
||||
self.assertEqual(genre.get_property("icon-name"), "emblem-generic")
|
||||
self.assertEqual(genre.get_property("map-table"), "genre_map")
|
||||
|
||||
def test_delete(self):
|
||||
track = db.make_fake_track(1, 1, "Test Track", "/a/b/c/1.ogg")
|
||||
genre = db.genre.Table.find("Test Genre")
|
||||
genre.add_track(track)
|
||||
genre.delete()
|
||||
self.assertEqual(genre.get_n_tracks(), 0)
|
||||
self.assertIsNone(db.genre.Table.lookup("Test Genre"))
|
||||
|
||||
def test_add_remove_track(self):
|
||||
genre = db.genre.Table.find("Test Genre")
|
||||
genre.connect("track-added", self.track_added)
|
||||
self.assertEqual(genre.get_n_tracks(), 0)
|
||||
self.assertEqual(genre.get_tracks(), [ ])
|
||||
|
||||
track = db.make_fake_track(1, 1, "Test Track", "/a/b/c/1.ogg")
|
||||
self.assertTrue(genre.add_track(track))
|
||||
self.assertEqual(genre.get_n_tracks(), 1)
|
||||
self.assertEqual(genre.get_track(0), track)
|
||||
self.assertEqual(genre.get_tracks(), [ track ])
|
||||
self.assertEqual(genre.get_track_index(track), 0)
|
||||
self.assertEqual(self.added, track)
|
||||
|
||||
genre.connect("track-removed", self.track_removed)
|
||||
self.assertTrue(genre.remove_track(track))
|
||||
self.assertFalse(genre.remove_track(track))
|
||||
self.assertEqual(genre.get_n_tracks(), 0)
|
||||
self.assertEqual(genre.get_tracks(), [ ])
|
||||
self.assertEqual(self.removed, (track, False))
|
||||
|
||||
|
||||
class TestGenreTable(unittest.TestCase):
|
||||
def setUp(self):
|
||||
db.reset()
|
||||
|
||||
def test_init(self):
|
||||
table = db.genre.GenreTable()
|
||||
self.assertIsInstance(table, db.playlist.Model)
|
||||
self.assertEqual(table.table, "genres")
|
||||
self.assertEqual(table.order, "sort")
|
||||
|
||||
self.assertIsInstance(db.genre.Table, db.genre.GenreTable)
|
||||
db.sql.execute("SELECT genreid,plstateid,name,sort FROM genres")
|
||||
db.sql.execute("SELECT genreid,trackid FROM genre_map")
|
||||
|
||||
def test_insert(self):
|
||||
table = db.genre.GenreTable()
|
||||
genre = table.insert("Test Genre")
|
||||
|
||||
self.assertIsInstance(genre, db.genre.Genre)
|
||||
self.assertEqual(genre._name, "Test Genre")
|
||||
self.assertEqual(genre._rowkey, "genreid")
|
||||
|
||||
with self.assertRaises(sqlite3.IntegrityError):
|
||||
db.genre.Table.insert("Test Genre")
|
||||
|
||||
def test_lookup(self):
|
||||
genre = db.genre.Table.insert("Test Genre")
|
||||
self.assertEqual(db.genre.Table.lookup("Test Genre"), genre)
|
||||
self.assertIsNone(db.genre.Table.lookup("none"))
|
|
@ -1,103 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import db
|
||||
import pathlib
|
||||
import sqlite3
|
||||
import unittest
|
||||
from gi.repository import GObject
|
||||
|
||||
|
||||
class TestLibrary(unittest.TestCase):
|
||||
def track_added(self, library, added):
|
||||
self.added = added
|
||||
|
||||
def track_removed(self, library, removed, adjusted_current):
|
||||
self. removed = (removed, adjusted_current)
|
||||
|
||||
def setUp(self):
|
||||
db.reset()
|
||||
|
||||
def test_init(self):
|
||||
library = db.library.Table.insert(pathlib.Path("/a/b/c"))
|
||||
self.assertIsInstance(library, db.playlist.Playlist)
|
||||
self.assertEqual(library.get_property("name"), "/a/b/c")
|
||||
self.assertEqual(library.get_property("icon-name"), "folder-music")
|
||||
|
||||
def test_delete(self):
|
||||
library = db.library.Table.find(pathlib.Path("/a/b/c"))
|
||||
library.delete()
|
||||
self.assertIsNone(db.library.Table.lookup(pathlib.Path("/a/b/c")))
|
||||
|
||||
def test_path(self):
|
||||
library = db.library.Table.insert(pathlib.Path("/a/b/c"))
|
||||
self.assertIsInstance(library._path, pathlib.Path)
|
||||
self.assertEqual(library.get_property("path"), library._path)
|
||||
|
||||
def test_enabled(self):
|
||||
library = db.library.Table.insert(pathlib.Path("/a/b/c"))
|
||||
self.assertTrue(library._enabled)
|
||||
self.assertTrue(library.get_property("enabled"))
|
||||
|
||||
library.enabled = False
|
||||
self.assertFalse(library._enabled)
|
||||
|
||||
def test_tracks(self):
|
||||
library = db.library.Table.insert(pathlib.Path("/a/b/c"))
|
||||
library.connect("track-added", self.track_added)
|
||||
self.assertEqual(library.get_n_tracks(), 0)
|
||||
self.assertEqual(library.get_tracks(), [ ])
|
||||
|
||||
track = db.make_fake_track(1, 1, "Test Track", "/a/b/c/1.ogg")
|
||||
self.assertEqual(library.get_n_tracks(), 1)
|
||||
self.assertEqual(library.get_track(0), track)
|
||||
self.assertEqual(library.get_tracks(), [ track ])
|
||||
self.assertEqual(library.get_track_index(track), 0)
|
||||
self.assertEqual(self.added, track)
|
||||
|
||||
library.connect("track-removed", self.track_removed)
|
||||
db.track.Table.delete(track)
|
||||
self.assertEqual(library.get_tracks(), [ ])
|
||||
self.assertEqual(self.removed, (track, False))
|
||||
|
||||
|
||||
class TestLibraryTable(unittest.TestCase):
|
||||
def setUp(self):
|
||||
db.reset()
|
||||
|
||||
def test_init(self):
|
||||
table = db.library.LibraryTable()
|
||||
self.assertIsInstance(table, db.playlist.Model)
|
||||
self.assertIsInstance(table, db.table.Model)
|
||||
self.assertEqual(table.table, "libraries")
|
||||
self.assertEqual(table.order, "path")
|
||||
|
||||
self.assertIsInstance(db.library.Table, db.library.LibraryTable)
|
||||
db.sql.execute("SELECT libraryid,plstateid,enabled,path FROM libraries")
|
||||
|
||||
def test_insert(self):
|
||||
table = db.library.LibraryTable()
|
||||
library = table.insert(pathlib.Path("/a/b/c"))
|
||||
|
||||
self.assertIsInstance(library, db.library.Library)
|
||||
self.assertIsInstance(library._plstate, db.state.PlaylistState)
|
||||
self.assertEqual(library._rowkey, "libraryid")
|
||||
self.assertEqual(library._path, pathlib.Path("/a/b/c"))
|
||||
self.assertTrue(library._enabled)
|
||||
|
||||
with self.assertRaises(sqlite3.IntegrityError):
|
||||
db.library.Table.insert(pathlib.Path("/a/b/c"))
|
||||
|
||||
def test_delete(self):
|
||||
table = db.library.LibraryTable()
|
||||
library = table.insert(pathlib.Path("/a/b/c"))
|
||||
state = library.plist_state
|
||||
|
||||
table.delete(library)
|
||||
self.assertIsNone(db.library.Table.lookup(pathlib.Path("/a/b/c")))
|
||||
self.assertIsNone(db.state.Table.get(state.rowid))
|
||||
|
||||
def test_lookup(self):
|
||||
table = db.library.LibraryTable()
|
||||
library = table.insert(pathlib.Path("/a/b/c"))
|
||||
|
||||
self.assertEqual(table.lookup(pathlib.Path("/a/b/c")), library)
|
||||
self.assertIsNone(table.lookup(pathlib.Path("/a/b/d")))
|
|
@ -1,245 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import db
|
||||
import random
|
||||
import unittest
|
||||
from gi.repository import GObject
|
||||
from . import playlist
|
||||
|
||||
class TestRow:
|
||||
def __init__(self): pass
|
||||
def keys(self): return [ "testid", "plstateid" ]
|
||||
def __getitem__(self, key):
|
||||
if key == "testid" or key == 0: return 1
|
||||
return 10
|
||||
|
||||
class TestPlaylist(unittest.TestCase):
|
||||
def setUp(self): db.reset()
|
||||
|
||||
def on_refresh(self, plist): self.refreshed = True
|
||||
|
||||
def test_init(self):
|
||||
db.reset()
|
||||
plist = playlist.Playlist(TestRow(), "missing-icon")
|
||||
|
||||
self.assertIsInstance(plist, GObject.GObject)
|
||||
self.assertFalse(plist.has_children())
|
||||
self.assertFalse(plist.can_add_remove_tracks())
|
||||
|
||||
self.assertEqual(plist._rowid, 1)
|
||||
self.assertEqual(plist._rowkey, "testid")
|
||||
self.assertEqual(plist._icon_name, "missing-icon")
|
||||
|
||||
self.assertEqual(plist.get_property("rowid"), 1)
|
||||
self.assertEqual(plist.get_property("rowkey"), "testid")
|
||||
self.assertEqual(plist.get_property("icon-name"), "missing-icon")
|
||||
|
||||
self.assertIsNone(plist._plstate)
|
||||
self.assertIsNone(plist.get_property("plist_state"))
|
||||
|
||||
with self.assertRaises(NotImplementedError):
|
||||
plist.get_property("name")
|
||||
with self.assertRaises(NotImplementedError):
|
||||
plist.delete()
|
||||
|
||||
def test_add_track(self):
|
||||
plist = db.user.Table.find("Test Playlist")
|
||||
track1 = db.make_fake_track(1, 1, "Track 1", "/a/b/c/1.ogg")
|
||||
track2 = db.make_fake_track(2, 2, "Track 2", "/a/b/c/2.ogg")
|
||||
track3 = db.make_fake_track(3, 3, "Track 3", "/a/b/c/3.ogg")
|
||||
plist.sort = [ "tracks.number ASC" ]
|
||||
|
||||
plist.add_track(track2)
|
||||
self.assertEqual(plist.get_track(0), track2)
|
||||
self.assertEqual(plist.current, -1)
|
||||
self.assertFalse(plist.track_adjusts_current(track2))
|
||||
|
||||
plist.current = 0
|
||||
self.assertTrue(plist.track_adjusts_current(track2))
|
||||
|
||||
plist.add_track(track1)
|
||||
self.assertEqual(plist.get_track(0), track1)
|
||||
self.assertEqual(plist.get_track(1), track2)
|
||||
self.assertEqual(plist.current, 1)
|
||||
|
||||
plist.add_track(track3)
|
||||
self.assertEqual(plist.get_track(0), track1)
|
||||
self.assertEqual(plist.get_track(1), track2)
|
||||
self.assertEqual(plist.get_track(2), track3)
|
||||
self.assertEqual(plist.current, 1)
|
||||
|
||||
def test_remove_track(self):
|
||||
plist = db.user.Table.find("Test Playlist")
|
||||
track1 = db.make_fake_track(1, 1, "Track 1", "/a/b/c/1.ogg")
|
||||
track2 = db.make_fake_track(2, 2, "Track 2", "/a/b/c/2.ogg")
|
||||
track3 = db.make_fake_track(3, 3, "Track 3", "/a/b/c/3.ogg")
|
||||
plist.sort = [ "tracks.number ASC" ]
|
||||
|
||||
plist.add_track(track1)
|
||||
plist.add_track(track2)
|
||||
plist.add_track(track3)
|
||||
plist.current = 1
|
||||
|
||||
self.assertTrue(plist.track_adjusts_current(track1))
|
||||
self.assertTrue(plist.track_adjusts_current(track2))
|
||||
self.assertFalse(plist.track_adjusts_current(track3))
|
||||
|
||||
plist.remove_track(track3)
|
||||
self.assertEqual(plist.current, 1)
|
||||
plist.remove_track(track1)
|
||||
self.assertEqual(plist.current, 0)
|
||||
plist.remove_track(track2)
|
||||
self.assertEqual(plist.current, -1)
|
||||
|
||||
def test_current(self):
|
||||
plist = db.user.Table.find("Test Playlist")
|
||||
self.assertEqual(plist.get_property("current"), -1)
|
||||
self.assertIsNone(plist.get_current_track())
|
||||
plist.set_property("current", 1)
|
||||
self.assertEqual(plist.get_property("current"), -1)
|
||||
|
||||
plist.add_track(db.make_fake_track(1, 1, "Track 1", "/a/b/c/1.ogg"))
|
||||
plist.add_track(db.make_fake_track(2, 2, "Track 2", "/a/b/c/2.ogg"))
|
||||
|
||||
plist.set_property("current", 0)
|
||||
self.assertEqual(plist.get_property("current"), 0)
|
||||
self.assertEqual(plist.get_current_track(), plist.get_track(0))
|
||||
|
||||
plist.set_property("current", 1)
|
||||
self.assertEqual(plist.get_property("current"), 1)
|
||||
self.assertEqual(plist.get_current_track(), plist.get_track(1))
|
||||
|
||||
plist.set_property("current", 2)
|
||||
self.assertEqual(plist.get_property("current"), 2)
|
||||
self.assertIsNone(plist.get_current_track())
|
||||
|
||||
plist.set_property("current", 3)
|
||||
self.assertEqual(plist.get_property("current"), 2)
|
||||
self.assertIsNone(plist.get_current_track())
|
||||
|
||||
def test_loop(self):
|
||||
plist = db.user.Table.find("Test Playlist")
|
||||
self.assertFalse(plist.get_property("loop"))
|
||||
plist.set_property("loop", True)
|
||||
self.assertTrue(plist.get_property("loop"))
|
||||
self.assertTrue(plist.plist_state.get_property("loop"))
|
||||
|
||||
plist.set_property("current", 0)
|
||||
self.assertEqual(plist.get_property("current"), -1)
|
||||
|
||||
plist.add_track(db.make_fake_track(1, 1, "Track 1", "/a/b/c/1.ogg"))
|
||||
plist.add_track(db.make_fake_track(2, 2, "Track 2", "/a/b/c/2.ogg"))
|
||||
|
||||
plist.set_property("current", 0)
|
||||
self.assertEqual(plist.get_current_track(), plist.get_track(0))
|
||||
plist.set_property("current", 1)
|
||||
self.assertEqual(plist.get_current_track(), plist.get_track(1))
|
||||
plist.set_property("current", 2)
|
||||
self.assertEqual(plist.get_property("current"), 0)
|
||||
self.assertEqual(plist.get_current_track(), plist.get_track(0))
|
||||
|
||||
def test_random(self):
|
||||
plist = db.user.Table.find("Test Playlist")
|
||||
self.assertFalse(plist.get_property("random"))
|
||||
plist.set_property("random", True)
|
||||
self.assertTrue(plist.get_property("random"))
|
||||
self.assertTrue(plist.plist_state.get_property("random"))
|
||||
|
||||
plist.set_property("current", 0)
|
||||
self.assertEqual(plist.get_property("current"), -1)
|
||||
|
||||
plist.add_track(db.make_fake_track(1, 1, "Track 1", "/a/b/c/1.ogg"))
|
||||
plist.add_track(db.make_fake_track(2, 2, "Track 2", "/a/b/c/2.ogg"))
|
||||
|
||||
plist.set_property("current", 0)
|
||||
self.assertEqual(plist.get_current_track(), plist.get_track(0))
|
||||
plist.set_property("current", 5)
|
||||
self.assertEqual(plist.get_property("current"), 1)
|
||||
self.assertEqual(plist.get_current_track(), plist.get_track(1))
|
||||
|
||||
def test_sort(self):
|
||||
plist = db.user.Table.find("Test Playlist")
|
||||
plist.connect("refreshed", self.on_refresh)
|
||||
self.assertEqual(plist.get_property("sort"), plist.plist_state.sort)
|
||||
|
||||
plist.set_property("sort", [ "tracks.number ASC" ])
|
||||
self.assertEqual(plist.get_property("sort"),
|
||||
[ "tracks.number ASC", "tracks.trackid ASC" ])
|
||||
self.assertEqual(plist.plist_state.get_property("sort"),
|
||||
[ "tracks.number ASC", "tracks.trackid ASC" ])
|
||||
self.assertEqual(plist.get_property("current"), -1)
|
||||
self.assertTrue(self.refreshed)
|
||||
|
||||
plist.add_track(db.make_fake_track(1, 1, "Track 1", "/a/b/c/1.ogg"))
|
||||
plist.add_track(db.make_fake_track(2, 2, "Track 2", "/a/b/c/2.ogg"))
|
||||
|
||||
plist.set_property("sort", [ "tracks.number DESC" ])
|
||||
self.assertEqual(plist.get_property("sort"),
|
||||
[ "tracks.number DESC", "tracks.trackid ASC" ])
|
||||
self.assertEqual(plist.get_property("current"), -1)
|
||||
|
||||
plist.set_property("current", 0)
|
||||
self.assertEqual(plist.get_current_track().number, 2)
|
||||
plist.set_property("sort", [ "tracks.number ASC" ])
|
||||
self.assertEqual(plist.get_property("current"), 1)
|
||||
|
||||
def test_passthrough_plstateid(self):
|
||||
plist = db.user.Table.find("Test Playlist")
|
||||
self.assertEqual(plist.plstateid, plist.plist_state.rowid)
|
||||
|
||||
def test_next_track(self):
|
||||
plist = db.user.Table.find("Test Playlist")
|
||||
self.assertIsNone(plist.next_track())
|
||||
self.assertEqual(plist.get_property("current"), -1)
|
||||
|
||||
plist.add_track(db.make_fake_track(1, 1, "Track 1", "/a/b/c/1.ogg"))
|
||||
plist.add_track(db.make_fake_track(2, 2, "Track 2", "/a/b/c/2.ogg"))
|
||||
self.assertEqual(plist.next_track(), plist.get_track(0))
|
||||
self.assertEqual(plist.next_track(), plist.get_track(1))
|
||||
self.assertIsNone(plist.next_track())
|
||||
self.assertIsNone(plist.next_track())
|
||||
|
||||
def test_random_next_track(self):
|
||||
plist = db.user.Table.find("Test Playlist")
|
||||
plist.random = True
|
||||
self.assertIsNone(plist.next_track())
|
||||
self.assertEqual(plist.get_property("current"), -1)
|
||||
|
||||
plist.add_track(db.make_fake_track(1, 1, "Track 1", "/a/b/c/1.ogg"))
|
||||
self.assertEqual(plist.next_track(), plist.get_track(0))
|
||||
self.assertEqual(plist.get_property("current"), 0)
|
||||
|
||||
plist.add_track(db.make_fake_track(2, 2, "Track 2", "/a/b/c/2.ogg"))
|
||||
plist.add_track(db.make_fake_track(3, 3, "Track 3", "/a/b/c/3.ogg"))
|
||||
|
||||
random.seed(20210318)
|
||||
plist.current = -1
|
||||
self.assertEqual(plist.next_track(), plist.get_track(1))
|
||||
self.assertEqual(plist.next_track(), plist.get_track(2))
|
||||
self.assertEqual(plist.next_track(), plist.get_track(0))
|
||||
self.assertEqual(plist.next_track(), plist.get_track(2))
|
||||
|
||||
|
||||
class TestMappedPlaylist(unittest.TestCase):
|
||||
def test_init(self):
|
||||
mapped = playlist.MappedPlaylist(TestRow(), "missing-icon", "test_map")
|
||||
|
||||
self.assertIsInstance(mapped, playlist.Playlist)
|
||||
self.assertEqual(mapped._map_table, "test_map")
|
||||
self.assertEqual(mapped.get_property("map-table"), "test_map")
|
||||
|
||||
|
||||
class TestParentPlaylist(unittest.TestCase):
|
||||
def test_init(self):
|
||||
parent = playlist.ParentPlaylist(TestRow(), "missing-icon")
|
||||
|
||||
self.assertIsInstance(parent, playlist.Playlist)
|
||||
self.assertTrue(parent.has_children())
|
||||
|
||||
with self.assertRaises(NotImplementedError):
|
||||
parent.get_child_table()
|
||||
with self.assertRaises(NotImplementedError):
|
||||
parent.get_n_children()
|
||||
with self.assertRaises(NotImplementedError):
|
||||
parent.get_child(0)
|
||||
with self.assertRaises(NotImplementedError):
|
||||
parent.get_child_index(0)
|
|
@ -1,14 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import lib
|
||||
import sqlite3
|
||||
import unittest
|
||||
from . import sql
|
||||
|
||||
class TestSQL(unittest.TestCase):
|
||||
def test_init(self):
|
||||
self.assertEqual(sql.File, lib.data.emmental_data / "emmental.sqlite")
|
||||
self.assertIsInstance(sql.Connection, sqlite3.Connection)
|
||||
self.assertEqual(sql.Connection.row_factory, sqlite3.Row)
|
||||
|
||||
self.assertEqual(sql.commit, sql.Connection.commit)
|
||||
self.assertEqual(sql.execute, sql.Connection.execute)
|
|
@ -1,82 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import db
|
||||
import sqlite3
|
||||
import unittest
|
||||
from gi.repository import GObject
|
||||
|
||||
|
||||
class TestPlaylistState(unittest.TestCase):
|
||||
def test_init(self):
|
||||
state = db.state.Table.insert()
|
||||
|
||||
self.assertIsInstance(state, GObject.GObject)
|
||||
self.assertIsNotNone(state._plstateid)
|
||||
self.assertEqual(state.rowid, state._plstateid)
|
||||
|
||||
def test_random(self):
|
||||
state = db.state.Table.insert()
|
||||
self.assertFalse(state._random)
|
||||
self.assertFalse(state.get_property("random"))
|
||||
|
||||
state.random = True
|
||||
self.assertTrue(state._random)
|
||||
|
||||
def test_loop(self):
|
||||
state = db.state.Table.insert()
|
||||
self.assertFalse(state._loop)
|
||||
self.assertFalse(state.get_property("loop"))
|
||||
|
||||
state.loop = True
|
||||
self.assertTrue(state._loop)
|
||||
|
||||
def test_current(self):
|
||||
state = db.state.Table.insert()
|
||||
self.assertEqual(state._current, -1)
|
||||
self.assertEqual(state.current, -1)
|
||||
|
||||
state.current = 3
|
||||
self.assertEqual(state._current, 3)
|
||||
|
||||
def test_sort(self):
|
||||
state = db.state.Table.insert()
|
||||
self.assertEqual(state._sort, ",".join(db.state.DefaultSort))
|
||||
self.assertEqual(state.sort, db.state.DefaultSort)
|
||||
|
||||
state.sort = [ "test", "sort" ]
|
||||
self.assertEqual(state._sort, "test,sort,tracks.trackid ASC" )
|
||||
state.sort = [ ]
|
||||
self.assertEqual(state._sort, "tracks.trackid ASC" )
|
||||
|
||||
|
||||
class TestPlaylistStateTable(unittest.TestCase):
|
||||
def setUp(self):
|
||||
db.reset()
|
||||
|
||||
def test_init(self):
|
||||
table = db.state.PlaylistStateTable()
|
||||
self.assertIsInstance(table, db.table.Table)
|
||||
self.assertEqual(table.table, "playlist_states")
|
||||
|
||||
self.assertIsInstance(db.state.Table, db.state.PlaylistStateTable)
|
||||
db.sql.execute("SELECT plstateid,random,loop,current,sort "
|
||||
"FROM playlist_states")
|
||||
|
||||
def test_default_sort(self):
|
||||
self.assertEqual(db.state.DefaultSort[0], "artists.sort ASC")
|
||||
self.assertEqual(db.state.DefaultSort[1], "albums.release ASC")
|
||||
self.assertEqual(db.state.DefaultSort[2], "albums.sort ASC")
|
||||
self.assertEqual(db.state.DefaultSort[3], "discs.number ASC")
|
||||
self.assertEqual(db.state.DefaultSort[4], "tracks.number ASC")
|
||||
|
||||
def test_insert(self):
|
||||
table = db.state.PlaylistStateTable()
|
||||
state = table.insert()
|
||||
|
||||
self.assertFalse(state.random)
|
||||
self.assertFalse(state.loop)
|
||||
self.assertEqual(state.current, -1)
|
||||
self.assertEqual(state.sort, db.state.DefaultSort)
|
||||
|
||||
def test_lookup(self):
|
||||
with self.assertRaises(NotImplementedError):
|
||||
db.state.Table.lookup()
|
189
db/test_table.py
189
db/test_table.py
|
@ -1,189 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import unittest
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gio
|
||||
from . import sql
|
||||
from . import table
|
||||
|
||||
class FakeRow(GObject.GObject):
|
||||
def __init__(self, data):
|
||||
GObject.GObject.__init__(self)
|
||||
self.rowid = data["fakeid"]
|
||||
self.name = data["name"]
|
||||
|
||||
class FakeTable(table.Table):
|
||||
def __init__(self):
|
||||
table.Table.__init__(self, "fake_table")
|
||||
self.reset()
|
||||
|
||||
def do_create(self):
|
||||
sql.execute("CREATE TABLE IF NOT EXISTS fake_table "
|
||||
"(fakeid INTEGER PRIMARY KEY, name TEXT UNIQUE)")
|
||||
|
||||
def do_factory(self, row):
|
||||
return FakeRow(row)
|
||||
|
||||
def do_insert(self, name):
|
||||
return sql.execute("INSERT INTO fake_table (name) VALUES (?)", [ name ])
|
||||
|
||||
def do_lookup(self, name):
|
||||
return sql.execute("SELECT * FROM fake_table WHERE name=?", [ name ])
|
||||
|
||||
class FakeModel(table.Model, FakeTable):
|
||||
def __init__(self):
|
||||
table.Model.__init__(self, "fake_table", "lower(name)")
|
||||
self.reset()
|
||||
|
||||
class FakeChild(table.Child):
|
||||
def __init__(self):
|
||||
table.Child.__init__(self, "fake_child", "parentid", "lower(name)")
|
||||
self.reset()
|
||||
|
||||
def do_create(self):
|
||||
sql.execute("CREATE TABLE IF NOT EXISTS fake_child "
|
||||
"(fakeid INTEGER PRIMARY KEY, "
|
||||
"parentid INTEGER, "
|
||||
"name TEXT UNIQUE, "
|
||||
"FOREIGN KEY(parentid) REFERENCES fake_table(fakeid))")
|
||||
|
||||
def do_factory(self, row):
|
||||
return FakeRow(row)
|
||||
|
||||
def do_insert(self, parent, name):
|
||||
return sql.execute("INSERT INTO fake_child (parentid, name) "
|
||||
"VALUES (?,?)", [ parent.rowid, name ])
|
||||
|
||||
|
||||
class TestTable(unittest.TestCase):
|
||||
def test_init(self):
|
||||
fake = FakeTable()
|
||||
self.assertIsInstance(fake.cache, dict)
|
||||
self.assertEqual(fake.table, "fake_table")
|
||||
|
||||
def test_interface(self):
|
||||
with self.assertRaises(NotImplementedError):
|
||||
table.Table.do_create(None)
|
||||
with self.assertRaises(NotImplementedError):
|
||||
table.Table.do_factory(None, None)
|
||||
with self.assertRaises(NotImplementedError):
|
||||
table.Table.do_insert(None, "Text")
|
||||
with self.assertRaises(NotImplementedError):
|
||||
table.Table.do_lookup(None, "Text")
|
||||
|
||||
def test_insert_delete(self):
|
||||
fake = FakeTable()
|
||||
row = fake.insert("Test Name")
|
||||
self.assertIsInstance(row, FakeRow)
|
||||
self.assertEqual(fake.cache[1], row)
|
||||
self.assertEqual(fake.get(1), row)
|
||||
|
||||
fake.delete(row)
|
||||
self.assertEqual(fake.cache, { })
|
||||
|
||||
def test_find(self):
|
||||
fake = FakeTable()
|
||||
row = fake.find("Test Name")
|
||||
self.assertIsInstance(row, FakeRow)
|
||||
self.assertEqual(fake.cache[1], row)
|
||||
self.assertEqual(fake.find("Test Name"), row)
|
||||
|
||||
def test_lookup(self):
|
||||
fake = FakeTable()
|
||||
row = fake.insert("Test Name")
|
||||
fake.cache.clear()
|
||||
|
||||
row = fake.lookup("Test Name")
|
||||
self.assertEqual(row.name, "Test Name")
|
||||
self.assertEqual(fake.cache, { 1 : row })
|
||||
self.assertEqual(fake.lookup("Test Name"), row)
|
||||
|
||||
def test_reset(self):
|
||||
fake = FakeTable()
|
||||
sql.execute("SELECT fakeid,name FROM fake_table")
|
||||
fake.insert("Test Name")
|
||||
|
||||
fake.reset()
|
||||
self.assertEqual(fake.cache, { })
|
||||
|
||||
|
||||
class TestModel(unittest.TestCase):
|
||||
def items_changed(self, table, pos, rm, add):
|
||||
self.changed = (pos, rm, add)
|
||||
|
||||
def setUp(self):
|
||||
self.changed = None
|
||||
|
||||
def test_init(self):
|
||||
fake = FakeModel()
|
||||
self.assertIsInstance(fake, GObject.GObject)
|
||||
self.assertIsInstance(fake, Gio.ListModel)
|
||||
self.assertIsInstance(fake, table.Table)
|
||||
self.assertEqual(fake.order, "lower(name)")
|
||||
|
||||
def test_insert_delete(self):
|
||||
fake = FakeModel()
|
||||
fake.connect("items-changed", self.items_changed)
|
||||
|
||||
row = fake.insert("Test Row")
|
||||
self.assertEqual(self.changed, (0, 0, 1))
|
||||
|
||||
fake.delete(row)
|
||||
self.assertEqual(self.changed, (0, 1, 0))
|
||||
|
||||
def test_model(self):
|
||||
fake = FakeModel()
|
||||
self.assertEqual(fake.get_item_type(), GObject.TYPE_PYOBJECT)
|
||||
self.assertEqual(fake.get_n_items(), 0)
|
||||
|
||||
c = fake.insert("C")
|
||||
self.assertEqual(fake.get_n_items(), 1)
|
||||
self.assertEqual(fake.get_item(0), c)
|
||||
|
||||
b = fake.insert("B")
|
||||
self.assertEqual(fake.get_n_items(), 2)
|
||||
self.assertEqual(fake.get_item(0), b)
|
||||
self.assertEqual(fake.get_item(1), c)
|
||||
|
||||
a = fake.insert("A")
|
||||
self.assertEqual(fake.get_n_items(), 3)
|
||||
self.assertEqual(fake.get_item(0), a)
|
||||
self.assertEqual(fake.get_item(1), b)
|
||||
self.assertEqual(fake.get_item(2), c)
|
||||
|
||||
def test_reset(self):
|
||||
fake = FakeModel()
|
||||
fake.insert("Test Row")
|
||||
fake.insert("Test Row 2")
|
||||
fake.connect("items-changed", self.items_changed)
|
||||
fake.reset()
|
||||
self.assertEqual(self.changed, (0, 2, 0))
|
||||
|
||||
|
||||
class TestChild(unittest.TestCase):
|
||||
def test_init(self):
|
||||
child = FakeChild()
|
||||
|
||||
self.assertIsInstance(child, table.Table)
|
||||
self.assertEqual(child.parent, "parentid")
|
||||
self.assertEqual(child.order, "lower(name)")
|
||||
|
||||
def test_children(self):
|
||||
model = FakeModel()
|
||||
child = FakeChild()
|
||||
parent = model.insert("Fake Parent")
|
||||
|
||||
self.assertEqual(child.get_n_children(parent), 0)
|
||||
|
||||
c = child.insert(parent, "C")
|
||||
b = child.insert(parent, "B")
|
||||
a = child.insert(parent, "A")
|
||||
|
||||
self.assertEqual(child.get_n_children(parent), 3)
|
||||
|
||||
self.assertEqual(child.get_child(parent, 0), a)
|
||||
self.assertEqual(child.get_child(parent, 1), b)
|
||||
self.assertEqual(child.get_child(parent, 2), c)
|
||||
|
||||
self.assertEqual(child.get_child_index(parent, a), 0)
|
||||
self.assertEqual(child.get_child_index(parent, b), 1)
|
||||
self.assertEqual(child.get_child_index(parent, c), 2)
|
|
@ -1,95 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import datetime
|
||||
import db
|
||||
import pathlib
|
||||
import sqlite3
|
||||
import unittest
|
||||
from gi.repository import GObject
|
||||
|
||||
class TestTrack(unittest.TestCase):
|
||||
def setUp(self):
|
||||
db.reset()
|
||||
|
||||
def test_init(self):
|
||||
track = db.make_fake_track(1, 1.234, "Test Title", "/a/b/c/1.ogg")
|
||||
self.assertIsInstance(track.library, db.library.Library)
|
||||
self.assertIsInstance(track.artist, db.artist.Artist)
|
||||
self.assertIsInstance(track.album, db.album.Album)
|
||||
self.assertIsInstance(track.disc, db.disc.Disc)
|
||||
self.assertIsInstance(track.decade, db.decade.Decade)
|
||||
self.assertIsInstance(track.year, db.year.Year)
|
||||
self.assertEqual(track.number, 1)
|
||||
self.assertEqual(track.playcount, 0)
|
||||
self.assertIsNone(track.lastplayed, None)
|
||||
self.assertEqual(track.length, 1.234)
|
||||
self.assertEqual(track.title, "Test Title")
|
||||
|
||||
def test_genres(self):
|
||||
track = db.make_fake_track(1, 1.234, "Test Title", "/a/b/c/1.ogg")
|
||||
self.assertEqual(track.genres(), [ ])
|
||||
|
||||
genre = db.genre.Table.find("Test Genre")
|
||||
genre.add_track(track)
|
||||
self.assertEqual(track.genres(), [ genre ])
|
||||
|
||||
db.track.Table.delete(track)
|
||||
self.assertEqual(genre.get_n_tracks(), 0)
|
||||
|
||||
def test_playlists(self):
|
||||
new = db.user.Table.lookup("New Tracks")
|
||||
favorites = db.user.Table.lookup("Favorites")
|
||||
|
||||
track = db.make_fake_track(1, 1.234, "Test Title", "/a/b/c/1.ogg")
|
||||
self.assertEqual(track.playlists(), [ new ])
|
||||
favorites.add_track(track)
|
||||
self.assertEqual(track.playlists(), [ favorites, new ])
|
||||
|
||||
db.track.Table.delete(track)
|
||||
self.assertEqual(new.get_n_tracks(), 0)
|
||||
self.assertEqual(favorites.get_n_tracks(), 0)
|
||||
|
||||
def test_played(self):
|
||||
track = db.make_fake_track(1, 1.234, "Test Title", "/a/b/c/1.ogg")
|
||||
track.played()
|
||||
self.assertEqual(track.playcount, 1)
|
||||
self.assertEqual(track.lastplayed.date(), datetime.date.today())
|
||||
|
||||
|
||||
class TestTrackTable(unittest.TestCase):
|
||||
def setUp(self):
|
||||
db.reset()
|
||||
|
||||
def test_init(self):
|
||||
table = db.track.TrackTable()
|
||||
self.assertIsInstance(table, db.table.Table)
|
||||
self.assertEqual(table.table, "tracks")
|
||||
|
||||
self.assertIsInstance(db.track.Table, db.track.TrackTable)
|
||||
db.sql.execute("SELECT trackid,libraryid,artistid,albumid,discid,decadeid,yearid FROM tracks")
|
||||
db.sql.execute("SELECT number,playcount,lastplayed,length,title,path FROM tracks")
|
||||
|
||||
def test_insert(self):
|
||||
library = db.library.Table.find(pathlib.Path("/a/b/c"))
|
||||
artist = db.artist.Table.find("Test Artist", "test artist")
|
||||
album = artist.find_album("Test Album", datetime.date(2021, 3, 18))
|
||||
disc = album.find_disc(1, None)
|
||||
decade = db.decade.Table.find(2020)
|
||||
year = decade.find_year(2021)
|
||||
track = db.track.Table.insert(library, artist, album, disc, decade,
|
||||
year, 1, 1.234, "Test Title",
|
||||
pathlib.Path("/a/b/c/d.efg"))
|
||||
|
||||
self.assertIsInstance(track, db.track.Track)
|
||||
|
||||
with self.assertRaises(sqlite3.IntegrityError):
|
||||
db.track.Table.insert(library, artist, album, disc, decade, year,
|
||||
1, 1.234, "Test Title", pathlib.Path("/a/b/c/d.efg"))
|
||||
|
||||
def test_lookup(self):
|
||||
track = db.make_fake_track(1, 1.234, "Test Title", "/a/b/c/d.efg")
|
||||
self.assertEqual(db.track.Table.lookup(pathlib.Path("/a/b/c/d.efg")), track)
|
||||
self.assertIsNone(db.library.Table.lookup(pathlib.Path("/a/b/d/h.ijk")))
|
||||
|
||||
def test_find(self):
|
||||
with self.assertRaises(NotImplementedError):
|
||||
db.track.Table.find(pathlib.Path("/a/b/c/d.efg"))
|
386
db/test_user.py
386
db/test_user.py
|
@ -1,386 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import db
|
||||
import sqlite3
|
||||
import unittest
|
||||
from gi.repository import GObject
|
||||
from . import sql
|
||||
|
||||
class TestCollection(unittest.TestCase):
|
||||
def track_added(self, plist, added):
|
||||
self.added = added
|
||||
|
||||
def track_removed(self, plist, removed, adjusted_current):
|
||||
self.removed = (removed, adjusted_current)
|
||||
|
||||
def refreshed(self, plist):
|
||||
self.refreshed = True
|
||||
|
||||
def setUp(self): db.reset()
|
||||
|
||||
def test_init(self):
|
||||
collection = db.user.Table.find("Collection")
|
||||
self.assertIsInstance(collection, db.playlist.Playlist)
|
||||
self.assertIsInstance(collection, db.user.Collection)
|
||||
self.assertEqual(collection.name, "Collection")
|
||||
self.assertEqual(collection.icon_name, "media-playback-start")
|
||||
self.assertTrue(collection.plist_state.loop)
|
||||
|
||||
def test_tracks(self):
|
||||
collection = db.user.Table.find("Collection")
|
||||
collection.connect("track-added", self.track_added)
|
||||
self.assertEqual(collection.get_n_tracks(), 0)
|
||||
self.assertEqual(collection.get_tracks(), [ ])
|
||||
|
||||
track = db.make_fake_track(1, 1, "Test Track", "/a/b/c/1.ogg")
|
||||
self.assertEqual(collection.get_n_tracks(), 1)
|
||||
self.assertEqual(collection.get_track(0), track)
|
||||
self.assertEqual(collection.get_tracks(), [ track ])
|
||||
self.assertEqual(collection.get_track_index(track), 0)
|
||||
self.assertEqual(self.added, track)
|
||||
|
||||
collection.connect("track-removed", self.track_removed)
|
||||
db.track.Table.delete(track)
|
||||
self.assertEqual(collection.get_tracks(), [ ])
|
||||
self.assertEqual(self.removed, (track, False))
|
||||
|
||||
def test_library_enabled(self):
|
||||
collection = db.user.Table.find("Collection")
|
||||
track1 = db.make_fake_track(1, 1, "Test Track 1", "/a/b/c/1.ogg")
|
||||
track2 = db.make_fake_track(2, 2, "Test Track 2", "/a/b/c/2.ogg")
|
||||
self.assertEqual(collection.get_n_tracks(), 2)
|
||||
self.assertEqual(collection.get_tracks(), [ track1, track2 ])
|
||||
collection.connect("refreshed", self.refreshed)
|
||||
|
||||
track1.library.enabled = False
|
||||
self.assertEqual(collection.get_n_tracks(), 0)
|
||||
self.assertEqual(collection.get_tracks(), [ ])
|
||||
self.assertTrue(self.refreshed)
|
||||
|
||||
self.refreshed = None
|
||||
track1.library.enabled = True
|
||||
self.assertEqual(collection.get_n_tracks(), 2)
|
||||
self.assertEqual(collection.get_tracks(), [ track1, track2 ])
|
||||
self.assertTrue(self.refreshed)
|
||||
|
||||
|
||||
class TestFavorites(unittest.TestCase):
|
||||
def track_added(self, plist, added):
|
||||
self.added = added
|
||||
|
||||
def track_removed(self, plist, removed, adjusted_current):
|
||||
self.removed = (removed, False)
|
||||
|
||||
def setUp(self): db.reset()
|
||||
|
||||
def test_init(self):
|
||||
favorites = db.user.Table.find("Favorites")
|
||||
self.assertIsInstance(favorites, db.playlist.MappedPlaylist)
|
||||
self.assertIsInstance(favorites, db.user.UserPlaylist)
|
||||
self.assertEqual(favorites.name, "Favorites")
|
||||
self.assertEqual(favorites.icon_name, "emmental-favorites")
|
||||
self.assertEqual(favorites.map_table, "playlist_map")
|
||||
self.assertFalse(favorites.plist_state.loop)
|
||||
self.assertTrue(favorites.can_add_remove_tracks())
|
||||
|
||||
def test_add_remove_track(self):
|
||||
favorites = db.user.Table.find("Favorites")
|
||||
track = db.make_fake_track(1, 1, "Test Track", "/a/b/c/1.ogg")
|
||||
favorites.connect("track-added", self.track_added)
|
||||
self.assertEqual(favorites.get_n_tracks(), 0)
|
||||
self.assertEqual(favorites.get_tracks(), [ ])
|
||||
|
||||
self.assertTrue(favorites.add_track(track))
|
||||
self.assertFalse(favorites.add_track(track))
|
||||
self.assertEqual(favorites.get_n_tracks(), 1)
|
||||
self.assertEqual(favorites.get_track(0), track)
|
||||
self.assertEqual(favorites.get_tracks(), [ track ])
|
||||
self.assertEqual(favorites.get_track_index(track), 0)
|
||||
self.assertEqual(self.added, track)
|
||||
|
||||
favorites.connect("track-removed", self.track_removed)
|
||||
self.assertTrue(favorites.remove_track(track))
|
||||
self.assertFalse(favorites.remove_track(track))
|
||||
self.assertEqual(favorites.get_n_tracks(), 0)
|
||||
self.assertEqual(favorites.get_tracks(), [ ])
|
||||
self.assertEqual(self.removed, (track, False))
|
||||
|
||||
|
||||
class TestNewTracks(unittest.TestCase):
|
||||
def track_added(self, plist, added):
|
||||
self.added = added
|
||||
|
||||
def track_removed(self, plist, removed, adjusted_current):
|
||||
self.removed = (removed, adjusted_current)
|
||||
|
||||
def setUp(self): db.reset()
|
||||
|
||||
def test_init(self):
|
||||
new = db.user.Table.find("New Tracks")
|
||||
self.assertIsInstance(new, db.playlist.MappedPlaylist)
|
||||
self.assertIsInstance(new, db.user.UserPlaylist)
|
||||
self.assertEqual(new.name, "New Tracks")
|
||||
self.assertEqual(new.icon_name, "starred")
|
||||
self.assertEqual(new.map_table, "temp_playlist_map")
|
||||
self.assertFalse(new.plist_state.loop)
|
||||
self.assertFalse(new.can_add_remove_tracks())
|
||||
|
||||
def test_add_remove_track(self):
|
||||
new = db.user.Table.find("New Tracks")
|
||||
new.connect("track-added", self.track_added)
|
||||
self.assertEqual(new.get_n_tracks(), 0)
|
||||
self.assertEqual(new.get_tracks(), [ ])
|
||||
|
||||
track = db.make_fake_track(1, 1, "Test Track", "/a/b/c/1.ogg")
|
||||
self.assertEqual(new.get_n_tracks(), 1)
|
||||
self.assertEqual(new.get_track(0), track)
|
||||
self.assertEqual(new.get_tracks(), [ track ])
|
||||
self.assertEqual(new.get_track_index(track), 0)
|
||||
self.added = track
|
||||
|
||||
new.connect("track-removed", self.track_removed)
|
||||
self.assertTrue(new.remove_track(track))
|
||||
self.assertFalse(new.remove_track(track))
|
||||
self.assertEqual(new.get_n_tracks(), 0)
|
||||
self.assertEqual(new.get_tracks(), [ ])
|
||||
self.assertEqual(self.removed, (track, False))
|
||||
|
||||
|
||||
class TestPrevious(unittest.TestCase):
|
||||
def track_added(self, plist, added):
|
||||
self.added = added
|
||||
|
||||
def track_removed(self, plist, removed, adjusted_current):
|
||||
self.removed = (removed, adjusted_current)
|
||||
|
||||
def setUp(self): db.reset()
|
||||
|
||||
def test_init(self):
|
||||
previous = db.user.Table.find("Previous")
|
||||
self.assertIsInstance(previous, db.user.UserPlaylist)
|
||||
self.assertIsInstance(previous, db.user.Previous)
|
||||
self.assertEqual(previous.name, "Previous")
|
||||
self.assertEqual(previous.icon_name, "media-skip-backward")
|
||||
self.assertEqual(previous.map_table, "temp_playlist_map")
|
||||
self.assertEqual(previous.plist_state.sort, [ "temp_playlist_map.rowid DESC" ])
|
||||
self.assertFalse(previous.plist_state.loop)
|
||||
self.assertFalse(previous.can_add_remove_tracks())
|
||||
|
||||
def test_add_remove_track(self):
|
||||
previous = db.user.Table.find("Previous")
|
||||
track1 = db.make_fake_track(1, 1, "Test Track", "/a/b/c/1.ogg")
|
||||
track2 = db.make_fake_track(2, 2, "Test Track 2", "/a/b/c/2.ogg")
|
||||
previous.connect("track-added", self.track_added)
|
||||
self.assertEqual(previous.get_n_tracks(), 0)
|
||||
self.assertEqual(previous.get_tracks(), [ ])
|
||||
|
||||
self.assertTrue(previous.add_track(track1))
|
||||
self.assertEqual(previous.get_n_tracks(), 1)
|
||||
self.assertEqual(previous.get_track(0), track1)
|
||||
self.assertEqual(previous.get_tracks(), [ track1 ])
|
||||
self.assertEqual(previous.get_track_index(track1), 0)
|
||||
self.assertEqual(self.added, track1)
|
||||
|
||||
self.assertTrue(previous.add_track(track2))
|
||||
self.assertEqual(previous.get_n_tracks(), 2)
|
||||
self.assertEqual(previous.get_track(0), track2)
|
||||
self.assertEqual(previous.get_track(1), track1)
|
||||
self.assertEqual(previous.get_tracks(), [ track2, track1 ])
|
||||
self.assertEqual(previous.get_track_index(track2), 0)
|
||||
self.assertEqual(previous.get_track_index(track1), 1)
|
||||
self.assertEqual(self.added, track2)
|
||||
|
||||
previous.connect("track-removed", self.track_removed)
|
||||
self.assertTrue(previous.add_track(track1))
|
||||
self.assertEqual(previous.get_n_tracks(), 2)
|
||||
self.assertEqual(previous.get_track(0), track1)
|
||||
self.assertEqual(previous.get_track(1), track2)
|
||||
self.assertEqual(previous.get_tracks(), [ track1, track2 ])
|
||||
self.assertEqual(previous.get_track_index(track1), 0)
|
||||
self.assertEqual(previous.get_track_index(track2), 1)
|
||||
self.assertEqual(self.removed, (track1, False))
|
||||
self.assertEqual(self.added, track1)
|
||||
|
||||
self.assertTrue(previous.remove_track(track1))
|
||||
self.assertFalse(previous.remove_track(track1))
|
||||
self.assertEqual(previous.get_n_tracks(), 1)
|
||||
self.assertEqual(previous.get_tracks(), [ track2 ])
|
||||
self.assertEqual(previous.get_track_index(track2), 0)
|
||||
self.assertEqual(self.removed, (track1, True))
|
||||
|
||||
def test_previous_track(self):
|
||||
previous = db.user.Table.find("Previous")
|
||||
self.assertEqual(previous.get_property("current"), -1)
|
||||
|
||||
previous.add_track(db.make_fake_track(1, 1, "Track 1", "/a/b/c/1.ogg"))
|
||||
self.assertEqual(previous.get_property("current"), 0)
|
||||
previous.add_track(db.make_fake_track(2, 2, "Track 2", "/a/b/c/2.ogg"))
|
||||
self.assertEqual(previous.get_property("current"), 0)
|
||||
previous.add_track(db.make_fake_track(3, 3, "Track 3", "/a/b/c/3.ogg"))
|
||||
self.assertEqual(previous.get_property("current"), 0)
|
||||
|
||||
self.assertEqual(previous.previous_track(), previous.get_track(1))
|
||||
self.assertEqual(previous.previous_track(), previous.get_track(2))
|
||||
self.assertIsNone(previous.previous_track())
|
||||
self.assertIsNone(previous.previous_track())
|
||||
|
||||
def test_next_track(self):
|
||||
previous = db.user.Table.find("Previous")
|
||||
previous.add_track(db.make_fake_track(1, 1, "Track 1", "/a/b/c/1.ogg"))
|
||||
previous.add_track(db.make_fake_track(2, 2, "Track 2", "/a/b/c/2.ogg"))
|
||||
previous.add_track(db.make_fake_track(3, 3, "Track 3", "/a/b/c/3.ogg"))
|
||||
previous.current = 2
|
||||
|
||||
self.assertEqual(previous.next_track(), previous.get_track(1))
|
||||
self.assertEqual(previous.current, 1)
|
||||
self.assertEqual(previous.next_track(), previous.get_track(0))
|
||||
self.assertEqual(previous.current, 0)
|
||||
self.assertIsNone(previous.next_track())
|
||||
self.assertEqual(previous.current, -1)
|
||||
self.assertIsNone(previous.next_track())
|
||||
self.assertEqual(previous.current, -1)
|
||||
|
||||
|
||||
class TestQueuedTracks(unittest.TestCase):
|
||||
def track_added(self, plist, added):
|
||||
self.added = added
|
||||
|
||||
def track_removed(self, plist, removed, adjusted_current):
|
||||
self.removed = (removed, adjusted_current)
|
||||
|
||||
def setUp(self): db.reset()
|
||||
|
||||
def test_init(self):
|
||||
queued = db.user.Table.find("Queued Tracks")
|
||||
self.assertIsInstance(queued, db.user.UserPlaylist)
|
||||
self.assertIsInstance(queued, db.user.QueuedTracks)
|
||||
self.assertEqual(queued.name, "Queued Tracks")
|
||||
self.assertEqual(queued.icon_name, "media-skip-forward")
|
||||
self.assertEqual(queued.map_table, "playlist_map")
|
||||
self.assertEqual(queued.plist_state.sort, [ "playlist_map.rowid ASC" ])
|
||||
self.assertFalse(queued.plist_state.loop)
|
||||
self.assertTrue(queued.can_add_remove_tracks())
|
||||
|
||||
def test_add_remove_track(self):
|
||||
queued = db.user.Table.find("Queued Tracks")
|
||||
track = db.make_fake_track(1, 1, "Test Track", "/a/b/c/1.ogg")
|
||||
queued.connect("track-added", self.track_added)
|
||||
self.assertEqual(queued.get_n_tracks(), 0)
|
||||
self.assertEqual(queued.get_tracks(), [ ])
|
||||
|
||||
self.assertTrue(queued.add_track(track))
|
||||
self.assertFalse(queued.add_track(track))
|
||||
self.assertEqual(queued.get_n_tracks(), 1)
|
||||
self.assertEqual(queued.get_track(0), track)
|
||||
self.assertEqual(queued.get_tracks(), [ track ])
|
||||
self.assertEqual(queued.get_track_index(track), 0)
|
||||
self.assertEqual(self.added, track)
|
||||
|
||||
queued.connect("track-removed", self.track_removed)
|
||||
self.assertTrue(queued.remove_track(track))
|
||||
self.assertFalse(queued.remove_track(track))
|
||||
self.assertEqual(queued.get_n_tracks(), 0)
|
||||
self.assertEqual(queued.get_tracks(), [ ])
|
||||
self.assertEqual(self.removed, (track, False))
|
||||
|
||||
def test_next_track(self):
|
||||
queued = db.user.Table.find("Queued Tracks")
|
||||
queued.add_track(db.make_fake_track(1, 1, "Test 1", "/a/b/c/1.ogg"))
|
||||
queued.add_track(db.make_fake_track(2, 2, "Test 2", "/a/b/c/2.ogg"))
|
||||
queued.add_track(db.make_fake_track(3, 3, "Test 3", "/a/b/c/3.ogg"))
|
||||
|
||||
self.assertEqual(queued.next_track(), db.track.Table.lookup("/a/b/c/1.ogg"))
|
||||
self.assertEqual(queued.get_n_tracks(), 2)
|
||||
self.assertEqual(queued.next_track(), db.track.Table.lookup("/a/b/c/2.ogg"))
|
||||
self.assertEqual(queued.get_n_tracks(), 1)
|
||||
self.assertEqual(queued.next_track(), db.track.Table.lookup("/a/b/c/3.ogg"))
|
||||
self.assertEqual(queued.get_n_tracks(), 0)
|
||||
self.assertIsNone(queued.next_track())
|
||||
|
||||
|
||||
class TestUserPlaylist(unittest.TestCase):
|
||||
def track_added(self, plist, added):
|
||||
self.added = added
|
||||
|
||||
def track_removed(self, plist, removed, adjusted_current):
|
||||
self.removed = (removed, adjusted_current)
|
||||
|
||||
def setUp(self): db.reset()
|
||||
|
||||
def test_init(self):
|
||||
plist = db.user.Table.find("Test Playlist")
|
||||
self.assertIsInstance(plist, db.playlist.MappedPlaylist)
|
||||
self.assertIsInstance(plist, db.user.UserPlaylist)
|
||||
self.assertEqual(plist.name, "Test Playlist")
|
||||
self.assertEqual(plist.icon_name, "audio-x-generic")
|
||||
self.assertEqual(plist.map_table, "playlist_map")
|
||||
self.assertFalse(plist.plist_state.loop)
|
||||
self.assertTrue(plist.can_add_remove_tracks())
|
||||
|
||||
def test_delete(self):
|
||||
track = db.make_fake_track(1, 1, "Test Track", "/a/b/c/1.ogg")
|
||||
plist = db.user.Table.find("Test Playlist")
|
||||
plist.add_track(track)
|
||||
plist.delete()
|
||||
self.assertEqual(plist.get_n_tracks(), 0)
|
||||
self.assertIsNone(db.user.Table.lookup("Test Playlist"))
|
||||
|
||||
def test_add_remove_track(self):
|
||||
plist = db.user.Table.find("Test Playlist")
|
||||
track = db.make_fake_track(1, 1, "Test Track", "/a/b/c/1.ogg")
|
||||
plist.connect("track-added", self.track_added)
|
||||
self.assertEqual(plist.get_n_tracks(), 0)
|
||||
self.assertEqual(plist.get_tracks(), [ ])
|
||||
|
||||
self.assertTrue(plist.add_track(track))
|
||||
self.assertFalse(plist.add_track(track))
|
||||
self.assertEqual(plist.get_n_tracks(), 1)
|
||||
self.assertEqual(plist.get_track(0), track)
|
||||
self.assertEqual(plist.get_tracks(), [ track ])
|
||||
self.assertEqual(plist.get_track_index(track), 0)
|
||||
self.assertEqual(self.added, track)
|
||||
|
||||
plist.connect("track-removed", self.track_removed)
|
||||
self.assertTrue(plist.remove_track(track))
|
||||
self.assertFalse(plist.remove_track(track))
|
||||
self.assertEqual(plist.get_n_tracks(), 0)
|
||||
self.assertEqual(plist.get_tracks(), [ ])
|
||||
self.assertEqual(self.removed, (track, False))
|
||||
|
||||
|
||||
class TestUserTable(unittest.TestCase):
|
||||
def setUp(self):
|
||||
db.reset()
|
||||
|
||||
def test_init(self):
|
||||
table = db.user.UserTable()
|
||||
self.assertIsInstance(table, db.playlist.Model)
|
||||
self.assertEqual(table.table, "playlists")
|
||||
self.assertEqual(table.order, "sort")
|
||||
|
||||
self.assertIsInstance(db.user.Table, db.user.UserTable)
|
||||
db.sql.execute("SELECT playlistid,plstateid,name,sort FROM playlists")
|
||||
|
||||
def test_insert(self):
|
||||
table = db.user.UserTable()
|
||||
playlist = table.find("Test Playlist")
|
||||
|
||||
self.assertIsInstance(playlist, db.user.UserPlaylist)
|
||||
self.assertEqual(playlist._name, "Test Playlist")
|
||||
self.assertEqual(playlist._rowkey, "playlistid")
|
||||
|
||||
with self.assertRaises(sqlite3.IntegrityError):
|
||||
db.user.Table.insert("Test Playlist")
|
||||
|
||||
def test_lookup(self):
|
||||
playlist = db.user.Table.insert("Test Playlist")
|
||||
self.assertEqual(db.user.Table.lookup("Test Playlist"), playlist)
|
||||
self.assertIsNone(db.user.Table.lookup("none"))
|
||||
|
||||
def test_default_playlists(self):
|
||||
table = db.user.UserTable()
|
||||
self.assertEqual(table.get_n_items(), 5)
|
||||
self.assertEqual(table.get_item(0).name, "Collection")
|
||||
self.assertEqual(table.get_item(1).name, "Favorites")
|
||||
self.assertEqual(table.get_item(2).name, "New Tracks")
|
||||
self.assertEqual(table.get_item(3).name, "Previous")
|
||||
self.assertEqual(table.get_item(4).name, "Queued Tracks")
|
|
@ -1,79 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import db
|
||||
import sqlite3
|
||||
import unittest
|
||||
from gi.repository import GObject
|
||||
|
||||
class TestYear(unittest.TestCase):
|
||||
def track_added(self, plist, added):
|
||||
self.added = added
|
||||
|
||||
def track_removed(self, plist, removed, adjusted_current):
|
||||
self.removed = (removed, adjusted_current)
|
||||
|
||||
def setUp(self):
|
||||
db.reset()
|
||||
|
||||
def test_init(self):
|
||||
decade = db.decade.Table.find(2020)
|
||||
year = decade.find_year(2021)
|
||||
self.assertIsInstance(year, db.playlist.Playlist)
|
||||
self.assertEqual(year.get_property("name"), "2021")
|
||||
self.assertEqual(year.get_property("year"), 2021)
|
||||
self.assertEqual(year.get_property("icon-name"), "x-office-calendar")
|
||||
|
||||
def test_delete(self):
|
||||
decade = db.decade.Table.find(2020)
|
||||
year = decade.find_year(2021)
|
||||
year.delete()
|
||||
self.assertIsNone(db.year.Table.lookup(2021))
|
||||
|
||||
def test_tracks(self):
|
||||
decade = db.decade.Table.find(2020)
|
||||
year = decade.find_year(2021)
|
||||
year.connect("track-added", self.track_added)
|
||||
self.assertEqual(year.get_n_tracks(), 0)
|
||||
self.assertEqual(year.get_tracks(), [ ])
|
||||
|
||||
track = db.make_fake_track(1, 1, "Test Track", "/a/b/c/1.ogg")
|
||||
self.assertEqual(year.get_n_tracks(), 1)
|
||||
self.assertEqual(year.get_track(0), track)
|
||||
self.assertEqual(year.get_tracks(), [ track ])
|
||||
self.assertEqual(year.get_track_index(track), 0)
|
||||
self.assertEqual(self.added, track)
|
||||
|
||||
year.connect("track-removed", self.track_removed)
|
||||
db.track.Table.delete(track)
|
||||
self.assertEqual(year.get_tracks(), [ ])
|
||||
self.assertEqual(self.removed, (track, False))
|
||||
|
||||
|
||||
class TestYearTable(unittest.TestCase):
|
||||
def setUp(self):
|
||||
db.reset()
|
||||
|
||||
def test_init(self):
|
||||
table = db.year.YearTable()
|
||||
self.assertIsInstance(table, db.playlist.ChildModel)
|
||||
self.assertEqual(table.table, "years")
|
||||
self.assertEqual(table.order, "year")
|
||||
|
||||
self.assertIsInstance(db.year.Table, db.year.YearTable)
|
||||
db.sql.execute("SELECT yearid,decadeid,plstateid,year FROM years")
|
||||
|
||||
def test_insert(self):
|
||||
decade = db.decade.Table.insert(2020)
|
||||
year = decade.find_year(2021)
|
||||
|
||||
self.assertIsInstance(year, db.year.Year)
|
||||
self.assertEqual(year._year, 2021)
|
||||
self.assertEqual(year._rowkey, "yearid")
|
||||
|
||||
with self.assertRaises(sqlite3.IntegrityError):
|
||||
db.year.Table.insert(decade, 2021)
|
||||
|
||||
def test_lookup(self):
|
||||
decade = db.decade.Table.find(2020)
|
||||
year = decade.find_year(2021)
|
||||
self.assertEqual(db.year.Table.lookup(2021), year)
|
||||
self.assertIsNone(db.year.Table.lookup(2022))
|
178
db/track.py
178
db/track.py
|
@ -1,178 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
#
|
||||
# Table: tracks
|
||||
# +---------+-----------+------------+---------+--------+--------+
|
||||
# | trackid | libraryid | artistid | albumid | discid | yearid |
|
||||
# +---------+-----------+------------+---------+--------+--------+
|
||||
# | number | playcount | lastplayed | length | title | path |
|
||||
# +---------+-----------+------------+---------+--------+--------|
|
||||
import datetime
|
||||
import pathlib
|
||||
from gi.repository import GObject
|
||||
from . import artist
|
||||
from . import album
|
||||
from . import decade
|
||||
from . import disc
|
||||
from . import genre
|
||||
from . import library
|
||||
from . import sql
|
||||
from . import table
|
||||
from . import user
|
||||
from . import year
|
||||
|
||||
class Track(GObject.GObject):
|
||||
def __init__(self, row):
|
||||
GObject.GObject.__init__(self)
|
||||
self._trackid = row["trackid"]
|
||||
self._library = library.Table.get(row["libraryid"])
|
||||
self._artist = artist.Table.get(row["artistid"])
|
||||
self._album = album.Table.get(row["albumid"])
|
||||
self._disc = disc.Table.get(row["discid"])
|
||||
self._decade = decade.Table.get(row["decadeid"])
|
||||
self._year = year.Table.get(row["yearid"])
|
||||
self._number = row["number"]
|
||||
self._playcount = row["playcount"]
|
||||
self._lastplayed = row["lastplayed"]
|
||||
self._length = row["length"]
|
||||
self._title = row["title"]
|
||||
self._path = pathlib.Path(row["path"])
|
||||
|
||||
@GObject.Property
|
||||
def rowid(self): return self._trackid
|
||||
|
||||
@GObject.Property
|
||||
def library(self): return self._library
|
||||
|
||||
@GObject.Property
|
||||
def artist(self): return self._artist
|
||||
|
||||
@GObject.Property
|
||||
def album(self): return self._album
|
||||
|
||||
@GObject.Property
|
||||
def disc(self): return self._disc
|
||||
|
||||
@GObject.Property
|
||||
def decade(self): return self._decade
|
||||
|
||||
@GObject.Property
|
||||
def year(self): return self._year
|
||||
|
||||
@GObject.Property
|
||||
def number(self): return self._number
|
||||
|
||||
@GObject.Property
|
||||
def playcount(self): return self._playcount
|
||||
|
||||
@playcount.setter
|
||||
def playcount(self, newval):
|
||||
self._playcount = self.update("playcount", newval)
|
||||
|
||||
@GObject.Property
|
||||
def lastplayed(self): return self._lastplayed
|
||||
|
||||
@lastplayed.setter
|
||||
def lastplayed(self, newval):
|
||||
self._lastplayed = self.update("lastplayed", newval)
|
||||
|
||||
@GObject.Property
|
||||
def length(self): return self._length
|
||||
|
||||
@GObject.Property
|
||||
def title(self): return self._title
|
||||
|
||||
@GObject.Property
|
||||
def path(self): return self._path
|
||||
|
||||
def genres(self):
|
||||
rows = sql.execute(f"SELECT genreid FROM genre_map WHERE trackid=?",
|
||||
[ self.rowid ]).fetchall()
|
||||
return [ genre.Table.get(row[0]) for row in rows ]
|
||||
|
||||
def playlists(self):
|
||||
rows = sql.execute(f"SELECT playlistid FROM playlist_map UNION "
|
||||
f"SELECT playlistid FROM temp_playlist_map "
|
||||
f"WHERE trackid=?", [ self.rowid ]).fetchall()
|
||||
return [ user.Table.get(row[0]) for row in rows ]
|
||||
|
||||
def update(self, column, newval):
|
||||
sql.execute(f"UPDATE tracks SET {column}=? WHERE trackid=?",
|
||||
[ newval, self.rowid ])
|
||||
return newval
|
||||
|
||||
def played(self):
|
||||
self.playcount += 1
|
||||
self.lastplayed = datetime.datetime.now()
|
||||
sql.commit()
|
||||
|
||||
|
||||
class TrackTable(table.Table):
|
||||
def __init__(self):
|
||||
table.Table.__init__(self, "tracks")
|
||||
|
||||
def do_create(self):
|
||||
sql.execute("CREATE TABLE IF NOT EXISTS tracks "
|
||||
"(trackid INTEGER PRIMARY KEY, "
|
||||
" libraryid INTEGER, "
|
||||
" artistid INTEGER, "
|
||||
" albumid INTEGER, "
|
||||
" discid INTEGER, "
|
||||
" decadeid INTEGER, "
|
||||
" yearid INTEGER, "
|
||||
" number INTEGER, "
|
||||
" playcount INTEGER DEFAULT 0,"
|
||||
" lastplayed TIMESTAMP DEFAULT NULL, "
|
||||
" length REAL, "
|
||||
" title TEXT, "
|
||||
" path TEXT UNIQUE, "
|
||||
" FOREIGN KEY(libraryid) REFERENCES libraries(libraryid), "
|
||||
" FOREIGN KEY(artistid) REFERENCES artists(artistid), "
|
||||
" FOREIGN KEY(albumid) REFERENCES albums(albumid), "
|
||||
" FOREIGN KEY(discid) REFERENCES discs(discid), "
|
||||
" FOREIGN KEY(yearid) REFERENCES years(yearid))")
|
||||
|
||||
def do_factory(self, row):
|
||||
return Track(row)
|
||||
|
||||
def do_insert(self, library, artist, album, disc, decade,
|
||||
year, number, length, title, path):
|
||||
return sql.execute("INSERT INTO tracks (libraryid, artistid, albumid, "
|
||||
"discid, decadeid, yearid, "
|
||||
"number, length, title, path) "
|
||||
"VALUES (?,?,?,?,?,?,?,?,?,?)",
|
||||
[ library.rowid, artist.rowid, album.rowid, disc.rowid,
|
||||
decade.rowid, year.rowid, number, length, title, str(path) ])
|
||||
|
||||
def do_lookup(self, path):
|
||||
return sql.execute("SELECT * FROM tracks WHERE path=?", [ str(path) ])
|
||||
|
||||
def delete(self, track):
|
||||
for plist in track.genres() + track.playlists():
|
||||
plist.remove_track(track)
|
||||
|
||||
plists = [ track.artist, track.album, track.disc,
|
||||
track.decade, track.year, track.library,
|
||||
user.Table.find("Collection") ]
|
||||
adjust = [ p.track_adjusts_current(track) for p in plists ]
|
||||
|
||||
super().delete(track)
|
||||
for (plist, adjust) in zip(plists, adjust):
|
||||
plist.remove_track(track, adjust)
|
||||
|
||||
def find(self, *args):
|
||||
raise NotImplementedError
|
||||
|
||||
def insert(self, library, artist, album, disc, decade,
|
||||
year, number, length, title, path):
|
||||
track = super().insert(library, artist, album, disc, decade,
|
||||
year, number, length, title, path)
|
||||
|
||||
user.Table.find("New Tracks").add_track(track)
|
||||
for plist in [ artist, album, disc, decade, year, library,
|
||||
user.Table.find("Collection") ]:
|
||||
plist.add_track(track)
|
||||
|
||||
return track
|
||||
|
||||
|
||||
Table = TrackTable()
|
170
db/user.py
170
db/user.py
|
@ -1,170 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
#
|
||||
# Table: playlists
|
||||
# +------ ---+-----------+------+------+
|
||||
# | playlistid | plstateid | name | sort |
|
||||
# +-------- -+-----------+------+------+
|
||||
from gi.repository import GObject
|
||||
from . import playlist
|
||||
from . import sql
|
||||
from . import state
|
||||
from . import track
|
||||
|
||||
|
||||
class Collection(playlist.Playlist):
|
||||
def __init__(self, row):
|
||||
playlist.Playlist.__init__(self, row, "media-playback-start")
|
||||
self._name = row["name"]
|
||||
|
||||
def get_n_tracks(self):
|
||||
cur = sql.execute("SELECT COUNT(*) FROM tracks "
|
||||
"JOIN libraries USING(libraryid) WHERE enabled=1")
|
||||
return cur.fetchone()[0]
|
||||
|
||||
def get_track(self, n):
|
||||
order = ', '.join(self.plist_state.sort)
|
||||
row = sql.execute(f"SELECT * FROM tracks "
|
||||
f"INNER JOIN artists USING(artistid) "
|
||||
f"INNER JOIN albums USING(albumid) "
|
||||
f"INNER JOIN discs USING(discid) "
|
||||
f"INNER JOIN libraries USING(libraryid) "
|
||||
f"WHERE libraries.enabled=1 "
|
||||
f"ORDER BY {order} LIMIT 1 OFFSET ?",
|
||||
[ n ]).fetchone()
|
||||
return track.Table.factory(row)
|
||||
|
||||
def get_tracks(self):
|
||||
order = ', '.join(self.plist_state.sort)
|
||||
rows = sql.execute(f"SELECT * FROM tracks "
|
||||
f"INNER JOIN artists USING(artistid) "
|
||||
f"INNER JOIN albums USING(albumid) "
|
||||
f"INNER JOIN discs USING(discid) "
|
||||
f"INNER JOIN libraries USING(libraryid) "
|
||||
f"WHERE libraries.enabled=1 "
|
||||
f"ORDER BY {order}").fetchall()
|
||||
return [ track.Table.factory(row) for row in rows ]
|
||||
|
||||
def get_track_index(self, track):
|
||||
order = ', '.join(self.plist_state.sort)
|
||||
cur = sql.execute(f"SELECT * FROM (SELECT trackid,ROW_NUMBER() "
|
||||
f"OVER (ORDER BY {order}) "
|
||||
f"FROM tracks "
|
||||
f"INNER JOIN artists USING(artistid) "
|
||||
f"INNER JOIN albums USING(albumid) "
|
||||
f"INNER JOIN discs USING(discid) "
|
||||
f"INNER JOIN libraries USING(libraryid) "
|
||||
f"WHERE libraries.enabled=1) "
|
||||
f"WHERE trackid=?", [ track.rowid ])
|
||||
return cur.fetchone()[1] - 1
|
||||
|
||||
@GObject.Property
|
||||
def name(self): return self._name
|
||||
|
||||
|
||||
class UserPlaylist(playlist.MappedPlaylist):
|
||||
def __init__(self, row, icon_name, map_table):
|
||||
playlist.MappedPlaylist.__init__(self, row, icon_name, map_table)
|
||||
self._name = row["name"]
|
||||
|
||||
def delete(self):
|
||||
self.clear()
|
||||
Table.delete(self)
|
||||
sql.commit()
|
||||
|
||||
@GObject.Property
|
||||
def name(self): return self._name
|
||||
|
||||
|
||||
class Previous(UserPlaylist):
|
||||
def __init__(self, row):
|
||||
UserPlaylist.__init__(self, row, "media-skip-backward", "temp_playlist_map")
|
||||
|
||||
def add_track(self, track):
|
||||
if self.get_track_index(track):
|
||||
self.remove_track(track)
|
||||
super().add_track(track)
|
||||
self.current = 0
|
||||
return True
|
||||
|
||||
def next_track(self):
|
||||
self.current = max(-1, self.current - 1)
|
||||
return self.get_current_track()
|
||||
|
||||
def previous_track(self):
|
||||
return super().next_track()
|
||||
|
||||
|
||||
class QueuedTracks(UserPlaylist):
|
||||
def __init__(self, row):
|
||||
UserPlaylist.__init__(self, row, "media-skip-forward", "playlist_map")
|
||||
|
||||
def next_track(self):
|
||||
if track := super().next_track():
|
||||
self.remove_track(track)
|
||||
return track
|
||||
|
||||
|
||||
class UserTable(playlist.Model):
|
||||
def __init__(self):
|
||||
playlist.Model.__init__(self, "playlists", "sort")
|
||||
|
||||
def do_create(self):
|
||||
sql.execute("CREATE TABLE IF NOT EXISTS playlists "
|
||||
"(playlistid INTEGER PRIMARY KEY, "
|
||||
" plstateid INTEGER NOT NULL, "
|
||||
" name TEXT UNIQUE, "
|
||||
" sort TEXT, "
|
||||
" FOREIGN KEY (plstateid) REFERENCES playlist_states(plstateid))")
|
||||
sql.execute(f"CREATE TABLE IF NOT EXISTS playlist_map "
|
||||
"(playlistid INTEGER, "
|
||||
" trackid INTEGER, "
|
||||
" FOREIGN KEY(playlistid) REFERENCES playlists(playlistid), "
|
||||
" FOREIGN KEY(trackid) REFERENCES tracks(trackid), "
|
||||
" UNIQUE(playlistid, trackid))")
|
||||
sql.execute(f"CREATE TEMPORARY TABLE IF NOT EXISTS temp_playlist_map "
|
||||
"(playlistid INTEGER, "
|
||||
" trackid INTEGER, "
|
||||
" FOREIGN KEY(playlistid) REFERENCES playlists(playlistid), "
|
||||
" FOREIGN KEY(trackid) REFERENCES tracks(trackid), "
|
||||
" UNIQUE(playlistid, trackid))")
|
||||
|
||||
self.find("Collection", loop=True)
|
||||
self.find("Favorites")
|
||||
self.find("New Tracks")
|
||||
self.find("Previous", sort=["temp_playlist_map.rowid DESC"])
|
||||
self.find("Queued Tracks", sort=["playlist_map.rowid ASC"])
|
||||
|
||||
def do_drop(self):
|
||||
sql.execute("DROP TABLE playlists")
|
||||
sql.execute("DROP TABLE playlist_map")
|
||||
sql.execute("DROP TABLE temp_playlist_map")
|
||||
|
||||
def do_factory(self, row):
|
||||
match row["name"]:
|
||||
case "Collection":
|
||||
return Collection(row)
|
||||
case "Favorites":
|
||||
return UserPlaylist(row, "emmental-favorites", "playlist_map")
|
||||
case "New Tracks":
|
||||
return UserPlaylist(row, "starred", "temp_playlist_map")
|
||||
case "Previous":
|
||||
return Previous(row)
|
||||
case "Queued Tracks":
|
||||
return QueuedTracks(row)
|
||||
case _:
|
||||
return UserPlaylist(row, "audio-x-generic", "playlist_map")
|
||||
|
||||
def do_insert(self, plstate, name):
|
||||
return sql.execute("INSERT INTO playlists (plstateid, name, sort) "
|
||||
"VALUES (?, ?, ?)", [ plstate.rowid, name, name.casefold() ])
|
||||
|
||||
def do_lookup(self, name):
|
||||
return sql.execute("SELECT * FROM playlists WHERE name=?", [ name ])
|
||||
|
||||
def find(self, name, loop=False, sort=state.DefaultSort):
|
||||
if (res := self.lookup(name)) == None:
|
||||
res = self.insert(name, loop=loop, sort=sort)
|
||||
return res
|
||||
|
||||
|
||||
Table = UserTable()
|
49
db/year.py
49
db/year.py
|
@ -1,49 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
#
|
||||
# Table: years
|
||||
# +--------+----------+-----------+------+
|
||||
# | yearid | decadeid | plstateid | year |
|
||||
# +--------+----------+-----------+------+
|
||||
from gi.repository import GObject
|
||||
from . import playlist
|
||||
from . import sql
|
||||
|
||||
class Year(playlist.Playlist):
|
||||
def __init__(self, row):
|
||||
playlist.Playlist.__init__(self, row, "x-office-calendar")
|
||||
self._year = row["year"]
|
||||
|
||||
def delete(self): Table.delete(self)
|
||||
|
||||
@GObject.Property
|
||||
def name(self): return str(self._year)
|
||||
|
||||
@GObject.Property
|
||||
def year(self): return self._year
|
||||
|
||||
|
||||
class YearTable(playlist.ChildModel):
|
||||
def __init__(self):
|
||||
playlist.ChildModel.__init__(self, "years", "decadeid", "year")
|
||||
|
||||
def do_create(self):
|
||||
sql.execute("CREATE TABLE IF NOT EXISTS years "
|
||||
"(yearid INTEGER PRIMARY KEY, "
|
||||
" decadeid INTEGER, "
|
||||
" plstateid INTEGER NOT NULL, "
|
||||
" year INTEGER UNIQUE, "
|
||||
" FOREIGN KEY(decadeid) REFERENCES decades(decadeid), "
|
||||
" FOREIGN KEY(plstateid) REFERENCES playlist_states(plstateid))")
|
||||
|
||||
def do_insert(self, plstate, dec, year):
|
||||
return sql.execute("INSERT INTO years (decadeid, plstateid, year) "
|
||||
"VALUES (?, ?, ?)", [ dec.rowid, plstate.rowid, year ])
|
||||
|
||||
def do_factory(self, row):
|
||||
return Year(row)
|
||||
|
||||
def do_lookup(self, year):
|
||||
return sql.execute("SELECT * FROM years WHERE year=?", [ year ])
|
||||
|
||||
|
||||
Table = YearTable()
|
32
emmental.py
32
emmental.py
|
@ -1,32 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import lib
|
||||
lib.settings.load()
|
||||
|
||||
import db
|
||||
import scanner
|
||||
import ui
|
||||
from gi.repository import Gtk
|
||||
|
||||
class Application(Gtk.Application):
|
||||
def __init__(self, *args, **kwargs):
|
||||
app_id = f"org.gtk.emmental{'-debug' if __debug__ else ''}"
|
||||
Gtk.Application.__init__(self, *args, application_id=app_id, **kwargs)
|
||||
|
||||
def do_startup(self):
|
||||
Gtk.Application.do_startup(self)
|
||||
self.add_window(ui.window.Window())
|
||||
for i in range(db.library.Table.get_n_items()):
|
||||
scanner.update_library(db.library.Table.get_item(i))
|
||||
|
||||
def do_activate(self):
|
||||
for window in self.get_windows():
|
||||
window.present()
|
||||
|
||||
def do_shutdown(self):
|
||||
Gtk.Application.do_shutdown(self)
|
||||
scanner.Queue.clear()
|
||||
db.sql.optimize()
|
||||
|
||||
if __name__ == "__main__":
|
||||
Application().run()
|
|
@ -1,9 +0,0 @@
|
|||
# Copyright 2020 (c) Anna Schumaker.
|
||||
import gi
|
||||
gi.require_version("Gtk", "4.0")
|
||||
gi.require_version("Gst", "1.0")
|
||||
|
||||
from . import data
|
||||
from . import filter
|
||||
from . import settings
|
||||
from . import version
|
64
lib/data.py
64
lib/data.py
|
@ -1,64 +0,0 @@
|
|||
# Copyright 2019 (c) Anna Schumaker.
|
||||
from . import version
|
||||
import pathlib
|
||||
import pickle
|
||||
import xdg.BaseDirectory
|
||||
|
||||
__resource = "emmental"
|
||||
if version.TESTING == True:
|
||||
__resource = "emmental-testing"
|
||||
elif __debug__ == True:
|
||||
__resource = "emmental-debug"
|
||||
|
||||
READ = 'rb'
|
||||
WRITE = 'wb'
|
||||
READ_TEXT = 'r'
|
||||
WRITE_TEXT = 'w'
|
||||
|
||||
emmental_data = pathlib.Path(xdg.BaseDirectory.save_data_path(__resource))
|
||||
|
||||
class DataFile:
|
||||
def __init__(self, path, mode):
|
||||
self.path = emmental_data / path
|
||||
self.temp = emmental_data / f".{path}.tmp"
|
||||
self.mode = mode
|
||||
self.file = None
|
||||
|
||||
def __enter__(self):
|
||||
if self.mode in [ WRITE, WRITE_TEXT ]:
|
||||
self.file = self.temp.open(self.mode)
|
||||
elif self.mode in [ READ, READ_TEXT ] and self.path.exists():
|
||||
self.file = self.path.open(self.mode)
|
||||
return self
|
||||
|
||||
def __exit__(self, exp_type, exp_value, traceback):
|
||||
if self.file:
|
||||
self.file.flush()
|
||||
self.file.close()
|
||||
if self.mode in [ WRITE, WRITE_TEXT ] and exp_type == None:
|
||||
self.temp.replace(self.path)
|
||||
self.file = None
|
||||
return exp_type == None
|
||||
|
||||
def exists(self):
|
||||
return self.path.exists()
|
||||
|
||||
def pickle(self, obj):
|
||||
if self.file:
|
||||
pickle.dump(obj, self.file, pickle.HIGHEST_PROTOCOL)
|
||||
|
||||
def read(self):
|
||||
if self.file:
|
||||
return self.file.read()
|
||||
|
||||
def remove(self):
|
||||
if self.file == None:
|
||||
self.path.unlink(missing_ok = True)
|
||||
|
||||
def unpickle(self):
|
||||
if self.file:
|
||||
return pickle.load(self.file)
|
||||
|
||||
def write(self, text):
|
||||
if self.file:
|
||||
self.file.write(text)
|
|
@ -1,65 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import re
|
||||
from gi.repository import Gtk
|
||||
from . import version
|
||||
|
||||
class Regex(Gtk.Filter):
|
||||
def __init__(self):
|
||||
Gtk.Filter.__init__(self)
|
||||
self.pattern = re.compile("")
|
||||
|
||||
def do_match(self, item):
|
||||
raise NotImplementedError
|
||||
|
||||
def search(self, text):
|
||||
return self.pattern.search(text) != None
|
||||
|
||||
def set_pattern(self, pattern):
|
||||
change = Gtk.FilterChange.DIFFERENT
|
||||
if pattern in self.pattern.pattern:
|
||||
change = Gtk.FilterChange.LESS_STRICT
|
||||
elif self.pattern.pattern in pattern:
|
||||
change = Gtk.FilterChange.MORE_STRICT
|
||||
|
||||
self.pattern = re.compile(pattern, re.I)
|
||||
self.changed(change)
|
||||
|
||||
|
||||
class Popover(Gtk.Popover):
|
||||
def __init__(self):
|
||||
Gtk.Popover.__init__(self)
|
||||
self.set_child(Gtk.Label())
|
||||
self.set_autohide(False)
|
||||
|
||||
def popup(self, text):
|
||||
self.get_child().set_text(text)
|
||||
if not version.TESTING:
|
||||
super().popup()
|
||||
|
||||
|
||||
class Entry(Gtk.SearchEntry):
|
||||
def __init__(self, filter):
|
||||
Gtk.SearchEntry.__init__(self)
|
||||
self.filter = filter
|
||||
self.popover = Popover()
|
||||
|
||||
self.set_margin_top(5)
|
||||
self.set_margin_bottom(5)
|
||||
self.set_margin_start(5)
|
||||
self.set_margin_end(5)
|
||||
|
||||
self.set_property("placeholder-text", "Type to filter")
|
||||
self.connect("search-changed", self.changed)
|
||||
self.popover.set_parent(self)
|
||||
|
||||
def __del__(self):
|
||||
self.popover.unparent()
|
||||
|
||||
def changed(self, entry):
|
||||
try:
|
||||
self.filter.set_pattern(self.get_text())
|
||||
self.remove_css_class("warning")
|
||||
self.popover.popdown()
|
||||
except re.error as e:
|
||||
self.add_css_class("warning")
|
||||
self.popover.popup(str(e))
|
|
@ -1,60 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
from. import data
|
||||
import configparser
|
||||
import threading
|
||||
|
||||
config_file = "settings.cfg"
|
||||
config = configparser.ConfigParser()
|
||||
lock = threading.Lock()
|
||||
|
||||
class Prepare:
|
||||
def __init__(self, setting):
|
||||
self.setting = setting
|
||||
def __enter__(self):
|
||||
lock.acquire()
|
||||
return self.setting.split(".")
|
||||
def __exit__(self, exp_type, exp_value, traceback):
|
||||
lock.release()
|
||||
return exp_type == None
|
||||
|
||||
|
||||
def get(setting, default=None):
|
||||
with Prepare(setting) as (section, key):
|
||||
return config.get(section, key, fallback=default)
|
||||
|
||||
def get_int(setting, default=None):
|
||||
with Prepare(setting) as (section, key):
|
||||
return config.getint(section, key, fallback=default)
|
||||
|
||||
def get_float(setting, default=None):
|
||||
with Prepare(setting) as (section, key):
|
||||
return config.getfloat(section, key, fallback=default)
|
||||
|
||||
def get_bool(setting, default=False):
|
||||
with Prepare(setting) as (section, key):
|
||||
return config.getboolean(section, key, fallback=default)
|
||||
|
||||
def __do_set(section, key, value):
|
||||
if not config.has_section(section):
|
||||
config.add_section(section)
|
||||
config.set(section, key, str(value))
|
||||
with data.DataFile(config_file, data.WRITE_TEXT) as f:
|
||||
config.write(f.file)
|
||||
|
||||
def set(setting, value):
|
||||
with Prepare(setting) as (section, key):
|
||||
__do_set(section, key, value)
|
||||
|
||||
def initialize(setting, value):
|
||||
with Prepare(setting) as (section, key):
|
||||
if not config.has_option(section, key):
|
||||
__do_set(section, key, value)
|
||||
|
||||
def load():
|
||||
with data.DataFile(config_file, data.READ_TEXT) as f:
|
||||
if f.exists():
|
||||
config.read_file(f.file)
|
||||
|
||||
def reset():
|
||||
config.clear()
|
||||
data.DataFile(config_file, data.WRITE_TEXT).remove()
|
114
lib/test_data.py
114
lib/test_data.py
|
@ -1,114 +0,0 @@
|
|||
# Copyright 2019 (c) Anna Schumaker.
|
||||
from . import data
|
||||
import os
|
||||
import pathlib
|
||||
import unittest
|
||||
import xdg.BaseDirectory
|
||||
|
||||
xdg_data_home = pathlib.Path(xdg.BaseDirectory.xdg_data_home)
|
||||
testing_data = xdg_data_home / "emmental-testing"
|
||||
testing_file = testing_data / "test.file"
|
||||
testing_temp = testing_data / ".test.file.tmp"
|
||||
|
||||
class TestDataModule(unittest.TestCase):
|
||||
def tearDown(self):
|
||||
testing_file.unlink(missing_ok=True)
|
||||
|
||||
def test_dir(self):
|
||||
self.assertEqual(data.emmental_data, testing_data)
|
||||
self.assertTrue(testing_data.exists())
|
||||
self.assertTrue(testing_data.is_dir())
|
||||
self.assertEqual(data.READ, 'rb')
|
||||
self.assertEqual(data.WRITE, 'wb')
|
||||
self.assertEqual(data.READ_TEXT, 'r')
|
||||
self.assertEqual(data.WRITE_TEXT, 'w')
|
||||
|
||||
def test_data_file_init(self):
|
||||
f = data.DataFile("test.file", data.READ)
|
||||
self.assertEqual(f.path, testing_file)
|
||||
self.assertEqual(f.temp, testing_temp)
|
||||
self.assertFalse(f.exists())
|
||||
self.assertEqual(f.mode, data.READ)
|
||||
self.assertIsNone(f.file)
|
||||
|
||||
f = data.DataFile("test.file", data.WRITE)
|
||||
self.assertEqual(f.temp, testing_temp)
|
||||
self.assertFalse(f.exists())
|
||||
self.assertEqual(f.mode, data.WRITE)
|
||||
self.assertIsNone(f.file)
|
||||
|
||||
def test_data_file_read_write(self):
|
||||
test = [ "test", "saving", "a", "list" ]
|
||||
with data.DataFile("test.file", data.READ) as f:
|
||||
self.assertIsNone(f.file)
|
||||
f.pickle(test)
|
||||
self.assertIsNone(f.unpickle())
|
||||
f.remove()
|
||||
self.assertFalse(f.exists())
|
||||
|
||||
with data.DataFile("test.file", data.WRITE) as f:
|
||||
self.assertIsNotNone(f.file)
|
||||
self.assertEqual(pathlib.Path(f.file.name), testing_temp)
|
||||
self.assertFalse(f.exists())
|
||||
self.assertTrue(testing_temp.exists())
|
||||
f.pickle(test)
|
||||
f.remove()
|
||||
self.assertFalse(f.exists())
|
||||
|
||||
self.assertIsNone(f.file)
|
||||
self.assertFalse(testing_temp.exists())
|
||||
self.assertTrue(f.exists())
|
||||
|
||||
with data.DataFile("test.file", data.READ) as f:
|
||||
self.assertIsNotNone(f.file)
|
||||
self.assertEqual(pathlib.Path(f.file.name), testing_file)
|
||||
lst = f.unpickle()
|
||||
self.assertEqual(test, lst)
|
||||
f.remove()
|
||||
self.assertTrue(f.exists())
|
||||
|
||||
f = data.DataFile("test.file", data.READ)
|
||||
f.remove()
|
||||
self.assertFalse(f.exists())
|
||||
|
||||
def test_data_file_read_write_text(self):
|
||||
test = "test saving a string"
|
||||
with data.DataFile("test.file", data.READ_TEXT) as f:
|
||||
self.assertIsNone(f.file)
|
||||
f.write(test)
|
||||
self.assertIsNone(f.read())
|
||||
f.remove()
|
||||
self.assertFalse(f.exists())
|
||||
|
||||
with data.DataFile("test.file", data.WRITE_TEXT) as f:
|
||||
self.assertIsNotNone(f.file)
|
||||
self.assertEqual(pathlib.Path(f.file.name), testing_temp)
|
||||
self.assertFalse(f.exists())
|
||||
self.assertTrue(testing_temp.exists())
|
||||
f.write(test)
|
||||
f.remove()
|
||||
self.assertFalse(f.exists())
|
||||
|
||||
self.assertIsNone(f.file)
|
||||
self.assertFalse(testing_temp.exists())
|
||||
self.assertTrue(f.exists())
|
||||
|
||||
with data.DataFile("test.file", data.READ_TEXT) as f:
|
||||
self.assertIsNotNone(f.file)
|
||||
self.assertEqual(pathlib.Path(f.file.name), testing_file)
|
||||
txt = f.read()
|
||||
self.assertEqual(test, txt)
|
||||
f.remove()
|
||||
self.assertTrue(f.exists())
|
||||
|
||||
f = data.DataFile("test.file", data.READ)
|
||||
f.remove()
|
||||
self.assertFalse(f.exists())
|
||||
|
||||
def test_data_file_exception(self):
|
||||
f = data.DataFile("test.file", data.WRITE_TEXT)
|
||||
with self.assertRaises(Exception):
|
||||
with f:
|
||||
f.write("test")
|
||||
raise Exception("Test Exception")
|
||||
self.assertFalse(f.exists())
|
|
@ -1,83 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import lib
|
||||
import re
|
||||
import unittest
|
||||
from gi.repository import Gtk
|
||||
|
||||
class TestRegex(unittest.TestCase):
|
||||
def changed(self, filter, how):
|
||||
self.how = how
|
||||
|
||||
def test_init(self):
|
||||
filter = lib.filter.Regex()
|
||||
self.assertIsInstance(filter, Gtk.Filter)
|
||||
self.assertIsInstance(filter.pattern, re.Pattern)
|
||||
self.assertEqual(filter.pattern.pattern, "")
|
||||
with self.assertRaises(NotImplementedError):
|
||||
filter.do_match(None)
|
||||
|
||||
def test_set_pattern(self):
|
||||
filter = lib.filter.Regex()
|
||||
filter.connect("changed", self.changed)
|
||||
|
||||
filter.set_pattern("abc")
|
||||
self.assertEqual(filter.pattern.pattern, "abc")
|
||||
self.assertEqual(self.how, Gtk.FilterChange.MORE_STRICT)
|
||||
|
||||
filter.set_pattern("ab")
|
||||
self.assertEqual(filter.pattern.pattern, "ab")
|
||||
self.assertEqual(self.how, Gtk.FilterChange.LESS_STRICT)
|
||||
|
||||
filter.set_pattern("cd")
|
||||
self.assertEqual(self.how, Gtk.FilterChange.DIFFERENT)
|
||||
self.assertEqual(filter.pattern.pattern, "cd")
|
||||
|
||||
def test_search(self):
|
||||
filter = lib.filter.Regex()
|
||||
filter.set_pattern("ab")
|
||||
|
||||
self.assertTrue(filter.search("abc"))
|
||||
self.assertFalse(filter.search("cde"))
|
||||
|
||||
|
||||
class TestPopover(unittest.TestCase):
|
||||
def test_init(self):
|
||||
popover = lib.filter.Popover()
|
||||
self.assertIsInstance(popover, Gtk.Popover)
|
||||
self.assertIsInstance(popover.get_child(), Gtk.Label)
|
||||
self.assertFalse(popover.get_autohide())
|
||||
|
||||
def test_popup(self):
|
||||
popover = lib.filter.Popover()
|
||||
popover.popup("test")
|
||||
self.assertEqual(popover.get_child().get_text(), "test")
|
||||
|
||||
|
||||
class TestEntry(unittest.TestCase):
|
||||
def test_init(self):
|
||||
filter = lib.filter.Regex()
|
||||
entry = lib.filter.Entry(filter)
|
||||
|
||||
self.assertIsInstance(entry, Gtk.SearchEntry)
|
||||
self.assertIsInstance(entry.popover, lib.filter.Popover)
|
||||
self.assertEqual(entry.get_property("placeholder-text"), "Type to filter")
|
||||
self.assertEqual(entry.get_margin_top(), 5)
|
||||
self.assertEqual(entry.get_margin_bottom(), 5)
|
||||
self.assertEqual(entry.get_margin_start(), 5)
|
||||
self.assertEqual(entry.get_margin_end(), 5)
|
||||
self.assertEqual(entry.filter, filter)
|
||||
self.assertEqual(entry.popover.get_parent(), entry)
|
||||
|
||||
def test_changed(self):
|
||||
filter = lib.filter.Regex()
|
||||
entry = lib.filter.Entry(filter)
|
||||
|
||||
entry.set_text("(abc")
|
||||
entry.changed(entry)
|
||||
self.assertEqual(filter.pattern.pattern, "")
|
||||
self.assertTrue(entry.has_css_class("warning"))
|
||||
|
||||
entry.set_text("(abc)")
|
||||
entry.changed(entry)
|
||||
self.assertEqual(filter.pattern.pattern, "(abc)")
|
||||
self.assertFalse(entry.has_css_class("warning"))
|
|
@ -1,66 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
from . import data
|
||||
from . import settings
|
||||
import configparser
|
||||
import threading
|
||||
import unittest
|
||||
|
||||
class TestSettings(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.cfg_file = data.DataFile(settings.config_file, data.READ_TEXT)
|
||||
|
||||
def tearDown(self):
|
||||
settings.reset()
|
||||
|
||||
def test_init(self):
|
||||
self.assertEqual(settings.config_file, "settings.cfg")
|
||||
self.assertIsInstance(settings.config, configparser.ConfigParser)
|
||||
self.assertIsInstance(settings.lock, type(threading.Lock()))
|
||||
|
||||
def test_set_get(self):
|
||||
self.assertIsNone(settings.get("test.key"))
|
||||
|
||||
self.assertFalse(self.cfg_file.exists())
|
||||
settings.set("test.key", 42)
|
||||
self.assertTrue(self.cfg_file.exists())
|
||||
self.assertEqual(settings.get("test.key"), "42")
|
||||
self.assertIsNone(settings.get("test.nokey"))
|
||||
self.assertEqual(settings.get("test.nokey", 1), 1)
|
||||
self.assertEqual(settings.get("no.key", 1), 1)
|
||||
|
||||
self.assertEqual(settings.get_int("test.key"), 42)
|
||||
self.assertEqual(settings.get_float("test.key"), 42.0)
|
||||
|
||||
self.assertFalse(settings.get_bool("test.bool"))
|
||||
settings.set("test.bool", True)
|
||||
self.assertTrue(settings.get_bool("test.bool"))
|
||||
|
||||
def test_initialize(self):
|
||||
self.assertIsNone(settings.get("test.key"))
|
||||
settings.initialize("test.key", 1)
|
||||
self.assertEqual(settings.get_int("test.key"), 1)
|
||||
settings.initialize("test.key", 2)
|
||||
self.assertEqual(settings.get_int("test.key"), 1)
|
||||
|
||||
def test_load(self):
|
||||
settings.load()
|
||||
|
||||
settings.set("test.key1", 1)
|
||||
settings.set("test.key2", 2)
|
||||
settings.set("test.key3", True)
|
||||
settings.set("test.key4", 4.2)
|
||||
|
||||
settings.config.clear()
|
||||
settings.load()
|
||||
|
||||
self.assertEqual(settings.get_int("test.key1"), 1)
|
||||
self.assertEqual(settings.get_int("test.key2"), 2)
|
||||
self.assertEqual(settings.get_bool("test.key3"), True)
|
||||
self.assertEqual(settings.get_float("test.key4"), 4.2)
|
||||
|
||||
def test_reset(self):
|
||||
settings.set("test.key", 42)
|
||||
self.assertTrue(self.cfg_file.exists())
|
||||
settings.reset()
|
||||
self.assertIsNone(settings.get("test.key"))
|
||||
self.assertFalse(self.cfg_file.exists())
|
|
@ -1,13 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
from . import version
|
||||
import unittest
|
||||
|
||||
class TestVersion(unittest.TestCase):
|
||||
def test_version(self):
|
||||
self.assertEqual(version.MAJOR, 2)
|
||||
self.assertEqual(version.MINOR, 10)
|
||||
|
||||
self.assertTrue(__debug__)
|
||||
self.assertTrue(version.TESTING)
|
||||
|
||||
self.assertEqual(version.string(), "Emmental 2.10-debug")
|
|
@ -1,10 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import os
|
||||
import sys
|
||||
|
||||
MAJOR = 2
|
||||
MINOR = 10
|
||||
TESTING = "unittest" in sys.modules
|
||||
|
||||
def string():
|
||||
return f"Emmental {MAJOR}.{MINOR}{'-debug' if __debug__ else ''}"
|
|
@ -1,63 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import db
|
||||
import audio
|
||||
from gi.repository import Gdk
|
||||
from gi.repository import Gtk
|
||||
from . import footer
|
||||
from . import header
|
||||
from . import view
|
||||
|
||||
class Panel(Gtk.Box):
|
||||
def __init__(self):
|
||||
Gtk.Box.__init__(self)
|
||||
self.set_orientation(Gtk.Orientation.VERTICAL)
|
||||
self.header = header.Header()
|
||||
self.window = view.PlaylistWindow()
|
||||
self.footer = footer.Footer(self.window.get_filter_model())
|
||||
|
||||
self.append(self.header)
|
||||
self.append(Gtk.Separator.new(Gtk.Orientation.HORIZONTAL))
|
||||
self.append(self.window)
|
||||
self.append(Gtk.Separator.new(Gtk.Orientation.HORIZONTAL))
|
||||
self.append(self.footer)
|
||||
|
||||
keypress = Gtk.EventControllerKey.new()
|
||||
keypress.connect("key-pressed", self.key_pressed)
|
||||
self.add_controller(keypress)
|
||||
|
||||
self.header.get_jump_button().connect("clicked", self.jump_clicked)
|
||||
|
||||
def get_playlist(self): return self.window.get_playlist()
|
||||
def set_playlist(self, plist):
|
||||
if plist:
|
||||
self.header.set_playlist(plist)
|
||||
self.window.set_playlist(plist)
|
||||
|
||||
def key_pressed(self, event, keyval, keycode, state):
|
||||
match Gdk.keyval_name(keyval):
|
||||
case "Escape": self.window.clear_selection()
|
||||
case "Delete":
|
||||
playlist = self.get_playlist()
|
||||
if not (playlist and playlist.can_add_remove_tracks()):
|
||||
return False
|
||||
for track in self.selected_tracks():
|
||||
playlist.remove_track(track)
|
||||
case "f": self.add_selected_tracks(db.user.Table.find("Favorites"))
|
||||
case "q": self.add_selected_tracks(db.user.Table.find("Queued Tracks"))
|
||||
case _: return False
|
||||
return True
|
||||
|
||||
def jump_clicked(self, button):
|
||||
view = self.window.get_child()
|
||||
view.track_changed(audio.Player, None, audio.Player.track)
|
||||
|
||||
def selected_tracks(self):
|
||||
for track in self.window.selected_tracks():
|
||||
yield track
|
||||
|
||||
def add_selected_tracks(self, plist):
|
||||
for track in self.selected_tracks():
|
||||
plist.add_track(track)
|
||||
if plist == db.user.Table.find("Queued Tracks"):
|
||||
audio.Player.set_playlist(plist)
|
||||
db.sql.commit()
|
|
@ -1,128 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import audio
|
||||
import lib
|
||||
from gi.repository import Gtk, GLib, Pango
|
||||
from lib import settings
|
||||
|
||||
BoldAttr = Pango.AttrList.new()
|
||||
BoldAttr.insert(Pango.attr_weight_new(Pango.Weight.BOLD))
|
||||
|
||||
class TrackLabel(Gtk.Label):
|
||||
def set_item(self, item, text):
|
||||
audio.Player.connect("track-changed", self.track_changed, item)
|
||||
self.track_changed(audio.Player, None, audio.Player.track, item)
|
||||
self.set_text(text)
|
||||
|
||||
def unset_item(self, item):
|
||||
self.set_text("")
|
||||
audio.Player.disconnect_by_func(self.track_changed)
|
||||
|
||||
def track_changed(self, player, old, new, this):
|
||||
self.set_attributes(BoldAttr if this == new else None)
|
||||
|
||||
|
||||
class LabelFactory(Gtk.SignalListItemFactory):
|
||||
def __init__(self, xalign):
|
||||
Gtk.SignalListItemFactory.__init__(self)
|
||||
self.connect("setup", self.on_setup)
|
||||
self.connect("bind", self.on_bind)
|
||||
self.connect("unbind", self.on_unbind)
|
||||
self.connect("teardown", self.on_teardown)
|
||||
self.xalign = xalign
|
||||
|
||||
def get_track_text(self, track):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_track_dim(self, track):
|
||||
return False
|
||||
|
||||
def on_setup(self, factory, listitem):
|
||||
listitem.set_child(TrackLabel(xalign=self.xalign))
|
||||
|
||||
def on_bind(self, factory, listitem):
|
||||
item = listitem.get_item()
|
||||
if child := listitem.get_child():
|
||||
child.set_item(item, self.get_track_text(item))
|
||||
child.set_sensitive(not self.get_track_dim(item))
|
||||
|
||||
def on_unbind(self, factory, listitem):
|
||||
listitem.get_child().unset_item(listitem.get_item())
|
||||
|
||||
def on_teardown(self, factory, listitem):
|
||||
listitem.set_child(None)
|
||||
|
||||
|
||||
class TracknoFactory(LabelFactory):
|
||||
def __init__(self): LabelFactory.__init__(self, xalign=1)
|
||||
def get_track_text(self, track): return f"{track.disc.number}-{track.number:02}"
|
||||
|
||||
|
||||
class TitleFactory(LabelFactory):
|
||||
def __init__(self): LabelFactory.__init__(self, xalign=0)
|
||||
def get_track_text(self, track): return track.title
|
||||
|
||||
|
||||
class LengthFactory(LabelFactory):
|
||||
def __init__(self): LabelFactory.__init__(self, xalign=1)
|
||||
def get_track_text(self, track):
|
||||
(m, s) = divmod(int(track.length), 60)
|
||||
return f"{m}:{s:02}"
|
||||
|
||||
|
||||
class ArtistFactory(LabelFactory):
|
||||
def __init__(self): LabelFactory.__init__(self, xalign=0)
|
||||
def get_track_text(self, track): return track.artist.name
|
||||
|
||||
|
||||
class AlbumFactory(LabelFactory):
|
||||
def __init__(self): LabelFactory.__init__(self, xalign=0)
|
||||
def get_track_text(self, track): return track.album.name
|
||||
|
||||
|
||||
class SubtitleFactory(LabelFactory):
|
||||
def __init__(self): LabelFactory.__init__(self, xalign=0)
|
||||
def get_track_dim(self, track): return len(track.disc.subtitle) == 0
|
||||
def get_track_text(self, track):
|
||||
subtitle = track.disc.subtitle
|
||||
return subtitle if len(subtitle) > 0 else track.disc.name
|
||||
|
||||
|
||||
class YearFactory(LabelFactory):
|
||||
def __init__(self): LabelFactory.__init__(self, xalign=1)
|
||||
def get_track_text(self, track): return str(track.year.year)
|
||||
|
||||
|
||||
class PlayCountFactory(LabelFactory):
|
||||
def __init__(self): LabelFactory.__init__(self, xalign=1)
|
||||
def get_track_text(self, track): return str(track.playcount)
|
||||
|
||||
|
||||
class LastPlayedFactory(LabelFactory):
|
||||
def __init__(self): LabelFactory.__init__(self, xalign=0)
|
||||
def get_track_text(self, track):
|
||||
return "Never" if track.playcount == 0 else str(track.lastplayed)
|
||||
|
||||
|
||||
class Column(Gtk.ColumnViewColumn):
|
||||
def __init__(self, title, factory, width=-1, **kwargs):
|
||||
Gtk.ColumnViewColumn.__init__(self, title=title, **kwargs)
|
||||
self.set_factory(factory)
|
||||
self.set_resizable(True)
|
||||
|
||||
lib.settings.initialize(f"column.{title}", width)
|
||||
self.set_fixed_width(settings.get_int(f"column.{title}"))
|
||||
self.connect("notify::fixed-width", self.width_changed)
|
||||
|
||||
def width_changed(self, col, param):
|
||||
lib.settings.set(f"column.{self.get_title()}", self.get_fixed_width())
|
||||
|
||||
|
||||
def TracknoColumn(): return Column("#", TracknoFactory())
|
||||
def TitleColumn(): return Column("Title", TitleFactory(), width=250, expand=True)
|
||||
def LengthColumn(): return Column("Length", LengthFactory())
|
||||
def ArtistColumn(): return Column("Artist", ArtistFactory(), width=150, expand=True)
|
||||
def AlbumColumn(): return Column("Album", AlbumFactory(), width=150, expand=True)
|
||||
def SubtitleColumn(): return Column("Subtitle", SubtitleFactory(), width=150, expand=True)
|
||||
def YearColumn(): return Column("Year", YearFactory())
|
||||
def PlayCountColumn(): return Column("Count", PlayCountFactory())
|
||||
def LastPlayedColumn(): return Column("Last Played", LastPlayedFactory())
|
|
@ -1,49 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
from gi.repository import Gtk
|
||||
|
||||
class VisibleTracks(Gtk.Label):
|
||||
def __init__(self):
|
||||
Gtk.Label.__init__(self)
|
||||
self.set_halign(Gtk.Align.START)
|
||||
self.set_hexpand(True)
|
||||
|
||||
def set_count(self, n):
|
||||
s = 's' if n != 1 else ''
|
||||
self.set_text(f"Showing {n} track{s}")
|
||||
|
||||
|
||||
class Runtime(Gtk.Label):
|
||||
def __init__(self):
|
||||
Gtk.Label.__init__(self)
|
||||
self.set_halign(Gtk.Align.END)
|
||||
self.set_hexpand(True)
|
||||
|
||||
def set_runtime(self, seconds):
|
||||
(min, sec) = divmod(seconds, 60)
|
||||
(hour, min) = divmod(min, 60)
|
||||
(day, hour) = divmod(hour, 24)
|
||||
(week, day) = divmod(day, 7)
|
||||
|
||||
vals = [ ("week", week), ("day", day), ("hour", hour),
|
||||
("minute", min), ("second", sec) ]
|
||||
text = [ f"{v} {k}{'s' if v != 1 else ''}" for (k, v) in vals if v != 0 ]
|
||||
|
||||
self.set_text(', '.join(text) if len(text) > 0 else "0 seconds")
|
||||
|
||||
|
||||
class Footer(Gtk.Box):
|
||||
def __init__(self, filter):
|
||||
Gtk.Box.__init__(self)
|
||||
self.visible = VisibleTracks()
|
||||
self.runtime = Runtime()
|
||||
|
||||
self.append(self.visible)
|
||||
self.append(self.runtime)
|
||||
|
||||
filter.connect("notify::pending", self.update_visible)
|
||||
self.set_margin_start(5)
|
||||
self.set_margin_end(5)
|
||||
|
||||
def update_visible(self, model, param):
|
||||
self.visible.set_count(model.get_n_items())
|
||||
self.runtime.set_runtime(model.get_runtime())
|
|
@ -1,142 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import audio
|
||||
import db
|
||||
import lib
|
||||
from gi.repository import Gtk
|
||||
from . import model
|
||||
from . import view
|
||||
|
||||
class FilterEntry(lib.filter.Entry):
|
||||
def __init__(self):
|
||||
lib.filter.Entry.__init__(self, model.PlaylistFilter)
|
||||
self.set_margin_start(200)
|
||||
self.set_margin_end(200)
|
||||
self.set_hexpand(True)
|
||||
|
||||
|
||||
class PropertyToggle(Gtk.ToggleButton):
|
||||
def __init__(self, prop, icon):
|
||||
Gtk.ToggleButton.__init__(self)
|
||||
self.set_icon_name(icon)
|
||||
self.set_sensitive(False)
|
||||
self.property = prop
|
||||
self.playlist = None
|
||||
|
||||
def set_playlist(self, plist):
|
||||
self.playlist = plist
|
||||
self.set_active(plist.get_property(self.property))
|
||||
|
||||
def do_toggled(self):
|
||||
if self.playlist:
|
||||
self.playlist.set_property(self.property, self.get_active())
|
||||
|
||||
|
||||
class RandomToggle(PropertyToggle):
|
||||
def __init__(self):
|
||||
PropertyToggle.__init__(self, "random", "media-playlist-shuffle")
|
||||
|
||||
def set_playlist(self, plist):
|
||||
super().set_playlist(plist)
|
||||
self.set_sensitive(plist != db.user.Table.find("Previous"))
|
||||
|
||||
|
||||
class LoopToggle(PropertyToggle):
|
||||
def __init__(self):
|
||||
PropertyToggle.__init__(self, "loop", "media-playlist-repeat")
|
||||
|
||||
def set_playlist(self, plist):
|
||||
super().set_playlist(plist)
|
||||
self.set_sensitive(plist != db.user.Table.find("Collection") and
|
||||
plist != db.user.Table.find("Previous") and
|
||||
plist != db.user.Table.find("Queued Tracks"))
|
||||
|
||||
|
||||
class SortButton(Gtk.MenuButton):
|
||||
def __init__(self):
|
||||
Gtk.MenuButton.__init__(self)
|
||||
self.set_icon_name("view-sort-ascending")
|
||||
self.set_popover(view.SortOrderPopover())
|
||||
self.set_sensitive(False)
|
||||
|
||||
def set_playlist(self, plist):
|
||||
self.get_popover().set_playlist(plist)
|
||||
self.set_sensitive(plist != db.user.Table.find("Previous"))
|
||||
|
||||
|
||||
class FavoriteButton(Gtk.ToggleButton):
|
||||
def __init__(self):
|
||||
Gtk.ToggleButton.__init__(self)
|
||||
self.set_icon_name("emmental-favorites")
|
||||
self.track_changed(audio.Player, None, audio.Player.track)
|
||||
audio.Player.connect("track-changed", self.track_changed)
|
||||
|
||||
def set_playlist(self, plist):
|
||||
pass
|
||||
|
||||
def track_changed(self, player, old, new):
|
||||
self.set_sensitive(new != None)
|
||||
self.set_active(new in db.user.Table.find("Favorites").get_tracks())
|
||||
|
||||
def do_toggled(self):
|
||||
if audio.Player.track:
|
||||
fav = db.user.Table.find("Favorites")
|
||||
if self.get_active():
|
||||
fav.add_track(audio.Player.track)
|
||||
else:
|
||||
fav.remove_track(audio.Player.track)
|
||||
|
||||
|
||||
class JumpButton(Gtk.Button):
|
||||
def __init__(self):
|
||||
Gtk.Button.__init__(self)
|
||||
self.set_icon_name("go-jump")
|
||||
self.set_sensitive(False)
|
||||
|
||||
def set_playlist(self, plist):
|
||||
self.set_sensitive(plist is not None)
|
||||
|
||||
|
||||
class ControlBox(Gtk.Box):
|
||||
def __init__(self):
|
||||
Gtk.Box.__init__(self, margin_top=5, margin_bottom=5,
|
||||
margin_end=5, margin_start=5)
|
||||
self.add_css_class("linked")
|
||||
|
||||
def set_playlist(self, plist):
|
||||
child = self.get_first_child()
|
||||
while child:
|
||||
child.set_playlist(plist)
|
||||
child = child.get_next_sibling()
|
||||
|
||||
|
||||
class TrackBox(ControlBox):
|
||||
def __init__(self):
|
||||
ControlBox.__init__(self)
|
||||
self.append(FavoriteButton())
|
||||
self.append(JumpButton())
|
||||
|
||||
def get_jump_button(self):
|
||||
return self.get_last_child()
|
||||
|
||||
|
||||
class PlaylistBox(ControlBox):
|
||||
def __init__(self):
|
||||
ControlBox.__init__(self)
|
||||
self.append(RandomToggle())
|
||||
self.append(LoopToggle())
|
||||
self.append(SortButton())
|
||||
|
||||
|
||||
class Header(Gtk.Box):
|
||||
def __init__(self):
|
||||
Gtk.Box.__init__(self)
|
||||
self.append(TrackBox())
|
||||
self.append(FilterEntry())
|
||||
self.append(PlaylistBox())
|
||||
|
||||
def get_jump_button(self):
|
||||
return self.get_first_child().get_jump_button()
|
||||
|
||||
def set_playlist(self, plist):
|
||||
self.get_first_child().set_playlist(plist)
|
||||
self.get_last_child().set_playlist(plist)
|
|
@ -1,209 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import lib
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gio
|
||||
from gi.repository import Gtk
|
||||
|
||||
class PlaylistModel(GObject.GObject, Gio.ListModel):
|
||||
def __init__(self):
|
||||
GObject.GObject.__init__(self)
|
||||
self.playlist = None
|
||||
self.tracks = [ ]
|
||||
|
||||
def do_get_item_type(self): return GObject.TYPE_PYOBJECT
|
||||
def do_get_n_items(self): return len(self.tracks)
|
||||
def do_get_item(self, n):
|
||||
return self.tracks[n] if n < len(self.tracks) else None
|
||||
|
||||
def get_playlist(self): return self.playlist
|
||||
def set_playlist(self, playlist):
|
||||
if self.playlist:
|
||||
self.playlist.disconnect_by_func(self.refreshed)
|
||||
self.playlist.disconnect_by_func(self.track_added)
|
||||
self.playlist.disconnect_by_func(self.track_removed)
|
||||
self.playlist = playlist
|
||||
self.refreshed(playlist)
|
||||
self.playlist.connect("refreshed", self.refreshed)
|
||||
self.playlist.connect("track-added", self.track_added)
|
||||
self.playlist.connect("track-removed", self.track_removed)
|
||||
|
||||
def track_added(self, plist, track):
|
||||
index = plist.get_track_index(track)
|
||||
self.tracks.insert(index, track)
|
||||
self.emit("items-changed", index, 0, 1)
|
||||
|
||||
def track_removed(self, plist, track, adjusted_current):
|
||||
index = self.tracks.index(track)
|
||||
del self.tracks[index]
|
||||
self.emit("items-changed", index, 1, 0)
|
||||
|
||||
def refreshed(self, plist):
|
||||
rm = len(self.tracks)
|
||||
self.tracks = plist.get_tracks()
|
||||
self.emit("items-changed", 0, rm, len(self.tracks))
|
||||
|
||||
|
||||
class Filter(lib.filter.Regex):
|
||||
def do_match(self, track):
|
||||
fields = [ f"disc={track.disc.number}",
|
||||
f"track={track.number}",
|
||||
f"title={track.title}",
|
||||
f"artist={track.artist.name}",
|
||||
f"album={track.album.name}",
|
||||
f"subtitle={track.disc.subtitle}",
|
||||
f"year={track.year.year}" ]
|
||||
return self.search(' & '.join(fields))
|
||||
PlaylistFilter = Filter()
|
||||
|
||||
|
||||
class FilterPlaylistModel(Gtk.FilterListModel):
|
||||
def __init__(self):
|
||||
Gtk.FilterListModel.__init__(self)
|
||||
self.set_model(PlaylistModel())
|
||||
self.set_filter(PlaylistFilter)
|
||||
|
||||
def get_playlist(self): return self.get_model().get_playlist()
|
||||
def set_playlist(self, plist):
|
||||
self.set_incremental(plist.get_n_tracks() > 1000)
|
||||
self.get_model().set_playlist(plist)
|
||||
|
||||
def get_runtime(self):
|
||||
n = self.get_n_items()
|
||||
return sum([ int(self.get_item(i).length) for i in range(n) ])
|
||||
|
||||
|
||||
class PlaylistSelection(Gtk.MultiSelection):
|
||||
def __init__(self):
|
||||
Gtk.MultiSelection.__init__(self)
|
||||
self.set_model(FilterPlaylistModel())
|
||||
|
||||
def get_filter_model(self): return self.get_model()
|
||||
def get_playlist(self): return self.get_model().get_playlist()
|
||||
def set_playlist(self, plist): return self.get_model().set_playlist(plist)
|
||||
|
||||
|
||||
class SortPlaylistModel(Gtk.StringList):
|
||||
def __init__(self):
|
||||
Gtk.StringList.__init__(self)
|
||||
self.playlist = None
|
||||
self.connect("items-changed", self.order_changed)
|
||||
|
||||
def get_index(self, string):
|
||||
fields = [ self.get_string(i).split()[0] for i in range(self.get_n_items()) ]
|
||||
return fields.index(string) if string in fields else None
|
||||
|
||||
def get_direction(self, string):
|
||||
return self.get_string(self.get_index(string)).split()[1]
|
||||
|
||||
def set_playlist(self, plist):
|
||||
self.handler_block_by_func(self.order_changed)
|
||||
self.playlist = plist
|
||||
order = [ f for f in plist.sort if f.split()[0] not in ( "discs.number", "tracks.trackid") ]
|
||||
self.splice(0, self.get_n_items(), order)
|
||||
self.handler_unblock_by_func(self.order_changed)
|
||||
|
||||
def append(self, field):
|
||||
super().append(f"{field} ASC")
|
||||
|
||||
def remove(self, field):
|
||||
super().remove(self.get_index(field))
|
||||
|
||||
def move_up(self, field):
|
||||
if (index := self.get_index(field)) > 0:
|
||||
self.splice(index - 1, 2, [ self.get_string(index),
|
||||
self.get_string(index - 1) ])
|
||||
|
||||
def move_down(self, field):
|
||||
if (index := self.get_index(field)) < (self.get_n_items() - 1):
|
||||
self.splice(index, 2, [ self.get_string(index + 1),
|
||||
self.get_string(index) ])
|
||||
|
||||
def reverse(self, field):
|
||||
index = self.get_index(field)
|
||||
dir = 'DESC' if self.get_direction(field) == 'ASC' else 'ASC'
|
||||
self.splice(index, 1, [ f"{field} {dir}" ])
|
||||
|
||||
def order_changed(self, model, pos, rm, add):
|
||||
order = [ self.get_string(i) for i in range(self.get_n_items()) ]
|
||||
if i := self.get_index("tracks.number"):
|
||||
order.insert(i, f"discs.number {self.get_direction('tracks.number')}")
|
||||
self.playlist.sort = order
|
||||
|
||||
|
||||
class SortOptionsModel(Gtk.StringList):
|
||||
def __init__(self):
|
||||
Gtk.StringList.__init__(self)
|
||||
self.append("tracks.number ASC")
|
||||
self.append("tracks.title ASC"),
|
||||
self.append("tracks.length ASC"),
|
||||
self.append("artists.sort ASC"),
|
||||
self.append("albums.sort ASC"),
|
||||
self.append("discs.subtitle ASC"),
|
||||
self.append("albums.release ASC"),
|
||||
self.append("tracks.playcount ASC"),
|
||||
self.append("tracks.lastplayed ASC")
|
||||
|
||||
|
||||
class DisabledOptionsFilter(Gtk.Filter):
|
||||
def __init__(self):
|
||||
Gtk.Filter.__init__(self)
|
||||
self.playlist = None
|
||||
|
||||
def changed(self):
|
||||
super().changed(Gtk.FilterChange.DIFFERENT)
|
||||
|
||||
def do_match(self, item):
|
||||
if self.playlist == None: return True
|
||||
field = item.get_string().split()[0]
|
||||
return field not in [ f.split()[0] for f in self.playlist.sort ]
|
||||
|
||||
def set_playlist(self, plist):
|
||||
self.playlist = plist
|
||||
self.changed()
|
||||
|
||||
|
||||
class DisabledOptionsModel(Gtk.FilterListModel):
|
||||
def __init__(self):
|
||||
Gtk.FilterListModel.__init__(self)
|
||||
self.set_model(SortOptionsModel())
|
||||
self.set_filter(DisabledOptionsFilter())
|
||||
|
||||
def set_playlist(self, plist):
|
||||
self.get_filter().set_playlist(plist)
|
||||
|
||||
|
||||
class SortModelsModel(GObject.GObject, Gio.ListModel):
|
||||
def __init__(self):
|
||||
GObject.GObject.__init__(self)
|
||||
self.models = [ SortPlaylistModel(), DisabledOptionsModel() ]
|
||||
|
||||
def do_get_item_type(self): return Gio.ListModel
|
||||
def do_get_n_items(self): return len(self.models)
|
||||
def do_get_item(self, n): return self.models[n]
|
||||
|
||||
def set_playlist(self, plist):
|
||||
for model in self.models:
|
||||
model.set_playlist(plist)
|
||||
|
||||
|
||||
class FlatSortModel(Gtk.FlattenListModel):
|
||||
def __init__(self):
|
||||
Gtk.FlattenListModel.__init__(self)
|
||||
self.set_model(SortModelsModel())
|
||||
|
||||
def set_playlist(self, plist):
|
||||
self.get_model().set_playlist(plist)
|
||||
|
||||
def get_n_enabled(self):
|
||||
return self.get_enabled_model().get_n_items()
|
||||
|
||||
def get_enabled_model(self):
|
||||
return self.get_model().get_item(0)
|
||||
|
||||
def enable(self, field):
|
||||
self.get_model().get_item(0).append(field)
|
||||
self.get_model().get_item(1).get_filter().changed()
|
||||
|
||||
def disable(self, field):
|
||||
self.get_model().get_item(0).remove(field)
|
||||
self.get_model().get_item(1).get_filter().changed()
|
169
playlist/sort.py
169
playlist/sort.py
|
@ -1,169 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gtk
|
||||
|
||||
class UpDownBox(Gtk.Box):
|
||||
def __init__(self):
|
||||
Gtk.Box.__init__(self)
|
||||
self.append(Gtk.Button.new_from_icon_name("go-up-symbolic"))
|
||||
self.append(Gtk.Button.new_from_icon_name("go-down-symbolic"))
|
||||
self.add_css_class("linked")
|
||||
|
||||
self.get_first_child().connect("clicked", self.clicked, "move-up")
|
||||
self.get_last_child().connect("clicked", self.clicked, "move-down")
|
||||
|
||||
self.get_first_child().set_sensitive(False)
|
||||
self.get_last_child().set_sensitive(False)
|
||||
|
||||
def set_active(self, active, first, last):
|
||||
self.get_first_child().set_sensitive(active and not first)
|
||||
self.get_last_child().set_sensitive(active and not last)
|
||||
|
||||
def clicked(self, button, signal):
|
||||
self.emit(signal)
|
||||
|
||||
@GObject.Signal
|
||||
def move_up(self): pass
|
||||
|
||||
@GObject.Signal
|
||||
def move_down(self): pass
|
||||
|
||||
|
||||
class DirectionButton(Gtk.Button):
|
||||
def __init__(self):
|
||||
Gtk.Button.__init__(self, sensitive=False)
|
||||
self.set_icon_name("view-sort-ascending")
|
||||
|
||||
def set_direction(self, direction, active):
|
||||
sort = "ascending" if direction == "ASC" else "descending"
|
||||
self.set_icon_name(f"view-sort-{sort}")
|
||||
self.set_sensitive(active)
|
||||
|
||||
|
||||
class FieldLabel(Gtk.Label):
|
||||
def __init__(self):
|
||||
Gtk.Label.__init__(self, sensitive=False, hexpand=True)
|
||||
|
||||
def set_field(self, field, active):
|
||||
self.set_sensitive(active)
|
||||
self.set_text({ "tracks.number" : "Track Number",
|
||||
"tracks.title" : "Track Title",
|
||||
"tracks.length" : "Track Length",
|
||||
"artists.sort" : "Artist Name",
|
||||
"albums.sort" : "Album Name",
|
||||
"discs.subtitle" : "Disc Subtitle",
|
||||
"albums.release" : "Release Date",
|
||||
"tracks.playcount" : "Play Count",
|
||||
"tracks.lastplayed" : "Last Played",
|
||||
"playlist_map.rowid" : "Order Added"}.get(field, field))
|
||||
|
||||
def get_field(self):
|
||||
text = self.get_text()
|
||||
return { "Track Number" : "tracks.number",
|
||||
"Track Title" : "tracks.title",
|
||||
"Track Length" : "tracks.length",
|
||||
"Artist Name" : "artists.sort",
|
||||
"Album Name" : "albums.sort",
|
||||
"Disc Subtitle" : "discs.subtitle",
|
||||
"Release Date" : "albums.release",
|
||||
"Play Count" : "tracks.playcount",
|
||||
"Last Played" : "tracks.lastplayed",
|
||||
"Order Added" : "playlist_map.rowid"}.get(text, text)
|
||||
|
||||
|
||||
class SortRow(Gtk.Box):
|
||||
def __init__(self):
|
||||
Gtk.Box.__init__(self, spacing=5)
|
||||
self.switch = Gtk.Switch(valign=Gtk.Align.CENTER)
|
||||
self.label = FieldLabel()
|
||||
self.direction = DirectionButton()
|
||||
self.updown = UpDownBox()
|
||||
|
||||
self.append(self.switch)
|
||||
self.append(self.label)
|
||||
self.append(self.direction)
|
||||
self.append(self.updown)
|
||||
|
||||
self.switch.connect("notify::active", self.switch_active)
|
||||
self.direction.connect("clicked", self.reverse)
|
||||
self.updown.connect("move-up", self.moved_up)
|
||||
self.updown.connect("move-down", self.moved_down)
|
||||
|
||||
def set_item(self, text, index, n_active):
|
||||
(field, direction) = text.split()
|
||||
self.switch.set_active(index < n_active)
|
||||
self.switch.set_sensitive("rowid" not in field)
|
||||
self.label.set_field(field, index < n_active)
|
||||
self.direction.set_direction(direction, index < n_active)
|
||||
self.updown.set_active(index < n_active, index == 0,
|
||||
index == n_active - 1)
|
||||
|
||||
def switch_active(self, switch, param): self.emit("switched", switch.get_active())
|
||||
def reverse(self, button): self.emit("reversed")
|
||||
def moved_up(self, button): self.emit("move-up")
|
||||
def moved_down(self, button): self.emit("move-down")
|
||||
|
||||
@GObject.Signal(arg_types=(bool,))
|
||||
def switched(self, switch): pass
|
||||
|
||||
@GObject.Signal
|
||||
def reversed(self): pass
|
||||
|
||||
@GObject.Signal
|
||||
def move_up(self): pass
|
||||
|
||||
@GObject.Signal
|
||||
def move_down(self): pass
|
||||
|
||||
|
||||
class Factory(Gtk.SignalListItemFactory):
|
||||
def __init__(self, model):
|
||||
self.model = model
|
||||
Gtk.SignalListItemFactory.__init__(self)
|
||||
self.connect("setup", self.setup)
|
||||
self.connect("bind", self.bind)
|
||||
self.connect("unbind", self.unbind)
|
||||
self.connect("teardown", self.teardown)
|
||||
|
||||
def setup(self, factory, listitem):
|
||||
listitem.set_child(SortRow())
|
||||
|
||||
def bind(self, factory, listitem):
|
||||
child = listitem.get_child()
|
||||
child.set_item(listitem.get_item().get_string(),
|
||||
listitem.get_position(),
|
||||
self.model.get_n_enabled())
|
||||
child.connect("switched", self.row_switched)
|
||||
child.connect("reversed", self.row_reversed)
|
||||
child.connect("move-up", self.move_row_up)
|
||||
child.connect("move-down", self.move_row_down)
|
||||
|
||||
def unbind(self, factory, listitem):
|
||||
child = listitem.get_child()
|
||||
child.disconnect_by_func(self.row_switched)
|
||||
child.disconnect_by_func(self.row_reversed)
|
||||
child.disconnect_by_func(self.move_row_up)
|
||||
child.disconnect_by_func(self.move_row_down)
|
||||
|
||||
def teardown(self, factory, listitem):
|
||||
listitem.set_child(None)
|
||||
|
||||
def row_switched(self, row, enabled):
|
||||
field = row.label.get_field()
|
||||
if enabled:
|
||||
self.model.enable(field)
|
||||
index = self.model.get_enabled_model().get_index(field)
|
||||
else:
|
||||
index = self.model.get_enabled_model().get_index(field)
|
||||
self.model.disable(field)
|
||||
if index > 0:
|
||||
self.model.emit("items-changed", index - 1, 1, 1)
|
||||
|
||||
def row_reversed(self, row):
|
||||
self.model.get_enabled_model().reverse(row.label.get_field())
|
||||
|
||||
def move_row_up(self, row):
|
||||
self.model.get_enabled_model().move_up(row.label.get_field())
|
||||
|
||||
def move_row_down(self, row):
|
||||
self.model.get_enabled_model().move_down(row.label.get_field())
|
|
@ -1,216 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import audio
|
||||
import db
|
||||
import lib
|
||||
import unittest
|
||||
from gi.repository import Gtk
|
||||
from . import column
|
||||
|
||||
class FakeListItem:
|
||||
def __init__(self): self.child = None
|
||||
def get_item(self): return None
|
||||
def get_child(self): return self.child
|
||||
def set_child(self, child): self.child = child
|
||||
|
||||
|
||||
class TestLabelFactory(unittest.TestCase):
|
||||
def setUp(self): db.reset()
|
||||
|
||||
def test_init(self):
|
||||
factory = column.LabelFactory(0.5)
|
||||
self.assertIsInstance(factory, Gtk.SignalListItemFactory)
|
||||
self.assertEqual(factory.xalign, 0.5)
|
||||
|
||||
def test_label(self):
|
||||
audio.Player.track = None
|
||||
track = db.make_fake_track(1, 1, "Test Track", "/a/b/c/1.ogg")
|
||||
label = column.TrackLabel()
|
||||
|
||||
self.assertIsInstance(label, Gtk.Label)
|
||||
self.assertIsNone(label.get_attributes())
|
||||
|
||||
label.set_attributes(column.BoldAttr)
|
||||
label.set_item(track, "Test Track")
|
||||
self.assertEqual(label.get_text(), "Test Track")
|
||||
self.assertIsNone(label.get_attributes())
|
||||
|
||||
audio.Player.change_track(track)
|
||||
self.assertIsNotNone(label.get_attributes())
|
||||
|
||||
label.unset_item(track)
|
||||
self.assertEqual(label.get_text(), "")
|
||||
|
||||
def test_setup_teardown(self):
|
||||
fake = FakeListItem()
|
||||
factory = column.LabelFactory(0.5)
|
||||
|
||||
factory.on_setup(factory, fake)
|
||||
self.assertIsInstance(fake.child, column.TrackLabel)
|
||||
self.assertEqual(fake.child.get_xalign(), 0.5)
|
||||
|
||||
factory.on_teardown(factory, fake)
|
||||
self.assertIsNone(fake.child)
|
||||
|
||||
def test_bind_unbind(self):
|
||||
track = db.make_fake_track(1, 1, "Test Track", "/a/b/c/1.ogg")
|
||||
fake = FakeListItem()
|
||||
factory = column.LabelFactory(0.5)
|
||||
factory.on_setup(factory, fake)
|
||||
|
||||
with self.assertRaises(NotImplementedError):
|
||||
factory.on_bind(factory, fake)
|
||||
|
||||
fake.child.set_item(track, "abcde")
|
||||
factory.on_unbind(factory, fake)
|
||||
self.assertEqual(fake.child.get_text(), "")
|
||||
|
||||
|
||||
class TestColumnFactories(unittest.TestCase):
|
||||
def setUp(self):
|
||||
db.reset()
|
||||
self.track = db.make_fake_track(1, 1, "Test Track", "/a/b/c/1.ogg")
|
||||
|
||||
def test_trackno(self):
|
||||
factory = column.TracknoFactory()
|
||||
self.assertIsInstance(factory, column.LabelFactory)
|
||||
self.assertEqual(factory.get_track_text(self.track), "1-01")
|
||||
self.assertEqual(factory.xalign, 1)
|
||||
|
||||
def test_title(self):
|
||||
factory = column.TitleFactory()
|
||||
self.assertIsInstance(factory, column.LabelFactory)
|
||||
self.assertEqual(factory.get_track_text(self.track), "Test Track")
|
||||
|
||||
def test_length(self):
|
||||
factory = column.LengthFactory()
|
||||
self.assertIsInstance(factory, column.LabelFactory)
|
||||
self.assertEqual(factory.get_track_text(self.track), "0:01")
|
||||
self.assertEqual(factory.xalign, 1)
|
||||
|
||||
def test_artist(self):
|
||||
factory = column.ArtistFactory()
|
||||
self.assertIsInstance(factory, column.LabelFactory)
|
||||
self.assertEqual(factory.get_track_text(self.track), "Test Artist")
|
||||
|
||||
def test_album(self):
|
||||
factory = column.AlbumFactory()
|
||||
self.assertIsInstance(factory, column.LabelFactory)
|
||||
self.assertEqual(factory.get_track_text(self.track), "Test Album")
|
||||
|
||||
def test_subtitle(self):
|
||||
factory = column.SubtitleFactory()
|
||||
self.assertIsInstance(factory, column.LabelFactory)
|
||||
self.assertEqual(factory.get_track_text(self.track), "Disc 1")
|
||||
self.assertTrue(factory.get_track_dim(self.track))
|
||||
self.track.disc._subtitle = "Test Subtitle"
|
||||
self.assertEqual(factory.get_track_text(self.track), "Test Subtitle")
|
||||
self.assertFalse(factory.get_track_dim(self.track))
|
||||
|
||||
def test_year(self):
|
||||
factory = column.YearFactory()
|
||||
self.assertIsInstance(factory, column.LabelFactory)
|
||||
self.assertEqual(factory.get_track_text(self.track), "2021")
|
||||
self.assertEqual(factory.xalign, 1)
|
||||
|
||||
def test_playcount(self):
|
||||
factory = column.PlayCountFactory()
|
||||
self.assertIsInstance(factory, column.LabelFactory)
|
||||
self.assertEqual(factory.get_track_text(self.track), "0")
|
||||
self.assertEqual(factory.xalign, 1)
|
||||
|
||||
def test_lastplayed(self):
|
||||
factory = column.LastPlayedFactory()
|
||||
self.assertIsInstance(factory, column.LabelFactory)
|
||||
self.assertEqual(factory.get_track_text(self.track), "Never")
|
||||
self.track.played()
|
||||
self.assertNotEqual(factory.get_track_text(self.track), "Never")
|
||||
|
||||
|
||||
class TestColumn(unittest.TestCase):
|
||||
def test_init(self):
|
||||
factory = column.LabelFactory(0.5)
|
||||
col = column.Column("Test", factory)
|
||||
|
||||
self.assertIsInstance(col, Gtk.ColumnViewColumn)
|
||||
self.assertEqual(col.get_title(), "Test")
|
||||
self.assertEqual(col.get_factory(), factory)
|
||||
self.assertFalse(col.get_expand())
|
||||
self.assertTrue(col.get_resizable())
|
||||
|
||||
col = column.Column("Test2", factory, expand=True)
|
||||
self.assertTrue(col.get_expand())
|
||||
|
||||
def test_width(self):
|
||||
factory = column.LabelFactory(0.5)
|
||||
col = column.Column("Test", factory, width=-1)
|
||||
|
||||
self.assertEqual(lib.settings.get_int("column.Test"), -1)
|
||||
col.set_fixed_width(200)
|
||||
self.assertEqual(lib.settings.get_int("column.Test"), 200)
|
||||
|
||||
col2 = column.Column("Test", factory, width=-1)
|
||||
self.assertEqual(col2.get_fixed_width(), 200)
|
||||
|
||||
|
||||
class TestColumns(unittest.TestCase):
|
||||
def test_trackno(self):
|
||||
col = column.TracknoColumn()
|
||||
self.assertIsInstance(col, column.Column)
|
||||
self.assertIsInstance(col.get_factory(), column.TracknoFactory)
|
||||
self.assertEqual(col.get_title(), "#")
|
||||
|
||||
def test_title(self):
|
||||
col = column.TitleColumn()
|
||||
self.assertIsInstance(col, column.Column)
|
||||
self.assertIsInstance(col.get_factory(), column.TitleFactory)
|
||||
self.assertEqual(col.get_title(), "Title")
|
||||
self.assertEqual(col.get_fixed_width(), 250)
|
||||
self.assertTrue(col.get_expand())
|
||||
|
||||
def test_length(self):
|
||||
col = column.LengthColumn()
|
||||
self.assertIsInstance(col, column.Column)
|
||||
self.assertIsInstance(col.get_factory(), column.LengthFactory)
|
||||
self.assertEqual(col.get_title(), "Length")
|
||||
|
||||
def test_artist(self):
|
||||
col = column.ArtistColumn()
|
||||
self.assertIsInstance(col, column.Column)
|
||||
self.assertIsInstance(col.get_factory(), column.ArtistFactory)
|
||||
self.assertEqual(col.get_title(), "Artist")
|
||||
self.assertEqual(col.get_fixed_width(), 150)
|
||||
self.assertTrue(col.get_expand())
|
||||
|
||||
def test_album(self):
|
||||
col = column.AlbumColumn()
|
||||
self.assertIsInstance(col, column.Column)
|
||||
self.assertIsInstance(col.get_factory(), column.AlbumFactory)
|
||||
self.assertEqual(col.get_title(), "Album")
|
||||
self.assertEqual(col.get_fixed_width(), 150)
|
||||
self.assertTrue(col.get_expand())
|
||||
|
||||
def test_subtitle(self):
|
||||
col = column.SubtitleColumn()
|
||||
self.assertIsInstance(col, column.Column)
|
||||
self.assertIsInstance(col.get_factory(), column.SubtitleFactory)
|
||||
self.assertEqual(col.get_title(), "Subtitle")
|
||||
self.assertEqual(col.get_fixed_width(), 150)
|
||||
self.assertTrue(col.get_expand())
|
||||
|
||||
def test_year(self):
|
||||
col = column.YearColumn()
|
||||
self.assertIsInstance(col, column.Column)
|
||||
self.assertIsInstance(col.get_factory(), column.YearFactory)
|
||||
self.assertEqual(col.get_title(), "Year")
|
||||
|
||||
def test_playcount(self):
|
||||
col = column.PlayCountColumn()
|
||||
self.assertIsInstance(col, column.Column)
|
||||
self.assertIsInstance(col.get_factory(), column.PlayCountFactory)
|
||||
self.assertEqual(col.get_title(), "Count")
|
||||
|
||||
def test_lastplayed(self):
|
||||
col = column.LastPlayedColumn()
|
||||
self.assertIsInstance(col, column.Column)
|
||||
self.assertIsInstance(col.get_factory(), column.LastPlayedFactory)
|
||||
self.assertEqual(col.get_title(), "Last Played")
|
|
@ -1,69 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import db
|
||||
import unittest
|
||||
from gi.repository import Gtk
|
||||
from . import footer
|
||||
from . import model
|
||||
|
||||
class TestVisibleTracks(unittest.TestCase):
|
||||
def test_init(self):
|
||||
visible = footer.VisibleTracks()
|
||||
self.assertIsInstance(visible, Gtk.Label)
|
||||
self.assertEqual(visible.get_halign(), Gtk.Align.START)
|
||||
self.assertTrue(visible.get_hexpand())
|
||||
|
||||
def test_count(self):
|
||||
visible = footer.VisibleTracks()
|
||||
visible.set_count(0)
|
||||
self.assertEqual(visible.get_text(), "Showing 0 tracks")
|
||||
visible.set_count(1)
|
||||
self.assertEqual(visible.get_text(), "Showing 1 track")
|
||||
visible.set_count(2)
|
||||
self.assertEqual(visible.get_text(), "Showing 2 tracks")
|
||||
|
||||
|
||||
class TestRuntime(unittest.TestCase):
|
||||
def test_init(self):
|
||||
runtime = footer.Runtime()
|
||||
self.assertIsInstance(runtime, Gtk.Label)
|
||||
self.assertEqual(runtime.get_halign(), Gtk.Align.END)
|
||||
self.assertTrue(runtime.get_hexpand())
|
||||
|
||||
def test_runtime(self):
|
||||
runtime = footer.Runtime()
|
||||
runtime.set_runtime(0)
|
||||
self.assertEqual(runtime.get_text(), "0 seconds")
|
||||
runtime.set_runtime(61)
|
||||
self.assertEqual(runtime.get_text(), "1 minute, 1 second")
|
||||
runtime.set_runtime(3720)
|
||||
self.assertEqual(runtime.get_text(), "1 hour, 2 minutes")
|
||||
runtime.set_runtime(93600)
|
||||
self.assertEqual(runtime.get_text(), "1 day, 2 hours")
|
||||
runtime.set_runtime(777600)
|
||||
self.assertEqual(runtime.get_text(), "1 week, 2 days")
|
||||
runtime.set_runtime(1209600)
|
||||
self.assertEqual(runtime.get_text(), "2 weeks")
|
||||
|
||||
|
||||
class TestFooter(unittest.TestCase):
|
||||
def test_init(self):
|
||||
foot = footer.Footer(model.FilterPlaylistModel())
|
||||
self.assertIsInstance(foot, Gtk.Box)
|
||||
self.assertIsInstance(foot.visible, footer.VisibleTracks)
|
||||
self.assertIsInstance(foot.runtime, footer.Runtime)
|
||||
|
||||
self.assertEqual(foot.get_orientation(), Gtk.Orientation.HORIZONTAL)
|
||||
self.assertEqual(foot.get_margin_start(), 5)
|
||||
self.assertEqual(foot.get_margin_end(), 5)
|
||||
self.assertEqual(foot.get_first_child(), foot.visible)
|
||||
self.assertEqual(foot.visible.get_next_sibling(), foot.runtime)
|
||||
|
||||
def test_updates(self):
|
||||
db.reset()
|
||||
filter = model.FilterPlaylistModel()
|
||||
foot = footer.Footer(filter)
|
||||
filter.set_playlist(db.user.Table.find("Collection"))
|
||||
|
||||
db.make_fake_track(1, 1, "Test Track 1", "/a/b/c/1.ogg")
|
||||
self.assertEqual(foot.visible.get_text(), "Showing 1 track")
|
||||
self.assertEqual(foot.runtime.get_text(), "1 second")
|
|
@ -1,247 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import audio
|
||||
import db
|
||||
import lib
|
||||
import unittest
|
||||
from gi.repository import Gtk
|
||||
from . import header
|
||||
from . import model
|
||||
from . import view
|
||||
|
||||
class TestFilterEntry(unittest.TestCase):
|
||||
def test_init(self):
|
||||
entry = header.FilterEntry()
|
||||
self.assertIsInstance(entry, lib.filter.Entry)
|
||||
self.assertEqual(entry.filter, model.PlaylistFilter)
|
||||
self.assertEqual(entry.get_margin_start(), 200)
|
||||
self.assertEqual(entry.get_margin_end(), 200)
|
||||
self.assertTrue(entry.get_hexpand())
|
||||
|
||||
|
||||
class TestPropertyToggle(unittest.TestCase):
|
||||
def test_init(self):
|
||||
prop = header.PropertyToggle("random", "media-playlist-shuffle")
|
||||
self.assertIsInstance(prop, Gtk.ToggleButton)
|
||||
self.assertEqual(prop.get_icon_name(), "media-playlist-shuffle")
|
||||
self.assertFalse(prop.get_sensitive())
|
||||
|
||||
self.assertEqual(prop.property, "random")
|
||||
self.assertIsNone(prop.playlist)
|
||||
prop.set_active(True)
|
||||
|
||||
def test_set_playlist(self):
|
||||
collection = db.user.Table.find("Collection")
|
||||
prop = header.PropertyToggle("random", "media-playlist-shuffle")
|
||||
|
||||
collection.set_property("random", True)
|
||||
prop.set_playlist(collection)
|
||||
self.assertEqual(prop.playlist, collection)
|
||||
self.assertTrue(prop.get_active())
|
||||
self.assertFalse(prop.get_sensitive())
|
||||
|
||||
collection.set_property("random", False)
|
||||
prop.set_playlist(collection)
|
||||
self.assertFalse(prop.get_active())
|
||||
|
||||
def test_toggle(self):
|
||||
collection = db.user.Table.find("Collection")
|
||||
prop = header.PropertyToggle("random", "media-playlist-shuffle")
|
||||
|
||||
collection.set_property("random", False)
|
||||
prop.set_playlist(collection)
|
||||
prop.set_active(True)
|
||||
self.assertTrue(collection.get_property("random"))
|
||||
prop.set_active(False)
|
||||
self.assertFalse(collection.get_property("random"))
|
||||
|
||||
|
||||
class TestRandomToggle(unittest.TestCase):
|
||||
def test_init(self):
|
||||
random = header.RandomToggle()
|
||||
self.assertIsInstance(random, header.PropertyToggle)
|
||||
self.assertEqual(random.get_icon_name(), "media-playlist-shuffle")
|
||||
self.assertEqual(random.property, "random")
|
||||
|
||||
def test_set_playlist(self):
|
||||
random = header.RandomToggle()
|
||||
random.set_playlist(db.user.Table.find("Collection"))
|
||||
self.assertTrue(random.get_sensitive())
|
||||
random.set_playlist(db.user.Table.find("Previous"))
|
||||
self.assertFalse(random.get_sensitive())
|
||||
|
||||
|
||||
class TestLoopToggle(unittest.TestCase):
|
||||
def test_init(self):
|
||||
loop = header.LoopToggle()
|
||||
self.assertIsInstance(loop, header.PropertyToggle)
|
||||
self.assertEqual(loop.get_icon_name(), "media-playlist-repeat")
|
||||
self.assertEqual(loop.property, "loop")
|
||||
|
||||
def test_set_playlist(self):
|
||||
loop = header.LoopToggle()
|
||||
loop.set_playlist(db.user.Table.find("Collection"))
|
||||
self.assertFalse(loop.get_sensitive())
|
||||
loop.set_playlist(db.user.Table.find("Favorites"))
|
||||
self.assertTrue(loop.get_sensitive())
|
||||
loop.set_playlist(db.user.Table.find("New Tracks"))
|
||||
self.assertTrue(loop.get_sensitive())
|
||||
loop.set_playlist(db.user.Table.find("Previous"))
|
||||
self.assertFalse(loop.get_sensitive())
|
||||
loop.set_playlist(db.user.Table.find("Queued Tracks"))
|
||||
self.assertFalse(loop.get_sensitive())
|
||||
|
||||
|
||||
class TestSortButton(unittest.TestCase):
|
||||
def test_init(self):
|
||||
sort = header.SortButton()
|
||||
self.assertIsInstance(sort, Gtk.MenuButton)
|
||||
self.assertIsInstance(sort.get_popover(), Gtk.Popover)
|
||||
self.assertIsInstance(sort.get_popover(), view.SortOrderPopover)
|
||||
self.assertEqual(sort.get_icon_name(), "view-sort-ascending")
|
||||
self.assertFalse(sort.get_sensitive())
|
||||
|
||||
def test_set_playlist(self):
|
||||
sort = header.SortButton()
|
||||
sort.set_playlist(db.user.Table.find("Collection"))
|
||||
self.assertTrue(sort.get_sensitive())
|
||||
sort.set_playlist(db.user.Table.find("Favorites"))
|
||||
self.assertTrue(sort.get_sensitive())
|
||||
sort.set_playlist(db.user.Table.find("New Tracks"))
|
||||
self.assertTrue(sort.get_sensitive())
|
||||
sort.set_playlist(db.user.Table.find("Previous"))
|
||||
self.assertFalse(sort.get_sensitive())
|
||||
sort.set_playlist(db.user.Table.find("Queued Tracks"))
|
||||
self.assertTrue(sort.get_sensitive())
|
||||
|
||||
|
||||
class TestFavoriteButton(unittest.TestCase):
|
||||
def setUp(self):
|
||||
db.reset()
|
||||
audio.Player.track = None
|
||||
|
||||
def test_init(self):
|
||||
fav = header.FavoriteButton()
|
||||
self.assertIsInstance(fav, Gtk.ToggleButton)
|
||||
self.assertEqual(fav.get_icon_name(), "emmental-favorites")
|
||||
self.assertFalse(fav.get_sensitive())
|
||||
|
||||
def test_sensitive(self):
|
||||
track = db.make_fake_track(1, 1, "Test Track", "/a/b/c/1.ogg")
|
||||
fav = header.FavoriteButton()
|
||||
fav.track_changed(audio.Player, None, track)
|
||||
self.assertTrue(fav.get_sensitive())
|
||||
fav.track_changed(audio.Player, None, None)
|
||||
self.assertFalse(fav.get_sensitive())
|
||||
|
||||
def test_active(self):
|
||||
fav = header.FavoriteButton()
|
||||
t1 = db.make_fake_track(1, 1, "Test Track 1", "/a/b/c/1.ogg")
|
||||
t2 = db.make_fake_track(2, 2, "Test Track 2", "/a/b/c/2.ogg")
|
||||
db.user.Table.find("Favorites").add_track(t1)
|
||||
|
||||
fav.track_changed(audio.Player, None, t1)
|
||||
self.assertTrue(fav.get_active())
|
||||
fav.track_changed(audio.Player, t1, t2)
|
||||
self.assertFalse(fav.get_active())
|
||||
|
||||
def test_toggle(self):
|
||||
track = db.make_fake_track(1, 1, "Test Track", "/a/b/c/1.ogg")
|
||||
fav = header.FavoriteButton()
|
||||
|
||||
fav.set_active(True)
|
||||
self.assertEqual(db.user.Table.find("Favorites").get_n_tracks(), 0)
|
||||
fav.set_active(False)
|
||||
|
||||
audio.Player.track = track
|
||||
self.assertNotIn(track, db.user.Table.find("Favorites").get_tracks())
|
||||
fav.set_active(True)
|
||||
self.assertIn(track, db.user.Table.find("Favorites").get_tracks())
|
||||
fav.set_active(False)
|
||||
self.assertNotIn(track, db.user.Table.find("Favorites").get_tracks())
|
||||
|
||||
|
||||
class TestJumpButton(unittest.TestCase):
|
||||
def test_init(self):
|
||||
jump = header.JumpButton()
|
||||
self.assertIsInstance(jump, Gtk.Button)
|
||||
self.assertEqual(jump.get_icon_name(), "go-jump")
|
||||
self.assertFalse(jump.get_sensitive())
|
||||
|
||||
def test_set_playlist(self):
|
||||
jump = header.JumpButton()
|
||||
self.assertFalse(jump.get_sensitive())
|
||||
jump.set_playlist(db.user.Table.find("Collection"))
|
||||
self.assertTrue(jump.get_sensitive())
|
||||
|
||||
|
||||
class TestControlBox(unittest.TestCase):
|
||||
def test_init(self):
|
||||
box = header.ControlBox()
|
||||
self.assertIsInstance(box, Gtk.Box)
|
||||
self.assertEqual(box.get_orientation(), Gtk.Orientation.HORIZONTAL)
|
||||
self.assertEqual(box.get_margin_top(), 5)
|
||||
self.assertEqual(box.get_margin_bottom(), 5)
|
||||
self.assertEqual(box.get_margin_start(), 5)
|
||||
self.assertEqual(box.get_margin_end(), 5)
|
||||
self.assertTrue(box.has_css_class("linked"))
|
||||
|
||||
|
||||
class TestTrackBox(unittest.TestCase):
|
||||
def test_init(self):
|
||||
box = header.TrackBox()
|
||||
self.assertIsInstance(box, header.ControlBox)
|
||||
|
||||
def test_children(self):
|
||||
box = header.TrackBox()
|
||||
|
||||
child = box.get_first_child()
|
||||
self.assertIsInstance(child, header.FavoriteButton)
|
||||
|
||||
child = child.get_next_sibling()
|
||||
self.assertIsInstance(child, header.JumpButton)
|
||||
self.assertEqual(box.get_jump_button(), child)
|
||||
|
||||
|
||||
class TestPlaylistBox(unittest.TestCase):
|
||||
def test_init(self):
|
||||
box = header.PlaylistBox()
|
||||
self.assertIsInstance(box, header.ControlBox)
|
||||
|
||||
def test_children(self):
|
||||
collection = db.user.Table.find("Collection")
|
||||
box = header.PlaylistBox()
|
||||
box.set_playlist(collection)
|
||||
|
||||
child = box.get_first_child()
|
||||
self.assertIsInstance(child, header.RandomToggle)
|
||||
self.assertEqual(child.playlist, collection)
|
||||
|
||||
child = child.get_next_sibling()
|
||||
self.assertIsInstance(child, header.LoopToggle)
|
||||
self.assertEqual(child.playlist, collection)
|
||||
|
||||
child = child.get_next_sibling()
|
||||
self.assertIsInstance(child, header.SortButton)
|
||||
|
||||
|
||||
class TestHeader(unittest.TestCase):
|
||||
def test_init(self):
|
||||
box = header.Header()
|
||||
self.assertIsInstance(box, Gtk.Box)
|
||||
self.assertIsInstance(box.get_jump_button(), header.JumpButton)
|
||||
self.assertEqual(box.get_orientation(), Gtk.Orientation.HORIZONTAL)
|
||||
|
||||
def test_children(self):
|
||||
collection = db.user.Table.find("Collection")
|
||||
box = header.Header()
|
||||
box.set_playlist(collection)
|
||||
|
||||
child = box.get_first_child()
|
||||
self.assertIsInstance(child, header.TrackBox)
|
||||
|
||||
child = child.get_next_sibling()
|
||||
self.assertIsInstance(child, header.FilterEntry)
|
||||
|
||||
child = child.get_next_sibling()
|
||||
self.assertIsInstance(child, header.ControlBox)
|
||||
self.assertEqual(child.get_first_child().playlist, collection)
|
|
@ -1,353 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import db
|
||||
import lib
|
||||
import unittest
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gio
|
||||
from gi.repository import Gtk
|
||||
from . import model
|
||||
|
||||
|
||||
class TestPlaylistModel(unittest.TestCase):
|
||||
def items_changed(self, mod, pos, rm, add): self.changed = (pos, rm, add)
|
||||
def setUp(self): db.reset()
|
||||
|
||||
def test_init(self):
|
||||
plist = model.PlaylistModel()
|
||||
self.assertIsInstance(plist, Gio.ListModel)
|
||||
self.assertIsNone(plist.playlist)
|
||||
self.assertEqual(plist.tracks, [ ])
|
||||
|
||||
self.assertEqual(plist.get_item_type(), GObject.TYPE_PYOBJECT)
|
||||
self.assertEqual(plist.get_n_items(), 0)
|
||||
self.assertIsNone(plist.get_item(0))
|
||||
|
||||
def test_set_playlist(self):
|
||||
test1 = db.user.Table.find("Test Playlist 1")
|
||||
test2 = db.user.Table.find("Test Playlist 2")
|
||||
track = db.make_fake_track(1, 1, "Test Track 1", "/a/b/c/1.ogg")
|
||||
plist = model.PlaylistModel()
|
||||
|
||||
test1.add_track(track)
|
||||
plist.set_playlist(test1)
|
||||
self.assertEqual(plist.playlist, test1)
|
||||
self.assertEqual(plist.get_playlist(), test1)
|
||||
self.assertEqual(plist.tracks, [ track ])
|
||||
|
||||
plist.set_playlist(test2)
|
||||
self.assertEqual(plist.tracks, [ ])
|
||||
test1.add_track(db.make_fake_track(2, 2, "Test Track 2", "/a/b/c/2.ogg"))
|
||||
self.assertEqual(plist.tracks, [ ])
|
||||
|
||||
def test_add_remove(self):
|
||||
test = db.user.Table.find("Test Playlist")
|
||||
track = db.make_fake_track(1, 1, "Test Track", "/a/b/c/1.ogg")
|
||||
plist = model.PlaylistModel()
|
||||
plist.set_playlist(test)
|
||||
plist.connect("items-changed", self.items_changed)
|
||||
|
||||
test.add_track(track)
|
||||
self.assertEqual(plist.get_n_items(), 1)
|
||||
self.assertEqual(plist.get_item(0), track)
|
||||
self.assertEqual(self.changed, (0, 0, 1))
|
||||
|
||||
test.remove_track(track)
|
||||
self.assertEqual(plist.get_n_items(), 0)
|
||||
self.assertEqual(self.changed, (0, 1, 0))
|
||||
|
||||
def test_refresh(self):
|
||||
test = db.user.Table.find("Test Playlist")
|
||||
track = db.make_fake_track(1, 1, "Test Track", "/a/b/c/1.ogg")
|
||||
plist = model.PlaylistModel()
|
||||
|
||||
test.add_track(track)
|
||||
plist.set_playlist(test)
|
||||
plist.connect("items-changed", self.items_changed)
|
||||
test.refresh()
|
||||
self.assertEqual(self.changed, (0, 1, 1))
|
||||
|
||||
|
||||
class TestFilterPlaylistModel(unittest.TestCase):
|
||||
def setUp(self):
|
||||
db.reset()
|
||||
model.PlaylistFilter.set_pattern("")
|
||||
|
||||
def test_init(self):
|
||||
filter = model.FilterPlaylistModel()
|
||||
self.assertIsInstance(filter, Gtk.FilterListModel)
|
||||
self.assertIsInstance(filter.get_model(), model.PlaylistModel)
|
||||
self.assertEqual(filter.get_filter(), model.PlaylistFilter)
|
||||
self.assertIsNone(filter.get_playlist())
|
||||
|
||||
filter.set_playlist(db.user.Table.find("Test"))
|
||||
self.assertEqual(filter.get_playlist(), db.user.Table.find("Test"))
|
||||
|
||||
def test_filter(self):
|
||||
track = db.make_fake_track(1, 1, "Test Track", "/a/b/c/1.ogg",
|
||||
subtitle="Test Subtitle")
|
||||
self.assertIsInstance(model.PlaylistFilter, lib.filter.Regex)
|
||||
|
||||
model.PlaylistFilter.set_pattern("disc=1")
|
||||
self.assertTrue(model.PlaylistFilter.do_match(track))
|
||||
model.PlaylistFilter.set_pattern("disc=1 & track=1")
|
||||
self.assertTrue(model.PlaylistFilter.do_match(track))
|
||||
model.PlaylistFilter.set_pattern("track=1 & title=test")
|
||||
self.assertTrue(model.PlaylistFilter.do_match(track))
|
||||
model.PlaylistFilter.set_pattern("title=test track & artist=test artist")
|
||||
self.assertTrue(model.PlaylistFilter.do_match(track))
|
||||
model.PlaylistFilter.set_pattern("artist=test artist & album=test album")
|
||||
self.assertTrue(model.PlaylistFilter.do_match(track))
|
||||
model.PlaylistFilter.set_pattern("album=test album & subtitle=test subtitle")
|
||||
self.assertTrue(model.PlaylistFilter.do_match(track))
|
||||
model.PlaylistFilter.set_pattern("subtitle=test subtitle & year=2021")
|
||||
self.assertTrue(model.PlaylistFilter.do_match(track))
|
||||
model.PlaylistFilter.set_pattern("year=2021 & year=2022")
|
||||
self.assertFalse(model.PlaylistFilter.do_match(track))
|
||||
|
||||
def test_runtime(self):
|
||||
filter = model.FilterPlaylistModel()
|
||||
filter.set_playlist(db.user.Table.find("Collection"))
|
||||
|
||||
self.assertEqual(filter.get_runtime(), 0)
|
||||
track = db.make_fake_track(1, 1, "Test Track", "/a/b/c/1.ogg")
|
||||
self.assertEqual(filter.get_runtime(), 1)
|
||||
|
||||
|
||||
class TestPlaylistSelectionModel(unittest.TestCase):
|
||||
def test_init(self):
|
||||
selection = model.PlaylistSelection()
|
||||
self.assertIsInstance(selection, Gtk.MultiSelection)
|
||||
self.assertIsInstance(selection.get_model(), model.FilterPlaylistModel)
|
||||
self.assertEqual(selection.get_filter_model(), selection.get_model())
|
||||
self.assertIsNone(selection.get_playlist())
|
||||
|
||||
|
||||
class TestSortPlaylistModel(unittest.TestCase):
|
||||
def setUp(self):
|
||||
db.reset()
|
||||
|
||||
def test_init(self):
|
||||
sort = model.SortPlaylistModel()
|
||||
self.assertIsInstance(sort, Gtk.StringList)
|
||||
self.assertIsNone(sort.playlist)
|
||||
|
||||
def test_set_playlist(self):
|
||||
collection = db.user.Table.find("Collection")
|
||||
sort = model.SortPlaylistModel()
|
||||
|
||||
sort.set_playlist(collection)
|
||||
self.assertEqual(sort.playlist, collection)
|
||||
self.assertEqual(sort.get_string(0), "artists.sort ASC")
|
||||
self.assertEqual(sort.get_string(1), "albums.release ASC")
|
||||
self.assertEqual(sort.get_string(2), "albums.sort ASC")
|
||||
self.assertEqual(sort.get_string(3), "tracks.number ASC")
|
||||
|
||||
def test_append(self):
|
||||
collection = db.user.Table.find("Collection")
|
||||
sort = model.SortPlaylistModel()
|
||||
|
||||
sort.set_playlist(collection)
|
||||
sort.append("tracks.title")
|
||||
self.assertEqual(sort.get_n_items(), 5)
|
||||
self.assertEqual(sort.get_string(0), "artists.sort ASC")
|
||||
self.assertEqual(sort.get_string(1), "albums.release ASC")
|
||||
self.assertEqual(sort.get_string(2), "albums.sort ASC")
|
||||
self.assertEqual(sort.get_string(3), "tracks.number ASC")
|
||||
self.assertEqual(sort.get_string(4), "tracks.title ASC")
|
||||
|
||||
self.assertEqual(collection.sort, [ "artists.sort ASC",
|
||||
"albums.release ASC",
|
||||
"albums.sort ASC",
|
||||
"discs.number ASC",
|
||||
"tracks.number ASC",
|
||||
"tracks.title ASC",
|
||||
"tracks.trackid ASC" ])
|
||||
|
||||
def test_remove(self):
|
||||
collection = db.user.Table.find("Collection")
|
||||
sort = model.SortPlaylistModel()
|
||||
|
||||
sort.set_playlist(collection)
|
||||
sort.remove("albums.release")
|
||||
self.assertEqual(sort.get_n_items(), 3)
|
||||
self.assertEqual(sort.get_string(0), "artists.sort ASC")
|
||||
self.assertEqual(sort.get_string(1), "albums.sort ASC")
|
||||
self.assertEqual(sort.get_string(2), "tracks.number ASC")
|
||||
|
||||
self.assertEqual(collection.sort, [ "artists.sort ASC",
|
||||
"albums.sort ASC",
|
||||
"discs.number ASC",
|
||||
"tracks.number ASC",
|
||||
"tracks.trackid ASC" ])
|
||||
|
||||
def test_move_up(self):
|
||||
collection = db.user.Table.find("Collection")
|
||||
sort = model.SortPlaylistModel()
|
||||
sort.set_playlist(collection)
|
||||
|
||||
sort.move_up("albums.release")
|
||||
self.assertEqual(sort.get_n_items(), 4)
|
||||
self.assertEqual(sort.get_string(0), "albums.release ASC")
|
||||
self.assertEqual(sort.get_string(1), "artists.sort ASC")
|
||||
self.assertEqual(sort.get_string(2), "albums.sort ASC")
|
||||
self.assertEqual(sort.get_string(3), "tracks.number ASC")
|
||||
|
||||
self.assertEqual(collection.sort, [ "albums.release ASC",
|
||||
"artists.sort ASC",
|
||||
"albums.sort ASC",
|
||||
"discs.number ASC",
|
||||
"tracks.number ASC",
|
||||
"tracks.trackid ASC" ])
|
||||
|
||||
sort.move_up("artists.sort")
|
||||
self.assertEqual(sort.get_n_items(), 4)
|
||||
self.assertEqual(sort.get_string(0), "artists.sort ASC")
|
||||
self.assertEqual(sort.get_string(1), "albums.release ASC")
|
||||
self.assertEqual(sort.get_string(2), "albums.sort ASC")
|
||||
self.assertEqual(sort.get_string(3), "tracks.number ASC")
|
||||
|
||||
def test_move_down(self):
|
||||
collection = db.user.Table.find("Collection")
|
||||
sort = model.SortPlaylistModel()
|
||||
sort.set_playlist(collection)
|
||||
|
||||
sort.move_down("albums.sort")
|
||||
self.assertEqual(sort.get_n_items(), 4)
|
||||
self.assertEqual(sort.get_string(0), "artists.sort ASC")
|
||||
self.assertEqual(sort.get_string(1), "albums.release ASC")
|
||||
self.assertEqual(sort.get_string(2), "tracks.number ASC")
|
||||
self.assertEqual(sort.get_string(3), "albums.sort ASC")
|
||||
|
||||
self.assertEqual(collection.sort, [ "artists.sort ASC",
|
||||
"albums.release ASC",
|
||||
"discs.number ASC",
|
||||
"tracks.number ASC",
|
||||
"albums.sort ASC",
|
||||
"tracks.trackid ASC" ])
|
||||
|
||||
sort.move_down("tracks.number")
|
||||
self.assertEqual(sort.get_n_items(), 4)
|
||||
self.assertEqual(sort.get_string(0), "artists.sort ASC")
|
||||
self.assertEqual(sort.get_string(1), "albums.release ASC")
|
||||
self.assertEqual(sort.get_string(2), "albums.sort ASC")
|
||||
self.assertEqual(sort.get_string(3), "tracks.number ASC")
|
||||
|
||||
def test_reverse(self):
|
||||
collection = db.user.Table.find("Collection")
|
||||
sort = model.SortPlaylistModel()
|
||||
sort.set_playlist(collection)
|
||||
|
||||
sort.reverse("tracks.number")
|
||||
self.assertEqual(sort.get_n_items(), 4)
|
||||
self.assertEqual(sort.get_string(0), "artists.sort ASC")
|
||||
self.assertEqual(sort.get_string(1), "albums.release ASC")
|
||||
self.assertEqual(sort.get_string(2), "albums.sort ASC")
|
||||
self.assertEqual(sort.get_string(3), "tracks.number DESC")
|
||||
|
||||
self.assertEqual(collection.sort, [ "artists.sort ASC",
|
||||
"albums.release ASC",
|
||||
"albums.sort ASC",
|
||||
"discs.number DESC",
|
||||
"tracks.number DESC",
|
||||
"tracks.trackid ASC" ])
|
||||
|
||||
sort.reverse("tracks.number")
|
||||
self.assertEqual(sort.get_n_items(), 4)
|
||||
self.assertEqual(sort.get_string(0), "artists.sort ASC")
|
||||
self.assertEqual(sort.get_string(1), "albums.release ASC")
|
||||
self.assertEqual(sort.get_string(2), "albums.sort ASC")
|
||||
self.assertEqual(sort.get_string(3), "tracks.number ASC")
|
||||
|
||||
|
||||
|
||||
class TestSortOptionsModel(unittest.TestCase):
|
||||
def test_init(self):
|
||||
sort = model.SortOptionsModel()
|
||||
self.assertIsInstance(sort, Gtk.StringList)
|
||||
self.assertEqual(sort.get_item(0).get_string(), "tracks.number ASC")
|
||||
self.assertEqual(sort.get_item(1).get_string(), "tracks.title ASC")
|
||||
self.assertEqual(sort.get_item(2).get_string(), "tracks.length ASC")
|
||||
self.assertEqual(sort.get_item(3).get_string(), "artists.sort ASC")
|
||||
self.assertEqual(sort.get_item(4).get_string(), "albums.sort ASC")
|
||||
self.assertEqual(sort.get_item(5).get_string(), "discs.subtitle ASC")
|
||||
self.assertEqual(sort.get_item(6).get_string(), "albums.release ASC")
|
||||
self.assertEqual(sort.get_item(7).get_string(), "tracks.playcount ASC")
|
||||
self.assertEqual(sort.get_item(8).get_string(), "tracks.lastplayed ASC")
|
||||
|
||||
|
||||
class TestDisabledOptionsModel(unittest.TestCase):
|
||||
def setUp(self):
|
||||
db.reset()
|
||||
|
||||
def test_init(self):
|
||||
collection = db.user.Table.find("Collection")
|
||||
filter = model.DisabledOptionsModel()
|
||||
self.assertIsInstance(filter, Gtk.FilterListModel)
|
||||
self.assertIsInstance(filter.get_model(), model.SortOptionsModel)
|
||||
self.assertIsInstance(filter.get_filter(), model.DisabledOptionsFilter)
|
||||
self.assertIsNone(filter.get_filter().playlist)
|
||||
|
||||
filter.set_playlist(collection)
|
||||
self.assertEqual(filter.get_filter().playlist, collection)
|
||||
|
||||
def test_filter(self):
|
||||
collection = db.user.Table.find("Collection")
|
||||
filter = model.DisabledOptionsFilter()
|
||||
filter.set_playlist(collection)
|
||||
self.assertFalse(filter.do_match(Gtk.StringObject.new("tracks.number ASC")))
|
||||
self.assertTrue( filter.do_match(Gtk.StringObject.new("tracks.title DESC")))
|
||||
self.assertTrue( filter.do_match(Gtk.StringObject.new("tracks.length ASC")))
|
||||
self.assertFalse(filter.do_match(Gtk.StringObject.new("artists.sort DESC")))
|
||||
self.assertFalse(filter.do_match(Gtk.StringObject.new("albums.sort ASC")))
|
||||
self.assertTrue( filter.do_match(Gtk.StringObject.new("discs.subtitle DESC")))
|
||||
self.assertFalse(filter.do_match(Gtk.StringObject.new("albums.release ASC")))
|
||||
self.assertTrue( filter.do_match(Gtk.StringObject.new("tracks.playcount DESC")))
|
||||
self.assertTrue( filter.do_match(Gtk.StringObject.new("tracks.lastplayed ASC")))
|
||||
|
||||
|
||||
class TestSortModelsModel(unittest.TestCase):
|
||||
def test_init(self):
|
||||
sort = model.SortModelsModel()
|
||||
self.assertIsInstance(sort, Gio.ListModel)
|
||||
self.assertEqual(sort.get_n_items(), 2)
|
||||
self.assertIsInstance(sort.get_item(0), model.SortPlaylistModel)
|
||||
self.assertIsInstance(sort.get_item(1), model.DisabledOptionsModel)
|
||||
|
||||
def test_set_playlist(self):
|
||||
collection = db.user.Table.find("Collection")
|
||||
sort = model.SortModelsModel()
|
||||
sort.set_playlist(collection)
|
||||
self.assertEqual(sort.get_item(0).playlist, collection)
|
||||
self.assertEqual(sort.get_item(1).get_filter().playlist, collection)
|
||||
|
||||
|
||||
class TestFlatSortModel(unittest.TestCase):
|
||||
def test_init(self):
|
||||
sort = model.FlatSortModel()
|
||||
self.assertIsInstance(sort, Gtk.FlattenListModel)
|
||||
self.assertIsInstance(sort.get_model(), model.SortModelsModel)
|
||||
self.assertEqual(sort.get_enabled_model(), sort.get_model()[0])
|
||||
|
||||
def test_set_playlist(self):
|
||||
collection = db.user.Table.find("Collection")
|
||||
sort = model.FlatSortModel()
|
||||
models = sort.get_model()
|
||||
sort.set_playlist(collection)
|
||||
self.assertEqual(models.get_item(0).playlist, collection)
|
||||
self.assertEqual(models.get_item(1).get_filter().playlist, collection)
|
||||
|
||||
def test_enable_disable(self):
|
||||
collection = db.user.Table.find("Collection")
|
||||
sort = model.FlatSortModel()
|
||||
sort.set_playlist(collection)
|
||||
models = sort.get_model()
|
||||
|
||||
sort.enable("tracks.title")
|
||||
self.assertEqual(models.models[0].get_n_items(), 5)
|
||||
self.assertEqual(models.models[1].get_n_items(), 4)
|
||||
self.assertEqual(sort.get_n_enabled(), 5)
|
||||
|
||||
sort.disable("tracks.title")
|
||||
self.assertEqual(models.models[0].get_n_items(), 4)
|
||||
self.assertEqual(models.models[1].get_n_items(), 5)
|
||||
self.assertEqual(sort.get_n_enabled(), 4)
|
|
@ -1,40 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import db
|
||||
import playlist
|
||||
import unittest
|
||||
from gi.repository import Gtk
|
||||
|
||||
class TestPanel(unittest.TestCase):
|
||||
def test_init(self):
|
||||
panel = playlist.Panel()
|
||||
self.assertIsInstance(panel, Gtk.Box)
|
||||
self.assertIsInstance(panel.header, playlist.header.Header)
|
||||
self.assertIsInstance(panel.window, playlist.view.PlaylistWindow)
|
||||
self.assertIsInstance(panel.footer, playlist.footer.Footer)
|
||||
self.assertEqual(panel.get_orientation(), Gtk.Orientation.VERTICAL)
|
||||
|
||||
def test_items(self):
|
||||
panel = playlist.Panel()
|
||||
child = panel.get_first_child()
|
||||
self.assertEqual(child, panel.header)
|
||||
|
||||
child = child.get_next_sibling()
|
||||
self.assertIsInstance(child, Gtk.Separator)
|
||||
|
||||
child = child.get_next_sibling()
|
||||
self.assertEqual(child, panel.window)
|
||||
|
||||
child = child.get_next_sibling()
|
||||
self.assertIsInstance(child, Gtk.Separator)
|
||||
|
||||
child = child.get_next_sibling()
|
||||
self.assertEqual(child, panel.footer)
|
||||
|
||||
def test_set_playlist(self):
|
||||
collection = db.user.Table.find("Collection")
|
||||
panel = playlist.Panel()
|
||||
|
||||
self.assertIsNone(panel.get_playlist())
|
||||
panel.set_playlist(collection)
|
||||
self.assertEqual(panel.header.get_last_child().get_first_child().playlist, collection)
|
||||
self.assertEqual(panel.get_playlist(), collection)
|
|
@ -1,203 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import unittest
|
||||
from gi.repository import Gtk
|
||||
from . import model
|
||||
from . import sort
|
||||
|
||||
class TestUpDownBox(unittest.TestCase):
|
||||
def move_up(self, button): self.up = True
|
||||
def move_down(self, button): self.down = True
|
||||
|
||||
def test_init(self):
|
||||
updown = sort.UpDownBox()
|
||||
self.assertIsInstance(updown, Gtk.Box)
|
||||
self.assertTrue(updown.has_css_class("linked"))
|
||||
|
||||
child = updown.get_first_child()
|
||||
self.assertIsInstance(child, Gtk.Button)
|
||||
self.assertEqual(child.get_icon_name(), "go-up-symbolic")
|
||||
self.assertFalse(child.get_sensitive())
|
||||
|
||||
child = child.get_next_sibling()
|
||||
self.assertIsInstance(child, Gtk.Button)
|
||||
self.assertEqual(child.get_icon_name(), "go-down-symbolic")
|
||||
self.assertFalse(child.get_sensitive())
|
||||
|
||||
def test_sensitive(self):
|
||||
updown = sort.UpDownBox()
|
||||
updown.set_active(True, first=False, last=False)
|
||||
self.assertTrue(updown.get_first_child().get_sensitive())
|
||||
self.assertTrue(updown.get_last_child().get_sensitive())
|
||||
|
||||
updown.set_active(True, first=True, last=False)
|
||||
self.assertFalse(updown.get_first_child().get_sensitive())
|
||||
self.assertTrue(updown.get_last_child().get_sensitive())
|
||||
|
||||
updown.set_active(True, first=False, last=True)
|
||||
self.assertTrue(updown.get_first_child().get_sensitive())
|
||||
self.assertFalse(updown.get_last_child().get_sensitive())
|
||||
|
||||
updown.set_active(False, first=True, last=True)
|
||||
self.assertFalse(updown.get_first_child().get_sensitive())
|
||||
self.assertFalse(updown.get_last_child().get_sensitive())
|
||||
|
||||
def test_clicks(self):
|
||||
updown = sort.UpDownBox()
|
||||
updown.connect("move-up", self.move_up)
|
||||
updown.connect("move-down", self.move_down)
|
||||
|
||||
updown.get_first_child().emit("clicked")
|
||||
self.assertTrue(self.up)
|
||||
updown.get_last_child().emit("clicked")
|
||||
self.assertTrue(self.down)
|
||||
|
||||
|
||||
class TestDirectionButton(unittest.TestCase):
|
||||
def test_init(self):
|
||||
button = sort.DirectionButton()
|
||||
self.assertIsInstance(button, Gtk.Button)
|
||||
self.assertEqual(button.get_icon_name(), "view-sort-ascending")
|
||||
self.assertFalse(button.get_sensitive())
|
||||
|
||||
def test_set_direction(self):
|
||||
button = sort.DirectionButton()
|
||||
button.set_direction("DESC", active=True)
|
||||
self.assertEqual(button.get_icon_name(), "view-sort-descending")
|
||||
self.assertTrue(button.get_sensitive())
|
||||
|
||||
button.set_direction("ASC", active=False)
|
||||
self.assertEqual(button.get_icon_name(), "view-sort-ascending")
|
||||
self.assertFalse(button.get_sensitive())
|
||||
|
||||
|
||||
class TestFieldLabel(unittest.TestCase):
|
||||
def test_init(self):
|
||||
label = sort.FieldLabel()
|
||||
self.assertIsInstance(label, Gtk.Label)
|
||||
self.assertFalse(label.get_sensitive())
|
||||
self.assertTrue(label.get_hexpand())
|
||||
|
||||
def test_set_field(self):
|
||||
label = sort.FieldLabel()
|
||||
label.set_field("tracks.number", active=True)
|
||||
self.assertEqual(label.get_text(), "Track Number")
|
||||
self.assertEqual(label.get_field(), "tracks.number")
|
||||
self.assertTrue(label.get_sensitive())
|
||||
|
||||
label.set_field("tracks.title", active=False),
|
||||
self.assertEqual(label.get_text(), "Track Title")
|
||||
self.assertEqual(label.get_field(), "tracks.title")
|
||||
self.assertFalse(label.get_sensitive())
|
||||
|
||||
label.set_field("tracks.length", active=True),
|
||||
self.assertEqual(label.get_text(), "Track Length")
|
||||
self.assertEqual(label.get_field(), "tracks.length")
|
||||
self.assertTrue(label.get_sensitive())
|
||||
|
||||
label.set_field("artists.sort", active=False),
|
||||
self.assertEqual(label.get_text(), "Artist Name")
|
||||
self.assertEqual(label.get_field(), "artists.sort")
|
||||
self.assertFalse(label.get_sensitive())
|
||||
|
||||
label.set_field("albums.sort", active=True),
|
||||
self.assertEqual(label.get_text(), "Album Name")
|
||||
self.assertEqual(label.get_field(), "albums.sort")
|
||||
self.assertTrue(label.get_sensitive())
|
||||
|
||||
label.set_field("discs.subtitle", active=False),
|
||||
self.assertEqual(label.get_text(), "Disc Subtitle")
|
||||
self.assertEqual(label.get_field(), "discs.subtitle")
|
||||
self.assertFalse(label.get_sensitive())
|
||||
|
||||
label.set_field("albums.release", active=True),
|
||||
self.assertEqual(label.get_text(), "Release Date")
|
||||
self.assertEqual(label.get_field(), "albums.release")
|
||||
self.assertTrue(label.get_sensitive())
|
||||
|
||||
label.set_field("tracks.playcount", active=False),
|
||||
self.assertEqual(label.get_text(), "Play Count")
|
||||
self.assertEqual(label.get_field(), "tracks.playcount")
|
||||
self.assertFalse(label.get_sensitive())
|
||||
|
||||
label.set_field("tracks.lastplayed", active=True)
|
||||
self.assertEqual(label.get_text(), "Last Played")
|
||||
self.assertEqual(label.get_field(), "tracks.lastplayed")
|
||||
self.assertTrue(label.get_sensitive())
|
||||
|
||||
label.set_field("playlist_map.rowid", active=False)
|
||||
self.assertEqual(label.get_text(), "Order Added")
|
||||
self.assertEqual(label.get_field(), "playlist_map.rowid")
|
||||
self.assertFalse(label.get_sensitive())
|
||||
|
||||
|
||||
class TestSortRow(unittest.TestCase):
|
||||
def on_switched(self, row, enabled):
|
||||
self.enabled = enabled
|
||||
|
||||
def on_reversed(self, row):
|
||||
self.reversed = True
|
||||
|
||||
def on_move_up(self, row):
|
||||
self.move_up = True
|
||||
|
||||
def on_move_down(self, row):
|
||||
self.move_down = True
|
||||
|
||||
def test_init(self):
|
||||
row = sort.SortRow()
|
||||
self.assertIsInstance(row, Gtk.Box)
|
||||
self.assertIsInstance(row.switch, Gtk.Switch)
|
||||
self.assertIsInstance(row.label, sort.FieldLabel)
|
||||
self.assertIsInstance(row.direction, sort.DirectionButton)
|
||||
self.assertIsInstance(row.updown, sort.UpDownBox)
|
||||
|
||||
self.assertEqual(row.get_spacing(), 5)
|
||||
self.assertEqual(row.switch.get_valign(), Gtk.Align.CENTER)
|
||||
|
||||
self.assertEqual(row.get_first_child(), row.switch)
|
||||
self.assertEqual(row.switch.get_next_sibling(), row.label)
|
||||
self.assertEqual(row.label.get_next_sibling(), row.direction)
|
||||
self.assertEqual(row.direction.get_next_sibling(), row.updown)
|
||||
|
||||
def test_set_item(self):
|
||||
row = sort.SortRow()
|
||||
row.set_item("tracks.number ASC", 0, 3)
|
||||
self.assertTrue(row.switch.get_active())
|
||||
self.assertTrue(row.switch.get_sensitive())
|
||||
self.assertEqual(row.label.get_field(), "tracks.number")
|
||||
self.assertTrue(row.label.get_sensitive())
|
||||
self.assertEqual(row.direction.get_icon_name(), "view-sort-ascending")
|
||||
self.assertTrue(row.direction.get_sensitive())
|
||||
self.assertFalse(row.updown.get_first_child().get_sensitive())
|
||||
self.assertTrue(row.updown.get_last_child().get_sensitive())
|
||||
|
||||
row.set_item("playlist_map.rowid ASC", 1, 2)
|
||||
self.assertTrue(row.switch.get_active())
|
||||
self.assertFalse(row.switch.get_sensitive())
|
||||
|
||||
def test_signals(self):
|
||||
row = sort.SortRow()
|
||||
row.connect("switched", self.on_switched)
|
||||
row.connect("reversed", self.on_reversed)
|
||||
row.connect("move-up", self.on_move_up)
|
||||
row.connect("move-down", self.on_move_down)
|
||||
|
||||
row.switch.set_active(True)
|
||||
self.assertTrue(self.enabled)
|
||||
row.switch.set_active(False)
|
||||
self.assertFalse(self.enabled)
|
||||
|
||||
row.direction.emit("clicked")
|
||||
self.assertTrue(self.reversed)
|
||||
|
||||
row.updown.emit("move-up")
|
||||
self.assertTrue(self.move_up)
|
||||
|
||||
row.updown.emit("move-down")
|
||||
self.assertTrue(self.move_down)
|
||||
|
||||
|
||||
class TestFactory(unittest.TestCase):
|
||||
def test_init(self):
|
||||
factory = sort.Factory(model.FlatSortModel())
|
||||
self.assertIsInstance(factory, Gtk.SignalListItemFactory)
|
|
@ -1,62 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import db
|
||||
import unittest
|
||||
from gi.repository import Gtk
|
||||
from . import model
|
||||
from . import sort
|
||||
from . import view
|
||||
|
||||
class TestPlaylistView(unittest.TestCase):
|
||||
def test_init(self):
|
||||
collection = db.user.Table.find("Collection")
|
||||
pview = view.PlaylistView()
|
||||
|
||||
self.assertIsInstance(pview, Gtk.ColumnView)
|
||||
self.assertIsInstance(pview.get_model(), model.PlaylistSelection)
|
||||
self.assertIsInstance(pview.get_filter_model(), Gtk.FilterListModel)
|
||||
|
||||
self.assertTrue(pview.has_css_class("data-table"))
|
||||
self.assertTrue(pview.get_enable_rubberband())
|
||||
self.assertTrue(pview.get_hexpand())
|
||||
self.assertTrue(pview.get_vexpand())
|
||||
|
||||
self.assertIsNone(pview.get_playlist())
|
||||
pview.set_playlist(collection)
|
||||
self.assertEqual(pview.get_playlist(), collection)
|
||||
|
||||
|
||||
class TestPlaylistWindow(unittest.TestCase):
|
||||
def test_init(self):
|
||||
collection = db.user.Table.find("Collection")
|
||||
window = view.PlaylistWindow()
|
||||
|
||||
self.assertIsInstance(window, Gtk.ScrolledWindow)
|
||||
self.assertIsInstance(window.get_child(), view.PlaylistView)
|
||||
self.assertIsInstance(window.get_filter_model(), Gtk.FilterListModel)
|
||||
|
||||
self.assertIsNone(window.get_playlist())
|
||||
window.set_playlist(collection)
|
||||
self.assertEqual(window.get_playlist(), collection)
|
||||
|
||||
|
||||
class TestSortOrderView(unittest.TestCase):
|
||||
def test_init(self):
|
||||
collection = db.user.Table.find("Collection")
|
||||
sview = view.SortOrderView()
|
||||
|
||||
self.assertIsInstance(sview, Gtk.ListView)
|
||||
self.assertIsInstance(sview.get_model(), Gtk.NoSelection)
|
||||
self.assertIsInstance(sview.get_model().get_model(), model.FlatSortModel)
|
||||
self.assertIsInstance(sview.get_factory(), sort.Factory)
|
||||
|
||||
sview.set_playlist(collection)
|
||||
self.assertEqual(sview.get_model().get_model().get_enabled_model().playlist, collection)
|
||||
|
||||
|
||||
class TestSortOrderPopover(unittest.TestCase):
|
||||
def test_init(self):
|
||||
pop = view.SortOrderPopover()
|
||||
|
||||
self.assertIsInstance(pop, Gtk.Popover)
|
||||
self.assertIsInstance(pop.get_child(), view.SortOrderView)
|
||||
pop.set_playlist(db.user.Table.find("Collection"))
|
|
@ -1,97 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import db
|
||||
import audio
|
||||
from gi.repository import Gtk
|
||||
from . import column
|
||||
from . import model
|
||||
from . import sort
|
||||
|
||||
class PlaylistView(Gtk.ColumnView):
|
||||
def __init__(self):
|
||||
Gtk.ColumnView.__init__(self)
|
||||
self.add_css_class("data-table")
|
||||
self.set_model(model.PlaylistSelection())
|
||||
self.set_enable_rubberband(True)
|
||||
self.set_hexpand(True)
|
||||
self.set_vexpand(True)
|
||||
self.connect("activate", self.activated)
|
||||
audio.Player.connect("track-changed", self.track_changed)
|
||||
|
||||
self.append_column(column.TracknoColumn())
|
||||
self.append_column(column.TitleColumn())
|
||||
self.append_column(column.LengthColumn())
|
||||
self.append_column(column.ArtistColumn())
|
||||
self.append_column(column.AlbumColumn())
|
||||
self.append_column(column.SubtitleColumn())
|
||||
self.append_column(column.YearColumn())
|
||||
self.append_column(column.PlayCountColumn())
|
||||
self.append_column(column.LastPlayedColumn())
|
||||
|
||||
def activated(self, view, position):
|
||||
track = self.get_model().get_item(position)
|
||||
if audio.play_track(track):
|
||||
plist = self.get_playlist()
|
||||
if index := plist.get_track_index(track):
|
||||
plist.current = index
|
||||
if plist == db.user.Table.find("Queued Tracks"):
|
||||
plist.remove_track(track)
|
||||
|
||||
def selected_tracks(self):
|
||||
model = self.get_model()
|
||||
selection = model.get_selection()
|
||||
for n in range(selection.get_size()):
|
||||
yield model.get_item(selection.get_nth(n))
|
||||
model.unselect_all()
|
||||
|
||||
def clear_selection(self):
|
||||
self.get_model().unselect_all()
|
||||
|
||||
def scroll_to(self, track):
|
||||
selection = self.get_model()
|
||||
adjustment = self.get_vadjustment()
|
||||
for i in range(selection.get_n_items()):
|
||||
if selection.get_item(i) == track:
|
||||
selection.select_item(i, True)
|
||||
pos = max(i - 5, 0) * adjustment.get_upper()
|
||||
adjustment.set_value(pos / selection.get_n_items())
|
||||
return True
|
||||
return False
|
||||
|
||||
def track_changed(self, player, old, new):
|
||||
if not self.scroll_to(new):
|
||||
self.scroll_to(self.get_playlist().get_current_track())
|
||||
|
||||
def get_filter_model(self): return self.get_model().get_filter_model()
|
||||
def get_playlist(self): return self.get_model().get_playlist()
|
||||
def set_playlist(self, plist): self.get_model().set_playlist(plist)
|
||||
|
||||
|
||||
class PlaylistWindow(Gtk.ScrolledWindow):
|
||||
def __init__(self):
|
||||
Gtk.ScrolledWindow.__init__(self)
|
||||
self.set_child(PlaylistView())
|
||||
|
||||
def get_filter_model(self): return self.get_child().get_filter_model()
|
||||
def get_playlist(self): return self.get_child().get_playlist()
|
||||
def set_playlist(self, plist): self.get_child().set_playlist(plist)
|
||||
def selected_tracks(self): return self.get_child().selected_tracks()
|
||||
def clear_selection(self): self.get_child().clear_selection()
|
||||
|
||||
|
||||
class SortOrderView(Gtk.ListView):
|
||||
def __init__(self):
|
||||
Gtk.ListView.__init__(self)
|
||||
self.set_model(Gtk.NoSelection.new(model.FlatSortModel()))
|
||||
self.set_factory(sort.Factory(self.get_model().get_model()))
|
||||
|
||||
def set_playlist(self, plist):
|
||||
self.get_model().get_model().set_playlist(plist)
|
||||
|
||||
|
||||
class SortOrderPopover(Gtk.Popover):
|
||||
def __init__(self):
|
||||
Gtk.Popover.__init__(self)
|
||||
self.set_child(SortOrderView())
|
||||
|
||||
def set_playlist(self, plist):
|
||||
self.get_child().set_playlist(plist)
|
|
@ -1,37 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
from . import metadata
|
||||
from . import queue
|
||||
from . import task
|
||||
from . import widgets
|
||||
|
||||
Queue = queue.TaskQueue()
|
||||
|
||||
|
||||
def ProgressBar():
|
||||
return widgets.ProgressBar(Queue)
|
||||
|
||||
def AddFolderButton():
|
||||
return widgets.AddFolderButton(Queue)
|
||||
|
||||
def UpdateButton(library):
|
||||
return widgets.UpdateButton(library, Queue)
|
||||
|
||||
def UpdateAllButton():
|
||||
return widgets.UpdateAllButton(Queue)
|
||||
|
||||
def RemoveButton(library):
|
||||
return widgets.RemoveButton(library, Queue)
|
||||
|
||||
def EnableSwitch(library):
|
||||
return widgets.EnableSwitch(library, Queue)
|
||||
|
||||
|
||||
def commit():
|
||||
Queue.push(task.CommitTask())
|
||||
|
||||
def update_library(lib):
|
||||
Queue.push(task.CheckSchedulerTask(lib))
|
||||
Queue.push(task.DirectoryTask(lib, lib.path))
|
||||
|
||||
def remove_library(lib):
|
||||
Queue.push(task.RemoveLibrarySchedulerTask(lib))
|
|
@ -1,63 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import datetime
|
||||
import mutagen
|
||||
import re
|
||||
|
||||
class Metadata:
|
||||
def __init__(self, filepath):
|
||||
self.path = filepath
|
||||
self.file = None
|
||||
|
||||
def __enter__(self):
|
||||
self.file = mutagen.File(self.path)
|
||||
return self if self.file else None
|
||||
|
||||
def __exit__(self, exp_type, exp_value, traceback):
|
||||
self.file = None
|
||||
return (exp_type == None) or (exp_type == AttributeError)
|
||||
|
||||
def album(self):
|
||||
return self.file.get("album", [ "Unknown Album" ])[0]
|
||||
|
||||
def artist(self):
|
||||
artist = self.file.get("artist", [ "Unknown Artist" ])
|
||||
return self.file.get("albumartist", artist)[0]
|
||||
|
||||
def artistsort(self):
|
||||
sort = self.file.get("artistsort", [ self.artist() ])
|
||||
return self.file.get("albumartistsort", sort)[0]
|
||||
|
||||
def decade(self):
|
||||
return (self.year() // 10) * 10
|
||||
|
||||
def discnumber(self):
|
||||
return int(self.file.get("discnumber", [ 1 ])[0])
|
||||
|
||||
def discsubtitle(self):
|
||||
return self.file.get("discsubtitle", [ None ])[0]
|
||||
|
||||
def genres(self):
|
||||
genre = self.file.get("genre", [ "" ])[0]
|
||||
return [ g.strip() for g in re.split(",|;|/|:", genre) ]
|
||||
|
||||
def length(self):
|
||||
return int(self.file.info.length)
|
||||
|
||||
def release(self):
|
||||
date = self.file.get("date", [ "0" ])
|
||||
date = self.file.get("originalyear", date)[0].split("-")
|
||||
year = int(date[0]) if len(date) > 0 else 1
|
||||
month = int(date[1]) if len(date) > 1 else 1
|
||||
day = int(date[2]) if len(date) > 2 else 1
|
||||
return datetime.date(max(year, 1), month, day)
|
||||
|
||||
def title(self):
|
||||
return self.file.get("title", [ "" ])[0]
|
||||
|
||||
def tracknumber(self):
|
||||
return int(self.file.get("tracknumber", [ 0 ])[0])
|
||||
|
||||
def year(self):
|
||||
year = self.file.get("date", [ "0" ])
|
||||
year = self.file.get("originalyear", year)[0]
|
||||
return int(re.match("\d+", year).group(0))
|
|
@ -1,56 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
from gi.repository import GLib
|
||||
from gi.repository import GObject
|
||||
from . import task
|
||||
|
||||
class TaskQueue(GObject.GObject):
|
||||
def __init__(self):
|
||||
GObject.GObject.__init__(self)
|
||||
self.tasks = [ ]
|
||||
self.total = 0
|
||||
self.idleid = None
|
||||
|
||||
def __del__(self):
|
||||
if self.idleid != None:
|
||||
GLib.source_remove(self.idleid)
|
||||
|
||||
def push(self, task):
|
||||
self.emit("task-pushed", task)
|
||||
|
||||
def clear(self):
|
||||
self.emit("tasks-finished")
|
||||
|
||||
def run(self):
|
||||
self.emit("run-task", self.tasks.pop(0))
|
||||
if len(self.tasks) > 0:
|
||||
return GLib.SOURCE_CONTINUE
|
||||
|
||||
self.emit("tasks-finished")
|
||||
return GLib.SOURCE_REMOVE
|
||||
|
||||
@GObject.Property
|
||||
def progress(self):
|
||||
if len(self.tasks) == 0:
|
||||
return 0
|
||||
return (self.total - len(self.tasks)) / self.total
|
||||
|
||||
@GObject.Signal(arg_types=(task.Task,))
|
||||
def task_pushed(self, task):
|
||||
self.tasks.append(task)
|
||||
self.total += 1
|
||||
if self.idleid == None:
|
||||
self.idleid = GLib.idle_add(self.run)
|
||||
|
||||
@GObject.Signal(arg_types=(task.Task,))
|
||||
def run_task(self, task):
|
||||
if tasks := task.run_task():
|
||||
self.tasks += tasks
|
||||
self.total += len(tasks)
|
||||
|
||||
@GObject.Signal
|
||||
def tasks_finished(self):
|
||||
if self.idleid:
|
||||
GLib.source_remove(self.idleid)
|
||||
self.tasks.clear()
|
||||
self.total = 0
|
||||
self.idleid = None
|
134
scanner/task.py
134
scanner/task.py
|
@ -1,134 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import db
|
||||
import datetime
|
||||
from gi.repository import GObject
|
||||
from . import metadata
|
||||
|
||||
class Task(GObject.GObject):
|
||||
def run_task(self): raise NotImplementedError
|
||||
|
||||
|
||||
class CommitTask(Task):
|
||||
def run_task(self):
|
||||
db.sql.commit()
|
||||
db.sql.optimize()
|
||||
|
||||
|
||||
class FileTask(Task):
|
||||
def __init__(self, library, filepath):
|
||||
Task.__init__(self)
|
||||
self.library = library
|
||||
self.filepath = filepath
|
||||
|
||||
def run_task(self):
|
||||
if db.track.Table.lookup(self.filepath):
|
||||
return
|
||||
|
||||
with metadata.Metadata(self.filepath) as meta:
|
||||
artist = db.artist.Table.find(meta.artist(), meta.artistsort())
|
||||
album = artist.find_album(meta.album(), meta.release())
|
||||
disc = album.find_disc(meta.discnumber(), meta.discsubtitle())
|
||||
decade = db.decade.Table.find(meta.decade())
|
||||
year = decade.find_year(meta.year())
|
||||
track = db.track.Table.insert(self.library, artist, album, disc,
|
||||
decade, year, meta.tracknumber(),
|
||||
meta.length(), meta.title(),
|
||||
self.filepath)
|
||||
for genre in meta.genres():
|
||||
db.genre.Table.find(genre).add_track(track)
|
||||
|
||||
|
||||
class DirectoryTask(Task):
|
||||
def __init__(self, library, dirpath):
|
||||
Task.__init__(self)
|
||||
self.library = library
|
||||
self.dirpath = dirpath
|
||||
|
||||
def run_task(self):
|
||||
res = [ ]
|
||||
for f in self.dirpath.iterdir():
|
||||
if f.is_file():
|
||||
res.append(FileTask(self.library, f))
|
||||
elif f.is_dir():
|
||||
res.append(DirectoryTask(self.library, f))
|
||||
res.append(CommitTask())
|
||||
return res
|
||||
|
||||
|
||||
class CheckTask(Task):
|
||||
def __init__(self, tracks):
|
||||
Task.__init__(self)
|
||||
self.tracks = tracks
|
||||
|
||||
def run_task(self):
|
||||
need_commit = False
|
||||
for track in self.tracks:
|
||||
if not track.path.exists():
|
||||
db.track.Table.delete(track)
|
||||
need_commit = True
|
||||
if need_commit:
|
||||
db.sql.commit()
|
||||
|
||||
|
||||
class CheckSchedulerTask(Task):
|
||||
def __init__(self, library):
|
||||
Task.__init__(self)
|
||||
self.library = library
|
||||
|
||||
def track_chunks(self):
|
||||
tracks = self.library.tracks()
|
||||
while len(tracks) > 0:
|
||||
res = tracks[:50]
|
||||
tracks = tracks[50:]
|
||||
yield res
|
||||
|
||||
def run_task(self):
|
||||
return [ CheckTask(tracks) for tracks in self.track_chunks() ]
|
||||
|
||||
|
||||
class RemoveTask(Task):
|
||||
def __init__(self, tracks):
|
||||
Task.__init__(self)
|
||||
self.tracks = tracks
|
||||
|
||||
def run_task(self):
|
||||
for track in self.tracks:
|
||||
db.track.Table.delete(track)
|
||||
db.sql.commit()
|
||||
|
||||
|
||||
class RemoveLibraryTask(Task):
|
||||
def __init__(self, library):
|
||||
Task.__init__(self)
|
||||
self.library = library
|
||||
|
||||
def run_task(self):
|
||||
db.library.Table.delete(self.library)
|
||||
db.sql.commit()
|
||||
|
||||
|
||||
class RemoveLibrarySchedulerTask(Task):
|
||||
def __init__(self, library):
|
||||
Task.__init__(self)
|
||||
self.library = library
|
||||
|
||||
def track_chunks(self):
|
||||
tracks = self.library.tracks()
|
||||
while len(tracks) > 0:
|
||||
res = tracks[:50]
|
||||
tracks = tracks[50:]
|
||||
yield res
|
||||
|
||||
def run_task(self):
|
||||
return [ RemoveTask(tracks) for tracks in self.track_chunks() ] + \
|
||||
[ RemoveLibraryTask(self.library) ]
|
||||
|
||||
|
||||
class EnableLibraryTask(Task):
|
||||
def __init__(self, library, enable):
|
||||
Task.__init__(self)
|
||||
self.library = library
|
||||
self.enable = enable
|
||||
|
||||
def run_task(self):
|
||||
self.library.set_property("enabled", self.enable)
|
|
@ -1,44 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import datetime
|
||||
import pathlib
|
||||
import scanner
|
||||
import unittest
|
||||
|
||||
test_tracks = pathlib.Path("./data/Test Album")
|
||||
track_01 = test_tracks / "01 - Test Track.ogg"
|
||||
track_02 = test_tracks / "02 - Test {Disc 2}.ogg"
|
||||
text_txt = test_tracks / "text.txt"
|
||||
|
||||
class TestMetadata(unittest.TestCase):
|
||||
def test_init(self):
|
||||
mdf = scanner.metadata.Metadata(track_01)
|
||||
self.assertEqual(mdf.path, track_01)
|
||||
self.assertIsNone(mdf.file)
|
||||
|
||||
def test_track_01(self):
|
||||
with scanner.metadata.Metadata(track_01) as mdf:
|
||||
self.assertEqual(mdf.album(), "Test Album")
|
||||
self.assertEqual(mdf.artist(), "Test Artist")
|
||||
self.assertEqual(mdf.artistsort(), "Artist, Test")
|
||||
self.assertEqual(mdf.decade(), 2010)
|
||||
self.assertEqual(mdf.discnumber(), 1)
|
||||
self.assertEqual(mdf.discsubtitle(), None)
|
||||
self.assertEqual(mdf.genres(), [ "Test" ])
|
||||
self.assertEqual(mdf.length(), 10)
|
||||
self.assertEqual(mdf.release(), datetime.date(2019, 2, 1))
|
||||
self.assertEqual(mdf.title(), "Test Track")
|
||||
self.assertEqual(mdf.tracknumber(), 1)
|
||||
self.assertEqual(mdf.year(), 2019)
|
||||
|
||||
def test_track_02(self):
|
||||
with scanner.metadata.Metadata(track_02) as mdf:
|
||||
self.assertEqual(mdf.artist(), "Test Album Artist")
|
||||
self.assertEqual(mdf.artistsort(), "Album Artist, Test")
|
||||
self.assertEqual(mdf.discsubtitle(), "Electric Boogaloo")
|
||||
self.assertEqual(mdf.genres(), [ "Test", "Genre", "List" ])
|
||||
self.assertEqual(mdf.release(), datetime.date(2019, 1, 1))
|
||||
self.assertEqual(mdf.year(), 2019)
|
||||
|
||||
def test_text_txt(self):
|
||||
with scanner.metadata.Metadata(text_txt) as mdf:
|
||||
mdf.artist()
|
|
@ -1,88 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import unittest
|
||||
from gi.repository import GObject
|
||||
from . import queue
|
||||
from . import task
|
||||
|
||||
class FakeTask(task.Task):
|
||||
def __init__(self, res=None):
|
||||
task.Task.__init__(self)
|
||||
self.res = res
|
||||
def run_task(self): return self.res
|
||||
|
||||
class TestTaskQueue(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.idle_start = None
|
||||
|
||||
def on_task_pushed(self, queue, task):
|
||||
self.pushed_task = task
|
||||
|
||||
def on_run_task(self, queue, task):
|
||||
self.run_task = task
|
||||
|
||||
def on_tasks_finished(self, queue):
|
||||
self.tasks_finished = True
|
||||
|
||||
def test_init(self):
|
||||
q = queue.TaskQueue()
|
||||
|
||||
self.assertIsInstance(q, GObject.GObject)
|
||||
self.assertEqual(q.tasks, [ ])
|
||||
self.assertEqual(q.total, 0)
|
||||
self.assertEqual(q.idleid, None)
|
||||
self.assertEqual(q.progress, 0)
|
||||
|
||||
def test_push(self):
|
||||
q = queue.TaskQueue()
|
||||
fake = FakeTask()
|
||||
q.connect("task-pushed", self.on_task_pushed)
|
||||
|
||||
q.push(fake)
|
||||
self.assertEqual(q.tasks, [ fake ])
|
||||
self.assertIsNotNone(q.idleid)
|
||||
self.assertEqual(self.pushed_task, fake)
|
||||
|
||||
def test_clear(self):
|
||||
q = queue.TaskQueue()
|
||||
q.connect("tasks-finished", self.on_tasks_finished)
|
||||
|
||||
q.push(FakeTask())
|
||||
q.clear()
|
||||
self.assertEqual(q.tasks, [ ])
|
||||
self.assertTrue(self.tasks_finished)
|
||||
self.assertIsNone(q.idleid)
|
||||
|
||||
def test_run(self):
|
||||
q = queue.TaskQueue()
|
||||
fake3 = FakeTask()
|
||||
fake2 = FakeTask()
|
||||
fake1 = FakeTask([ fake2, fake3 ])
|
||||
q.connect("run-task", self.on_run_task)
|
||||
q.connect("tasks-finished", self.on_tasks_finished)
|
||||
|
||||
q.push(fake1)
|
||||
self.assertEqual(q.tasks, [ fake1 ])
|
||||
self.assertEqual(q.total, 1)
|
||||
self.assertEqual(q.progress, 0/1)
|
||||
|
||||
q.run()
|
||||
self.assertEqual(self.run_task, fake1)
|
||||
self.assertEqual(q.tasks, [ fake2, fake3 ])
|
||||
self.assertEqual(q.total, 3)
|
||||
self.assertEqual(q.progress, 1/3)
|
||||
self.assertIsNotNone(q.idleid)
|
||||
|
||||
q.run()
|
||||
self.assertEqual(self.run_task, fake2)
|
||||
self.assertEqual(q.tasks, [ fake3 ])
|
||||
self.assertEqual(q.progress, 2/3)
|
||||
self.assertIsNotNone(q.idleid)
|
||||
|
||||
q.run()
|
||||
self.assertEqual(self.run_task, fake3)
|
||||
self.assertEqual(q.tasks, [ ])
|
||||
|
||||
self.assertTrue(self.tasks_finished)
|
||||
self.assertEqual(q.total, 0)
|
||||
self.assertEqual(q.progress, 0)
|
||||
self.assertIsNone(q.idleid)
|
|
@ -1,50 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import datetime
|
||||
import db
|
||||
import pathlib
|
||||
import scanner
|
||||
import unittest
|
||||
|
||||
test_album = pathlib.Path("./data/Test Album")
|
||||
test_track = test_album / "01 - Test Track.ogg"
|
||||
|
||||
class TestScanner(unittest.TestCase):
|
||||
def tearDown(self):
|
||||
scanner.Queue.tasks_finished()
|
||||
|
||||
def test_init(self):
|
||||
self.assertIsInstance(scanner.Queue, scanner.queue.TaskQueue)
|
||||
|
||||
def test_update_library(self):
|
||||
lib = db.library.Table.find(test_album)
|
||||
scanner.update_library(lib)
|
||||
self.assertIsInstance(scanner.Queue.tasks[0],
|
||||
scanner.task.CheckSchedulerTask)
|
||||
self.assertIsInstance(scanner.Queue.tasks[1],
|
||||
scanner.task.DirectoryTask)
|
||||
|
||||
def test_remove_library(self):
|
||||
lib = db.library.Table.find(test_album)
|
||||
scanner.remove_library(lib)
|
||||
self.assertIsInstance(scanner.Queue.tasks[0],
|
||||
scanner.task.RemoveLibrarySchedulerTask)
|
||||
|
||||
def test_commit(self):
|
||||
scanner.commit()
|
||||
self.assertIsInstance(scanner.Queue.tasks[0], scanner.task.CommitTask)
|
||||
|
||||
def test_widgets(self):
|
||||
lib = db.library.Table.find(test_album)
|
||||
|
||||
self.assertIsInstance(scanner.ProgressBar(),
|
||||
scanner.widgets.ProgressBar)
|
||||
self.assertIsInstance(scanner.AddFolderButton(),
|
||||
scanner.widgets.AddFolderButton)
|
||||
self.assertIsInstance(scanner.UpdateButton(lib),
|
||||
scanner.widgets.UpdateButton)
|
||||
self.assertIsInstance(scanner.UpdateAllButton(),
|
||||
scanner.widgets.UpdateAllButton)
|
||||
self.assertIsInstance(scanner.RemoveButton(lib),
|
||||
scanner.widgets.RemoveButton)
|
||||
self.assertIsInstance(scanner.EnableSwitch(lib),
|
||||
scanner.widgets.EnableSwitch)
|
|
@ -1,223 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import datetime
|
||||
import db
|
||||
import pathlib
|
||||
import unittest
|
||||
from gi.repository import GObject
|
||||
from . import task
|
||||
|
||||
test_tracks = pathlib.Path("./data/Test Album")
|
||||
test_track01 = test_tracks / "01 - Test Track.ogg"
|
||||
|
||||
class TestScannerTask(unittest.TestCase):
|
||||
def test_task(self):
|
||||
t = task.Task()
|
||||
self.assertIsInstance(t, GObject.GObject)
|
||||
with self.assertRaises(NotImplementedError):
|
||||
t.run_task()
|
||||
|
||||
|
||||
class TestScannerCommitTask(unittest.TestCase):
|
||||
def test_task(self):
|
||||
ct = task.CommitTask()
|
||||
self.assertIsInstance(ct, task.Task)
|
||||
self.assertEqual(ct.run_task(), None)
|
||||
|
||||
|
||||
class TestScannerFileTask(unittest.TestCase):
|
||||
def setUp(self):
|
||||
db.reset()
|
||||
|
||||
def test_task(self):
|
||||
lib = db.library.Table.find(test_tracks)
|
||||
ft = task.FileTask(lib, test_track01)
|
||||
|
||||
self.assertIsInstance(ft, task.Task)
|
||||
self.assertEqual(ft.library, lib)
|
||||
self.assertEqual(ft.filepath, test_track01)
|
||||
|
||||
self.assertIsNone(ft.run_task())
|
||||
self.assertIsNone(ft.run_task())
|
||||
|
||||
artist = db.artist.Table.lookup("Test Artist")
|
||||
self.assertIsNotNone(artist)
|
||||
|
||||
album = db.album.Table.lookup(artist, "Test Album",
|
||||
datetime.date(2019, 2, 1))
|
||||
self.assertIsNotNone(album)
|
||||
|
||||
self.assertIsNotNone(db.disc.Table.lookup(album, 1))
|
||||
|
||||
decade = db.decade.Table.lookup(2010)
|
||||
self.assertIsNotNone(decade)
|
||||
self.assertIsNotNone(db.year.Table.lookup(2019))
|
||||
|
||||
track = db.track.Table.lookup(test_track01)
|
||||
self.assertEqual(track.number, 1)
|
||||
self.assertEqual(track.length, 10)
|
||||
self.assertEqual(track.title, "Test Track")
|
||||
self.assertEqual(track.path, test_track01)
|
||||
|
||||
genre = db.genre.Table.lookup("Test")
|
||||
self.assertEqual(genre.get_track(0), track)
|
||||
|
||||
new = db.user.Table.find("New Tracks")
|
||||
self.assertEqual(new.get_track(0), track)
|
||||
|
||||
|
||||
class TestScannerDirectoryTask(unittest.TestCase):
|
||||
def setUp(self):
|
||||
db.reset()
|
||||
|
||||
def test_task(self):
|
||||
lib = db.library.Table.find(test_tracks)
|
||||
dt = task.DirectoryTask(lib, test_tracks)
|
||||
|
||||
self.assertIsInstance(dt, task.Task)
|
||||
self.assertEqual(dt.library, lib)
|
||||
self.assertEqual(dt.dirpath, test_tracks)
|
||||
|
||||
tasks = dt.run_task()
|
||||
self.assertIsNotNone(tasks)
|
||||
self.assertEqual(len(tasks), 15)
|
||||
|
||||
file_tasks = [ t for t in tasks if isinstance(t, task.FileTask) ]
|
||||
dir_tasks = [ t for t in tasks if isinstance(t, task.DirectoryTask) ]
|
||||
commit_task = [ t for t in tasks if isinstance(t, task.CommitTask) ]
|
||||
|
||||
self.assertEqual(len(file_tasks), 13)
|
||||
self.assertEqual(len(dir_tasks), 1)
|
||||
self.assertEqual(len(commit_task), 1)
|
||||
|
||||
|
||||
class TestScannerCheckTask(unittest.TestCase):
|
||||
def setUp(self):
|
||||
db.reset()
|
||||
|
||||
def test_task(self):
|
||||
lib = db.library.Table.find(test_tracks)
|
||||
for i in [ 1, 2, 3 ]:
|
||||
db.make_fake_track(i, i, f"Test Track {i}", f"{lib.path}/{i}.ogg", lib.path)
|
||||
track = db.make_fake_track(4, 4, "data/Test Album/04 - Test (Disc Two).ogg", lib.path)
|
||||
|
||||
tracks = [ db.track.Table.get(i) for i in [ 1, 2, 3, 4 ] ]
|
||||
ct = task.CheckTask(tracks)
|
||||
|
||||
self.assertIsInstance(ct, task.Task)
|
||||
self.assertEqual(ct.tracks, tracks)
|
||||
|
||||
self.assertIsNone(ct.run_task())
|
||||
self.assertIsNone(db.track.Table.get(1))
|
||||
self.assertIsNone(db.track.Table.get(2))
|
||||
self.assertIsNone(db.track.Table.get(3))
|
||||
self.assertEqual(db.track.Table.get(4), track)
|
||||
|
||||
|
||||
class TestScannerCheckSchedulerTask(unittest.TestCase):
|
||||
def setUp(self):
|
||||
db.reset()
|
||||
|
||||
def test_task(self):
|
||||
lib = db.library.Table.find(test_tracks)
|
||||
track = db.make_fake_track(1, 1, "Test Album Track", str(test_track01), str(test_tracks))
|
||||
|
||||
lib2 = db.library.Table.find("/a/b/c")
|
||||
for i in range(75):
|
||||
db.make_fake_track(i, i, f"Test Track {i}",f"/a/b/c/{i}.ogg", lib="/a/b/c")
|
||||
|
||||
cst = task.CheckSchedulerTask(lib2)
|
||||
self.assertIsInstance(cst, task.Task)
|
||||
self.assertEqual(cst.library, lib2)
|
||||
|
||||
tasks = cst.run_task()
|
||||
self.assertEqual(len(tasks), 2)
|
||||
self.assertIsInstance(tasks[0], task.CheckTask)
|
||||
self.assertIsInstance(tasks[1], task.CheckTask)
|
||||
self.assertEqual(len(tasks[0].tracks), 50)
|
||||
self.assertEqual(len(tasks[1].tracks), 25)
|
||||
tasks[0].run_task()
|
||||
tasks[1].run_task()
|
||||
|
||||
self.assertEqual(db.track.Table.get(1), track)
|
||||
for i in range(1, 76):
|
||||
self.assertIsNone(db.track.Table.get(1+i))
|
||||
|
||||
|
||||
class TestScannerRemoveTask(unittest.TestCase):
|
||||
def setUp(self):
|
||||
db.reset()
|
||||
|
||||
def test_task(self):
|
||||
lib = db.library.Table.find(test_tracks)
|
||||
for i in [ 1, 2, 3, 4, 5 ]:
|
||||
db.make_fake_track(i, i, f"Test Track {i}", f"{lib.path}/{i}.ogg", lib.path)
|
||||
tracks = [ db.track.Table.get(i) for i in [ 1, 2, 3, 4, 5 ] ]
|
||||
rt = task.RemoveTask(tracks)
|
||||
|
||||
self.assertIsInstance(rt, task.Task)
|
||||
self.assertEqual(rt.tracks, tracks)
|
||||
|
||||
self.assertIsNone(rt.run_task())
|
||||
for i in [ 1, 2, 3, 4, 5 ]:
|
||||
self.assertIsNone(db.track.Table.get(i))
|
||||
|
||||
|
||||
class TestScannerRemoveLibraryTask(unittest.TestCase):
|
||||
def setUp(self):
|
||||
db.reset()
|
||||
|
||||
def test_task(self):
|
||||
lib = db.library.Table.find(test_tracks)
|
||||
rlt = task.RemoveLibraryTask(lib)
|
||||
|
||||
self.assertIsInstance(rlt, task.Task)
|
||||
self.assertEqual(rlt.library, lib)
|
||||
|
||||
self.assertIsNone(rlt.run_task())
|
||||
self.assertIsNone(db.library.Table.lookup(test_tracks))
|
||||
|
||||
|
||||
class TestScannerRemoveLibrarySchedulerTask(unittest.TestCase):
|
||||
def setUp(self):
|
||||
db.reset()
|
||||
|
||||
def test_task(self):
|
||||
lib = db.library.Table.find(test_tracks)
|
||||
for i in range(75):
|
||||
db.make_fake_track(i, i, f"Test Track {i}",f"/a/b/c/{i}.ogg", lib.path)
|
||||
|
||||
rlst = task.RemoveLibrarySchedulerTask(lib)
|
||||
self.assertIsInstance(rlst, task.Task)
|
||||
self.assertEqual(rlst.library, lib)
|
||||
|
||||
tasks = rlst.run_task()
|
||||
self.assertEqual(len(tasks), 3)
|
||||
self.assertIsInstance(tasks[0], task.RemoveTask)
|
||||
self.assertIsInstance(tasks[1], task.RemoveTask)
|
||||
self.assertIsInstance(tasks[2], task.RemoveLibraryTask)
|
||||
self.assertEqual(len(tasks[0].tracks), 50)
|
||||
self.assertEqual(len(tasks[1].tracks), 25)
|
||||
self.assertEqual(tasks[2].library, lib)
|
||||
|
||||
for t in tasks: t.run_task()
|
||||
|
||||
for i in range(1, 76):
|
||||
self.assertIsNone(db.track.Table.get(i))
|
||||
self.assertIsNone(db.library.Table.lookup(test_tracks))
|
||||
|
||||
|
||||
class TestEnableLibraryTask(unittest.TestCase):
|
||||
def test_task(self):
|
||||
lib = db.library.Table.find(test_tracks)
|
||||
elt = task.EnableLibraryTask(lib, False)
|
||||
|
||||
self.assertIsInstance(elt, task.Task)
|
||||
self.assertEqual(elt.library, lib)
|
||||
self.assertEqual(elt.enable, False)
|
||||
|
||||
self.assertIsNone(elt.run_task())
|
||||
self.assertFalse(lib.enabled)
|
||||
|
||||
elt.enable = True
|
||||
self.assertIsNone(elt.run_task())
|
||||
self.assertTrue(lib.enabled)
|
|
@ -1,192 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import db
|
||||
import pathlib
|
||||
import time
|
||||
import unittest
|
||||
from gi.repository import Gio
|
||||
from gi.repository import GLib
|
||||
from gi.repository import Gtk
|
||||
from . import queue
|
||||
from . import task
|
||||
from . import widgets
|
||||
|
||||
main_context = GLib.main_context_default()
|
||||
test_album = pathlib.Path("./data/Test Album/").absolute()
|
||||
test_subdir = test_album / "Test Subdir"
|
||||
|
||||
class FakeTask(task.Task):
|
||||
def run_task(self): return None
|
||||
|
||||
class TestScannerProgressBar(unittest.TestCase):
|
||||
def test_init(self):
|
||||
q = queue.TaskQueue()
|
||||
pb = widgets.ProgressBar(q)
|
||||
|
||||
self.assertIsInstance(pb, Gtk.ProgressBar)
|
||||
self.assertTrue( pb.get_hexpand())
|
||||
self.assertFalse(pb.get_visible())
|
||||
self.assertEqual(pb.get_valign(), Gtk.Align.CENTER)
|
||||
self.assertEqual(pb.queue, q)
|
||||
|
||||
for i in range(3): q.push(FakeTask())
|
||||
self.assertTrue(pb.get_visible())
|
||||
self.assertTrue(widgets.ProgressBar(q).get_visible())
|
||||
|
||||
for i in [ 1, 2, 0 ]:
|
||||
q.run()
|
||||
self.assertEqual(pb.get_fraction(), i/3)
|
||||
self.assertFalse(pb.get_visible())
|
||||
|
||||
|
||||
class DirectoryChooserWidget(unittest.TestCase):
|
||||
def test_init(self):
|
||||
dcw = widgets.DirectoryChooserWidget()
|
||||
|
||||
self.assertIsInstance(dcw, Gtk.FileChooserWidget)
|
||||
self.assertIsInstance(dcw.filter, Gtk.FileFilter)
|
||||
|
||||
self.assertEqual(dcw.get_action(), Gtk.FileChooserAction.SELECT_FOLDER)
|
||||
self.assertEqual(dcw.get_filter(), dcw.filter)
|
||||
self.assertFalse(dcw.get_create_folders())
|
||||
|
||||
gfile = Gio.File.new_for_path(str(test_album))
|
||||
self.assertTrue(dcw.set_current_folder(gfile))
|
||||
while main_context.iteration(may_block=False): pass
|
||||
self.assertEqual(dcw.selected_path(), test_album)
|
||||
|
||||
gfile = Gio.File.new_for_path(str(test_subdir))
|
||||
self.assertTrue(dcw.set_file(gfile))
|
||||
while main_context.iteration(may_block=False): time.sleep(0.001)
|
||||
self.assertEqual(dcw.selected_path(), test_subdir)
|
||||
|
||||
|
||||
class DirectoryChooserPopover(unittest.TestCase):
|
||||
def test_init(self):
|
||||
db.reset()
|
||||
q = queue.TaskQueue()
|
||||
dcp = widgets.DirectoryChooserPopover(q)
|
||||
|
||||
self.assertIsInstance(dcp, Gtk.Popover)
|
||||
self.assertIsInstance(dcp.chooser, widgets.DirectoryChooserWidget)
|
||||
self.assertIsInstance(dcp.cancel, Gtk.Button)
|
||||
self.assertIsInstance(dcp.select, Gtk.Button)
|
||||
self.assertIsInstance(dcp.grid, Gtk.Grid)
|
||||
|
||||
self.assertEqual(dcp.queue, q)
|
||||
self.assertEqual(dcp.cancel.get_label(), "Cancel")
|
||||
self.assertEqual(dcp.select.get_label(), "Add Folder")
|
||||
|
||||
self.assertTrue(dcp.has_css_class("normal-icons"))
|
||||
self.assertTrue(dcp.select.has_css_class("suggested-action"))
|
||||
|
||||
self.assertEqual(dcp.get_child(), dcp.grid)
|
||||
self.assertIn(dcp.chooser, dcp.grid)
|
||||
self.assertIn(dcp.cancel, dcp.grid)
|
||||
self.assertIn(dcp.select, dcp.grid)
|
||||
|
||||
gfile = Gio.File.new_for_path(str(test_album))
|
||||
self.assertTrue(dcp.chooser.set_current_folder(gfile))
|
||||
while main_context.iteration(may_block=False): pass
|
||||
|
||||
dcp.select.emit("clicked")
|
||||
self.assertIsNotNone(db.library.Table.find(test_album))
|
||||
self.assertEqual(len(q.tasks), 2)
|
||||
self.assertIsInstance(q.tasks[0], task.CheckSchedulerTask)
|
||||
self.assertIsInstance(q.tasks[1], task.DirectoryTask)
|
||||
q.tasks_finished()
|
||||
|
||||
|
||||
class TestScannerAddFolderButton(unittest.TestCase):
|
||||
def test_init(self):
|
||||
q = queue.TaskQueue()
|
||||
afb = widgets.AddFolderButton(q)
|
||||
|
||||
self.assertIsInstance(afb, Gtk.MenuButton)
|
||||
self.assertIsInstance(afb.popover, widgets.DirectoryChooserPopover)
|
||||
|
||||
self.assertEqual(afb.get_popover(), afb.popover)
|
||||
self.assertEqual(afb.get_icon_name(), "folder-new")
|
||||
self.assertEqual(afb.get_direction(), Gtk.ArrowType.UP)
|
||||
|
||||
|
||||
class TestScannerUpdateButton(unittest.TestCase):
|
||||
def test_init(self):
|
||||
lib = db.library.Table.find("/a/b/c")
|
||||
q = queue.TaskQueue()
|
||||
ub = widgets.UpdateButton(lib, q)
|
||||
|
||||
self.assertIsInstance(ub, Gtk.Button)
|
||||
self.assertIsInstance(ub, widgets.ScannerButton)
|
||||
self.assertEqual(ub.get_icon_name(), "view-refresh")
|
||||
self.assertEqual(ub.library, lib)
|
||||
self.assertEqual(ub.queue, q)
|
||||
self.assertTrue(ub.get_hexpand())
|
||||
|
||||
ub.emit("clicked")
|
||||
self.assertEqual(len(q.tasks), 2)
|
||||
self.assertIsInstance(q.tasks[0], task.CheckSchedulerTask)
|
||||
self.assertIsInstance(q.tasks[1], task.DirectoryTask)
|
||||
q.tasks_finished()
|
||||
|
||||
|
||||
class TestScannerUpdateAllButton(unittest.TestCase):
|
||||
def test_init(self):
|
||||
db.reset()
|
||||
lib1 = db.library.Table.find("/a/b/c")
|
||||
lib2 = db.library.Table.find("/d/e/f")
|
||||
q = queue.TaskQueue()
|
||||
uba = widgets.UpdateAllButton(q)
|
||||
|
||||
self.assertIsInstance(uba, Gtk.Button)
|
||||
self.assertIsInstance(uba, widgets.ScannerButton)
|
||||
self.assertEqual(uba.get_icon_name(), "view-refresh")
|
||||
self.assertEqual(uba.queue, q)
|
||||
self.assertTrue(uba.get_hexpand())
|
||||
|
||||
uba.emit("clicked")
|
||||
self.assertEqual(len(q.tasks), 4)
|
||||
self.assertIsInstance(q.tasks[0], task.CheckSchedulerTask)
|
||||
self.assertIsInstance(q.tasks[1], task.DirectoryTask)
|
||||
self.assertIsInstance(q.tasks[2], task.CheckSchedulerTask)
|
||||
self.assertIsInstance(q.tasks[3], task.DirectoryTask)
|
||||
q.tasks_finished()
|
||||
|
||||
|
||||
class TestScannerRemoveButton(unittest.TestCase):
|
||||
def test_init(self):
|
||||
lib = db.library.Table.find("/a/b/c")
|
||||
q = queue.TaskQueue()
|
||||
rb = widgets.RemoveButton(lib, q)
|
||||
|
||||
self.assertIsInstance(rb, Gtk.Button)
|
||||
self.assertIsInstance(rb, widgets.ScannerButton)
|
||||
self.assertEqual(rb.get_icon_name(), "list-remove")
|
||||
self.assertEqual(rb.library, lib)
|
||||
self.assertEqual(rb.queue, q)
|
||||
self.assertTrue(rb.get_hexpand())
|
||||
|
||||
rb.emit("clicked")
|
||||
self.assertEqual(len(q.tasks), 1)
|
||||
self.assertIsInstance(q.tasks[0], task.RemoveLibrarySchedulerTask)
|
||||
q.tasks_finished()
|
||||
|
||||
|
||||
class TestEnableSwitch(unittest.TestCase):
|
||||
def test_init(self):
|
||||
lib = db.library.Table.find("/a/b/c")
|
||||
q = queue.TaskQueue()
|
||||
es = widgets.EnableSwitch(lib, q)
|
||||
|
||||
self.assertIsInstance(es, Gtk.Switch)
|
||||
self.assertEqual(es.library, lib)
|
||||
self.assertEqual(es.queue, q)
|
||||
self.assertEqual(es.get_halign(), Gtk.Align.CENTER)
|
||||
self.assertEqual(es.get_valign(), Gtk.Align.CENTER)
|
||||
self.assertTrue(es.get_active())
|
||||
|
||||
es.set_active(False)
|
||||
self.assertFalse(es.library.enabled)
|
||||
self.assertFalse(widgets.EnableSwitch(lib, q).get_active())
|
||||
|
||||
es.set_active(True)
|
||||
self.assertTrue(lib.enabled)
|
|
@ -1,140 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import db
|
||||
import pathlib
|
||||
from gi.repository import Gio
|
||||
from gi.repository import Gtk
|
||||
from . import task
|
||||
|
||||
class ProgressBar(Gtk.ProgressBar):
|
||||
def __init__(self, queue):
|
||||
Gtk.ProgressBar.__init__(self)
|
||||
|
||||
self.queue = queue
|
||||
self.queue.connect("task-pushed", self.on_task_pushed)
|
||||
self.queue.connect("run-task", self.on_run_task)
|
||||
self.queue.connect("tasks-finished", self.on_tasks_finished)
|
||||
|
||||
self.set_valign(Gtk.Align.CENTER)
|
||||
self.set_hexpand(True)
|
||||
self.set_visible(len(queue.tasks) > 0)
|
||||
|
||||
def on_task_pushed(self, queue, task):
|
||||
self.set_visible(True)
|
||||
|
||||
def on_run_task(self, queue, task):
|
||||
self.set_fraction(queue.progress)
|
||||
|
||||
def on_tasks_finished(self, queue):
|
||||
self.set_visible(False)
|
||||
|
||||
|
||||
class DirectoryChooserWidget(Gtk.FileChooserWidget):
|
||||
def __init__(self):
|
||||
Gtk.FileChooserWidget.__init__(self)
|
||||
self.filter = Gtk.FileFilter()
|
||||
self.filter.add_mime_type("inode/directory")
|
||||
|
||||
music_dir = pathlib.Path("~/Music").expanduser()
|
||||
music_gfile = Gio.File.new_for_path(str(music_dir))
|
||||
|
||||
self.add_shortcut_folder(music_gfile)
|
||||
self.set_action(Gtk.FileChooserAction.SELECT_FOLDER)
|
||||
self.set_create_folders(False)
|
||||
self.set_filter(self.filter)
|
||||
|
||||
def selected_path(self):
|
||||
if (file := self.get_file()) == None:
|
||||
file = self.get_current_folder()
|
||||
return pathlib.Path(file.get_path())
|
||||
|
||||
|
||||
class DirectoryChooserPopover(Gtk.Popover):
|
||||
def __init__(self, queue):
|
||||
Gtk.Popover.__init__(self)
|
||||
self.chooser = DirectoryChooserWidget()
|
||||
self.cancel = Gtk.Button.new_with_label("Cancel")
|
||||
self.select = Gtk.Button.new_with_label("Add Folder")
|
||||
self.queue = queue
|
||||
|
||||
self.cancel.connect("clicked", self.do_cancel)
|
||||
self.select.connect("clicked", self.do_select)
|
||||
|
||||
self.grid = Gtk.Grid()
|
||||
self.grid.attach(self.chooser, 0, 0, 2, 1)
|
||||
self.grid.attach(self.cancel, 0, 1, 1, 1)
|
||||
self.grid.attach(self.select, 1, 1, 1, 1)
|
||||
|
||||
self.select.add_css_class("suggested-action")
|
||||
self.add_css_class("normal-icons")
|
||||
self.set_child(self.grid)
|
||||
|
||||
def do_cancel(self, button):
|
||||
self.popdown()
|
||||
|
||||
def do_select(self, button):
|
||||
if (path := self.chooser.selected_path()) != None:
|
||||
library = db.library.Table.find(path)
|
||||
self.queue.push(task.CheckSchedulerTask(library))
|
||||
self.queue.push(task.DirectoryTask(library, library.path))
|
||||
self.popdown()
|
||||
|
||||
|
||||
class AddFolderButton(Gtk.MenuButton):
|
||||
def __init__(self, queue):
|
||||
Gtk.MenuButton.__init__(self)
|
||||
self.popover = DirectoryChooserPopover(queue)
|
||||
self.set_popover(self.popover)
|
||||
self.set_icon_name("folder-new")
|
||||
self.set_direction(Gtk.ArrowType.UP)
|
||||
|
||||
|
||||
class ScannerButton(Gtk.Button):
|
||||
def __init__(self, queue, icon_name):
|
||||
Gtk.Button.__init__(self)
|
||||
self.set_icon_name(icon_name)
|
||||
self.set_hexpand(True)
|
||||
self.queue = queue
|
||||
|
||||
|
||||
class UpdateButton(ScannerButton):
|
||||
def __init__(self, library, queue):
|
||||
ScannerButton.__init__(self, queue, "view-refresh")
|
||||
self.library = library
|
||||
|
||||
def do_clicked(self):
|
||||
self.queue.push(task.CheckSchedulerTask(self.library))
|
||||
self.queue.push(task.DirectoryTask(self.library, self.library.path))
|
||||
|
||||
|
||||
class UpdateAllButton(ScannerButton):
|
||||
def __init__(self, queue):
|
||||
ScannerButton.__init__(self, queue, "view-refresh")
|
||||
|
||||
def do_clicked(self):
|
||||
for i in range(db.library.Table.get_n_items()):
|
||||
library = db.library.Table.get_item(i)
|
||||
self.queue.push(task.CheckSchedulerTask(library))
|
||||
self.queue.push(task.DirectoryTask(library, library.path))
|
||||
|
||||
|
||||
class RemoveButton(ScannerButton):
|
||||
def __init__(self, library, queue):
|
||||
ScannerButton.__init__(self, queue, "list-remove")
|
||||
self.library = library
|
||||
|
||||
def do_clicked(self):
|
||||
self.queue.push(task.RemoveLibrarySchedulerTask(self.library))
|
||||
|
||||
|
||||
class EnableSwitch(Gtk.Switch):
|
||||
def __init__(self, library, queue):
|
||||
Gtk.Switch.__init__(self)
|
||||
self.set_active(library.enabled)
|
||||
self.set_halign(Gtk.Align.CENTER)
|
||||
self.set_valign(Gtk.Align.CENTER)
|
||||
self.connect("notify::active", self.switched)
|
||||
self.library = library
|
||||
self.queue = queue
|
||||
|
||||
def switched(self, switch, param):
|
||||
self.library.enabled = switch.get_active()
|
|
@ -1,27 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import audio
|
||||
import db
|
||||
from gi.repository import Gtk
|
||||
from . import stack
|
||||
|
||||
class Sidebar(Gtk.Box):
|
||||
def __init__(self, panel):
|
||||
Gtk.Box.__init__(self)
|
||||
self.panel = panel
|
||||
switcher = stack.Switcher()
|
||||
box = stack.Box(switcher.get_stack())
|
||||
|
||||
self.append(switcher)
|
||||
self.append(box)
|
||||
|
||||
stak = switcher.get_stack()
|
||||
stak.connect("playlist-changed", self.playlist_changed)
|
||||
panel.set_playlist(stak.get_visible_child().get_selected_playlist())
|
||||
box.get_add_update_button().connect("add-to-playlist", self.add_to_playlist)
|
||||
|
||||
def playlist_changed(self, stack, plist):
|
||||
self.panel.set_playlist(plist)
|
||||
|
||||
def add_to_playlist(self, button, playlist):
|
||||
if playlist.can_add_remove_tracks():
|
||||
self.panel.add_selected_tracks(playlist)
|
|
@ -1,90 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import db
|
||||
import lib
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gio
|
||||
from gi.repository import Gtk
|
||||
|
||||
class ChildModel(GObject.GObject, Gio.ListModel):
|
||||
def __init__(self, parent):
|
||||
GObject.GObject.__init__(self)
|
||||
self.parent = parent
|
||||
self.parent.connect("children-changed", self.children_changed)
|
||||
|
||||
def do_get_item_type(self): return GObject.TYPE_PYOBJECT
|
||||
def do_get_n_items(self): return self.parent.get_n_children()
|
||||
def do_get_item(self, n): return self.parent.get_child(n)
|
||||
|
||||
def children_changed(self, item, pos, rm, add):
|
||||
self.emit("items-changed", pos, rm, add)
|
||||
|
||||
|
||||
def create_child_func(item):
|
||||
return ChildModel(item) if item.has_children() else None
|
||||
|
||||
def TreeListModel(table):
|
||||
return Gtk.TreeListModel.new(table, passthrough=False, autoexpand=False,
|
||||
create_func=create_child_func)
|
||||
|
||||
|
||||
class Filter(lib.filter.Regex):
|
||||
def do_check(self, item):
|
||||
if self.search(item.name):
|
||||
return True
|
||||
if item.has_children():
|
||||
for n in range(item.get_n_children()):
|
||||
if self.do_check(item.get_child(n)):
|
||||
return True
|
||||
return False
|
||||
|
||||
def do_match(self, treeitem):
|
||||
item = treeitem.get_item()
|
||||
return self.do_check(item) if item else False
|
||||
TableFilter = Filter()
|
||||
|
||||
|
||||
class FilterTableModel(Gtk.FilterListModel):
|
||||
def __init__(self, table):
|
||||
Gtk.FilterListModel.__init__(self)
|
||||
self.set_model(TreeListModel(table))
|
||||
self.set_filter(TableFilter)
|
||||
|
||||
def get_table(self):
|
||||
return self.get_model().get_model()
|
||||
|
||||
|
||||
class TableSelection(Gtk.SingleSelection):
|
||||
def __init__(self, table):
|
||||
Gtk.SingleSelection.__init__(self)
|
||||
self.set_model(FilterTableModel(table))
|
||||
self.set_can_unselect(True)
|
||||
self.set_autoselect(False)
|
||||
|
||||
def get_selected_playlist(self):
|
||||
item = self.get_selected_item()
|
||||
return None if item is None else item.get_item()
|
||||
|
||||
def get_playlist(self, n):
|
||||
row = self.get_item(n)
|
||||
return row.get_item() if row else None
|
||||
|
||||
def get_table(self):
|
||||
return self.get_model().get_table()
|
||||
|
||||
|
||||
class UserFilter(Gtk.Filter):
|
||||
def do_match(self, item):
|
||||
return item.name not in [ "Collection", "New Tracks", "Previous" ]
|
||||
|
||||
|
||||
class FilterUserModel(Gtk.FilterListModel):
|
||||
def __init__(self):
|
||||
Gtk.FilterListModel.__init__(self)
|
||||
self.set_model(db.user.Table)
|
||||
self.set_filter(UserFilter())
|
||||
|
||||
|
||||
class UserSelection(Gtk.SingleSelection):
|
||||
def __init__(self):
|
||||
Gtk.SingleSelection.__init__(self)
|
||||
self.set_model(FilterUserModel())
|
181
sidebar/row.py
181
sidebar/row.py
|
@ -1,181 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import audio
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Pango
|
||||
from . import widgets
|
||||
|
||||
BoldAttr = Pango.AttrList.new()
|
||||
BoldAttr.insert(Pango.attr_weight_new(Pango.Weight.BOLD))
|
||||
|
||||
|
||||
class Label(Gtk.Label):
|
||||
def __init__(self):
|
||||
Gtk.Label.__init__(self)
|
||||
self.set_ellipsize(Pango.EllipsizeMode.MIDDLE)
|
||||
self.set_halign(Gtk.Align.START)
|
||||
self.set_hexpand(True)
|
||||
|
||||
def set_bold(self, newval):
|
||||
self.set_attributes(BoldAttr if newval else None)
|
||||
|
||||
|
||||
class Grid(Gtk.Grid):
|
||||
def __init__(self):
|
||||
Gtk.Grid.__init__(self)
|
||||
self.icon = self.attach(Gtk.Image(), 0, 0, 1, 2)
|
||||
self.name = self.attach(Label(), 1, 0, 1, 1)
|
||||
self.count = self.attach(Label(), 1, 1, 1, 1)
|
||||
self.set_column_spacing(5)
|
||||
self.cbid = None
|
||||
|
||||
def attach(self, widget, x, y, w, h):
|
||||
super().attach(widget, x, y, w, h)
|
||||
return widget
|
||||
|
||||
def plist_changed(self, player, old, new, this):
|
||||
self.name.set_bold(new == this)
|
||||
self.count.set_bold(new == this)
|
||||
|
||||
def set_count(self, item, *args):
|
||||
n = item.get_n_tracks()
|
||||
self.count.set_text(f"{n} Track{'s' if n != 1 else ''}")
|
||||
|
||||
def set_item(self, item):
|
||||
self.icon.set_from_icon_name(item.get_property("icon-name"))
|
||||
self.name.set_text(item.get_property("name"))
|
||||
self.plist_changed(None, None, audio.Player.playlist, item)
|
||||
self.set_count(item)
|
||||
|
||||
item.connect("track-added", self.set_count)
|
||||
item.connect("track-removed", self.set_count)
|
||||
item.connect("refreshed", self.set_count)
|
||||
self.cbid = audio.Player.connect("playlist-changed",
|
||||
self.plist_changed, item)
|
||||
|
||||
def unset_item(self, item):
|
||||
if item:
|
||||
self.cbid = audio.Player.disconnect(self.cbid)
|
||||
item.disconnect_by_func(self.set_count)
|
||||
item.disconnect_by_func(self.set_count)
|
||||
item.disconnect_by_func(self.set_count)
|
||||
|
||||
|
||||
class UserGrid(Grid):
|
||||
def __init__(self):
|
||||
Grid.__init__(self)
|
||||
self.remove = self.attach(Gtk.Button(), 2, 0, 1, 2)
|
||||
self.remove.set_icon_name("list-remove")
|
||||
self.remove.add_css_class("flat")
|
||||
self.remove.set_halign(Gtk.Align.CENTER)
|
||||
self.remove.set_valign(Gtk.Align.CENTER)
|
||||
|
||||
def set_item(self, item):
|
||||
system = [ "Collection", "Favorites", "New Tracks",
|
||||
"Previous", "Queued Tracks" ]
|
||||
super().set_item(item)
|
||||
self.remove.set_visible(item.name not in system)
|
||||
self.remove.connect("clicked", self.clicked, item)
|
||||
|
||||
def unset_item(self, item):
|
||||
self.remove.disconnect_by_func(self.clicked)
|
||||
|
||||
def clicked(self, button, item):
|
||||
item.delete()
|
||||
|
||||
|
||||
class LibraryGrid(Grid):
|
||||
def __init__(self):
|
||||
Grid.__init__(self)
|
||||
self.menu = self.attach(Gtk.MenuButton(), 2, 0, 1, 2)
|
||||
self.menu.add_css_class("flat")
|
||||
self.menu.get_first_child().add_css_class("flat")
|
||||
self.menu.set_direction(Gtk.ArrowType.LEFT)
|
||||
self.menu.set_halign(Gtk.Align.CENTER)
|
||||
self.menu.set_valign(Gtk.Align.CENTER)
|
||||
|
||||
def set_item(self, item):
|
||||
super().set_item(item)
|
||||
self.menu.set_popover(widgets.LibraryPopover(item))
|
||||
|
||||
def unset_item(self, item):
|
||||
super().unset_item(item)
|
||||
self.menu.set_popover(None)
|
||||
|
||||
|
||||
class TreeRow(Gtk.TreeExpander):
|
||||
def __init__(self, child):
|
||||
Gtk.TreeExpander.__init__(self)
|
||||
self.set_child(child)
|
||||
|
||||
def set_item(self, listitem):
|
||||
self.set_list_row(listitem)
|
||||
self.get_child().set_item(listitem.get_item())
|
||||
|
||||
def unset_item(self, listitem):
|
||||
self.get_child().unset_item(listitem.get_item())
|
||||
self.set_list_row(None)
|
||||
|
||||
|
||||
class TreeRowFactory(Gtk.SignalListItemFactory):
|
||||
def __init__(self):
|
||||
Gtk.SignalListItemFactory.__init__(self)
|
||||
self.connect("setup", self.setup)
|
||||
self.connect("bind", self.bind)
|
||||
self.connect("unbind", self.unbind)
|
||||
self.connect("teardown", self.teardown)
|
||||
|
||||
def setup(self, factory, treeitem):
|
||||
treeitem.set_child(TreeRow(Grid()))
|
||||
|
||||
def bind(self, factory, treeitem):
|
||||
treeitem.get_child().set_item(treeitem.get_item())
|
||||
|
||||
def unbind(self, factory, treeitem):
|
||||
treeitem.get_child().unset_item(treeitem.get_item())
|
||||
|
||||
def teardown(self, factory, treeitem):
|
||||
treeitem.set_child(None)
|
||||
Factory = TreeRowFactory()
|
||||
|
||||
|
||||
class UserTreeRowFactory(TreeRowFactory):
|
||||
def setup(self, factory, treeitem):
|
||||
treeitem.set_child(TreeRow(UserGrid()))
|
||||
UserFactory = UserTreeRowFactory()
|
||||
|
||||
|
||||
class LibraryTreeRowFactory(TreeRowFactory):
|
||||
def setup(self, factory, treeitem):
|
||||
treeitem.set_child(TreeRow(LibraryGrid()))
|
||||
LibraryFactory = LibraryTreeRowFactory()
|
||||
|
||||
|
||||
class UserRow(Gtk.Box):
|
||||
def __init__(self):
|
||||
Gtk.Box.__init__(self)
|
||||
self.icon = Gtk.Image()
|
||||
self.name = Label()
|
||||
self.set_spacing(5)
|
||||
self.append(self.icon)
|
||||
self.append(self.name)
|
||||
|
||||
def set_item(self, item):
|
||||
self.icon.set_from_icon_name(item.get_property("icon-name"))
|
||||
self.name.set_text(item.get_property("name"))
|
||||
|
||||
|
||||
class UserRowFactory(Gtk.SignalListItemFactory):
|
||||
def __init__(self):
|
||||
Gtk.SignalListItemFactory.__init__(self)
|
||||
self.connect("setup", self.setup)
|
||||
self.connect("bind", self.bind)
|
||||
self.connect("teardown", self.teardown)
|
||||
|
||||
def setup(self, factory, item):
|
||||
item.set_child(UserRow())
|
||||
|
||||
def bind(self, factory, item):
|
||||
item.get_child().set_item(item.get_item())
|
||||
|
||||
def teardown(self, factory, item):
|
||||
item.set_child(None)
|
|
@ -1,72 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import audio
|
||||
import db
|
||||
import lib
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gtk
|
||||
from . import model
|
||||
from . import view
|
||||
from . import widgets
|
||||
|
||||
class Stack(Gtk.Stack):
|
||||
def __init__(self):
|
||||
Gtk.Stack.__init__(self)
|
||||
self.set_transition_type(Gtk.StackTransitionType.OVER_UP_DOWN)
|
||||
self.set_vexpand(True)
|
||||
|
||||
self.add_page(db.user.Table, "Playlists", "audio-x-generic")
|
||||
self.add_page(db.artist.Table, "Artists", "avatar-default-symbolic")
|
||||
self.add_page(db.genre.Table, "Genres", "emblem-generic")
|
||||
self.add_page(db.decade.Table, "Decades", "x-office-calendar")
|
||||
self.add_page(db.library.Table, "Libraries", "folder-music")
|
||||
|
||||
lib.settings.initialize("sidebar.page", "Playlists")
|
||||
self.set_visible_child_name(lib.settings.get("sidebar.page"))
|
||||
self.connect("notify::visible-child-name", self.change_page)
|
||||
|
||||
def add_page(self, table, name, icon):
|
||||
window = view.TableWindow(table)
|
||||
self.add_named(window, name).set_icon_name(icon)
|
||||
window.get_selection().connect("selection-changed", self.selection_changed)
|
||||
|
||||
def change_page(self, stack, param):
|
||||
lib.settings.set("sidebar.page", self.get_visible_child_name())
|
||||
self.emit("playlist-changed", self.get_visible_child().get_selected_playlist())
|
||||
|
||||
def selection_changed(self, selection, pos, n_items):
|
||||
self.emit("playlist-changed", selection.get_selected_playlist())
|
||||
|
||||
@GObject.Signal(arg_types=(db.playlist.Playlist,))
|
||||
def playlist_changed(self, plist): pass
|
||||
|
||||
|
||||
class Switcher(Gtk.StackSwitcher):
|
||||
def __init__(self):
|
||||
Gtk.StackSwitcher.__init__(self)
|
||||
self.add_css_class("large-icons")
|
||||
self.add_css_class("osd")
|
||||
self.set_orientation(Gtk.Orientation.VERTICAL)
|
||||
self.set_stack(Stack())
|
||||
|
||||
|
||||
class Box(Gtk.Box):
|
||||
def __init__(self, stack):
|
||||
Gtk.Box.__init__(self)
|
||||
self.set_orientation(Gtk.Orientation.VERTICAL)
|
||||
|
||||
self.append(audio.Artwork())
|
||||
self.append(Gtk.Separator.new(Gtk.Orientation.HORIZONTAL))
|
||||
self.append(lib.filter.Entry(model.TableFilter))
|
||||
self.append(Gtk.Separator.new(Gtk.Orientation.HORIZONTAL))
|
||||
self.append(stack)
|
||||
self.append(Gtk.Separator.new(Gtk.Orientation.HORIZONTAL))
|
||||
self.append(widgets.AddUpdateBox())
|
||||
self.append(Gtk.Separator.new(Gtk.Orientation.HORIZONTAL))
|
||||
self.append(widgets.ProgressBar())
|
||||
|
||||
def get_add_update_button(self):
|
||||
child = self.get_first_child()
|
||||
while child:
|
||||
if isinstance(child, widgets.AddUpdateBox):
|
||||
return child.get_first_child()
|
||||
child = child.get_next_sibling()
|
|
@ -1,128 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import datetime
|
||||
import db
|
||||
import lib
|
||||
import unittest
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gio
|
||||
from gi.repository import Gtk
|
||||
from . import model
|
||||
|
||||
class TestChildModel(unittest.TestCase):
|
||||
def items_changed(self, table, pos, rm, add):
|
||||
self.changed = (pos, rm, add)
|
||||
|
||||
def setUp(self):
|
||||
db.reset()
|
||||
|
||||
def test_init(self):
|
||||
artist = db.artist.Table.find("Test Artist", "Test Sort")
|
||||
child = model.ChildModel(artist)
|
||||
|
||||
self.assertIsInstance(child, GObject.GObject)
|
||||
self.assertIsInstance(child, Gio.ListModel)
|
||||
|
||||
self.assertEqual(child.parent, artist)
|
||||
self.assertEqual(child.get_item_type(), GObject.TYPE_PYOBJECT)
|
||||
self.assertEqual(child.get_n_items(), 0)
|
||||
|
||||
def test_children(self):
|
||||
artist = db.artist.Table.find("Test Artist", "Test Sort")
|
||||
child = model.ChildModel(artist)
|
||||
child.connect("items-changed", self.items_changed)
|
||||
|
||||
album = artist.find_album("Test Album", datetime.date.today())
|
||||
self.assertEqual(child.get_n_items(), 1)
|
||||
self.assertEqual(child.get_item(0), album)
|
||||
self.assertEqual(self.changed, (0, 0, 1))
|
||||
|
||||
|
||||
class TestTreeListModel(unittest.TestCase):
|
||||
def test_init(self):
|
||||
tree = model.TreeListModel(db.artist.Table)
|
||||
self.assertIsInstance(tree, Gtk.TreeListModel)
|
||||
self.assertEqual(tree.get_model(), db.artist.Table)
|
||||
self.assertFalse(tree.get_autoexpand())
|
||||
self.assertFalse(tree.get_passthrough())
|
||||
|
||||
def test_create_child_func(self):
|
||||
artist = db.artist.Table.find("Test Artist", "Test Sort")
|
||||
child = model.create_child_func(artist)
|
||||
self.assertEqual(child.parent, artist)
|
||||
|
||||
genre = db.genre.Table.find("Test Genre")
|
||||
self.assertIsNone(model.create_child_func(genre))
|
||||
|
||||
|
||||
class TestFilterTableModel(unittest.TestCase):
|
||||
def tearDown(self):
|
||||
model.TableFilter.set_pattern("")
|
||||
|
||||
def test_init(self):
|
||||
filter = model.FilterTableModel(db.artist.Table)
|
||||
self.assertIsInstance(filter, Gtk.FilterListModel)
|
||||
self.assertIsInstance(filter.get_model(), Gtk.TreeListModel)
|
||||
self.assertEqual(filter.get_table(), db.artist.Table)
|
||||
self.assertEqual(filter.get_filter(), model.TableFilter)
|
||||
|
||||
def test_filter(self):
|
||||
artist = db.artist.Table.find("Test Artist", "Test Sort")
|
||||
album = artist.find_album("Test Album", datetime.date.today())
|
||||
self.assertIsInstance(model.TableFilter, lib.filter.Regex)
|
||||
|
||||
model.TableFilter.set_pattern("Artist")
|
||||
self.assertTrue(model.TableFilter.do_check(artist))
|
||||
model.TableFilter.set_pattern("Album")
|
||||
self.assertTrue(model.TableFilter.do_check(artist))
|
||||
model.TableFilter.set_pattern("Disc")
|
||||
self.assertFalse(model.TableFilter.do_check(artist))
|
||||
|
||||
|
||||
class TestTableSelectionModel(unittest.TestCase):
|
||||
def test_init(self):
|
||||
selection = model.TableSelection(db.artist.Table)
|
||||
self.assertIsInstance(selection, Gtk.SingleSelection)
|
||||
self.assertIsInstance(selection.get_model(), model.FilterTableModel)
|
||||
self.assertEqual(selection.get_table(), db.artist.Table)
|
||||
self.assertTrue(selection.get_can_unselect())
|
||||
self.assertFalse(selection.get_autoselect())
|
||||
|
||||
def test_selected_playlist(self):
|
||||
db.reset()
|
||||
selection = model.TableSelection(db.artist.Table)
|
||||
self.assertIsNone(selection.get_selected_playlist())
|
||||
|
||||
track = db.make_fake_track(1, 1, "Test Track", "/a/b/c/1.ogg")
|
||||
selection.set_selected(0)
|
||||
self.assertEqual(selection.get_selected_playlist(), track.artist)
|
||||
|
||||
def test_get_playlist(self):
|
||||
selection = model.TableSelection(db.artist.Table)
|
||||
track = db.make_fake_track(1, 1, "Test Track", "/a/b/c/1.ogg")
|
||||
self.assertEqual(selection.get_playlist(0), track.artist)
|
||||
self.assertIsNone(selection.get_playlist(1))
|
||||
|
||||
|
||||
class TestFilterUserModel(unittest.TestCase):
|
||||
def test_init(self):
|
||||
filter = model.FilterUserModel()
|
||||
self.assertIsInstance(filter, Gtk.FilterListModel)
|
||||
self.assertIsInstance(filter.get_filter(), model.UserFilter)
|
||||
self.assertEqual(filter.get_model(), db.user.Table)
|
||||
|
||||
def test_filter(self):
|
||||
filter = model.UserFilter()
|
||||
self.assertFalse(filter.do_match(db.user.Table.find("Collection")))
|
||||
self.assertTrue(filter.do_match(db.user.Table.find("Favorites")))
|
||||
self.assertFalse(filter.do_match(db.user.Table.find("New Tracks")))
|
||||
self.assertFalse(filter.do_match(db.user.Table.find("Previous")))
|
||||
self.assertTrue(filter.do_match(db.user.Table.find("Queued Tracks")))
|
||||
self.assertTrue(filter.do_match(db.user.Table.find("Test Playlist")))
|
||||
|
||||
|
||||
class TestUserSelectionModel(unittest.TestCase):
|
||||
def test_init(self):
|
||||
selection = model.UserSelection()
|
||||
self.assertIsInstance(selection, Gtk.SingleSelection)
|
||||
self.assertIsInstance(selection.get_model(), model.FilterUserModel)
|
||||
self.assertEqual(selection.get_selected_item(), db.user.Table.find("Favorites"))
|
|
@ -1,176 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import audio
|
||||
import db
|
||||
import unittest
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Pango
|
||||
from . import row
|
||||
from . import widgets
|
||||
|
||||
class TestLabel(unittest.TestCase):
|
||||
def test_init(self):
|
||||
label = row.Label()
|
||||
self.assertIsInstance(label, Gtk.Label)
|
||||
self.assertEqual(label.get_ellipsize(), Pango.EllipsizeMode.MIDDLE)
|
||||
self.assertEqual(label.get_halign(), Gtk.Align.START)
|
||||
self.assertTrue(label.get_hexpand())
|
||||
|
||||
def test_bold(self):
|
||||
label = row.Label()
|
||||
self.assertIsNone(label.get_attributes())
|
||||
label.set_bold(True)
|
||||
self.assertIsNotNone(label.get_attributes())
|
||||
label.set_bold(False)
|
||||
self.assertIsNone(label.get_attributes())
|
||||
|
||||
|
||||
class TestGrid(unittest.TestCase):
|
||||
def setUp(self): db.reset()
|
||||
|
||||
def test_init(self):
|
||||
grid = row.Grid()
|
||||
self.assertIsInstance(grid, Gtk.Grid)
|
||||
self.assertIsInstance(grid.icon, Gtk.Image)
|
||||
self.assertIsInstance(grid.name, row.Label)
|
||||
self.assertIsInstance(grid.count, row.Label)
|
||||
self.assertEqual(grid.get_column_spacing(), 5)
|
||||
|
||||
def test_attach(self):
|
||||
grid = row.Grid()
|
||||
self.assertEqual(grid.get_child_at(0, 0), grid.icon)
|
||||
self.assertEqual(grid.get_child_at(1, 0), grid.name)
|
||||
self.assertEqual(grid.get_child_at(1, 1), grid.count)
|
||||
|
||||
def test_set_item(self):
|
||||
artist = db.artist.Table.find("Test Artist", "Test Sort")
|
||||
grid = row.Grid()
|
||||
|
||||
grid.set_item(artist)
|
||||
self.assertEqual(grid.icon.get_icon_name(), artist.icon_name)
|
||||
self.assertEqual(grid.name.get_text(), "Test Artist")
|
||||
self.assertEqual(grid.count.get_text(), "0 Tracks")
|
||||
self.assertIsNone(grid.name.get_attributes())
|
||||
self.assertIsNone(grid.count.get_attributes())
|
||||
|
||||
audio.Player.set_playlist(artist)
|
||||
self.assertIsNotNone(grid.name.get_attributes())
|
||||
self.assertIsNotNone(grid.count.get_attributes())
|
||||
|
||||
grid.unset_item(artist)
|
||||
track = db.make_fake_track(1, 1, "Test Track", "/a/b/c/1.ogg")
|
||||
self.assertEqual(grid.count.get_text(), "0 Tracks")
|
||||
|
||||
def test_count_label(self):
|
||||
artist = db.artist.Table.find("Test Artist", "Test Sort")
|
||||
grid = row.Grid()
|
||||
|
||||
grid.set_item(artist)
|
||||
self.assertEqual(grid.count.get_text(), "0 Tracks")
|
||||
track = db.make_fake_track(1, 1, "Test Track", "/a/b/c/1.ogg")
|
||||
self.assertEqual(grid.count.get_text(), "1 Track")
|
||||
|
||||
grid.count.set_text("abcde")
|
||||
artist.refresh()
|
||||
self.assertEqual(grid.count.get_text(), "1 Track")
|
||||
|
||||
db.track.Table.delete(track)
|
||||
artist.remove_track(track, False)
|
||||
self.assertEqual(grid.count.get_text(), "0 Tracks")
|
||||
|
||||
|
||||
class TestUserGrid(unittest.TestCase):
|
||||
def tearDown(self): db.reset()
|
||||
|
||||
def test_init(self):
|
||||
grid = row.UserGrid()
|
||||
self.assertIsInstance(grid, row.Grid)
|
||||
self.assertIsInstance(grid.remove, Gtk.Button)
|
||||
self.assertEqual(grid.get_child_at(2, 0), grid.remove)
|
||||
self.assertEqual(grid.remove.get_icon_name(), "list-remove")
|
||||
self.assertEqual(grid.remove.get_halign(), Gtk.Align.CENTER)
|
||||
self.assertEqual(grid.remove.get_valign(), Gtk.Align.CENTER)
|
||||
self.assertTrue(grid.remove.has_css_class("flat"))
|
||||
|
||||
def test_set_item(self):
|
||||
grid = row.UserGrid()
|
||||
grid.set_item(db.user.Table.find("Collection"))
|
||||
self.assertFalse(grid.remove.get_visible())
|
||||
grid.set_item(db.user.Table.find("Favorites"))
|
||||
self.assertFalse(grid.remove.get_visible())
|
||||
grid.set_item(db.user.Table.find("New Tracks"))
|
||||
self.assertFalse(grid.remove.get_visible())
|
||||
grid.set_item(db.user.Table.find("Previous"))
|
||||
self.assertFalse(grid.remove.get_visible())
|
||||
grid.set_item(db.user.Table.find("Queued Tracks"))
|
||||
self.assertFalse(grid.remove.get_visible())
|
||||
grid.set_item(db.user.Table.find("Test Playlist"))
|
||||
self.assertTrue(grid.remove.get_visible())
|
||||
|
||||
def test_click(self):
|
||||
grid = row.UserGrid()
|
||||
grid.set_item(db.user.Table.find("Test Playlist"))
|
||||
grid.remove.emit("clicked")
|
||||
self.assertIsNone(db.user.Table.lookup("Test Playlist"))
|
||||
|
||||
|
||||
class TestLibraryGrid(unittest.TestCase):
|
||||
def test_init(self):
|
||||
grid = row.LibraryGrid()
|
||||
self.assertIsInstance(grid, row.Grid)
|
||||
self.assertIsInstance(grid.menu, Gtk.MenuButton)
|
||||
self.assertEqual(grid.get_child_at(2, 0), grid.menu)
|
||||
self.assertEqual(grid.menu.get_direction(), Gtk.ArrowType.LEFT)
|
||||
self.assertEqual(grid.menu.get_halign(), Gtk.Align.CENTER)
|
||||
self.assertEqual(grid.menu.get_valign(), Gtk.Align.CENTER)
|
||||
self.assertTrue(grid.menu.has_css_class("flat"))
|
||||
self.assertTrue(grid.menu.get_first_child().has_css_class("flat"))
|
||||
self.assertIsNone(grid.menu.get_popover())
|
||||
|
||||
def test_set_item(self):
|
||||
grid = row.LibraryGrid()
|
||||
grid.set_item(db.library.Table.find("/a/b/c"))
|
||||
self.assertIsInstance(grid.menu.get_popover(), widgets.LibraryPopover)
|
||||
grid.unset_item(db.library.Table.find("/a/b/c"))
|
||||
self.assertIsNone(grid.menu.get_popover())
|
||||
|
||||
|
||||
class TestTreeRow(unittest.TestCase):
|
||||
def test_init(self):
|
||||
tree = row.TreeRow(row.Grid())
|
||||
self.assertIsInstance(tree, Gtk.TreeExpander)
|
||||
self.assertIsInstance(tree.get_child(), row.Grid)
|
||||
|
||||
def test_factory(self):
|
||||
factory = row.TreeRowFactory()
|
||||
self.assertIsInstance(factory, Gtk.SignalListItemFactory)
|
||||
|
||||
def test_user_factory(self):
|
||||
factory = row.UserTreeRowFactory()
|
||||
self.assertIsInstance(factory, Gtk.SignalListItemFactory)
|
||||
self.assertIsInstance(row.Factory, row.TreeRowFactory)
|
||||
|
||||
def test_library_factory(self):
|
||||
factory = row.LibraryTreeRowFactory()
|
||||
self.assertIsInstance(factory, Gtk.SignalListItemFactory)
|
||||
self.assertIsInstance(row.Factory, row.TreeRowFactory)
|
||||
|
||||
|
||||
class TestUserRow(unittest.TestCase):
|
||||
def test_init(self):
|
||||
user = row.UserRow()
|
||||
self.assertIsInstance(user, Gtk.Box)
|
||||
self.assertIsInstance(user.icon, Gtk.Image)
|
||||
self.assertIsInstance(user.name, row.Label)
|
||||
self.assertEqual(user.get_spacing(), 5)
|
||||
self.assertEqual(user.get_first_child(), user.icon)
|
||||
self.assertEqual(user.get_last_child(), user.name)
|
||||
|
||||
def test_set_item(self):
|
||||
user = row.UserRow()
|
||||
user.set_item(db.user.Table.find("Favorites"))
|
||||
self.assertEqual(user.icon.get_icon_name(), "emmental-favorites")
|
||||
self.assertEqual(user.name.get_text(), "Favorites")
|
||||
|
||||
def test_factory(self):
|
||||
factory = row.UserRowFactory()
|
||||
self.assertIsInstance(factory, Gtk.SignalListItemFactory)
|
|
@ -1,30 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import datetime
|
||||
import db
|
||||
import playlist
|
||||
import sidebar
|
||||
import unittest
|
||||
from gi.repository import Gtk
|
||||
|
||||
class TestSidebar(unittest.TestCase):
|
||||
def test_init(self):
|
||||
panel = playlist.Panel()
|
||||
sbar = sidebar.Sidebar(panel)
|
||||
self.assertIsInstance(sbar, Gtk.Box)
|
||||
self.assertEqual(sbar.panel, panel)
|
||||
|
||||
child = sbar.get_first_child()
|
||||
self.assertIsInstance(child, sidebar.stack.Switcher)
|
||||
child = child.get_next_sibling()
|
||||
self.assertIsInstance(child, sidebar.stack.Box)
|
||||
|
||||
def test_change_playlist(self):
|
||||
artist = db.artist.Table.find("Test Artist", "Test Sort")
|
||||
album = artist.find_album("Test Album", datetime.date(2021, 3, 18))
|
||||
panel = playlist.Panel()
|
||||
sbar = sidebar.Sidebar(panel)
|
||||
|
||||
sbar.playlist_changed(None, artist)
|
||||
self.assertEqual(panel.get_playlist(), artist)
|
||||
sbar.playlist_changed(None, album)
|
||||
self.assertEqual(panel.get_playlist(), album)
|
|
@ -1,125 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import audio
|
||||
import db
|
||||
import lib
|
||||
import unittest
|
||||
from gi.repository import Gtk
|
||||
from . import model
|
||||
from . import stack
|
||||
from . import view
|
||||
from . import widgets
|
||||
|
||||
class TestStack(unittest.TestCase):
|
||||
def playlist_changed(self, stack, playlist):
|
||||
self.changed = playlist
|
||||
|
||||
def test_init(self):
|
||||
s = stack.Stack()
|
||||
self.assertIsInstance(s, Gtk.Stack)
|
||||
self.assertTrue(s.get_vexpand())
|
||||
self.assertEqual(s.get_transition_type(),
|
||||
Gtk.StackTransitionType.OVER_UP_DOWN)
|
||||
|
||||
def test_pages(self):
|
||||
s = stack.Stack()
|
||||
|
||||
child = s.get_child_by_name("Playlists")
|
||||
self.assertIsInstance(child, view.TableWindow)
|
||||
self.assertEqual(child.get_table(), db.user.Table)
|
||||
self.assertEqual(s.get_page(child).get_icon_name(), "audio-x-generic")
|
||||
|
||||
child = s.get_child_by_name("Artists")
|
||||
self.assertIsInstance(child, view.TableWindow)
|
||||
self.assertEqual(child.get_table(), db.artist.Table)
|
||||
self.assertEqual(s.get_page(child).get_icon_name(), "avatar-default-symbolic")
|
||||
|
||||
child = s.get_child_by_name("Genres")
|
||||
self.assertIsInstance(child, view.TableWindow)
|
||||
self.assertEqual(child.get_table(), db.genre.Table)
|
||||
self.assertEqual(s.get_page(child).get_icon_name(), "emblem-generic")
|
||||
|
||||
child = s.get_child_by_name("Decades")
|
||||
self.assertIsInstance(child, view.TableWindow)
|
||||
self.assertEqual(child.get_table(), db.decade.Table)
|
||||
self.assertEqual(s.get_page(child).get_icon_name(), "x-office-calendar")
|
||||
|
||||
child = s.get_child_by_name("Libraries")
|
||||
self.assertIsInstance(child, view.TableWindow)
|
||||
self.assertEqual(child.get_table(), db.library.Table)
|
||||
self.assertEqual(s.get_page(child).get_icon_name(), "folder-music")
|
||||
|
||||
def test_change_page(self):
|
||||
lib.settings.reset()
|
||||
genre = db.genre.Table.find("Test Genre")
|
||||
s = stack.Stack()
|
||||
s.connect("playlist-changed", self.playlist_changed)
|
||||
|
||||
self.assertEqual(lib.settings.get("sidebar.page"), "Playlists")
|
||||
self.assertEqual(s.get_visible_child_name(), "Playlists")
|
||||
s.set_visible_child(s.get_child_by_name("Genres"))
|
||||
self.assertEqual(lib.settings.get("sidebar.page"), "Genres")
|
||||
self.assertEqual(self.changed, genre)
|
||||
|
||||
s2 = stack.Stack()
|
||||
self.assertEqual(s2.get_visible_child_name(), "Genres")
|
||||
|
||||
def test_change_selected(self):
|
||||
g1 = db.genre.Table.find("Test Genre")
|
||||
g2 = db.genre.Table.find("Test Genre 2")
|
||||
s = stack.Stack()
|
||||
|
||||
child = s.get_child_by_name("Genres")
|
||||
s.set_visible_child(child)
|
||||
s.connect("playlist-changed", self.playlist_changed)
|
||||
|
||||
child.get_selection().set_selected(1)
|
||||
self.assertEqual(self.changed, g2)
|
||||
child.get_selection().set_selected(0)
|
||||
self.assertEqual(self.changed, g1)
|
||||
|
||||
|
||||
class TestSwitcher(unittest.TestCase):
|
||||
def test_init(self):
|
||||
switcher = stack.Switcher()
|
||||
self.assertIsInstance(switcher, Gtk.StackSwitcher)
|
||||
self.assertIsInstance(switcher.get_stack(), stack.Stack)
|
||||
self.assertEqual(switcher.get_orientation(), Gtk.Orientation.VERTICAL)
|
||||
self.assertTrue(switcher.has_css_class("large-icons"))
|
||||
self.assertTrue(switcher.has_css_class("osd"))
|
||||
|
||||
|
||||
class TestBox(unittest.TestCase):
|
||||
def test_init(self):
|
||||
s = stack.Stack()
|
||||
box = stack.Box(s)
|
||||
self.assertIsInstance(box, Gtk.Box)
|
||||
self.assertEqual(box.get_orientation(), Gtk.Orientation.VERTICAL)
|
||||
|
||||
child = box.get_first_child()
|
||||
self.assertIsInstance(child, audio.artwork.Artwork)
|
||||
|
||||
child = child.get_next_sibling()
|
||||
self.assertIsInstance(child, Gtk.Separator)
|
||||
|
||||
child = child.get_next_sibling()
|
||||
self.assertIsInstance(child, lib.filter.Entry)
|
||||
self.assertEqual(child.filter, model.TableFilter)
|
||||
|
||||
child = child.get_next_sibling()
|
||||
self.assertIsInstance(child, Gtk.Separator)
|
||||
|
||||
child = child.get_next_sibling()
|
||||
self.assertEqual(child, s)
|
||||
|
||||
child = child.get_next_sibling()
|
||||
self.assertIsInstance(child, Gtk.Separator)
|
||||
|
||||
child = child.get_next_sibling()
|
||||
self.assertIsInstance(child, widgets.AddUpdateBox)
|
||||
self.assertEqual(box.get_add_update_button(), child.get_first_child())
|
||||
|
||||
child = child.get_next_sibling()
|
||||
self.assertIsInstance(child, Gtk.Separator)
|
||||
|
||||
child = child.get_next_sibling()
|
||||
self.assertIsInstance(child, widgets.ProgressBar)
|
|
@ -1,60 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import db
|
||||
import unittest
|
||||
from gi.repository import Gtk
|
||||
from . import model
|
||||
from . import row
|
||||
from . import view
|
||||
|
||||
class TestTableView(unittest.TestCase):
|
||||
def test_init(self):
|
||||
table = view.TableView(db.artist.Table)
|
||||
self.assertIsInstance(table, Gtk.ListView)
|
||||
self.assertIsInstance(table.get_model(), model.TableSelection)
|
||||
self.assertEqual(table.get_table(), db.artist.Table)
|
||||
|
||||
self.assertEqual(table.get_factory(), row.Factory)
|
||||
self.assertTrue(table.has_css_class("normal-icons"))
|
||||
self.assertTrue(table.get_vexpand())
|
||||
|
||||
def test_selection(self):
|
||||
artist = db.artist.Table.find("Test Artist", "Test Sort")
|
||||
table = view.TableView(db.artist.Table)
|
||||
self.assertEqual(table.get_selection(), table.get_model())
|
||||
self.assertEqual(table.get_selected_playlist(), artist)
|
||||
|
||||
def test_factory(self):
|
||||
table = view.TableView(db.user.Table)
|
||||
self.assertEqual(table.get_factory(), row.UserFactory)
|
||||
table = view.TableView(db.artist.Table)
|
||||
self.assertEqual(table.get_factory(), row.Factory)
|
||||
table = view.TableView(db.genre.Table)
|
||||
self.assertEqual(table.get_factory(), row.Factory)
|
||||
table = view.TableView(db.decade.Table)
|
||||
self.assertEqual(table.get_factory(), row.Factory)
|
||||
table = view.TableView(db.library.Table)
|
||||
self.assertEqual(table.get_factory(), row.LibraryFactory)
|
||||
|
||||
|
||||
class TestTableWindow(unittest.TestCase):
|
||||
def test_init(self):
|
||||
window = view.TableWindow(db.artist.Table)
|
||||
self.assertIsInstance(window, Gtk.ScrolledWindow)
|
||||
self.assertIsInstance(window.get_child(), view.TableView)
|
||||
self.assertEqual(window.get_table(), db.artist.Table)
|
||||
|
||||
def test_selection(self):
|
||||
artist = db.artist.Table.find("Test Artist", "Test Sort")
|
||||
window = view.TableWindow(db.artist.Table)
|
||||
self.assertEqual(window.get_selection(), window.get_child().get_selection())
|
||||
self.assertEqual(window.get_selected_playlist(), artist)
|
||||
|
||||
|
||||
class TestUserView(unittest.TestCase):
|
||||
def test_init(self):
|
||||
user = view.UserView()
|
||||
self.assertIsInstance(user, Gtk.ListView)
|
||||
self.assertIsInstance(user.get_model(), model.UserSelection)
|
||||
self.assertIsInstance(user.get_factory(), row.UserRowFactory)
|
||||
self.assertTrue(user.has_css_class("normal-icons"))
|
||||
self.assertTrue(user.get_single_click_activate())
|
|
@ -1,143 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import db
|
||||
import scanner
|
||||
import unittest
|
||||
from gi.repository import Gtk
|
||||
from . import view
|
||||
from . import widgets
|
||||
|
||||
class TestLibraryButtons(unittest.TestCase):
|
||||
def test_init(self):
|
||||
library = db.library.Table.find("/a/b/c")
|
||||
buttons = widgets.LibraryButtons(library)
|
||||
self.assertIsInstance(buttons, Gtk.Box)
|
||||
self.assertTrue(buttons.has_css_class("linked"))
|
||||
|
||||
child = buttons.get_first_child()
|
||||
self.assertIsInstance(child, scanner.widgets.RemoveButton)
|
||||
child = child.get_next_sibling()
|
||||
self.assertIsInstance(child, scanner.widgets.UpdateButton)
|
||||
|
||||
|
||||
class TestLibraryPopover(unittest.TestCase):
|
||||
def test_init(self):
|
||||
library = db.library.Table.find("/a/b/c")
|
||||
popover = widgets.LibraryPopover(library)
|
||||
self.assertIsInstance(popover, Gtk.Popover)
|
||||
|
||||
box = popover.get_child()
|
||||
self.assertIsInstance(box, Gtk.Box)
|
||||
self.assertEqual(box.get_spacing(), 10)
|
||||
|
||||
child = box.get_first_child()
|
||||
self.assertIsInstance(child, widgets.LibraryButtons)
|
||||
child = child.get_next_sibling()
|
||||
self.assertIsInstance(child, scanner.widgets.EnableSwitch)
|
||||
|
||||
|
||||
class TestProgressBar(unittest.TestCase):
|
||||
def test_init(self):
|
||||
pbar = widgets.ProgressBar()
|
||||
self.assertIsInstance(pbar, Gtk.Box)
|
||||
|
||||
child = pbar.get_first_child()
|
||||
self.assertIsInstance(child, Gtk.Label)
|
||||
self.assertEqual(child.get_text(), " ")
|
||||
|
||||
child = child.get_next_sibling()
|
||||
self.assertIsInstance(child, scanner.widgets.ProgressBar)
|
||||
|
||||
child = child.get_next_sibling()
|
||||
self.assertIsInstance(child, Gtk.Label)
|
||||
self.assertEqual(child.get_text(), " ")
|
||||
|
||||
|
||||
class TestAddPlaylistEntry(unittest.TestCase):
|
||||
def activated_playlist(self, entry, playlist):
|
||||
self.playlist = playlist
|
||||
|
||||
def test_init(self):
|
||||
entry = widgets.AddPlaylistEntry()
|
||||
self.assertIsInstance(entry, Gtk.Entry)
|
||||
self.assertEqual(entry.get_placeholder_text(), "Add new playlist")
|
||||
self.assertEqual(entry.get_icon_name(Gtk.EntryIconPosition.PRIMARY),
|
||||
"list-add")
|
||||
|
||||
def test_clear(self):
|
||||
entry = widgets.AddPlaylistEntry()
|
||||
self.assertIsNone(entry.get_icon_name(Gtk.EntryIconPosition.SECONDARY))
|
||||
entry.set_text("Test")
|
||||
self.assertEqual(entry.get_icon_name(Gtk.EntryIconPosition.SECONDARY),
|
||||
"edit-clear-symbolic")
|
||||
entry.icon_released(entry, Gtk.EntryIconPosition.SECONDARY)
|
||||
self.assertEqual(entry.get_text(), "")
|
||||
|
||||
def test_activate(self):
|
||||
entry = widgets.AddPlaylistEntry()
|
||||
entry.connect("activated-playlist", self.activated_playlist)
|
||||
entry.set_text("Test Playlist")
|
||||
self.assertIsNone(db.user.Table.lookup("Test Playlist"))
|
||||
|
||||
entry.icon_released(entry, Gtk.EntryIconPosition.PRIMARY)
|
||||
self.assertEqual(entry.get_text(), "")
|
||||
self.assertEqual(self.playlist, db.user.Table.lookup("Test Playlist"))
|
||||
self.assertIsNotNone(self.playlist)
|
||||
|
||||
entry.icon_released(entry, Gtk.EntryIconPosition.PRIMARY)
|
||||
self.assertIsNone(db.user.Table.lookup(""))
|
||||
|
||||
|
||||
class TestAddPlaylistPopover(unittest.TestCase):
|
||||
def test_init(self):
|
||||
popover = widgets.AddPlaylistPopover()
|
||||
self.assertIsInstance(popover, Gtk.Popover)
|
||||
self.assertIsInstance(popover.box, Gtk.Box)
|
||||
self.assertEqual(popover.get_child(), popover.box)
|
||||
self.assertEqual(popover.box.get_spacing(), 5)
|
||||
self.assertEqual(popover.box.get_orientation(),
|
||||
Gtk.Orientation.VERTICAL)
|
||||
self.assertEqual(popover.box.get_focus_child(),
|
||||
popover.box.get_last_child())
|
||||
self.assertTrue(popover.has_css_class("normal-icons"))
|
||||
|
||||
def test_children(self):
|
||||
popover = widgets.AddPlaylistPopover()
|
||||
child = popover.box.get_first_child()
|
||||
self.assertIsInstance(child, view.UserView)
|
||||
self.assertEqual(child, popover.get_view())
|
||||
|
||||
child = child.get_next_sibling()
|
||||
self.assertIsInstance(child, widgets.AddPlaylistEntry)
|
||||
self.assertEqual(child, popover.get_entry())
|
||||
|
||||
|
||||
class TestAddPlaylistButton(unittest.TestCase):
|
||||
def test_init(self):
|
||||
add = widgets.AddPlaylistButton()
|
||||
self.assertIsInstance(add, Gtk.MenuButton)
|
||||
self.assertIsInstance(add.get_popover(), widgets.AddPlaylistPopover)
|
||||
self.assertEqual(add.get_icon_name(), "list-add")
|
||||
self.assertEqual(add.get_direction(), Gtk.ArrowType.UP)
|
||||
|
||||
|
||||
class TestAddUpdateBox(unittest.TestCase):
|
||||
def test_init(self):
|
||||
box = widgets.AddUpdateBox()
|
||||
self.assertIsInstance(box, Gtk.Box)
|
||||
self.assertTrue(box.has_css_class("large-icons"))
|
||||
self.assertTrue(box.has_css_class("linked"))
|
||||
self.assertTrue(box.get_homogeneous())
|
||||
|
||||
self.assertEqual(box.get_margin_top(), 5)
|
||||
self.assertEqual(box.get_margin_bottom(), 5)
|
||||
self.assertEqual(box.get_margin_start(), 5)
|
||||
self.assertEqual(box.get_margin_end(), 5)
|
||||
|
||||
def test_children(self):
|
||||
box = widgets.AddUpdateBox()
|
||||
child = box.get_first_child()
|
||||
self.assertIsInstance(child, widgets.AddPlaylistButton)
|
||||
child = child.get_next_sibling()
|
||||
self.assertIsInstance(child, scanner.widgets.AddFolderButton)
|
||||
child = child.get_next_sibling()
|
||||
self.assertIsInstance(child, scanner.widgets.UpdateAllButton)
|
|
@ -1,57 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import audio
|
||||
import db
|
||||
from gi.repository import Gtk
|
||||
from . import model
|
||||
from . import row
|
||||
|
||||
class TableView(Gtk.ListView):
|
||||
def __init__(self, table):
|
||||
Gtk.ListView.__init__(self)
|
||||
self.add_css_class("normal-icons")
|
||||
self.set_vexpand(True)
|
||||
self.set_model(model.TableSelection(table))
|
||||
self.connect("activate", self.activated)
|
||||
|
||||
if table == db.user.Table:
|
||||
self.set_factory(row.UserFactory)
|
||||
elif table == db.library.Table:
|
||||
self.set_factory(row.LibraryFactory)
|
||||
else:
|
||||
self.set_factory(row.Factory)
|
||||
|
||||
def activated(self, view, position):
|
||||
audio.Player.set_playlist(self.get_model().get_playlist(position))
|
||||
|
||||
def get_selection(self):
|
||||
return self.get_model()
|
||||
|
||||
def get_selected_playlist(self):
|
||||
return self.get_model().get_selected_playlist()
|
||||
|
||||
def get_table(self):
|
||||
return self.get_model().get_table()
|
||||
|
||||
|
||||
class TableWindow(Gtk.ScrolledWindow):
|
||||
def __init__(self, table):
|
||||
Gtk.ScrolledWindow.__init__(self)
|
||||
self.set_child(TableView(table))
|
||||
|
||||
def get_selection(self):
|
||||
return self.get_child().get_selection()
|
||||
|
||||
def get_selected_playlist(self):
|
||||
return self.get_child().get_selected_playlist()
|
||||
|
||||
def get_table(self):
|
||||
return self.get_child().get_table()
|
||||
|
||||
|
||||
class UserView(Gtk.ListView):
|
||||
def __init__(self):
|
||||
Gtk.ListView.__init__(self)
|
||||
self.set_model(model.UserSelection())
|
||||
self.set_factory(row.UserRowFactory())
|
||||
self.add_css_class("normal-icons")
|
||||
self.set_single_click_activate(True)
|
|
@ -1,108 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import db
|
||||
import scanner
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import GObject
|
||||
from . import view
|
||||
|
||||
class LibraryButtons(Gtk.Box):
|
||||
def __init__(self, library):
|
||||
Gtk.Box.__init__(self)
|
||||
self.append(scanner.RemoveButton(library))
|
||||
self.append(scanner.UpdateButton(library))
|
||||
self.add_css_class("linked")
|
||||
|
||||
|
||||
class LibraryPopover(Gtk.Popover):
|
||||
def __init__(self, library):
|
||||
Gtk.Popover.__init__(self)
|
||||
box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 10)
|
||||
box.append(LibraryButtons(library))
|
||||
box.append(scanner.EnableSwitch(library))
|
||||
self.set_child(box)
|
||||
|
||||
|
||||
class ProgressBar(Gtk.Box):
|
||||
def __init__(self):
|
||||
Gtk.Box.__init__(self)
|
||||
self.append(Gtk.Label.new(" "))
|
||||
self.append(scanner.ProgressBar())
|
||||
self.append(Gtk.Label.new(" "))
|
||||
|
||||
|
||||
class AddPlaylistEntry(Gtk.Entry):
|
||||
def __init__(self):
|
||||
Gtk.Entry.__init__(self)
|
||||
self.set_placeholder_text("Add new playlist")
|
||||
self.set_icon_from_icon_name(Gtk.EntryIconPosition.PRIMARY, "list-add")
|
||||
self.connect("icon-release", self.icon_released)
|
||||
|
||||
def icon_released(self, entry, pos):
|
||||
if pos == Gtk.EntryIconPosition.SECONDARY:
|
||||
self.set_text("")
|
||||
else:
|
||||
self.emit("activate")
|
||||
|
||||
def do_activate(self):
|
||||
if self.get_text() != "":
|
||||
self.emit("activated-playlist", db.user.Table.find(self.get_text()))
|
||||
scanner.commit()
|
||||
self.set_text("")
|
||||
|
||||
def do_changed(self):
|
||||
icon = None if self.get_text() == "" else "edit-clear-symbolic"
|
||||
self.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon)
|
||||
|
||||
@GObject.Signal(arg_types=(db.playlist.Playlist,))
|
||||
def activated_playlist(self, playlist): pass
|
||||
|
||||
|
||||
class AddPlaylistPopover(Gtk.Popover):
|
||||
def __init__(self):
|
||||
Gtk.Popover.__init__(self)
|
||||
self.add_css_class("normal-icons")
|
||||
self.box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 5)
|
||||
self.box.append(view.UserView())
|
||||
self.box.append(AddPlaylistEntry())
|
||||
self.box.set_focus_child(self.box.get_last_child())
|
||||
self.set_child(self.box)
|
||||
|
||||
def get_view(self): return self.box.get_first_child()
|
||||
def get_entry(self): return self.box.get_last_child()
|
||||
|
||||
|
||||
class AddPlaylistButton(Gtk.MenuButton):
|
||||
def __init__(self):
|
||||
Gtk.MenuButton.__init__(self)
|
||||
self.set_icon_name("list-add")
|
||||
self.set_direction(Gtk.ArrowType.UP)
|
||||
self.set_popover(AddPlaylistPopover())
|
||||
|
||||
self.get_popover().get_view().connect("activate", self.view_activate)
|
||||
self.get_popover().get_entry().connect("activated-playlist", self.entry_activate)
|
||||
|
||||
def entry_activate(self, entry, playlist):
|
||||
self.emit("add-to-playlist", playlist)
|
||||
|
||||
def view_activate(self, view, position):
|
||||
self.emit("add-to-playlist", view.get_model().get_item(position))
|
||||
|
||||
@GObject.Signal(arg_types=(db.playlist.Playlist,))
|
||||
def add_to_playlist(self, playlist): pass
|
||||
|
||||
|
||||
class AddUpdateBox(Gtk.Box):
|
||||
def __init__(self):
|
||||
Gtk.Box.__init__(self)
|
||||
self.add_css_class("large-icons")
|
||||
self.add_css_class("linked")
|
||||
self.set_homogeneous(True)
|
||||
|
||||
self.set_margin_top(5)
|
||||
self.set_margin_bottom(5)
|
||||
self.set_margin_start(5)
|
||||
self.set_margin_end(5)
|
||||
|
||||
self.append(AddPlaylistButton())
|
||||
self.append(scanner.AddFolderButton())
|
||||
self.append(scanner.UpdateAllButton())
|
|
@ -1,96 +0,0 @@
|
|||
# Copyright 2019 (c) Anna Schumaker.
|
||||
import base64
|
||||
import mutagen
|
||||
import mutagen.flac
|
||||
import mutagen.id3
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
data = os.path.abspath("data")
|
||||
ffmpeg = "ffmpeg -hide_banner -nostdin -f s16le -i /dev/zero -codec libvorbis -loglevel warning".split()
|
||||
|
||||
image = mutagen.flac.Picture()
|
||||
image.data = open("data/emmental.png", "rb").read()
|
||||
image.type = mutagen.id3.PictureType.COVER_FRONT
|
||||
image.mime = u"image/png"
|
||||
image.width = 512
|
||||
image.height = 512
|
||||
image.depth = 16
|
||||
encoded_data = base64.b64encode(image.write())
|
||||
image_data = encoded_data.decode("ascii")
|
||||
|
||||
|
||||
def generate_track(length, filename, tags={}):
|
||||
path = os.path.join(data, filename)
|
||||
if os.path.exists(path):
|
||||
return
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
subprocess.run(ffmpeg + [ "-t", str(length), path ])
|
||||
|
||||
fileinfo = mutagen.File(path)
|
||||
for (key, value) in tags.items():
|
||||
fileinfo[key] = value
|
||||
fileinfo["metadata_block_picture"] = [ image_data ]
|
||||
fileinfo.save()
|
||||
|
||||
# Create a bunch of tracks in the Test Album directory
|
||||
generate_track( 0, "Test Album/00 - Empty Track.ogg")
|
||||
generate_track(10, "Test Album/01 - Test Track.ogg", { "title" : "Test Track",
|
||||
"artist" : "Test Artist",
|
||||
"artistsort" : "Artist, Test",
|
||||
"album" : "Test Album",
|
||||
"genre" : "Test",
|
||||
"date" : "2019-02",
|
||||
"tracknumber" : "1",
|
||||
"tracktotal" : "12",
|
||||
"discnumber" : "1" })
|
||||
generate_track(15, "Test Album/02 - Test {Disc 2}.ogg", { "Title" : "Test {Disc 2}",
|
||||
"albumartist" : "Test Album Artist",
|
||||
"albumartistsort" : "Album Artist, Test",
|
||||
"artist" : "Test Artist",
|
||||
"artistsort" : "Artist, Test",
|
||||
"album" : "Test Album {Disc 2}",
|
||||
"discsubtitle" : "Electric Boogaloo",
|
||||
"genre" : "Test, Genre, List",
|
||||
"originalyear" : "2019",
|
||||
"date" : "2020-10-18"})
|
||||
generate_track(20, "Test Album/03 - Test [Disk One].ogg", { "Title" : "Test [Disk One]",
|
||||
"album" : "Test Album [Disk One]",
|
||||
"discnumber" : "2"})
|
||||
generate_track(25, "Test Album/04 - Test (Disc Two).ogg", { "Title" : "Test (Disc Two)",
|
||||
"album" : "Test Album (Disc Two)" })
|
||||
generate_track(30, "Test Album/05 - Test - Disc Three.ogg", { "Title" : "Test - Disc Three",
|
||||
"album" : "Test - Disc Three" })
|
||||
generate_track(35, "Test Album/06 - Test;CD Four.ogg", { "Title" : "Test;CD Four",
|
||||
"album" : "Test;CD Four" })
|
||||
generate_track(40, "Test Album/07 - Test;CdFive.ogg", { "Title" : "Test;CdFive",
|
||||
"album" : "Test;CdFive" })
|
||||
generate_track(45, "Test Album/08 - Test CD 6_10.ogg", { "Title" : "Test CD 6/10",
|
||||
"album" : "Test CD 6/10" })
|
||||
generate_track(50, "Test Album/09 - Test {Disc 02}.ogg", { "Title" : "Test {Disc 02}",
|
||||
"album" : "Test Album {Disc 02}" })
|
||||
generate_track(55, "Test Album/10 - Test {Disc 20}.ogg", { "Title" : "Test {Disc 20}",
|
||||
"album" : "Test Album {Disc 20}" })
|
||||
generate_track(60, "Test Album/11 - Test Track 11.ogg", { "Title" : "Test Track 11",
|
||||
"album" : "Test Album 11",
|
||||
"discnumber" : "1",
|
||||
"tracknumber" : "11" })
|
||||
with open(os.path.join(data, "Test Album/text.txt"), 'w') as f:
|
||||
f.write("Test Text")
|
||||
os.makedirs("data/Test Album/Test Subdir", exist_ok=True)
|
||||
|
||||
# Create a giant library for testing
|
||||
for artistno in range(1, 26):
|
||||
artist = f"Test Artist {artistno:02}"
|
||||
for albumno in range(1, 6):
|
||||
album = f"Test Album {albumno}"
|
||||
for trackno in range(1, 11):
|
||||
title = f"Test Track {trackno:02}"
|
||||
genre = f"Test Genre {albumno}"
|
||||
generate_track(trackno, f"Test Library/{artist}/{album}/{trackno:02} - {title}.ogg",
|
||||
{ "title" : title,
|
||||
"artist" : artist,
|
||||
"album" : album,
|
||||
"genre" : genre,
|
||||
"date" : str(1970 + (albumno * 3)),
|
||||
"tracknumber" : f"{trackno:02}" })
|
|
@ -1,3 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
from . import icons
|
||||
from . import window
|
13
ui/icons.py
13
ui/icons.py
|
@ -1,13 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
from . import window
|
||||
from gi.repository import Gtk
|
||||
import pathlib
|
||||
|
||||
IconPath = pathlib.Path("data/").absolute()
|
||||
|
||||
if __debug__ == True:
|
||||
Display = Gtk.Label().get_display()
|
||||
Theme = Gtk.IconTheme.get_for_display(Display)
|
||||
|
||||
paths = Theme.get_search_path()
|
||||
Theme.set_search_path([ str(IconPath) ] + paths)
|
|
@ -1,37 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
from gi.repository import Gtk, Gdk
|
||||
import audio
|
||||
|
||||
Event = Gtk.EventControllerKey()
|
||||
Event.set_propagation_phase(Gtk.PropagationPhase.CAPTURE)
|
||||
|
||||
Shortcuts = dict()
|
||||
|
||||
def initialize():
|
||||
Shortcuts.clear()
|
||||
Shortcuts.update({
|
||||
"Return" : audio.Player.next,
|
||||
"BackSpace" : audio.Player.previous,
|
||||
"space" : audio.Player.playpause,
|
||||
"plus" : audio.Player.Autopause.increment,
|
||||
"KP_Add" : audio.Player.Autopause.increment,
|
||||
"minus" : audio.Player.Autopause.decrement,
|
||||
"KP_Subtract" : audio.Player.Autopause.decrement,
|
||||
})
|
||||
initialize()
|
||||
|
||||
|
||||
def on_key_released(controller, keyval, keycode, state):
|
||||
window = Event.get_widget()
|
||||
name = Gdk.keyval_name(keyval)
|
||||
if name == "Escape":
|
||||
window.set_focus(None)
|
||||
elif not isinstance(window.get_focus(), Gtk.Text):
|
||||
if func := Shortcuts.get(name):
|
||||
func()
|
||||
return True
|
||||
#else:
|
||||
# print(name)
|
||||
return False
|
||||
|
||||
Event.connect("key-released", on_key_released)
|
22
ui/pane.py
22
ui/pane.py
|
@ -1,22 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import lib
|
||||
import playlist
|
||||
import sidebar
|
||||
from gi.repository import Gtk
|
||||
|
||||
class Pane(Gtk.Paned):
|
||||
def __init__(self):
|
||||
Gtk.Paned.__init__(self)
|
||||
panel = playlist.Panel()
|
||||
|
||||
self.set_shrink_start_child(False)
|
||||
self.set_start_child(sidebar.Sidebar(panel))
|
||||
self.set_end_child(panel)
|
||||
self.set_vexpand(True)
|
||||
|
||||
lib.settings.initialize("sidebar.width", 250)
|
||||
self.set_position(lib.settings.get_int("sidebar.width"))
|
||||
self.connect("notify::position", self.change_position)
|
||||
|
||||
def change_position(self, pane, param):
|
||||
lib.settings.set("sidebar.width", self.get_position())
|
|
@ -1,21 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
from . import icons
|
||||
from . import window
|
||||
from gi.repository import Gdk, Gtk
|
||||
import pathlib
|
||||
import unittest
|
||||
|
||||
Path = pathlib.Path("./data").absolute()
|
||||
|
||||
class TestUIIcons(unittest.TestCase):
|
||||
def test_icons(self):
|
||||
self.assertIsInstance(icons.Display, Gdk.Display)
|
||||
self.assertIsInstance(icons.Theme, Gtk.IconTheme)
|
||||
|
||||
self.assertEqual(icons.Display, Gtk.Image().get_display())
|
||||
self.assertEqual(icons.Theme, Gtk.IconTheme.get_for_display(icons.Display))
|
||||
self.assertEqual(icons.IconPath, Path)
|
||||
|
||||
self.assertIn(str(Path), icons.Theme.get_search_path())
|
||||
self.assertTrue(icons.Theme.has_icon("emmental"))
|
||||
self.assertTrue(icons.Theme.has_icon("emmental-favorites"))
|
|
@ -1,19 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
from . import keyboard
|
||||
from gi.repository import Gtk
|
||||
import audio
|
||||
import unittest
|
||||
|
||||
class TestUIKeyboard(unittest.TestCase):
|
||||
def setUp(self):
|
||||
keyboard.initialize()
|
||||
|
||||
def test_keyboard_init(self):
|
||||
self.assertIsInstance(keyboard.Event, Gtk.EventControllerKey)
|
||||
|
||||
self.assertEqual(keyboard.Shortcuts["Return"], audio.Player.next)
|
||||
self.assertEqual(keyboard.Shortcuts["BackSpace"], audio.Player.previous)
|
||||
self.assertEqual(keyboard.Shortcuts["plus"], audio.Player.Autopause.increment)
|
||||
self.assertEqual(keyboard.Shortcuts["KP_Add"], audio.Player.Autopause.increment)
|
||||
self.assertEqual(keyboard.Shortcuts["minus"], audio.Player.Autopause.decrement)
|
||||
self.assertEqual(keyboard.Shortcuts["KP_Subtract"], audio.Player.Autopause.decrement)
|
|
@ -1,29 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import lib
|
||||
import playlist
|
||||
import sidebar
|
||||
import unittest
|
||||
from gi.repository import Gtk
|
||||
from . import pane
|
||||
|
||||
class TestPane(unittest.TestCase):
|
||||
def setUp(self):
|
||||
lib.settings.reset()
|
||||
|
||||
def test_init(self):
|
||||
paned = pane.Pane()
|
||||
|
||||
self.assertIsInstance(paned, Gtk.Paned)
|
||||
self.assertIsInstance(paned.get_start_child(), sidebar.Sidebar)
|
||||
self.assertIsInstance(paned.get_end_child(), playlist.Panel)
|
||||
self.assertEqual(paned.get_orientation(), Gtk.Orientation.HORIZONTAL)
|
||||
self.assertFalse(paned.get_shrink_start_child())
|
||||
self.assertTrue(paned.get_vexpand())
|
||||
|
||||
def test_position(self):
|
||||
paned = pane.Pane()
|
||||
self.assertEqual(paned.get_position(), 250)
|
||||
self.assertEqual(lib.settings.get_int("sidebar.width"), 250)
|
||||
|
||||
paned.set_position(100)
|
||||
self.assertEqual(lib.settings.get_int("sidebar.width"), 100)
|
|
@ -1,51 +0,0 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import audio
|
||||
import lib
|
||||
import sidebar
|
||||
import unittest
|
||||
from gi.repository import Gtk
|
||||
from . import keyboard
|
||||
from . import pane
|
||||
from . import window
|
||||
|
||||
class TestWindow(unittest.TestCase):
|
||||
def setUp(self):
|
||||
lib.settings.reset()
|
||||
|
||||
def test_init(self):
|
||||
win = window.Window()
|
||||
self.assertIsInstance(win, Gtk.ApplicationWindow)
|
||||
self.assertIsInstance(win.get_titlebar(), audio.Header)
|
||||
self.assertIsInstance(win.get_child(), pane.Pane)
|
||||
|
||||
self.assertEqual(win.get_icon_name(), "emmental")
|
||||
self.assertEqual(win.get_title(), lib.version.string())
|
||||
self.assertEqual(keyboard.Event.get_widget(), win)
|
||||
win.remove_controller(keyboard.Event)
|
||||
|
||||
def test_size(self):
|
||||
win = window.Window()
|
||||
self.assertEqual(lib.settings.get_int("window.width"), 1400)
|
||||
self.assertEqual(lib.settings.get_int("window.height"), 800)
|
||||
self.assertEqual(win.get_default_size(), (1400, 800))
|
||||
|
||||
win.set_default_size(1000, 500)
|
||||
self.assertEqual(lib.settings.get_int("window.width"), 1000)
|
||||
self.assertEqual(lib.settings.get_int("window.height"), 500)
|
||||
win.remove_controller(keyboard.Event)
|
||||
|
||||
def test_maximize(self):
|
||||
win = window.Window()
|
||||
self.assertFalse(lib.settings.get_bool("window.maximized"))
|
||||
(width, height) = win.get_default_size()
|
||||
|
||||
win.maximize()
|
||||
self.assertTrue(lib.settings.get_bool("window.maximized"))
|
||||
self.assertEqual(lib.settings.get_int("window.width"), width)
|
||||
self.assertEqual(lib.settings.get_int("window.height"), height)
|
||||
|
||||
win.unmaximize()
|
||||
self.assertEqual(lib.settings.get_int("window.width"), width)
|
||||
self.assertEqual(lib.settings.get_int("window.height"), height)
|
||||
self.assertFalse(lib.settings.get_bool("window.maximized"))
|
||||
win.remove_controller(keyboard.Event)
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user