1# Copyright 2006-2009 Scott Horowitz <stonecrest@gmail.com>
2# Copyright 2009-2014 Jonathan Ballet <jon@multani.info>
3#
4# This file is part of Sonata.
5#
6# Sonata is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10#
11# Sonata is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with Sonata.  If not, see <http://www.gnu.org/licenses/>.
18
19from os.path import expanduser
20import unittest
21
22try:
23    from unittest.mock import Mock, patch, call
24except ImportError: # pragma: nocover
25    from mock import Mock, patch, call
26
27import os
28import shutil
29import tempfile
30
31from sonata.artwork import artwork_path
32from sonata.artwork import ArtworkLocator
33from sonata import consts
34
35
36class _MixinTestDirectory:
37    def mkdir(self, *paths):
38        path = os.path.join(self.music_dir, *paths)
39        os.makedirs(path, exist_ok=True)
40        return path
41
42    def touch(self, *paths):
43        path = os.path.join(self.music_dir, *paths)
44        with open(path, 'wb') as fp:
45            pass
46        return path
47
48    def assertDirEqual(self, expected, got):
49        expected = expected.replace('/TMP', self.music_dir)
50        self.assertEqual(expected, got)
51
52    def setUp(self):
53        super().setUp()
54        self.music_dir = tempfile.mkdtemp(prefix="sonata-tests-")
55        self.config = Mock('Config', current_musicdir=self.music_dir)
56
57    def tearDown(self):
58        super().tearDown()
59        shutil.rmtree(self.music_dir)
60
61
62class TestArtworkPathFinder(unittest.TestCase):
63    def setUp(self):
64        self.config = Mock('Config',
65                           current_musicdir="/foo",
66                           art_location=1,
67                           art_location_custom_filename="bla")
68        self.locator = ArtworkLocator(self.config)
69
70    # artwork_path_from_data
71    def _call_artwork_path_from_data(self,  location_type=None,
72                                     artist="Toto",
73                                     album="Tata",
74                                     song_dir="To/Ta"):
75
76        return self.locator.path(artist, album, song_dir, location_type)
77
78    def test_find_path_from_data_in_home_covers(self):
79        self.config.art_location = consts.ART_LOCATION_HOMECOVERS
80
81        res = self._call_artwork_path_from_data()
82        self.assertEqual(expanduser('~/.covers/Toto-Tata.jpg'), res)
83
84    def test_find_path_from_data_as_cover_dot_jpg(self):
85        self.config.art_location = consts.ART_LOCATION_COVER
86
87        res = self._call_artwork_path_from_data()
88        self.assertEqual('/foo/To/Ta/cover.jpg', res)
89
90    def test_find_path_from_data_as_folder_dot_jpg(self):
91        self.config.art_location = consts.ART_LOCATION_FOLDER
92
93        res = self._call_artwork_path_from_data()
94        self.assertEqual('/foo/To/Ta/folder.jpg', res)
95
96    def test_find_path_from_data_as_album_dot_jpg(self):
97        self.config.art_location = consts.ART_LOCATION_ALBUM
98
99        res = self._call_artwork_path_from_data()
100        self.assertEqual('/foo/To/Ta/album.jpg', res)
101
102    def test_find_path_from_data_as_custom_dot_jpg(self):
103        self.config.art_location_custom_filename = "bar.png"
104        self.config.art_location = consts.ART_LOCATION_CUSTOM
105
106        res = self._call_artwork_path_from_data()
107        self.assertEqual('/foo/To/Ta/bar.png', res)
108
109    def test_find_path_from_data_as_custom_without_filename(self):
110        self.config.art_location_custom_filename = ""
111        self.config.art_location = consts.ART_LOCATION_CUSTOM
112
113        res = self._call_artwork_path_from_data()
114        # We tried CUSTOM without a valid custom filename :/
115        self.assertEqual(None, res)
116
117    def test_find_path_from_data_force_location_type(self):
118        self.config.art_location = "foo bar" # Should not be used
119
120        res = self._call_artwork_path_from_data(consts.ART_LOCATION_COVER)
121        self.assertEqual('/foo/To/Ta/cover.jpg', res)
122
123    # artwork_path_from_song
124    def test_find_path_from_song(self):
125        self.config.art_location = consts.ART_LOCATION_COVER
126        song = Mock('Song', artist='', album='', file='Foo/Bar/1.ogg')
127        res = self.locator.path_from_song(song)
128        self.assertEqual('/foo/Foo/Bar/cover.jpg', res)
129
130    def test_find_path_from_song_with_home_cover(self):
131        song = Mock('Song', artist='Toto', album='Tata', file='Foo/Bar/1.ogg')
132        res = self.locator.path_from_song(song,
133                                          consts.ART_LOCATION_HOMECOVERS)
134        self.assertEqual(expanduser('~/.covers/Toto-Tata.jpg'), res)
135
136    # artwork_path
137    def test_artwork_path_with_song_name(self):
138        song = Mock('Song')
139        song.configure_mock(name="plop", artist='Toto', album='Tata',
140                            file='F/B/1.ogg')
141        res = artwork_path(song, self.config)
142        self.assertEqual(expanduser("~/.covers/plop.jpg"), res)
143
144
145class TestArtworkLookupSingleImage(_MixinTestDirectory, unittest.TestCase):
146    def setUp(self):
147        super().setUp()
148        self.locator = ArtworkLocator(self.config)
149
150    def func(self, path):
151        path = os.path.join(self.music_dir, path)
152        return self.locator._lookup_single_image(path)
153
154    def test_path_doesnt_exists(self):
155        res = list(self.func("bar/baz"))
156        self.assertEqual(0, len(res))
157
158    def test_no_images(self):
159        self.mkdir("bar", "baz")
160
161        res = list(self.func("bar/baz"))
162        self.assertEqual(0, len(res))
163
164    def test_one_image(self):
165        self.mkdir("bar", "baz")
166        self.touch("bar", "baz", "loc2.jpg")
167
168        res = list(self.func("bar/baz"))
169        self.assertEqual(1, len(res))
170        self.assertDirEqual("/TMP/bar/baz/loc2.jpg", res[0])
171
172    def test_several_images_no_artwork(self):
173        self.mkdir("bar", "baz")
174        self.touch("bar", "baz", "loc1.jpg")
175        self.touch("bar", "baz", "loc2.jpg")
176
177        res = list(self.func("bar/baz"))
178        self.assertEqual(0, len(res))
179
180
181class TestArtworkLocator(unittest.TestCase):
182    def setUp(self):
183        self.music_dir = "/foo"
184        self.config = Mock('Config',
185                           current_musicdir=self.music_dir,
186                           art_location_custom_filename="")
187        self.locator = ArtworkLocator(self.config)
188
189    def get_locations(self, artist="Toto", album="Tata", song_dir="To/Ta",
190                      default_kind=None):
191        return self.locator._get_locations(artist, album,
192                                           song_dir, default_kind)
193
194    def test_simple_locations(self):
195        l = self.get_locations()
196
197        for key in dir(consts):
198            if not key.startswith('ART_LOCATION_'):
199                continue
200
201            self.assertIn(getattr(consts, key), l)
202
203    def test_home_covers(self):
204        l = self.get_locations()[consts.ART_LOCATION_HOMECOVERS]
205        self.assertEqual(1, len(l))
206        self.assertEqual(os.path.expanduser("~/.covers/Toto-Tata.jpg"), l[0])
207
208    def test_cover_jpg(self):
209        l = self.get_locations()[consts.ART_LOCATION_COVER]
210        self.assertEqual(1, len(l))
211        self.assertEqual("/foo/To/Ta/cover.jpg", l[0])
212
213    def test_folder_jpg(self):
214        l = self.get_locations()[consts.ART_LOCATION_FOLDER]
215        self.assertEqual(1, len(l))
216        self.assertEqual("/foo/To/Ta/folder.jpg", l[0])
217
218    def test_album_jpg(self):
219        l = self.get_locations()[consts.ART_LOCATION_ALBUM]
220        self.assertEqual(1, len(l))
221        self.assertEqual("/foo/To/Ta/album.jpg", l[0])
222
223    def test_custom_valid(self):
224        self.config.art_location_custom_filename = "pouet.jpg"
225
226        l = self.get_locations()[consts.ART_LOCATION_CUSTOM]
227        self.assertEqual(1, len(l))
228        self.assertEqual("/foo/To/Ta/pouet.jpg", l[0])
229
230    def test_custom_but_empty_custom_file(self):
231        self.config.art_location_custom_filename = ""
232
233        l = self.get_locations()[consts.ART_LOCATION_CUSTOM]
234        self.assertEqual(0, len(l))
235
236    def test_misc_location(self):
237        old_misc = consts.ART_LOCATIONS_MISC
238        consts.ART_LOCATIONS_MISC = files = ['1.jpg', '2.jpg', '3.jpg']
239
240        try:
241            l = list(self.get_locations()[consts.ART_LOCATION_MISC])
242        finally:
243            consts.ART_LOCATIONS_MISC = old_misc
244
245        expected = ["/foo/To/Ta/%s" % f for f in files]
246        self.assertEqual(expected, l)
247
248
249class TestArtworkLocatorPathChecks(_MixinTestDirectory, unittest.TestCase):
250    def setUp(self):
251        super().setUp()
252        self.config = Mock('Config',
253                           current_musicdir=self.music_dir,
254                           art_location_custom_filename="")
255        self.locator = ArtworkLocator(self.config)
256
257    def test_locate_existing(self):
258        self.mkdir('To', 'Ta')
259        cover_path = self.touch('To', 'Ta', 'cover.jpg')
260        self.config.art_location = consts.ART_LOCATION_COVER
261
262        res = self.locator.locate('Toto', 'Tata', 'To/Ta')
263
264        self.assertEqual((self.config.art_location, cover_path), res)
265
266    def test_locate_config_has_priority(self):
267        self.mkdir('To', 'Ta')
268        cover_path  = self.touch('To', 'Ta', 'cover.jpg')
269        folder_path = self.touch('To', 'Ta', 'folder.jpg')
270
271        # We request "cover.jpg", it exists, we got it
272        self.config.art_location = consts.ART_LOCATION_COVER
273        res = self.locator.locate('Toto', 'Tata', 'To/Ta')
274        self.assertEqual((self.config.art_location, cover_path), res)
275
276        # If we request now "folder.jpg", we get it before "cover.jpg"
277        self.config.art_location = consts.ART_LOCATION_FOLDER
278        res = self.locator.locate('Toto', 'Tata', 'To/Ta')
279        self.assertEqual((self.config.art_location, folder_path), res)
280
281    def test_locate_nothing_valid(self):
282        self.mkdir('To', 'Ta')
283        self.config.art_location = consts.ART_LOCATION_COVER
284
285        res = self.locator.locate('Toto', 'Tata', 'To/Ta')
286
287        self.assertEqual((None, None), res)
288