Skip to content

Commit

Permalink
Fix migration on NC from Music app older than v1.0.0
Browse files Browse the repository at this point in the history
When defining the DB schema for Nextcloud was moved to use the
migrations mechanism in v1.2.1 (commit 6982d91), a problem was
introduced: The migration worked only either on a clean database (no
earlier versions of Music installed) or on v1.0.0 or newer. Installing
over any v0.x.y got broken. This was because the auto-generated
migration script Version010200Date20210513171803 newer altered the DB
schema of existing tables; it just created tables which were missing.

The not so well-working migration script has now been replaced with the
hand-written Version010000Date20210903000000. This script checks
separately that each needed column exists in the database and creates
the missing ones. It also removes couple of obsolete columns (removed
over the years) if those are still present in the database.

This new script should be able to migrate the database from any version
starting from v0.4.0 to the v1.0.0 level. Most of these versions have
not been tested, though.

refs owncloud#889
refs owncloud#883
  • Loading branch information
paulijar committed Sep 2, 2021
1 parent aeae186 commit ff96ad2
Show file tree
Hide file tree
Showing 2 changed files with 273 additions and 401 deletions.
273 changes: 273 additions & 0 deletions lib/Migration/Version010000Date20210903000000.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
<?php

declare(strict_types=1);

namespace OCA\Music\Migration;

use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\Migration\SimpleMigrationStep;
use OCP\Migration\IOutput;

/**
* Migrate the DB schema to Music v1.0.0 level from any previous version starting from v0.4.0
*/
class Version010000Date20210903000000 extends SimpleMigrationStep {

/**
* @param IOutput $output
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
* @param array $options
*/
public function preSchemaChange(IOutput $output, Closure $schemaClosure, array $options) {
}

/**
* @param IOutput $output
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
* @param array $options
* @return null|ISchemaWrapper
*/
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();

$this->migrateMusicArtists($schema);
$this->migrateMusicAlbums($schema);
$this->migrateMusicTracks($schema);
$this->migrateMusicPlaylists($schema);
$this->migrateMusicGenres($schema);
$this->migrateMusicRadioStations($schema);
$this->migrateMusicAmpacheSessions($schema);
$this->migrateAmpacheUsers($schema);
$this->migrateMusicCache($schema);
$this->migrateMusicBookmarks($schema);

return $schema;
}

/**
* @param IOutput $output
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
* @param array $options
*/
public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options) {
}

private function migrateMusicArtists(ISchemaWrapper $schema) {
$table = $this->getOrCreateTable($schema, 'music_artists');
$this->setColumns($table, [
[ 'id', 'integer', ['autoincrement' => true, 'notnull' => true, 'unsigned' => true] ],
[ 'user_id', 'string', ['notnull' => true, 'length' => 64] ],
[ 'name', 'string', ['notnull' => false, 'length' => 256] ],
[ 'cover_file_id', 'bigint', ['notnull' => false,'length' => 10] ],
[ 'mbid', 'string', ['notnull' => false, 'length' => 36] ],
[ 'hash', 'string', ['notnull' => true, 'length' => 32] ],
[ 'starred', 'datetime', ['notnull' => false] ],
[ 'created', 'datetime', ['notnull' => false] ],
[ 'updated', 'datetime', ['notnull' => false] ]
]);
$this->dropObsoleteColumn($table, 'image');

$this->setPrimaryKey($table, ['id']);
$this->setUniqueIndex($table, 'user_id_hash_idx', ['user_id', 'hash']);
}

private function migrateMusicAlbums(ISchemaWrapper $schema) {
$table = $this->getOrCreateTable($schema, 'music_albums');
$this->setColumns($table, [
[ 'id', 'integer', ['autoincrement' => true, 'notnull' => true, 'unsigned' => true] ],
[ 'user_id', 'string', ['notnull' => true, 'length' => 64] ],
[ 'name', 'string', ['notnull' => false, 'length' => 256] ],
[ 'cover_file_id', 'bigint', ['notnull' => false, 'length' => 10] ],
[ 'mbid', 'string', ['notnull' => false, 'length' => 36] ],
[ 'disk', 'integer', ['notnull' => false, 'unsigned' => true] ],
[ 'mbid_group', 'string', ['notnull' => false, 'length' => 36] ],
[ 'album_artist_id', 'integer', ['notnull' => false] ],
[ 'hash', 'string', ['notnull' => true, 'length' => 32] ],
[ 'starred', 'datetime', ['notnull' => false] ],
[ 'created', 'datetime', ['notnull' => false] ],
[ 'updated', 'datetime', ['notnull' => false] ]
]);
$this->dropObsoleteColumn($table, 'year');

$this->setPrimaryKey($table, ['id']);
$this->setIndex($table, 'ma_cover_file_id_idx', ['cover_file_id']);
$this->setIndex($table, 'ma_album_artist_id_idx', ['album_artist_id']);
$this->setUniqueIndex($table, 'ma_user_id_hash_idx', ['user_id', 'hash']);
}

