diff --git a/Roadie.Api.Library.Tests/FolderPathHelperTests.cs b/Roadie.Api.Library.Tests/FolderPathHelperTests.cs
index 3f48e7c..7eb5042 100644
--- a/Roadie.Api.Library.Tests/FolderPathHelperTests.cs
+++ b/Roadie.Api.Library.Tests/FolderPathHelperTests.cs
@@ -105,6 +105,22 @@ namespace Roadie.Library.Tests
Assert.Equal(t.FullName, artistFolder);
}
+ [Theory]
+ [InlineData("!SÖmëthing el$e", @"S\SO")]
+ [InlineData("Alternative Singer/Songwriter", @"A\AL")]
+ [InlineData("Adult Alternative", @"A\AD")]
+ [InlineData("Progressive Bluegrass", @"P\PR")]
+ [InlineData("Western European Traditions", @"W\WE")]
+ [InlineData("2-Step/British Garage", @"0\2S")]
+ [InlineData("80's/synthwave/outrun/new retro wave", @"0\80")]
+ [InlineData("Western Swing Revival", @"W\WE")]
+ public void GenerateGenreFolderNames(string input, string shouldBe)
+ {
+ var artistFolder = FolderPathHelper.GenrePath(Configuration, input);
+ var t = new DirectoryInfo(Path.Combine(Configuration.GenreImageFolder, 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")]
diff --git a/Roadie.Api.Library/Data/CollectionPartial.cs b/Roadie.Api.Library/Data/CollectionPartial.cs
index dca475f..55d762c 100644
--- a/Roadie.Api.Library/Data/CollectionPartial.cs
+++ b/Roadie.Api.Library/Data/CollectionPartial.cs
@@ -44,9 +44,14 @@ namespace Roadie.Library.Data
///
/// Returns a full file path to the Collection Image
///
- public string PathToImage(IRoadieSettings configuration)
+ public string PathToImage(IRoadieSettings configuration, bool makeFolderIfNotExist = false)
{
- return Path.Combine(configuration.CollectionImageFolder, $"{ (SortName ?? Name).ToFileNameFriendly() } [{ Id }].jpg");
+ var folder = configuration.CollectionImageFolder;
+ if (!Directory.Exists(folder) && makeFolderIfNotExist)
+ {
+ Directory.CreateDirectory(folder);
+ }
+ return Path.Combine(folder, $"{ (SortName ?? Name).ToFileNameFriendly() } [{ Id }].jpg");
}
public int PositionColumn
diff --git a/Roadie.Api.Library/Data/Genre.cs b/Roadie.Api.Library/Data/Genre.cs
index e277819..6ecd9ec 100644
--- a/Roadie.Api.Library/Data/Genre.cs
+++ b/Roadie.Api.Library/Data/Genre.cs
@@ -12,7 +12,16 @@ namespace Roadie.Library.Data
public ICollection Comments { get; set; }
- [Column("name")] [MaxLength(100)] public string Name { get; set; }
+ [Column("name")]
+ [MaxLength(100)]
+ [Required]
+ public string Name { get; set; }
+
+ [Column("sortName")]
+ [MaxLength(100)]
+ public string SortName { get; set; }
+
+ public string SortNameValue => string.IsNullOrEmpty(SortName) ? Name : SortName;
[Column("description")]
[MaxLength(4000)]
diff --git a/Roadie.Api.Library/Data/GenrePartial.cs b/Roadie.Api.Library/Data/GenrePartial.cs
index d871acc..a4188f1 100644
--- a/Roadie.Api.Library/Data/GenrePartial.cs
+++ b/Roadie.Api.Library/Data/GenrePartial.cs
@@ -1,5 +1,6 @@
using Roadie.Library.Configuration;
using Roadie.Library.Extensions;
+using Roadie.Library.Utility;
using System;
using System.IO;
@@ -11,14 +12,37 @@ namespace Roadie.Library.Data
public string CacheRegion => CacheRegionUrn(RoadieId);
+ /////
+ ///// Returns a full file path to the Genre Image
+ /////
+ //public string PathToImage(IRoadieSettings configuration)
+ //{
+ // return Path.Combine(configuration.GenreImageFolder, $"{ Name.ToFileNameFriendly() } [{ Id }].jpg");
+ //}
+
///
/// Returns a full file path to the Genre Image
///
- public string PathToImage(IRoadieSettings configuration)
+ public string PathToImage(IRoadieSettings configuration, bool makeFolderIfNotExist = false)
{
- return Path.Combine(configuration.GenreImageFolder, $"{ Name.ToFileNameFriendly() } [{ Id }].jpg");
+ var folder = FolderPathHelper.GenrePath(configuration, SortNameValue);
+ if(!Directory.Exists(folder) && makeFolderIfNotExist)
+ {
+ Directory.CreateDirectory(folder);
+ }
+ return Path.Combine(folder, $"{ SortNameValue.ToFileNameFriendly() } [{ Id }].jpg");
}
+ ///
+ /// Returns a full file path to the Label Image
+ ///
+ [Obsolete("This is only here for migration will be removed in future release.")]
+ public string OldPathToImage(IRoadieSettings configuration)
+ {
+ return Path.Combine(configuration.GenreImageFolder, $"{ SortNameValue.ToFileNameFriendly() } [{ Id }].jpg");
+ }
+
+
public static string CacheRegionUrn(Guid Id)
{
return string.Format("urn:genre:{0}", Id);
diff --git a/Roadie.Api.Library/Data/LabelPartial.cs b/Roadie.Api.Library/Data/LabelPartial.cs
index 5bcf372..252ff4f 100644
--- a/Roadie.Api.Library/Data/LabelPartial.cs
+++ b/Roadie.Api.Library/Data/LabelPartial.cs
@@ -28,9 +28,14 @@ namespace Roadie.Library.Data
///
/// Returns a full file path to the Label Image
///
- public string PathToImage(IRoadieSettings configuration)
+ public string PathToImage(IRoadieSettings configuration, bool makeFolderIfNotExist = false)
{
- return Path.Combine(FolderPathHelper.LabelPath(configuration, SortNameValue), $"{ SortNameValue.ToFileNameFriendly() } [{ Id }].jpg");
+ var folder = FolderPathHelper.LabelPath(configuration, SortNameValue);
+ if (!Directory.Exists(folder) && makeFolderIfNotExist)
+ {
+ Directory.CreateDirectory(folder);
+ }
+ return Path.Combine(folder, $"{ SortNameValue.ToFileNameFriendly() } [{ Id }].jpg");
}
///
diff --git a/Roadie.Api.Library/Data/PlaylistPartial.cs b/Roadie.Api.Library/Data/PlaylistPartial.cs
index 6631ede..7c79135 100644
--- a/Roadie.Api.Library/Data/PlaylistPartial.cs
+++ b/Roadie.Api.Library/Data/PlaylistPartial.cs
@@ -25,9 +25,14 @@ namespace Roadie.Library.Data
///
/// Returns a full file path to the Playlist Image
///
- public string PathToImage(IRoadieSettings configuration)
+ public string PathToImage(IRoadieSettings configuration, bool makeFolderIfNotExist = false)
{
- return Path.Combine(configuration.PlaylistImageFolder, $"{ (SortName ?? Name).ToFileNameFriendly() } [{ Id }].jpg");
+ var folder = configuration.PlaylistImageFolder;
+ if (!Directory.Exists(folder) && makeFolderIfNotExist)
+ {
+ Directory.CreateDirectory(folder);
+ }
+ return Path.Combine(folder, $"{ (SortName ?? Name).ToFileNameFriendly() } [{ Id }].jpg");
}
public override string ToString()
diff --git a/Roadie.Api.Library/Identity/ApplicationUserPartial.cs b/Roadie.Api.Library/Identity/ApplicationUserPartial.cs
index a644ec2..e86780b 100644
--- a/Roadie.Api.Library/Identity/ApplicationUserPartial.cs
+++ b/Roadie.Api.Library/Identity/ApplicationUserPartial.cs
@@ -20,9 +20,14 @@ namespace Roadie.Library.Identity
///
/// Returns a full file path to the User Image
///
- public string PathToImage(IRoadieSettings configuration)
+ public string PathToImage(IRoadieSettings configuration, bool makeFolderIfNotExist = false)
{
- return Path.Combine(configuration.UserImageFolder, $"{ UserName.ToFileNameFriendly() } [{ Id }].gif");
+ var folder = configuration.UserImageFolder;
+ if (!Directory.Exists(folder) && makeFolderIfNotExist)
+ {
+ Directory.CreateDirectory(folder);
+ }
+ return Path.Combine(folder, $"{ UserName.ToFileNameFriendly() } [{ Id }].gif");
}
public ApplicationUser()
diff --git a/Roadie.Api.Library/Utility/FolderPathHelper.cs b/Roadie.Api.Library/Utility/FolderPathHelper.cs
index 5431df9..6ab3078 100644
--- a/Roadie.Api.Library/Utility/FolderPathHelper.cs
+++ b/Roadie.Api.Library/Utility/FolderPathHelper.cs
@@ -19,7 +19,8 @@ namespace Roadie.Library.Utility
public static int MaximumLibraryFolderNameLength = 44;
public static int MaximumArtistFolderNameLength = 100;
public static int MaximumReleaseFolderNameLength = 100;
- public static int MaximumLabelFolderNameLength = 100;
+ public static int MaximumLabelFolderNameLength = 80;
+ public static int MaximumGenreFolderNameLength = 80;
public static int MaximumTrackFileNameLength = 500;
public static IEnumerable FolderSpaceReplacements = new List { ".", "~", "_", "=", "-" };
@@ -78,11 +79,10 @@ namespace Roadie.Library.Utility
return directoryInfo.FullName;
}
-
public static string LabelPath(IRoadieSettings configuration, string labelSortName)
{
SimpleContract.Requires(!string.IsNullOrEmpty(labelSortName), "Invalid Label Sort Name");
- SimpleContract.Requires(configuration.LibraryFolder.Length < MaximumLibraryFolderNameLength, $"Library Folder maximum length is [{ MaximumLibraryFolderNameLength }]");
+ SimpleContract.Requires(configuration.LabelImageFolder.Length < MaximumLabelFolderNameLength, $"Label Image Folder maximum length is [{ MaximumLibraryFolderNameLength }]");
var lsn = new StringBuilder(labelSortName);
foreach (var stringReplacement in FolderSpaceReplacements)
@@ -122,6 +122,50 @@ namespace Roadie.Library.Utility
return directoryInfo.FullName;
}
+ public static string GenrePath(IRoadieSettings configuration, string genreSortName)
+ {
+ SimpleContract.Requires(!string.IsNullOrEmpty(genreSortName), "Invalid Genre Sort Name");
+ SimpleContract.Requires(configuration.GenreImageFolder.Length < MaximumGenreFolderNameLength, $"Genre Image Folder maximum length is [{ MaximumLibraryFolderNameLength }]");
+
+ var lsn = new StringBuilder(genreSortName);
+ foreach (var stringReplacement in FolderSpaceReplacements)
+ {
+ if (!lsn.Equals(stringReplacement))
+ {
+ lsn.Replace(stringReplacement, " ");
+ }
+ }
+ var genreFolder = lsn.ToString().ToAlphanumericName(false, false).ToFolderNameFriendly().ToTitleCase(false);
+ if (string.IsNullOrEmpty(genreFolder))
+ {
+ throw new Exception($"GenreFolder [{ genreFolder }] is invalid. GenreSortName [{ genreSortName }].");
+ }
+ var lfUpper = genreFolder.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.GenreImageFolder, 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)
{
diff --git a/Roadie.Api.Services/AdminService.cs b/Roadie.Api.Services/AdminService.cs
index 84b77b8..d571fac 100644
--- a/Roadie.Api.Services/AdminService.cs
+++ b/Roadie.Api.Services/AdminService.cs
@@ -1110,6 +1110,28 @@ namespace Roadie.Api.Services
}
Logger.LogInformation($"Label Migration Complete. Migrated [{ labelsMigrated }] Labels.");
+ var genresMigrated = 0;
+ foreach (var genre in DbContext.Genres.Where(x => x.Status == Statuses.ReadyToMigrate).ToArray())
+ {
+ var oldGenreImageFileName = genre.OldPathToImage(Configuration);
+ var genreImageFileName = genre.PathToImage(Configuration);
+ if (File.Exists(oldGenreImageFileName))
+ {
+ var genreFileInfo = new FileInfo(genreImageFileName);
+ if (!genreFileInfo.Directory.Exists)
+ {
+ Directory.CreateDirectory(genreFileInfo.Directory.FullName);
+ }
+ File.Move(oldGenreImageFileName, genreImageFileName, true);
+ genre.Status = Statuses.Migrated;
+ genre.LastUpdated = now;
+ await DbContext.SaveChangesAsync();
+ Logger.LogInformation($"Migrated Genre Storage `{ genre}` From [{ oldGenreImageFileName }] => [{ genreImageFileName }]");
+ genresMigrated++;
+ }
+ }
+ Logger.LogInformation($"Genre Migration Complete. Migrated [{ genresMigrated }] Genres.");
+
var releases = DbContext.Releases
.Include(x => x.Artist)
.Include(x => x.Medias)
diff --git a/Roadie.Api.Services/CollectionService.cs b/Roadie.Api.Services/CollectionService.cs
index 0dc198d..01d18f6 100644
--- a/Roadie.Api.Services/CollectionService.cs
+++ b/Roadie.Api.Services/CollectionService.cs
@@ -301,7 +301,7 @@ namespace Roadie.Api.Services
if (collectionImage != null)
{
// Save unaltered collection image
- File.WriteAllBytes(collection.PathToImage(Configuration), ImageHelper.ConvertToJpegFormat(collectionImage));
+ File.WriteAllBytes(collection.PathToImage(Configuration, true), ImageHelper.ConvertToJpegFormat(collectionImage));
}
if (model.Maintainer?.Value != null)
{
diff --git a/Roadie.Api.Services/GenreService.cs b/Roadie.Api.Services/GenreService.cs
index bc86b23..3a712ac 100644
--- a/Roadie.Api.Services/GenreService.cs
+++ b/Roadie.Api.Services/GenreService.cs
@@ -236,14 +236,17 @@ namespace Roadie.Api.Services
sw.Start();
var errors = new List();
var genre = DbContext.Genres.FirstOrDefault(x => x.RoadieId == id);
- if (genre == null) return new OperationResult(true, string.Format("Genre Not Found [{0}]", id));
+ if (genre == null)
+ {
+ return new OperationResult(true, string.Format("Genre Not Found [{0}]", id));
+ }
try
{
var now = DateTime.UtcNow;
if (imageBytes != null)
{
- // Save unaltered label image
- File.WriteAllBytes(genre.PathToImage(Configuration), ImageHelper.ConvertToJpegFormat(imageBytes));
+ // Save unaltered genre image
+ File.WriteAllBytes(genre.PathToImage(Configuration, true), ImageHelper.ConvertToJpegFormat(imageBytes));
}
genre.LastUpdated = now;
await DbContext.SaveChangesAsync();
diff --git a/Roadie.Api.Services/LabelService.cs b/Roadie.Api.Services/LabelService.cs
index 0173bd2..489bbe1 100644
--- a/Roadie.Api.Services/LabelService.cs
+++ b/Roadie.Api.Services/LabelService.cs
@@ -461,7 +461,7 @@ namespace Roadie.Api.Services
if (imageBytes != null)
{
// Save unaltered label image
- File.WriteAllBytes(label.PathToImage(Configuration), ImageHelper.ConvertToJpegFormat(imageBytes));
+ File.WriteAllBytes(label.PathToImage(Configuration, true), ImageHelper.ConvertToJpegFormat(imageBytes));
}
label.LastUpdated = now;
await DbContext.SaveChangesAsync();
diff --git a/Roadie.Api.Services/PlaylistService.cs b/Roadie.Api.Services/PlaylistService.cs
index e801752..9ecc3f3 100644
--- a/Roadie.Api.Services/PlaylistService.cs
+++ b/Roadie.Api.Services/PlaylistService.cs
@@ -352,7 +352,7 @@ namespace Roadie.Api.Services
if (playlistImage != null)
{
// Save unaltered playlist image
- File.WriteAllBytes(playlist.PathToImage(Configuration), ImageHelper.ConvertToJpegFormat(playlistImage));
+ File.WriteAllBytes(playlist.PathToImage(Configuration, true), ImageHelper.ConvertToJpegFormat(playlistImage));
}
playlist.LastUpdated = now;
await DbContext.SaveChangesAsync();
diff --git a/Roadie.Api.Services/UserService.cs b/Roadie.Api.Services/UserService.cs
index bdfa99b..5836c3e 100644
--- a/Roadie.Api.Services/UserService.cs
+++ b/Roadie.Api.Services/UserService.cs
@@ -503,7 +503,7 @@ namespace Roadie.Api.Services
imageData = ImageHelper.ConvertToGifFormat(imageData);
// Save unaltered user image
- File.WriteAllBytes(user.PathToImage(Configuration), imageData);
+ File.WriteAllBytes(user.PathToImage(Configuration, true), imageData);
}
}
diff --git a/Roadie.sln b/Roadie.sln
index 04ed91b..6a14477 100644
--- a/Roadie.sln
+++ b/Roadie.sln
@@ -19,6 +19,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scripts", "Scripts", "{1BA7
Upgrade0005.sql = Upgrade0005.sql
Upgrade0006.sql = Upgrade0006.sql
Upgrade0007.sql = Upgrade0007.sql
+ Upgrade0008.sql = Upgrade0008.sql
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Roadie.Api.Services", "Roadie.Api.Services\Roadie.Api.Services.csproj", "{7B37031E-F2AE-4BE2-9F6F-005CA7A6FDF1}"
diff --git a/Upgrade0008.sql b/Upgrade0008.sql
new file mode 100644
index 0000000..ff3a5b5
--- /dev/null
+++ b/Upgrade0008.sql
@@ -0,0 +1,2 @@
+-- New SortName columns
+ALTER TABLE `genre` ADD sortName varchar(100) NULL;
diff --git a/roadie.sql b/roadie.sql
index 4025008..e8b9d38 100644
--- a/roadie.sql
+++ b/roadie.sql
@@ -337,6 +337,7 @@ CREATE TABLE `genre` (
`createdDate` datetime DEFAULT NULL,
`lastUpdated` datetime DEFAULT NULL,
`name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `sortName` varchar(250) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`normalizedName` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`thumbnail` blob DEFAULT NULL,
`alternateNames` mediumtext COLLATE utf8mb4_unicode_ci DEFAULT NULL,
@@ -511,6 +512,7 @@ CREATE TABLE `release` (
`lastUpdated` datetime DEFAULT NULL,
`isVirtual` tinyint(1) DEFAULT NULL,
`title` varchar(250) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `sortTitle` varchar(250) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`alternateNames` mediumtext COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`releaseDate` date DEFAULT NULL,
`rating` smallint(6) NOT NULL,
@@ -534,7 +536,6 @@ 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`),