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