mirror of
https://github.com/sphildreth/roadie
synced 2024-11-10 06:44:12 +00:00
collection update
This commit is contained in:
parent
2334e0840f
commit
aea5afead7
9 changed files with 190 additions and 28 deletions
|
@ -114,5 +114,7 @@ namespace Roadie.Library.Data
|
||||||
void UpdateRange(params object[] entities);
|
void UpdateRange(params object[] entities);
|
||||||
|
|
||||||
void UpdateRange(IEnumerable<object> entities);
|
void UpdateRange(IEnumerable<object> entities);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="CsvHelper" Version="12.1.1" />
|
<PackageReference Include="CsvHelper" Version="12.1.1" />
|
||||||
|
<PackageReference Include="EFCore.BulkExtensions" Version="2.3.7" />
|
||||||
<PackageReference Include="FluentFTP" Version="19.2.2" />
|
<PackageReference Include="FluentFTP" Version="19.2.2" />
|
||||||
<PackageReference Include="HtmlAgilityPack" Version="1.8.11" />
|
<PackageReference Include="HtmlAgilityPack" Version="1.8.11" />
|
||||||
<PackageReference Include="IdSharp.Common" Version="1.0.1" />
|
<PackageReference Include="IdSharp.Common" Version="1.0.1" />
|
||||||
|
|
|
@ -6,6 +6,7 @@ using Roadie.Api.Hubs;
|
||||||
using Roadie.Library;
|
using Roadie.Library;
|
||||||
using Roadie.Library.Caching;
|
using Roadie.Library.Caching;
|
||||||
using Roadie.Library.Configuration;
|
using Roadie.Library.Configuration;
|
||||||
|
using Roadie.Library.Data;
|
||||||
using Roadie.Library.Encoding;
|
using Roadie.Library.Encoding;
|
||||||
using Roadie.Library.Engines;
|
using Roadie.Library.Engines;
|
||||||
using Roadie.Library.Enums;
|
using Roadie.Library.Enums;
|
||||||
|
@ -24,6 +25,7 @@ using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using data = Roadie.Library.Data;
|
using data = Roadie.Library.Data;
|
||||||
|
|
||||||
namespace Roadie.Api.Services
|
namespace Roadie.Api.Services
|
||||||
|
@ -292,6 +294,127 @@ namespace Roadie.Api.Services
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<OperationResult<bool>> ScanCollection(ApplicationUser user, Guid collectionId, bool isReadOnly = false, bool doPurgeFirst = true)
|
||||||
|
{
|
||||||
|
var sw = new Stopwatch();
|
||||||
|
sw.Start();
|
||||||
|
|
||||||
|
var result = new List<PositionAristRelease>();
|
||||||
|
var errors = new List<Exception>();
|
||||||
|
var collection = this.DbContext.Collections.FirstOrDefault(x => x.RoadieId == collectionId);
|
||||||
|
if (collection == null)
|
||||||
|
{
|
||||||
|
await this.LogAndPublish($"ScanCollection Unknown Collection [{ collectionId}]", LogLevel.Warning);
|
||||||
|
return new OperationResult<bool>(true, $"Collection Not Found [{ collectionId }]");
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (doPurgeFirst)
|
||||||
|
{
|
||||||
|
var crs = this.DbContext.CollectionReleases.Where(x => x.CollectionId == collection.Id).ToArray();
|
||||||
|
this.DbContext.CollectionReleases.RemoveRange(crs);
|
||||||
|
await this.DbContext.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
var par = collection.PositionArtistReleases();
|
||||||
|
if (par != null)
|
||||||
|
{
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
var modifiedDb = false;
|
||||||
|
foreach (var csvRelease in par)
|
||||||
|
{
|
||||||
|
data.Release release = null;
|
||||||
|
CollectionRelease isInCollection = null;
|
||||||
|
OperationResult<data.Release> releaseResult = null;
|
||||||
|
data.Artist artist = null;
|
||||||
|
var artistResult = await this.ArtistLookupEngine.GetByName(new AudioMetaData { Artist = csvRelease.Artist });
|
||||||
|
if (!artistResult.IsSuccess)
|
||||||
|
{
|
||||||
|
this.Logger.LogWarning("Unable To Find Artist [{0}]", csvRelease.Artist);
|
||||||
|
csvRelease.Status = Library.Enums.Statuses.Missing;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
artist = artistResult.Data;
|
||||||
|
}
|
||||||
|
if (artist != null)
|
||||||
|
{
|
||||||
|
releaseResult = await this.ReleaseLookupEngine.GetByName(artist, new AudioMetaData { Release = csvRelease.Release });
|
||||||
|
if (!releaseResult.IsSuccess)
|
||||||
|
{
|
||||||
|
this.Logger.LogWarning("Unable To Find Release [{0}]", csvRelease.Release);
|
||||||
|
csvRelease.Status = Library.Enums.Statuses.Missing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (releaseResult != null)
|
||||||
|
{
|
||||||
|
release = releaseResult.Data;
|
||||||
|
}
|
||||||
|
if (artist != null && release != null)
|
||||||
|
{
|
||||||
|
isInCollection = this.DbContext.CollectionReleases.FirstOrDefault(x => x.CollectionId == collection.Id && x.ListNumber == csvRelease.Position && x.ReleaseId == release.Id);
|
||||||
|
// Found in Database but not in collection add to Collection
|
||||||
|
if (isInCollection == null)
|
||||||
|
{
|
||||||
|
this.DbContext.CollectionReleases.Add(new CollectionRelease
|
||||||
|
{
|
||||||
|
CollectionId = collection.Id,
|
||||||
|
ReleaseId = release.Id,
|
||||||
|
ListNumber = csvRelease.Position,
|
||||||
|
});
|
||||||
|
modifiedDb = true;
|
||||||
|
}
|
||||||
|
// If Item in Collection is at different List number update CollectionRelease
|
||||||
|
else if (isInCollection.ListNumber != csvRelease.Position)
|
||||||
|
{
|
||||||
|
isInCollection.LastUpdated = now;
|
||||||
|
isInCollection.ListNumber = csvRelease.Position;
|
||||||
|
modifiedDb = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.Logger.LogWarning("Unable To Find Artist Or Release For Collection Entry [{0}]", csvRelease.ToString());
|
||||||
|
result.Add(csvRelease);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (modifiedDb)
|
||||||
|
{
|
||||||
|
collection.LastUpdated = now;
|
||||||
|
var dto = new Library.Models.Collections.CollectionList
|
||||||
|
{
|
||||||
|
CollectionCount = collection.CollectionCount,
|
||||||
|
CollectionFoundCount = (from cr in this.DbContext.CollectionReleases
|
||||||
|
where cr.CollectionId == collection.Id
|
||||||
|
select cr.CollectionId).Count()
|
||||||
|
};
|
||||||
|
if (dto.PercentComplete == 100)
|
||||||
|
{
|
||||||
|
// Lock so future scans dont happen, with DB RI when releases are deleted they are removed from collection
|
||||||
|
collection.IsLocked = true;
|
||||||
|
collection.Status = Statuses.Complete;
|
||||||
|
}
|
||||||
|
await this.DbContext.SaveChangesAsync();
|
||||||
|
this.CacheManager.ClearRegion(collection.CacheRegion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
this.Logger.LogError(ex);
|
||||||
|
errors.Add(ex);
|
||||||
|
}
|
||||||
|
sw.Stop();
|
||||||
|
this.Logger.LogInformation(string.Format("RescanCollection `{0}`, By User `{1}`", collection, user));
|
||||||
|
|
||||||
|
return new OperationResult<bool>
|
||||||
|
{
|
||||||
|
IsSuccess = !errors.Any(),
|
||||||
|
Data = true,
|
||||||
|
OperationTime = sw.ElapsedMilliseconds,
|
||||||
|
Errors = errors
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<OperationResult<bool>> ScanInboundFolder(ApplicationUser user, bool isReadOnly = false)
|
public async Task<OperationResult<bool>> ScanInboundFolder(ApplicationUser user, bool isReadOnly = false)
|
||||||
{
|
{
|
||||||
var d = new DirectoryInfo(this.Configuration.InboundFolder);
|
var d = new DirectoryInfo(this.Configuration.InboundFolder);
|
||||||
|
|
|
@ -23,5 +23,7 @@ namespace Roadie.Api.Services
|
||||||
Task<OperationResult<bool>> ScanLibraryFolder(ApplicationUser user, bool isReadOnly = false);
|
Task<OperationResult<bool>> ScanLibraryFolder(ApplicationUser user, bool isReadOnly = false);
|
||||||
|
|
||||||
Task<OperationResult<bool>> ScanRelease(ApplicationUser user, Guid releaseId, bool isReadOnly = false);
|
Task<OperationResult<bool>> ScanRelease(ApplicationUser user, Guid releaseId, bool isReadOnly = false);
|
||||||
|
|
||||||
|
Task<OperationResult<bool>> ScanCollection(ApplicationUser user, Guid collectionId, bool isReadOnly = false, bool doPurgeFirst = true);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -177,7 +177,7 @@ namespace Roadie.Api.Services
|
||||||
join r in this.DbContext.Releases on cr.ReleaseId equals r.Id
|
join r in this.DbContext.Releases on cr.ReleaseId equals r.Id
|
||||||
where c.RoadieId == request.FilterToCollectionId.Value
|
where c.RoadieId == request.FilterToCollectionId.Value
|
||||||
orderby cr.ListNumber
|
orderby cr.ListNumber
|
||||||
select r.Id).Skip(request.SkipValue).Take(request.LimitValue).ToArray();
|
select r.Id).ToArray();
|
||||||
}
|
}
|
||||||
int[] favoriteReleaseIds = new int[0];
|
int[] favoriteReleaseIds = new int[0];
|
||||||
if (request.FilterFavoriteOnly)
|
if (request.FilterFavoriteOnly)
|
||||||
|
@ -334,10 +334,9 @@ namespace Roadie.Api.Services
|
||||||
var collectionReleases = (from c in this.DbContext.Collections
|
var collectionReleases = (from c in this.DbContext.Collections
|
||||||
join cr in this.DbContext.CollectionReleases on c.Id equals cr.CollectionId
|
join cr in this.DbContext.CollectionReleases on c.Id equals cr.CollectionId
|
||||||
where c.RoadieId == request.FilterToCollectionId
|
where c.RoadieId == request.FilterToCollectionId
|
||||||
where collectionReleaseIds.Contains(cr.ReleaseId)
|
|
||||||
orderby cr.ListNumber
|
|
||||||
select cr);
|
select cr);
|
||||||
foreach (var par in collection.PositionArtistReleases().OrderBy(x => x.Index).Skip(request.SkipValue).Take(request.LimitValue))
|
var pars = collection.PositionArtistReleases().ToArray();
|
||||||
|
foreach (var par in pars)
|
||||||
{
|
{
|
||||||
var cr = collectionReleases.FirstOrDefault(x => x.ListNumber == par.Position);
|
var cr = collectionReleases.FirstOrDefault(x => x.ListNumber == par.Position);
|
||||||
// Release is known for Collection CSV, find newRow and update ListNumber
|
// Release is known for Collection CSV, find newRow and update ListNumber
|
||||||
|
@ -350,12 +349,6 @@ namespace Roadie.Api.Services
|
||||||
{
|
{
|
||||||
parRelease.ListNumber = par.Position;
|
parRelease.ListNumber = par.Position;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
var anotherInstanceOfReleaseInCollection = parRelease.ShallowCopy();
|
|
||||||
anotherInstanceOfReleaseInCollection.ListNumber = par.Position;
|
|
||||||
newRows.Add(anotherInstanceOfReleaseInCollection);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Release is not known add missing dummy release to rows
|
// Release is not known add missing dummy release to rows
|
||||||
|
@ -379,7 +372,7 @@ namespace Roadie.Api.Services
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Resort the list for the collection by listNumber
|
// Resort the list for the collection by listNumber
|
||||||
rows = newRows.OrderBy(x => x.ListNumber).ToArray();
|
rows = newRows.OrderBy(x => x.ListNumber).Skip(request.SkipValue).Take(request.LimitValue).ToArray();
|
||||||
rowCount = collection.CollectionCount;
|
rowCount = collection.CollectionCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -112,27 +112,29 @@ namespace Roadie.Api.Services
|
||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var user = this.GetUser(request.u);
|
var user = this.DbContext.Users
|
||||||
|
.FirstOrDefault(x => x.UserName == request.u);
|
||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
this.Logger.LogInformation($"Unknown User [{ request.u }]");
|
this.Logger.LogInformation($"Unknown User [{ request.u }]");
|
||||||
return new subsonic.SubsonicOperationResult<subsonic.SubsonicAuthenticateResponse>(subsonic.ErrorCodes.WrongUsernameOrPassword, $"Unknown Username");
|
return new subsonic.SubsonicOperationResult<subsonic.SubsonicAuthenticateResponse>(subsonic.ErrorCodes.WrongUsernameOrPassword, $"Unknown Username");
|
||||||
}
|
}
|
||||||
var password = request.Password;
|
var password = request.Password;
|
||||||
var wasAuthenticatedAgainstPassword = false;
|
|
||||||
if (!string.IsNullOrEmpty(request.s))
|
if (!string.IsNullOrEmpty(request.s))
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
var token = HashHelper.MD5Hash((user.ApiToken ?? user.Email) + request.s);
|
var token = HashHelper.MD5Hash((user.ApiToken ?? user.Email) + request.s);
|
||||||
if (!token.Equals(request.t, StringComparison.OrdinalIgnoreCase))
|
if (!token.Equals(request.t, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
user = null;
|
user = null;
|
||||||
}
|
}
|
||||||
else
|
}
|
||||||
|
catch
|
||||||
{
|
{
|
||||||
wasAuthenticatedAgainstPassword = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (user != null && !string.IsNullOrEmpty(user.PasswordHash) && !string.IsNullOrEmpty(password))
|
if (user != null && !string.IsNullOrEmpty(user.PasswordHash) && !string.IsNullOrEmpty(password))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -141,24 +143,21 @@ namespace Roadie.Api.Services
|
||||||
{
|
{
|
||||||
user = null;
|
user = null;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
wasAuthenticatedAgainstPassword = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (wasAuthenticatedAgainstPassword)
|
if (user != null)
|
||||||
{
|
{
|
||||||
// Since API dont update LastLogin which likely invalidates any browser logins
|
var now = DateTime.UtcNow;
|
||||||
user.LastApiAccess = DateTime.UtcNow;
|
user.LastUpdated = now;
|
||||||
|
user.LastApiAccess = now;
|
||||||
await this.DbContext.SaveChangesAsync();
|
await this.DbContext.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
this.Logger.LogInformation($"Unknown User [{ request.u }]");
|
this.Logger.LogInformation($"Invalid Credentials given for User [{ request.u }]");
|
||||||
return new subsonic.SubsonicOperationResult<subsonic.SubsonicAuthenticateResponse>(subsonic.ErrorCodes.WrongUsernameOrPassword, $"Unknown Username");
|
return new subsonic.SubsonicOperationResult<subsonic.SubsonicAuthenticateResponse>(subsonic.ErrorCodes.WrongUsernameOrPassword, $"Unknown Username");
|
||||||
}
|
}
|
||||||
this.Logger.LogInformation($"Subsonic: Successfully Authenticated User [{ user.ToString() }] via Application [{ request.c }], Application Version [{ request.v }]");
|
this.Logger.LogInformation($"Subsonic: Successfully Authenticated User [{ user.ToString() }] via Application [{ request.c }], Application Version [{ request.v }]");
|
||||||
|
|
|
@ -84,6 +84,18 @@ namespace Roadie.Api.Controllers
|
||||||
return Ok(result);
|
return Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("scan/collection/{id}")]
|
||||||
|
[ProducesResponseType(200)]
|
||||||
|
public async Task<IActionResult> ScanCollection(Guid id)
|
||||||
|
{
|
||||||
|
var result = await this.AdminService.ScanCollection(await this.UserManager.GetUserAsync(User), id);
|
||||||
|
if (!result.IsSuccess)
|
||||||
|
{
|
||||||
|
return StatusCode((int)HttpStatusCode.InternalServerError);
|
||||||
|
}
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
[HttpPost("delete/release/{id}")]
|
[HttpPost("delete/release/{id}")]
|
||||||
[ProducesResponseType(200)]
|
[ProducesResponseType(200)]
|
||||||
public async Task<IActionResult> DeleteRelease(Guid id, bool? doDeleteFiles)
|
public async Task<IActionResult> DeleteRelease(Guid id, bool? doDeleteFiles)
|
||||||
|
|
|
@ -6,6 +6,8 @@ using Microsoft.Extensions.Logging;
|
||||||
using Roadie.Api.Services;
|
using Roadie.Api.Services;
|
||||||
using Roadie.Library.Caching;
|
using Roadie.Library.Caching;
|
||||||
using Roadie.Library.Identity;
|
using Roadie.Library.Identity;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Roadie.Api.Controllers
|
namespace Roadie.Api.Controllers
|
||||||
|
@ -25,6 +27,34 @@ namespace Roadie.Api.Controllers
|
||||||
this.StatisticsService = statisticsService;
|
this.StatisticsService = statisticsService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("ping")]
|
||||||
|
[ProducesResponseType(200)]
|
||||||
|
[AllowAnonymous]
|
||||||
|
public IActionResult Ping()
|
||||||
|
{
|
||||||
|
return Ok("pong");
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("info")]
|
||||||
|
[ProducesResponseType(200)]
|
||||||
|
[AllowAnonymous]
|
||||||
|
public IActionResult Info()
|
||||||
|
{
|
||||||
|
var proc = Process.GetCurrentProcess();
|
||||||
|
var mem = proc.WorkingSet64;
|
||||||
|
var cpu = proc.TotalProcessorTime;
|
||||||
|
var messages = new List<string>();
|
||||||
|
messages.Add("▜ Memory Information: ");
|
||||||
|
messages.Add(string.Format("My process used working set {0:n3} K of working set and CPU {1:n} msec", mem / 1024.0, cpu.TotalMilliseconds));
|
||||||
|
foreach (var aProc in Process.GetProcesses())
|
||||||
|
{
|
||||||
|
messages.Add(string.Format("Proc {0,30} CPU {1,-20:n} msec", aProc.ProcessName, cpu.TotalMilliseconds));
|
||||||
|
}
|
||||||
|
messages.Add("▟ Memory Information: ");
|
||||||
|
|
||||||
|
return Ok(messages);
|
||||||
|
}
|
||||||
|
|
||||||
[HttpGet("library")]
|
[HttpGet("library")]
|
||||||
[ProducesResponseType(200)]
|
[ProducesResponseType(200)]
|
||||||
public async Task<IActionResult> Library()
|
public async Task<IActionResult> Library()
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
"Roadie.Api": {
|
"Roadie.Api": {
|
||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Production"
|
||||||
},
|
},
|
||||||
"applicationUrl": "http://localhost:5123/"
|
"applicationUrl": "http://localhost:5123/"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue