diff options
Diffstat (limited to 'Jellyfin.Server/Migrations/Routines')
7 files changed, 180 insertions, 72 deletions
diff --git a/Jellyfin.Server/Migrations/Routines/FixPlaylistOwner.cs b/Jellyfin.Server/Migrations/Routines/FixPlaylistOwner.cs index 55aadae79..cf3182003 100644 --- a/Jellyfin.Server/Migrations/Routines/FixPlaylistOwner.cs +++ b/Jellyfin.Server/Migrations/Routines/FixPlaylistOwner.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Threading; using Jellyfin.Data.Enums; using MediaBrowser.Controller.Entities; @@ -53,13 +54,21 @@ internal class FixPlaylistOwner : IMigrationRoutine foreach (var playlist in playlists) { var shares = playlist.Shares; - var firstEditShare = shares.First(x => x.CanEdit); - if (firstEditShare is not null && Guid.TryParse(firstEditShare.UserId, out var guid)) + if (shares.Length > 0) { - playlist.OwnerUserId = guid; - playlist.Shares = shares.Where(x => x != firstEditShare).ToArray(); - - _playlistManager.UpdatePlaylistAsync(playlist).GetAwaiter().GetResult(); + var firstEditShare = shares.First(x => x.CanEdit); + if (firstEditShare is not null && Guid.TryParse(firstEditShare.UserId, out var guid)) + { + playlist.OwnerUserId = guid; + playlist.Shares = shares.Where(x => x != firstEditShare).ToArray(); + playlist.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).GetAwaiter().GetResult(); + _playlistManager.SavePlaylistFile(playlist); + } + } + else + { + playlist.OpenAccess = true; + playlist.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).GetAwaiter().GetResult(); } } } diff --git a/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs index e8a0af9f8..2f23cb1f8 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs @@ -5,9 +5,9 @@ using Emby.Server.Implementations.Data; using Jellyfin.Data.Entities; using Jellyfin.Server.Implementations; using MediaBrowser.Controller; +using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; -using SQLitePCL.pretty; namespace Jellyfin.Server.Migrations.Routines { @@ -61,17 +61,15 @@ namespace Jellyfin.Server.Migrations.Routines }; var dataPath = _paths.DataPath; - using (var connection = SQLite3.Open( - Path.Combine(dataPath, DbFilename), - ConnectionFlags.ReadOnly, - null)) + using (var connection = new SqliteConnection($"Filename={Path.Combine(dataPath, DbFilename)}")) { - using var userDbConnection = SQLite3.Open(Path.Combine(dataPath, "users.db"), ConnectionFlags.ReadOnly, null); + connection.Open(); + + using var userDbConnection = new SqliteConnection($"Filename={Path.Combine(dataPath, "users.db")}"); + userDbConnection.Open(); _logger.LogWarning("Migrating the activity database may take a while, do not stop Jellyfin."); using var dbContext = _provider.CreateDbContext(); - var queryResult = connection.Query("SELECT * FROM ActivityLog ORDER BY Id"); - // Make sure that the database is empty in case of failed migration due to power outages, etc. dbContext.ActivityLogs.RemoveRange(dbContext.ActivityLogs); dbContext.SaveChanges(); @@ -81,51 +79,52 @@ namespace Jellyfin.Server.Migrations.Routines var newEntries = new List<ActivityLog>(); + var queryResult = connection.Query("SELECT * FROM ActivityLog ORDER BY Id"); + foreach (var entry in queryResult) { - if (!logLevelDictionary.TryGetValue(entry[8].ToString(), out var severity)) + if (!logLevelDictionary.TryGetValue(entry.GetString(8), out var severity)) { severity = LogLevel.Trace; } var guid = Guid.Empty; - if (entry[6].SQLiteType != SQLiteType.Null && !Guid.TryParse(entry[6].ToString(), out guid)) + if (!entry.IsDBNull(6) && !entry.TryGetGuid(6, out guid)) { + var id = entry.GetString(6); // This is not a valid Guid, see if it is an internal ID from an old Emby schema - _logger.LogWarning("Invalid Guid in UserId column: {Guid}", entry[6].ToString()); + _logger.LogWarning("Invalid Guid in UserId column: {Guid}", id); using var statement = userDbConnection.PrepareStatement("SELECT guid FROM LocalUsersv2 WHERE Id=@Id"); - statement.TryBind("@Id", entry[6].ToString()); + statement.TryBind("@Id", id); - foreach (var row in statement.Query()) + using var reader = statement.ExecuteReader(); + if (reader.HasRows && reader.Read() && reader.TryGetGuid(0, out guid)) { - if (row.Count > 0 && Guid.TryParse(row[0].ToString(), out guid)) - { - // Successfully parsed a Guid from the user table. - break; - } + // Successfully parsed a Guid from the user table. + break; } } - var newEntry = new ActivityLog(entry[1].ToString(), entry[4].ToString(), guid) + var newEntry = new ActivityLog(entry.GetString(1), entry.GetString(4), guid) { - DateCreated = entry[7].ReadDateTime(), + DateCreated = entry.GetDateTime(7), LogSeverity = severity }; - if (entry[2].SQLiteType != SQLiteType.Null) + if (entry.TryGetString(2, out var result)) { - newEntry.Overview = entry[2].ToString(); + newEntry.Overview = result; } - if (entry[3].SQLiteType != SQLiteType.Null) + if (entry.TryGetString(3, out result)) { - newEntry.ShortOverview = entry[3].ToString(); + newEntry.ShortOverview = result; } - if (entry[5].SQLiteType != SQLiteType.Null) + if (entry.TryGetString(5, out result)) { - newEntry.ItemId = entry[5].ToString(); + newEntry.ItemId = result; } newEntries.Add(newEntry); diff --git a/Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs index 09daae0ff..c845beef2 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs @@ -6,9 +6,9 @@ using Jellyfin.Data.Entities.Security; using Jellyfin.Server.Implementations; using MediaBrowser.Controller; using MediaBrowser.Controller.Library; +using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; -using SQLitePCL.pretty; namespace Jellyfin.Server.Migrations.Routines { @@ -56,34 +56,32 @@ namespace Jellyfin.Server.Migrations.Routines public void Perform() { var dataPath = _appPaths.DataPath; - using (var connection = SQLite3.Open( - Path.Combine(dataPath, DbFilename), - ConnectionFlags.ReadOnly, - null)) + using (var connection = new SqliteConnection($"Filename={Path.Combine(dataPath, DbFilename)}")) { + connection.Open(); using var dbContext = _dbProvider.CreateDbContext(); var authenticatedDevices = connection.Query("SELECT * FROM Tokens"); foreach (var row in authenticatedDevices) { - var dateCreatedStr = row[9].ToString(); + var dateCreatedStr = row.GetString(9); _ = DateTime.TryParse(dateCreatedStr, out var dateCreated); - var dateLastActivityStr = row[10].ToString(); + var dateLastActivityStr = row.GetString(10); _ = DateTime.TryParse(dateLastActivityStr, out var dateLastActivity); - if (row[6].IsDbNull()) + if (row.IsDBNull(6)) { - dbContext.ApiKeys.Add(new ApiKey(row[3].ToString()) + dbContext.ApiKeys.Add(new ApiKey(row.GetString(3)) { - AccessToken = row[1].ToString(), + AccessToken = row.GetString(1), DateCreated = dateCreated, DateLastActivity = dateLastActivity }); } else { - var userId = new Guid(row[6].ToString()); + var userId = row.GetGuid(6); var user = _userManager.GetUserById(userId); if (user is null) { @@ -92,14 +90,14 @@ namespace Jellyfin.Server.Migrations.Routines } dbContext.Devices.Add(new Device( - new Guid(row[6].ToString()), - row[3].ToString(), - row[4].ToString(), - row[5].ToString(), - row[2].ToString()) + userId, + row.GetString(3), + row.GetString(4), + row.GetString(5), + row.GetString(2)) { - AccessToken = row[1].ToString(), - IsActive = row[8].ToBool(), + AccessToken = row.GetString(1), + IsActive = row.GetBoolean(8), DateCreated = dateCreated, DateLastActivity = dateLastActivity }); @@ -110,12 +108,12 @@ namespace Jellyfin.Server.Migrations.Routines var deviceIds = new HashSet<string>(); foreach (var row in deviceOptions) { - if (row[2].IsDbNull()) + if (row.IsDBNull(2)) { continue; } - var deviceId = row[2].ToString(); + var deviceId = row.GetString(2); if (deviceIds.Contains(deviceId)) { continue; @@ -125,7 +123,7 @@ namespace Jellyfin.Server.Migrations.Routines dbContext.DeviceOptions.Add(new DeviceOptions(deviceId) { - CustomName = row[1].IsDbNull() ? null : row[1].ToString() + CustomName = row.IsDBNull(1) ? null : row.GetString(1) }); } diff --git a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs index 8fe2b087d..249b39ae4 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs @@ -4,15 +4,16 @@ using System.IO; using System.Linq; using System.Text.Json; using System.Text.Json.Serialization; +using Emby.Server.Implementations.Data; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using Jellyfin.Server.Implementations; using MediaBrowser.Controller; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Dto; +using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; -using SQLitePCL.pretty; namespace Jellyfin.Server.Migrations.Routines { @@ -83,22 +84,23 @@ namespace Jellyfin.Server.Migrations.Routines var displayPrefs = new HashSet<string>(StringComparer.OrdinalIgnoreCase); var customDisplayPrefs = new HashSet<string>(StringComparer.OrdinalIgnoreCase); var dbFilePath = Path.Combine(_paths.DataPath, DbFilename); - using (var connection = SQLite3.Open(dbFilePath, ConnectionFlags.ReadOnly, null)) + using (var connection = new SqliteConnection($"Filename={dbFilePath}")) { + connection.Open(); using var dbContext = _provider.CreateDbContext(); var results = connection.Query("SELECT * FROM userdisplaypreferences"); foreach (var result in results) { - var dto = JsonSerializer.Deserialize<DisplayPreferencesDto>(result[3].ToBlob(), _jsonOptions); + var dto = JsonSerializer.Deserialize<DisplayPreferencesDto>(result.GetStream(3), _jsonOptions); if (dto is null) { continue; } - var itemId = new Guid(result[1].ToBlob()); - var dtoUserId = new Guid(result[1].ToBlob()); - var client = result[2].ToString(); + var itemId = result.GetGuid(1); + var dtoUserId = itemId; + var client = result.GetString(2); var displayPreferencesKey = $"{dtoUserId}|{itemId}|{client}"; if (displayPrefs.Contains(displayPreferencesKey)) { diff --git a/Jellyfin.Server/Migrations/Routines/MigrateRatingLevels.cs b/Jellyfin.Server/Migrations/Routines/MigrateRatingLevels.cs new file mode 100644 index 000000000..e1a43bb48 --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/MigrateRatingLevels.cs @@ -0,0 +1,99 @@ +using System; +using System.Globalization; +using System.IO; +using Emby.Server.Implementations.Data; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Globalization; +using Microsoft.Data.Sqlite; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Migrations.Routines +{ + /// <summary> + /// Migrate rating levels to new rating level system. + /// </summary> + internal class MigrateRatingLevels : IMigrationRoutine + { + private const string DbFilename = "library.db"; + private readonly ILogger<MigrateRatingLevels> _logger; + private readonly IServerApplicationPaths _applicationPaths; + private readonly ILocalizationManager _localizationManager; + + public MigrateRatingLevels( + IServerApplicationPaths applicationPaths, + ILoggerFactory loggerFactory, + ILocalizationManager localizationManager) + { + _applicationPaths = applicationPaths; + _localizationManager = localizationManager; + _logger = loggerFactory.CreateLogger<MigrateRatingLevels>(); + } + + /// <inheritdoc/> + public Guid Id => Guid.Parse("{67445D54-B895-4B24-9F4C-35CE0690EA07}"); + + /// <inheritdoc/> + public string Name => "MigrateRatingLevels"; + + /// <inheritdoc/> + public bool PerformOnNewInstall => false; + + /// <inheritdoc/> + public void Perform() + { + var dbPath = Path.Combine(_applicationPaths.DataPath, DbFilename); + + // Back up the database before modifying any entries + for (int i = 1; ; i++) + { + var bakPath = string.Format(CultureInfo.InvariantCulture, "{0}.bak{1}", dbPath, i); + if (!File.Exists(bakPath)) + { + try + { + File.Copy(dbPath, bakPath); + _logger.LogInformation("Library database backed up to {BackupPath}", bakPath); + break; + } + catch (Exception ex) + { + _logger.LogError(ex, "Cannot make a backup of {Library} at path {BackupPath}", DbFilename, bakPath); + throw; + } + } + } + + // Migrate parental rating strings to new levels + _logger.LogInformation("Recalculating parental rating levels based on rating string."); + using var connection = new SqliteConnection($"Filename={dbPath}"); + connection.Open(); + using (var transaction = connection.BeginTransaction()) + { + var queryResult = connection.Query("SELECT DISTINCT OfficialRating FROM TypedBaseItems"); + foreach (var entry in queryResult) + { + if (!entry.TryGetString(0, out var ratingString) || string.IsNullOrEmpty(ratingString)) + { + connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = NULL WHERE OfficialRating IS NULL OR OfficialRating='';"); + } + else + { + var ratingValue = _localizationManager.GetRatingLevel(ratingString).ToString(); + if (string.IsNullOrEmpty(ratingValue)) + { + ratingValue = "NULL"; + } + + using var statement = connection.PrepareStatement("UPDATE TypedBaseItems SET InheritedParentalRatingValue = @Value WHERE OfficialRating = @Rating;"); + statement.TryBind("@Value", ratingValue); + statement.TryBind("@Rating", ratingString); + statement.ExecuteNonQuery(); + } + } + + transaction.Commit(); + } + } + } +} diff --git a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs index 9bf1e6b80..4fee88b68 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs @@ -11,9 +11,9 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Users; +using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; -using SQLitePCL.pretty; using JsonSerializer = System.Text.Json.JsonSerializer; namespace Jellyfin.Server.Migrations.Routines @@ -64,8 +64,9 @@ namespace Jellyfin.Server.Migrations.Routines var dataPath = _paths.DataPath; _logger.LogInformation("Migrating the user database may take a while, do not stop Jellyfin."); - using (var connection = SQLite3.Open(Path.Combine(dataPath, DbFilename), ConnectionFlags.ReadOnly, null)) + using (var connection = new SqliteConnection($"Filename={Path.Combine(dataPath, DbFilename)}")) { + connection.Open(); var dbContext = _provider.CreateDbContext(); var queryResult = connection.Query("SELECT * FROM LocalUsersv2"); @@ -75,7 +76,7 @@ namespace Jellyfin.Server.Migrations.Routines foreach (var entry in queryResult) { - UserMockup? mockup = JsonSerializer.Deserialize<UserMockup>(entry[2].ToBlob(), JsonDefaults.Options); + UserMockup? mockup = JsonSerializer.Deserialize<UserMockup>(entry.GetStream(2), JsonDefaults.Options); if (mockup is null) { continue; @@ -108,8 +109,8 @@ namespace Jellyfin.Server.Migrations.Routines var user = new User(mockup.Name, policy.AuthenticationProviderId!, policy.PasswordResetProviderId!) { - Id = entry[1].ReadGuidFromBlob(), - InternalId = entry[0].ToInt64(), + Id = entry.GetGuid(1), + InternalId = entry.GetInt64(0), MaxParentalAgeRating = policy.MaxParentalRating, EnableUserPreferenceAccess = policy.EnableUserPreferenceAccess, RemoteClientBitrateLimit = policy.RemoteClientBitrateLimit, @@ -127,7 +128,6 @@ namespace Jellyfin.Server.Migrations.Routines RememberSubtitleSelections = config.RememberSubtitleSelections, SubtitleLanguagePreference = config.SubtitleLanguagePreference, Password = mockup.Password, - EasyPassword = mockup.EasyPassword, LastLoginDate = mockup.LastLoginDate, LastActivityDate = mockup.LastActivityDate }; diff --git a/Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs b/Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs index 6c26e47e1..7b0d9456d 100644 --- a/Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs +++ b/Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs @@ -1,10 +1,11 @@ using System; using System.Globalization; using System.IO; - +using System.Linq; +using Emby.Server.Implementations.Data; using MediaBrowser.Controller; +using Microsoft.Data.Sqlite; using Microsoft.Extensions.Logging; -using SQLitePCL.pretty; namespace Jellyfin.Server.Migrations.Routines { @@ -37,14 +38,13 @@ namespace Jellyfin.Server.Migrations.Routines { var dataPath = _paths.DataPath; var dbPath = Path.Combine(dataPath, DbFilename); - using (var connection = SQLite3.Open( - dbPath, - ConnectionFlags.ReadWrite, - null)) + using var connection = new SqliteConnection($"Filename={dbPath}"); + connection.Open(); + using (var transaction = connection.BeginTransaction()) { // Query the database for the ids of duplicate extras var queryResult = connection.Query("SELECT t1.Path FROM TypedBaseItems AS t1, TypedBaseItems AS t2 WHERE t1.Path=t2.Path AND t1.Type!=t2.Type AND t1.Type='MediaBrowser.Controller.Entities.Video'"); - var bads = string.Join(", ", queryResult.SelectScalarString()); + var bads = string.Join(", ", queryResult.Select(x => x.GetString(0))); // Do nothing if no duplicate extras were detected if (bads.Length == 0) @@ -76,6 +76,7 @@ namespace Jellyfin.Server.Migrations.Routines // Delete all duplicate extras _logger.LogInformation("Removing found duplicated extras for the following items: {DuplicateExtras}", bads); connection.Execute("DELETE FROM TypedBaseItems WHERE rowid IN (SELECT t1.rowid FROM TypedBaseItems AS t1, TypedBaseItems AS t2 WHERE t1.Path=t2.Path AND t1.Type!=t2.Type AND t1.Type='MediaBrowser.Controller.Entities.Video')"); + transaction.Commit(); } } } |
