mirror of
https://github.com/sphildreth/roadie
synced 2024-11-22 04:03:10 +00:00
resolves #30
This commit is contained in:
parent
f385c8f6fc
commit
a370108b64
35 changed files with 688 additions and 169 deletions
|
@ -21,7 +21,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="2.4.2" />
|
||||
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="2.4.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
141
Roadie.Api.Library.Tests/FolderPathHelperTests.cs
Normal file
141
Roadie.Api.Library.Tests/FolderPathHelperTests.cs
Normal file
|
@ -0,0 +1,141 @@
|
|||
using Microsoft.Extensions.Configuration;
|
||||
using Roadie.Library.Configuration;
|
||||
using Roadie.Library.Utility;
|
||||
using System.IO;
|
||||
using Xunit;
|
||||
|
||||
namespace Roadie.Library.Tests
|
||||
{
|
||||
public class FolderPathHelperTests
|
||||
{
|
||||
private IRoadieSettings Configuration { get; }
|
||||
|
||||
public FolderPathHelperTests()
|
||||
{
|
||||
var settings = new RoadieSettings();
|
||||
IConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
|
||||
configurationBuilder.AddJsonFile("appsettings.test.json");
|
||||
IConfiguration configuration = configurationBuilder.Build();
|
||||
configuration.GetSection("RoadieSettings").Bind(settings);
|
||||
this.Configuration = settings;
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Bob Jones", 1, @"B\BO\Bob Jones [1]")]
|
||||
[InlineData("Smith, Angie", 2, @"S\SM\Smith, Angie [2]")]
|
||||
[InlineData("Blues Traveler", 89, @"B\BL\Blues Traveler [89]")]
|
||||
[InlineData("Scott, Travis", 3, @"S\SC\Scott, Travis [3]")]
|
||||
[InlineData("Straight, George", 783, @"S\ST\Straight, George [783]")]
|
||||
[InlineData("Good! Times?", 213, @"G\GO\Good Times [213]")]
|
||||
[InlineData("3rd Attempt, The", 56, @"0\3R\3rd Attempt, The [56]")]
|
||||
[InlineData("3Oh!3", 589, @"0\3O\3oh3 [589]")]
|
||||
[InlineData(" 3OH!3", 589, @"0\3O\3oh3 [589]")]
|
||||
[InlineData("3oh!3 ", 589, @"0\3O\3oh3 [589]")]
|
||||
[InlineData("B", 3423, @"B\B\B [3423]")]
|
||||
[InlineData("BB", 3423, @"B\BB\Bb [3423]")]
|
||||
[InlineData("Bad.Boys.Club", 433423, @"B\BA\Bad Boys Club [433423]")]
|
||||
[InlineData("Bad_Boys_Club", 433423, @"B\BA\Bad Boys Club [433423]")]
|
||||
[InlineData("Bad.Boys Club", 433423, @"B\BA\Bad Boys Club [433423]")]
|
||||
[InlineData("Bad~Boys~Club", 433423, @"B\BA\Bad Boys Club [433423]")]
|
||||
[InlineData("Bad-Boys=Club", 433423, @"B\BA\Bad Boys Club [433423]")]
|
||||
[InlineData("Bad.Boys~Club_2", 433423, @"B\BA\Bad Boys Club 2 [433423]")]
|
||||
[InlineData("13", 43234, @"0\13\13 [43234]")]
|
||||
[InlineData("69 Eyes, The", 123, @"0\69\69 Eyes, The [123]")]
|
||||
[InlineData("2 Unlimited", 234, @"0\2U\2 Unlimited [234]")]
|
||||
[InlineData("A Flock Of Seagulls", 567, @"A\AF\A Flock Of Seagulls [567]")]
|
||||
[InlineData("1975, The", 23, @"0\19\1975, The [23]")]
|
||||
[InlineData("1914", 89, @"0\19\1914 [89]")]
|
||||
[InlineData("13th Floor Elevators, The", 24, @"0\13\13th Floor Elevators, The [24]")]
|
||||
[InlineData("13TH FLOOR ELEVATORS, THE", 24, @"0\13\13th Floor Elevators, The [24]")]
|
||||
[InlineData("13th floor elevators, the", 24, @"0\13\13th Floor Elevators, The [24]")]
|
||||
[InlineData("3 Doors Down", 11111111, @"0\3D\3 Doors Down [11111111]")]
|
||||
[InlineData(" 3 Doors Down", 11111111, @"0\3D\3 Doors Down [11111111]")]
|
||||
[InlineData("01234567890123456789012345678901234567890123456789", 123, @"0\01\01234567890123456789012345678901234567890123456789 [123]")]
|
||||
[InlineData("012345678901234567890", 1111111111, @"0\01\012345678901234567890 [1111111111]")]
|
||||
[InlineData("Royce Da 5'9\"", 876, @"R\RO\Royce Da 59 [876]")]
|
||||
[InlineData("8", 434, @"0\8\8 [434]")]
|
||||
[InlineData("Hay, Colin", 888, @"H\HA\Hay, Colin [888]")]
|
||||
[InlineData("Ty Dolla $Ign", 6542, @"T\TY\Ty Dolla Sign [6542]")] //
|
||||
[InlineData("Panic! At The Disco", 32, @"P\PA\Panic At The Disco [32]")]
|
||||
[InlineData("Ke$ha", 98, @"K\KE\Kesha [98]")]
|
||||
[InlineData("(Whispers)", 432, @"W\WH\Whispers [432]")]
|
||||
[InlineData("Whiskers, John \"Catfish\"", 32342, @"W\WH\Whiskers, John Catfish [32342]")]
|
||||
[InlineData("'68", 54321, @"0\68\68 [54321]")]
|
||||
[InlineData("2Pac", 987654321, @"0\2P\2pac [987654321]")]
|
||||
[InlineData("007 Superhero", 1, @"0\00\007 Superhero [1]")]
|
||||
[InlineData("4 Non Blondes", 666, @"0\4N\4 Non Blondes [666]")]
|
||||
[InlineData("Dream Theater", 99, @"D\DR\Dream Theater [99]")]
|
||||
[InlineData("A.X.E. Project, The", 100, @"A\AX\A X E Project, The [100]")]
|
||||
[InlineData("IRON MAIDEN", 9909, @"I\IR\Iron Maiden [9909]")]
|
||||
[InlineData("iRoN MaiDEn", 9909, @"I\IR\Iron Maiden [9909]")]
|
||||
[InlineData(" iRoN MaiDEn ", 9909, @"I\IR\Iron Maiden [9909]")]
|
||||
[InlineData(" i R o N M a i D E n ", 9909, @"I\IR\I R O N M A I D E N [9909]")]
|
||||
[InlineData("iron maiden", 9909, @"I\IR\Iron Maiden [9909]")]
|
||||
[InlineData("Dreamside, The", 85, @"D\DR\Dreamside, The [85]")]
|
||||
[InlineData("Stacy & Tom", 41, @"S\ST\Stacy And Tom [41]")]
|
||||
[InlineData("A! Whack To The Head", 2111, @"A\AW\A Whack To The Head [2111]")]
|
||||
[InlineData("!WHACKO!", 2112, @"W\WH\Whacko [2112]")]
|
||||
[InlineData("Öysterhead", 7653, @"O\OY\Oysterhead [7653]")]
|
||||
[InlineData("Öystërheäd", 7653, @"O\OY\Oysterhead [7653]")]
|
||||
[InlineData("2Nu", 7653, @"0\2N\2nu [7653]")]
|
||||
[InlineData("!SÖmëthing el$e", 73223, @"S\SO\Something Else [73223]")]
|
||||
[InlineData("McDonald, Audra", 1232, @"M\MC\McDonald, Audra [1232]")]
|
||||
[InlineData("McKnight, Brian", 1233, @"M\MC\McKnight, Brian [1233]")]
|
||||
[InlineData("Womack, Lee Ann", 1234, @"W\WO\Womack, Lee Ann [1234]")]
|
||||
[InlineData("At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non providenta", 123456789, @"A\AT\At Vero Eos Et Accusamus Et Iusto Odio Dignissimos Ducimus Qui Blanditiis Praesent [123456789]")]
|
||||
public void GenerateArtistFolderNames(string input, int artistId, string shouldBe)
|
||||
{
|
||||
var artistFolder = FolderPathHelper.ArtistPath(Configuration, artistId, input);
|
||||
var t = new DirectoryInfo(Path.Combine(Configuration.LibraryFolder, shouldBe));
|
||||
Assert.Equal(t.FullName, artistFolder);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("!SÖmëthing el$e", @"S\SO")]
|
||||
[InlineData("A&M Records", @"A\AA")]
|
||||
[InlineData("A!M Records", @"A\AM")]
|
||||
[InlineData("Aware Records", @"A\AW")]
|
||||
[InlineData("Jive", @"J\JI")]
|
||||
[InlineData("Le Plan", @"L\LE")]
|
||||
[InlineData("42 Records", @"0\42")]
|
||||
public void GenerateLabelFolderNames(string input, string shouldBe)
|
||||
{
|
||||
var artistFolder = FolderPathHelper.LabelPath(Configuration, input);
|
||||
var t = new DirectoryInfo(Path.Combine(Configuration.LabelImageFolder, shouldBe));
|
||||
Assert.Equal(t.FullName, artistFolder);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("What Dreams May Come", "01/15/2004", @"D\DR\Dream Theater [99]", @"D\DR\Dream Theater [99]\[2004] What Dreams May Come")]
|
||||
[InlineData("Killers", "01/01/1980", @"I\IR\Iron Maiden [9909]", @"I\IR\Iron Maiden [9909]\[1980] Killers")]
|
||||
[InlineData("Original Hits (80S 12'') (72 Original Hits) Cd 5", "01/01/1980", @"I\IR\Iron Maiden [9909]", @"I\IR\Iron Maiden [9909]\[1980] Original Hits 80s 12 72 Original Hits Cd 5")]
|
||||
[InlineData("Vices & Virtues", "01/01/1973", @"I\IR\Iron Maiden [9909]", @"I\IR\Iron Maiden [9909]\[1973] Vices And Virtues")]
|
||||
[InlineData("Pretty, Odd.", "01/01/1973", @"I\IR\Iron Maiden [9909]", @"I\IR\Iron Maiden [9909]\[1973] Pretty, Odd.")]
|
||||
[InlineData(" !A Pretty Odd Day", "01/01/1973", @"I\IR\Iron Maiden [9909]", @"I\IR\Iron Maiden [9909]\[1973] A Pretty Odd Day")]
|
||||
[InlineData("A.Pretty~Odd=Day", "01/01/1973", @"I\IR\Iron Maiden [9909]", @"I\IR\Iron Maiden [9909]\[1973] A Pretty Odd Day")]
|
||||
[InlineData("A", "01/01/1973", @"I\IR\Iron Maiden [9909]", @"I\IR\Iron Maiden [9909]\[1973] A")]
|
||||
[InlineData("X", "01/01/1973", @"I\IR\Iron Maiden [9909]", @"I\IR\Iron Maiden [9909]\[1973] X")]
|
||||
[InlineData("Done-Over", "01/01/1973", @"I\IR\Iron Maiden [9909]", @"I\IR\Iron Maiden [9909]\[1973] Done Over")]
|
||||
[InlineData("%", "01/01/1973", @"I\IR\Iron Maiden [9909]", @"I\IR\Iron Maiden [9909]\[1973] Per")]
|
||||
[InlineData("The Blues Brothers: Music From The Soundtrack", "01/01/1973", @"I\IR\Iron Maiden [9909]", @"I\IR\Iron Maiden [9909]\[1973] The Blues Brothers Music From The Soundtrack")]
|
||||
[InlineData("14", "01/01/1973", @"I\IR\Iron Maiden [9909]", @"I\IR\Iron Maiden [9909]\[1973] 14")]
|
||||
[InlineData(@"12\18\2103", "01/01/1973", @"I\IR\Iron Maiden [9909]", @"I\IR\Iron Maiden [9909]\[1973] 12182103")]
|
||||
[InlineData(@"Insect Warfare / The Kill", "01/01/1973", @"I\IR\Iron Maiden [9909]", @"I\IR\Iron Maiden [9909]\[1973] Insect Warfare The Kill")]
|
||||
[InlineData("Live (1955)", "01/01/1973", @"I\IR\Iron Maiden [9909]", @"I\IR\Iron Maiden [9909]\[1973] Live 1955")]
|
||||
[InlineData("We're Only In It For The Money", "01/01/1968", @"I\IR\Iron Maiden [9909]", @"I\IR\Iron Maiden [9909]\[1968] Were Only In It For The Money")]
|
||||
[InlineData("Rock \"N Roll High School", "01/01/1968", @"I\IR\Iron Maiden [9909]", @"I\IR\Iron Maiden [9909]\[1968] Rock N Roll High School")]
|
||||
[InlineData("1958-1959 Dr. Jekyll (With Cannonball Adderley)", "01/01/1968", @"I\IR\Iron Maiden [9909]", @"I\IR\Iron Maiden [9909]\[1968] 1958 1959 Dr Jekyll With Cannonball Adderley")]
|
||||
[InlineData("Hello, Dolly!", "01/01/1968", @"I\IR\Iron Maiden [9909]", @"I\IR\Iron Maiden [9909]\[1968] Hello, Dolly")]
|
||||
[InlineData("Deäth", "01/01/1981", @"I\IR\Iron Maiden [9909]", @"I\IR\Iron Maiden [9909]\[1981] Death")]
|
||||
[InlineData("Sinatra's Swingin' Session!!! And More", "01/01/1981", @"I\IR\Iron Maiden [9909]", @"I\IR\Iron Maiden [9909]\[1981] Sinatras Swingin Session And More")]
|
||||
[InlineData("01234567890123456789012345678901234567890123456789", "01/01/1974", @"I\IR\Iron Maiden [9909]", @"I\IR\Iron Maiden [9909]\[1974] 01234567890123456789012345678901234567890123456789")]
|
||||
[InlineData("At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non providenta", "01/01/1975", @"I\IR\Iron Maiden [9909]", @"I\IR\Iron Maiden [9909]\[1975] At Vero Eos Et Accusamus Et Iusto Odio Dignissimos Ducimus Qui Blanditiis Praesentium Volupta")]
|
||||
public void GenerateReleaseFolderNames(string input, string releaseDate, string artistFolder, string shouldBe)
|
||||
{
|
||||
var af = new DirectoryInfo(Path.Combine(Configuration.LibraryFolder, artistFolder));
|
||||
var releaseFolder = FolderPathHelper.ReleasePath(af.FullName, input, SafeParser.ToDateTime(releaseDate).Value);
|
||||
var t = new DirectoryInfo(Path.Combine(Configuration.LibraryFolder, shouldBe));
|
||||
Assert.Equal(t.FullName, releaseFolder);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
using Roadie.Library.Imaging;
|
||||
using System;
|
||||
using System.IO;
|
||||
using Xunit;
|
||||
|
||||
namespace Roadie.Library.Tests
|
||||
|
@ -9,6 +10,10 @@ namespace Roadie.Library.Tests
|
|||
[Fact]
|
||||
public void GenerateImageHash()
|
||||
{
|
||||
if(!Directory.Exists(@"C:\temp\image_tests"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
var imageFilename = @"C:\temp\image_tests\1.jpg";
|
||||
var secondImagFilename = @"C:\temp\image_tests\2.jpg";
|
||||
var resizedFirstImageFilename = @"C:\temp\image_tests\1-resized.jpg";
|
||||
|
|
|
@ -413,7 +413,7 @@ namespace Roadie.Library.Tests
|
|||
}
|
||||
context.SaveChanges();
|
||||
|
||||
foreach (var release in context.Releases.Include(x => x.Artist).Where(x => x.Thumbnail != null).OrderBy(x => x.Title))
|
||||
foreach (var release in context.Releases.Include(x => x.Artist).Where(x => x.Thumbnail != null).OrderBy(x => x.SortTitle ?? x.Title))
|
||||
{
|
||||
var artistFolder = release.Artist.ArtistFileFolder(settings);
|
||||
var releaseFolder = release.ReleaseFileFolder(artistFolder);
|
||||
|
|
|
@ -24,8 +24,8 @@
|
|||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.3.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
|
|
@ -244,7 +244,8 @@ namespace Roadie.Library.Tests
|
|||
[InlineData("Colin Hay!", "colinhay")]
|
||||
[InlineData("colinhay", "colinhay")]
|
||||
[InlineData("COLINHAY", "colinhay")]
|
||||
[InlineData("C.O!L"I$N⌐HƒAY;", "colinhay")]
|
||||
[InlineData("C.O!L"IN⌐HƒAY;", "colinhay")]
|
||||
[InlineData("$tacy $exp0t", "stacysexp0t")]
|
||||
[InlineData(" Leslie & Tom", "leslieandtom")]
|
||||
[InlineData("<b>Leslie &    Tom</b>", "leslieandtom")]
|
||||
[InlineData("Leslie;/&/;Tom", "leslieandtom")]
|
||||
|
|
|
@ -50,7 +50,7 @@ namespace Roadie.Library.Data
|
|||
|
||||
public string ArtistFileFolder(IRoadieSettings configuration)
|
||||
{
|
||||
return FolderPathHelper.ArtistPath(configuration, SortNameValue);
|
||||
return FolderPathHelper.ArtistPath(configuration, Id, SortNameValue);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
|
|
|
@ -23,12 +23,23 @@ namespace Roadie.Library.Data
|
|||
}
|
||||
}
|
||||
|
||||
public string SortNameValue => string.IsNullOrEmpty(SortName) ? Name : SortName;
|
||||
|
||||
/// <summary>
|
||||
/// Returns a full file path to the Label Image
|
||||
/// </summary>
|
||||
public string PathToImage(IRoadieSettings configuration)
|
||||
{
|
||||
return Path.Combine(configuration.LabelImageFolder, $"{ (SortName ?? Name).ToFileNameFriendly() } [{ Id }].jpg");
|
||||
return Path.Combine(FolderPathHelper.LabelPath(configuration, SortNameValue), $"{ SortNameValue.ToFileNameFriendly() } [{ Id }].jpg");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a full file path to the Label Image
|
||||
/// </summary>
|
||||
[Obsolete("This is only here for migration will be removed in future release.")]
|
||||
public string OldPathToImage(IRoadieSettings configuration)
|
||||
{
|
||||
return Path.Combine(configuration.LabelImageFolder, $"{ SortNameValue.ToFileNameFriendly() } [{ Id }].jpg");
|
||||
}
|
||||
|
||||
public bool IsValid => !string.IsNullOrEmpty(Name);
|
||||
|
|
|
@ -91,6 +91,12 @@ namespace Roadie.Library.Data
|
|||
[Required]
|
||||
public string Title { get; set; }
|
||||
|
||||
[MaxLength(250)]
|
||||
[Column("sortTitle")]
|
||||
public string SortTitle { get; set; }
|
||||
|
||||
public string SortTitleValue => string.IsNullOrEmpty(SortTitle) ? Title : SortTitle;
|
||||
|
||||
[Column("trackCount")] public short TrackCount { get; set; }
|
||||
|
||||
[Column("urls", TypeName = "text")]
|
||||
|
|
|
@ -102,14 +102,11 @@ namespace Roadie.Library.Data
|
|||
/// </summary>
|
||||
/// <param name="artistFolder"></param>
|
||||
/// <returns></returns>
|
||||
public string ReleaseFileFolder(string artistFolder)
|
||||
{
|
||||
return FolderPathHelper.ReleasePath(artistFolder, Title, ReleaseDate.Value);
|
||||
}
|
||||
public string ReleaseFileFolder(string artistFolder) => FolderPathHelper.ReleasePath(artistFolder, SortTitleValue, ReleaseDate.Value);
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Id [{Id}], Status [{ Status }], LibraryStatus [{ LibraryStatus }], Title [{Title}], Release Date [{ReleaseYear}]";
|
||||
return $"Id [{Id}], Status [{ Status }], LibraryStatus [{ LibraryStatus }], Title [{Title}], SortTitle [{ SortTitleValue }], Release Date [{ReleaseYear}]";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -72,20 +72,6 @@ namespace Roadie.Library.Engines
|
|||
artist.AlternateNames = artist.AlternateNames.AddToDelimitedList(new[] { artist.Name.ToAlphanumericName() });
|
||||
artist.Genres = null;
|
||||
artist.Images = null;
|
||||
if (artist.Thumbnail == null && ArtistImages != null)
|
||||
{
|
||||
// Set the thumbnail to the first image
|
||||
var firstImageWithNotNullBytes = ArtistImages.Where(x => x.Bytes != null).FirstOrDefault();
|
||||
if (firstImageWithNotNullBytes != null)
|
||||
{
|
||||
artist.Thumbnail = firstImageWithNotNullBytes.Bytes;
|
||||
if (artist.Thumbnail != null)
|
||||
{
|
||||
artist.Thumbnail = ImageHelper.ResizeToThumbnail(artist.Thumbnail, Configuration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!artist.IsValid)
|
||||
{
|
||||
return new OperationResult<Artist>
|
||||
|
@ -93,10 +79,6 @@ namespace Roadie.Library.Engines
|
|||
Errors = new Exception[1] { new Exception("Artist is Invalid") }
|
||||
};
|
||||
}
|
||||
if (artist.Thumbnail != null)
|
||||
{
|
||||
artist.Thumbnail = ImageHelper.ResizeToThumbnail(artist.Thumbnail, Configuration);
|
||||
}
|
||||
var addArtistResult = DbContext.Artists.Add(artist);
|
||||
var inserted = 0;
|
||||
inserted = await DbContext.SaveChangesAsync();
|
||||
|
@ -378,9 +360,6 @@ namespace Roadie.Library.Engines
|
|||
BeginDate = i.BeginDate,
|
||||
Name = result.Name ?? i.ArtistName,
|
||||
SortName = result.SortName ?? i.ArtistSortName,
|
||||
Thumbnail = i.ArtistThumbnailUrl != null
|
||||
? WebHelper.BytesForImageUrl(i.ArtistThumbnailUrl)
|
||||
: null,
|
||||
ArtistType = result.ArtistType ?? i.ArtistType
|
||||
});
|
||||
}
|
||||
|
@ -443,9 +422,6 @@ namespace Roadie.Library.Engines
|
|||
BeginDate = mb.BeginDate,
|
||||
Name = result.Name ?? mb.ArtistName,
|
||||
SortName = result.SortName ?? mb.ArtistSortName,
|
||||
Thumbnail = mb.ArtistThumbnailUrl != null
|
||||
? WebHelper.BytesForImageUrl(mb.ArtistThumbnailUrl)
|
||||
: null,
|
||||
ArtistType = mb.ArtistType
|
||||
});
|
||||
}
|
||||
|
@ -488,9 +464,6 @@ namespace Roadie.Library.Engines
|
|||
BeginDate = l.BeginDate,
|
||||
Name = result.Name ?? l.ArtistName,
|
||||
SortName = result.SortName ?? l.ArtistSortName,
|
||||
Thumbnail = l.ArtistThumbnailUrl != null
|
||||
? WebHelper.BytesForImageUrl(l.ArtistThumbnailUrl)
|
||||
: null,
|
||||
ArtistType = result.ArtistType ?? l.ArtistType
|
||||
});
|
||||
}
|
||||
|
@ -530,9 +503,6 @@ namespace Roadie.Library.Engines
|
|||
BeginDate = s.BeginDate,
|
||||
Name = result.Name ?? s.ArtistName,
|
||||
SortName = result.SortName ?? s.ArtistSortName,
|
||||
Thumbnail = s.ArtistThumbnailUrl != null
|
||||
? WebHelper.BytesForImageUrl(s.ArtistThumbnailUrl)
|
||||
: null,
|
||||
ArtistType = result.ArtistType ?? s.ArtistType
|
||||
});
|
||||
}
|
||||
|
@ -573,9 +543,6 @@ namespace Roadie.Library.Engines
|
|||
DiscogsId = d.DiscogsId,
|
||||
Name = result.Name ?? d.ArtistName,
|
||||
RealName = result.RealName ?? d.ArtistRealName,
|
||||
Thumbnail = d.ArtistThumbnailUrl != null
|
||||
? WebHelper.BytesForImageUrl(d.ArtistThumbnailUrl)
|
||||
: null,
|
||||
ArtistType = result.ArtistType ?? d.ArtistType
|
||||
});
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ using Roadie.Library.Configuration;
|
|||
using Roadie.Library.Data;
|
||||
using Roadie.Library.Encoding;
|
||||
using Roadie.Library.Extensions;
|
||||
using Roadie.Library.Imaging;
|
||||
using Roadie.Library.SearchEngines.MetaData;
|
||||
using Roadie.Library.Utility;
|
||||
using System;
|
||||
|
@ -35,10 +34,6 @@ namespace Roadie.Library.Engines
|
|||
{
|
||||
var now = DateTime.UtcNow;
|
||||
label.AlternateNames = label.AlternateNames.AddToDelimitedList(new[] { label.Name.ToAlphanumericName() });
|
||||
if (label.Thumbnail != null)
|
||||
{
|
||||
label.Thumbnail = ImageHelper.ResizeToThumbnail(label.Thumbnail, Configuration);
|
||||
}
|
||||
if (!label.IsValid)
|
||||
{
|
||||
return new OperationResult<Label>
|
||||
|
@ -182,8 +177,7 @@ namespace Roadie.Library.Engines
|
|||
{
|
||||
Profile = HttpEncoder.HtmlEncode(d.Profile),
|
||||
DiscogsId = d.DiscogsId,
|
||||
Name = result.Name ?? d.LabelName.ToTitleCase(),
|
||||
Thumbnail = d.LabelImageUrl != null ? WebHelper.BytesForImageUrl(d.LabelImageUrl) : null
|
||||
Name = result.Name ?? d.LabelName.ToTitleCase()
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -845,8 +845,6 @@ namespace Roadie.Library.Engines
|
|||
{
|
||||
var image = metaData.Images.FirstOrDefault(x => x.Type == AudioMetaDataImageType.FrontCover);
|
||||
if (image == null) image = metaData.Images.FirstOrDefault();
|
||||
// If there is an image on the metadata file itself then that over-rides metadata providers.
|
||||
if (image != null) result.Thumbnail = image.Data;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(artistFolder))
|
||||
|
@ -873,13 +871,6 @@ namespace Roadie.Library.Engines
|
|||
{
|
||||
coverFileName = cover.First().FullName;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(coverFileName))
|
||||
{
|
||||
// Read image and convert to jpeg
|
||||
result.Thumbnail = File.ReadAllBytes(coverFileName);
|
||||
Logger.LogDebug("PerformMetaDataProvidersReleaseSearch: Using Release Cover File [{0}]", coverFileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
sw.Stop();
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
Wishlist = 5,
|
||||
AdminRemoved = 6,
|
||||
Expired = 7,
|
||||
ReadyToMigrate = 8,
|
||||
Migrated = 9,
|
||||
Deleted = 99
|
||||
}
|
||||
}
|
|
@ -226,15 +226,22 @@ namespace Roadie.Library.Extensions
|
|||
return input;
|
||||
}
|
||||
|
||||
public static string ToAlphanumericName(this string input)
|
||||
public static string ToAlphanumericName(this string input, bool stripSpaces = true, bool stripCommas = true)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input)) return input;
|
||||
if (string.IsNullOrEmpty(input))
|
||||
{
|
||||
return input;
|
||||
}
|
||||
input = input.ToLower()
|
||||
.Replace("$", "s")
|
||||
.Replace("%", "per");
|
||||
input = WebUtility.HtmlDecode(input);
|
||||
input = input.ScrubHtml().ToLower().Trim().Replace("&", "and");
|
||||
input = input.ScrubHtml().ToLower()
|
||||
.Replace("&", "and");
|
||||
var arr = input.ToCharArray();
|
||||
arr = Array.FindAll(arr, c => char.IsLetterOrDigit(c));
|
||||
arr = Array.FindAll(arr, c => (c == ',' && !stripCommas) || (char.IsWhiteSpace(c) && !stripSpaces) || char.IsLetterOrDigit(c));
|
||||
input = new string(arr).RemoveDiacritics().RemoveUnicodeAccents().Translit();
|
||||
input = Regex.Replace(input, @"[^A-Za-z0-9]+", "");
|
||||
input = Regex.Replace(input, $"[^A-Za-z0-9{ ( !stripSpaces ? @"\s" : "") }{ (!stripCommas ? "," : "")}]+", "");
|
||||
return input;
|
||||
}
|
||||
|
||||
|
@ -252,7 +259,11 @@ namespace Roadie.Library.Extensions
|
|||
|
||||
public static string ToFolderNameFriendly(this string input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input)) return null;
|
||||
if (string.IsNullOrEmpty(input))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
input = input.Replace("$", "s");
|
||||
return Regex.Replace(PathSanitizer.SanitizeFilename(input, ' '), @"\s+", " ").Trim().TrimEnd('.');
|
||||
}
|
||||
|
||||
|
|
|
@ -322,16 +322,22 @@ namespace Roadie.Library.FilePlugins
|
|||
int? submissionId)
|
||||
{
|
||||
var artist = await ArtistLookupEngine.GetByName(metaData, !doJustInfo);
|
||||
if (!artist.IsSuccess) return null;
|
||||
if (!artist.IsSuccess)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
_artistId = artist.Data.RoadieId;
|
||||
var release =
|
||||
await ReleaseLookupEngine.GetByName(artist.Data, metaData, !doJustInfo, submissionId: submissionId);
|
||||
if (!release.IsSuccess) return null;
|
||||
var release = await ReleaseLookupEngine.GetByName(artist.Data, metaData, !doJustInfo, submissionId: submissionId);
|
||||
if (!release.IsSuccess)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
_releaseId = release.Data.RoadieId;
|
||||
release.Data.ReleaseDate = SafeParser.ToDateTime(release.Data.ReleaseYear ?? metaData.Year);
|
||||
if (release.Data.ReleaseYear.HasValue && release.Data.ReleaseYear != metaData.Year)
|
||||
Logger.LogWarning(
|
||||
$"Found Release `{release.Data}` has different Release Year than MetaData Year `{metaData}`");
|
||||
{
|
||||
Logger.LogWarning($"Found Release `{release.Data}` has different Release Year than MetaData Year `{metaData}`");
|
||||
}
|
||||
return release.Data.ReleaseFileFolder(artistFolder);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ namespace Roadie.Library.Imaging
|
|||
IImageFormat imageFormat = null;
|
||||
using (var image = SixLabors.ImageSharp.Image.Load(imageBytes, out imageFormat))
|
||||
{
|
||||
image.Save(outStream, ImageFormats.Jpeg);
|
||||
image.SaveAsJpeg(outStream);
|
||||
}
|
||||
return outStream.ToArray();
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ namespace Roadie.Library.Imaging
|
|||
IImageFormat imageFormat = null;
|
||||
using (var image = SixLabors.ImageSharp.Image.Load(imageBytes, out imageFormat))
|
||||
{
|
||||
image.Save(outStream, ImageFormats.Gif);
|
||||
image.SaveAsGif(outStream);
|
||||
}
|
||||
return outStream.ToArray();
|
||||
}
|
||||
|
@ -138,6 +138,10 @@ namespace Roadie.Library.Imaging
|
|||
|
||||
public static string[] GetFiles(string path, string[] patterns = null, SearchOption options = SearchOption.TopDirectoryOnly)
|
||||
{
|
||||
if(!Directory.Exists(path))
|
||||
{
|
||||
return new string[0];
|
||||
}
|
||||
if (patterns == null || patterns.Length == 0)
|
||||
{
|
||||
return Directory.GetFiles(path, "*", options);
|
||||
|
|
|
@ -477,7 +477,7 @@ namespace Roadie.Library.Inspect
|
|||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine($"❌ Deleting Empty folders in [{delEmptyFolderIn.FullName}]");
|
||||
Console.ResetColor();
|
||||
FolderPathHelper.DeleteEmptyDirs(directoryToInspect, false);
|
||||
FolderPathHelper.DeleteEmptyFolders(delEmptyFolderIn);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -77,6 +77,8 @@ namespace Roadie.Library.Models.Releases
|
|||
public Image Thumbnail { get; set; }
|
||||
[MaxLength(250)] [Required] public string Title { get; set; }
|
||||
|
||||
[MaxLength(250)] public string SortTitle { get; set; }
|
||||
|
||||
public short TrackCount { get; set; }
|
||||
public UserRelease UserRating { get; set; }
|
||||
|
||||
|
|
|
@ -9,9 +9,9 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AutoCompare.Core" Version="1.0.0" />
|
||||
<PackageReference Include="CsvHelper" Version="12.1.3" />
|
||||
<PackageReference Include="CsvHelper" Version="12.2.1" />
|
||||
<PackageReference Include="EFCore.BulkExtensions" Version="3.0.0" />
|
||||
<PackageReference Include="FluentFTP" Version="28.0.0" />
|
||||
<PackageReference Include="FluentFTP" Version="28.0.1" />
|
||||
<PackageReference Include="Hashids.net" Version="1.3.0" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.16" />
|
||||
<PackageReference Include="IdSharp.Common" Version="1.0.1" />
|
||||
|
@ -27,14 +27,14 @@
|
|||
<PackageReference Include="Microsoft.Net.Http.Headers" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.PowerShell.SDK" Version="6.2.3" />
|
||||
<PackageReference Include="MimeMapping" Version="1.0.1.17" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="NodaTime" Version="2.4.7" />
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.0.0-rc1.final" />
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.0.0-rc3.final" />
|
||||
<PackageReference Include="RestSharp" Version="106.6.10" />
|
||||
<PackageReference Include="SixLabors.Core" Version="1.0.0-beta0006" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.0-beta0005" />
|
||||
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta0005" />
|
||||
<PackageReference Include="SixLabors.Shapes" Version="1.0.0-beta0007" />
|
||||
<PackageReference Include="SixLabors.Core" Version="1.0.0-beta0008" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.0-beta0007" />
|
||||
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta0007" />
|
||||
<PackageReference Include="SixLabors.Shapes" Version="1.0.0-beta0009" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="4.6.0" />
|
||||
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="4.6.0" />
|
||||
<PackageReference Include="System.Runtime.Caching" Version="4.6.0" />
|
||||
|
|
|
@ -3,9 +3,11 @@ using Roadie.Library.Data;
|
|||
using Roadie.Library.Extensions;
|
||||
using Roadie.Library.MetaData.Audio;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Roadie.Library.Utility
|
||||
{
|
||||
|
@ -14,11 +16,114 @@ namespace Roadie.Library.Utility
|
|||
/// </summary>
|
||||
public static class FolderPathHelper
|
||||
{
|
||||
public static int MaximumLibraryFolderNameLength = 44;
|
||||
public static int MaximumArtistFolderNameLength = 100;
|
||||
public static int MaximumReleaseFolderNameLength = 100;
|
||||
public static int MaximumLabelFolderNameLength = 100;
|
||||
public static int MaximumTrackFileNameLength = 500;
|
||||
|
||||
public static IEnumerable<string> FolderSpaceReplacements = new List<string> { ".", "~", "_", "=", "-" };
|
||||
|
||||
/// <summary>
|
||||
/// Full path to Artist folder
|
||||
/// </summary>
|
||||
/// <param name="artistSortName">Sort name of Artist to use for folder name</param>
|
||||
public static string ArtistPath(IRoadieSettings configuration, string artistSortName)
|
||||
public static string ArtistPath(IRoadieSettings configuration, int artistId, string artistSortName)
|
||||
{
|
||||
SimpleContract.Requires<ArgumentException>(!string.IsNullOrEmpty(artistSortName), "Invalid Artist Sort Name");
|
||||
SimpleContract.Requires<ArgumentException>(configuration.LibraryFolder.Length < MaximumLibraryFolderNameLength, $"Library Folder maximum length is [{ MaximumLibraryFolderNameLength }]");
|
||||
|
||||
var asn = new StringBuilder(artistSortName);
|
||||
foreach (var stringReplacement in FolderSpaceReplacements)
|
||||
{
|
||||
if (!asn.Equals(stringReplacement))
|
||||
{
|
||||
asn.Replace(stringReplacement, " ");
|
||||
}
|
||||
}
|
||||
var artistFolder = asn.ToString().ToAlphanumericName(false, false).ToFolderNameFriendly().ToTitleCase(false);
|
||||
if (string.IsNullOrEmpty(artistFolder))
|
||||
{
|
||||
throw new Exception($"ArtistFolder [{ artistFolder }] is invalid. ArtistId [{ artistId }], ArtistSortName [{ artistSortName }].");
|
||||
}
|
||||
var afUpper = artistFolder.ToUpper();
|
||||
var fnSubPart1 = afUpper.ToUpper().ToCharArray().Take(1).First();
|
||||
if (!char.IsLetterOrDigit(fnSubPart1))
|
||||
{
|
||||
fnSubPart1 = '#';
|
||||
}
|
||||
else if (char.IsNumber(fnSubPart1))
|
||||
{
|
||||
fnSubPart1 = '0';
|
||||
}
|
||||
var fnSubPart2 = afUpper.Length > 2 ? afUpper.Substring(0, 2) : afUpper;
|
||||
if (fnSubPart2.EndsWith(" "))
|
||||
{
|
||||
var pos = 1;
|
||||
while (fnSubPart2.EndsWith(" "))
|
||||
{
|
||||
pos++;
|
||||
fnSubPart2 = fnSubPart2.Substring(0, 1) + afUpper.Substring(pos, 1);
|
||||
}
|
||||
}
|
||||
var fnSubPart = Path.Combine(fnSubPart1.ToString(), fnSubPart2);
|
||||
var fnIdPart = $" [{ artistId }]";
|
||||
var maxFnLength = (MaximumArtistFolderNameLength - (fnSubPart.Length + fnIdPart.Length)) - 2;
|
||||
if (artistFolder.Length > maxFnLength)
|
||||
{
|
||||
artistFolder = artistFolder.Substring(0, maxFnLength);
|
||||
}
|
||||
artistFolder = Path.Combine(fnSubPart, $"{ artistFolder }{ fnIdPart }");
|
||||
var directoryInfo = new DirectoryInfo(Path.Combine(configuration.LibraryFolder, artistFolder));
|
||||
return directoryInfo.FullName;
|
||||
}
|
||||
|
||||
|
||||
public static string LabelPath(IRoadieSettings configuration, string labelSortName)
|
||||
{
|
||||
SimpleContract.Requires<ArgumentException>(!string.IsNullOrEmpty(labelSortName), "Invalid Label Sort Name");
|
||||
SimpleContract.Requires<ArgumentException>(configuration.LibraryFolder.Length < MaximumLibraryFolderNameLength, $"Library Folder maximum length is [{ MaximumLibraryFolderNameLength }]");
|
||||
|
||||
var lsn = new StringBuilder(labelSortName);
|
||||
foreach (var stringReplacement in FolderSpaceReplacements)
|
||||
{
|
||||
if (!lsn.Equals(stringReplacement))
|
||||
{
|
||||
lsn.Replace(stringReplacement, " ");
|
||||
}
|
||||
}
|
||||
var labelFolder = lsn.ToString().ToAlphanumericName(false, false).ToFolderNameFriendly().ToTitleCase(false);
|
||||
if (string.IsNullOrEmpty(labelFolder))
|
||||
{
|
||||
throw new Exception($"LabelFolder [{ labelFolder }] is invalid. LabelSortName [{ labelSortName }].");
|
||||
}
|
||||
var lfUpper = labelFolder.ToUpper();
|
||||
var fnSubPart1 = lfUpper.ToUpper().ToCharArray().Take(1).First();
|
||||
if (!char.IsLetterOrDigit(fnSubPart1))
|
||||
{
|
||||
fnSubPart1 = '#';
|
||||
}
|
||||
else if (char.IsNumber(fnSubPart1))
|
||||
{
|
||||
fnSubPart1 = '0';
|
||||
}
|
||||
var fnSubPart2 = lfUpper.Length > 2 ? lfUpper.Substring(0, 2) : lfUpper;
|
||||
if (fnSubPart2.EndsWith(" "))
|
||||
{
|
||||
var pos = 1;
|
||||
while (fnSubPart2.EndsWith(" "))
|
||||
{
|
||||
pos++;
|
||||
fnSubPart2 = fnSubPart2.Substring(0, 1) + lfUpper.Substring(pos, 1);
|
||||
}
|
||||
}
|
||||
var fnSubPart = Path.Combine(fnSubPart1.ToString(), fnSubPart2);
|
||||
var directoryInfo = new DirectoryInfo(Path.Combine(configuration.LabelImageFolder, fnSubPart));
|
||||
return directoryInfo.FullName;
|
||||
}
|
||||
|
||||
[Obsolete("This is only here for migration will be removed in future release.")]
|
||||
public static string ArtistPathOld(IRoadieSettings configuration, string artistSortName)
|
||||
{
|
||||
SimpleContract.Requires<ArgumentException>(!string.IsNullOrEmpty(artistSortName),"Invalid Artist Sort Name");
|
||||
|
||||
|
@ -27,33 +132,53 @@ namespace Roadie.Library.Utility
|
|||
return directoryInfo.FullName;
|
||||
}
|
||||
|
||||
public static void DeleteEmptyDirs(string dir, bool deleteDirIfEmpty = true)
|
||||
|
||||
/// <summary>
|
||||
/// Full path to Release folder using given full Artist folder
|
||||
/// </summary>
|
||||
/// <param name="artistFolder">Full path to Artist folder</param>
|
||||
/// <param name="releaseTitle">Title of Release</param>
|
||||
/// <param name="releaseDate">Date of Release</param>
|
||||
public static string ReleasePath(string artistFolder, string releaseTitle, DateTime releaseDate)
|
||||
{
|
||||
if (string.IsNullOrEmpty(dir))
|
||||
SimpleContract.Requires<ArgumentException>(!string.IsNullOrEmpty(artistFolder), "Invalid Artist Folder");
|
||||
SimpleContract.Requires<ArgumentException>(artistFolder.Length < MaximumArtistFolderNameLength, $"Artist Folder is longer than maximum allowed [{ MaximumArtistFolderNameLength }]");
|
||||
|
||||
SimpleContract.Requires<ArgumentException>(!string.IsNullOrEmpty(releaseTitle), "Invalid Release Title");
|
||||
SimpleContract.Requires<ArgumentException>(releaseDate != DateTime.MinValue, "Invalid Release Date");
|
||||
|
||||
var rt = new StringBuilder(releaseTitle);
|
||||
foreach (var stringReplacement in FolderSpaceReplacements)
|
||||
{
|
||||
throw new ArgumentException("Starting directory is a null reference or an empty string", "dir");
|
||||
}
|
||||
try
|
||||
{
|
||||
foreach (var d in Directory.EnumerateDirectories(dir)) DeleteEmptyDirs(d);
|
||||
var entries = Directory.EnumerateFileSystemEntries(dir);
|
||||
if (!entries.Any() && deleteDirIfEmpty)
|
||||
if(!rt.Equals(stringReplacement))
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.Delete(dir);
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
}
|
||||
catch (DirectoryNotFoundException)
|
||||
{
|
||||
}
|
||||
rt.Replace(stringReplacement, " ");
|
||||
}
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
var releasePathTitle = rt.ToString().ToAlphanumericName(false, false).ToFolderNameFriendly().ToTitleCase(false);
|
||||
if(string.IsNullOrEmpty(releasePathTitle))
|
||||
{
|
||||
throw new Exception($"ReleaseTitle [{ releaseTitle }] is invalid. ArtistFolder [{ artistFolder }].");
|
||||
}
|
||||
var maxFnLength = MaximumReleaseFolderNameLength - 7;
|
||||
if (releasePathTitle.Length > maxFnLength)
|
||||
{
|
||||
releasePathTitle = releasePathTitle.Substring(0, maxFnLength);
|
||||
}
|
||||
var releasePath = $"[{ releaseDate.ToString("yyyy")}] {releasePathTitle}";
|
||||
var directoryInfo = new DirectoryInfo(Path.Combine(artistFolder, releasePath));
|
||||
return directoryInfo.FullName;
|
||||
}
|
||||
|
||||
[Obsolete("This is only here for migration will be removed in future release.")]
|
||||
public static string ReleasePathOld(string artistFolder, string releaseTitle, DateTime releaseDate)
|
||||
{
|
||||
SimpleContract.Requires<ArgumentException>(!string.IsNullOrEmpty(artistFolder), "Invalid Artist Folder");
|
||||
SimpleContract.Requires<ArgumentException>(!string.IsNullOrEmpty(releaseTitle), "Invalid Release Title");
|
||||
SimpleContract.Requires<ArgumentException>(releaseDate != DateTime.MinValue, "Invalid Release Date");
|
||||
|
||||
var directoryInfo = new DirectoryInfo(Path.Combine(artistFolder, string.Format("{1}{0}", releaseTitle.ToTitleCase(false).ToFolderNameFriendly(), string.Format("[{0}] ", releaseDate.ToString("yyyy")))));
|
||||
return directoryInfo.FullName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -67,6 +192,7 @@ namespace Roadie.Library.Utility
|
|||
{
|
||||
return true;
|
||||
}
|
||||
var result = false;
|
||||
try
|
||||
{
|
||||
foreach (var folder in processingFolder.GetDirectories("*.*", SearchOption.AllDirectories))
|
||||
|
@ -78,27 +204,34 @@ namespace Roadie.Library.Utility
|
|||
if (!folder.GetFiles("*.*", SearchOption.AllDirectories).Any())
|
||||
{
|
||||
folder.Delete(true);
|
||||
Trace.WriteLine(string.Format("Deleting Empty Folder [{0}]", folder.FullName), "Debug");
|
||||
Trace.WriteLine($"Deleting Empty Folder [{folder.FullName}]", "Debug");
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
result = false;
|
||||
Trace.WriteLine($"UnauthorizedAccessException Deleting Empty Folder [{folder.FullName}]", "Debug");
|
||||
}
|
||||
catch (DirectoryNotFoundException)
|
||||
{
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
result = false;
|
||||
Trace.WriteLine($"DirectoryNotFoundException Deleting Empty Folder [{folder.FullName}]", "Debug");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(DirectoryNotFoundException)
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
result = false;
|
||||
Trace.WriteLine($"UnauthorizedAccessException Deleting Empty Folder [{processingFolder.FullName}]", "Debug");
|
||||
}
|
||||
catch (Exception)
|
||||
catch (DirectoryNotFoundException)
|
||||
{
|
||||
throw;
|
||||
result = false;
|
||||
Trace.WriteLine($"DirectoryNotFoundException Deleting Empty Folder [{processingFolder.FullName}]", "Debug");
|
||||
}
|
||||
return true;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -143,22 +276,6 @@ namespace Roadie.Library.Utility
|
|||
return directoryInfo.FullName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Full path to Release folder using given full Artist folder
|
||||
/// </summary>
|
||||
/// <param name="artistFolder">Full path to Artist folder</param>
|
||||
/// <param name="releaseTitle">Title of Release</param>
|
||||
/// <param name="releaseDate">Date of Release</param>
|
||||
public static string ReleasePath(string artistFolder, string releaseTitle, DateTime releaseDate)
|
||||
{
|
||||
SimpleContract.Requires<ArgumentException>(!string.IsNullOrEmpty(artistFolder), "Invalid Artist Folder");
|
||||
SimpleContract.Requires<ArgumentException>(!string.IsNullOrEmpty(releaseTitle), "Invalid Release Title");
|
||||
SimpleContract.Requires<ArgumentException>(releaseDate != DateTime.MinValue, "Invalid Release Date");
|
||||
|
||||
var directoryInfo = new DirectoryInfo(Path.Combine(artistFolder, string.Format("{1}{0}", releaseTitle.ToTitleCase(false).ToFolderNameFriendly(), string.Format("[{0}] ", releaseDate.ToString("yyyy")))));
|
||||
return directoryInfo.FullName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the FileName for given Track details, this is not the Full Path (FQDN) only the FileName
|
||||
/// </summary>
|
||||
|
@ -202,9 +319,12 @@ namespace Roadie.Library.Utility
|
|||
.ToTitleCase(false);
|
||||
var trackPathReplace = configuration.TrackPathReplace;
|
||||
if (trackPathReplace != null)
|
||||
{
|
||||
foreach (var kp in trackPathReplace)
|
||||
{
|
||||
fileNameFromTitle = fileNameFromTitle.Replace(kp.Key, kp.Value);
|
||||
|
||||
}
|
||||
}
|
||||
return string.Format("{0}{1} {2}.{3}", disc, track, fileNameFromTitle, fileExtension.ToLower());
|
||||
}
|
||||
|
||||
|
@ -215,7 +335,7 @@ namespace Roadie.Library.Utility
|
|||
/// <param name="artistFolder">Optional ArtistFolder default is to get from MetaData artist</param>
|
||||
public static string TrackFullPath(IRoadieSettings configuration, AudioMetaData metaData, string artistFolder = null, string releaseFolder = null)
|
||||
{
|
||||
return TrackFullPath(configuration, metaData.Artist, metaData.Release,
|
||||
return TrackFullPath(configuration, 0, metaData.Artist, metaData.Release,
|
||||
SafeParser.ToDateTime(metaData.Year).Value,
|
||||
metaData.Title, metaData.TrackNumber ?? 0, metaData.Disc ?? 0,
|
||||
metaData.TotalTrackNumbers ?? 0,
|
||||
|
@ -231,7 +351,7 @@ namespace Roadie.Library.Utility
|
|||
/// <param name="track">Track</param>
|
||||
/// <param name="destinationFolder">Optional Root folder defaults to Library Folder from Settings</param>
|
||||
/// <returns></returns>
|
||||
public static string TrackFullPath(IRoadieSettings configuration, Artist artist, Release release, Track track) => TrackFullPath(configuration, artist.SortNameValue, release.Title, release.ReleaseDate.Value, track.Title, track.TrackNumber);
|
||||
public static string TrackFullPath(IRoadieSettings configuration, Artist artist, Release release, Track track) => TrackFullPath(configuration, artist.Id, artist.SortNameValue, release.SortTitleValue, release.ReleaseDate.Value, track.Title, track.TrackNumber);
|
||||
|
||||
/// <summary>
|
||||
/// Return the full path (FQDN) for given Track details
|
||||
|
@ -244,20 +364,16 @@ namespace Roadie.Library.Utility
|
|||
/// <param name="discNumber">Optional disc number defaults to 0</param>
|
||||
/// <param name="totalTrackNumber">Optional Total Tracks defaults to TrackNumber</param>
|
||||
/// <param name="fileExtension">Optional File Extension defaults to mp3</param>
|
||||
public static string TrackFullPath(IRoadieSettings configuration, string artistSortName, string releaseTitle,
|
||||
public static string TrackFullPath(IRoadieSettings configuration, int artistId, string artistSortName, string releaseTitle,
|
||||
DateTime releaseDate, string trackTitle, short trackNumber, int? discNumber = null, int? totalTrackNumber = null, string fileExtension = "mp3",
|
||||
string artistFolder = null, string releaseFolder = null)
|
||||
{
|
||||
artistFolder = artistFolder ?? ArtistPath(configuration, artistSortName);
|
||||
releaseFolder = releaseFolder ?? ReleasePath(artistFolder, releaseTitle, releaseDate);
|
||||
artistFolder ??= ArtistPath(configuration, artistId, artistSortName);
|
||||
releaseFolder ??= ReleasePath(artistFolder, releaseTitle, releaseDate);
|
||||
var trackFileName = TrackFileName(configuration, trackTitle, trackNumber, discNumber, totalTrackNumber, fileExtension);
|
||||
|
||||
var result = Path.Combine(artistFolder, releaseFolder, trackFileName);
|
||||
var resultInfo = new DirectoryInfo(result);
|
||||
Trace.WriteLine(string.Format(
|
||||
"TrackPath [{0}] For ArtistName [{1}], ReleaseTitle [{2}], ReleaseDate [{3}], ReleaseYear [{4}], TrackNumber [{5}]",
|
||||
resultInfo.FullName, artistSortName, releaseTitle, releaseDate.ToString("s"),
|
||||
releaseDate.ToString("yyyy"), trackNumber));
|
||||
return resultInfo.FullName;
|
||||
}
|
||||
|
||||
|
@ -268,11 +384,15 @@ namespace Roadie.Library.Utility
|
|||
/// <param name="destinationFolder">Optional Root folder defaults to Library Folder from Settings</param>
|
||||
/// <param name="artistFolder">Optional ArtistFolder default is to get from MetaData artist</param>
|
||||
/// ///
|
||||
public static string TrackPath(IRoadieSettings configuration, AudioMetaData metaData,
|
||||
string destinationFolder = null, string artistFolder = null)
|
||||
public static string TrackPath(IRoadieSettings configuration, AudioMetaData metaData, string destinationFolder = null, string artistFolder = null)
|
||||
{
|
||||
var fileInfo = new FileInfo(TrackFullPath(configuration, metaData, destinationFolder, artistFolder));
|
||||
return fileInfo.Directory.Name;
|
||||
var tf = fileInfo.Directory.Parent.FullName.Replace(new DirectoryInfo(configuration.LibraryFolder).FullName, "");
|
||||
if (tf.StartsWith(Path.DirectorySeparatorChar))
|
||||
{
|
||||
tf = tf.RemoveFirst(Path.DirectorySeparatorChar.ToString());
|
||||
}
|
||||
return Path.Combine(tf, fileInfo.Directory.Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -283,8 +403,14 @@ namespace Roadie.Library.Utility
|
|||
/// <param name="track">Track</param>
|
||||
public static string TrackPath(IRoadieSettings configuration, Artist artist, Release release, Track track)
|
||||
{
|
||||
var fileInfo = new FileInfo(TrackFullPath(configuration, artist.SortNameValue, release.Title, release.ReleaseDate.Value, track.Title, track.TrackNumber));
|
||||
return fileInfo.Directory.Name;
|
||||
var fileInfo = new FileInfo(TrackFullPath(configuration, artist.Id, artist.SortNameValue, release.SortTitleValue, release.ReleaseDate.Value, track.Title, track.TrackNumber));
|
||||
var tf = fileInfo.Directory.Parent.FullName.Replace(new DirectoryInfo(configuration.LibraryFolder).FullName, "");
|
||||
if (tf.StartsWith(Path.DirectorySeparatorChar))
|
||||
{
|
||||
tf = tf.RemoveFirst(Path.DirectorySeparatorChar.ToString());
|
||||
}
|
||||
var result = Path.Combine(tf, fileInfo.Directory.Name);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -297,11 +423,17 @@ namespace Roadie.Library.Utility
|
|||
/// <param name="destinationFolder">Optional Root folder defaults to Library Folder from Settings</param>
|
||||
/// <param name="discNumber">Optional disc number defaults to 0</param>
|
||||
/// <param name="totalTrackNumber">Optional Total Tracks defaults to TrackNumber</param>
|
||||
public static string TrackPath(IRoadieSettings configuration, string artistSortName, string releaseTitle,
|
||||
public static string TrackPath(IRoadieSettings configuration, int artistId, string artistSortName, string releaseTitle,
|
||||
DateTime releaseDate, string trackTitle, short trackNumber, int? discNumber = null, int? totalTrackNumber = null)
|
||||
{
|
||||
var fileInfo = new FileInfo(TrackFullPath(configuration, artistSortName, releaseTitle, releaseDate, trackTitle, trackNumber, discNumber, totalTrackNumber));
|
||||
return fileInfo.Directory.Name;
|
||||
var fileInfo = new FileInfo(TrackFullPath(configuration, artistId, artistSortName, releaseTitle, releaseDate, trackTitle, trackNumber, discNumber, totalTrackNumber));
|
||||
var tf = fileInfo.Directory.Parent.FullName.Replace(new DirectoryInfo(configuration.LibraryFolder).FullName, "");
|
||||
if (tf.StartsWith(Path.DirectorySeparatorChar))
|
||||
{
|
||||
tf = tf.RemoveFirst(Path.DirectorySeparatorChar.ToString());
|
||||
}
|
||||
var result = Path.Combine(tf, fileInfo.Directory.Name);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -27,17 +27,25 @@ namespace Roadie.Library.Utility
|
|||
c.AddRange(Path.GetInvalidFileNameChars());
|
||||
|
||||
// Some Roadie instances run in Linux to Windows SMB clients via Samba this helps with Windows clients and invalid characters in Windows
|
||||
var badWindowsFileAndFoldercharacters = new List<char> { '\\', '/', ':', '*', '?', '\'', '<', '>', '|', '*' };
|
||||
var badWindowsFileAndFoldercharacters = new List<char> { '\\', '"', '/', ':', '*', '$', '?', '\'', '<', '>', '|', '*' };
|
||||
foreach (var badWindowsFilecharacter in badWindowsFileAndFoldercharacters)
|
||||
{
|
||||
if (!c.Contains(badWindowsFilecharacter))
|
||||
{
|
||||
c.Add(badWindowsFilecharacter);
|
||||
}
|
||||
}
|
||||
invalidFilenameChars = c.ToArray();
|
||||
|
||||
var f = new List<char>();
|
||||
f.AddRange(Path.GetInvalidPathChars());
|
||||
foreach (var badWindowsFilecharacter in badWindowsFileAndFoldercharacters)
|
||||
{
|
||||
if (!f.Contains(badWindowsFilecharacter))
|
||||
{
|
||||
f.Add(badWindowsFilecharacter);
|
||||
}
|
||||
}
|
||||
invalidFilenameChars = c.ToArray();
|
||||
invalidPathChars = f.ToArray();
|
||||
|
||||
|
@ -77,17 +85,25 @@ namespace Roadie.Library.Utility
|
|||
private static string Sanitize(string input, char[] invalidChars, char errorChar)
|
||||
{
|
||||
// null always sanitizes to null
|
||||
if (input == null) return null;
|
||||
if (input == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var result = new StringBuilder();
|
||||
foreach (var characterToTest in input)
|
||||
{
|
||||
// we binary search for the character in the invalid set. This should be lightning fast.
|
||||
if (Array.BinarySearch(invalidChars, characterToTest) >= 0)
|
||||
{
|
||||
// we found the character in the array of
|
||||
result.Append(errorChar);
|
||||
}
|
||||
else
|
||||
{
|
||||
// the character was not found in invalid, so it is valid.
|
||||
result.Append(characterToTest);
|
||||
|
||||
}
|
||||
}
|
||||
// we're done.
|
||||
return result.ToString();
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ namespace Roadie.Api.Services
|
|||
FileDirectoryProcessorService = fileDirectoryProcessorService;
|
||||
}
|
||||
|
||||
public async Task<OperationResult<bool>> DeleteArtist(ApplicationUser user, Guid artistId)
|
||||
public async Task<OperationResult<bool>> DeleteArtist(ApplicationUser user, Guid artistId, bool deleteFolder)
|
||||
{
|
||||
var sw = new Stopwatch();
|
||||
sw.Start();
|
||||
|
@ -74,7 +74,7 @@ namespace Roadie.Api.Services
|
|||
|
||||
try
|
||||
{
|
||||
var result = await ArtistService.Delete(user, artist);
|
||||
var result = await ArtistService.Delete(user, artist, deleteFolder);
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
return new OperationResult<bool>
|
||||
|
@ -1012,6 +1012,208 @@ namespace Roadie.Api.Services
|
|||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Migrate Storage from old folder structure to new folder structure.
|
||||
/// </summary>
|
||||
public async Task<OperationResult<bool>> MigrateStorage(ApplicationUser user, bool deleteEmptyFolders = true)
|
||||
{
|
||||
var sw = new Stopwatch();
|
||||
sw.Start();
|
||||
var errors = new List<Exception>();
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
var artistsMigrated = 0;
|
||||
foreach (var artist in DbContext.Artists.Where(x => x.Status == Statuses.ReadyToMigrate).ToArray())
|
||||
{
|
||||
var oldArtistPath = FolderPathHelper.ArtistPathOld(Configuration, artist.SortNameValue);
|
||||
var artistpath = FolderPathHelper.ArtistPath(Configuration, artist.Id, artist.SortNameValue);
|
||||
|
||||
if (Directory.Exists(oldArtistPath))
|
||||
{
|
||||
var artistInfoFile = new FileInfo(Path.Combine(oldArtistPath, "roadie.artist.json"));
|
||||
if (artistInfoFile.Exists)
|
||||
{
|
||||
artistInfoFile.MoveTo(Path.Combine(artistpath, "roadie.artist.json"));
|
||||
}
|
||||
}
|
||||
|
||||
var createdDirectory = false;
|
||||
var filesMoved = 0;
|
||||
var artistImages = ImageHelper.FindImageTypeInDirectory(new DirectoryInfo(oldArtistPath), ImageType.Artist);
|
||||
var artistSecondaryImages = ImageHelper.FindImageTypeInDirectory(new DirectoryInfo(oldArtistPath), ImageType.ArtistSecondary).ToList();
|
||||
if (artistImages.Any())
|
||||
{
|
||||
if (!Directory.Exists(artistpath))
|
||||
{
|
||||
Directory.CreateDirectory(artistpath);
|
||||
createdDirectory = true;
|
||||
}
|
||||
var artistToMergeIntoPrimaryImage = ImageHelper.FindImageTypeInDirectory(new DirectoryInfo(artistpath), ImageType.Artist).FirstOrDefault();
|
||||
if (artistToMergeIntoPrimaryImage != null)
|
||||
{
|
||||
artistSecondaryImages.Add(artistImages.First());
|
||||
}
|
||||
else
|
||||
{
|
||||
var artistImageFilename = Path.Combine(artistpath, ImageHelper.ArtistImageFilename);
|
||||
artistImages.First().MoveTo(artistImageFilename, true);
|
||||
filesMoved++;
|
||||
}
|
||||
}
|
||||
if (artistSecondaryImages.Any())
|
||||
{
|
||||
if (!Directory.Exists(artistpath))
|
||||
{
|
||||
Directory.CreateDirectory(artistpath);
|
||||
createdDirectory = true;
|
||||
}
|
||||
var looper = 0;
|
||||
foreach (var artistSecondaryImage in artistSecondaryImages)
|
||||
{
|
||||
var artistImageFilename = Path.Combine(artistpath, string.Format(ImageHelper.ArtistSecondaryImageFilename, looper.ToString("00")));
|
||||
while (File.Exists(artistImageFilename))
|
||||
{
|
||||
looper++;
|
||||
artistImageFilename = Path.Combine(artistpath, string.Format(ImageHelper.ArtistSecondaryImageFilename, looper.ToString("00")));
|
||||
}
|
||||
artistSecondaryImage.MoveTo(artistImageFilename, true);
|
||||
filesMoved++;
|
||||
}
|
||||
}
|
||||
artist.Status = Statuses.Migrated;
|
||||
artist.LastUpdated = now;
|
||||
await DbContext.SaveChangesAsync();
|
||||
Logger.LogInformation($"Migrated Artist Storage `{ artist}` From [{ oldArtistPath }] => [{ artistpath }]");
|
||||
artistsMigrated++;
|
||||
}
|
||||
Logger.LogInformation($"Artist Migration Complete. Migrated [{ artistsMigrated }] Artists.");
|
||||
|
||||
var labelsMigrated = 0;
|
||||
foreach (var label in DbContext.Labels.Where(x => x.Status == Statuses.ReadyToMigrate).ToArray())
|
||||
{
|
||||
var oldLabelImageFileName = label.OldPathToImage(Configuration);
|
||||
var labelImageFileName = label.PathToImage(Configuration);
|
||||
if(File.Exists(oldLabelImageFileName))
|
||||
{
|
||||
var labelFileInfo = new FileInfo(labelImageFileName);
|
||||
if(!labelFileInfo.Directory.Exists)
|
||||
{
|
||||
Directory.CreateDirectory(labelFileInfo.Directory.FullName);
|
||||
}
|
||||
File.Move(oldLabelImageFileName, labelImageFileName, true);
|
||||
label.Status = Statuses.Migrated;
|
||||
label.LastUpdated = now;
|
||||
await DbContext.SaveChangesAsync();
|
||||
Logger.LogInformation($"Migrated Label Storage `{ label}` From [{ oldLabelImageFileName }] => [{ labelImageFileName }]");
|
||||
labelsMigrated++;
|
||||
}
|
||||
}
|
||||
Logger.LogInformation($"Label Migration Complete. Migrated [{ labelsMigrated }] Labels.");
|
||||
|
||||
var releases = DbContext.Releases
|
||||
.Include(x => x.Artist)
|
||||
.Include(x => x.Medias)
|
||||
.Where(x => x.Status == Statuses.ReadyToMigrate)
|
||||
.ToArray();
|
||||
var releasesMigrated = 0;
|
||||
foreach (var release in releases)
|
||||
{
|
||||
var oldArtistPath = FolderPathHelper.ArtistPathOld(Configuration, release.Artist.SortNameValue);
|
||||
var oldReleasePath = FolderPathHelper.ReleasePathOld(oldArtistPath, release.SortTitleValue, release.ReleaseDate.Value);
|
||||
|
||||
var artistpath = FolderPathHelper.ArtistPath(Configuration, release.Artist.Id, release.Artist.SortNameValue);
|
||||
var releasePath = FolderPathHelper.ReleasePath(artistpath, release.SortTitleValue, release.ReleaseDate.Value);
|
||||
|
||||
if (!Directory.Exists(artistpath))
|
||||
{
|
||||
Directory.CreateDirectory(artistpath);
|
||||
}
|
||||
if (!Directory.Exists(releasePath))
|
||||
{
|
||||
Directory.CreateDirectory(releasePath);
|
||||
}
|
||||
var releaseTracks = (from r in DbContext.Releases
|
||||
join rm in DbContext.ReleaseMedias on r.Id equals rm.ReleaseId
|
||||
join t in DbContext.Tracks on rm.Id equals t.ReleaseMediaId
|
||||
where r.Id == release.Id
|
||||
where t.FileName != null
|
||||
select t).ToArray();
|
||||
|
||||
foreach(var releaseTrack in releaseTracks)
|
||||
{
|
||||
var oldTrackFileName = Path.Combine(oldReleasePath, releaseTrack.FileName);
|
||||
var newTrackFileName = Path.Combine(releasePath, releaseTrack.FileName.ToFileNameFriendly());
|
||||
if(File.Exists(oldTrackFileName))
|
||||
{
|
||||
File.Move(oldTrackFileName, newTrackFileName, true);
|
||||
releaseTrack.FilePath = FolderPathHelper.TrackPath(Configuration, release.Artist, release, releaseTrack);
|
||||
releaseTrack.FileName = releaseTrack.FileName.ToFileNameFriendly();
|
||||
releaseTrack.LastUpdated = now;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogWarning($"Migration: Track `{ releaseTrack }` Track File [{ oldTrackFileName }] Not Found");
|
||||
}
|
||||
}
|
||||
var releaseInfoFile = new FileInfo(Path.Combine(oldReleasePath, "roadie.albuminfo.json"));
|
||||
if(releaseInfoFile.Exists)
|
||||
{
|
||||
releaseInfoFile.MoveTo(Path.Combine(releasePath, "roadie.releaseinfo.json"));
|
||||
}
|
||||
var releaseToMergeImages = ImageHelper.FindImageTypeInDirectory(new DirectoryInfo(oldReleasePath), ImageType.Release);
|
||||
var releaseToMergeSecondaryImages = ImageHelper.FindImageTypeInDirectory(new DirectoryInfo(oldReleasePath), ImageType.ReleaseSecondary).ToList();
|
||||
if (releaseToMergeImages.Any())
|
||||
{
|
||||
var releaseToMergeIntoPrimaryImage = ImageHelper.FindImageTypeInDirectory(new DirectoryInfo(releasePath), ImageType.Release).FirstOrDefault();
|
||||
if (releaseToMergeIntoPrimaryImage != null)
|
||||
{
|
||||
releaseToMergeSecondaryImages.Add(releaseToMergeImages.First());
|
||||
}
|
||||
else
|
||||
{
|
||||
var releaseImageFilename = Path.Combine(releasePath, ImageHelper.ReleaseCoverFilename);
|
||||
releaseToMergeImages.First().MoveTo(releaseImageFilename, true);
|
||||
}
|
||||
}
|
||||
if (releaseToMergeSecondaryImages.Any())
|
||||
{
|
||||
var looper = 0;
|
||||
foreach (var releaseSecondaryImage in releaseToMergeSecondaryImages)
|
||||
{
|
||||
var releaseImageFilename = Path.Combine(releasePath, string.Format(ImageHelper.ReleaseSecondaryImageFilename, looper.ToString("00")));
|
||||
while (File.Exists(releaseImageFilename))
|
||||
{
|
||||
looper++;
|
||||
releaseImageFilename = Path.Combine(releasePath, string.Format(ImageHelper.ReleaseSecondaryImageFilename, looper.ToString("00")));
|
||||
}
|
||||
releaseSecondaryImage.MoveTo(releaseImageFilename, true);
|
||||
}
|
||||
}
|
||||
|
||||
release.Status = Statuses.Migrated;
|
||||
release.LastUpdated = now;
|
||||
await DbContext.SaveChangesAsync();
|
||||
Logger.LogInformation($"Migrated Release `{ release}` From [{ oldReleasePath }] => [{ releasePath }]");
|
||||
releasesMigrated++;
|
||||
}
|
||||
Logger.LogInformation($"Release Migration Complete. Migrated [{ releasesMigrated }] Releases.");
|
||||
|
||||
if (deleteEmptyFolders)
|
||||
{
|
||||
Logger.LogInformation($"Deleting Empty Folders in Library [{ Configuration.LibraryFolder }] Folder.");
|
||||
Services.FileDirectoryProcessorService.DeleteEmptyFolders(new DirectoryInfo(Configuration.LibraryFolder), Logger);
|
||||
}
|
||||
CacheManager.Clear();
|
||||
|
||||
return new OperationResult<bool>
|
||||
{
|
||||
IsSuccess = !errors.Any(),
|
||||
Data = true,
|
||||
OperationTime = sw.ElapsedMilliseconds,
|
||||
Errors = errors
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Migrate images from Images table and Thumbnails to file storage.
|
||||
/// </summary>
|
||||
|
@ -1111,7 +1313,7 @@ namespace Roadie.Api.Services
|
|||
}
|
||||
await DbContext.SaveChangesAsync();
|
||||
|
||||
foreach (var release in DbContext.Releases.Include(x => x.Artist).Where(x => x.Thumbnail != null).OrderBy(x => x.Title))
|
||||
foreach (var release in DbContext.Releases.Include(x => x.Artist).Where(x => x.Thumbnail != null).OrderBy(x => x.SortTitle ?? x.Title))
|
||||
{
|
||||
var artistFolder = release.Artist.ArtistFileFolder(Configuration);
|
||||
if (!Directory.Exists(artistFolder))
|
||||
|
|
|
@ -147,7 +147,7 @@ namespace Roadie.Api.Services
|
|||
};
|
||||
}
|
||||
|
||||
public async Task<OperationResult<bool>> Delete(ApplicationUser user, data.Artist artist)
|
||||
public async Task<OperationResult<bool>> Delete(ApplicationUser user, data.Artist artist, bool deleteFolder)
|
||||
{
|
||||
var isSuccess = false;
|
||||
try
|
||||
|
@ -156,7 +156,11 @@ namespace Roadie.Api.Services
|
|||
{
|
||||
DbContext.Artists.Remove(artist);
|
||||
await DbContext.SaveChangesAsync();
|
||||
// TODO delete artist folder if empty?
|
||||
if(deleteFolder)
|
||||
{
|
||||
var artistDir = new DirectoryInfo(artist.ArtistFileFolder(Configuration));
|
||||
FolderPathHelper.DeleteEmptyFolders(artistDir.Parent);
|
||||
}
|
||||
CacheManager.ClearRegion(artist.CacheRegion);
|
||||
Logger.LogWarning("User `{0}` deleted Artist `{1}]`", user, artist);
|
||||
isSuccess = true;
|
||||
|
@ -1410,7 +1414,7 @@ namespace Roadie.Api.Services
|
|||
}
|
||||
await DbContext.SaveChangesAsync();
|
||||
|
||||
await Delete(user, artistToMerge);
|
||||
await Delete(user, artistToMerge, true);
|
||||
|
||||
result = true;
|
||||
|
||||
|
|
|
@ -97,7 +97,7 @@ namespace Roadie.Api.Services
|
|||
MakeArtistThumbnailImage(Configuration, HttpContext, release.Artist.RoadieId),
|
||||
MakeReleaseThumbnailImage(Configuration, HttpContext, release.RoadieId));
|
||||
row.Thumbnail = MakeReleaseThumbnailImage(Configuration, HttpContext, release.RoadieId);
|
||||
row.SortName = release.Title;
|
||||
row.SortName = release.SortTitleValue;
|
||||
break;
|
||||
|
||||
case BookmarkType.Track:
|
||||
|
|
|
@ -9,7 +9,7 @@ namespace Roadie.Api.Services
|
|||
{
|
||||
public interface IAdminService
|
||||
{
|
||||
Task<OperationResult<bool>> DeleteArtist(ApplicationUser user, Guid artistId);
|
||||
Task<OperationResult<bool>> DeleteArtist(ApplicationUser user, Guid artistId, bool deleteFolder);
|
||||
|
||||
Task<OperationResult<bool>> DeleteArtistReleases(ApplicationUser user, Guid artistId, bool doDeleteFiles = false);
|
||||
|
||||
|
@ -54,5 +54,7 @@ namespace Roadie.Api.Services
|
|||
Task<OperationResult<bool>> ValidateInviteToken(Guid? tokenId);
|
||||
|
||||
Task<OperationResult<bool>> MigrateImages(ApplicationUser user);
|
||||
|
||||
Task<OperationResult<bool>> MigrateStorage(ApplicationUser user, bool deleteEmptyFolders);
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@ namespace Roadie.Api.Services
|
|||
{
|
||||
Task<OperationResult<Artist>> ById(User user, Guid id, IEnumerable<string> includes);
|
||||
|
||||
Task<OperationResult<bool>> Delete(ApplicationUser user, Library.Data.Artist Artist);
|
||||
Task<OperationResult<bool>> Delete(ApplicationUser user, Library.Data.Artist Artist, bool deleteFolder);
|
||||
|
||||
Task<PagedResult<ArtistList>> List(User user, PagedRequest request, bool? doRandomize = false, bool? onlyIncludeWithReleases = true);
|
||||
|
||||
|
|
|
@ -225,7 +225,7 @@ namespace Roadie.Api.Services
|
|||
// Update the track path to have the new album title. This is needed because future scans might not work properly without updating track title.
|
||||
foreach (var track in DbContext.Tracks.Where(x => x.ReleaseMediaId == releaseMedia.Id).ToArray())
|
||||
{
|
||||
track.FilePath = Path.Combine(releaseDirectoryInfo.Parent.Name, releaseDirectoryInfo.Name);
|
||||
track.FilePath = FolderPathHelper.TrackPath(Configuration, release.Artist, release, track);
|
||||
var trackPath = track.PathToTrack(Configuration);
|
||||
var trackFileInfo = new FileInfo(trackPath);
|
||||
if (trackFileInfo.Exists)
|
||||
|
@ -344,8 +344,12 @@ namespace Roadie.Api.Services
|
|||
var now = DateTime.UtcNow;
|
||||
if (doUpdateArtistCounts) await UpdateArtistCounts(release.Artist.Id, now);
|
||||
if (releaseLabelIds != null && releaseLabelIds.Any())
|
||||
{
|
||||
foreach (var releaseLabelId in releaseLabelIds)
|
||||
{
|
||||
await UpdateLabelCounts(releaseLabelId, now);
|
||||
}
|
||||
}
|
||||
sw.Stop();
|
||||
Logger.LogWarning("User `{0}` deleted Release `{1}]`", user, release);
|
||||
return new OperationResult<bool>
|
||||
|
@ -1109,7 +1113,7 @@ namespace Roadie.Api.Services
|
|||
zipBytes = zipStream.ToArray();
|
||||
}
|
||||
|
||||
zipFileName = $"{release.Artist.Name}_{release.Title}.zip".ToFileNameFriendly();
|
||||
zipFileName = $"{release.Artist.Name}_{release.SortTitleValue}.zip".ToFileNameFriendly();
|
||||
Logger.LogTrace(
|
||||
$"User `{roadieUser}` downloaded Release `{release}` ZipFileName [{zipFileName}], Zip Size [{zipBytes?.Length}]");
|
||||
}
|
||||
|
@ -1255,7 +1259,7 @@ namespace Roadie.Api.Services
|
|||
string partTitles = null;
|
||||
var audioMetaData = await AudioMetaDataHelper.GetInfo(file, doJustInfo);
|
||||
// This is the path for the new track not in the database but the found MP3 file to be added to library
|
||||
var trackPath = Path.Combine(releaseDirectory.Parent.Name, releaseDirectory.Name);
|
||||
var trackPath = FolderPathHelper.TrackPath(Configuration, release.Artist.Id, release.Artist.SortNameValue, release.SortTitleValue, release.ReleaseDate.Value, audioMetaData.Title, audioMetaData.TrackNumber.Value);
|
||||
|
||||
if (audioMetaData.IsValid)
|
||||
{
|
||||
|
@ -1550,6 +1554,7 @@ namespace Roadie.Api.Services
|
|||
release.IsVirtual = model.IsVirtual;
|
||||
release.Status = SafeParser.ToEnum<Statuses>(model.Status);
|
||||
release.Title = model.Title;
|
||||
release.SortTitle = model.SortTitle;
|
||||
var specialReleaseTitle = model.Title.ToAlphanumericName();
|
||||
var alt = new List<string>(model.AlternateNamesList);
|
||||
if (!model.AlternateNamesList.Contains(specialReleaseTitle, StringComparer.OrdinalIgnoreCase))
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<PackageReference Include="Hashids.net" Version="1.3.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="3.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="5.6.0" />
|
||||
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.0.19" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -40,9 +40,9 @@ namespace Roadie.Api.Controllers
|
|||
|
||||
[HttpPost("delete/artist/{id}")]
|
||||
[ProducesResponseType(200)]
|
||||
public async Task<IActionResult> DeleteArtist(Guid id)
|
||||
public async Task<IActionResult> DeleteArtist(Guid id, bool? doDeleteFile)
|
||||
{
|
||||
var result = await AdminService.DeleteArtist(await UserManager.GetUserAsync(User), id);
|
||||
var result = await AdminService.DeleteArtist(await UserManager.GetUserAsync(User), id, doDeleteFile ?? true);
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
if (result.Messages?.Any() ?? false)
|
||||
|
@ -354,5 +354,21 @@ namespace Roadie.Api.Controllers
|
|||
return Ok(result);
|
||||
}
|
||||
|
||||
|
||||
[HttpPost("migratestorage")]
|
||||
[ProducesResponseType(200)]
|
||||
public async Task<IActionResult> MigrateStorage(bool? deleteEmptyFolders)
|
||||
{
|
||||
var result = await AdminService.MigrateStorage(await UserManager.GetUserAsync(User), deleteEmptyFolders ?? true);
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
if (result.Messages?.Any() ?? false)
|
||||
{
|
||||
return StatusCode((int)HttpStatusCode.BadRequest, result.Messages);
|
||||
}
|
||||
return StatusCode((int)HttpStatusCode.InternalServerError);
|
||||
}
|
||||
return Ok(result);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -32,7 +32,7 @@
|
|||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.3.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.0.0" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="3.1.0" />
|
||||
<PackageReference Include="Serilog.Enrichers.Environment" Version="2.1.3" />
|
||||
<PackageReference Include="Serilog.Enrichers.Thread" Version="3.1.0" />
|
||||
|
|
|
@ -438,7 +438,7 @@ namespace Roadie.Dlna.Services
|
|||
{
|
||||
return (from r in DbContext.Releases
|
||||
where r.ArtistId == artistId
|
||||
orderby r.ReleaseYear, r.Title
|
||||
orderby r.ReleaseYear, r.SortTitleValue
|
||||
select r).ToArray();
|
||||
}, "urn:DlnaServiceRegion");
|
||||
}
|
||||
|
@ -481,7 +481,7 @@ namespace Roadie.Dlna.Services
|
|||
join cr in DbContext.CollectionReleases on c.Id equals cr.CollectionId
|
||||
join r in DbContext.Releases on cr.ReleaseId equals r.Id
|
||||
where c.Id == collectionId
|
||||
orderby cr.ListNumber, r.Title
|
||||
orderby cr.ListNumber, r.SortTitleValue
|
||||
select r).ToArray();
|
||||
}, "urn:DlnaServiceRegion");
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scripts", "Scripts", "{1BA7
|
|||
Upgrade0004.sql = Upgrade0004.sql
|
||||
Upgrade0005.sql = Upgrade0005.sql
|
||||
Upgrade0006.sql = Upgrade0006.sql
|
||||
Upgrade0007.sql = Upgrade0007.sql
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Roadie.Api.Services", "Roadie.Api.Services\Roadie.Api.Services.csproj", "{7B37031E-F2AE-4BE2-9F6F-005CA7A6FDF1}"
|
||||
|
|
2
Upgrade0007.sql
Normal file
2
Upgrade0007.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
-- New Release SortTitle for file friendly names
|
||||
ALTER TABLE `release` ADD sortTitle varchar(250) NULL;
|
|
@ -534,6 +534,7 @@ CREATE TABLE `release` (
|
|||
`playedCount` int(11) DEFAULT NULL,
|
||||
`duration` int(11) DEFAULT NULL,
|
||||
`rank` decimal(9,2) DEFAULT NULL,
|
||||
`sortTitle` varchar(250) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `idx_releaseArtistAndTitle` (`artistId`,`title`),
|
||||
KEY `ix_release_roadieId` (`roadieId`),
|
||||
|
|
Loading…
Reference in a new issue