Removed Image table definition, resolves #34

This commit is contained in:
Steven Hildreth 2019-11-20 16:49:49 -06:00
parent 82544c143b
commit 818afd2eb0
25 changed files with 380 additions and 567 deletions

View file

@ -22,7 +22,7 @@
<ItemGroup>
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="2.4.4" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.0.1" />
</ItemGroup>
<ItemGroup>

View file

@ -23,7 +23,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.0.1" />
<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" />

View file

@ -6,6 +6,7 @@ namespace Roadie.Library.Configuration
{
public enum DbContexts : short
{
MySQL = 1
MySQL = 1,
File = 2
}
}

View file

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Roadie.Library.Configuration
{
public enum FileDatabaseFormat
{
JSON,
BSON,
XML,
CSV
}
}

View file

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Roadie.Library.Configuration
{
/// <summary>
/// Options specific when using a FileDatabase DbContext.
/// </summary>
public sealed class FileDatabaseOptions : IFileDatabaseOptions
{
public FileDatabaseFormat DatabaseFormat { get; set; }
public string DatabaseName { get; set; }
public string DatabaseFolder { get; set; }
public FileDatabaseOptions()
{
DatabaseFormat = FileDatabaseFormat.BSON;
DatabaseName = "roadie";
DatabaseFolder = @"M:\db";
}
}
}

View file

@ -0,0 +1,8 @@
namespace Roadie.Library.Configuration
{
public interface IFileDatabaseOptions
{
string DatabaseFolder { get; set; }
FileDatabaseFormat DatabaseFormat { get; set; }
}
}

View file

@ -11,6 +11,7 @@ namespace Roadie.Library.Configuration
string ConnectionString { get; set; }
string ContentPath { get; set; }
Converting Converting { get; set; }
FileDatabaseOptions FileDatabaseOptions { get; set; }
short DefaultRowsPerPage { get; set; }
string DefaultTimeZone { get; set; }
Dlna Dlna { get; set; }

View file

@ -37,6 +37,8 @@ namespace Roadie.Library.Configuration
public Converting Converting { get; set; }
public FileDatabaseOptions FileDatabaseOptions { get; set; }
public short DefaultRowsPerPage { get; set; }
public string DefaultTimeZone { get; set; }
@ -175,6 +177,7 @@ namespace Roadie.Library.Configuration
Inspector = new Inspector();
Converting = new Converting();
FileDatabaseOptions = new FileDatabaseOptions();
Integrations = new Integrations();
Processing = new Processing();
Dlna = new Dlna();

View file

@ -1,4 +1,5 @@
using Microsoft.EntityFrameworkCore;
using FileContextCore;
using Microsoft.EntityFrameworkCore;
using Pomelo.EntityFrameworkCore.MySql.Infrastructure;
using Roadie.Library.Configuration;
using Roadie.Library.Data.Context.Implementation;
@ -12,6 +13,13 @@ namespace Roadie.Library.Data.Context
{
switch (configuration.DbContextToUse)
{
case DbContexts.File:
var fileOptionsBuilder = new DbContextOptionsBuilder<MySQLRoadieDbContext>();
fileOptionsBuilder.UseFileContextDatabase(configuration.FileDatabaseOptions.DatabaseFormat.ToString().ToLower(),
databaseName: configuration.FileDatabaseOptions.DatabaseName,
location: configuration.FileDatabaseOptions.DatabaseFolder);
return new FileRoadieDbContext(fileOptionsBuilder.Options);
default:
var mysqlOptionsBuilder = new DbContextOptionsBuilder<MySQLRoadieDbContext>();
mysqlOptionsBuilder.UseMySql(configuration.ConnectionString, mySqlOptions =>

View file

@ -26,7 +26,6 @@ namespace Roadie.Library.Data.Context
DbSet<Comment> Comments { get; set; }
DatabaseFacade Database { get; }
DbSet<Genre> Genres { get; set; }
DbSet<Image> Images { get; set; }
DbSet<Label> Labels { get; set; }
DbSet<Playlist> Playlists { get; set; }
DbSet<PlaylistTrack> PlaylistTracks { get; set; }

View file

@ -0,0 +1,16 @@
using Microsoft.EntityFrameworkCore;
namespace Roadie.Library.Data.Context.Implementation
{
/// <summary>
/// File based Context using FileContextCore
/// <seealso cref="https://github.com/morrisjdev/FileContextCore"/>
/// </summary>
public sealed class FileRoadieDbContext : LinqDbContextBase
{
public FileRoadieDbContext(DbContextOptions options)
: base(options)
{
}
}
}

View file

@ -0,0 +1,210 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Roadie.Library.Data.Context.Implementation
{
/// <summary>
/// Base DbContext using just LINQ statements against EF
/// </summary>
public abstract class LinqDbContextBase : RoadieDbContext
{
public LinqDbContextBase(DbContextOptions options)
: base(options)
{
}
public override async Task<Artist> LastPlayedArtist(int userId)
{
return await (from ut in UserTracks
join t in Tracks on ut.TrackId equals t.Id
join rm in ReleaseMedias on t.ReleaseMediaId equals rm.Id
join r in Releases on rm.ReleaseId equals r.Id
join a in Artists on r.ArtistId equals a.Id
where ut.UserId == userId
orderby ut.LastPlayed descending
select a).FirstOrDefaultAsync();
}
public override async Task<Release> LastPlayedRelease(int userId)
{
return await (from ut in UserTracks
join t in Tracks on ut.TrackId equals t.Id
join rm in ReleaseMedias on t.ReleaseMediaId equals rm.Id
join r in Releases on rm.ReleaseId equals r.Id
where ut.UserId == userId
orderby ut.LastPlayed descending
select r).FirstOrDefaultAsync();
}
public override async Task<Track> LastPlayedTrack(int userId)
{
return await (from ut in UserTracks
join t in Tracks on ut.TrackId equals t.Id
where ut.UserId == userId
orderby ut.LastPlayed descending
select t).FirstOrDefaultAsync();
}
public override async Task<Artist> MostPlayedArtist(int userId)
{
var mostPlayedTrack = await MostPlayedTrack(userId);
if (mostPlayedTrack != null)
{
return await (from t in Tracks
join rm in ReleaseMedias on t.ReleaseMediaId equals rm.Id
join r in Releases on rm.ReleaseId equals r.Id
join a in Artists on r.ArtistId equals a.Id
where t.Id == mostPlayedTrack.Id
select a).FirstOrDefaultAsync();
}
return null;
}
public override async Task<Release> MostPlayedRelease(int userId)
{
var mostPlayedTrack = await MostPlayedTrack(userId);
if (mostPlayedTrack != null)
{
return await (from t in Tracks
join rm in ReleaseMedias on t.ReleaseMediaId equals rm.Id
join r in Releases on rm.ReleaseId equals r.Id
where t.Id == mostPlayedTrack.Id
select r).FirstOrDefaultAsync();
}
return null;
}
public override async Task<Track> MostPlayedTrack(int userId)
{
return await (from ut in UserTracks
join t in Tracks on ut.TrackId equals t.Id
where ut.UserId == userId
orderby ut.PlayedCount descending
select t).FirstOrDefaultAsync();
}
public override async Task<SortedDictionary<int, int>> RandomArtistIds(int userId, int randomLimit, bool doOnlyFavorites = false, bool doOnlyRated = false)
{
List<Artist> randomArtists = null;
if (doOnlyFavorites)
{
randomArtists = await (from ua in UserArtists
join a in Artists on ua.ArtistId equals a.Id
where ua.UserId == userId
where ua.IsFavorite == true
select a
).OrderBy(x => Guid.NewGuid())
.Take(randomLimit)
.ToListAsync();
}
else if (doOnlyRated)
{
randomArtists = await (from ua in UserArtists
join a in Artists on ua.ArtistId equals a.Id
where ua.UserId == userId
where ua.Rating > 0
select a
).OrderBy(x => Guid.NewGuid())
.Take(randomLimit)
.ToListAsync();
}
else
{
randomArtists = await Artists.OrderBy(x => Guid.NewGuid())
.Take(randomLimit)
.ToListAsync();
}
var dict = randomArtists.Select((x, i) => new { key = i, value = x.Id }).Take(randomLimit).ToDictionary(x => x.key, x => x.value);
return new SortedDictionary<int, int>(dict);
}
public override async Task<SortedDictionary<int, int>> RandomGenreIds(int userId, int randomLimit, bool doOnlyFavorites = false, bool doOnlyRated = false)
{
var randomGenres = await Genres.OrderBy(x => Guid.NewGuid())
.Take(randomLimit)
.ToListAsync();
var dict = randomGenres.Select((x, i) => new { key = i, value = x.Id }).Take(randomLimit).ToDictionary(x => x.key, x => x.value);
return new SortedDictionary<int, int>(dict);
}
public override async Task<SortedDictionary<int, int>> RandomLabelIds(int userId, int randomLimit, bool doOnlyFavorites = false, bool doOnlyRated = false)
{
var randomLabels = await Labels.OrderBy(x => Guid.NewGuid()).Take(randomLimit).ToListAsync();
var dict = randomLabels.Select((x, i) => new { key = i, value = x.Id }).Take(randomLimit).ToDictionary(x => x.key, x => x.value);
return new SortedDictionary<int, int>(dict);
}
public override async Task<SortedDictionary<int, int>> RandomReleaseIds(int userId, int randomLimit, bool doOnlyFavorites = false, bool doOnlyRated = false)
{
List<Release> randomReleases = null;
if (doOnlyFavorites)
{
randomReleases = await (from ur in UserReleases
join r in Releases on ur.ReleaseId equals r.Id
where ur.UserId == userId
where ur.IsFavorite == true
select r
).OrderBy(x => Guid.NewGuid())
.Take(randomLimit)
.ToListAsync();
}
else if (doOnlyRated)
{
randomReleases = await (from ur in UserReleases
join r in Releases on ur.ReleaseId equals r.Id
where ur.UserId == userId
where ur.Rating > 0
select r
).OrderBy(x => Guid.NewGuid())
.Take(randomLimit)
.ToListAsync();
}
else
{
randomReleases = await Releases.OrderBy(x => Guid.NewGuid())
.Take(randomLimit)
.ToListAsync();
}
var dict = randomReleases.Select((x, i) => new { key = i, value = x.Id }).Take(randomLimit).ToDictionary(x => x.key, x => x.value);
return new SortedDictionary<int, int>(dict);
}
public override async Task<SortedDictionary<int, int>> RandomTrackIds(int userId, int randomLimit, bool doOnlyFavorites = false, bool doOnlyRated = false)
{
List<Track> randomTracks = null;
if (doOnlyFavorites)
{
randomTracks = await (from ut in UserTracks
join t in Tracks on ut.TrackId equals t.Id
where ut.UserId == userId
where ut.IsFavorite == true
select t
).OrderBy(x => Guid.NewGuid())
.Take(randomLimit)
.ToListAsync();
}
else if (doOnlyRated)
{
randomTracks = await (from ut in UserTracks
join t in Tracks on ut.TrackId equals t.Id
where ut.UserId == userId
where ut.Rating > 0
select t
).OrderBy(x => Guid.NewGuid())
.Take(randomLimit)
.ToListAsync();
}
else
{
randomTracks = await Tracks.OrderBy(x => Guid.NewGuid())
.Take(randomLimit)
.ToListAsync();
}
var dict = randomTracks.Select((x, i) => new { key = i, value = x.Id }).Take(randomLimit).ToDictionary(x => x.key, x => x.value);
return new SortedDictionary<int, int>(dict);
}
}
}

View file

@ -1,5 +1,4 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@ -7,7 +6,7 @@ using System.Threading.Tasks;
namespace Roadie.Library.Data.Context.Implementation
{
/// <summary>
/// MySQL/MariaDB implementation of DbContext
/// MySQL/MariaDB implementation of DbContext using SQL statements for better performance.
/// </summary>
public sealed class MySQLRoadieDbContext : RoadieDbContext
{
@ -37,7 +36,7 @@ namespace Roadie.Library.Data.Context.Implementation
join `track` t on (ut.trackId = t.id)
join `releasemedia` rm on (t.releaseMediaId = rm.id)
join `release` r on (rm.releaseId = r.id)
WHERE ut.userId = {0}
WHERE ut.userId = {0}
ORDER by ut.lastPlayed desc
LIMIT 1";
return await Releases.FromSqlRaw(sql, userId)

View file

@ -3,7 +3,6 @@ using Microsoft.EntityFrameworkCore.ChangeTracking;
using Roadie.Library.Enums;
using Roadie.Library.Identity;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@ -35,8 +34,6 @@ namespace Roadie.Library.Data.Context
public DbSet<Genre> Genres { get; set; }
public DbSet<Image> Images { get; set; }
public DbSet<Label> Labels { get; set; }
public DbSet<Playlist> Playlists { get; set; }
@ -72,7 +69,7 @@ namespace Roadie.Library.Data.Context
public DbSet<UserTrack> UserTracks { get; set; }
public DbSet<InviteToken> InviteTokens { get; set; }
public RoadieDbContext(DbContextOptions options)
public RoadieDbContext(DbContextOptions options)
: base(options)
{
}
@ -218,10 +215,15 @@ namespace Roadie.Library.Data.Context
}
Task<EntityEntry> IRoadieDbContext.AddAsync(object entity, CancellationToken cancellationToken) => throw new NotImplementedException();
Task<EntityEntry<TEntity>> IRoadieDbContext.AddAsync<TEntity>(TEntity entity, CancellationToken cancellationToken) => throw new NotImplementedException();
Task<TEntity> IRoadieDbContext.FindAsync<TEntity>(params object[] keyValues) => throw new NotImplementedException();
Task<object> IRoadieDbContext.FindAsync(Type entityType, object[] keyValues, CancellationToken cancellationToken) => throw new NotImplementedException();
Task<TEntity> IRoadieDbContext.FindAsync<TEntity>(object[] keyValues, CancellationToken cancellationToken) => throw new NotImplementedException();
Task<object> IRoadieDbContext.FindAsync(Type entityType, params object[] keyValues) => throw new NotImplementedException();
}
}

View file

@ -1,26 +0,0 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Roadie.Library.Data
{
[Obsolete("Only here for transition. Will be removed in future release. Use Library.Imaging.Image")]
[Table("image")]
public partial class Image : EntityBase
{
public Artist Artist { get; set; }
[Column("artistId")] public int? ArtistId { get; set; }
[Column("image", TypeName = "mediumblob")]
public byte[] Bytes { get; set; }
[Column("caption")] [MaxLength(100)] public string Caption { get; set; }
public Release Release { get; set; }
[Column("releaseId")] public int? ReleaseId { get; set; }
[Column("signature")] [MaxLength(50)] public string Signature { get; set; }
[Column("url")] [MaxLength(500)] public string Url { get; set; }
}
}

View file

@ -20,11 +20,12 @@
<PackageReference Include="LiteDB" Version="4.1.4" />
<PackageReference Include="Mapster" Version="4.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.0.1" />
<PackageReference Include="Microsoft.Extensions.Caching.Redis" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.0.1" />
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="1.3.0" />
<PackageReference Include="Microsoft.Net.Http.Headers" Version="2.2.0" />
<PackageReference Include="Microsoft.Net.Http.Headers" Version="2.2.8" />
<PackageReference Include="Microsoft.PowerShell.SDK" Version="6.2.3" />
<PackageReference Include="MimeMapping" Version="1.0.1.17" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
@ -35,7 +36,7 @@
<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.Drawing.Common" Version="4.6.1" />
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="4.6.0" />
<PackageReference Include="System.Runtime.Caching" Version="4.6.0" />
<PackageReference Include="z440.atl.core" Version="2.13.0" />
@ -43,6 +44,9 @@
</ItemGroup>
<ItemGroup>
<Reference Include="FileContextCore">
<HintPath>..\libraries\FileContextCore.dll</HintPath>
</Reference>
<Reference Include="IdSharp.AudioInfo-core">
<HintPath>..\libraries\IdSharp.AudioInfo-core.dll</HintPath>
</Reference>

View file

@ -1011,449 +1011,6 @@ 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 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)
.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>
public async Task<OperationResult<bool>> MigrateImages(ApplicationUser user)
{
var sw = new Stopwatch();
sw.Start();
var errors = new List<Exception>();
var now = DateTime.UtcNow;
foreach (var artist in DbContext.Artists.Where(x => x.Thumbnail != null).OrderBy(x => x.SortName ?? x.Name))
{
var artistFolder = artist.ArtistFileFolder(Configuration);
if (!Directory.Exists(artistFolder))
{
Directory.CreateDirectory(artistFolder);
}
var artistImage = Path.Combine(artistFolder, ImageHelper.ArtistImageFilename);
if (!File.Exists(artistImage))
{
File.WriteAllBytes(artistImage, ImageHelper.ConvertToJpegFormat(artist.Thumbnail));
}
artist.Thumbnail = null;
artist.LastUpdated = now;
Logger.LogInformation($"Saved Artist Image `{artist}` path [{ artistImage }]");
}
await DbContext.SaveChangesAsync();
var artistImages = (from i in DbContext.Images
join a in DbContext.Artists on i.ArtistId equals a.Id
select new { i, a });
foreach (var artistImage in artistImages)
{
var looper = 0;
var artistFolder = artistImage.a.ArtistFileFolder(Configuration);
var artistImageFilename = Path.Combine(artistFolder, string.Format(ImageHelper.ArtistSecondaryImageFilename, looper.ToString("00")));
while (File.Exists(artistImageFilename))
{
looper++;
artistImageFilename = Path.Combine(artistFolder, string.Format(ImageHelper.ArtistSecondaryImageFilename, looper.ToString("00")));
}
File.WriteAllBytes(artistImageFilename, ImageHelper.ConvertToJpegFormat(artistImage.i.Bytes));
DbContext.Images.Remove(artistImage.i);
Logger.LogInformation($"Saved Artist Secondary Image `{artistImage.a}` path [{ artistImageFilename }]");
}
await DbContext.SaveChangesAsync();
foreach (var collection in DbContext.Collections.Where(x => x.Thumbnail != null).OrderBy(x => x.SortName ?? x.Name))
{
var image = collection.PathToImage(Configuration);
if (!File.Exists(image))
{
File.WriteAllBytes(image, ImageHelper.ConvertToJpegFormat(collection.Thumbnail));
}
collection.Thumbnail = null;
collection.LastUpdated = now;
Logger.LogInformation($"Saved Collection Image `{collection}` path [{ image }]");
}
await DbContext.SaveChangesAsync();
foreach (var genre in DbContext.Genres.Where(x => x.Thumbnail != null).OrderBy(x => x.Name))
{
var image = genre.PathToImage(Configuration);
if (!File.Exists(image))
{
File.WriteAllBytes(image, ImageHelper.ConvertToJpegFormat(genre.Thumbnail));
}
genre.Thumbnail = null;
genre.LastUpdated = now;
Logger.LogInformation($"Saved Genre Image `{genre}` path [{ image }]");
}
await DbContext.SaveChangesAsync();
foreach (var label in DbContext.Labels.Where(x => x.Thumbnail != null).OrderBy(x => x.SortName ?? x.Name))
{
var image = label.PathToImage(Configuration);
if (!File.Exists(image))
{
File.WriteAllBytes(image, ImageHelper.ConvertToJpegFormat(label.Thumbnail));
}
label.Thumbnail = null;
label.LastUpdated = now;
Logger.LogInformation($"Saved Label Image `{label}` path [{ image }]");
}
await DbContext.SaveChangesAsync();
foreach (var playlist in DbContext.Playlists.Where(x => x.Thumbnail != null).OrderBy(x => x.Name))
{
var image = playlist.PathToImage(Configuration);
if (!File.Exists(image))
{
File.WriteAllBytes(image, ImageHelper.ConvertToJpegFormat(playlist.Thumbnail));
}
playlist.Thumbnail = null;
playlist.LastUpdated = now;
Logger.LogInformation($"Saved Playlist Image `{playlist}` path [{ image }]");
}
await DbContext.SaveChangesAsync();
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))
{
Directory.CreateDirectory(artistFolder);
}
var releaseFolder = release.ReleaseFileFolder(artistFolder);
try
{
if (!Directory.Exists(releaseFolder))
{
Directory.CreateDirectory(releaseFolder);
}
var releaseImage = Path.Combine(releaseFolder, "cover.jpg");
if (!File.Exists(releaseImage))
{
File.WriteAllBytes(releaseImage, ImageHelper.ConvertToJpegFormat(release.Thumbnail));
}
release.Thumbnail = null;
release.LastUpdated = now;
Logger.LogInformation($"Saved Release Image `{release}` path [{ releaseImage }]");
}
catch (Exception ex)
{
Logger.LogError(ex, $"Error saving Release Image `{release}` folder [{ releaseFolder }]");
}
}
await DbContext.SaveChangesAsync();
var releaseImages = (from i in DbContext.Images
join r in DbContext.Releases.Include(x => x.Artist) on i.ReleaseId equals r.Id
select new { i, r });
foreach (var releaseImage in releaseImages)
{
var looper = 0;
var artistFolder = releaseImage.r.Artist.ArtistFileFolder(Configuration);
if (!Directory.Exists(artistFolder))
{
Directory.CreateDirectory(artistFolder);
}
var releaseFolder = releaseImage.r.ReleaseFileFolder(artistFolder);
if (!Directory.Exists(releaseFolder))
{
Directory.CreateDirectory(releaseFolder);
}
var releaseImageFilename = Path.Combine(releaseFolder, string.Format(ImageHelper.ReleaseSecondaryImageFilename, looper.ToString("00")));
try
{
while (File.Exists(releaseImageFilename))
{
looper++;
releaseImageFilename = Path.Combine(releaseFolder, string.Format(ImageHelper.ReleaseSecondaryImageFilename, looper.ToString("00")));
}
File.WriteAllBytes(releaseImageFilename, ImageHelper.ConvertToJpegFormat(releaseImage.i.Bytes));
DbContext.Images.Remove(releaseImage.i);
Logger.LogInformation($"Saved Release Secondary Image `{releaseImage.r}` path [{ releaseImageFilename }]");
}
catch (Exception ex)
{
Logger.LogError(ex, $"Error saving Release Secondary Image [{releaseImageFilename}] folder [{ releaseFolder }]");
}
}
await DbContext.SaveChangesAsync();
foreach (var track in DbContext.Tracks.Include(x => x.ReleaseMedia)
.Include(x => x.ReleaseMedia.Release)
.Include(x => x.ReleaseMedia.Release.Artist)
.Where(x => x.Thumbnail != null).OrderBy(x => x.Title))
{
var artistFolder = track.ReleaseMedia.Release.Artist.ArtistFileFolder(Configuration);
if (!Directory.Exists(artistFolder))
{
Directory.CreateDirectory(artistFolder);
}
var releaseFolder = track.ReleaseMedia.Release.ReleaseFileFolder(artistFolder);
if (!Directory.Exists(releaseFolder))
{
Directory.CreateDirectory(releaseFolder);
}
var trackImage = track.PathToTrackThumbnail(Configuration);
try
{
if (!File.Exists(trackImage))
{
File.WriteAllBytes(trackImage, ImageHelper.ConvertToJpegFormat(track.Thumbnail));
}
track.Thumbnail = null;
track.LastUpdated = now;
Logger.LogInformation($"Saved Track Image `{track}` path [{ trackImage }]");
}
catch (Exception ex)
{
Logger.LogError(ex, $"Error saving Track Image [{trackImage}] folder [{ releaseFolder }]");
}
}
await DbContext.SaveChangesAsync();
foreach (var usr in DbContext.Users.Where(x => x.Avatar != null).OrderBy(x => x.UserName))
{
var image = usr.PathToImage(Configuration);
if (!File.Exists(image))
{
File.WriteAllBytes(image, ImageHelper.ConvertToJpegFormat(usr.Avatar));
}
usr.Avatar = null;
usr.LastUpdated = now;
Logger.LogInformation($"Saved User Image `{user}` path [{ image }]");
}
await DbContext.SaveChangesAsync();
return new OperationResult<bool>
{
IsSuccess = !errors.Any(),
Data = true,
OperationTime = sw.ElapsedMilliseconds,
Errors = errors
};
}
public async Task<OperationResult<bool>> ValidateInviteToken(Guid? tokenId)
{
var sw = new Stopwatch();

View file

@ -52,9 +52,5 @@ namespace Roadie.Api.Services
Task<OperationResult<bool>> UpdateInviteTokenUsed(Guid? tokenId);
Task<OperationResult<bool>> ValidateInviteToken(Guid? tokenId);
Task<OperationResult<bool>> MigrateImages(ApplicationUser user);
Task<OperationResult<bool>> MigrateStorage(ApplicationUser user, bool deleteEmptyFolders);
}
}

