optimize local library scanning (#112)
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -61,21 +62,30 @@ namespace ErsatzTV.Application.MediaSources.Commands
|
|||||||
var lastScan = new DateTimeOffset(localLibrary.LastScan ?? DateTime.MinValue, TimeSpan.Zero);
|
var lastScan = new DateTimeOffset(localLibrary.LastScan ?? DateTime.MinValue, TimeSpan.Zero);
|
||||||
if (forceScan || lastScan < DateTimeOffset.Now - TimeSpan.FromHours(6))
|
if (forceScan || lastScan < DateTimeOffset.Now - TimeSpan.FromHours(6))
|
||||||
{
|
{
|
||||||
|
var sw = new Stopwatch();
|
||||||
|
sw.Start();
|
||||||
|
|
||||||
foreach (LibraryPath libraryPath in localLibrary.Paths)
|
foreach (LibraryPath libraryPath in localLibrary.Paths)
|
||||||
{
|
{
|
||||||
switch (localLibrary.MediaKind)
|
switch (localLibrary.MediaKind)
|
||||||
{
|
{
|
||||||
case LibraryMediaKind.Movies:
|
case LibraryMediaKind.Movies:
|
||||||
await _movieFolderScanner.ScanFolder(libraryPath, ffprobePath);
|
await _movieFolderScanner.ScanFolder(libraryPath, ffprobePath, lastScan);
|
||||||
break;
|
break;
|
||||||
case LibraryMediaKind.Shows:
|
case LibraryMediaKind.Shows:
|
||||||
await _televisionFolderScanner.ScanFolder(libraryPath, ffprobePath);
|
await _televisionFolderScanner.ScanFolder(libraryPath, ffprobePath, lastScan);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
localLibrary.LastScan = DateTime.UtcNow;
|
localLibrary.LastScan = DateTime.UtcNow;
|
||||||
await _libraryRepository.UpdateLastScan(localLibrary);
|
await _libraryRepository.UpdateLastScan(localLibrary);
|
||||||
|
|
||||||
|
sw.Stop();
|
||||||
|
_logger.LogDebug(
|
||||||
|
"Scan of library {Name} completed in {Duration}",
|
||||||
|
localLibrary.Name,
|
||||||
|
TimeSpan.FromMilliseconds(sw.ElapsedMilliseconds));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -81,7 +81,10 @@ namespace ErsatzTV.Core.Tests.Metadata
|
|||||||
);
|
);
|
||||||
var libraryPath = new LibraryPath { Path = BadFakeRoot };
|
var libraryPath = new LibraryPath { Path = BadFakeRoot };
|
||||||
|
|
||||||
Either<BaseError, Unit> result = await service.ScanFolder(libraryPath, FFprobePath);
|
Either<BaseError, Unit> result = await service.ScanFolder(
|
||||||
|
libraryPath,
|
||||||
|
FFprobePath,
|
||||||
|
DateTimeOffset.MinValue);
|
||||||
|
|
||||||
result.IsLeft.Should().BeTrue();
|
result.IsLeft.Should().BeTrue();
|
||||||
result.IfLeft(error => error.Should().BeOfType<MediaSourceInaccessible>());
|
result.IfLeft(error => error.Should().BeOfType<MediaSourceInaccessible>());
|
||||||
@@ -101,7 +104,10 @@ namespace ErsatzTV.Core.Tests.Metadata
|
|||||||
);
|
);
|
||||||
var libraryPath = new LibraryPath { Id = 1, Path = FakeRoot };
|
var libraryPath = new LibraryPath { Id = 1, Path = FakeRoot };
|
||||||
|
|
||||||
Either<BaseError, Unit> result = await service.ScanFolder(libraryPath, FFprobePath);
|
Either<BaseError, Unit> result = await service.ScanFolder(
|
||||||
|
libraryPath,
|
||||||
|
FFprobePath,
|
||||||
|
DateTimeOffset.MinValue);
|
||||||
|
|
||||||
result.IsRight.Should().BeTrue();
|
result.IsRight.Should().BeTrue();
|
||||||
|
|
||||||
@@ -137,7 +143,10 @@ namespace ErsatzTV.Core.Tests.Metadata
|
|||||||
);
|
);
|
||||||
var libraryPath = new LibraryPath { Id = 1, Path = FakeRoot };
|
var libraryPath = new LibraryPath { Id = 1, Path = FakeRoot };
|
||||||
|
|
||||||
Either<BaseError, Unit> result = await service.ScanFolder(libraryPath, FFprobePath);
|
Either<BaseError, Unit> result = await service.ScanFolder(
|
||||||
|
libraryPath,
|
||||||
|
FFprobePath,
|
||||||
|
DateTimeOffset.MinValue);
|
||||||
|
|
||||||
result.IsRight.Should().BeTrue();
|
result.IsRight.Should().BeTrue();
|
||||||
|
|
||||||
@@ -174,7 +183,10 @@ namespace ErsatzTV.Core.Tests.Metadata
|
|||||||
);
|
);
|
||||||
var libraryPath = new LibraryPath { Id = 1, Path = FakeRoot };
|
var libraryPath = new LibraryPath { Id = 1, Path = FakeRoot };
|
||||||
|
|
||||||
Either<BaseError, Unit> result = await service.ScanFolder(libraryPath, FFprobePath);
|
Either<BaseError, Unit> result = await service.ScanFolder(
|
||||||
|
libraryPath,
|
||||||
|
FFprobePath,
|
||||||
|
DateTimeOffset.MinValue);
|
||||||
|
|
||||||
result.IsRight.Should().BeTrue();
|
result.IsRight.Should().BeTrue();
|
||||||
|
|
||||||
@@ -215,7 +227,10 @@ namespace ErsatzTV.Core.Tests.Metadata
|
|||||||
);
|
);
|
||||||
var libraryPath = new LibraryPath { Id = 1, Path = FakeRoot };
|
var libraryPath = new LibraryPath { Id = 1, Path = FakeRoot };
|
||||||
|
|
||||||
Either<BaseError, Unit> result = await service.ScanFolder(libraryPath, FFprobePath);
|
Either<BaseError, Unit> result = await service.ScanFolder(
|
||||||
|
libraryPath,
|
||||||
|
FFprobePath,
|
||||||
|
DateTimeOffset.MinValue);
|
||||||
|
|
||||||
result.IsRight.Should().BeTrue();
|
result.IsRight.Should().BeTrue();
|
||||||
|
|
||||||
@@ -259,7 +274,10 @@ namespace ErsatzTV.Core.Tests.Metadata
|
|||||||
);
|
);
|
||||||
var libraryPath = new LibraryPath { Id = 1, Path = FakeRoot };
|
var libraryPath = new LibraryPath { Id = 1, Path = FakeRoot };
|
||||||
|
|
||||||
Either<BaseError, Unit> result = await service.ScanFolder(libraryPath, FFprobePath);
|
Either<BaseError, Unit> result = await service.ScanFolder(
|
||||||
|
libraryPath,
|
||||||
|
FFprobePath,
|
||||||
|
DateTimeOffset.MinValue);
|
||||||
|
|
||||||
result.IsRight.Should().BeTrue();
|
result.IsRight.Should().BeTrue();
|
||||||
|
|
||||||
@@ -303,7 +321,10 @@ namespace ErsatzTV.Core.Tests.Metadata
|
|||||||
);
|
);
|
||||||
var libraryPath = new LibraryPath { Id = 1, Path = FakeRoot };
|
var libraryPath = new LibraryPath { Id = 1, Path = FakeRoot };
|
||||||
|
|
||||||
Either<BaseError, Unit> result = await service.ScanFolder(libraryPath, FFprobePath);
|
Either<BaseError, Unit> result = await service.ScanFolder(
|
||||||
|
libraryPath,
|
||||||
|
FFprobePath,
|
||||||
|
DateTimeOffset.MinValue);
|
||||||
|
|
||||||
result.IsRight.Should().BeTrue();
|
result.IsRight.Should().BeTrue();
|
||||||
|
|
||||||
@@ -346,7 +367,10 @@ namespace ErsatzTV.Core.Tests.Metadata
|
|||||||
);
|
);
|
||||||
var libraryPath = new LibraryPath { Id = 1, Path = FakeRoot };
|
var libraryPath = new LibraryPath { Id = 1, Path = FakeRoot };
|
||||||
|
|
||||||
Either<BaseError, Unit> result = await service.ScanFolder(libraryPath, FFprobePath);
|
Either<BaseError, Unit> result = await service.ScanFolder(
|
||||||
|
libraryPath,
|
||||||
|
FFprobePath,
|
||||||
|
DateTimeOffset.MinValue);
|
||||||
|
|
||||||
result.IsRight.Should().BeTrue();
|
result.IsRight.Should().BeTrue();
|
||||||
|
|
||||||
@@ -385,7 +409,10 @@ namespace ErsatzTV.Core.Tests.Metadata
|
|||||||
);
|
);
|
||||||
var libraryPath = new LibraryPath { Id = 1, Path = FakeRoot };
|
var libraryPath = new LibraryPath { Id = 1, Path = FakeRoot };
|
||||||
|
|
||||||
Either<BaseError, Unit> result = await service.ScanFolder(libraryPath, FFprobePath);
|
Either<BaseError, Unit> result = await service.ScanFolder(
|
||||||
|
libraryPath,
|
||||||
|
FFprobePath,
|
||||||
|
DateTimeOffset.MinValue);
|
||||||
|
|
||||||
result.IsRight.Should().BeTrue();
|
result.IsRight.Should().BeTrue();
|
||||||
|
|
||||||
@@ -418,7 +445,10 @@ namespace ErsatzTV.Core.Tests.Metadata
|
|||||||
);
|
);
|
||||||
var libraryPath = new LibraryPath { Id = 1, Path = FakeRoot };
|
var libraryPath = new LibraryPath { Id = 1, Path = FakeRoot };
|
||||||
|
|
||||||
Either<BaseError, Unit> result = await service.ScanFolder(libraryPath, FFprobePath);
|
Either<BaseError, Unit> result = await service.ScanFolder(
|
||||||
|
libraryPath,
|
||||||
|
FFprobePath,
|
||||||
|
DateTimeOffset.MinValue);
|
||||||
|
|
||||||
result.IsRight.Should().BeTrue();
|
result.IsRight.Should().BeTrue();
|
||||||
|
|
||||||
@@ -453,7 +483,10 @@ namespace ErsatzTV.Core.Tests.Metadata
|
|||||||
);
|
);
|
||||||
var libraryPath = new LibraryPath { Id = 1, Path = FakeRoot };
|
var libraryPath = new LibraryPath { Id = 1, Path = FakeRoot };
|
||||||
|
|
||||||
Either<BaseError, Unit> result = await service.ScanFolder(libraryPath, FFprobePath);
|
Either<BaseError, Unit> result = await service.ScanFolder(
|
||||||
|
libraryPath,
|
||||||
|
FFprobePath,
|
||||||
|
DateTimeOffset.MinValue);
|
||||||
|
|
||||||
result.IsRight.Should().BeTrue();
|
result.IsRight.Should().BeTrue();
|
||||||
|
|
||||||
@@ -477,7 +510,10 @@ namespace ErsatzTV.Core.Tests.Metadata
|
|||||||
);
|
);
|
||||||
var libraryPath = new LibraryPath { Id = 1, Path = FakeRoot };
|
var libraryPath = new LibraryPath { Id = 1, Path = FakeRoot };
|
||||||
|
|
||||||
Either<BaseError, Unit> result = await service.ScanFolder(libraryPath, FFprobePath);
|
Either<BaseError, Unit> result = await service.ScanFolder(
|
||||||
|
libraryPath,
|
||||||
|
FFprobePath,
|
||||||
|
DateTimeOffset.MinValue);
|
||||||
|
|
||||||
result.IsRight.Should().BeTrue();
|
result.IsRight.Should().BeTrue();
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Threading.Tasks;
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using ErsatzTV.Core.Domain;
|
using ErsatzTV.Core.Domain;
|
||||||
using LanguageExt;
|
using LanguageExt;
|
||||||
|
|
||||||
@@ -6,6 +7,6 @@ namespace ErsatzTV.Core.Interfaces.Metadata
|
|||||||
{
|
{
|
||||||
public interface IMovieFolderScanner
|
public interface IMovieFolderScanner
|
||||||
{
|
{
|
||||||
Task<Either<BaseError, Unit>> ScanFolder(LibraryPath libraryPath, string ffprobePath);
|
Task<Either<BaseError, Unit>> ScanFolder(LibraryPath libraryPath, string ffprobePath, DateTimeOffset lastScan);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Threading.Tasks;
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using ErsatzTV.Core.Domain;
|
using ErsatzTV.Core.Domain;
|
||||||
using LanguageExt;
|
using LanguageExt;
|
||||||
|
|
||||||
@@ -6,6 +7,6 @@ namespace ErsatzTV.Core.Interfaces.Metadata
|
|||||||
{
|
{
|
||||||
public interface ITelevisionFolderScanner
|
public interface ITelevisionFolderScanner
|
||||||
{
|
{
|
||||||
Task<Either<BaseError, Unit>> ScanFolder(LibraryPath libraryPath, string ffprobePath);
|
Task<Either<BaseError, Unit>> ScanFolder(LibraryPath libraryPath, string ffprobePath, DateTimeOffset lastScan);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,10 @@ namespace ErsatzTV.Core.Metadata
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Either<BaseError, Unit>> ScanFolder(LibraryPath libraryPath, string ffprobePath)
|
public async Task<Either<BaseError, Unit>> ScanFolder(
|
||||||
|
LibraryPath libraryPath,
|
||||||
|
string ffprobePath,
|
||||||
|
DateTimeOffset lastScan)
|
||||||
{
|
{
|
||||||
if (!_localFileSystem.IsLibraryPathAccessible(libraryPath))
|
if (!_localFileSystem.IsLibraryPathAccessible(libraryPath))
|
||||||
{
|
{
|
||||||
@@ -76,6 +79,11 @@ namespace ErsatzTV.Core.Metadata
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_localFileSystem.GetLastWriteTime(movieFolder) < lastScan)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
foreach (string file in allFiles.OrderBy(identity))
|
foreach (string file in allFiles.OrderBy(identity))
|
||||||
{
|
{
|
||||||
// TODO: figure out how to rebuild playlists
|
// TODO: figure out how to rebuild playlists
|
||||||
|
|||||||
@@ -45,7 +45,10 @@ namespace ErsatzTV.Core.Metadata
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Either<BaseError, Unit>> ScanFolder(LibraryPath libraryPath, string ffprobePath)
|
public async Task<Either<BaseError, Unit>> ScanFolder(
|
||||||
|
LibraryPath libraryPath,
|
||||||
|
string ffprobePath,
|
||||||
|
DateTimeOffset lastScan)
|
||||||
{
|
{
|
||||||
if (!_localFileSystem.IsLibraryPathAccessible(libraryPath))
|
if (!_localFileSystem.IsLibraryPathAccessible(libraryPath))
|
||||||
{
|
{
|
||||||
@@ -77,7 +80,7 @@ namespace ErsatzTV.Core.Metadata
|
|||||||
await _searchIndex.UpdateItems(new List<MediaItem> { result.Item });
|
await _searchIndex.UpdateItems(new List<MediaItem> { result.Item });
|
||||||
}
|
}
|
||||||
|
|
||||||
await ScanSeasons(libraryPath, ffprobePath, result.Item, showFolder);
|
await ScanSeasons(libraryPath, ffprobePath, result.Item, showFolder, lastScan);
|
||||||
},
|
},
|
||||||
_ => Task.FromResult(Unit.Default));
|
_ => Task.FromResult(Unit.Default));
|
||||||
}
|
}
|
||||||
@@ -113,7 +116,8 @@ namespace ErsatzTV.Core.Metadata
|
|||||||
LibraryPath libraryPath,
|
LibraryPath libraryPath,
|
||||||
string ffprobePath,
|
string ffprobePath,
|
||||||
Show show,
|
Show show,
|
||||||
string showFolder)
|
string showFolder,
|
||||||
|
DateTimeOffset lastScan)
|
||||||
{
|
{
|
||||||
foreach (string seasonFolder in _localFileSystem.ListSubdirectories(showFolder).Filter(ShouldIncludeFolder)
|
foreach (string seasonFolder in _localFileSystem.ListSubdirectories(showFolder).Filter(ShouldIncludeFolder)
|
||||||
.OrderBy(identity))
|
.OrderBy(identity))
|
||||||
@@ -127,7 +131,7 @@ namespace ErsatzTV.Core.Metadata
|
|||||||
.BindT(season => UpdatePoster(season, seasonFolder));
|
.BindT(season => UpdatePoster(season, seasonFolder));
|
||||||
|
|
||||||
await maybeSeason.Match(
|
await maybeSeason.Match(
|
||||||
season => ScanEpisodes(libraryPath, ffprobePath, season, seasonFolder),
|
season => ScanEpisodes(libraryPath, ffprobePath, season, seasonFolder, lastScan),
|
||||||
_ => Task.FromResult(Unit.Default));
|
_ => Task.FromResult(Unit.Default));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -139,8 +143,14 @@ namespace ErsatzTV.Core.Metadata
|
|||||||
LibraryPath libraryPath,
|
LibraryPath libraryPath,
|
||||||
string ffprobePath,
|
string ffprobePath,
|
||||||
Season season,
|
Season season,
|
||||||
string seasonPath)
|
string seasonPath,
|
||||||
|
DateTimeOffset lastScan)
|
||||||
{
|
{
|
||||||
|
if (_localFileSystem.GetLastWriteTime(seasonPath) < lastScan)
|
||||||
|
{
|
||||||
|
return Unit.Default;
|
||||||
|
}
|
||||||
|
|
||||||
foreach (string file in _localFileSystem.ListFiles(seasonPath)
|
foreach (string file in _localFileSystem.ListFiles(seasonPath)
|
||||||
.Filter(f => VideoFileExtensions.Contains(Path.GetExtension(f))).OrderBy(identity))
|
.Filter(f => VideoFileExtensions.Contains(Path.GetExtension(f))).OrderBy(identity))
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user