Passed
Push — feature/786_podcasts ( 95c51f...7b8be7 )
by Pauli
02:47
created

Maintenance   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 199
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 68
c 1
b 0
f 0
dl 0
loc 199
rs 10
wmc 20

15 Methods

Rating   Name   Duplication   Size   Complexity  
A removeTracksWithNoAlbum() 0 2 1
A removeTracksWithNoArtist() 0 2 1
A removeObsoleteBookmarks() 0 2 1
A removeUnreferencedDbRows() 0 11 1
A removeObsoleteArtistCoverImages() 0 2 1
A removeAlbumsWithNoArtist() 0 2 1
A removeObsoleteArtists() 0 3 1
A removeObsoleteAlbums() 0 2 1
A __construct() 0 3 1
A removeObsoleteTracks() 0 2 1
A removeObsoleteCoverImagesFromTable() 0 6 1
A removeObsoleteAlbumCoverImages() 0 2 1
B resetDb() 0 28 6
A cleanUp() 0 21 1
A removeObsoletePodcastEpisodes() 0 2 1
1
<?php declare(strict_types=1);
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 2014
12
 * @copyright Pauli Järvinen 2017 - 2020
13
 */
14
15
namespace OCA\Music\Db;
16
17
use OCP\IDBConnection;
18
19
use \OCA\Music\AppFramework\Core\Logger;
20
21
class Maintenance {
22
23
	/** @var IDBConnection */
24
	private $db;
25
	/** @var Logger */
26
	private $logger;
27
28
	public function __construct(IDBConnection $db, Logger $logger) {
29
		$this->db = $db;
30
		$this->logger = $logger;
31
	}
32
33
	/**
34
	 * Remove cover_file_id from album if the corresponding file does not exist
35
	 */
36
	private function removeObsoleteCoverImagesFromTable($table) {
37
		return $this->db->executeUpdate(
38
			"UPDATE `*PREFIX*$table` SET `cover_file_id` = NULL
39
			WHERE `cover_file_id` IS NOT NULL AND `cover_file_id` IN (
40
				SELECT `cover_file_id` FROM (
41
					SELECT `cover_file_id` FROM `*PREFIX*$table`
42
					LEFT JOIN `*PREFIX*filecache`
43
						ON `cover_file_id`=`fileid`
44
					WHERE `fileid` IS NULL
45
				) mysqlhack
46
			)"
47
		);
48
	}
49
50
	/**
51
	 * Remove cover_file_id from album if the corresponding file does not exist
52
	 */
53
	private function removeObsoleteAlbumCoverImages() {
54
		return $this->removeObsoleteCoverImagesFromTable('music_albums');
55
	}
56
57
	/**
58
	 * Remove cover_file_id from artist if the corresponding file does not exist
59
	 */
60
	private function removeObsoleteArtistCoverImages() {
61
		return $this->removeObsoleteCoverImagesFromTable('music_artists');
62
	}
63
64
	/**
65
	 * Remove all such rows from $tgtTable which don't have corresponding rows in $refTable
66
	 * so that $tgtTableKey = $refTableKey.
67
	 * @param string $tgtTable
68
	 * @param string $refTable
69
	 * @param string $tgtTableKey
70
	 * @param string $refTableKey
71
	 * @return Number of removed rows
72
	 */
73
	private function removeUnreferencedDbRows($tgtTable, $refTable, $tgtTableKey, $refTableKey) {
74
		$tgtTable = '*PREFIX*' . $tgtTable;
75
		$refTable = '*PREFIX*' . $refTable;
76
77
		return $this->db->executeUpdate(
78
			"DELETE FROM `$tgtTable` WHERE `id` IN (
79
				SELECT `id` FROM (
80
					SELECT `$tgtTable`.`id`
81
					FROM `$tgtTable` LEFT JOIN `$refTable`
82
					ON `$tgtTable`.`$tgtTableKey` = `$refTable`.`$refTableKey`
83
					WHERE `$refTable`.`$refTableKey` IS NULL
84
				) mysqlhack
85
			)"
86
		);
87
	}
88
89
	/**
90
	 * Remvoe tracks which do not have corresponding file in the file system
91
	 * @return Number of removed tracks
92
	 */
93
	private function removeObsoleteTracks() {
94
		return $this->removeUnreferencedDbRows('music_tracks', 'filecache', 'file_id', 'fileid');
95
	}
96
97
	/**
98
	 * Remove tracks which belong to non-existing album
99
	 * @return Number of removed tracks
100
	 */
101
	private function removeTracksWithNoAlbum() {
102
		return $this->removeUnreferencedDbRows('music_tracks', 'music_albums', 'album_id', 'id');
103
	}