private function migrateMusicTracks(ISchemaWrapper $schema) {
$table = $this->getOrCreateTable($schema, 'music_tracks');
$this->setColumns($table, [
[ 'id', 'integer', ['autoincrement' => true, 'notnull' => true, 'unsigned' => true, ] ],
[ 'user_id', 'string', ['notnull' => true, 'length' => 64, ] ],
[ 'title', 'string', ['notnull' => true, 'length' => 256, ] ],
[ 'number', 'integer', ['notnull' => false, 'unsigned' => true] ],
[ 'disk', 'integer', ['notnull' => false, 'unsigned' => true] ],
[ 'year', 'integer', ['notnull' => false, 'unsigned' => true] ],
[ 'artist_id', 'integer', ['notnull' => false] ],
[ 'album_id', 'integer', ['notnull' => false] ],
[ 'length', 'integer', ['notnull' => false, 'unsigned' => true] ],
[ 'file_id', 'bigint', ['notnull' => true, 'length' => 10] ],
[ 'bitrate', 'integer', ['notnull' => false, 'unsigned' => true] ],
[ 'mimetype', 'string', ['notnull' => true, 'length' => 256] ],
[ 'mbid', 'string', ['notnull' => false, 'length' => 36] ],
[ 'starred', 'datetime', ['notnull' => false] ],
[ 'genre_id', 'integer', ['notnull' => false, 'unsigned' => true, ] ],
[ 'created', 'datetime', ['notnull' => false] ],
[ 'updated', 'datetime', ['notnull' => false] ]
]);

$this->setPrimaryKey($table, ['id']);
$this->setIndex($table, 'music_tracks_artist_id_idx', ['artist_id']);
$this->setIndex($table, 'music_tracks_album_id_idx', ['album_id']);
$this->setIndex($table, 'music_tracks_user_id_idx', ['user_id']);
$this->setUniqueIndex($table, 'music_tracks_file_user_id_idx', ['file_id', 'user_id']);
}

private function migrateMusicPlaylists(ISchemaWrapper $schema) {
$table = $this->getOrCreateTable($schema, 'music_playlists');
$this->setColumns($table, [
[ 'id', 'integer', ['autoincrement' => true, 'notnull' => true, 'unsigned' => true] ],
[ 'user_id', 'string', ['notnull' => true, 'length' => 64] ],
[ 'name', 'string', ['notnull' => false, 'length' => 256] ],
[ 'track_ids', 'text', ['notnull' => false] ],
[ 'created', 'datetime', ['notnull' => false] ],
[ 'updated', 'datetime', ['notnull' => false] ],
[ 'comment', 'string', ['notnull' => false, 'length' => 256] ]
]);

$this->setPrimaryKey($table, ['id']);
}

private function migrateMusicGenres(ISchemaWrapper $schema) {
$table = $this->getOrCreateTable($schema, 'music_genres');
$this->setColumns($table, [
[ 'id', 'integer', ['autoincrement' => true, 'notnull' => true, 'unsigned' => true] ],
[ 'user_id', 'string', ['notnull' => true, 'length' => 64] ],
[ 'name', 'string', ['notnull' => false, 'length' => 64] ],
[ 'lower_name', 'string', ['notnull' => false, 'length' => 64] ],
[ 'created', 'datetime', ['notnull' => false] ],
[ 'updated', 'datetime', ['notnull' => false] ]
]);

$this->setPrimaryKey($table, ['id']);
$this->setUniqueIndex($table, 'mg_lower_name_user_id_idx', ['lower_name', 'user_id']);
}

private function migrateMusicRadioStations(ISchemaWrapper $schema) {
$table = $this->getOrCreateTable($schema, 'music_radio_stations');
$this->setColumns($table, [
[ 'id', 'integer', ['autoincrement' => true, 'notnull' => true, 'unsigned' => true] ],
[ 'user_id', 'string', ['notnull' => true, 'length' => 64] ],
[ 'name', 'string', ['notnull' => false, 'length' => 256] ],
[ 'stream_url', 'string', ['notnull' => true, 'length' => 2048] ],
[ 'home_url', 'string', ['notnull' => false, 'length' => 2048] ],
[ 'created', 'datetime', ['notnull' => false] ],
[ 'updated', 'datetime', ['notnull' => false] ]
]);

$this->setPrimaryKey($table, ['id']);
}

