mirror of
https://github.com/lidarr/lidarr.git
synced 2025-08-14 10:47:08 -07:00
New: Unmapped files view (#888)
* New: Unmapped files view Displays all trackfiles that haven't been matched to a track. Generalised the file details component and adds it to the album details screen. * Add sorting by quality * New: MediaServiceTests & MediaRepoTests
This commit is contained in:
parent
74cb2a6f52
commit
4413c7e46c
36 changed files with 1507 additions and 404 deletions
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Nancy;
|
||||
using NzbDrone.Core.Datastore.Events;
|
||||
|
@ -23,20 +22,23 @@ namespace Lidarr.Api.V1.TrackFiles
|
|||
{
|
||||
private readonly IMediaFileService _mediaFileService;
|
||||
private readonly IDeleteMediaFiles _mediaFileDeletionService;
|
||||
private readonly IAudioTagService _audioTagService;
|
||||
private readonly IArtistService _artistService;
|
||||
private readonly IAlbumService _albumService;
|
||||
private readonly IUpgradableSpecification _upgradableSpecification;
|
||||
|
||||
public TrackFileModule(IBroadcastSignalRMessage signalRBroadcaster,
|
||||
IMediaFileService mediaFileService,
|
||||
IDeleteMediaFiles mediaFileDeletionService,
|
||||
IArtistService artistService,
|
||||
IAlbumService albumService,
|
||||
IUpgradableSpecification upgradableSpecification)
|
||||
IMediaFileService mediaFileService,
|
||||
IDeleteMediaFiles mediaFileDeletionService,
|
||||
IAudioTagService audioTagService,
|
||||
IArtistService artistService,
|
||||
IAlbumService albumService,
|
||||
IUpgradableSpecification upgradableSpecification)
|
||||
: base(signalRBroadcaster)
|
||||
{
|
||||
_mediaFileService = mediaFileService;
|
||||
_mediaFileDeletionService = mediaFileDeletionService;
|
||||
_audioTagService = audioTagService;
|
||||
_artistService = artistService;
|
||||
_albumService = albumService;
|
||||
_upgradableSpecification = upgradableSpecification;
|
||||
|
@ -50,11 +52,23 @@ namespace Lidarr.Api.V1.TrackFiles
|
|||
Delete["/bulk"] = trackFiles => DeleteTrackFiles();
|
||||
}
|
||||
|
||||
private TrackFileResource MapToResource(TrackFile trackFile)
|
||||
{
|
||||
if (trackFile.AlbumId > 0 && trackFile.Artist != null && trackFile.Artist.Value != null)
|
||||
{
|
||||
return trackFile.ToResource(trackFile.Artist.Value, _upgradableSpecification);
|
||||
}
|
||||
else
|
||||
{
|
||||
return trackFile.ToResource();
|
||||
}
|
||||
}
|
||||
|
||||
private TrackFileResource GetTrackFile(int id)
|
||||
{
|
||||
var trackFile = _mediaFileService.Get(id);
|
||||
|
||||
return trackFile.ToResource(trackFile.Artist.Value, _upgradableSpecification);
|
||||
var resource = MapToResource(_mediaFileService.Get(id));
|
||||
resource.AudioTags = _audioTagService.ReadTags(resource.Path);
|
||||
return resource;
|
||||
}
|
||||
|
||||
private List<TrackFileResource> GetTrackFiles()
|
||||
|
@ -62,10 +76,17 @@ namespace Lidarr.Api.V1.TrackFiles
|
|||
var artistIdQuery = Request.Query.ArtistId;
|
||||
var trackFileIdsQuery = Request.Query.TrackFileIds;
|
||||
var albumIdQuery = Request.Query.AlbumId;
|
||||
var unmappedQuery = Request.Query.Unmapped;
|
||||
|
||||
if (!artistIdQuery.HasValue && !trackFileIdsQuery.HasValue && !albumIdQuery.HasValue)
|
||||
if (!artistIdQuery.HasValue && !trackFileIdsQuery.HasValue && !albumIdQuery.HasValue && !unmappedQuery.HasValue)
|
||||
{
|
||||
throw new Lidarr.Http.REST.BadRequestException("artistId, albumId, or trackFileIds must be provided");
|
||||
throw new Lidarr.Http.REST.BadRequestException("artistId, albumId, trackFileIds or unmapped must be provided");
|
||||
}
|
||||
|
||||
if (unmappedQuery.HasValue && Convert.ToBoolean(unmappedQuery.Value))
|
||||
{
|
||||
var files = _mediaFileService.GetUnmappedFiles();
|
||||
return files.ConvertAll(f => MapToResource(f));
|
||||
}
|
||||
|
||||
if (artistIdQuery.HasValue && !albumIdQuery.HasValue)
|
||||
|
@ -105,7 +126,7 @@ namespace Lidarr.Api.V1.TrackFiles
|
|||
|
||||
// trackfiles will come back with the artist already populated
|
||||
var trackFiles = _mediaFileService.Get(trackFileIds);
|
||||
return trackFiles.ConvertAll(e => e.ToResource(e.Artist.Value, _upgradableSpecification));
|
||||
return trackFiles.ConvertAll(e => MapToResource(e));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -144,9 +165,14 @@ namespace Lidarr.Api.V1.TrackFiles
|
|||
throw new NzbDroneClientException(HttpStatusCode.NotFound, "Track file not found");
|
||||
}
|
||||
|
||||
var artist = trackFile.Artist.Value;
|
||||
|
||||
_mediaFileDeletionService.DeleteTrackFile(artist, trackFile);
|
||||
if (trackFile.AlbumId > 0 && trackFile.Artist != null && trackFile.Artist.Value != null)
|
||||
{
|
||||
_mediaFileDeletionService.DeleteTrackFile(trackFile.Artist.Value, trackFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
_mediaFileDeletionService.DeleteTrackFile(trackFile, "Unmapped_Files");
|
||||
}
|
||||
}
|
||||
|
||||
private Response DeleteTrackFiles()
|
||||
|
@ -165,19 +191,12 @@ namespace Lidarr.Api.V1.TrackFiles
|
|||
|
||||
public void Handle(TrackFileAddedEvent message)
|
||||
{
|
||||
// don't process files that are added but not matched
|
||||
if (message.TrackFile.AlbumId == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
BroadcastResourceChange(ModelAction.Updated, message.TrackFile.ToResource(message.TrackFile.Artist.Value, _upgradableSpecification));
|
||||
BroadcastResourceChange(ModelAction.Updated, MapToResource(message.TrackFile));
|
||||
}
|
||||
|
||||
public void Handle(TrackFileDeletedEvent message)
|
||||
{
|
||||
BroadcastResourceChange(ModelAction.Deleted, message.TrackFile.ToResource(message.TrackFile.Artist.Value, _upgradableSpecification));
|
||||
BroadcastResourceChange(ModelAction.Deleted, MapToResource(message.TrackFile));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ using NzbDrone.Core.MediaFiles;
|
|||
using NzbDrone.Core.Qualities;
|
||||
using Lidarr.Http.REST;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using System.Linq;
|
||||
|
||||
namespace Lidarr.Api.V1.TrackFiles
|
||||
{
|
||||
|
@ -16,34 +18,43 @@ namespace Lidarr.Api.V1.TrackFiles
|
|||
public long Size { get; set; }
|
||||
public DateTime DateAdded { get; set; }
|
||||
public QualityModel Quality { get; set; }
|
||||
public int QualityWeight { get; set; }
|
||||
public MediaInfoResource MediaInfo { get; set; }
|
||||
|
||||
public bool QualityCutoffNotMet { get; set; }
|
||||
|
||||
public ParsedTrackInfo AudioTags { get; set; }
|
||||
}
|
||||
|
||||
public static class TrackFileResourceMapper
|
||||
{
|
||||
private static TrackFileResource ToResource(this TrackFile model)
|
||||
private static int QualityWeight(QualityModel quality)
|
||||
{
|
||||
if (quality == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int qualityWeight = Quality.DefaultQualityDefinitions.Single(q => q.Quality == quality.Quality).Weight;
|
||||
qualityWeight += quality.Revision.Real * 10;
|
||||
qualityWeight += quality.Revision.Version;
|
||||
return qualityWeight;
|
||||
}
|
||||
|
||||
public static TrackFileResource ToResource(this TrackFile model)
|
||||
{
|
||||
if (model == null) return null;
|
||||
|
||||
return new TrackFileResource
|
||||
{
|
||||
Id = model.Id,
|
||||
|
||||
ArtistId = model.Artist.Value.Id,
|
||||
AlbumId = model.AlbumId,
|
||||
RelativePath = model.Artist.Value.Path.GetRelativePath(model.Path),
|
||||
Path = model.Path,
|
||||
Size = model.Size,
|
||||
DateAdded = model.DateAdded,
|
||||
// SceneName = model.SceneName,
|
||||
Quality = model.Quality,
|
||||
QualityWeight = QualityWeight(model.Quality),
|
||||
MediaInfo = model.MediaInfo.ToResource()
|
||||
//QualityCutoffNotMet
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
public static TrackFileResource ToResource(this TrackFile model, NzbDrone.Core.Music.Artist artist, IUpgradableSpecification upgradableSpecification)
|
||||
|
@ -60,8 +71,8 @@ namespace Lidarr.Api.V1.TrackFiles
|
|||
RelativePath = artist.Path.GetRelativePath(model.Path),
|
||||
Size = model.Size,
|
||||
DateAdded = model.DateAdded,
|
||||
//SceneName = model.SceneName,
|
||||
Quality = model.Quality,
|
||||
QualityWeight = QualityWeight(model.Quality),
|
||||
MediaInfo = model.MediaInfo.ToResource(),
|
||||
QualityCutoffNotMet = upgradableSpecification.QualityCutoffNotMet(artist.QualityProfile.Value, model.Quality)
|
||||
};
|
||||
|
|
|
@ -19,7 +19,7 @@ namespace NzbDrone.Automation.Test
|
|||
[Test]
|
||||
public void artist_page()
|
||||
{
|
||||
page.ArtistNavIcon.Click();
|
||||
page.LibraryNavIcon.Click();
|
||||
page.WaitForNoSpinner();
|
||||
page.Find(By.CssSelector("div[class*='ArtistIndex']")).Should().NotBeNull();
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ namespace NzbDrone.Automation.Test
|
|||
[Test]
|
||||
public void add_artist_page()
|
||||
{
|
||||
page.ArtistNavIcon.Click();
|
||||
page.LibraryNavIcon.Click();
|
||||
page.WaitForNoSpinner();
|
||||
|
||||
page.Find(By.LinkText("Add New")).Click();
|
||||
|
|
|
@ -47,7 +47,7 @@ namespace NzbDrone.Automation.Test.PageModel
|
|||
});
|
||||
}
|
||||
|
||||
public IWebElement ArtistNavIcon => Find(By.LinkText("Artist"));
|
||||
public IWebElement LibraryNavIcon => Find(By.LinkText("Library"));
|
||||
|
||||
public IWebElement CalendarNavIcon => Find(By.LinkText("Calendar"));
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
|||
{
|
||||
private Artist artist;
|
||||
private Album album;
|
||||
private List<AlbumRelease> releases;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
|
@ -36,7 +37,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
|||
.Build();
|
||||
Db.Insert(album);
|
||||
|
||||
var releases = Builder<AlbumRelease>.CreateListOfSize(2)
|
||||
releases = Builder<AlbumRelease>.CreateListOfSize(2)
|
||||
.All()
|
||||
.With(a => a.Id = 0)
|
||||
.With(a => a.AlbumId = album.Id)
|
||||
|
@ -44,7 +45,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
|||
.With(a => a.Monitored = true)
|
||||
.TheNext(1)
|
||||
.With(a => a.Monitored = false)
|
||||
.Build();
|
||||
.Build().ToList();
|
||||
Db.InsertMany(releases);
|
||||
|
||||
var files = Builder<TrackFile>.CreateListOfSize(10)
|
||||
|
@ -53,6 +54,10 @@ namespace NzbDrone.Core.Test.MediaFiles
|
|||
.With(c => c.Quality =new QualityModel(Quality.MP3_192))
|
||||
.TheFirst(5)
|
||||
.With(c => c.AlbumId = album.Id)
|
||||
.TheFirst(1)
|
||||
.With(c => c.Path = "/Test/Path/Artist/somefile1.flac")
|
||||
.TheNext(1)
|
||||
.With(c => c.Path = "/Test/Path/Artist/somefile2.flac")
|
||||
.BuildListOfNew();
|
||||
Db.InsertMany(files);
|
||||
|
||||
|
@ -88,6 +93,55 @@ namespace NzbDrone.Core.Test.MediaFiles
|
|||
artistFiles.Should().OnlyContain(c => c.Artist.Value.Id == artist.Id);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void get_unmapped_files()
|
||||
{
|
||||
VerifyData();
|
||||
var unmappedfiles = Subject.GetUnmappedFiles();
|
||||
VerifyUnmapped(unmappedfiles);
|
||||
|
||||
unmappedfiles.Should().HaveCount(5);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void get_files_by_release()
|
||||
{
|
||||
VerifyData();
|
||||
var firstReleaseFiles = Subject.GetFilesByRelease(releases[0].Id);
|
||||
var secondReleaseFiles = Subject.GetFilesByRelease(releases[1].Id);
|
||||
VerifyEagerLoaded(firstReleaseFiles);
|
||||
VerifyEagerLoaded(secondReleaseFiles);
|
||||
|
||||
firstReleaseFiles.Should().HaveCount(4);
|
||||
secondReleaseFiles.Should().HaveCount(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void get_files_by_base_path()
|
||||
{
|
||||
VerifyData();
|
||||
var firstReleaseFiles = Subject.GetFilesWithBasePath("/Test/Path");
|
||||
VerifyEagerLoaded(firstReleaseFiles);
|
||||
|
||||
firstReleaseFiles.Should().HaveCount(2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void get_file_by_path()
|
||||
{
|
||||
VerifyData();
|
||||
var file = Subject.GetFileWithPath("/Test/Path/Artist/somefile2.flac");
|
||||
|
||||
file.Should().NotBeNull();
|
||||
file.Tracks.IsLoaded.Should().BeTrue();
|
||||
file.Tracks.Value.Should().NotBeNull();
|
||||
file.Tracks.Value.Should().NotBeEmpty();
|
||||
file.Album.IsLoaded.Should().BeTrue();
|
||||
file.Album.Value.Should().NotBeNull();
|
||||
file.Artist.IsLoaded.Should().BeTrue();
|
||||
file.Artist.Value.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void get_files_by_artist_should_only_return_tracks_for_monitored_releases()
|
||||
{
|
||||
|
@ -142,6 +196,20 @@ namespace NzbDrone.Core.Test.MediaFiles
|
|||
}
|
||||
}
|
||||
|
||||
private void VerifyUnmapped(List<TrackFile> files)
|
||||
{
|
||||
foreach (var file in files)
|
||||
{
|
||||
file.Tracks.IsLoaded.Should().BeFalse();
|
||||
file.Tracks.Value.Should().NotBeNull();
|
||||
file.Tracks.Value.Should().BeEmpty();
|
||||
file.Album.IsLoaded.Should().BeFalse();
|
||||
file.Album.Value.Should().BeNull();
|
||||
file.Artist.IsLoaded.Should().BeFalse();
|
||||
file.Artist.Value.Should().BeNull();
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void delete_files_by_album_should_work_if_join_fails()
|
||||
{
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FizzWare.NBuilder;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.MediaFiles.Events;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Core.Music;
|
||||
|
||||
namespace NzbDrone.Core.Test.MediaFiles.TrackFileMovingServiceTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class MediaFileServiceFixture : CoreTest<MediaFileService>
|
||||
{
|
||||
private Album _album;
|
||||
private List<TrackFile> _trackFiles;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_album = Builder<Album>.CreateNew()
|
||||
.Build();
|
||||
|
||||
_trackFiles = Builder<TrackFile>.CreateListOfSize(3)
|
||||
.TheFirst(2)
|
||||
.With(f => f.AlbumId = _album.Id)
|
||||
.TheNext(1)
|
||||
.With(f => f.AlbumId = 0)
|
||||
.Build().ToList();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_throw_trackFileDeletedEvent_for_each_mapped_track_on_deletemany()
|
||||
{
|
||||
Subject.DeleteMany(_trackFiles, DeleteMediaFileReason.Manual);
|
||||
|
||||
VerifyEventPublished<TrackFileDeletedEvent>(Times.Exactly(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_throw_trackFileDeletedEvent_for_mapped_track_on_delete()
|
||||
{
|
||||
Subject.Delete(_trackFiles[0], DeleteMediaFileReason.Manual);
|
||||
|
||||
VerifyEventPublished<TrackFileDeletedEvent>(Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_throw_trackFileAddedEvent_for_each_track_added_on_addmany()
|
||||
{
|
||||
Subject.AddMany(_trackFiles);
|
||||
|
||||
VerifyEventPublished<TrackFileAddedEvent>(Times.Exactly(3));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_throw_trackFileAddedEvent_for_track_added()
|
||||
{
|
||||
Subject.Add(_trackFiles[0]);
|
||||
|
||||
VerifyEventPublished<TrackFileAddedEvent>(Times.Once());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
|||
{
|
||||
public class MediaFileTableCleanupServiceFixture : CoreTest<MediaFileTableCleanupService>
|
||||
{
|
||||
private readonly string DELETED_PATH = @"c:\ANY FILE WITH THIS PATH IS CONSIDERED DELETED!".AsOsAgnostic();
|
||||
private readonly string DELETED_PATH = @"c:\ANY FILE STARTING WITH THIS PATH IS CONSIDERED DELETED!".AsOsAgnostic();
|
||||
private List<Track> _tracks;
|
||||
private Artist _artist;
|
||||
|
||||
|
@ -29,29 +29,23 @@ namespace NzbDrone.Core.Test.MediaFiles
|
|||
.With(s => s.Path = @"C:\Test\Music\Artist".AsOsAgnostic())
|
||||
.Build();
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(e => e.FileExists(It.Is<string>(c => !c.Contains(DELETED_PATH))))
|
||||
.Returns(true);
|
||||
|
||||
Mocker.GetMock<ITrackService>()
|
||||
.Setup(c => c.GetTracksByArtist(It.IsAny<int>()))
|
||||
.Returns(_tracks);
|
||||
.Setup(c => c.GetTracksByFileId(It.IsAny<IEnumerable<int>>()))
|
||||
.Returns((IEnumerable<int> ids) => _tracks.Where(y => ids.Contains(y.TrackFileId)).ToList());
|
||||
}
|
||||
|
||||
private void GivenTrackFiles(IEnumerable<TrackFile> trackFiles)
|
||||
{
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Setup(c => c.GetFilesByArtist(It.IsAny<int>()))
|
||||
.Setup(c => c.GetFilesWithBasePath(It.IsAny<string>()))
|
||||
.Returns(trackFiles.ToList());
|
||||
}
|
||||
|
||||
private void GivenFilesAreNotAttachedToTrack()
|
||||
{
|
||||
_tracks.ForEach(e => e.TrackFileId = 0);
|
||||
|
||||
Mocker.GetMock<ITrackService>()
|
||||
.Setup(c => c.GetTracksByArtist(It.IsAny<int>()))
|
||||
.Returns(_tracks);
|
||||
.Setup(c => c.GetTracksByFileId(It.IsAny<int>()))
|
||||
.Returns(new List<Track>());
|
||||
}
|
||||
|
||||
private List<string> FilesOnDisk(IEnumerable<TrackFile> trackFiles)
|
||||
|
@ -71,7 +65,8 @@ namespace NzbDrone.Core.Test.MediaFiles
|
|||
|
||||
Subject.Clean(_artist, FilesOnDisk(trackFiles));
|
||||
|
||||
Mocker.GetMock<ITrackService>().Verify(c => c.UpdateTrack(It.IsAny<Track>()), Times.Never());
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Verify(c => c.DeleteMany(It.Is<List<TrackFile>>(x => x.Count == 0), DeleteMediaFileReason.MissingFromDisk), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -81,24 +76,31 @@ namespace NzbDrone.Core.Test.MediaFiles
|
|||
.All()
|
||||
.With(x => x.Path = Path.Combine(@"c:\test".AsOsAgnostic(), Path.GetRandomFileName()))
|
||||
.Random(2)
|
||||
.With(c => c.Path = DELETED_PATH)
|
||||
.With(c => c.Path = Path.Combine(DELETED_PATH, Path.GetRandomFileName()))
|
||||
.Build();
|
||||
|
||||
GivenTrackFiles(trackFiles);
|
||||
|
||||
Subject.Clean(_artist, FilesOnDisk(trackFiles.Where(e => e.Path != DELETED_PATH)));
|
||||
Subject.Clean(_artist, FilesOnDisk(trackFiles.Where(e => !e.Path.StartsWith(DELETED_PATH))));
|
||||
|
||||
Mocker.GetMock<IMediaFileService>().Verify(c => c.Delete(It.Is<TrackFile>(e => e.Path == DELETED_PATH), DeleteMediaFileReason.MissingFromDisk), Times.Exactly(2));
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Verify(c => c.DeleteMany(It.Is<List<TrackFile>>(e => e.Count == 2 && e.All(y => y.Path.StartsWith(DELETED_PATH))), DeleteMediaFileReason.MissingFromDisk), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_unlink_track_when_trackFile_does_not_exist()
|
||||
{
|
||||
GivenTrackFiles(new List<TrackFile>());
|
||||
var trackFiles = Builder<TrackFile>.CreateListOfSize(10)
|
||||
.Random(10)
|
||||
.With(c => c.Path = Path.Combine(@"c:\test".AsOsAgnostic(), Path.GetRandomFileName()))
|
||||
.Build();
|
||||
|
||||
GivenTrackFiles(trackFiles);
|
||||
|
||||
Subject.Clean(_artist, new List<string>());
|
||||
|
||||
Mocker.GetMock<ITrackService>().Verify(c => c.UpdateTrack(It.Is<Track>(e => e.TrackFileId == 0)), Times.Exactly(10));
|
||||
Mocker.GetMock<ITrackService>()
|
||||
.Verify(c => c.SetFileIds(It.Is<List<Track>>(e => e.Count == 10 && e.All(y => y.TrackFileId == 0))), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -113,7 +115,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
|||
|
||||
Subject.Clean(_artist, FilesOnDisk(trackFiles));
|
||||
|
||||
Mocker.GetMock<ITrackService>().Verify(c => c.UpdateTrack(It.IsAny<Track>()), Times.Never());
|
||||
Mocker.GetMock<ITrackService>().Verify(c => c.SetFileIds(It.Is<List<Track>>(x => x.Count == 0)), Times.Once());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ namespace NzbDrone.Core.MediaFiles
|
|||
public interface IDeleteMediaFiles
|
||||
{
|
||||
void DeleteTrackFile(Artist artist, TrackFile trackFile);
|
||||
void DeleteTrackFile(TrackFile trackFile, string subfolder = "");
|
||||
}
|
||||
|
||||
public class MediaFileDeletionService : IDeleteMediaFiles,
|
||||
|
@ -62,12 +63,26 @@ namespace NzbDrone.Core.MediaFiles
|
|||
throw new NzbDroneClientException(HttpStatusCode.Conflict, "Artist's root folder ({0}) is empty.", rootFolder);
|
||||
}
|
||||
|
||||
if (_diskProvider.FolderExists(artist.Path) && _diskProvider.FileExists(fullPath))
|
||||
if (_diskProvider.FolderExists(artist.Path))
|
||||
{
|
||||
var subfolder = _diskProvider.GetParentFolder(artist.Path).GetRelativePath(_diskProvider.GetParentFolder(fullPath));
|
||||
DeleteTrackFile(trackFile, subfolder);
|
||||
}
|
||||
else
|
||||
{
|
||||
// delete from db even if the artist folder is missing
|
||||
_mediaFileService.Delete(trackFile, DeleteMediaFileReason.Manual);
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteTrackFile(TrackFile trackFile, string subfolder = "")
|
||||
{
|
||||
var fullPath = trackFile.Path;
|
||||
|
||||
if (_diskProvider.FileExists(fullPath))
|
||||
{
|
||||
_logger.Info("Deleting track file: {0}", fullPath);
|
||||
|
||||
var subfolder = _diskProvider.GetParentFolder(artist.Path).GetRelativePath(_diskProvider.GetParentFolder(fullPath));
|
||||
|
||||
try
|
||||
{
|
||||
_recycleBinProvider.DeleteFile(fullPath, subfolder);
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Marr.Data;
|
||||
using Marr.Data.QGen;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Datastore.Extensions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Music;
|
||||
|
||||
|
@ -12,6 +14,7 @@ namespace NzbDrone.Core.MediaFiles
|
|||
List<TrackFile> GetFilesByArtist(int artistId);
|
||||
List<TrackFile> GetFilesByAlbum(int albumId);
|
||||
List<TrackFile> GetFilesByRelease(int releaseId);
|
||||
List<TrackFile> GetUnmappedFiles();
|
||||
List<TrackFile> GetFilesWithBasePath(string path);
|
||||
TrackFile GetFileWithPath(string path);
|
||||
void DeleteFilesByAlbum(int albumId);
|
||||
|
@ -52,6 +55,16 @@ namespace NzbDrone.Core.MediaFiles
|
|||
.ToList();
|
||||
}
|
||||
|
||||
public List<TrackFile> GetUnmappedFiles()
|
||||
{
|
||||
var query = "SELECT TrackFiles.* " +
|
||||
"FROM TrackFiles " +
|
||||
"LEFT JOIN Tracks ON Tracks.TrackFileId = TrackFiles.Id " +
|
||||
"WHERE Tracks.Id IS NULL ";
|
||||
|
||||
return DataMapper.Query<TrackFile>().QueryText(query).ToList();
|
||||
}
|
||||
|
||||
public void DeleteFilesByAlbum(int albumId)
|
||||
{
|
||||
var ids = DataMapper.Query<TrackFile>().Where(x => x.AlbumId == albumId);
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
|
@ -20,9 +19,11 @@ namespace NzbDrone.Core.MediaFiles
|
|||
void Update(TrackFile trackFile);
|
||||
void Update(List<TrackFile> trackFile);
|
||||
void Delete(TrackFile trackFile, DeleteMediaFileReason reason);
|
||||
void DeleteMany(List<TrackFile> trackFiles, DeleteMediaFileReason reason);
|
||||
List<TrackFile> GetFilesByArtist(int artistId);
|
||||
List<TrackFile> GetFilesByAlbum(int albumId);
|
||||
List<TrackFile> GetFilesByRelease(int releaseId);
|
||||
List<TrackFile> GetUnmappedFiles();
|
||||
List<IFileInfo> FilterUnchangedFiles(List<IFileInfo> files, Artist artist, FilterFilesType filter);
|
||||
TrackFile Get(int id);
|
||||
List<TrackFile> Get(IEnumerable<int> ids);
|
||||
|
@ -81,6 +82,17 @@ namespace NzbDrone.Core.MediaFiles
|
|||
}
|
||||
}
|
||||
|
||||
public void DeleteMany(List<TrackFile> trackFiles, DeleteMediaFileReason reason)
|
||||
{
|
||||
_mediaFileRepository.DeleteMany(trackFiles);
|
||||
|
||||
// publish events where trackfile was mapped to a track
|
||||
foreach (var trackFile in trackFiles.Where(x => x.AlbumId > 0))
|
||||
{
|
||||
_eventAggregator.PublishEvent(new TrackFileDeletedEvent(trackFile, reason));
|
||||
}
|
||||
}
|
||||
|
||||
public List<IFileInfo> FilterUnchangedFiles(List<IFileInfo> files, Artist artist, FilterFilesType filter)
|
||||
{
|
||||
_logger.Debug($"Filtering {files.Count} files for unchanged files");
|
||||
|
@ -166,6 +178,11 @@ namespace NzbDrone.Core.MediaFiles
|
|||
return _mediaFileRepository.GetFilesByRelease(releaseId);
|
||||
}
|
||||
|
||||
public List<TrackFile> GetUnmappedFiles()
|
||||
{
|
||||
return _mediaFileRepository.GetUnmappedFiles();
|
||||
}
|
||||
|
||||
public void UpdateMediaInfo(List<TrackFile> trackFiles)
|
||||
{
|
||||
_mediaFileRepository.SetFields(trackFiles, t => t.MediaInfo);
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
@ -30,42 +29,20 @@ namespace NzbDrone.Core.MediaFiles
|
|||
|
||||
public void Clean(Artist artist, List<string> filesOnDisk)
|
||||
{
|
||||
var artistFiles = _mediaFileService.GetFilesByArtist(artist.Id);
|
||||
var tracks = _trackService.GetTracksByArtist(artist.Id);
|
||||
var dbFiles = _mediaFileService.GetFilesWithBasePath(artist.Path);
|
||||
|
||||
// get files in database that are missing on disk and remove from database
|
||||
var missingFiles = dbFiles.ExceptBy(x => x.Path, filesOnDisk, x => x, PathEqualityComparer.Instance).ToList();
|
||||
|
||||
var filesOnDiskKeys = new HashSet<string>(filesOnDisk, PathEqualityComparer.Instance);
|
||||
|
||||
foreach (var artistFile in artistFiles)
|
||||
{
|
||||
var trackFile = artistFile;
|
||||
var trackFilePath = trackFile.Path;
|
||||
_logger.Debug("The following files no longer exist on disk, removing from db:\n{0}",
|
||||
string.Join("\n", missingFiles.Select(x => x.Path)));
|
||||
|
||||
try
|
||||
{
|
||||
if (!filesOnDiskKeys.Contains(trackFilePath))
|
||||
{
|
||||
_logger.Debug("File [{0}] no longer exists on disk, removing from db", trackFilePath);
|
||||
_mediaFileService.Delete(artistFile, DeleteMediaFileReason.MissingFromDisk);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Unable to cleanup TrackFile in DB: {0}", trackFile.Id);
|
||||
}
|
||||
}
|
||||
_mediaFileService.DeleteMany(missingFiles, DeleteMediaFileReason.MissingFromDisk);
|
||||
|
||||
foreach (var t in tracks)
|
||||
{
|
||||
var track = t;
|
||||
|
||||
if (track.TrackFileId > 0 && artistFiles.None(f => f.Id == track.TrackFileId))
|
||||
{
|
||||
track.TrackFileId = 0;
|
||||
_trackService.UpdateTrack(track);
|
||||
}
|
||||
}
|
||||
// get any tracks matched to these trackfiles and unlink them
|
||||
var orphanedTracks = _trackService.GetTracksByFileId(missingFiles.Select(x => x.Id));
|
||||
orphanedTracks.ForEach(x => x.TrackFileId = 0);
|
||||
_trackService.SetFileIds(orphanedTracks);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ namespace NzbDrone.Core.Music
|
|||
List<Track> GetTracksByReleases(List<int> albumReleaseId);
|
||||
List<Track> GetTracksForRefresh(int albumReleaseId, IEnumerable<string> foreignTrackIds);
|
||||
List<Track> GetTracksByFileId(int fileId);
|
||||
List<Track> GetTracksByFileId(IEnumerable<int> ids);
|
||||
List<Track> TracksWithFiles(int artistId);
|
||||
List<Track> TracksWithoutFiles(int albumId);
|
||||
void SetFileId(List<Track> tracks);
|
||||
|
@ -85,6 +86,11 @@ namespace NzbDrone.Core.Music
|
|||
return Query.Where(e => e.TrackFileId == fileId).ToList();
|
||||
}
|
||||
|
||||
public List<Track> GetTracksByFileId(IEnumerable<int> ids)
|
||||
{
|
||||
return Query.Where($"[TrackFileId] IN ({string.Join(", ", ids)})").ToList();
|
||||
}
|
||||
|
||||
public List<Track> TracksWithFiles(int artistId)
|
||||
{
|
||||
string query = string.Format("SELECT Tracks.* " +
|
||||
|
|
|
@ -20,6 +20,7 @@ namespace NzbDrone.Core.Music
|
|||
List<Track> TracksWithFiles(int artistId);
|
||||
List<Track> TracksWithoutFiles(int albumId);
|
||||
List<Track> GetTracksByFileId(int trackFileId);
|
||||
List<Track> GetTracksByFileId(IEnumerable<int> trackFileIds);
|
||||
void UpdateTrack(Track track);
|
||||
void InsertMany(List<Track> tracks);
|
||||
void UpdateMany(List<Track> tracks);
|
||||
|
@ -93,6 +94,11 @@ namespace NzbDrone.Core.Music
|
|||
return _trackRepository.GetTracksByFileId(trackFileId);
|
||||
}
|
||||
|
||||
public List<Track> GetTracksByFileId(IEnumerable<int> trackFileIds)
|
||||
{
|
||||
return _trackRepository.GetTracksByFileId(trackFileIds);
|
||||
}
|
||||
|
||||
public void UpdateTrack(Track track)
|
||||
{
|
||||
_trackRepository.Update(track);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue