diff --git a/Makefile b/Makefile index b2edc93..49ebeaf 100644 --- a/Makefile +++ b/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/ diff --git a/audio/__init__.py b/audio/__init__.py deleted file mode 100644 index 0486f64..0000000 --- a/audio/__init__.py +++ /dev/null @@ -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 diff --git a/audio/artwork.py b/audio/artwork.py deleted file mode 100644 index 3114630..0000000 --- a/audio/artwork.py +++ /dev/null @@ -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()) diff --git a/audio/bass.py b/audio/bass.py deleted file mode 100644 index eec843e..0000000 --- a/audio/bass.py +++ /dev/null @@ -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") diff --git a/audio/controls.py b/audio/controls.py deleted file mode 100644 index 0150990..0000000 --- a/audio/controls.py +++ /dev/null @@ -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(" ") - 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"{text}") - - -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)) diff --git a/audio/nowplaying.py b/audio/nowplaying.py deleted file mode 100644 index 4f20c90..0000000 --- a/audio/nowplaying.py +++ /dev/null @@ -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"{GLib.markup_escape_text(text)}") - - -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"{GLib.markup_escape_text(text)}") - - -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) diff --git a/audio/player.py b/audio/player.py deleted file mode 100644 index 280a4c5..0000000 --- a/audio/player.py +++ /dev/null @@ -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) diff --git a/audio/replaygain.py b/audio/replaygain.py deleted file mode 100644 index 606dc3a..0000000 --- a/audio/replaygain.py +++ /dev/null @@ -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) diff --git a/audio/scale.py b/audio/scale.py deleted file mode 100644 index b3c37b8..0000000 --- a/audio/scale.py +++ /dev/null @@ -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)) diff --git a/audio/test_artwork.py b/audio/test_artwork.py deleted file mode 100644 index f5df320..0000000 --- a/audio/test_artwork.py +++ /dev/null @@ -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) diff --git a/audio/test_audio.py b/audio/test_audio.py deleted file mode 100644 index 5221d1c..0000000 --- a/audio/test_audio.py +++ /dev/null @@ -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) diff --git a/audio/test_bass.py b/audio/test_bass.py deleted file mode 100644 index 328d3b9..0000000 --- a/audio/test_bass.py +++ /dev/null @@ -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) diff --git a/audio/test_controls.py b/audio/test_controls.py deleted file mode 100644 index d8953aa..0000000 --- a/audio/test_controls.py +++ /dev/null @@ -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) diff --git a/audio/test_nowplaying.py b/audio/test_nowplaying.py deleted file mode 100644 index a827f4e..0000000 --- a/audio/test_nowplaying.py +++ /dev/null @@ -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()) diff --git a/audio/test_player.py b/audio/test_player.py deleted file mode 100644 index 689b806..0000000 --- a/audio/test_player.py +++ /dev/null @@ -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)) diff --git a/audio/test_replaygain.py b/audio/test_replaygain.py deleted file mode 100644 index f622488..0000000 --- a/audio/test_replaygain.py +++ /dev/null @@ -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) diff --git a/audio/test_scale.py b/audio/test_scale.py deleted file mode 100644 index 47a0d4d..0000000 --- a/audio/test_scale.py +++ /dev/null @@ -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) diff --git a/db/__init__.py b/db/__init__.py deleted file mode 100644 index 1787737..0000000 --- a/db/__init__.py +++ /dev/null @@ -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() diff --git a/db/album.py b/db/album.py deleted file mode 100644 index 67877b7..0000000 --- a/db/album.py +++ /dev/null @@ -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() diff --git a/db/artist.py b/db/artist.py deleted file mode 100644 index 0875705..0000000 --- a/db/artist.py +++ /dev/null @@ -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() diff --git a/db/decade.py b/db/decade.py deleted file mode 100644 index a3aec8e..0000000 --- a/db/decade.py +++ /dev/null @@ -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() diff --git a/db/disc.py b/db/disc.py deleted file mode 100644 index 4ec8185..0000000 --- a/db/disc.py +++ /dev/null @@ -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() diff --git a/db/genre.py b/db/genre.py deleted file mode 100644 index 141d610..0000000 --- a/db/genre.py +++ /dev/null @@ -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() diff --git a/db/library.py b/db/library.py deleted file mode 100644 index c9a5126..0000000 --- a/db/library.py +++ /dev/null @@ -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() diff --git a/db/playlist.py b/db/playlist.py deleted file mode 100644 index 63936f0..0000000 --- a/db/playlist.py +++ /dev/null @@ -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 diff --git a/db/sql.py b/db/sql.py deleted file mode 100644 index 72ee22e..0000000 --- a/db/sql.py +++ /dev/null @@ -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") diff --git a/db/state.py b/db/state.py deleted file mode 100644 index 990fec9..0000000 --- a/db/state.py +++ /dev/null @@ -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() diff --git a/db/table.py b/db/table.py deleted file mode 100644 index 155122a..0000000 --- a/db/table.py +++ /dev/null @@ -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 diff --git a/db/test_album.py b/db/test_album.py deleted file mode 100644 index 15e2534..0000000 --- a/db/test_album.py +++ /dev/null @@ -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))) diff --git a/db/test_artist.py b/db/test_artist.py deleted file mode 100644 index 496eb76..0000000 --- a/db/test_artist.py +++ /dev/null @@ -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")) diff --git a/db/test_db.py b/db/test_db.py deleted file mode 100644 index c0c253c..0000000 --- a/db/test_db.py +++ /dev/null @@ -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) diff --git a/db/test_decade.py b/db/test_decade.py deleted file mode 100644 index 700920d..0000000 --- a/db/test_decade.py +++ /dev/null @@ -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)) diff --git a/db/test_disc.py b/db/test_disc.py deleted file mode 100644 index 8776c9b..0000000 --- a/db/test_disc.py +++ /dev/null @@ -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")) diff --git a/db/test_genre.py b/db/test_genre.py deleted file mode 100644 index 63f00b0..0000000 --- a/db/test_genre.py +++ /dev/null @@ -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")) diff --git a/db/test_library.py b/db/test_library.py deleted file mode 100644 index f13cbeb..0000000 --- a/db/test_library.py +++ /dev/null @@ -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"))) diff --git a/db/test_playlist.py b/db/test_playlist.py deleted file mode 100644 index b6ba39d..0000000 --- a/db/test_playlist.py +++ /dev/null @@ -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) diff --git a/db/test_sql.py b/db/test_sql.py deleted file mode 100644 index a671e63..0000000 --- a/db/test_sql.py +++ /dev/null @@ -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) diff --git a/db/test_state.py b/db/test_state.py deleted file mode 100644 index db472d9..0000000 --- a/db/test_state.py +++ /dev/null @@ -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() diff --git a/db/test_table.py b/db/test_table.py deleted file mode 100644 index 09538ef..0000000 --- a/db/test_table.py +++ /dev/null @@ -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) diff --git a/db/test_track.py b/db/test_track.py deleted file mode 100644 index b4b9194..0000000 --- a/db/test_track.py +++ /dev/null @@ -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")) diff --git a/db/test_user.py b/db/test_user.py deleted file mode 100644 index ed889e2..0000000 --- a/db/test_user.py +++ /dev/null @@ -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") diff --git a/db/test_year.py b/db/test_year.py deleted file mode 100644 index f669d08..0000000 --- a/db/test_year.py +++ /dev/null @@ -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)) diff --git a/db/track.py b/db/track.py deleted file mode 100644 index cd17e1d..0000000 --- a/db/track.py +++ /dev/null @@ -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() diff --git a/db/user.py b/db/user.py deleted file mode 100644 index 6d9d51a..0000000 --- a/db/user.py +++ /dev/null @@ -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() diff --git a/db/year.py b/db/year.py deleted file mode 100644 index 9215034..0000000 --- a/db/year.py +++ /dev/null @@ -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() diff --git a/emmental.py b/emmental.py deleted file mode 100755 index dc4742e..0000000 --- a/emmental.py +++ /dev/null @@ -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() diff --git a/lib/__init__.py b/lib/__init__.py deleted file mode 100644 index 8c7669d..0000000 --- a/lib/__init__.py +++ /dev/null @@ -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 diff --git a/lib/data.py b/lib/data.py deleted file mode 100644 index 4dfeee2..0000000 --- a/lib/data.py +++ /dev/null @@ -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) diff --git a/lib/filter.py b/lib/filter.py deleted file mode 100644 index 524da09..0000000 --- a/lib/filter.py +++ /dev/null @@ -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)) diff --git a/lib/settings.py b/lib/settings.py deleted file mode 100644 index cf2fc46..0000000 --- a/lib/settings.py +++ /dev/null @@ -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() diff --git a/lib/test_data.py b/lib/test_data.py deleted file mode 100644 index 6c3967e..0000000 --- a/lib/test_data.py +++ /dev/null @@ -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()) diff --git a/lib/test_filter.py b/lib/test_filter.py deleted file mode 100644 index b75f5f2..0000000 --- a/lib/test_filter.py +++ /dev/null @@ -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")) diff --git a/lib/test_settings.py b/lib/test_settings.py deleted file mode 100644 index 0f41675..0000000 --- a/lib/test_settings.py +++ /dev/null @@ -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()) diff --git a/lib/test_version.py b/lib/test_version.py deleted file mode 100644 index a59e09c..0000000 --- a/lib/test_version.py +++ /dev/null @@ -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") diff --git a/lib/version.py b/lib/version.py deleted file mode 100644 index 4b3686f..0000000 --- a/lib/version.py +++ /dev/null @@ -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 ''}" diff --git a/playlist/__init__.py b/playlist/__init__.py deleted file mode 100644 index 8579fe1..0000000 --- a/playlist/__init__.py +++ /dev/null @@ -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() diff --git a/playlist/column.py b/playlist/column.py deleted file mode 100644 index 04f55b9..0000000 --- a/playlist/column.py +++ /dev/null @@ -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()) diff --git a/playlist/footer.py b/playlist/footer.py deleted file mode 100644 index 2896c98..0000000 --- a/playlist/footer.py +++ /dev/null @@ -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()) diff --git a/playlist/header.py b/playlist/header.py deleted file mode 100644 index c14b47d..0000000 --- a/playlist/header.py +++ /dev/null @@ -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) diff --git a/playlist/model.py b/playlist/model.py deleted file mode 100644 index 009fbcf..0000000 --- a/playlist/model.py +++ /dev/null @@ -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() diff --git a/playlist/sort.py b/playlist/sort.py deleted file mode 100644 index fb43252..0000000 --- a/playlist/sort.py +++ /dev/null @@ -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()) diff --git a/playlist/test_column.py b/playlist/test_column.py deleted file mode 100644 index b7bcf28..0000000 --- a/playlist/test_column.py +++ /dev/null @@ -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") diff --git a/playlist/test_footer.py b/playlist/test_footer.py deleted file mode 100644 index 2ed6239..0000000 --- a/playlist/test_footer.py +++ /dev/null @@ -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") diff --git a/playlist/test_header.py b/playlist/test_header.py deleted file mode 100644 index 342a962..0000000 --- a/playlist/test_header.py +++ /dev/null @@ -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) diff --git a/playlist/test_model.py b/playlist/test_model.py deleted file mode 100644 index ec80b58..0000000 --- a/playlist/test_model.py +++ /dev/null @@ -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) diff --git a/playlist/test_playlist.py b/playlist/test_playlist.py deleted file mode 100644 index 4d1a27c..0000000 --- a/playlist/test_playlist.py +++ /dev/null @@ -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) diff --git a/playlist/test_sort.py b/playlist/test_sort.py deleted file mode 100644 index bce50db..0000000 --- a/playlist/test_sort.py +++ /dev/null @@ -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) diff --git a/playlist/test_view.py b/playlist/test_view.py deleted file mode 100644 index 07ceee6..0000000 --- a/playlist/test_view.py +++ /dev/null @@ -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")) diff --git a/playlist/view.py b/playlist/view.py deleted file mode 100644 index 231b60d..0000000 --- a/playlist/view.py +++ /dev/null @@ -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) diff --git a/scanner/__init__.py b/scanner/__init__.py deleted file mode 100644 index a378085..0000000 --- a/scanner/__init__.py +++ /dev/null @@ -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)) diff --git a/scanner/metadata.py b/scanner/metadata.py deleted file mode 100644 index b38553a..0000000 --- a/scanner/metadata.py +++ /dev/null @@ -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)) diff --git a/scanner/queue.py b/scanner/queue.py deleted file mode 100644 index 0dcfee0..0000000 --- a/scanner/queue.py +++ /dev/null @@ -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 diff --git a/scanner/task.py b/scanner/task.py deleted file mode 100644 index a6d79af..0000000 --- a/scanner/task.py +++ /dev/null @@ -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) diff --git a/scanner/test_metadata.py b/scanner/test_metadata.py deleted file mode 100644 index 32decb5..0000000 --- a/scanner/test_metadata.py +++ /dev/null @@ -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() diff --git a/scanner/test_queue.py b/scanner/test_queue.py deleted file mode 100644 index 415a1b0..0000000 --- a/scanner/test_queue.py +++ /dev/null @@ -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) diff --git a/scanner/test_scanner.py b/scanner/test_scanner.py deleted file mode 100644 index e300d66..0000000 --- a/scanner/test_scanner.py +++ /dev/null @@ -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) diff --git a/scanner/test_task.py b/scanner/test_task.py deleted file mode 100644 index 6a19e23..0000000 --- a/scanner/test_task.py +++ /dev/null @@ -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) diff --git a/scanner/test_widgets.py b/scanner/test_widgets.py deleted file mode 100644 index dc2a731..0000000 --- a/scanner/test_widgets.py +++ /dev/null @@ -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) diff --git a/scanner/widgets.py b/scanner/widgets.py deleted file mode 100644 index b132fd6..0000000 --- a/scanner/widgets.py +++ /dev/null @@ -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() diff --git a/sidebar/__init__.py b/sidebar/__init__.py deleted file mode 100644 index 14e0cec..0000000 --- a/sidebar/__init__.py +++ /dev/null @@ -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) diff --git a/sidebar/model.py b/sidebar/model.py deleted file mode 100644 index c21fe7f..0000000 --- a/sidebar/model.py +++ /dev/null @@ -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()) diff --git a/sidebar/row.py b/sidebar/row.py deleted file mode 100644 index 4c00b55..0000000 --- a/sidebar/row.py +++ /dev/null @@ -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) diff --git a/sidebar/stack.py b/sidebar/stack.py deleted file mode 100644 index e99f826..0000000 --- a/sidebar/stack.py +++ /dev/null @@ -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() diff --git a/sidebar/test_model.py b/sidebar/test_model.py deleted file mode 100644 index 42b3169..0000000 --- a/sidebar/test_model.py +++ /dev/null @@ -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")) diff --git a/sidebar/test_row.py b/sidebar/test_row.py deleted file mode 100644 index 8eebece..0000000 --- a/sidebar/test_row.py +++ /dev/null @@ -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) diff --git a/sidebar/test_sidebar.py b/sidebar/test_sidebar.py deleted file mode 100644 index 638a84f..0000000 --- a/sidebar/test_sidebar.py +++ /dev/null @@ -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) diff --git a/sidebar/test_stack.py b/sidebar/test_stack.py deleted file mode 100644 index 868d584..0000000 --- a/sidebar/test_stack.py +++ /dev/null @@ -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) diff --git a/sidebar/test_view.py b/sidebar/test_view.py deleted file mode 100644 index 3ab3c14..0000000 --- a/sidebar/test_view.py +++ /dev/null @@ -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()) diff --git a/sidebar/test_widgets.py b/sidebar/test_widgets.py deleted file mode 100644 index 65917c6..0000000 --- a/sidebar/test_widgets.py +++ /dev/null @@ -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) diff --git a/sidebar/view.py b/sidebar/view.py deleted file mode 100644 index 222de2f..0000000 --- a/sidebar/view.py +++ /dev/null @@ -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) diff --git a/sidebar/widgets.py b/sidebar/widgets.py deleted file mode 100644 index a58226d..0000000 --- a/sidebar/widgets.py +++ /dev/null @@ -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()) diff --git a/tools/generate_tracks.py b/tools/generate_tracks.py deleted file mode 100644 index fd14d57..0000000 --- a/tools/generate_tracks.py +++ /dev/null @@ -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}" }) diff --git a/ui/__init__.py b/ui/__init__.py deleted file mode 100644 index 8c977a2..0000000 --- a/ui/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# Copyright 2021 (c) Anna Schumaker. -from . import icons -from . import window diff --git a/ui/icons.py b/ui/icons.py deleted file mode 100644 index 6b186e8..0000000 --- a/ui/icons.py +++ /dev/null @@ -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) diff --git a/ui/keyboard.py b/ui/keyboard.py deleted file mode 100644 index 0ceaeb6..0000000 --- a/ui/keyboard.py +++ /dev/null @@ -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) diff --git a/ui/pane.py b/ui/pane.py deleted file mode 100644 index ab3e113..0000000 --- a/ui/pane.py +++ /dev/null @@ -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()) diff --git a/ui/test_icons.py b/ui/test_icons.py deleted file mode 100644 index 820942e..0000000 --- a/ui/test_icons.py +++ /dev/null @@ -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")) diff --git a/ui/test_keyboard.py b/ui/test_keyboard.py deleted file mode 100644 index 808e66c..0000000 --- a/ui/test_keyboard.py +++ /dev/null @@ -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) diff --git a/ui/test_pane.py b/ui/test_pane.py deleted file mode 100644 index 572a655..0000000 --- a/ui/test_pane.py +++ /dev/null @@ -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) diff --git a/ui/test_window.py b/ui/test_window.py deleted file mode 100644 index 61c805d..0000000 --- a/ui/test_window.py +++ /dev/null @@ -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) diff --git a/ui/window.py b/ui/window.py deleted file mode 100644 index 4d361a5..0000000 --- a/ui/window.py +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright 2021 (c) Anna Schumaker. -import audio -import lib -import sidebar -from gi.repository import Gtk -from . import keyboard -from . import pane - -class Window(Gtk.ApplicationWindow): - def __init__(self): - Gtk.ApplicationWindow.__init__(self) - self.set_icon_name("emmental") - self.set_title(lib.version.string()) - self.set_titlebar(audio.Header()) - self.set_child(pane.Pane()) - self.add_controller(keyboard.Event) - - lib.settings.initialize("window.width", 1400) - lib.settings.initialize("window.height", 800) - lib.settings.initialize("window.maximized", False) - self.set_default_size(lib.settings.get_int("window.width"), - lib.settings.get_int("window.height")) - self.connect("notify::default-width", self.width_changed) - self.connect("notify::default-height", self.height_changed) - self.connect("notify::maximized", self.maximized) - - def width_changed(self, window, param): - lib.settings.set("window.width", self.get_default_size().width) - - def height_changed(self, window, param): - lib.settings.set("window.height", self.get_default_size().height) - - def maximized(self, window, param): - lib.settings.set("window.maximized", window.is_maximized())