Passed
Push — master ( d79fc9...d41de6 )
by Pauli
02:07
created

AlbumBusinessLayer::injectExtraFields()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 24
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 5.0073

Importance

Changes 0
Metric Value
cc 5
eloc 14
nc 5
nop 3
dl 0
loc 24
ccs 14
cts 15
cp 0.9333
crap 5.0073
rs 9.4888
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * ownCloud - Music app
5
 *
6
 * This file is licensed under the Affero General Public License version 3 or
7
 * later. See the COPYING file.
8
 *
9
 * @author Morris Jobke <[email protected]>
10
 * @author Pauli Järvinen <[email protected]>
11
 * @copyright Morris Jobke 2013, 2014
12
 * @copyright Pauli Järvinen 2016 - 2020
13
 */
14
15
namespace OCA\Music\BusinessLayer;
16
17
use \OCA\Music\AppFramework\BusinessLayer\BusinessLayer;
18
use \OCA\Music\AppFramework\BusinessLayer\BusinessLayerException;
19
use \OCA\Music\AppFramework\Core\Logger;
20
21
use \OCA\Music\Db\AlbumMapper;
22
use \OCA\Music\Db\Album;
23
use \OCA\Music\Db\SortBy;
24
25
use \OCA\Music\Utility\Util;
26
27
class AlbumBusinessLayer extends BusinessLayer {
28
	private $logger;
29
30 8
	public function __construct(AlbumMapper $albumMapper, Logger $logger) {
31 8
		parent::__construct($albumMapper);
32 8
		$this->logger = $logger;
33 8
	}
34
35
	/**
36
	 * Return an album
37
	 * @param integer $albumId the id of the album
38
	 * @param string $userId the name of the user
39
	 * @return Album album
40
	 */
41 1
	public function find($albumId, $userId) {
42 1
		$album = parent::find($albumId, $userId);
43 1
		return $this->injectExtraFields([$album], $userId)[0];
44
	}
45
46
	/**
47
	 * Returns all albums
48
	 * @param string $userId the name of the user
49
	 * @param integer $sortBy Sorting order of the result, default to unspecified
50
	 * @param integer|null $limit
51
	 * @param integer|null $offset
52
	 * @return Album[] albums
53
	 */
54 2
	public function findAll($userId, $sortBy=SortBy::None, $limit=null, $offset=null) {
55 2
		$albums = parent::findAll($userId, $sortBy, $limit, $offset);
56 2
		return $this->injectExtraFields($albums, $userId, true);
57
	}
58
59
	/**
60
	 * Returns all albums filtered by artist (both album and track artists are considered)
61
	 * @param string $artistId the id of the artist
62
	 * @return Album[] albums
63
	 */
64 1
	public function findAllByArtist($artistId, $userId) {
65 1
		$albums = $this->mapper->findAllByArtist($artistId, $userId);
66 1
		return $this->injectExtraFields($albums, $userId);
67
	}
68
69
	/**
70
	 * Returns all albums filtered by album artist
71
	 * @param string $artistId the id of the artist
72
	 * @return Album[] albums
73
	 */
74
	public function findAllByAlbumArtist($artistId, $userId) {
75
		$albums = $this->mapper->findAllByAlbumArtist($artistId, $userId);
76
		$albums = $this->injectExtraFields($albums, $userId);
77
		\usort($albums, ['\OCA\Music\Db\Album', 'compareYearAndName']);
78
		return $albums;
79
	}
80
81
	/**
82
	 * Returns all albums filtered by genre
83
	 * @param int $genreId the genre to include
84
	 * @param string $userId the name of the user
85
	 * @param int|null $limit
86
	 * @param int|null $offset
87
	 * @return Album[] albums
88
	 */
89
	public function findAllByGenre($genreId, $userId, $limit=null, $offset=null) {
90
		$albums = $this->mapper->findAllByGenre($genreId, $userId, $limit, $offset);
91
		return $this->injectExtraFields($albums, $userId);
92
	}
93
94
	/**
95
	 * Returns all albums filtered by release year
96
	 * @param int $fromYear
97
	 * @param int $toYear
98
	 * @param string $userId the name of the user
99
	 * @param int|null $limit
100
	 * @param int|null $offset
101
	 * @return Album[] albums
102
	 */
103
	public function findAllByYearRange($fromYear, $toYear, $userId, $limit=null, $offset=null) {
104
		$reverseOrder = false;
105
		if ($fromYear > $toYear) {
106
			$reverseOrder = true;
107
			Util::swap($fromYear, $toYear);
108
		}
109
110
		// Implement all the custom logic of this function here, without special Mapper function
111
		$albums = \array_filter($this->findAll($userId), function($album) use ($fromYear, $toYear) {
112
			$years = $album->getYears();
113
			return (!empty($years) && \min($years) <= $toYear && \max($years) >= $fromYear);
114
		});
115
116
		\usort($albums, function($album1, $album2) use ($reverseOrder) {
117
			return $reverseOrder
118
				? $album2->yearToAPI() - $album1->yearToAPI()
119
				: $album1->yearToAPI() - $album2->yearToAPI();
120
		});
121
122
		if ($limit || $offset) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $offset of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
Bug Best Practice introduced by
The expression $limit of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
123
			$albums = \array_slice($albums, $offset ?: 0, $limit);
124
		}
125
126
		return $albums;
127
	}