104
105
	/**
106
	 * Remove tracks which are performed by non-existing artist
107
	 * @return Number of removed tracks
108
	 */
109
	private function removeTracksWithNoArtist() {
110
		return $this->removeUnreferencedDbRows('music_tracks', 'music_artists', 'artist_id', 'id');
111
	}
112
113
	/**
114
	 * Remove albums which have no tracks
115
	 * @return Number of removed albums
116
	 */
117
	private function removeObsoleteAlbums() {
118
		return $this->removeUnreferencedDbRows('music_albums', 'music_tracks', 'id', 'album_id');
119
	}
120
121
	/**
122
	 * Remove albums which have a non-existing album artist
123
	 * @return Number of removed albums
124
	 */
125
	private function removeAlbumsWithNoArtist() {
126
		return $this->removeUnreferencedDbRows('music_albums', 'music_artists', 'album_artist_id', 'id');
127
	}
128
129
	/**
130
	 * Remove artists which have no albums and no tracks
131
	 * @return Number of removed artists
132
	 */
133
	private function removeObsoleteArtists() {
134
		return $this->db->executeUpdate(
135
			'DELETE FROM `*PREFIX*music_artists` WHERE `id` NOT IN (
136
				SELECT `album_artist_id` FROM `*PREFIX*music_albums`
137
				UNION
138
				SELECT `artist_id` FROM `*PREFIX*music_tracks`
139
			)'
140
		);
141
	}
142
143
	/**
144
	 * Remove bookmarks referring tracks which do not exist
145
	 * @return Number of removed bookmarks
146
	 */
147
	private function removeObsoleteBookmarks() {
148
		return $this->removeUnreferencedDbRows('music_bookmarks', 'music_tracks', 'track_id', 'id');
149
	}
150
151
	/**
152
	 * Remove podcast episodes which have a non-existing podcast channel
153
	 * @return Number of removed albums
154
	 */
155
	private function removeObsoletePodcastEpisodes() {
156
		return $this->removeUnreferencedDbRows('music_podcast_episodes', 'music_podcast_channels', 'channel_id', 'id');
157
	}
158
159
	/**
160
	 * Removes orphaned data from the database
161
	 * @return array describing the number of removed entries per type
162
	 */
163
	public function cleanUp() {
164
		$removedCovers = $this->removeObsoleteAlbumCoverImages();
165
		$removedCovers += $this->removeObsoleteArtistCoverImages();
166
167
		$removedTracks = $this->removeObsoleteTracks();
168
		$removedAlbums = $this->removeObsoleteAlbums();
169
		$removedArtists = $this->removeObsoleteArtists();
170
		$removedBookmarks = $this->removeObsoleteBookmarks();
171
		$removedEpisodes = $this->removeObsoletePodcastEpisodes();
172
173
		$removedAlbums += $this->removeAlbumsWithNoArtist();
174
		$removedTracks += $this->removeTracksWithNoAlbum();
175
		$removedTracks += $this->removeTracksWithNoArtist();
176
177
		return [
178
			'covers' => $removedCovers,
179
			'artists' => $removedArtists,
180
			'albums' => $removedAlbums,
181
			'tracks' => $removedTracks,
182
			'bookmarks' => $removedBookmarks,
183
			'podcast_episodes' => $removedEpisodes
184
		];
185
	}
186
187
	/**
188
	 * Wipe clean the music database of the given user, or all users
189
	 * @param string $userId
190
	 * @param boolean $allUsers
191
	 */
192
	public function resetDb($userId, $allUsers = false) {
193
		if ($userId && $allUsers) {
194
			throw new \InvalidArgumentException('userId should be null if allUsers targeted');
195
		}
196
197
		$sqls = [
198
				'DELETE FROM `*PREFIX*music_tracks`',
199
				'DELETE FROM `*PREFIX*music_albums`',
200
				'DELETE FROM `*PREFIX*music_artists`',
201
				'DELETE FROM `*PREFIX*music_playlists`',
202
				'DELETE FROM `*PREFIX*music_genres`',
203
				'DELETE FROM `*PREFIX*music_bookmarks`',
204
				'DELETE FROM `*PREFIX*music_cache`'
205
		];
206
207
		foreach ($sqls as $sql) {
208
			$params = [];
209
			if (!$allUsers) {
210
				$sql .=  ' WHERE `user_id` = ?';
211
				$params[] = $userId;
212
			}
213
			$this->db->executeUpdate($sql, $params);
214
		}
215
216
		if ($allUsers) {
217
			$this->logger->log("Erased music databases of all users", 'info');
218
		} else {
219
			$this->logger->log("Erased music database of user $userId", 'info');
220
		}
221
	}
222
}
223