View file

@ -319,7 +319,7 @@ namespace Roadie.Api.Services
if (labelImage != null)
{
// Save unaltered label image
File.WriteAllBytes(label.PathToImage(Configuration), ImageHelper.ConvertToJpegFormat(labelImage));
File.WriteAllBytes(label.PathToImage(Configuration, true), ImageHelper.ConvertToJpegFormat(labelImage));
}
label.LastUpdated = now;
await DbContext.SaveChangesAsync();

View file

@ -209,21 +209,23 @@ namespace Roadie.Api.Services
year = SafeParser.ToNumber<int>(x.Key),
count = x.Count()
});
var decadeInterval = 10;
var startingDecade = (decadeInfos.Min(x => x.year) / 10) * 10;
var endingDecade = (decadeInfos.Max(x => x.year) / 10) * 10;
for (int decade = startingDecade; decade <= endingDecade; decade += decadeInterval)
if (decadeInfos != null && decadeInfos.Any())
{
var endOfDecade = decade + 9;
var count = decadeInfos.Where(x => x.year >= decade && x.year <= endOfDecade).Sum(x => x.count);
if (count > 0)
var decadeInterval = 10;
var startingDecade = (decadeInfos.Min(x => x.year) / 10) * 10;
var endingDecade = (decadeInfos.Max(x => x.year) / 10) * 10;
for (int decade = startingDecade; decade <= endingDecade; decade += decadeInterval)
{
result.Add(new DateAndCount
var endOfDecade = decade + 9;
var count = decadeInfos.Where(x => x.year >= decade && x.year <= endOfDecade).Sum(x => x.count);
if (count > 0)
{
Date = decade.ToString(),
Count = count
});
result.Add(new DateAndCount
{
Date = decade.ToString(),
Count = count
});
}
}
}
sw.Stop();

View file

@ -338,37 +338,5 @@ namespace Roadie.Api.Controllers
return Ok(result);
}
[HttpPost("migrateimages")]
[ProducesResponseType(200)]
public async Task<IActionResult> MigrateImages()
{
var result = await AdminService.MigrateImages(await UserManager.GetUserAsync(User));
if (!result.IsSuccess)
{
if (result.Messages?.Any() ?? false)
{
return StatusCode((int)HttpStatusCode.BadRequest, result.Messages);
}
return StatusCode((int)HttpStatusCode.InternalServerError);
}
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);
}
}
}

View file

@ -30,7 +30,8 @@
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="3.3.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.3.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.3.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.0.1" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="Serilog.AspNetCore" Version="3.2.0" />
@ -56,4 +57,10 @@
<ProjectReference Include="..\Roadie.Dlna.Services\Roadie.Dlna.Services.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="FileContextCore">
<HintPath>..\libraries\FileContextCore.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

View file

@ -1,5 +1,6 @@
#region Usings
using FileContextCore;
using Mapster;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
@ -57,14 +58,6 @@ namespace Roadie.Api
public Startup(IConfiguration configuration)
{
_configuration = configuration;
TypeAdapterConfig<Library.Data.Image, Library.Models.Image>
.NewConfig()
.Map(i => i.ArtistId,
src => src.Artist == null ? null : (Guid?)src.Artist.RoadieId)
.Map(i => i.ReleaseId,
src => src.Release == null ? null : (Guid?)src.Release.RoadieId)
.Compile();
TypeAdapterConfig.GlobalSettings.Default.PreserveReference(true);
}
@ -105,6 +98,10 @@ namespace Roadie.Api
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
var settings = new RoadieSettings();
_configuration.GetSection("RoadieSettings").Bind(settings);
services.AddSingleton<ITokenService, TokenService>();
services.AddSingleton<IHttpEncoder, HttpEncoder>();
services.AddSingleton<IEmailSender, EmailSenderService>();
@ -115,29 +112,47 @@ namespace Roadie.Api
return new MemoryCacheManager(logger, new CachePolicy(TimeSpan.FromHours(4)));
});
services.AddDbContextPool<ApplicationUserDbContext>(
options => options.UseMySql(_configuration.GetConnectionString("RoadieDatabaseConnection"),
mySqlOptions =>
{
mySqlOptions.ServerVersion(new Version(5, 5), ServerType.MariaDb);
mySqlOptions.EnableRetryOnFailure(
10,
TimeSpan.FromSeconds(30),
null);
}
));
services.AddDbContextPool<IRoadieDbContext, MySQLRoadieDbContext>(
options => options.UseMySql(_configuration.GetConnectionString("RoadieDatabaseConnection"),
mySqlOptions =>
{
mySqlOptions.ServerVersion(new Version(5, 5), ServerType.MariaDb);
mySqlOptions.EnableRetryOnFailure(
10,
TimeSpan.FromSeconds(30),
null);
}
));
switch (settings.DbContextToUse)
{
case DbContexts.MySQL:
services.AddDbContextPool<ApplicationUserDbContext>(
options => options.UseMySql(_configuration.GetConnectionString("RoadieDatabaseConnection"),
mySqlOptions =>
{
mySqlOptions.ServerVersion(new Version(5, 5), ServerType.MariaDb);
mySqlOptions.EnableRetryOnFailure(
10,
TimeSpan.FromSeconds(30),
null);
}
));
services.AddDbContextPool<IRoadieDbContext, MySQLRoadieDbContext>(
options => options.UseMySql(_configuration.GetConnectionString("RoadieDatabaseConnection"),
mySqlOptions =>
{
mySqlOptions.ServerVersion(new Version(5, 5), ServerType.MariaDb);
mySqlOptions.EnableRetryOnFailure(
10,
TimeSpan.FromSeconds(30),
null);
}
));
break;
case DbContexts.File:
services.AddDbContext<ApplicationUserDbContext>(
options => options.UseFileContextDatabase(settings.FileDatabaseOptions.DatabaseFormat.ToString().ToLower(),
databaseName: settings.FileDatabaseOptions.DatabaseName,
location: settings.FileDatabaseOptions.DatabaseFolder)
);
services.AddDbContext<IRoadieDbContext, FileRoadieDbContext>(
options => options.UseFileContextDatabase(settings.FileDatabaseOptions.DatabaseFormat.ToString().ToLower(),
databaseName: settings.FileDatabaseOptions.DatabaseName,
location: settings.FileDatabaseOptions.DatabaseFolder)
);
break;
default:
throw new NotImplementedException("Unknown DbContext Type");
}
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddRoles<ApplicationRole>()

View file

@ -59,8 +59,12 @@
},
"CORSOrigins": "http://localhost:4200|http://localhost:8080|https://localhost:8080|http://localhost:80|https://localhost:80|http://192.168.1.177:8080",
"RoadieSettings": {
"DbContextToUse": "File",
"FileDatabaseOptions": {
"DatabaseFolder": "C:\\\\roadie_dev_root\\\\db"
},
"InboundFolder": "C:\\roadie_dev_root\\inbound",
"LibraryFolder": "C:\\\\roadie_dev_root\\\\library",
"LibraryFolder": "C:\\\\roadie_dev_root\\\\library",
"Dlna": {
"IsEnabled": false
},

Binary file not shown.