128
129
	/**
130
	 * Return all albums with name matching the search criteria
131
	 * @param string $name
132
	 * @param string $userId
133
	 * @param bool $fuzzy
134
	 * @param integer $limit
135
	 * @param integer $offset
136
	 * @return Album[]
137
	 */
138
	public function findAllByName($name, $userId, $fuzzy = false, $limit=null, $offset=null) {
139
		$albums = parent::findAllByName($name, $userId, $fuzzy, $limit, $offset);
140
		return $this->injectExtraFields($albums, $userId);
141
	}
142
143
	/**
144
	 * Add performing artists, release years, genres, and disk counts to the given album objects
145
	 * @param Album[] $albums
146
	 * @param string $userId
147
	 * @param bool $allAlbums Set to true if $albums contains all albums of the user.
148
	 *                        This has now effect on the outcome but helps in optimizing
149
	 *                        the database query.
150
	 * @return Album[]
151
	 */
152 4
	private function injectExtraFields($albums, $userId, $allAlbums = false) {
153 4
		if (\count($albums) > 0) {
154
			// In case we are injecting data to a lot of albums, do not limit the
155
			// SQL SELECTs to only those albums. Very large amount of SQL host parameters
156
			// could cause problems with SQLite (see #239) and probably it would be bad for
157
			// performance also on other DBMSs. For the proper operation of this function,
158
			// it doesn't matter if we fetch data for some extra albums.
159 3
			$albumIds = ($allAlbums || \count($albums) >= 999)
160 3
					? null : Util::extractIds($albums);
161
162 3
			$artists = $this->mapper->getPerformingArtistsByAlbumId($albumIds, $userId);
163 3
			$years = $this->mapper->getYearsByAlbumId($albumIds, $userId);
164 3
			$diskCounts = $this->mapper->getDiscCountByAlbumId($albumIds, $userId);
165 3
			$genres = $this->mapper->getGenresByAlbumId($albumIds, $userId);
166
167 3
			foreach ($albums as &$album) {
168 3
				$albumId = $album->getId();
169 3
				$album->setArtistIds($artists[$albumId]);
170 3
				$album->setNumberOfDisks($diskCounts[$albumId]);
171 3
				$album->setGenres(Util::arrayGetOrDefault($genres, $albumId));
172
				$album->setYears(Util::arrayGetOrDefault($years, $albumId));
173
			}
174
		}
175 1
		return $albums;
176
	}
