mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-06-27 08:50:20 -04:00
Fix modification checks and make sure to use UTC (#14347)
This commit is contained in:
parent
d5a76bdff8
commit
c6e568692e
22 changed files with 1990 additions and 67 deletions
|
@ -108,7 +108,7 @@ public class PhotoProvider : ICustomMetadataProvider<Photo>, IForcedProvider, IH
|
|||
var dateTaken = image.ImageTag.DateTime;
|
||||
if (dateTaken.HasValue)
|
||||
{
|
||||
item.DateCreated = dateTaken.Value;
|
||||
item.DateCreated = dateTaken.Value.ToUniversalTime();
|
||||
item.PremiereDate = dateTaken.Value;
|
||||
item.ProductionYear = dateTaken.Value.Year;
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
RemoteEndPoint = remoteEndPoint;
|
||||
|
||||
_jsonOptions = JsonDefaults.Options;
|
||||
LastActivityDate = DateTime.Now;
|
||||
LastActivityDate = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
|
@ -43,13 +43,11 @@ namespace Emby.Server.Implementations.Images
|
|||
protected IImageProcessor ImageProcessor { get; set; }
|
||||
|
||||
protected virtual IReadOnlyCollection<ImageType> SupportedImages { get; }
|
||||
= new ImageType[] { ImageType.Primary };
|
||||
= [ImageType.Primary];
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => "Dynamic Image Provider";
|
||||
|
||||
protected virtual int MaxImageAgeDays => 7;
|
||||
|
||||
public int Order => 0;
|
||||
|
||||
protected virtual bool Supports(BaseItem item) => true;
|
||||
|
@ -292,8 +290,14 @@ namespace Emby.Server.Implementations.Images
|
|||
|
||||
protected virtual bool HasChangedByDate(BaseItem item, ItemImageInfo image)
|
||||
{
|
||||
var age = DateTime.UtcNow - image.DateModified;
|
||||
return age.TotalDays > MaxImageAgeDays;
|
||||
var path = image.Path;
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
var modificationDate = FileSystem.GetLastWriteTimeUtc(path);
|
||||
return image.DateModified != modificationDate;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected string CreateSingleImage(IEnumerable<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType)
|
||||
|
|
|
@ -2050,13 +2050,17 @@ namespace Emby.Server.Implementations.Library
|
|||
/// <inheritdoc />
|
||||
public async Task UpdateItemsAsync(IReadOnlyList<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
|
||||
{
|
||||
_itemRepository.SaveItems(items, cancellationToken);
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
item.DateLastSaved = DateTime.UtcNow;
|
||||
await RunMetadataSavers(item, updateReason).ConfigureAwait(false);
|
||||
|
||||
// Modify again, so saved value is after write time of externally saved metadata
|
||||
item.DateLastSaved = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
_itemRepository.SaveItems(items, cancellationToken);
|
||||
|
||||
if (ItemUpdated is not null)
|
||||
{
|
||||
foreach (var item in items)
|
||||
|
@ -2097,8 +2101,6 @@ namespace Emby.Server.Implementations.Library
|
|||
await ProviderManager.SaveMetadataAsync(item, updateReason).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
item.DateLastSaved = DateTime.UtcNow;
|
||||
|
||||
await UpdateImagesAsync(item, updateReason >= ItemUpdateType.ImageUpdate).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
@ -2384,12 +2386,13 @@ namespace Emby.Server.Implementations.Library
|
|||
isNew = true;
|
||||
}
|
||||
|
||||
var refresh = isNew || DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval;
|
||||
var lastRefreshedUtc = item.DateLastRefreshed;
|
||||
var refresh = isNew || DateTime.UtcNow - lastRefreshedUtc >= _viewRefreshInterval;
|
||||
|
||||
if (!refresh && !item.DisplayParentId.IsEmpty())
|
||||
{
|
||||
var displayParent = GetItemById(item.DisplayParentId);
|
||||
refresh = displayParent is not null && displayParent.DateLastSaved > item.DateLastRefreshed;
|
||||
refresh = displayParent is not null && displayParent.DateLastSaved > lastRefreshedUtc;
|
||||
}
|
||||
|
||||
if (refresh)
|
||||
|
@ -2447,12 +2450,13 @@ namespace Emby.Server.Implementations.Library
|
|||
isNew = true;
|
||||
}
|
||||
|
||||
var refresh = isNew || DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval;
|
||||
var lastRefreshedUtc = item.DateLastRefreshed;
|
||||
var refresh = isNew || DateTime.UtcNow - lastRefreshedUtc >= _viewRefreshInterval;
|
||||
|
||||
if (!refresh && !item.DisplayParentId.IsEmpty())
|
||||
{
|
||||
var displayParent = GetItemById(item.DisplayParentId);
|
||||
refresh = displayParent is not null && displayParent.DateLastSaved > item.DateLastRefreshed;
|
||||
refresh = displayParent is not null && displayParent.DateLastSaved > lastRefreshedUtc;
|
||||
}
|
||||
|
||||
if (refresh)
|
||||
|
@ -2522,12 +2526,13 @@ namespace Emby.Server.Implementations.Library
|
|||
item.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
var refresh = isNew || DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval;
|
||||
var lastRefreshedUtc = item.DateLastRefreshed;
|
||||
var refresh = isNew || DateTime.UtcNow - lastRefreshedUtc >= _viewRefreshInterval;
|
||||
|
||||
if (!refresh && !item.DisplayParentId.IsEmpty())
|
||||
{
|
||||
var displayParent = GetItemById(item.DisplayParentId);
|
||||
refresh = displayParent is not null && displayParent.DateLastSaved > item.DateLastRefreshed;
|
||||
refresh = displayParent is not null && displayParent.DateLastSaved > lastRefreshedUtc;
|
||||
}
|
||||
|
||||
if (refresh)
|
||||
|
@ -2991,13 +2996,12 @@ namespace Emby.Server.Implementations.Library
|
|||
{
|
||||
var path = Person.GetPath(person.Name);
|
||||
var info = Directory.CreateDirectory(path);
|
||||
var lastWriteTime = info.LastWriteTimeUtc;
|
||||
personEntity = new Person()
|
||||
{
|
||||
Name = person.Name,
|
||||
Id = GetItemByNameId<Person>(path),
|
||||
DateCreated = info.CreationTimeUtc,
|
||||
DateModified = lastWriteTime,
|
||||
DateModified = info.LastWriteTimeUtc,
|
||||
Path = path
|
||||
};
|
||||
|
||||
|
|
|
@ -140,7 +140,7 @@ namespace Emby.Server.Implementations.Library
|
|||
if (fileCreationDate is not null)
|
||||
{
|
||||
var dateCreated = fileCreationDate;
|
||||
if (dateCreated.Equals(DateTime.MinValue))
|
||||
if (dateCreated == DateTime.MinValue)
|
||||
{
|
||||
dateCreated = DateTime.UtcNow;
|
||||
}
|
||||
|
|
|
@ -423,7 +423,7 @@ namespace Emby.Server.Implementations.Plugins
|
|||
Overview = packageInfo.Overview,
|
||||
Owner = packageInfo.Owner,
|
||||
TargetAbi = versionInfo.TargetAbi ?? string.Empty,
|
||||
Timestamp = string.IsNullOrEmpty(versionInfo.Timestamp) ? DateTime.MinValue : DateTime.Parse(versionInfo.Timestamp, CultureInfo.InvariantCulture),
|
||||
Timestamp = string.IsNullOrEmpty(versionInfo.Timestamp) ? DateTime.MinValue : DateTime.Parse(versionInfo.Timestamp, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal),
|
||||
Version = versionInfo.Version,
|
||||
Status = status == PluginStatus.Disabled ? PluginStatus.Disabled : PluginStatus.Active, // Keep disabled state.
|
||||
AutoUpdate = true,
|
||||
|
|
|
@ -5,7 +5,6 @@ using Jellyfin.Data.Enums;
|
|||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Controller.Sorting;
|
||||
using MediaBrowser.Model.Querying;
|
||||
|
||||
namespace Emby.Server.Implementations.Sorting
|
||||
{
|
||||
|
|
|
@ -540,7 +540,7 @@ public sealed class BaseItemRepository
|
|||
}
|
||||
|
||||
var itemValueMaps = tuples
|
||||
.Select(e => (Item: e.Item, Values: GetItemValuesToSave(e.Item, e.InheritedTags)))
|
||||
.Select(e => (e.Item, Values: GetItemValuesToSave(e.Item, e.InheritedTags)))
|
||||
.ToArray();
|
||||
var allListedItemValues = itemValueMaps
|
||||
.SelectMany(f => f.Values)
|
||||
|
@ -567,7 +567,7 @@ public sealed class BaseItemRepository
|
|||
|
||||
var itemValuesStore = existingValues.Concat(missingItemValues).ToArray();
|
||||
var valueMap = itemValueMaps
|
||||
.Select(f => (Item: f.Item, Values: f.Values.Select(e => itemValuesStore.First(g => g.Value == e.Value && g.Type == e.MagicNumber)).ToArray()))
|
||||
.Select(f => (f.Item, Values: f.Values.Select(e => itemValuesStore.First(g => g.Value == e.Value && g.Type == e.MagicNumber)).ToArray()))
|
||||
.ToArray();
|
||||
|
||||
var mappedValues = context.ItemValuesMap.Where(e => ids.Contains(e.ItemId)).ToList();
|
||||
|
@ -702,11 +702,11 @@ public sealed class BaseItemRepository
|
|||
dto.ExternalId = entity.ExternalId;
|
||||
dto.Size = entity.Size;
|
||||
dto.Genres = string.IsNullOrWhiteSpace(entity.Genres) ? [] : entity.Genres.Split('|');
|
||||
dto.DateCreated = entity.DateCreated.GetValueOrDefault();
|
||||
dto.DateModified = entity.DateModified.GetValueOrDefault();
|
||||
dto.DateCreated = entity.DateCreated ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
|
||||
dto.DateModified = entity.DateModified ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
|
||||
dto.ChannelId = entity.ChannelId ?? Guid.Empty;
|
||||
dto.DateLastRefreshed = entity.DateLastRefreshed.GetValueOrDefault();
|
||||
dto.DateLastSaved = entity.DateLastSaved.GetValueOrDefault();
|
||||
dto.DateLastRefreshed = entity.DateLastRefreshed ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
|
||||
dto.DateLastSaved = entity.DateLastSaved ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
|
||||
dto.OwnerId = string.IsNullOrWhiteSpace(entity.OwnerId) ? Guid.Empty : (Guid.TryParse(entity.OwnerId, out var ownerId) ? ownerId : Guid.Empty);
|
||||
dto.Width = entity.Width.GetValueOrDefault();
|
||||
dto.Height = entity.Height.GetValueOrDefault();
|
||||
|
@ -807,7 +807,7 @@ public sealed class BaseItemRepository
|
|||
|
||||
if (dto is Folder folder)
|
||||
{
|
||||
folder.DateLastMediaAdded = entity.DateLastMediaAdded;
|
||||
folder.DateLastMediaAdded = entity.DateLastMediaAdded ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
|
||||
}
|
||||
|
||||
return dto;
|
||||
|
@ -867,11 +867,11 @@ public sealed class BaseItemRepository
|
|||
entity.ExternalId = dto.ExternalId;
|
||||
entity.Size = dto.Size;
|
||||
entity.Genres = string.Join('|', dto.Genres);
|
||||
entity.DateCreated = dto.DateCreated;
|
||||
entity.DateModified = dto.DateModified;
|
||||
entity.DateCreated = dto.DateCreated == DateTime.MinValue ? null : dto.DateCreated;
|
||||
entity.DateModified = dto.DateModified == DateTime.MinValue ? null : dto.DateModified;
|
||||
entity.ChannelId = dto.ChannelId;
|
||||
entity.DateLastRefreshed = dto.DateLastRefreshed;
|
||||
entity.DateLastSaved = dto.DateLastSaved;
|
||||
entity.DateLastRefreshed = dto.DateLastRefreshed == DateTime.MinValue ? null : dto.DateLastRefreshed;
|
||||
entity.DateLastSaved = dto.DateLastSaved == DateTime.MinValue ? null : dto.DateLastSaved;
|
||||
entity.OwnerId = dto.OwnerId.ToString();
|
||||
entity.Width = dto.Width;
|
||||
entity.Height = dto.Height;
|
||||
|
@ -981,7 +981,7 @@ public sealed class BaseItemRepository
|
|||
|
||||
if (dto is Folder folder)
|
||||
{
|
||||
entity.DateLastMediaAdded = folder.DateLastMediaAdded;
|
||||
entity.DateLastMediaAdded = folder.DateLastMediaAdded == DateTime.MinValue ? null : folder.DateLastMediaAdded;
|
||||
entity.IsFolder = folder.IsFolder;
|
||||
}
|
||||
|
||||
|
@ -1302,7 +1302,7 @@ public sealed class BaseItemRepository
|
|||
{
|
||||
Path = appHost?.ExpandVirtualPath(e.Path) ?? e.Path,
|
||||
BlurHash = e.Blurhash is null ? null : Encoding.UTF8.GetString(e.Blurhash),
|
||||
DateModified = e.DateModified,
|
||||
DateModified = e.DateModified ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc),
|
||||
Height = e.Height,
|
||||
Width = e.Width,
|
||||
Type = (ImageType)e.ImageType
|
||||
|
|
168
Jellyfin.Server/Migrations/Routines/FixDates.cs
Normal file
168
Jellyfin.Server/Migrations/Routines/FixDates.cs
Normal file
|
@ -0,0 +1,168 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Database.Implementations;
|
||||
using Jellyfin.Server.ServerSetupApp;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Jellyfin.Server.Migrations.Routines;
|
||||
|
||||
/// <summary>
|
||||
/// Migration to fix dates saved in the database to always be UTC.
|
||||
/// </summary>
|
||||
[JellyfinMigration("2025-06-20T18:00:00", nameof(FixDates))]
|
||||
public class FixDates : IAsyncMigrationRoutine
|
||||
{
|
||||
private const int PageSize = 5000;
|
||||
|
||||
private readonly ILogger _logger;
|
||||
private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FixDates"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="startupLogger">The startup logger for Startup UI integration.</param>
|
||||
/// <param name="dbProvider">Instance of the <see cref="IDbContextFactory{JellyfinDbContext}"/> interface.</param>
|
||||
public FixDates(
|
||||
ILogger<FixDates> logger,
|
||||
IStartupLogger<FixDates> startupLogger,
|
||||
IDbContextFactory<JellyfinDbContext> dbProvider)
|
||||
{
|
||||
_logger = startupLogger.With(logger);
|
||||
_dbProvider = dbProvider;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task PerformAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (!TimeZoneInfo.Local.Equals(TimeZoneInfo.Utc))
|
||||
{
|
||||
using var context = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
|
||||
var sw = Stopwatch.StartNew();
|
||||
|
||||
await FixBaseItemsAsync(context, sw, cancellationToken).ConfigureAwait(false);
|
||||
sw.Reset();
|
||||
await FixChaptersAsync(context, sw, cancellationToken).ConfigureAwait(false);
|
||||
sw.Reset();
|
||||
await FixBaseItemImageInfos(context, sw, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task FixBaseItemsAsync(JellyfinDbContext context, Stopwatch sw, CancellationToken cancellationToken)
|
||||
{
|
||||
int itemCount = 0;
|
||||
|
||||
var baseQuery = context.BaseItems.OrderBy(e => e.Id);
|
||||
var records = baseQuery.Count();
|
||||
_logger.LogInformation("Fixing dates for {Count} BaseItems.", records);
|
||||
|
||||
sw.Start();
|
||||
await foreach (var result in context.BaseItems.OrderBy(e => e.Id)
|
||||
.WithPartitionProgress(
|
||||
(partition) =>
|
||||
_logger.LogInformation(
|
||||
"Processing BaseItems batch {BatchNumber} ({ProcessedSoFar}/{TotalRecords}) - Time: {ElapsedTime}",
|
||||
partition + 1,
|
||||
Math.Min((partition + 1) * PageSize, records),
|
||||
records,
|
||||
sw.Elapsed))
|
||||
.PartitionEagerAsync(PageSize, cancellationToken)
|
||||
.WithCancellation(cancellationToken)
|
||||
.ConfigureAwait(false))
|
||||
{
|
||||
result.DateCreated = ToUniversalTime(result.DateCreated);
|
||||
result.DateLastMediaAdded = ToUniversalTime(result.DateLastMediaAdded);
|
||||
result.DateLastRefreshed = ToUniversalTime(result.DateLastRefreshed);
|
||||
result.DateLastSaved = ToUniversalTime(result.DateLastSaved);
|
||||
result.DateModified = ToUniversalTime(result.DateModified);
|
||||
itemCount++;
|
||||
}
|
||||
|
||||
var saveCount = await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
_logger.LogInformation("BaseItems: Processed {ItemCount} items, saved {SaveCount} changes in {ElapsedTime}", itemCount, saveCount, sw.Elapsed);
|
||||
}
|
||||
|
||||
private async Task FixChaptersAsync(JellyfinDbContext context, Stopwatch sw, CancellationToken cancellationToken)
|
||||
{
|
||||
int itemCount = 0;
|
||||
|
||||
var baseQuery = context.Chapters;
|
||||
var records = baseQuery.Count();
|
||||
_logger.LogInformation("Fixing dates for {Count} Chapters.", records);
|
||||
|
||||
sw.Start();
|
||||
await foreach (var result in context.Chapters.OrderBy(e => e.ItemId)
|
||||
.WithPartitionProgress(
|
||||
(partition) =>
|
||||
_logger.LogInformation(
|
||||
"Processing Chapter batch {BatchNumber} ({ProcessedSoFar}/{TotalRecords}) - Time: {ElapsedTime}",
|
||||
partition + 1,
|
||||
Math.Min((partition + 1) * PageSize, records),
|
||||
records,
|
||||
sw.Elapsed))
|
||||
.PartitionEagerAsync(PageSize, cancellationToken)
|
||||
.WithCancellation(cancellationToken)
|
||||
.ConfigureAwait(false))
|
||||
{
|
||||
result.ImageDateModified = ToUniversalTime(result.ImageDateModified, true);
|
||||
itemCount++;
|
||||
}
|
||||
|
||||
var saveCount = await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
_logger.LogInformation("Chapters: Processed {ItemCount} items, saved {SaveCount} changes in {ElapsedTime}", itemCount, saveCount, sw.Elapsed);
|
||||
}
|
||||
|
||||
private async Task FixBaseItemImageInfos(JellyfinDbContext context, Stopwatch sw, CancellationToken cancellationToken)
|
||||
{
|
||||
int itemCount = 0;
|
||||
|
||||
var baseQuery = context.BaseItemImageInfos;
|
||||
var records = baseQuery.Count();
|
||||
_logger.LogInformation("Fixing dates for {Count} BaseItemImageInfos.", records);
|
||||
|
||||
sw.Start();
|
||||
await foreach (var result in context.BaseItemImageInfos.OrderBy(e => e.Id)
|
||||
.WithPartitionProgress(
|
||||
(partition) =>
|
||||
_logger.LogInformation(
|
||||
"Processing BaseItemImageInfos batch {BatchNumber} ({ProcessedSoFar}/{TotalRecords}) - Time: {ElapsedTime}",
|
||||
partition + 1,
|
||||
Math.Min((partition + 1) * PageSize, records),
|
||||
records,
|
||||
sw.Elapsed))
|
||||
.PartitionEagerAsync(PageSize, cancellationToken)
|
||||
.WithCancellation(cancellationToken)
|
||||
.ConfigureAwait(false))
|
||||
{
|
||||
result.DateModified = ToUniversalTime(result.DateModified);
|
||||
itemCount++;
|
||||
}
|
||||
|
||||
var saveCount = await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
_logger.LogInformation("BaseItemImageInfos: Processed {ItemCount} items, saved {SaveCount} changes in {ElapsedTime}", itemCount, saveCount, sw.Elapsed);
|
||||
}
|
||||
|
||||
private DateTime? ToUniversalTime(DateTime? dateTime, bool isUTC = false)
|
||||
{
|
||||
if (dateTime is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (dateTime.Value.Year == 1 && dateTime.Value.Month == 1 && dateTime.Value.Day == 1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (dateTime.Value.Kind == DateTimeKind.Utc || isUTC)
|
||||
{
|
||||
return dateTime.Value;
|
||||
}
|
||||
|
||||
return dateTime.Value.ToUniversalTime();
|
||||
}
|
||||
}
|
|
@ -1423,23 +1423,16 @@ namespace MediaBrowser.Controller.Entities
|
|||
|
||||
public virtual bool RequiresRefresh()
|
||||
{
|
||||
if (string.IsNullOrEmpty(Path) || DateModified == default)
|
||||
if (string.IsNullOrEmpty(Path) || DateModified == DateTime.MinValue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var info = FileSystem.GetFileSystemInfo(Path);
|
||||
if (info.Exists)
|
||||
{
|
||||
if (info.IsDirectory)
|
||||
{
|
||||
return info.LastWriteTimeUtc != DateModified;
|
||||
}
|
||||
|
||||
return info.LastWriteTimeUtc != DateModified;
|
||||
}
|
||||
|
||||
return false;
|
||||
return info.Exists
|
||||
? info.LastWriteTimeUtc != DateModified
|
||||
: false;
|
||||
}
|
||||
|
||||
public virtual List<string> GetUserDataKeys()
|
||||
|
|
|
@ -235,11 +235,11 @@ namespace MediaBrowser.LocalMetadata.Savers
|
|||
{
|
||||
if (item is Person)
|
||||
{
|
||||
await writer.WriteElementStringAsync(null, "DeathDate", null, item.EndDate.Value.ToLocalTime().ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)).ConfigureAwait(false);
|
||||
await writer.WriteElementStringAsync(null, "DeathDate", null, item.EndDate.Value.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)).ConfigureAwait(false);
|
||||
}
|
||||
else if (item is not Episode)
|
||||
{
|
||||
await writer.WriteElementStringAsync(null, "EndDate", null, item.EndDate.Value.ToLocalTime().ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)).ConfigureAwait(false);
|
||||
await writer.WriteElementStringAsync(null, "EndDate", null, item.EndDate.Value.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -73,11 +73,11 @@ namespace MediaBrowser.Providers.Manager
|
|||
|
||||
public virtual int Order => 0;
|
||||
|
||||
private FileSystemMetadata TryGetFile(string path, IDirectoryService directoryService)
|
||||
private FileSystemMetadata TryGetFileSystemMetadata(string path, IDirectoryService directoryService)
|
||||
{
|
||||
try
|
||||
{
|
||||
return directoryService.GetFile(path);
|
||||
return directoryService.GetFileSystemEntry(path);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -92,7 +92,7 @@ namespace MediaBrowser.Providers.Manager
|
|||
var updateType = ItemUpdateType.None;
|
||||
|
||||
var libraryOptions = LibraryManager.GetLibraryOptions(item);
|
||||
var isFirstRefresh = item.DateLastRefreshed.Date == DateTime.MinValue.Date;
|
||||
var isFirstRefresh = item.DateLastRefreshed == DateTime.MinValue;
|
||||
var hasRefreshedMetadata = true;
|
||||
var hasRefreshedImages = true;
|
||||
|
||||
|
@ -225,7 +225,7 @@ namespace MediaBrowser.Providers.Manager
|
|||
{
|
||||
if (item.IsFileProtocol)
|
||||
{
|
||||
var file = TryGetFile(item.Path, refreshOptions.DirectoryService);
|
||||
var file = TryGetFileSystemMetadata(item.Path, refreshOptions.DirectoryService);
|
||||
if (file is not null)
|
||||
{
|
||||
item.DateModified = file.LastWriteTimeUtc;
|
||||
|
@ -1180,12 +1180,12 @@ namespace MediaBrowser.Providers.Manager
|
|||
target.LockedFields = target.LockedFields.Concat(source.LockedFields).Distinct().ToArray();
|
||||
}
|
||||
|
||||
if (source.DateCreated != default)
|
||||
if (source.DateCreated != DateTime.MinValue)
|
||||
{
|
||||
target.DateCreated = source.DateCreated;
|
||||
}
|
||||
|
||||
if (replaceData || source.DateModified != default)
|
||||
if (replaceData || source.DateModified != DateTime.MinValue)
|
||||
{
|
||||
target.DateModified = source.DateModified;
|
||||
}
|
||||
|
|
|
@ -669,8 +669,13 @@ namespace MediaBrowser.Providers.Manager
|
|||
private async Task SaveMetadataAsync(BaseItem item, ItemUpdateType updateType, IEnumerable<IMetadataSaver> savers)
|
||||
{
|
||||
var libraryOptions = _libraryManager.GetLibraryOptions(item);
|
||||
var applicableSavers = savers.Where(i => IsSaverEnabledForItem(i, item, libraryOptions, updateType, false)).ToList();
|
||||
if (applicableSavers.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var saver in savers.Where(i => IsSaverEnabledForItem(i, item, libraryOptions, updateType, false)))
|
||||
foreach (var saver in applicableSavers)
|
||||
{
|
||||
_logger.LogDebug("Saving {Item} to {Saver}", item.Path ?? item.Name, saver.Name);
|
||||
|
||||
|
@ -714,6 +719,8 @@ namespace MediaBrowser.Providers.Manager
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
_libraryManager.CreateItem(item, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
|
|
@ -22,7 +22,7 @@ public class BaseItemImageInfo
|
|||
/// <summary>
|
||||
/// Gets or Sets the time the image was last modified.
|
||||
/// </summary>
|
||||
public DateTime DateModified { get; set; }
|
||||
public DateTime? DateModified { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or Sets the imagetype.
|
||||
|
|
|
@ -82,7 +82,7 @@ public static class QueryPartitionHelpers
|
|||
/// <typeparam name="TEntity">The entity to load.</typeparam>
|
||||
/// <param name="partitionInfo">The source query.</param>
|
||||
/// <param name="partitionSize">The number of elements to load per partition.</param>
|
||||
/// <param name="cancellationToken">The cancelation token.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A enumerable representing the whole of the query.</returns>
|
||||
public static async IAsyncEnumerable<TEntity> PartitionAsync<TEntity>(this ProgressablePartitionReporting<TEntity> partitionInfo, int partitionSize, [EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
|
@ -98,7 +98,7 @@ public static class QueryPartitionHelpers
|
|||
/// <typeparam name="TEntity">The entity to load.</typeparam>
|
||||
/// <param name="partitionInfo">The source query.</param>
|
||||
/// <param name="partitionSize">The number of elements to load per partition.</param>
|
||||
/// <param name="cancellationToken">The cancelation token.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A enumerable representing the whole of the query.</returns>
|
||||
public static async IAsyncEnumerable<TEntity> PartitionEagerAsync<TEntity>(this ProgressablePartitionReporting<TEntity> partitionInfo, int partitionSize, [EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
|
@ -115,7 +115,7 @@ public static class QueryPartitionHelpers
|
|||
/// <param name="query">The source query.</param>
|
||||
/// <param name="partitionSize">The number of elements to load per partition.</param>
|
||||
/// <param name="progressablePartition">Reporting helper.</param>
|
||||
/// <param name="cancellationToken">The cancelation token.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A enumerable representing the whole of the query.</returns>
|
||||
public static async IAsyncEnumerable<TEntity> PartitionAsync<TEntity>(
|
||||
this IOrderedQueryable<TEntity> query,
|
||||
|
@ -154,7 +154,7 @@ public static class QueryPartitionHelpers
|
|||
/// <param name="query">The source query.</param>
|
||||
/// <param name="partitionSize">The number of elements to load per partition.</param>
|
||||
/// <param name="progressablePartition">Reporting helper.</param>
|
||||
/// <param name="cancellationToken">The cancelation token.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A enumerable representing the whole of the query.</returns>
|
||||
public static async IAsyncEnumerable<TEntity> PartitionEagerAsync<TEntity>(
|
||||
this IOrderedQueryable<TEntity> query,
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,37 @@
|
|||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Jellyfin.Server.Implementations.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class BaseItemImageInfoDateModifiedNullable : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "DateModified",
|
||||
table: "BaseItemImageInfos",
|
||||
type: "TEXT",
|
||||
nullable: true,
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "TEXT");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "DateModified",
|
||||
table: "BaseItemImageInfos",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "TEXT",
|
||||
oldNullable: true);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -418,7 +418,7 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||
b.Property<byte[]>("Blurhash")
|
||||
.HasColumnType("BLOB");
|
||||
|
||||
b.Property<DateTime>("DateModified")
|
||||
b.Property<DateTime?>("DateModified")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Height")
|
||||
|
|
|
@ -1166,7 +1166,7 @@ namespace Jellyfin.LiveTv.Channels
|
|||
}
|
||||
}
|
||||
|
||||
if (isNew || forceUpdate || item.DateLastRefreshed == default)
|
||||
if (isNew || forceUpdate || item.DateLastRefreshed == DateTime.MinValue)
|
||||
{
|
||||
_providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.Normal);
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ namespace Jellyfin.Providers.Tests.Manager
|
|||
{
|
||||
var newLocked = new[] { MetadataField.Genres, MetadataField.Cast };
|
||||
var newString = "new";
|
||||
var newDate = DateTime.Now;
|
||||
var newDate = DateTime.UtcNow;
|
||||
|
||||
var oldLocked = new[] { MetadataField.Genres };
|
||||
var oldString = "old";
|
||||
|
@ -39,6 +39,7 @@ namespace Jellyfin.Providers.Tests.Manager
|
|||
DateCreated = newDate
|
||||
}
|
||||
};
|
||||
|
||||
if (defaultDate)
|
||||
{
|
||||
source.Item.DateCreated = default;
|
||||
|
@ -141,8 +142,8 @@ namespace Jellyfin.Providers.Tests.Manager
|
|||
{ "ProductionYear", 1, 2 },
|
||||
{ "CommunityRating", 1.0f, 2.0f },
|
||||
{ "CriticRating", 1.0f, 2.0f },
|
||||
{ "EndDate", DateTime.UnixEpoch, DateTime.Now },
|
||||
{ "PremiereDate", DateTime.UnixEpoch, DateTime.Now },
|
||||
{ "EndDate", DateTime.UnixEpoch, DateTime.UtcNow },
|
||||
{ "PremiereDate", DateTime.UnixEpoch, DateTime.UtcNow },
|
||||
{ "Video3DFormat", Video3DFormat.HalfSideBySide, Video3DFormat.FullSideBySide }
|
||||
};
|
||||
|
||||
|
|
|
@ -185,7 +185,7 @@ namespace Jellyfin.Server.Implementations.Tests.Plugins
|
|||
Description = packageInfo.Description,
|
||||
Overview = packageInfo.Overview,
|
||||
TargetAbi = packageInfo.Versions[0].TargetAbi!,
|
||||
Timestamp = DateTime.Parse(packageInfo.Versions[0].Timestamp!, CultureInfo.InvariantCulture),
|
||||
Timestamp = DateTimeOffset.Parse(packageInfo.Versions[0].Timestamp!, CultureInfo.InvariantCulture).UtcDateTime,
|
||||
Changelog = packageInfo.Versions[0].Changelog!,
|
||||
Version = new Version(1, 0).ToString(),
|
||||
ImagePath = string.Empty
|
||||
|
@ -221,7 +221,7 @@ namespace Jellyfin.Server.Implementations.Tests.Plugins
|
|||
Description = packageInfo.Description,
|
||||
Overview = packageInfo.Overview,
|
||||
TargetAbi = packageInfo.Versions[0].TargetAbi!,
|
||||
Timestamp = DateTime.Parse(packageInfo.Versions[0].Timestamp!, CultureInfo.InvariantCulture),
|
||||
Timestamp = DateTimeOffset.Parse(packageInfo.Versions[0].Timestamp!, CultureInfo.InvariantCulture).UtcDateTime,
|
||||
Changelog = packageInfo.Versions[0].Changelog!,
|
||||
Version = packageInfo.Versions[0].Version,
|
||||
ImagePath = string.Empty
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue