< Summary - Jellyfin

Information
Class: Jellyfin.Server.Implementations.Item.BaseItemMapper
Assembly: Jellyfin.Server.Implementations
File(s): /srv/git/jellyfin/Jellyfin.Server.Implementations/Item/BaseItemMapper.cs
Line coverage
88%
Covered lines: 256
Uncovered lines: 34
Coverable lines: 290
Total lines: 540
Line coverage: 88.2%
Branch coverage
60%
Covered branches: 106
Total branches: 174
Branch coverage: 60.9%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 5/16/2026 - 12:15:55 AM Line coverage: 71.7% (183/255) Branch coverage: 54.7% (92/168) Total lines: 4915/20/2026 - 12:15:44 AM Line coverage: 71.7% (183/255) Branch coverage: 48.2% (81/168) Total lines: 4917/6/2026 - 12:16:28 AM Line coverage: 71.7% (183/255) Branch coverage: 50.5% (85/168) Total lines: 4918/2/2026 - 12:17:28 AM Line coverage: 74.9% (191/255) Branch coverage: 50.6% (84/166) Total lines: 4918/3/2026 - 12:16:46 AM Line coverage: 75.9% (202/266) Branch coverage: 51.1% (86/168) Total lines: 5068/9/2026 - 12:16:58 AM Line coverage: 81.5% (217/266) Branch coverage: 54.1% (91/168) Total lines: 50610/29/2026 - 12:17:11 AM Line coverage: 88.2% (256/290) Branch coverage: 62.6% (109/174) Total lines: 54010/31/2026 - 12:17:46 AM Line coverage: 88.2% (256/290) Branch coverage: 60.9% (106/174) Total lines: 54011/1/2026 - 12:15:27 AM Line coverage: 88.2% (256/290) Branch coverage: 62.6% (109/174) Total lines: 54011/5/2026 - 12:17:36 AM Line coverage: 88.2% (256/290) Branch coverage: 60.9% (106/174) Total lines: 540 5/16/2026 - 12:15:55 AM Line coverage: 71.7% (183/255) Branch coverage: 54.7% (92/168) Total lines: 4915/20/2026 - 12:15:44 AM Line coverage: 71.7% (183/255) Branch coverage: 48.2% (81/168) Total lines: 4917/6/2026 - 12:16:28 AM Line coverage: 71.7% (183/255) Branch coverage: 50.5% (85/168) Total lines: 4918/2/2026 - 12:17:28 AM Line coverage: 74.9% (191/255) Branch coverage: 50.6% (84/166) Total lines: 4918/3/2026 - 12:16:46 AM Line coverage: 75.9% (202/266) Branch coverage: 51.1% (86/168) Total lines: 5068/9/2026 - 12:16:58 AM Line coverage: 81.5% (217/266) Branch coverage: 54.1% (91/168) Total lines: 50610/29/2026 - 12:17:11 AM Line coverage: 88.2% (256/290) Branch coverage: 62.6% (109/174) Total lines: 54010/31/2026 - 12:17:46 AM Line coverage: 88.2% (256/290) Branch coverage: 60.9% (106/174) Total lines: 54011/1/2026 - 12:15:27 AM Line coverage: 88.2% (256/290) Branch coverage: 62.6% (109/174) Total lines: 54011/5/2026 - 12:17:36 AM Line coverage: 88.2% (256/290) Branch coverage: 60.9% (106/174) Total lines: 540

Coverage delta

Coverage delta 9 -9

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
DetachUserData(...)100%66100%
Map(...)54.87%918288.78%
Map(...)51.61%816283.03%
MapImageFromEntity(...)50%88100%
MapImageToEntity(...)50%22100%
GetType(...)100%11100%
TypeRequiresDeserialization(...)100%11100%
DeserializeBaseItem(...)83.33%161270.58%
GetPathToSave(...)100%22100%

File(s)

/srv/git/jellyfin/Jellyfin.Server.Implementations/Item/BaseItemMapper.cs

