using System; using System.Collections.Generic; using System.Data; using System.IO; using System.Linq; using FluentMigrator; using Newtonsoft.Json; using NzbDrone.Common.Serializer; using NzbDrone.Core.Datastore.Migration.Framework; namespace NzbDrone.Core.Datastore.Migration { [Migration(51)] public class download_client_import : NzbDroneMigrationBase { protected override void MainDbUpgrade() { Execute.WithConnection(EnableCompletedDownloadHandlingForNewUsers); Execute.WithConnection(ConvertFolderSettings); Execute.WithConnection(AssociateImportedHistoryItems); } private void EnableCompletedDownloadHandlingForNewUsers(IDbConnection conn, IDbTransaction tran) { using (var cmd = conn.CreateCommand()) { cmd.Transaction = tran; cmd.CommandText = "SELECT \"Value\" FROM \"Config\" WHERE \"Key\" = 'downloadedepisodesfolder'"; var result = cmd.ExecuteScalar(); if (result == null) { cmd.CommandText = "INSERT INTO \"Config\" (\"Key\", \"Value\") VALUES ('enablecompleteddownloadhandling', 'True')"; cmd.ExecuteNonQuery(); } } } private void ConvertFolderSettings(IDbConnection conn, IDbTransaction tran) { using (var downloadClientsCmd = conn.CreateCommand()) { downloadClientsCmd.Transaction = tran; downloadClientsCmd.CommandText = "SELECT \"Value\" FROM \"Config\" WHERE \"Key\" = 'downloadedepisodesfolder'"; var downloadedEpisodesFolder = downloadClientsCmd.ExecuteScalar() as string; downloadClientsCmd.Transaction = tran; downloadClientsCmd.CommandText = "SELECT \"Id\", \"Implementation\", \"Settings\", \"ConfigContract\" FROM \"DownloadClients\" WHERE \"ConfigContract\" = 'FolderSettings'"; using (var downloadClientReader = downloadClientsCmd.ExecuteReader()) { while (downloadClientReader.Read()) { var id = downloadClientReader.GetInt32(0); var implementation = downloadClientReader.GetString(1); var settings = downloadClientReader.GetString(2); var configContract = downloadClientReader.GetString(3); var settingsJson = JsonConvert.DeserializeObject(settings) as Newtonsoft.Json.Linq.JObject; if (implementation == "Blackhole") { var newSettings = new { NzbFolder = settingsJson.Value("folder"), WatchFolder = downloadedEpisodesFolder }.ToJson(); using (var updateCmd = conn.CreateCommand()) { updateCmd.Transaction = tran; updateCmd.CommandText = "UPDATE \"DownloadClients\" SET \"Implementation\" = ?, \"Settings\" = ?, \"ConfigContract\" = ? WHERE \"Id\" = ?"; updateCmd.AddParameter("UsenetBlackhole"); updateCmd.AddParameter(newSettings); updateCmd.AddParameter("UsenetBlackholeSettings"); updateCmd.AddParameter(id); updateCmd.ExecuteNonQuery(); } } else if (implementation == "Pneumatic") { var newSettings = new { NzbFolder = settingsJson.Value("folder") }.ToJson(); using (var updateCmd = conn.CreateCommand()) { updateCmd.Transaction = tran; updateCmd.CommandText = "UPDATE \"DownloadClients\" SET \"Settings\" = ?, \"ConfigContract\" = ? WHERE \"Id\" = ?"; updateCmd.AddParameter(newSettings); updateCmd.AddParameter("PneumaticSettings"); updateCmd.AddParameter(id); updateCmd.ExecuteNonQuery(); } } else { using (var updateCmd = conn.CreateCommand()) { updateCmd.Transaction = tran; updateCmd.CommandText = "DELETE FROM \"DownloadClients\" WHERE \"Id\" = ?"; updateCmd.AddParameter(id); updateCmd.ExecuteNonQuery(); } } } } } } private sealed class MigrationHistoryItem { public int Id { get; set; } public int EpisodeId { get; set; } public int SeriesId { get; set; } public string SourceTitle { get; set; } public DateTime Date { get; set; } public Dictionary Data { get; set; } public MigrationHistoryEventType EventType { get; set; } } private enum MigrationHistoryEventType { Unknown = 0, Grabbed = 1, SeriesFolderImported = 2, DownloadFolderImported = 3, DownloadFailed = 4 } private void AssociateImportedHistoryItems(IDbConnection conn, IDbTransaction tran) { var historyItems = new List(); using (var historyCmd = conn.CreateCommand()) { historyCmd.Transaction = tran; historyCmd.CommandText = "SELECT \"Id\", \"EpisodeId\", \"SeriesId\", \"SourceTitle\", \"Date\", \"Data\", \"EventType\" FROM \"History\" WHERE \"EventType\" IS NOT NULL"; using (var historyRead = historyCmd.ExecuteReader()) { while (historyRead.Read()) { historyItems.Add(new MigrationHistoryItem { Id = historyRead.GetInt32(0), EpisodeId = historyRead.GetInt32(1), SeriesId = historyRead.GetInt32(2), SourceTitle = historyRead.GetString(3), Date = historyRead.GetDateTime(4), Data = Json.Deserialize>(historyRead.GetString(5)), EventType = (MigrationHistoryEventType)historyRead.GetInt32(6) }); } } } var numHistoryItemsNotAssociated = historyItems.Count(v => v.EventType == MigrationHistoryEventType.DownloadFolderImported && v.Data.GetValueOrDefault("downloadClientId") == null); if (numHistoryItemsNotAssociated == 0) { return; } var historyItemsToAssociate = new Dictionary(); var historyItemsLookup = historyItems.ToLookup(v => v.EpisodeId); foreach (var historyItemGroup in historyItemsLookup) { var list = historyItemGroup.ToList(); for (var i = 0; i < list.Count - 1; i++) { var grabbedEvent = list[i]; if (grabbedEvent.EventType != MigrationHistoryEventType.Grabbed) { continue; } if (grabbedEvent.Data.GetValueOrDefault("downloadClient") == null || grabbedEvent.Data.GetValueOrDefault("downloadClientId") == null) { continue; } // Check if it is already associated with a failed/imported event. int j; for (j = i + 1; j < list.Count; j++) { if (list[j].EventType != MigrationHistoryEventType.DownloadFolderImported && list[j].EventType != MigrationHistoryEventType.DownloadFailed) { continue; } if (list[j].Data.ContainsKey("downloadClient") && list[j].Data["downloadClient"] == grabbedEvent.Data["downloadClient"] && list[j].Data.ContainsKey("downloadClientId") && list[j].Data["downloadClientId"] == grabbedEvent.Data["downloadClientId"]) { break; } } if (j != list.Count) { list.RemoveAt(j); list.RemoveAt(i--); continue; } var importedEvent = list[i + 1]; if (importedEvent.EventType != MigrationHistoryEventType.DownloadFolderImported) { continue; } var droppedPath = importedEvent.Data.GetValueOrDefault("droppedPath"); if (droppedPath != null && new FileInfo(droppedPath).Directory.Name == grabbedEvent.SourceTitle) { historyItemsToAssociate[importedEvent] = grabbedEvent; list.RemoveAt(i + 1); list.RemoveAt(i--); } } } foreach (var pair in historyItemsToAssociate) { using (var updateHistoryCmd = conn.CreateCommand()) { pair.Key.Data["downloadClient"] = pair.Value.Data["downloadClient"]; pair.Key.Data["downloadClientId"] = pair.Value.Data["downloadClientId"]; updateHistoryCmd.Transaction = tran; updateHistoryCmd.CommandText = "UPDATE \"History\" SET \"Data\" = ? WHERE \"Id\" = ?"; updateHistoryCmd.AddParameter(pair.Key.Data.ToJson()); updateHistoryCmd.AddParameter(pair.Key.Id); updateHistoryCmd.ExecuteNonQuery(); } } _logger.Info("Updated old History items. {0}/{1} old ImportedEvents were associated with GrabbedEvents.", historyItemsToAssociate.Count, numHistoryItemsNotAssociated); } } }