1# Copyright 2019 The GNOME Music developers
2#
3# GNOME Music is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation; either version 2 of the License, or
6# (at your option) any later version.
7#
8# GNOME Music is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License along
14# with GNOME Music; if not, write to the Free Software Foundation, Inc.,
15# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
16#
17# The GNOME Music authors hereby grant permission for non-GPL compatible
18# GStreamer plugins to be used and distributed together with GStreamer
19# and GNOME Music.  This permission is above and beyond the permissions
20# granted by the GPL license by which GNOME Music is covered.  If you
21# modify this code, you may extend this exception to your version of the
22# code, but you are not obligated to do so.  If you do not wish to do so,
23# delete this exception statement from your version.
24
25import gi
26gi.require_versions({"Gfm": "0.1", "Grl": "0.3"})
27from gi.repository import Gfm, Gio, Grl, GObject
28
29import gnomemusic.utils as utils
30
31from gnomemusic.albumart import AlbumArt
32
33
34class CoreAlbum(GObject.GObject):
35    """Exposes a Grl.Media with relevant data as properties
36    """
37
38    artist = GObject.Property(type=str)
39    composer = GObject.Property(type=str, default=None)
40    duration = GObject.Property(type=int, default=0)
41    media = GObject.Property(type=Grl.Media)
42    title = GObject.Property(type=str)
43    url = GObject.Property(type=str)
44    year = GObject.Property(type=str, default="----")
45
46    def __init__(self, application, media):
47        """Initiate the CoreAlbum object
48
49        :param Application application: The application object
50        :param Grl.Media media: A media object
51        """
52        super().__init__()
53
54        self._application = application
55        self._coregrilo = application.props.coregrilo
56        self._model = None
57        self._selected = False
58        self._thumbnail = None
59
60        self.update(media)
61
62    def update(self, media):
63        """Update the CoreAlbum object with new info
64
65        :param Grl.Media media: A media object
66        """
67        self.props.media = media
68        self.props.artist = utils.get_artist_name(media)
69        self.props.composer = media.get_composer()
70        self.props.title = utils.get_media_title(media)
71        self.props.url = media.get_url()
72        self.props.year = utils.get_media_year(media)
73
74    def _get_album_model(self):
75        disc_model = Gio.ListStore()
76        disc_model_sort = Gfm.SortListModel.new(disc_model)
77
78        def _disc_order_sort(disc_a, disc_b):
79            return disc_a.props.disc_nr - disc_b.props.disc_nr
80
81        disc_model_sort.set_sort_func(
82            utils.wrap_list_store_sort_func(_disc_order_sort))
83
84        self._coregrilo.get_album_discs(self.props.media, disc_model)
85
86        return disc_model_sort
87
88    @GObject.Property(
89        type=Gfm.SortListModel, default=None,
90        flags=GObject.ParamFlags.READABLE)
91    def model(self):
92        if self._model is None:
93            self._model = self._get_album_model()
94            self._model.connect("items-changed", self._on_list_items_changed)
95            self._model.items_changed(0, 0, self._model.get_n_items())
96
97        return self._model
98
99    def _on_list_items_changed(self, model, position, removed, added):
100        with self.freeze_notify():
101            for coredisc in model:
102                coredisc.props.selected = self.props.selected
103
104            if added > 0:
105                for i in range(added):
106                    coredisc = model[position + i]
107                    coredisc.connect(
108                        "notify::duration", self._on_duration_changed)
109
110    def _on_duration_changed(self, coredisc, duration):
111        duration = 0
112
113        for coredisc in self.props.model:
114            duration += coredisc.props.duration
115
116        self.props.duration = duration
117
118    @GObject.Property(type=bool, default=False)
119    def selected(self):
120        return self._selected
121
122    @selected.setter  # type: ignore
123    def selected(self, value):
124        if value == self._selected:
125            return
126
127        self._selected = value
128
129        # The model is loaded on-demand, so the first time the model is
130        # returned it can still be empty. This is problem for returning
131        # a selection. Trigger loading of the model here if a selection
132        # is requested, it will trigger the filled model update as
133        # well.
134        self.props.model.items_changed(0, 0, 0)
135
136    @GObject.Property(type=str, default=None)
137    def thumbnail(self):
138        """Album art thumbnail retrieval
139
140        :return: The album art uri or "generic"
141        :rtype: string
142        """
143        if self._thumbnail is None:
144            self._thumbnail = "generic"
145            AlbumArt(self._application, self)
146
147        return self._thumbnail
148
149    @thumbnail.setter  # type: ignore
150    def thumbnail(self, value):
151        """Album art thumbnail setter
152
153        :param string value: uri or "generic"
154        """
155        self._thumbnail = value
156