mirror of
https://github.com/Radarr/Radarr.git
synced 2024-11-04 10:02:40 +01:00
commit
6959f6e13a
@ -16,7 +16,6 @@
|
||||
using NzbDrone.Core.Validation.Paths;
|
||||
using NzbDrone.Core.DataAugmentation.Scene;
|
||||
using NzbDrone.SignalR;
|
||||
using Omu.ValueInjecter;
|
||||
|
||||
namespace NzbDrone.Api.Series
|
||||
{
|
||||
|
@ -1,15 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Api.Mapping;
|
||||
using NzbDrone.Core.Datastore.Events;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Tags;
|
||||
using NzbDrone.SignalR;
|
||||
|
||||
namespace NzbDrone.Api.Tags
|
||||
{
|
||||
public class TagModule : NzbDroneRestModule<TagResource>
|
||||
public class TagModule : NzbDroneRestModuleWithSignalR<TagResource, Tag>, IHandle<TagsUpdatedEvent>
|
||||
{
|
||||
private readonly ITagService _tagService;
|
||||
|
||||
public TagModule(ITagService tagService)
|
||||
public TagModule(IBroadcastSignalRMessage signalRBroadcaster,
|
||||
ITagService tagService)
|
||||
: base(signalRBroadcaster)
|
||||
{
|
||||
_tagService = tagService;
|
||||
|
||||
@ -44,5 +49,10 @@ private void Delete(Int32 id)
|
||||
{
|
||||
_tagService.Delete(id);
|
||||
}
|
||||
|
||||
public void Handle(TagsUpdatedEvent message)
|
||||
{
|
||||
BroadcastResourceChange(ModelAction.Sync);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,148 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Jobs;
|
||||
using NzbDrone.Core.Tags;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Test.Datastore.Migration
|
||||
{
|
||||
[TestFixture]
|
||||
public class dedupe_tags : MigrationTest<Core.Datastore.Migration.dedupe_tags>
|
||||
{
|
||||
[Test]
|
||||
public void should_not_fail_if_series_tags_are_null()
|
||||
{
|
||||
WithTestDb(c =>
|
||||
{
|
||||
c.Insert.IntoTable("Series").Row(new
|
||||
{
|
||||
Tvdbid = 1,
|
||||
TvRageId = 1,
|
||||
Title = "Title1",
|
||||
CleanTitle = "CleanTitle1",
|
||||
Status = 1,
|
||||
Images = "",
|
||||
Path = "c:\\test",
|
||||
Monitored = 1,
|
||||
SeasonFolder = 1,
|
||||
Runtime = 0,
|
||||
SeriesType = 0,
|
||||
UseSceneNumbering = 0,
|
||||
LastInfoSync = "2000-01-01 00:00:00"
|
||||
});
|
||||
|
||||
c.Insert.IntoTable("Tags").Row(new
|
||||
{
|
||||
Label = "test"
|
||||
});
|
||||
});
|
||||
|
||||
Mocker.Resolve<TagRepository>().All().Should().HaveCount(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_fail_if_series_tags_are_empty()
|
||||
{
|
||||
WithTestDb(c =>
|
||||
{
|
||||
c.Insert.IntoTable("Series").Row(new
|
||||
{
|
||||
Tvdbid = 1,
|
||||
TvRageId = 1,
|
||||
Title = "Title1",
|
||||
CleanTitle = "CleanTitle1",
|
||||
Status = 1,
|
||||
Images = "",
|
||||
Path = "c:\\test",
|
||||
Monitored = 1,
|
||||
SeasonFolder = 1,
|
||||
Runtime = 0,
|
||||
SeriesType = 0,
|
||||
UseSceneNumbering = 0,
|
||||
LastInfoSync = "2000-01-01 00:00:00",
|
||||
Tags = "[]"
|
||||
});
|
||||
|
||||
c.Insert.IntoTable("Tags").Row(new
|
||||
{
|
||||
Label = "test"
|
||||
});
|
||||
});
|
||||
|
||||
Mocker.Resolve<TagRepository>().All().Should().HaveCount(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_remove_duplicate_labels_from_tags()
|
||||
{
|
||||
WithTestDb(c =>
|
||||
{
|
||||
c.Insert.IntoTable("Tags").Row(new
|
||||
{
|
||||
Label = "test"
|
||||
});
|
||||
|
||||
c.Insert.IntoTable("Tags").Row(new
|
||||
{
|
||||
Label = "test"
|
||||
});
|
||||
});
|
||||
|
||||
Mocker.Resolve<TagRepository>().All().Should().HaveCount(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_allow_duplicate_tag_to_be_inserted()
|
||||
{
|
||||
WithTestDb(c =>
|
||||
{
|
||||
c.Insert.IntoTable("Tags").Row(new
|
||||
{
|
||||
Label = "test"
|
||||
});
|
||||
});
|
||||
|
||||
Assert.That(() => Mocker.Resolve<TagRepository>().Insert(new Tag { Label = "test" }), Throws.Exception);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_replace_duplicated_tag_with_proper_tag()
|
||||
{
|
||||
WithTestDb(c =>
|
||||
{
|
||||
c.Insert.IntoTable("Series").Row(new
|
||||
{
|
||||
Tvdbid = 1,
|
||||
TvRageId = 1,
|
||||
Title = "Title1",
|
||||
CleanTitle = "CleanTitle1",
|
||||
Status = 1,
|
||||
Images = "",
|
||||
Path = "c:\\test",
|
||||
Monitored = 1,
|
||||
SeasonFolder = 1,
|
||||
Runtime = 0,
|
||||
SeriesType = 0,
|
||||
UseSceneNumbering = 0,
|
||||
LastInfoSync = "2000-01-01 00:00:00",
|
||||
Tags = "[2]"
|
||||
});
|
||||
|
||||
c.Insert.IntoTable("Tags").Row(new
|
||||
{
|
||||
Label = "test"
|
||||
});
|
||||
|
||||
c.Insert.IntoTable("Tags").Row(new
|
||||
{
|
||||
Label = "test"
|
||||
});
|
||||
});
|
||||
|
||||
Mocker.Resolve<SeriesRepository>().Get(1).Tags.First().Should().Be(1);
|
||||
}
|
||||
}
|
||||
}
|
@ -121,6 +121,7 @@
|
||||
<Compile Include="Datastore\Migration\074_disable_eztv.cs" />
|
||||
<Compile Include="Datastore\Migration\072_history_grabIdFixture.cs" />
|
||||
<Compile Include="Datastore\Migration\070_delay_profileFixture.cs" />
|
||||
<Compile Include="Datastore\Migration\079_dedupe_tagsFixture.cs" />
|
||||
<Compile Include="Datastore\Migration\075_force_lib_updateFixture.cs" />
|
||||
<Compile Include="Datastore\ObjectDatabaseFixture.cs" />
|
||||
<Compile Include="Datastore\PagingSpecExtensionsTests\PagingOffsetFixture.cs" />
|
||||
|
@ -16,6 +16,9 @@ protected override void MainDbUpgrade()
|
||||
|
||||
Alter.Table("Notifications")
|
||||
.AddColumn("Tags").AsString().Nullable();
|
||||
|
||||
Execute.Sql("UPDATE Series SET Tags = '[]'");
|
||||
Execute.Sql("UPDATE Notifications SET Tags = '[]'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
157
src/NzbDrone.Core/Datastore/Migration/079_dedupe_tags.cs
Normal file
157
src/NzbDrone.Core/Datastore/Migration/079_dedupe_tags.cs
Normal file
@ -0,0 +1,157 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using FluentMigrator;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(79)]
|
||||
public class dedupe_tags : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Execute.WithConnection(CleanupTags);
|
||||
|
||||
Alter.Table("Tags").AlterColumn("Label").AsString().Unique();
|
||||
}
|
||||
|
||||
private void CleanupTags(IDbConnection conn, IDbTransaction tran)
|
||||
{
|
||||
var tags = GetTags(conn, tran);
|
||||
var grouped = tags.GroupBy(t => t.Label.ToLowerInvariant());
|
||||
var replacements = new List<TagReplacement079>();
|
||||
|
||||
foreach (var group in grouped.Where(g => g.Count() > 1))
|
||||
{
|
||||
var first = group.First().Id;
|
||||
|
||||
foreach (var other in group.Skip(1).Select(t => t.Id))
|
||||
{
|
||||
replacements.Add(new TagReplacement079 { OldId = other, NewId = first });
|
||||
}
|
||||
}
|
||||
|
||||
UpdateTaggedModel(conn, tran, "Series", replacements);
|
||||
UpdateTaggedModel(conn, tran, "Notifications", replacements);
|
||||
UpdateTaggedModel(conn, tran, "DelayProfiles", replacements);
|
||||
UpdateTaggedModel(conn, tran, "Restrictions", replacements);
|
||||
|
||||
DeleteTags(conn, tran, replacements);
|
||||
}
|
||||
|
||||
private List<Tag079> GetTags(IDbConnection conn, IDbTransaction tran)
|
||||
{
|
||||
var tags = new List<Tag079>();
|
||||
|
||||
using (IDbCommand tagCmd = conn.CreateCommand())
|
||||
{
|
||||
tagCmd.Transaction = tran;
|
||||
tagCmd.CommandText = @"SELECT Id, Label FROM Tags";
|
||||
|
||||
using (IDataReader tagReader = tagCmd.ExecuteReader())
|
||||
{
|
||||
while (tagReader.Read())
|
||||
{
|
||||
var id = tagReader.GetInt32(0);
|
||||
var label = tagReader.GetString(1);
|
||||
|
||||
tags.Add(new Tag079 { Id = id, Label = label });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tags;
|
||||
}
|
||||
|
||||
private void UpdateTaggedModel(IDbConnection conn, IDbTransaction tran, string table, List<TagReplacement079> replacements)
|
||||
{
|
||||
var tagged = new List<TaggedModel079>();
|
||||
|
||||
using (IDbCommand tagCmd = conn.CreateCommand())
|
||||
{
|
||||
tagCmd.Transaction = tran;
|
||||
tagCmd.CommandText = String.Format("SELECT Id, Tags FROM {0}", table);
|
||||
|
||||
using (IDataReader tagReader = tagCmd.ExecuteReader())
|
||||
{
|
||||
while (tagReader.Read())
|
||||
{
|
||||
if (!tagReader.IsDBNull(1))
|
||||
{
|
||||
var id = tagReader.GetInt32(0);
|
||||
var tags = tagReader.GetString(1);
|
||||
|
||||
tagged.Add(new TaggedModel079
|
||||
{
|
||||
Id = id,
|
||||
Tags = Json.Deserialize<HashSet<int>>(tags)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var toUpdate = new List<TaggedModel079>();
|
||||
|
||||
foreach (var model in tagged)
|
||||
{
|
||||
foreach (var replacement in replacements)
|
||||
{
|
||||
if (model.Tags.Contains(replacement.OldId))
|
||||
{
|
||||
model.Tags.Remove(replacement.OldId);
|
||||
model.Tags.Add(replacement.NewId);
|
||||
|
||||
toUpdate.Add(model);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var model in toUpdate.DistinctBy(m => m.Id))
|
||||
{
|
||||
using (IDbCommand updateCmd = conn.CreateCommand())
|
||||
{
|
||||
updateCmd.Transaction = tran;
|
||||
updateCmd.CommandText = String.Format(@"UPDATE {0} SET Tags = ?", table);
|
||||
updateCmd.AddParameter(model.Tags.ToJson());
|
||||
|
||||
updateCmd.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DeleteTags(IDbConnection conn, IDbTransaction tran, List<TagReplacement079> replacements)
|
||||
{
|
||||
var idsToRemove = replacements.Select(r => r.OldId).Distinct();
|
||||
|
||||
using (IDbCommand removeCmd = conn.CreateCommand())
|
||||
{
|
||||
removeCmd.Transaction = tran;
|
||||
removeCmd.CommandText = String.Format("DELETE FROM Tags WHERE Id IN ({0})", String.Join(",", idsToRemove));
|
||||
removeCmd.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
|
||||
private class Tag079
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Label { get; set; }
|
||||
}
|
||||
|
||||
private class TagReplacement079
|
||||
{
|
||||
public int OldId { get; set; }
|
||||
public int NewId { get; set; }
|
||||
}
|
||||
|
||||
private class TaggedModel079
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public HashSet<int> Tags { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
@ -246,6 +246,7 @@
|
||||
<Compile Include="Datastore\Migration\074_disable_eztv.cs" />
|
||||
<Compile Include="Datastore\Migration\073_clear_ratings.cs" />
|
||||
<Compile Include="Datastore\Migration\077_add_add_options_to_series.cs" />
|
||||
<Compile Include="Datastore\Migration\079_dedupe_tags.cs" />
|
||||
<Compile Include="Datastore\Migration\070_delay_profile.cs" />
|
||||
<Compile Include="Datastore\Migration\Framework\MigrationContext.cs" />
|
||||
<Compile Include="Datastore\Migration\Framework\MigrationController.cs" />
|
||||
@ -809,6 +810,7 @@
|
||||
<Compile Include="Tags\Tag.cs" />
|
||||
<Compile Include="Tags\TagRepository.cs" />
|
||||
<Compile Include="Tags\TagService.cs" />
|
||||
<Compile Include="Tags\TagsUpdatedEvent.cs" />
|
||||
<Compile Include="ThingiProvider\ConfigContractNotFoundException.cs" />
|
||||
<Compile Include="ThingiProvider\Events\ProviderUpdatedEvent.cs" />
|
||||
<Compile Include="ThingiProvider\IProvider.cs" />
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
namespace NzbDrone.Core.Tags
|
||||
{
|
||||
@ -15,36 +16,51 @@ public interface ITagService
|
||||
|
||||
public class TagService : ITagService
|
||||
{
|
||||
private readonly ITagRepository _tagRepository;
|
||||
private readonly ITagRepository _repo;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
|
||||
public TagService(ITagRepository tagRepository)
|
||||
public TagService(ITagRepository repo, IEventAggregator eventAggregator)
|
||||
{
|
||||
_tagRepository = tagRepository;
|
||||
_repo = repo;
|
||||
_eventAggregator = eventAggregator;
|
||||
}
|
||||
|
||||
public Tag GetTag(Int32 tagId)
|
||||
{
|
||||
return _tagRepository.Get(tagId);
|
||||
return _repo.Get(tagId);
|
||||
}
|
||||
|
||||
public List<Tag> All()
|
||||
{
|
||||
return _tagRepository.All().ToList();
|
||||
return _repo.All().OrderBy(t => t.Label).ToList();
|
||||
}
|
||||
|
||||
public Tag Add(Tag tag)
|
||||
{
|
||||
return _tagRepository.Insert(tag);
|
||||
//TODO: check for duplicate tag by label and return that tag instead?
|
||||
|
||||
tag.Label = tag.Label.ToLowerInvariant();
|
||||
|
||||
_repo.Insert(tag);
|
||||
_eventAggregator.PublishEvent(new TagsUpdatedEvent());
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
public Tag Update(Tag tag)
|
||||
{
|
||||
return _tagRepository.Update(tag);
|
||||
tag.Label = tag.Label.ToLowerInvariant();
|
||||
|
||||
_repo.Update(tag);
|
||||
_eventAggregator.PublishEvent(new TagsUpdatedEvent());
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
public void Delete(Int32 tagId)
|
||||
{
|
||||
_tagRepository.Delete(tagId);
|
||||
_repo.Delete(tagId);
|
||||
_eventAggregator.PublishEvent(new TagsUpdatedEvent());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
8
src/NzbDrone.Core/Tags/TagsUpdatedEvent.cs
Normal file
8
src/NzbDrone.Core/Tags/TagsUpdatedEvent.cs
Normal file
@ -0,0 +1,8 @@
|
||||
using NzbDrone.Common.Messaging;
|
||||
|
||||
namespace NzbDrone.Core.Tags
|
||||
{
|
||||
public class TagsUpdatedEvent : IEvent
|
||||
{
|
||||
}
|
||||
}
|
@ -1,11 +1,16 @@
|
||||
var Backbone = require('backbone');
|
||||
var Backbone = require('backbone');
|
||||
var TagModel = require('./TagModel');
|
||||
var ApiData = require('../Shared/ApiData');
|
||||
|
||||
module.exports = (function(){
|
||||
var Collection = Backbone.Collection.extend({
|
||||
url : window.NzbDrone.ApiRoot + '/tag',
|
||||
model : TagModel
|
||||
});
|
||||
return new Collection(ApiData.get('tag'));
|
||||
}).call(this);
|
||||
require('../Mixins/backbone.signalr.mixin');
|
||||
|
||||
var collection = Backbone.Collection.extend({
|
||||
url : window.NzbDrone.ApiRoot + '/tag',
|
||||
model : TagModel,
|
||||
|
||||
comparator : function(model){
|
||||
return model.get('label');
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = new collection(ApiData.get('tag')).bindSignalR();
|
||||
|
Loading…
Reference in New Issue
Block a user