#LineLine coverage
 1#pragma warning disable RS0030 // Do not use banned APIs
 2
 3using System;
 4using System.Collections.Concurrent;
 5using System.Linq;
 6using System.Reflection;
 7using System.Text;
 8using System.Text.Json;
 9using Jellyfin.Database.Implementations.Entities;
 10using Jellyfin.Extensions;
 11using Jellyfin.Extensions.Json;
 12using MediaBrowser.Common;
 13using MediaBrowser.Controller;
 14using MediaBrowser.Controller.Entities;
 15using MediaBrowser.Controller.Entities.Audio;
 16using MediaBrowser.Controller.Entities.TV;
 17using MediaBrowser.Controller.LiveTv;
 18using MediaBrowser.Model.Entities;
 19using MediaBrowser.Model.LiveTv;
 20using Microsoft.Extensions.Logging;
 21using BaseItemDto = MediaBrowser.Controller.Entities.BaseItem;
 22using BaseItemEntity = Jellyfin.Database.Implementations.Entities.BaseItemEntity;
 23
 24namespace Jellyfin.Server.Implementations.Item;
 25
 26/// <summary>
 27/// Handles mapping between BaseItemEntity (database) and BaseItemDto (domain) objects.
 28/// </summary>
 29public static class BaseItemMapper
 30{
 31    /// <summary>
 32    /// This holds all the types in the running assemblies
 33    /// so that we can de-serialize properly when we don't have strong types.
 34    /// </summary>
 235    private static readonly ConcurrentDictionary<string, Type?> _typeMap = new ConcurrentDictionary<string, Type?>();
 36
 37    private static UserData[] DetachUserData(BaseItemEntity entity)
 38    {
 26439        if (entity.UserData is null || entity.UserData.Count == 0)
 40        {
 26141            return [];
 42        }
 43
 344        var detached = new UserData[entity.UserData.Count];
 345        var index = 0;
 1246        foreach (var userData in entity.UserData)
 47        {
 348            detached[index++] = new UserData
 349            {
 350                ItemId = userData.ItemId,
 351                Item = null,
 352                UserId = userData.UserId,
 353                User = null,
 354                CustomDataKey = userData.CustomDataKey,
 355                Rating = userData.Rating,
 356                PlaybackPositionTicks = userData.PlaybackPositionTicks,
 357                PlayCount = userData.PlayCount,
 358                IsFavorite = userData.IsFavorite,
 359                LastPlayedDate = userData.LastPlayedDate,
 360                Played = userData.Played,
 361                AudioStreamIndex = userData.AudioStreamIndex,
 362                SubtitleStreamIndex = userData.SubtitleStreamIndex,
 363                Likes = userData.Likes,
 364                RetentionDate = userData.RetentionDate
 365            };
 66        }
 67
 368        return detached;
 69    }
 70
 71    /// <summary>
 72    /// Maps a Entity to the DTO.
 73    /// </summary>
 74    /// <param name="entity">The entity.</param>
 75    /// <param name="dto">The dto base instance.</param>
 76    /// <param name="appHost">The Application server Host.</param>
 77    /// <returns>The dto to map.</returns>
 78    public static BaseItemDto Map(BaseItemEntity entity, BaseItemDto dto, IServerApplicationHost? appHost)
 79    {
 26480        dto.Id = entity.Id;
 26481        dto.ParentId = entity.ParentId.GetValueOrDefault();
 26482        dto.Path = appHost?.ExpandVirtualPath(entity.Path) ?? entity.Path;
 26483        dto.EndDate = entity.EndDate;
 26484        dto.CommunityRating = entity.CommunityRating;
 26485        dto.CustomRating = entity.CustomRating;
 26486        dto.IndexNumber = entity.IndexNumber;
 26487        dto.IsLocked = entity.IsLocked;
 26488        dto.Name = entity.Name;
 26489        dto.OfficialRating = entity.OfficialRating;
 26490        dto.Overview = entity.Overview;
 26491        dto.ParentIndexNumber = entity.ParentIndexNumber;
 26492        dto.PremiereDate = entity.PremiereDate;
 26493        dto.ProductionYear = entity.ProductionYear;
 26494        dto.SortName = entity.SortName;
 26495        dto.ForcedSortName = entity.ForcedSortName;
 26496        dto.RunTimeTicks = entity.RunTimeTicks;
 26497        dto.PreferredMetadataLanguage = entity.PreferredMetadataLanguage;
 26498        dto.PreferredMetadataCountryCode = entity.PreferredMetadataCountryCode;
 26499        dto.IsInMixedFolder = entity.IsInMixedFolder;
 264100        dto.InheritedParentalRatingValue = entity.InheritedParentalRatingValue;
 264101        dto.InheritedParentalRatingSubValue = entity.InheritedParentalRatingSubValue;
 264102        dto.CriticRating = entity.CriticRating;
 264103        dto.PresentationUniqueKey = entity.PresentationUniqueKey;
 264104        dto.OriginalTitle = entity.OriginalTitle;
 264105        dto.OriginalLanguage = entity.OriginalLanguage;
 264106        dto.Album = entity.Album;
 264107        dto.LUFS = entity.LUFS;
 264108        dto.NormalizationGain = entity.NormalizationGain;
 264109        dto.IsVirtualItem = entity.IsVirtualItem;
 264110        dto.ExternalSeriesId = entity.ExternalSeriesId;
 264111        dto.Tagline = entity.Tagline;
 264112        dto.TotalBitrate = entity.TotalBitrate;
 264113        dto.ExternalId = entity.ExternalId;
 264114        dto.Size = entity.Size;
 264115        dto.Genres = string.IsNullOrWhiteSpace(entity.Genres) ? [] : entity.Genres.Split('|');
 264116        dto.DateCreated = entity.DateCreated ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
 264117        dto.DateModified = entity.DateModified ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
 264118        dto.ChannelId = entity.ChannelId ?? Guid.Empty;
 264119        dto.DateLastRefreshed = entity.DateLastRefreshed ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
 264120        dto.DateLastSaved = entity.DateLastSaved ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
 264121        dto.OwnerId = entity.OwnerId ?? Guid.Empty;
 264122        dto.Width = entity.Width.GetValueOrDefault();
 264123        dto.Height = entity.Height.GetValueOrDefault();
 264124        dto.UserData = DetachUserData(entity);
 125
 264126        if (entity.Provider is not null)
 127        {
 248128            dto.ProviderIds = entity.Provider.ToDictionary(e => e.ProviderId, e => e.ProviderValue);
 129        }
 130
 264131        if (entity.ExtraType is not null)
 132        {
 0133            dto.ExtraType = (ExtraType)entity.ExtraType;
 134        }
 135
 264136        if (entity.LockedFields is not null)
 137        {
 248138            dto.LockedFields = entity.LockedFields?.Select(e => (MetadataField)e.Id).ToArray() ?? [];
 139        }
 140
 264141        if (entity.Audio is not null)
 142        {
 0143            dto.Audio = (ProgramAudio)entity.Audio;
 144        }
 145
 264146        dto.ProductionLocations = entity.ProductionLocations?.Split('|', StringSplitOptions.RemoveEmptyEntries) ?? [];
 264147        dto.Studios = entity.Studios?.Split('|') ?? [];
 264148        dto.Tags = string.IsNullOrWhiteSpace(entity.Tags) ? [] : entity.Tags.Split('|');
 149
 264150        if (dto is IHasProgramAttributes hasProgramAttributes)
 151        {
 0152            hasProgramAttributes.IsMovie = entity.IsMovie;
 0153            hasProgramAttributes.IsSeries = entity.IsSeries;
 0154            hasProgramAttributes.EpisodeTitle = entity.EpisodeTitle;
 0155            hasProgramAttributes.IsRepeat = entity.IsRepeat;
 156        }
 157
 264158        if (dto is LiveTvChannel liveTvChannel)
 159        {
 0160            liveTvChannel.ServiceName = entity.ExternalServiceId;
 161        }
 162
 264163        if (dto is Trailer trailer)
 164        {
 0165            trailer.TrailerTypes = entity.TrailerTypes?.Select(e => (TrailerType)e.Id).ToArray() ?? [];
 166        }
 167
 264168        if (dto is Video video)
 169        {
 31170            video.PrimaryVersionId = entity.PrimaryVersionId;
 171
 172            // The LinkedChildren table is the source of truth for version links
 31173            if (entity.LinkedChildEntities is not null)
 174            {
 30175                video.LinkedAlternateVersions = entity.LinkedChildEntities
 30176                    // LocalAlternateVersion links belong to Video.LocalAlternateVersions, not here
 30177                    .Where(e => e.ChildType == Database.Implementations.Entities.LinkedChildType.LinkedAlternateVersion)
 30178                    .OrderBy(e => e.SortOrder)
 30179                    .Select(e => new LinkedChild
 30180                    {
 30181                        ItemId = e.ChildId,
 30182                        Type = (MediaBrowser.Controller.Entities.LinkedChildType)e.ChildType
 30183                    })
 30184                    .ToArray();
 185            }
 186        }
 187
 264188        if (dto is IHasSeries hasSeriesName)
 189        {
 2190            hasSeriesName.SeriesName = entity.SeriesName;
 2191            hasSeriesName.SeriesId = entity.SeriesId.GetValueOrDefault();
 2192            hasSeriesName.SeriesPresentationUniqueKey = entity.SeriesPresentationUniqueKey;
 193        }
 194
 264195        if (dto is Episode episode)
 196        {
 2197            episode.SeasonName = entity.SeasonName;
 2198            episode.SeasonId = entity.SeasonId.GetValueOrDefault();
 199        }
 200
 264201        if (dto is IHasArtist hasArtists)
 202        {
 0203            hasArtists.Artists = entity.Artists?.Split('|', StringSplitOptions.RemoveEmptyEntries) ?? [];
 204        }
 205
 264206        if (dto is IHasAlbumArtist hasAlbumArtists)
 207        {
 0208            hasAlbumArtists.AlbumArtists = entity.AlbumArtists?.Split('|', StringSplitOptions.RemoveEmptyEntries) ?? [];
 209        }
 210
 264211        if (dto is LiveTvProgram program)
 212        {
 0213            program.ShowId = entity.ShowId;
 214        }
 215
 264216        if (entity.Images is not null)
 217        {
 257218            dto.ImageInfos = entity.Images.Select(e => MapImageFromEntity(e, appHost)).ToArray();
 219        }
 220
 264221        if (dto is IHasStartDate hasStartDate)
 222        {
 0223            hasStartDate.StartDate = entity.StartDate.GetValueOrDefault();
 224        }
 225
 226        // Fields that are present in the DB but are never actually used
 227        // dto.UnratedType = entity.UnratedType;
 228        // dto.TopParentId = entity.TopParentId;
 229        // dto.CleanName = entity.CleanName;
 230        // dto.UserDataKey = entity.UserDataKey;
 231
 264232        if (dto is Folder folder)
 233        {
 226234            folder.DateLastMediaAdded = entity.DateLastMediaAdded ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKin
 226235            if (entity.LinkedChildEntities is not null)
 236            {
 180237                folder.LinkedChildren = entity.LinkedChildEntities
 180238                    .OrderBy(e => e.SortOrder)
 180239                    .Select(e => new LinkedChild
 180240                    {
 180241                        ItemId = e.ChildId,
 180242                        Type = (MediaBrowser.Controller.Entities.LinkedChildType)e.ChildType
 180243                    })
 180244                    .ToArray();
 245            }
 246        }
 247
 264248        return dto;
 249    }
 250
 251    /// <summary>
 252    /// Maps a DTO to a database entity.
 253    /// </summary>
 254    /// <param name="dto">The DTO.</param>
 255    /// <param name="appHost">The application host for path resolution.</param>
 256    /// <returns>The database entity.</returns>
 257    public static BaseItemEntity Map(BaseItemDto dto, IServerApplicationHost appHost)
 258    {
 183259        var dtoType = dto.GetType();
 183260        var entity = new BaseItemEntity()
 183261        {
 183262            Type = dtoType.ToString(),
 183263            Id = dto.Id
 183264        };
 265
 183266        if (TypeRequiresDeserialization(dtoType))
 267        {
 151268            entity.Data = JsonSerializer.Serialize(dto, dtoType, JsonDefaults.Options);
 269        }
 270
 183271        entity.ParentId = !dto.ParentId.IsEmpty() ? dto.ParentId : null;
 183272        entity.Path = GetPathToSave(dto.Path, appHost);
 183273        entity.EndDate = dto.EndDate;
 183274        entity.CommunityRating = dto.CommunityRating;
 183275        entity.CustomRating = dto.CustomRating;
 183276        entity.IndexNumber = dto.IndexNumber;
 183277        entity.IsLocked = dto.IsLocked;
 183278        entity.Name = dto.Name;
 183279        entity.CleanName = dto.Name.GetCleanValue();
 183280        entity.OfficialRating = dto.OfficialRating;
 183281        entity.Overview = dto.Overview;
 183282        entity.ParentIndexNumber = dto.ParentIndexNumber;
 183283        entity.PremiereDate = dto.PremiereDate;
 183284        entity.ProductionYear = dto.ProductionYear;
 183285        entity.SortName = dto.SortName;
 183286        entity.ForcedSortName = dto.ForcedSortName;
 183287        entity.RunTimeTicks = dto.RunTimeTicks;
 183288        entity.PreferredMetadataLanguage = dto.PreferredMetadataLanguage;
 183289        entity.PreferredMetadataCountryCode = dto.PreferredMetadataCountryCode;
 183290        entity.IsInMixedFolder = dto.IsInMixedFolder;
 183291        entity.InheritedParentalRatingValue = dto.InheritedParentalRatingValue;
 183292        entity.InheritedParentalRatingSubValue = dto.InheritedParentalRatingSubValue;
 183293        entity.CriticRating = dto.CriticRating;
 183294        entity.PresentationUniqueKey = dto.PresentationUniqueKey;
 183295        entity.OriginalTitle = dto.OriginalTitle;
 183296        entity.OriginalLanguage = dto.OriginalLanguage;
 183297        entity.Album = dto.Album;
 183298        entity.LUFS = dto.LUFS;
 183299        entity.NormalizationGain = dto.NormalizationGain;
 183300        entity.IsVirtualItem = dto.IsVirtualItem;
 183301        entity.ExternalSeriesId = dto.ExternalSeriesId;
 183302        entity.Tagline = dto.Tagline;
 183303        entity.TotalBitrate = dto.TotalBitrate;
 183304        entity.ExternalId = dto.ExternalId;
 183305        entity.Size = dto.Size;
 183306        entity.Genres = string.Join('|', dto.Genres.Distinct(StringComparer.OrdinalIgnoreCase));
 183307        entity.DateCreated = dto.DateCreated == DateTime.MinValue ? null : dto.DateCreated;
 183308        entity.DateModified = dto.DateModified == DateTime.MinValue ? null : dto.DateModified;
 183309        entity.ChannelId = dto.ChannelId;
 183310        entity.DateLastRefreshed = dto.DateLastRefreshed == DateTime.MinValue ? null : dto.DateLastRefreshed;
 183311        entity.DateLastSaved = dto.DateLastSaved == DateTime.MinValue ? null : dto.DateLastSaved;
 183312        entity.OwnerId = dto.OwnerId == Guid.Empty ? null : dto.OwnerId;
 183313        entity.Width = dto.Width;
 183314        entity.Height = dto.Height;
 183315        entity.Provider = dto.ProviderIds.Select(e => new BaseItemProvider()
 183316        {
 183317            Item = entity,
 183318            ProviderId = e.Key,
 183319            ProviderValue = e.Value
 183320        }).ToList();
 321
 183322        if (dto.Audio.HasValue)
 323        {
 0324            entity.Audio = (ProgramAudioEntity)dto.Audio;
 325        }
 326
 183327        if (dto.ExtraType.HasValue)
 328        {
 0329            entity.ExtraType = (BaseItemExtraType)dto.ExtraType;
 330        }
 331
 183332        entity.ProductionLocations = dto.ProductionLocations is not null ? string.Join('|', dto.ProductionLocations.Wher
 183333        entity.Studios = dto.Studios is not null ? string.Join('|', dto.Studios.Distinct(StringComparer.OrdinalIgnoreCas
 183334        entity.Tags = dto.Tags is not null ? string.Join('|', dto.Tags.Distinct(StringComparer.OrdinalIgnoreCase)) : nul
 183335        entity.LockedFields = dto.LockedFields is not null ? dto.LockedFields
 183336            .Select(e => new BaseItemMetadataField()
 183337            {
 183338                Id = (int)e,
 183339                Item = entity,
 183340                ItemId = entity.Id
 183341            })
 183342            .ToArray() : null;
 343
 183344        if (dto is IHasProgramAttributes hasProgramAttributes)
 345        {
 0346            entity.IsMovie = hasProgramAttributes.IsMovie;
 0347            entity.IsSeries = hasProgramAttributes.IsSeries;
 0348            entity.EpisodeTitle = hasProgramAttributes.EpisodeTitle;
 0349            entity.IsRepeat = hasProgramAttributes.IsRepeat;
 350        }
 351
 183352        if (dto is LiveTvChannel liveTvChannel)
 353        {
 0354            entity.ExternalServiceId = liveTvChannel.ServiceName;
 355        }
 356
 183357        if (dto is Video video)
 358        {
 9359            entity.PrimaryVersionId = video.PrimaryVersionId;
 360        }
 361
 183362        if (dto is IHasSeries hasSeriesName)
 363        {
 5364            entity.SeriesName = hasSeriesName.SeriesName;
 5365            entity.SeriesId = hasSeriesName.SeriesId;
 5366            entity.SeriesPresentationUniqueKey = hasSeriesName.SeriesPresentationUniqueKey;
 367        }
 368
 183369        if (dto is Episode episode)
 370        {
 0371            entity.SeasonName = episode.SeasonName;
 0372            entity.SeasonId = episode.SeasonId;
 373        }
 374
 183375        if (dto is IHasArtist hasArtists)
 376        {
 0377            entity.Artists = hasArtists.Artists is not null ? string.Join('|', hasArtists.Artists.Distinct(StringCompare
 378        }
 379
 183380        if (dto is IHasAlbumArtist hasAlbumArtists)
 381        {
 0382            entity.AlbumArtists = hasAlbumArtists.AlbumArtists is not null ? string.Join('|', hasAlbumArtists.AlbumArtis
 383        }
 384
 183385        if (dto is LiveTvProgram program)
 386        {
 0387            entity.ShowId = program.ShowId;
 388        }
 389
 183390        if (dto.ImageInfos is not null)
 391        {
 183392            entity.Images = dto.ImageInfos.Select(f => MapImageToEntity(dto.Id, f)).ToArray();
 393        }
 394
 183395        if (dto is Trailer trailer)
 396        {
 0397            entity.TrailerTypes = trailer.TrailerTypes?.Select(e => new BaseItemTrailerType()
 0398            {
 0399                Id = (int)e,
 0400                Item = entity,
 0401                ItemId = entity.Id
 0402            }).ToArray() ?? [];
 403        }
 404
 183405        entity.MediaType = dto.MediaType.ToString();
 183406        if (dto is IHasStartDate hasStartDate)
 407        {
 0408            entity.StartDate = hasStartDate.StartDate;
 409        }
 410
 183411        entity.UnratedType = dto.GetBlockUnratedType().ToString();
 412
 413        // Fields that are present in the DB but are never actually used
 414        // dto.UserDataKey = entity.UserDataKey;
 415
 183416        if (dto is Folder folder)
 417        {
 169418            entity.DateLastMediaAdded = folder.DateLastMediaAdded == DateTime.MinValue ? null : folder.DateLastMediaAdde
 169419            entity.IsFolder = folder.IsFolder;
 420        }
 421
 183422        return entity;
 423    }
 424
 425    /// <summary>
 426    /// Maps a database image entity to a domain image info.
 427    /// </summary>
 428    /// <param name="e">The database image entity.</param>
 429    /// <param name="appHost">The application host.</param>
 430    /// <returns>The mapped image info.</returns>
 431    public static ItemImageInfo MapImageFromEntity(BaseItemImageInfo e, IServerApplicationHost? appHost)
 432    {
 4433        return new ItemImageInfo()
 4434        {
 4435            Path = appHost?.ExpandVirtualPath(e.Path) ?? e.Path,
 4436            BlurHash = e.Blurhash is null ? null : Encoding.UTF8.GetString(e.Blurhash),
 4437            DateModified = e.DateModified ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc),
 4438            Height = e.Height,
 4439            Width = e.Width,
 4440            Type = (ImageType)e.ImageType
 4441        };
 442    }
 443
 444    /// <summary>
 445    /// Maps a domain image info to a database image entity.
 446    /// </summary>
 447    /// <param name="baseItemId">The parent item ID.</param>
 448    /// <param name="e">The image info to map.</param>
 449    /// <returns>The mapped database entity.</returns>
 450    public static BaseItemImageInfo MapImageToEntity(Guid baseItemId, ItemImageInfo e)
 451    {
 8452        return new BaseItemImageInfo()
 8453        {
 8454            ItemId = baseItemId,
 8455            Id = Guid.NewGuid(),
 8456            Path = e.Path,
 8457            Blurhash = e.BlurHash is null ? null : Encoding.UTF8.GetBytes(e.BlurHash),
 8458            DateModified = e.DateModified,
 8459            Height = e.Height,
 8460            Width = e.Width,
 8461            ImageType = (ImageInfoImageType)e.Type,
 8462            Item = null!
 8463        };
 464    }
 465
 466    /// <summary>
 467    /// Gets the type from a type name string.
 468    /// </summary>
 469    /// <param name="typeName">The type name.</param>
 470    /// <returns>The resolved type, or null.</returns>
 471    public static Type? GetType(string typeName)
 472    {
 525473        ArgumentException.ThrowIfNullOrEmpty(typeName);
 474
 525475        return _typeMap.GetOrAdd(typeName, k => AppDomain.CurrentDomain.GetAssemblies()
 525476            .Select(a => a.GetType(k))
 525477            .FirstOrDefault(t => t is not null));
 478    }
 479
 480    /// <summary>
 481    /// Checks whether a type requires JSON deserialization.
 482    /// </summary>
 483    /// <param name="type">The type to check.</param>
 484    /// <returns>True if the type requires deserialization.</returns>
 485    public static bool TypeRequiresDeserialization(Type type)
 486    {
 445487        return type.GetCustomAttribute<RequiresSourceSerialisationAttribute>() == null;
 488    }
 489
 490    /// <summary>
 491    /// Deserializes a BaseItemEntity and sets all properties.
 492    /// </summary>
 493    /// <param name="baseItemEntity">The DB entity.</param>
 494    /// <param name="logger">Logger.</param>
 495    /// <param name="appHost">The application server Host.</param>
 496    /// <param name="skipDeserialization">If only mapping should be processed.</param>
 497    /// <returns>A mapped BaseItem, or null if the item type is unknown.</returns>
 498    public static BaseItemDto? DeserializeBaseItem(BaseItemEntity baseItemEntity, ILogger logger, IServerApplicationHost
 499    {
 264500        var type = GetType(baseItemEntity.Type);
 264501        if (type is null)
 502        {
 2503            logger.LogWarning(
 2504                "Skipping item {ItemId} with unknown type '{ItemType}'. This may indicate a removed plugin or database c
 2505                baseItemEntity.Id,
 2506                baseItemEntity.Type);
 2507            return null;
 508        }
 509
 262510        BaseItemDto? dto = null;
 262511        if (TypeRequiresDeserialization(type) && baseItemEntity.Data is not null && !skipDeserialization)
 512        {
 513            try
 514            {
 57515                dto = JsonSerializer.Deserialize(baseItemEntity.Data, type, JsonDefaults.Options) as BaseItemDto;
 57516            }
 0517            catch (JsonException ex)
 518            {
 0519                logger.LogError(ex, "Error deserializing item with JSON: {Data}", baseItemEntity.Data);
 0520            }
 521        }
 522
 262523        if (dto is null)
 524        {
 205525            dto = Activator.CreateInstance(type) as BaseItemDto ?? throw new InvalidOperationException("Cannot deseriali
 526        }
 527
 262528        return Map(baseItemEntity, dto, appHost);
 529    }
 530
 531    private static string? GetPathToSave(string path, IServerApplicationHost appHost)
 532    {
 183533        if (path is null)
 534        {
 15535            return null;
 536        }
 537
 168538        return appHost.ReverseVirtualPath(path);
 539    }
 540}