private function migrateMusicAmpacheSessions(ISchemaWrapper $schema) {
$table = $this->getOrCreateTable($schema, 'music_ampache_sessions');
$this->setColumns($table, [
[ 'id', 'integer', ['autoincrement' => true, 'notnull' => true, 'unsigned' => true] ],
[ 'user_id', 'string', ['notnull' => true, 'length' => 64] ],
[ 'token', 'string', ['notnull' => true, 'length' => 64] ],
[ 'expiry', 'integer', ['notnull' => true,'unsigned' => true] ]
]);

$this->setPrimaryKey($table, ['id']);
$this->setUniqueIndex($table, 'music_ampache_sessions_index', ['token']);
}

private function migrateAmpacheUsers(ISchemaWrapper $schema) {
$table = $this->getOrCreateTable($schema, 'music_ampache_users');
$this->setColumns($table, [
[ 'id', 'integer', ['autoincrement' => true, 'notnull' => true, 'length' => 4] ],
[ 'user_id', 'string', ['notnull' => true, 'length' => 64] ],
[ 'description', 'string', ['notnull' => false, 'length' => 64] ],
[ 'hash', 'string', ['notnull' => true, 'length' => 64] ]
]);

$this->setPrimaryKey($table, ['id']);
$this->setUniqueIndex($table, 'music_ampache_users_index', ['hash', 'user_id']);
}

private function migrateMusicCache(ISchemaWrapper $schema) {
$table = $this->getOrCreateTable($schema, 'music_cache');
$this->setColumns($table, [
[ 'id', 'integer', ['autoincrement' => true, 'notnull' => true, 'unsigned' => true] ],
[ 'key', 'string', ['notnull' => true, 'length' => 64] ],
[ 'user_id', 'string', ['notnull' => true, 'length' => 64] ],
[ 'data', 'text', ['notnull' => false] ],
]);

$this->setPrimaryKey($table, ['id']);
$this->setUniqueIndex($table, 'music_cache_index', ['user_id', 'key']);
}

private function migrateMusicBookmarks(ISchemaWrapper $schema) {
$table = $this->getOrCreateTable($schema, 'music_bookmarks');
$this->setColumns($table, [
[ 'id', 'integer', ['autoincrement' => true, 'notnull' => true, 'unsigned' => true] ],
[ 'user_id', 'string', ['notnull' => true, 'length' => 64] ],
[ 'track_id', 'integer', ['notnull' => true] ],
[ 'position', 'integer', ['notnull' => true] ],
[ 'comment', 'string', ['notnull' => false, 'length' => 256] ],
[ 'created', 'datetime', ['notnull' => false] ],
[ 'updated', 'datetime', ['notnull' => false] ]
]);

$this->setPrimaryKey($table, ['id']);
$this->setUniqueIndex($table, 'music_bookmarks_user_track', ['user_id', 'track_id']);
}

private function getOrCreateTable(ISchemaWrapper $schema, string $name) {
if (!$schema->hasTable($name)) {
return $schema->createTable($name);
} else {
return $schema->getTable($name);
}
}

private function setColumn($table, string $name, string $type, array $args) {
if (!$table->hasColumn($name)) {
$table->addColumn($name, $type, $args);
}
}

private function setColumns($table, array $nameTypeArgsPerCol) {
foreach ($nameTypeArgsPerCol as $nameTypeArgs) {
list($name, $type, $args) = $nameTypeArgs;
$this->setColumn($table, $name, $type, $args);
}
}

private function dropObsoleteColumn($table, string $name) {
if ($table->hasColumn($name)) {
$table->dropColumn($name);
}
}

private function setIndex($table, string $name, array $columns) {
if (!$table->hasIndex($name)) {
$table->addIndex($columns, $name);
}
}

private function setUniqueIndex($table, string $name, array $columns) {
if (!$table->hasIndex($name)) {
$table->addUniqueIndex($columns, $name);
}
}

private function setPrimaryKey($table, array $columns) {
if (!$table->hasPrimaryKey()) {
$table->setPrimaryKey($columns);
}
}
}
Loading

0 comments on commit ff96ad2

Please # to comment.