Passed
Push — master ( 35b7b2...cb91d5 )
by Pauli
02:01
created

DiskNumberMigration::moveTracksBetweenAlbums()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 4
nc 1
nop 2
dl 0
loc 5
rs 10
c 1
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 Pauli Järvinen <[email protected]>
10
 * @copyright Pauli Järvinen 2020
11
 */
12
13
namespace OCA\Music\Migration;
14
15
use OCP\IConfig;
0 ignored issues
show
Bug introduced by
The type OCP\IConfig 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...
16
use OCP\IDBConnection;
0 ignored issues
show
Bug introduced by
The type OCP\IDBConnection 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...
17
use OCP\Migration\IOutput;
0 ignored issues
show
Bug introduced by
The type OCP\Migration\IOutput 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...
18
use OCP\Migration\IRepairStep;
0 ignored issues
show
Bug introduced by
The type OCP\Migration\IRepairStep 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...
19
20
class DiskNumberMigration implements IRepairStep {
21
22
	/** @var IDBConnection */
23
	private $db;
24
25
	/** @var IConfig */
26
	private $config;
27
28
	/** @var int[] */
29
	private $obsoleteAlbums;
30
31
	public function __construct(IDBConnection $connection, IConfig $config) {
32
		$this->db = $connection;
33
		$this->config = $config;
34
		$this->obsoleteAlbums = [];
35
	}
36
37
	public function getName() {
38
		return 'Combine multi-disk albums and store disk numbers per track';
39
	}
40
41
	/**
42
	 * @inheritdoc
43
	 */
44
	public function run(IOutput $output) {
45
		$installedVersion = $this->config->getAppValue('music', 'installed_version');
46
47
		if (\version_compare($installedVersion, '0.13.0', '<=')) {
48
			$n = $this->copyDiskNumberToTracks();
49
			$output->info("$n tracks were updated with a disk number");
50
51
			$n = $this->combineMultiDiskAlbums();
52
			$output->info("$n tracks were assinged to new albums when combining multi-disk albums");
53
54
			$n = $this->removeObsoleteAlbums();
55
			$output->info("$n obsolete album entries were removed from the database");
56
57
			$n = $this->reEvaluateAlbumHashes();
58
			$output->info("$n albums were updated with new hashes");
59
60
			$n = $this->removeDiskNumbersFromAlbums();
61
			$output->info("obsolete disk number field was nullified in $n albums");
62
		}
63
	}
64
65
	/**
66
	 * Copy disk numbers from the albums table to the tracks table
67
	 */
68
	private function copyDiskNumberToTracks() {
69
		$sql = 'UPDATE `*PREFIX*music_tracks` '. 
70
				'SET `disk` = (SELECT `disk` '. 
71
				'              FROM `*PREFIX*music_albums` '.
72
				'              WHERE `*PREFIX*music_tracks`.`album_id` = `*PREFIX*music_albums`.`id`) '.
73
				'WHERE `disk` IS NULL';
74
		return $this->db->executeUpdate($sql);
75
	}
76
77
	/**
78
	 * Move all tracks belonging to separate disks of the same album title to the
79
	 * album entity matching the first of those disks. The album entities matching
80
	 * the rest of the disks become obsolete.
81
	 */
82
	private function combineMultiDiskAlbums() {
83
		$sql = 'SELECT `id`, `user_id`, `album_artist_id`, `name` '.
84
				'FROM `*PREFIX*music_albums` '.
85
				'ORDER BY `user_id`, `album_artist_id`, `name`';
86
87
		$rows = $this->db->executeQuery($sql)->fetchAll();
88
89
		$affectedTracks = 0;
90
		$prevId = null;
91
		$prevUser = null;
92
		$prevArtist = null;
93
		$prevName = null;
94
		foreach ($rows as $row) {
95
			$id = $row['id'];
96
			$user = $row['user_id'];
97
			$artist = $row['album_artist_id'];
98
			$name = \mb_strtolower($row['name']);
99
100
			if ($user === $prevUser && $artist === $prevArtist && $name === $prevName) {
101
				// another disk of the same album => merge
102
				$affectedTracks += $this->moveTracksBetweenAlbums($id, $prevId);
103
				$this->obsoleteAlbums[] = $id;
104
			}
105
			else {
106
				$prevId = $id;
107
				$prevUser = $user;
108
				$prevArtist = $artist;
109
				$prevName = $name;
110
			}
111
		}
112
113
		return $affectedTracks;
114
	}
115
116
	/**
117
	 * Move all tracks from the source album entity to the destination album entity
118
	 * @param int $sourceAlbum ID
119
	 * @param int $destinationAlbum ID
120
	 */
121
	private function moveTracksBetweenAlbums($sourceAlbum, $destinationAlbum) {
122
		$sql = 'UPDATE `*PREFIX*music_tracks` '.
123
				'SET `album_id` = ? '.
124
				'WHERE `album_id` = ?';
125
		return $this->db->executeUpdate($sql, [$destinationAlbum, $sourceAlbum]);
126
	}
127
128
	/**
129
	 * Delete from the albums table those rows which were made obsolete by the previous steps
130
	 */
131
	private function removeObsoleteAlbums() {
132
		$sql = 'DELETE FROM `*PREFIX*music_albums` '.
133
				'WHERE `id` IN '. $this->questionMarks(count($this->obsoleteAlbums));
134
135
		return $this->db->executeUpdate($sql, $this->obsoleteAlbums);
136
	}
137
138
	/**
139
	 * Recalculate the hashes for all albums in the table. The disk number is no longer part
140
	 * of the calculation schema.
141
	 */
142
	private function reEvaluateAlbumHashes() {
143
		$sql = 'SELECT `id`, `name`, `album_artist_id` '.
144
				'FROM `*PREFIX*music_albums`';
145
		$rows = $this->db->executeQuery($sql)->fetchAll();
146
147
		$affectedRows = 0;
148
		foreach ($rows as $row) {
149
			$lowerName = \mb_strtolower($row['name']);
150
			$artist = $row['album_artist_id'];
151
			$hash = \hash('md5', "$lowerName|$artist");
152
153
			$affectedRows += $this->db->executeUpdate(
154
				'UPDATE `*PREFIX*music_albums` SET `hash` = ? WHERE `id` = ?',
155
				[$hash, $row['id']]
156
			);
157
		}
158
159
		return $affectedRows;
160
	}
161
162
	/**
163
	 * Set all disk numbers stored in the albums table as NULL.
164
	 */
165
	private function removeDiskNumbersFromAlbums() {
166
		$sql = 'UPDATE `*PREFIX*music_albums` SET `disk` = NULL';
167
		return $this->db->executeUpdate($sql);
168
	}
169
170
	/**
171
	 * helper creating a string like '(?,?,?)' with the specified number of elements
172
	 * @param int $count
173
	 */
174
	private function questionMarks($count) {
175
		$questionMarks = [];
176
		for ($i = 0; $i < $count; $i++) {
177
			$questionMarks[] = '?';
178
		}
179
		return '(' . \implode(',', $questionMarks) . ')';
180
	}
181
}
182