Passed
Push — master ( 511b17...8295e0 )
by Pauli
01:59
created

TrackBusinessLayer   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 323
Duplicated Lines 0 %

Test Coverage

Coverage 52.94%

Importance

Changes 0
Metric Value
eloc 112
c 0
b 0
f 0
dl 0
loc 323
ccs 63
cts 119
cp 0.5294
rs 9.92
wmc 31

17 Methods

Rating   Name   Duplication   Size   Complexity  
A findAllByAlbum() 0 2 1
A findAllByArtist() 0 2 1
A findAllByFolder() 0 2 1
A __construct() 0 3 1
B findAllFolders() 0 64 7
A findAllByNameRecursive() 0 2 1
A findFilesWithoutScannedGenre() 0 2 1
A findAllByNameAndArtistName() 0 8 1
A findByFileId() 0 5 2
A findAllByGenre() 0 2 1
A countByArtist() 0 2 1
A getGenresByArtistId() 0 2 1
A findAllFileIds() 0 2 1
A countByAlbum() 0 2 1
A addOrUpdateTrack() 0 17 1
B deleteTracks() 0 60 8
A totalDurationOfAlbum() 0 2 1
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
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\Core\Logger;
19
20
use \OCA\Music\Db\TrackMapper;
21
use \OCA\Music\Db\Track;
22
23
use \OCA\Music\Utility\Util;
24
25
use \OCP\AppFramework\Db\DoesNotExistException;
0 ignored issues
show
Bug introduced by
The type OCP\AppFramework\Db\DoesNotExistException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
26
use \OCP\AppFramework\Db\MultipleObjectsReturnedException;
0 ignored issues
show
Bug introduced by
The type OCP\AppFramework\Db\Mult...bjectsReturnedException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
27
28
class TrackBusinessLayer extends BusinessLayer {
29
	private $logger;
30
31 7
	public function __construct(TrackMapper $trackMapper, Logger $logger) {
32 7
		parent::__construct($trackMapper);
33 7
		$this->logger = $logger;
34 7
	}
35
36
	/**
37
	 * Returns all tracks filtered by artist
38
	 * @param string $artistId the id of the artist
39
	 * @param string $userId the name of the user
40
	 * @return array of tracks
41
	 */
42 1
	public function findAllByArtist($artistId, $userId) {
43 1
		return $this->mapper->findAllByArtist($artistId, $userId);
44
	}
45
46
	/**
47
	 * Returns all tracks filtered by album
48
	 * @param string $albumId the id of the album
49
	 * @param string $userId the name of the user
50
	 * @return \OCA\Music\Db\Track[] tracks
51
	 */
52 1
	public function findAllByAlbum($albumId, $userId, $artistId = null) {
53 1
		return $this->mapper->findAllByAlbum($albumId, $userId, $artistId);
54
	}
55
56
	/**
57
	 * Returns all tracks filtered by parent folder
58
	 * @param integer $folderId the id of the folder
59
	 * @param string $userId the name of the user
60
	 * @return \OCA\Music\Db\Track[] tracks
61
	 */
62
	public function findAllByFolder($folderId, $userId) {
63
		return $this->mapper->findAllByFolder($folderId, $userId);
64
	}
65
66
	/**
67
	 * Returns all tracks filtered by genre
68
	 * @param int $genreId the genre to include
69
	 * @param string $userId the name of the user
70
	 * @param int|null $limit
71
	 * @param int|null $offset
72
	 * @return \OCA\Music\Db\Track[] tracks
73
	 */
74
	public function findAllByGenre($genreId, $userId, $limit=null, $offset=null) {
75
		return $this->mapper->findAllByGenre($genreId, $userId, $limit, $offset);
76
	}
77
78
	/**
79
	 * Returns all tracks filtered by name (of track/album/artist)
80
	 * @param string $name the name of the track/album/artist
81
	 * @param string $userId the name of the user
82
	 * @return \OCA\Music\Db\Track[] tracks
83
	 */
84
	public function findAllByNameRecursive($name, $userId) {
85
		return $this->mapper->findAllByNameRecursive($name, $userId);
86
	}
87
88
	/**
89
	 * Returns all tracks specified by name and/or artist name
90
	 * @param string|null $name the name of the track
91
	 * @param string|null $artistName the name of the artist
92
	 * @param string $userId the name of the user
93
	 * @return \OCA\Music\Db\Track[] Tracks matching the criteria
94
	 */
95
	public function findAllByNameAndArtistName($name, $artistName, $userId) {
96
		// find exact matches first and then append fuzzy matches which are not exact matches
97
		$strictMatches = $this->mapper->findAllByNameAndArtistName($name, $artistName, false, $userId);
98
		$fuzzyMatches = $this->mapper->findAllByNameAndArtistName($name, $artistName, true, $userId);
99
100
		$matches = \array_merge($strictMatches, $fuzzyMatches);
101
		$matches = \array_unique($matches, SORT_REGULAR);
102
		return $matches;
103
	}
104
105
	/**
106
	 * Returns the track for a file id
107
	 * @param string $fileId the file id of the track
108
	 * @param string $userId the name of the user
109
	 * @return \OCA\Music\Db\Track|null track
110
	 */
111 1
	public function findByFileId($fileId, $userId) {
112
		try {
113 1
			return $this->mapper->findByFileId($fileId, $userId);
114
		} catch (DoesNotExistException $e) {
115
			return null;
116
		}
117
	}
118
119
	/**
120
	 * Returns file IDs of all indexed tracks of the user
121
	 * @param string $userId
122
	 * @return int[]
123
	 */
124
	public function findAllFileIds($userId) {
125
		return $this->mapper->findAllFileIds($userId);
126
	}
127
128
	/**
129
	 * Returns all folders of the user containing indexed tracks, along with the contained track IDs
130
	 * @param string $userId
131
	 * @param Folder $userHome
0 ignored issues
show
Bug introduced by
The type OCA\Music\BusinessLayer\Folder was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
132
	 * @return array of entries like {id: int, name: string, path: string, trackIds: int[]}
133
	 */
134
	public function findAllFolders($userId, $userHome) {
135
		// All tracks of the user, grouped by their parent folders. Some of the parent folders
136
		// may be owned by other users and are invisible to this user (in case of shared files).
137
		$tracksByFolder = $this->mapper->findTrackAndFolderIds($userId);
138
139
		// Get the folder names and paths for ordinary local folders directly from the DB.
140
		// This is significantly more efficient than using the Files API because we need to
141
		// run only single DB query instead of one per folder.
142
		$folderNamesAndPaths = $this->mapper->findNodeNamesAndPaths(
143
				\array_keys($tracksByFolder), $userHome->getStorage()->getId());
144
145
		// root folder has to be handled as a special case because shared files from
146
		// many folders may be shown to this user mapped under the root folder
147
		$rootFolderTracks = [];
148
149
		// Build the final results. Use the previously fetched data for the ordinary
150
		// local folders and query the data through the Files API for the more special cases.
151
		$result = [];
152
		foreach ($tracksByFolder as $folderId => $trackIds) {
153
			if (isset($folderNamesAndPaths[$folderId])) {
154
				// normal folder within the user home storage
155
				$entry = $folderNamesAndPaths[$folderId];
156
				// remove the "files" from the beginning of the folder path
157
				$entry['path'] = \substr($entry['path'], 5);
158
				// special handling for the root folder
159
				if ($entry['path'] === '') {
160
					$entry = null;
161
				}
162
			}
163
			else {
164
				// shared folder or parent folder of a shared file or an externally mounted folder
165
				$folderNode = $userHome->getById($folderId);
166
				if (\count($folderNode) === 0) {
167
					// other user's folder with files shared with this user (mapped under root)
168
					$entry = null;
169
				}
170
				else {
171
					$entry = [
172
						'name' => $folderNode[0]->getName(),
173
						'path' => $userHome->getRelativePath($folderNode[0]->getPath())
174
					];
175
				}
176
			}
177
178
			if ($entry) {
179
				$entry['trackIds'] = $trackIds;
180
				$entry['id'] = $folderId;
181
				$result[] = $entry;
182
			} else {
183
				$rootFolderTracks = \array_merge($rootFolderTracks, $trackIds);
184
			}
185
		}
186
187
		// add the root folder if it contains any tracks
188
		if (!empty($rootFolderTracks)) {
189
			$result[] = [
190
				'name' => '',
191
				'path' => '/',
192
				'trackIds' => $rootFolderTracks,
193
				'id' => $userHome->getId()
194
			];
195
		}
196
197
		return $result;
198
	}
199
200
	/**
201
	 * Returns all genre IDs associated with the given artist
202
	 * @param int $artistId
203
	 * @param string $userId
204
	 * @return int[]
205
	 */
206
	public function getGenresByArtistId($artistId, $userId) {
207
		return $this->mapper->getGenresByArtistId($artistId, $userId);
208
	}
209
210
	/**
211
	 * Returns file IDs of the tracks which do not have genre scanned. This is not the same
212
	 * thing as unknown genre, which is stored as empty string and means that the genre has
213
	 * been scanned but was not found from the track metadata.
214
	 * @param string $userId
215
	 * @return int[]
216
	 */
217
	public function findFilesWithoutScannedGenre($userId) {
218
		return $this->mapper->findFilesWithoutScannedGenre($userId);
219
	}
220
221
	/**
222
	 * @param integer $artistId
223
	 * @return integer
224
	 */
225
	public function countByArtist($artistId) {
226
		return $this->mapper->countByArtist($artistId);
227
	}
228
229
	/**
230
	 * @param integer $albumId
231
	 * @return integer
232
	 */
233
	public function countByAlbum($albumId) {
234
		return $this->mapper->countByAlbum($albumId);
235
	}
236
237
	/**
238
	 * @param integer $albumId
239
	 * @return integer Duration in seconds
240
	 */
241
	public function totalDurationOfAlbum($albumId) {
242
		return $this->mapper->totalDurationOfAlbum($albumId);
243
	}
244
245
	/**
246
	 * Adds a track if it does not exist already or updates an existing track
247
	 * @param string $title the title of the track
248
	 * @param int|null $number the number of the track
249
	 * @param int|null $discNumber the number of the disc
250
	 * @param int|null $year the year of the release
251
	 * @param int $genreId the genre id of the track
252
	 * @param int $artistId the artist id of the track
253
	 * @param int $albumId the album id of the track
254
	 * @param int $fileId the file id of the track
255
	 * @param string $mimetype the mimetype of the track
256
	 * @param string $userId the name of the user
257
	 * @param int $length track length in seconds
258
	 * @param int $bitrate track bitrate in bits (not kbits)
259
	 * @return \OCA\Music\Db\Track The added/updated track
260
	 */
261 1
	public function addOrUpdateTrack(
262
			$title, $number, $discNumber, $year, $genreId, $artistId, $albumId,
263
			$fileId, $mimetype, $userId, $length=null, $bitrate=null) {
264 1
		$track = new Track();
265 1
		$track->setTitle(Util::truncate($title, 256)); // some DB setups can't truncate automatically to column max size
266 1
		$track->setNumber($number);
267 1
		$track->setDisk($discNumber);
268 1
		$track->setYear($year);
269 1
		$track->setGenreId($genreId);
270 1
		$track->setArtistId($artistId);
271 1
		$track->setAlbumId($albumId);
272 1
		$track->setFileId($fileId);
273 1
		$track->setMimetype($mimetype);
274 1
		$track->setUserId($userId);
275 1
		$track->setLength($length);
276 1
		$track->setBitrate($bitrate);
277 1
		return $this->mapper->insertOrUpdate($track);
278
	}
279
280
	/**
281
	 * Deletes a track
282
	 * @param int[] $fileIds file IDs of the tracks to delete
283
	 * @param string[]|null $userIds the target users; if omitted, the tracks matching the
284
	 *                      $fileIds are deleted from all users
285
	 * @return array|false  False is returned if no such track was found; otherwise array of six arrays
286
	 *         (named 'deletedTracks', 'remainingAlbums', 'remainingArtists', 'obsoleteAlbums',
287
	 *         'obsoleteArtists', and 'affectedUsers'). These contain the track, album, artist, and
288
	 *         user IDs of the deleted tracks. The 'obsolete' entities are such which no longer
289
	 *         have any tracks while 'remaining' entities have some left.
290
	 */
291 3
	public function deleteTracks($fileIds, $userIds = null) {
292 3
		$tracks = ($userIds !== null)
293
			? $this->mapper->findByFileIds($fileIds, $userIds)
294 3
			: $this->mapper->findAllByFileIds($fileIds);
295
296 3
		if (\count($tracks) === 0) {
297 1
			$result = false;
298
		} else {
299
			// delete all the matching tracks
300 2
			$trackIds = Util::extractIds($tracks);
301 2
			$this->deleteById($trackIds);
302
303
			// find all distinct albums, artists, and users of the deleted tracks
304 2
			$artists = [];
305 2
			$albums = [];
306 2
			$users = [];
307 2
			foreach ($tracks as $track) {
308 2
				$artists[$track->getArtistId()] = 1;
309 2
				$albums[$track->getAlbumId()] = 1;
310 2
				$users[$track->getUserId()] = 1;
311
			}
312 2
			$artists = \array_keys($artists);
313 2
			$albums = \array_keys($albums);
314 2
			$users = \array_keys($users);
315
316
			// categorize each artist as 'remaining' or 'obsolete'
317 2
			$remainingArtists = [];
318 2
			$obsoleteArtists = [];
319 2
			foreach ($artists as $artistId) {
320 2
				$result = $this->mapper->countByArtist($artistId);
321 2
				if ($result === '0') {
322 1
					$obsoleteArtists[] = $artistId;
323
				} else {
324 2
					$remainingArtists[] = $artistId;
325
				}
326
			}
327
328
			// categorize each album as 'remaining' or 'obsolete'
329 2
			$remainingAlbums = [];
330 2
			$obsoleteAlbums = [];
331 2
			foreach ($albums as $albumId) {
332 2
				$result = $this->mapper->countByAlbum($albumId);
333 2
				if ($result === '0') {
334 1
					$obsoleteAlbums[] = $albumId;
335
				} else {
336 2
					$remainingAlbums[] = $albumId;
337
				}
338
			}
339
340
			$result = [
341 2
				'deletedTracks'    => $trackIds,
342 2
				'remainingAlbums'  => $remainingAlbums,
343 2
				'remainingArtists' => $remainingArtists,
344 2
				'obsoleteAlbums'   => $obsoleteAlbums,
345 2
				'obsoleteArtists'  => $obsoleteArtists,
346 2
				'affectedUsers'    => $users
347
			];
348
		}
349
350 3
		return $result;
351
	}
352
}
353