177
178
	/**
179
	 * Returns the count of albums an Artist is featured in
180
	 * @param integer $artistId
181
	 * @return integer
182
	 */
183
	public function countByArtist($artistId) {
184
		return $this->mapper->countByArtist($artistId);
185
	}
186
187
	public function findAlbumOwner($albumId) {
188
		$entities = $this->findById([$albumId]);
189
		if (\count($entities) != 1) {
190
			throw new BusinessLayerException(
191
					'Expected to find one album but got ' . \count($entities));
192
		} else {
193
			return $entities[0]->getUserId();
194
		}
195
	}
196
197
	/**
198
	 * Adds an album if it does not exist already or updates an existing album
199
	 * @param string $name the name of the album
200
	 * @param integer $albumArtistId
201
	 * @param string $userId
202
	 * @return Album The added/updated album
203
	 */
204 1
	public function addOrUpdateAlbum($name, $albumArtistId, $userId) {
205 1
		$album = new Album();
206 1
		$album->setName(Util::truncate($name, 256)); // some DB setups can't truncate automatically to column max size
207 1
		$album->setUserId($userId);
208 1
		$album->setAlbumArtistId($albumArtistId);
209
210
		// Generate hash from the set of fields forming the album identity to prevent duplicates.
211
		// The uniqueness of album name is evaluated in case-insensitive manner.
212 1
		$lowerName = \mb_strtolower($album->getName());
213 1
		$hash = \hash('md5', "$lowerName|$albumArtistId");
214 1
		$album->setHash($hash);
215
216 1
		return $this->mapper->insertOrUpdate($album);
217
	}
218
219
	/**
220
	 * Check if given file is used as cover for the given album
221
	 * @param int $albumId
222
	 * @param int[] $fileIds
223
	 * @return boolean
224
	 */
225
	public function albumCoverIsOneOfFiles($albumId, $fileIds) {
226
		$albums = $this->findById([$albumId]);
227
		return (\count($albums) && \in_array($albums[0]->getCoverFileId(), $fileIds));
228
	}
229
230
	/**
231
	 * updates the cover for albums in the specified folder without cover
232
	 * @param integer $coverFileId the file id of the cover image
233
	 * @param integer $folderId the file id of the folder where the albums are looked from
234
	 * @return true if one or more albums were influenced
235
	 */
236 1
	public function updateFolderCover($coverFileId, $folderId) {
237 1
		return $this->mapper->updateFolderCover($coverFileId, $folderId);
238
	}
239
240
	/**
241
	 * set cover file for a specified album
242
	 * @param integer $coverFileId the file id of the cover image
243
	 * @param integer $albumId the id of the album to be modified
244
	 */
245
	public function setCover($coverFileId, $albumId) {
246
		$this->mapper->setCover($coverFileId, $albumId);
247
	}
248
249
	/**
250
	 * removes the cover art from albums, replacement covers will be searched in a background task
251
	 * @param integer[] $coverFileIds the file IDs of the cover images
252
	 * @param string[]|null $userIds the users whose music library is targeted; all users are targeted if omitted
253
	 * @return Album[] albums which got modified, empty array if none
254
	 */
255 1
	public function removeCovers($coverFileIds, $userIds=null) {
256 1
		return $this->mapper->removeCovers($coverFileIds, $userIds);
257
	}
258
259
	/**
260
	 * try to find cover arts for albums without covers
261
	 * @param string|null $userId target user; omit to target all users
262
	 * @return array of users whose collections got modified
263
	 */
264
	public function findCovers($userId = null) {
265
		$affectedUsers = [];
266
		$albums = $this->mapper->getAlbumsWithoutCover($userId);
267
		foreach ($albums as $album) {
268
			if ($this->mapper->findAlbumCover($album['albumId'], $album['parentFolderId'])) {
269
				$affectedUsers[$album['userId']] = 1;
270
			}
271
		}
272
		return \array_keys($affectedUsers);
273
	}
274
}
275