public static bool UpdateMetadata(BaseItem item, MetadataRefreshOptions options) {

            bool force = (options & MetadataRefreshOptions.Force) == MetadataRefreshOptions.Force;
            bool fastOnly = (options & MetadataRefreshOptions.FastOnly) == MetadataRefreshOptions.FastOnly;

            bool changed = false;
            if (force) {
                ClearItem(item); 
            }

            bool neverSavedProviderInfo;
            var providers = GetSupportedProviders(item, out neverSavedProviderInfo);

            var itemClone = (BaseItem)Serializer.Clone(item);
            // Parent is not serialized so its not cloned
            itemClone.Parent = item.Parent;

            foreach (var provider in providers) {
                provider.Item = itemClone;
            }

            if (force || NeedsRefresh(providers, fastOnly)) {

                // something changed clear the item before pulling metadata 
                if (!force) {
                    ClearItem(item);
                    ClearItem(itemClone);
                }

                // we must clear the provider data as well in case it is bad or out of date! 
                foreach (var provider in providers) {
                    ClearItem(provider);
                }

                Logger.ReportInfo("Metadata changed for the following item {0} (first pass : {1} forced via UI : {2})", item.Name, fastOnly, force);
                changed = UpdateMetadata(item, true, fastOnly, providers);
            }

            if (!changed && neverSavedProviderInfo) {
                Kernel.Instance.ItemRepository.SaveProviders(item.Id, providers);
            }

            return changed;
        }
        public static bool UpdateMetadata(BaseItem item, MetadataRefreshOptions options)
        {
            bool force = (options & MetadataRefreshOptions.Force) == MetadataRefreshOptions.Force;
            bool fastOnly = (options & MetadataRefreshOptions.FastOnly) == MetadataRefreshOptions.FastOnly;

            bool changed = false;
            if (force) {
                ClearItem(item);
            }
            var providers = GetSupportedProviders(item);

            var itemClone = (BaseItem)Serializer.Clone(item);
            // Parent is not serialized so its not cloned
            itemClone.Parent = item.Parent;

            foreach (var provider in providers) {
                provider.Item = itemClone;
            }

            if (force || NeedsRefresh(providers, fastOnly)) {
                changed = UpdateMetadata(item, force, fastOnly, providers);
            }
            return changed;
        }
Example #3
0
        public Task <ItemUpdateType> FetchAsync(Photo item, MetadataRefreshOptions options, CancellationToken cancellationToken)
        {
            item.SetImagePath(ImageType.Primary, item.Path);

            // Examples: https://github.com/mono/taglib-sharp/blob/a5f6949a53d09ce63ee7495580d6802921a21f14/tests/fixtures/TagLib.Tests.Images/NullOrientationTest.cs

            try
            {
                using (var file = TagLib.File.Create(item.Path))
                {
                    var image = file as TagLib.Image.File;

                    var tag = file.GetTag(TagTypes.TiffIFD) as IFDTag;

                    if (tag != null)
                    {
                        var structure = tag.Structure;

                        if (structure != null)
                        {
                            var exif = structure.GetEntry(0, (ushort)IFDEntryTag.ExifIFD) as SubIFDEntry;

                            if (exif != null)
                            {
                                var exifStructure = exif.Structure;

                                if (exifStructure != null)
                                {
                                    var entry = exifStructure.GetEntry(0, (ushort)ExifEntryTag.ApertureValue) as RationalIFDEntry;

                                    if (entry != null)
                                    {
                                        double val = entry.Value.Numerator;
                                        val          /= entry.Value.Denominator;
                                        item.Aperture = val;
                                    }

                                    entry = exifStructure.GetEntry(0, (ushort)ExifEntryTag.ShutterSpeedValue) as RationalIFDEntry;

                                    if (entry != null)
                                    {
                                        double val = entry.Value.Numerator;
                                        val /= entry.Value.Denominator;
                                        item.ShutterSpeed = val;
                                    }
                                }
                            }
                        }
                    }

                    item.CameraMake  = image.ImageTag.Make;
                    item.CameraModel = image.ImageTag.Model;

                    item.Width  = image.Properties.PhotoWidth;
                    item.Height = image.Properties.PhotoHeight;

                    var rating = image.ImageTag.Rating;
                    if (rating.HasValue)
                    {
                        item.CommunityRating = rating;
                    }
                    else
                    {
                        item.CommunityRating = null;
                    }

                    item.Overview = image.ImageTag.Comment;

                    if (!string.IsNullOrWhiteSpace(image.ImageTag.Title))
                    {
                        item.Name = image.ImageTag.Title;
                    }

                    var dateTaken = image.ImageTag.DateTime;
                    if (dateTaken.HasValue)
                    {
                        item.DateCreated    = dateTaken.Value;
                        item.PremiereDate   = dateTaken.Value;
                        item.ProductionYear = dateTaken.Value.Year;
                    }

                    item.Genres   = image.ImageTag.Genres.ToList();
                    item.Tags     = image.ImageTag.Keywords.ToList();
                    item.Software = image.ImageTag.Software;

                    if (image.ImageTag.Orientation == TagLib.Image.ImageOrientation.None)
                    {
                        item.Orientation = null;
                    }
                    else
                    {
                        Model.Drawing.ImageOrientation orientation;
                        if (Enum.TryParse(image.ImageTag.Orientation.ToString(), true, out orientation))
                        {
                            item.Orientation = orientation;
                        }
                    }

                    item.ExposureTime = image.ImageTag.ExposureTime;
                    item.FocalLength  = image.ImageTag.FocalLength;

                    item.Latitude  = image.ImageTag.Latitude;
                    item.Longitude = image.ImageTag.Longitude;
                    item.Altitude  = image.ImageTag.Altitude;

                    if (image.ImageTag.ISOSpeedRatings.HasValue)
                    {
                        item.IsoSpeedRating = Convert.ToInt32(image.ImageTag.ISOSpeedRatings.Value);
                    }
                    else
                    {
                        item.IsoSpeedRating = null;
                    }
                }
            }
            catch (Exception e)
            {
                _logger.ErrorException("Image Provider - Error reading image tag for {0}", e, item.Path);
            }

            const ItemUpdateType result = ItemUpdateType.ImageUpdate | ItemUpdateType.MetadataImport;

            return(Task.FromResult(result));
        }
Example #4
0
        public async Task RefreshAllMetadata(MetadataRefreshOptions refreshOptions, IProgress <double> progress, CancellationToken cancellationToken)
        {
            // Refresh bottom up, children first, then the boxset
            // By then hopefully the  movies within will have Tmdb collection values
            var items = GetRecursiveChildren();

            var totalItems  = items.Count;
            var numComplete = 0;

            // Refresh seasons
            foreach (var item in items)
            {
                if (!(item is Season))
                {
                    continue;
                }

                cancellationToken.ThrowIfCancellationRequested();

                if (refreshOptions.RefreshItem(item))
                {
                    await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
                }

                numComplete++;
                double percent = numComplete;
                percent /= totalItems;
                progress.Report(percent * 100);
            }

            // Refresh episodes and other children
            foreach (var item in items)
            {
                if ((item is Season))
                {
                    continue;
                }

                cancellationToken.ThrowIfCancellationRequested();

                var skipItem = false;

                var episode = item as Episode;

                if (episode != null &&
                    refreshOptions.MetadataRefreshMode != MetadataRefreshMode.FullRefresh &&
                    !refreshOptions.ReplaceAllMetadata &&
                    episode.IsMissingEpisode &&
                    episode.LocationType == LocationType.Virtual &&
                    episode.PremiereDate.HasValue &&
                    (DateTime.UtcNow - episode.PremiereDate.Value).TotalDays > 30)
                {
                    skipItem = true;
                }

                if (!skipItem)
                {
                    if (refreshOptions.RefreshItem(item))
                    {
                        await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
                    }
                }

                numComplete++;
                double percent = numComplete;
                percent /= totalItems;
                progress.Report(percent * 100);
            }

            refreshOptions = new MetadataRefreshOptions(refreshOptions);
            await ProviderManager.RefreshSingleItem(this, refreshOptions, cancellationToken).ConfigureAwait(false);
        }
Example #5
0
        private void FetchEmbeddedInfo(Video video, Model.MediaInfo.MediaInfo data, MetadataRefreshOptions options)
        {
            var isFullRefresh = options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh;

            if (!video.LockedFields.Contains(MetadataFields.OfficialRating))
            {
                if (!string.IsNullOrWhiteSpace(data.OfficialRating) || isFullRefresh)
                {
                    video.OfficialRating = data.OfficialRating;
                }
            }

            if (!video.LockedFields.Contains(MetadataFields.Genres))
            {
                if (video.Genres.Count == 0 || isFullRefresh)
                {
                    video.Genres.Clear();

                    foreach (var genre in data.Genres)
                    {
                        video.AddGenre(genre);
                    }
                }
            }

            if (!video.LockedFields.Contains(MetadataFields.Studios))
            {
                if (video.Studios.Count == 0 || isFullRefresh)
                {
                    video.Studios.Clear();

                    foreach (var studio in data.Studios)
                    {
                        video.AddStudio(studio);
                    }
                }
            }

            if (data.ProductionYear.HasValue)
            {
                if (!video.ProductionYear.HasValue || isFullRefresh)
                {
                    video.ProductionYear = data.ProductionYear;
                }
            }
            if (data.PremiereDate.HasValue)
            {
                if (!video.PremiereDate.HasValue || isFullRefresh)
                {
                    video.PremiereDate = data.PremiereDate;
                }
            }
            if (data.IndexNumber.HasValue)
            {
                if (!video.IndexNumber.HasValue || isFullRefresh)
                {
                    video.IndexNumber = data.IndexNumber;
                }
            }
            if (data.ParentIndexNumber.HasValue)
            {
                if (!video.ParentIndexNumber.HasValue || isFullRefresh)
                {
                    video.ParentIndexNumber = data.ParentIndexNumber;
                }
            }

            // If we don't have a ProductionYear try and get it from PremiereDate
            if (video.PremiereDate.HasValue && !video.ProductionYear.HasValue)
            {
                video.ProductionYear = video.PremiereDate.Value.ToLocalTime().Year;
            }

            if (!video.LockedFields.Contains(MetadataFields.Overview))
            {
                if (string.IsNullOrWhiteSpace(video.Overview) || isFullRefresh)
                {
                    video.Overview = data.Overview;
                }
            }
        }
Example #6
0
 public Task <ItemUpdateType> FetchAsync(Video item, MetadataRefreshOptions options, CancellationToken cancellationToken)
 {
     return(FetchInternal(item, options, cancellationToken));
 }
Example #7
0
        private async Task RunCustomProvider(ICustomMetadataProvider <TItemType> provider, TItemType item, string logName, MetadataRefreshOptions options, RefreshResult refreshResult, CancellationToken cancellationToken)
        {
            Logger.LogDebug("Running {Provider} for {Item}", provider.GetType().Name, logName);

            try
            {
                refreshResult.UpdateType |= await provider.FetchAsync(item, options, cancellationToken).ConfigureAwait(false);
            }
            catch (OperationCanceledException)
            {
                throw;
            }
            catch (Exception ex)
            {
                refreshResult.ErrorMessage = ex.Message;
                Logger.LogError(ex, "Error in {Provider}", provider.Name);
            }
        }
Example #8
0
        protected IEnumerable <IMetadataProvider> GetProviders(BaseItem item, LibraryOptions libraryOptions, MetadataRefreshOptions options, bool isFirstRefresh, bool requiresRefresh)
        {
            // Get providers to refresh
            var providers = ((ProviderManager)ProviderManager).GetMetadataProviders <TItemType>(item, libraryOptions).ToList();

            var metadataRefreshMode = options.MetadataRefreshMode;

            // Run all if either of these flags are true
            var runAllProviders = options.ReplaceAllMetadata ||
                                  metadataRefreshMode == MetadataRefreshMode.FullRefresh ||
                                  (isFirstRefresh && metadataRefreshMode >= MetadataRefreshMode.Default) ||
                                  (requiresRefresh && metadataRefreshMode >= MetadataRefreshMode.Default);

            if (!runAllProviders)
            {
                var providersWithChanges = providers
                                           .Where(i =>
                {
                    if (i is IHasItemChangeMonitor hasFileChangeMonitor)
                    {
                        return(HasChanged(item, hasFileChangeMonitor, options.DirectoryService));
                    }

                    return(false);
                })
                                           .ToList();

                if (providersWithChanges.Count == 0)
                {
                    providers = new List <IMetadataProvider <TItemType> >();
                }
                else
                {
                    var anyRemoteProvidersChanged = providersWithChanges.OfType <IRemoteMetadataProvider>()
                                                    .Any();

                    var anyLocalProvidersChanged = providersWithChanges.OfType <ILocalMetadataProvider>()
                                                   .Any();

                    var anyLocalPreRefreshProvidersChanged = providersWithChanges.OfType <IPreRefreshProvider>()
                                                             .Any();

                    providers = providers.Where(i =>
                    {
                        // If any provider reports a change, always run local ones as well
                        if (i is ILocalMetadataProvider)
                        {
                            return(anyRemoteProvidersChanged || anyLocalProvidersChanged || anyLocalPreRefreshProvidersChanged);
                        }

                        // If any remote providers changed, run them all so that priorities can be honored
                        if (i is IRemoteMetadataProvider)
                        {
                            if (options.MetadataRefreshMode == MetadataRefreshMode.ValidationOnly)
                            {
                                return(false);
                            }

                            return(anyRemoteProvidersChanged);
                        }

                        // Run custom refresh providers if they report a change or any remote providers change
                        return(anyRemoteProvidersChanged || providersWithChanges.Contains(i));
                    }).ToList();
                }
            }

            return(providers);
        }
Example #9
0
 public Task <ItemUpdateType> FetchAsync(AudioBook item, MetadataRefreshOptions options, CancellationToken cancellationToken)
 {
     return(FetchAudioInfo(item, cancellationToken));
 }
        /// <summary>
        /// Compare our current children (presumably just read from the repo) with the current state of the file system and adjust for any changes
        /// ***Currently does not contain logic to maintain items that are unavailable in the file system***
        /// </summary>
        /// <param name="progress">The progress.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <param name="recursive">if set to <c>true</c> [recursive].</param>
        /// <param name="refreshChildMetadata">if set to <c>true</c> [refresh child metadata].</param>
        /// <param name="refreshOptions">The refresh options.</param>
        /// <param name="directoryService">The directory service.</param>
        /// <returns>Task.</returns>
        protected override Task ValidateChildrenInternal(IProgress <double> progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService)
        {
            CreateResolveArgs(directoryService);
            ResetDynamicChildren();

            return(NullTaskResult);
        }
        protected async Task <ItemUpdateType> FetchAsync(IHasMetadata item, ImageType imageType, MetadataRefreshOptions options, CancellationToken cancellationToken)
        {
            var image = item.GetImageInfo(imageType, 0);

            if (image != null)
            {
                if (!image.IsLocalFile)
                {
                    return(ItemUpdateType.None);
                }

                if (!FileSystem.ContainsSubPath(item.GetInternalMetadataPath(), image.Path))
                {
                    return(ItemUpdateType.None);
                }
            }

            var items = GetItemsWithImages(item);

            return(await FetchToFileInternal(item, items, imageType, cancellationToken).ConfigureAwait(false));
        }
Example #12
0
        private async Task RunCustomProvider(ICustomMetadataProvider <TItemType> provider, TItemType item, MetadataRefreshOptions options, RefreshResult refreshResult, CancellationToken cancellationToken)
        {
            Logger.Debug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name);

            try
            {
                refreshResult.UpdateType = refreshResult.UpdateType | await provider.FetchAsync(item, options, cancellationToken).ConfigureAwait(false);
            }
            catch (OperationCanceledException)
            {
                throw;
            }
            catch (Exception ex)
            {
                refreshResult.Status       = ProviderRefreshStatus.CompletedWithErrors;
                refreshResult.ErrorMessage = ex.Message;
                Logger.ErrorException("Error in {0}", ex, provider.Name);
            }
        }
Example #13
0
        private async Task RefreshItem(BaseItem item, MetadataRefreshOptions refreshOptions, IProgress <double> progress, CancellationToken cancellationToken)
        {
            await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);

            progress.Report(100);
        }
Example #14
0
        public async Task RefreshAllMetadata(MetadataRefreshOptions refreshOptions, IProgress <double> progress, CancellationToken cancellationToken)
        {
            var items = RecursiveChildren.ToList();

            var songs = items.OfType <Audio>().ToList();

            var others = items.Except(songs).ToList();

            var totalItems  = songs.Count + others.Count;
            var percentages = new Dictionary <Guid, double>(totalItems);

            var tasks = new List <Task>();

            // Refresh songs
            foreach (var item in songs)
            {
                if (tasks.Count >= 3)
                {
                    await Task.WhenAll(tasks).ConfigureAwait(false);

                    tasks.Clear();
                }

                cancellationToken.ThrowIfCancellationRequested();
                var innerProgress = new ActionableProgress <double>();

                // Avoid implicitly captured closure
                var currentChild = item;
                innerProgress.RegisterAction(p =>
                {
                    lock (percentages)
                    {
                        percentages[currentChild.Id] = p / 100;

                        var percent = percentages.Values.Sum();
                        percent    /= totalItems;
                        percent    *= 100;
                        progress.Report(percent);
                    }
                });

                var taskChild = item;
                tasks.Add(Task.Run(async() => await RefreshItem(taskChild, refreshOptions, innerProgress, cancellationToken).ConfigureAwait(false), cancellationToken));
            }

            await Task.WhenAll(tasks).ConfigureAwait(false);

            tasks.Clear();

            // Refresh current item
            await RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);

            // Refresh all non-songs
            foreach (var item in others)
            {
                if (tasks.Count >= 3)
                {
                    await Task.WhenAll(tasks).ConfigureAwait(false);

                    tasks.Clear();
                }

                cancellationToken.ThrowIfCancellationRequested();
                var innerProgress = new ActionableProgress <double>();

                // Avoid implicitly captured closure
                var currentChild = item;
                innerProgress.RegisterAction(p =>
                {
                    lock (percentages)
                    {
                        percentages[currentChild.Id] = p / 100;

                        var percent = percentages.Values.Sum();
                        percent    /= totalItems;
                        percent    *= 100;
                        progress.Report(percent);
                    }
                });

                // Avoid implicitly captured closure
                var taskChild = item;
                tasks.Add(Task.Run(async() => await RefreshItem(taskChild, refreshOptions, innerProgress, cancellationToken).ConfigureAwait(false), cancellationToken));
            }

            await Task.WhenAll(tasks).ConfigureAwait(false);

            progress.Report(100);
        }
Example #15
0
        /// <summary>
        /// Validates the children internal.
        /// </summary>
        /// <param name="progress">The progress.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <param name="recursive">if set to <c>true</c> [recursive].</param>
        /// <param name="refreshChildMetadata">if set to <c>true</c> [refresh child metadata].</param>
        /// <param name="refreshOptions">The refresh options.</param>
        /// <param name="directoryService">The directory service.</param>
        /// <returns>Task.</returns>
        protected async virtual Task ValidateChildrenInternal(IProgress <double> progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService)
        {
            var locationType = LocationType;

            cancellationToken.ThrowIfCancellationRequested();

            var validChildren = new List <BaseItem>();

            var allLibraryPaths = LibraryManager
                                  .GetVirtualFolders()
                                  .SelectMany(i => i.Locations)
                                  .ToList();

            if (locationType != LocationType.Remote && locationType != LocationType.Virtual)
            {
                IEnumerable <BaseItem> nonCachedChildren;

                try
                {
                    nonCachedChildren = GetNonCachedChildren(directoryService);
                }
                catch (IOException ex)
                {
                    nonCachedChildren = new BaseItem[] { };

                    Logger.ErrorException("Error getting file system entries for {0}", ex, Path);
                }

                if (nonCachedChildren == null)
                {
                    return;                            //nothing to validate
                }
                progress.Report(5);

                //build a dictionary of the current children we have now by Id so we can compare quickly and easily
                var currentChildren = GetActualChildrenDictionary();

                //create a list for our validated children
                var newItems = new List <BaseItem>();

                cancellationToken.ThrowIfCancellationRequested();

                foreach (var child in nonCachedChildren)
                {
                    BaseItem currentChild;

                    if (currentChildren.TryGetValue(child.Id, out currentChild) && IsValidFromResolver(currentChild, child))
                    {
                        validChildren.Add(currentChild);

                        continue;
                    }

                    // Brand new item - needs to be added
                    child.SetParent(this);
                    newItems.Add(child);
                    validChildren.Add(child);
                }

                // If any items were added or removed....
                if (newItems.Count > 0 || currentChildren.Count != validChildren.Count)
                {
                    // That's all the new and changed ones - now see if there are any that are missing
                    var itemsRemoved   = currentChildren.Values.Except(validChildren).ToList();
                    var actualRemovals = new List <BaseItem>();

                    foreach (var item in itemsRemoved)
                    {
                        var itemLocationType = item.LocationType;
                        if (itemLocationType == LocationType.Virtual ||
                            itemLocationType == LocationType.Remote)
                        {
                        }

                        else if (!string.IsNullOrEmpty(item.Path) && IsPathOffline(item.Path, allLibraryPaths))
                        {
                        }
                        else
                        {
                            actualRemovals.Add(item);
                        }
                    }

                    if (actualRemovals.Count > 0)
                    {
                        foreach (var item in actualRemovals)
                        {
                            Logger.Debug("Removed item: " + item.Path);

                            item.SetParent(null);
                            await LibraryManager.DeleteItem(item, new DeleteOptions { DeleteFileLocation = false }).ConfigureAwait(false);

                            LibraryManager.ReportItemRemoved(item);
                        }
                    }

                    await LibraryManager.CreateItems(newItems, cancellationToken).ConfigureAwait(false);
                }
            }

            progress.Report(10);

            cancellationToken.ThrowIfCancellationRequested();

            if (recursive)
            {
                await ValidateSubFolders(ActualChildren.OfType <Folder>().ToList(), directoryService, progress, cancellationToken).ConfigureAwait(false);
            }

            progress.Report(20);

            if (refreshChildMetadata)
            {
                var container = this as IMetadataContainer;

                var innerProgress = new ActionableProgress <double>();

                innerProgress.RegisterAction(p => progress.Report(.80 * p + 20));

                if (container != null)
                {
                    await container.RefreshAllMetadata(refreshOptions, innerProgress, cancellationToken).ConfigureAwait(false);
                }
                else
                {
                    await RefreshMetadataRecursive(refreshOptions, recursive, innerProgress, cancellationToken);
                }
            }

            progress.Report(100);
        }
Example #16
0
        void FullRefresh(Folder folder, MetadataRefreshOptions options)
        {
            Kernel.Instance.MajorActivity = true;
            Information.AddInformationString(CurrentInstance.StringData("FullRefreshMsg"));
            folder.RefreshMetadata(options);

            using (new Profiler(CurrentInstance.StringData("FullValidationProf")))
            {
                RunActionRecursively(folder, item =>
                {
                    Folder f = item as Folder;
                    if (f != null) f.ValidateChildren();
                });
            }

            using (new Profiler(CurrentInstance.StringData("FastRefreshProf")))
            {
                RunActionRecursively(folder, item => item.RefreshMetadata(MetadataRefreshOptions.FastOnly));
            }

            using (new Profiler(CurrentInstance.StringData("SlowRefresh")))
            {
                RunActionRecursively(folder, item => item.RefreshMetadata(MetadataRefreshOptions.Default));
            }

            Information.AddInformationString(CurrentInstance.StringData("FullRefreshFinishedMsg"));
            Kernel.Instance.MajorActivity = false;
        }
Example #17
0
        protected async Task Fetch(Video video,
                                   CancellationToken cancellationToken,
                                   InternalMediaInfoResult data,
                                   IIsoMount isoMount,
                                   BlurayDiscInfo blurayInfo,
                                   MetadataRefreshOptions options)
        {
            var mediaInfo    = MediaEncoderHelpers.GetMediaInfo(data);
            var mediaStreams = mediaInfo.MediaStreams;

            video.TotalBitrate = mediaInfo.TotalBitrate;
            video.FormatName   = (mediaInfo.Format ?? string.Empty)
                                 .Replace("matroska", "mkv", StringComparison.OrdinalIgnoreCase);

            if (data.format != null)
            {
                // For dvd's this may not always be accurate, so don't set the runtime if the item already has one
                var needToSetRuntime = video.VideoType != VideoType.Dvd || video.RunTimeTicks == null || video.RunTimeTicks.Value == 0;

                if (needToSetRuntime && !string.IsNullOrEmpty(data.format.duration))
                {
                    video.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(data.format.duration, _usCulture)).Ticks;
                }

                if (video.VideoType == VideoType.VideoFile)
                {
                    var extension = (Path.GetExtension(video.Path) ?? string.Empty).TrimStart('.');

                    video.Container = extension;
                }
                else
                {
                    video.Container = null;
                }

                if (!string.IsNullOrEmpty(data.format.size))
                {
                    video.Size = long.Parse(data.format.size, _usCulture);
                }
                else
                {
                    video.Size = null;
                }
            }

            var mediaChapters = (data.Chapters ?? new MediaChapter[] { }).ToList();
            var chapters      = mediaChapters.Select(GetChapterInfo).ToList();

            if (video.VideoType == VideoType.BluRay || (video.IsoType.HasValue && video.IsoType.Value == IsoType.BluRay))
            {
                FetchBdInfo(video, chapters, mediaStreams, blurayInfo);
            }

            await AddExternalSubtitles(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);

            FetchWtvInfo(video, data);

            video.IsHD = mediaStreams.Any(i => i.Type == MediaStreamType.Video && i.Width.HasValue && i.Width.Value >= 1270);

            var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);

            video.VideoBitRate            = videoStream == null ? null : videoStream.BitRate;
            video.DefaultVideoStreamIndex = videoStream == null ? (int?)null : videoStream.Index;

            video.HasSubtitles = mediaStreams.Any(i => i.Type == MediaStreamType.Subtitle);

            ExtractTimestamp(video);
            UpdateFromMediaInfo(video, videoStream);

            await _itemRepo.SaveMediaStreams(video.Id, mediaStreams, cancellationToken).ConfigureAwait(false);

            if (options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh ||
                options.MetadataRefreshMode == MetadataRefreshMode.Default)
            {
                var chapterOptions = _chapterManager.GetConfiguration();

                try
                {
                    var remoteChapters = await DownloadChapters(video, chapters, chapterOptions, cancellationToken).ConfigureAwait(false);

                    if (remoteChapters.Count > 0)
                    {
                        chapters = remoteChapters;
                    }
                }
                catch (Exception ex)
                {
                    _logger.ErrorException("Error downloading chapters", ex);
                }

                if (chapters.Count == 0 && mediaStreams.Any(i => i.Type == MediaStreamType.Video))
                {
                    AddDummyChapters(video, chapters);
                }

                NormalizeChapterNames(chapters);

                await _encodingManager.RefreshChapterImages(new ChapterImageRefreshOptions
                {
                    Chapters      = chapters,
                    Video         = video,
                    ExtractImages = chapterOptions.ExtractDuringLibraryScan,
                    SaveChapters  = false
                }, cancellationToken).ConfigureAwait(false);

                await _chapterManager.SaveChapters(video.Id.ToString(), chapters, cancellationToken).ConfigureAwait(false);
            }
        }
Example #18
0
 /// <inheritdoc/>
 public Task RefreshFullItem(BaseItem item, MetadataRefreshOptions options, CancellationToken cancellationToken)
 {
     return(RefreshItem(item, options, cancellationToken));
 }
 public override bool RefreshMetadata(MetadataRefreshOptions options)
 {
     // do nothing, metadata is assigned external to the provider framework
     return false;
 }
Example #20
0
        protected virtual async Task <RefreshResult> RefreshWithProviders(
            MetadataResult <TItemType> metadata,
            TIdType id,
            MetadataRefreshOptions options,
            ICollection <IMetadataProvider> providers,
            ItemImageProvider imageService,
            CancellationToken cancellationToken)
        {
            var refreshResult = new RefreshResult
            {
                UpdateType = ItemUpdateType.None
            };

            var item = metadata.Item;

            var customProviders = providers.OfType <ICustomMetadataProvider <TItemType> >().ToList();
            var logName         = !item.IsFileProtocol ? item.Name ?? item.Path : item.Path ?? item.Name;

            foreach (var provider in customProviders.Where(i => i is IPreRefreshProvider))
            {
                await RunCustomProvider(provider, item, logName, options, refreshResult, cancellationToken).ConfigureAwait(false);
            }

            var temp = new MetadataResult <TItemType>
            {
                Item = CreateNew()
            };

            temp.Item.Path = item.Path;

            var userDataList = new List <UserItemData>();

            // If replacing all metadata, run internet providers first
            if (options.ReplaceAllMetadata)
            {
                var remoteResult = await ExecuteRemoteProviders(temp, logName, id, providers.OfType <IRemoteMetadataProvider <TItemType, TIdType> >(), cancellationToken)
                                   .ConfigureAwait(false);

                refreshResult.UpdateType  |= remoteResult.UpdateType;
                refreshResult.ErrorMessage = remoteResult.ErrorMessage;
                refreshResult.Failures    += remoteResult.Failures;
            }

            var hasLocalMetadata = false;

            foreach (var provider in providers.OfType <ILocalMetadataProvider <TItemType> >().ToList())
            {
                var providerName = provider.GetType().Name;
                Logger.LogDebug("Running {Provider} for {Item}", providerName, logName);

                var itemInfo = new ItemInfo(item);

                try
                {
                    var localItem = await provider.GetMetadata(itemInfo, options.DirectoryService, cancellationToken).ConfigureAwait(false);

                    if (localItem.HasMetadata)
                    {
                        foreach (var remoteImage in localItem.RemoteImages)
                        {
                            try
                            {
                                await ProviderManager.SaveImage(item, remoteImage.Url, remoteImage.Type, null, cancellationToken).ConfigureAwait(false);

                                refreshResult.UpdateType |= ItemUpdateType.ImageUpdate;
                            }
                            catch (HttpRequestException ex)
                            {
                                Logger.LogError(ex, "Could not save {ImageType} image: {Url}", Enum.GetName(remoteImage.Type), remoteImage.Url);
                            }
                        }

                        if (imageService.MergeImages(item, localItem.Images))
                        {
                            refreshResult.UpdateType |= ItemUpdateType.ImageUpdate;
                        }

                        if (localItem.UserDataList != null)
                        {
                            userDataList.AddRange(localItem.UserDataList);
                        }

                        MergeData(localItem, temp, Array.Empty <MetadataField>(), !options.ReplaceAllMetadata, true);
                        refreshResult.UpdateType |= ItemUpdateType.MetadataImport;

                        // Only one local provider allowed per item
                        if (item.IsLocked || localItem.Item.IsLocked || IsFullLocalMetadata(localItem.Item))
                        {
                            hasLocalMetadata = true;
                        }

                        break;
                    }

                    Logger.LogDebug("{Provider} returned no metadata for {Item}", providerName, logName);
                }
                catch (OperationCanceledException)
                {
                    throw;
                }
                catch (Exception ex)
                {
                    Logger.LogError(ex, "Error in {Provider}", provider.Name);

                    // If a local provider fails, consider that a failure
                    refreshResult.ErrorMessage = ex.Message;
                }
            }

            // Local metadata is king - if any is found don't run remote providers
            if (!options.ReplaceAllMetadata && (!hasLocalMetadata || options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || !item.StopRefreshIfLocalMetadataFound))
            {
                var remoteResult = await ExecuteRemoteProviders(temp, logName, id, providers.OfType <IRemoteMetadataProvider <TItemType, TIdType> >(), cancellationToken)
                                   .ConfigureAwait(false);

                refreshResult.UpdateType  |= remoteResult.UpdateType;
                refreshResult.ErrorMessage = remoteResult.ErrorMessage;
                refreshResult.Failures    += remoteResult.Failures;
            }

            if (providers.Any(i => i is not ICustomMetadataProvider))
            {
                if (refreshResult.UpdateType > ItemUpdateType.None)
                {
                    if (hasLocalMetadata)
                    {
                        MergeData(temp, metadata, item.LockedFields, true, true);
                    }
                    else
                    {
                        if (!options.RemoveOldMetadata)
                        {
                            MergeData(metadata, temp, Array.Empty <MetadataField>(), false, false);
                        }

                        MergeData(temp, metadata, item.LockedFields, true, false);
                    }
                }
            }

            // var isUnidentified = failedProviderCount > 0 && successfulProviderCount == 0;

            foreach (var provider in customProviders.Where(i => i is not IPreRefreshProvider))
            {
                await RunCustomProvider(provider, item, logName, options, refreshResult, cancellationToken).ConfigureAwait(false);
            }

            // ImportUserData(item, userDataList, cancellationToken);

            return(refreshResult);
        }
Example #21
0
 public Task <ItemUpdateType> FetchAsync(LiveTvAudioRecording item, MetadataRefreshOptions options, CancellationToken cancellationToken)
 {
     return(FetchAudioInfo(item, cancellationToken));
 }
Example #22
0
        public async Task <ItemUpdateType> RefreshMetadata(BaseItem item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken)
        {
            var itemOfType = (TItemType)item;

            var updateType = ItemUpdateType.None;

            var libraryOptions = LibraryManager.GetLibraryOptions(item);

            var requiresRefresh = libraryOptions.AutomaticRefreshIntervalDays > 0 && (DateTime.UtcNow - item.DateLastRefreshed).TotalDays >= libraryOptions.AutomaticRefreshIntervalDays;

            if (!requiresRefresh && refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None)
            {
                // TODO: If this returns true, should we instead just change metadata refresh mode to Full?
                requiresRefresh = item.RequiresRefresh();

                if (requiresRefresh)
                {
                    Logger.LogDebug("Refreshing {Type} {Item} because item.RequiresRefresh() returned true", typeof(TItemType).Name, item.Path ?? item.Name);
                }
            }

            var localImagesFailed = false;

            var allImageProviders = ((ProviderManager)ProviderManager).GetImageProviders(item, refreshOptions).ToList();

            if (refreshOptions.RemoveOldMetadata && refreshOptions.ReplaceAllImages)
            {
                if (ImageProvider.RemoveImages(item))
                {
                    updateType |= ItemUpdateType.ImageUpdate;
                }
            }

            // Start by validating images
            try
            {
                // Always validate images and check for new locally stored ones.
                if (ImageProvider.ValidateImages(item, allImageProviders.OfType <ILocalImageProvider>(), refreshOptions.DirectoryService))
                {
                    updateType |= ItemUpdateType.ImageUpdate;
                }
            }
            catch (Exception ex)
            {
                localImagesFailed = true;
                Logger.LogError(ex, "Error validating images for {Item}", item.Path ?? item.Name ?? "Unknown name");
            }

            var metadataResult = new MetadataResult <TItemType>
            {
                Item = itemOfType
            };

            bool hasRefreshedMetadata = true;
            bool hasRefreshedImages   = true;
            var  isFirstRefresh       = item.DateLastRefreshed == default;

            // Next run metadata providers
            if (refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None)
            {
                var providers = GetProviders(item, libraryOptions, refreshOptions, isFirstRefresh, requiresRefresh)
                                .ToList();

                if (providers.Count > 0 || isFirstRefresh || requiresRefresh)
                {
                    if (item.BeforeMetadataRefresh(refreshOptions.ReplaceAllMetadata))
                    {
                        updateType |= ItemUpdateType.MetadataImport;
                    }
                }

                if (providers.Count > 0)
                {
                    var id = itemOfType.GetLookupInfo();

                    if (refreshOptions.SearchResult != null)
                    {
                        ApplySearchResult(id, refreshOptions.SearchResult);
                    }

                    // await FindIdentities(id, cancellationToken).ConfigureAwait(false);
                    id.IsAutomated = refreshOptions.IsAutomated;

                    var result = await RefreshWithProviders(metadataResult, id, refreshOptions, providers, ImageProvider, cancellationToken).ConfigureAwait(false);

                    updateType |= result.UpdateType;
                    if (result.Failures > 0)
                    {
                        hasRefreshedMetadata = false;
                    }
                }
            }

            // Next run remote image providers, but only if local image providers didn't throw an exception
            if (!localImagesFailed && refreshOptions.ImageRefreshMode != MetadataRefreshMode.ValidationOnly)
            {
                var providers = GetNonLocalImageProviders(item, allImageProviders, refreshOptions).ToList();

                if (providers.Count > 0)
                {
                    var result = await ImageProvider.RefreshImages(itemOfType, libraryOptions, providers, refreshOptions, cancellationToken).ConfigureAwait(false);

                    updateType |= result.UpdateType;
                    if (result.Failures > 0)
                    {
                        hasRefreshedImages = false;
                    }
                }
            }

            var beforeSaveResult = BeforeSave(itemOfType, isFirstRefresh || refreshOptions.ReplaceAllMetadata || refreshOptions.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || requiresRefresh || refreshOptions.ForceSave, updateType);

            updateType |= beforeSaveResult;

            // Save if changes were made, or it's never been saved before
            if (refreshOptions.ForceSave || updateType > ItemUpdateType.None || isFirstRefresh || refreshOptions.ReplaceAllMetadata || requiresRefresh)
            {
                if (item.IsFileProtocol)
                {
                    var file = TryGetFile(item.Path, refreshOptions.DirectoryService);
                    if (file != null)
                    {
                        item.DateModified = file.LastWriteTimeUtc;
                    }
                }

                // If any of these properties are set then make sure the updateType is not None, just to force everything to save
                if (refreshOptions.ForceSave || refreshOptions.ReplaceAllMetadata)
                {
                    updateType |= ItemUpdateType.MetadataDownload;
                }

                if (hasRefreshedMetadata && hasRefreshedImages)
                {
                    item.DateLastRefreshed = DateTime.UtcNow;
                }
                else
                {
                    item.DateLastRefreshed = default;
                }

                // Save to database
                await SaveItemAsync(metadataResult, updateType, cancellationToken).ConfigureAwait(false);
            }

            await AfterMetadataRefresh(itemOfType, refreshOptions, cancellationToken).ConfigureAwait(false);

            return(updateType);
        }
Example #23
0
        private void AddToCollection(Guid collectionId, IEnumerable <string> ids, bool fireEvent, MetadataRefreshOptions refreshOptions)
        {
            var collection = _libraryManager.GetItemById(collectionId) as BoxSet;

            if (collection == null)
            {
                throw new ArgumentException("No collection exists with the supplied Id");
            }

            var list     = new List <LinkedChild>();
            var itemList = new List <BaseItem>();

            var linkedChildrenList       = collection.GetLinkedChildren();
            var currentLinkedChildrenIds = linkedChildrenList.Select(i => i.Id).ToList();

            foreach (var id in ids)
            {
                var guidId = new Guid(id);
                var item   = _libraryManager.GetItemById(guidId);

                if (item == null)
                {
                    throw new ArgumentException("No item exists with the supplied Id");
                }

                if (!currentLinkedChildrenIds.Contains(guidId))
                {
                    itemList.Add(item);

                    list.Add(LinkedChild.Create(item));
                    linkedChildrenList.Add(item);
                }
            }

            if (list.Count > 0)
            {
                var newList = collection.LinkedChildren.ToList();
                newList.AddRange(list);
                collection.LinkedChildren = newList.ToArray();

                collection.UpdateRatingToItems(linkedChildrenList);

                collection.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);

                refreshOptions.ForceSave = true;
                _providerManager.QueueRefresh(collection.Id, refreshOptions, RefreshPriority.High);

                if (fireEvent)
                {
                    ItemsAddedToCollection?.Invoke(this, new CollectionModifiedEventArgs
                    {
                        Collection   = collection,
                        ItemsChanged = itemList
                    });
                }
            }
        }
Example #24
0
        protected async Task Fetch(Video video,
                                   CancellationToken cancellationToken,
                                   Model.MediaInfo.MediaInfo mediaInfo,
                                   IIsoMount isoMount,
                                   BlurayDiscInfo blurayInfo,
                                   MetadataRefreshOptions options)
        {
            var mediaStreams = mediaInfo.MediaStreams;

            video.TotalBitrate = mediaInfo.Bitrate;
            //video.FormatName = (mediaInfo.Container ?? string.Empty)
            //    .Replace("matroska", "mkv", StringComparison.OrdinalIgnoreCase);

            // For dvd's this may not always be accurate, so don't set the runtime if the item already has one
            var needToSetRuntime = video.VideoType != VideoType.Dvd || video.RunTimeTicks == null || video.RunTimeTicks.Value == 0;

            if (needToSetRuntime)
            {
                video.RunTimeTicks = mediaInfo.RunTimeTicks;
            }

            if (video.VideoType == VideoType.VideoFile)
            {
                var extension = (Path.GetExtension(video.Path) ?? string.Empty).TrimStart('.');

                video.Container = extension;
            }
            else
            {
                video.Container = null;
            }

            var chapters = mediaInfo.Chapters ?? new List <ChapterInfo>();

            if (video.VideoType == VideoType.BluRay || (video.IsoType.HasValue && video.IsoType.Value == IsoType.BluRay))
            {
                FetchBdInfo(video, chapters, mediaStreams, blurayInfo);
            }

            await AddExternalSubtitles(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);

            FetchEmbeddedInfo(video, mediaInfo, options);
            await FetchPeople(video, mediaInfo, options).ConfigureAwait(false);

            video.IsHD = mediaStreams.Any(i => i.Type == MediaStreamType.Video && i.Width.HasValue && i.Width.Value >= 1270);

            var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);

            video.VideoBitRate            = videoStream == null ? null : videoStream.BitRate;
            video.DefaultVideoStreamIndex = videoStream == null ? (int?)null : videoStream.Index;

            video.HasSubtitles = mediaStreams.Any(i => i.Type == MediaStreamType.Subtitle);
            video.Timestamp    = mediaInfo.Timestamp;

            await _itemRepo.SaveMediaStreams(video.Id, mediaStreams, cancellationToken).ConfigureAwait(false);

            if (options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh ||
                options.MetadataRefreshMode == MetadataRefreshMode.Default)
            {
                var chapterOptions = _chapterManager.GetConfiguration();

                try
                {
                    var remoteChapters = await DownloadChapters(video, chapters, chapterOptions, cancellationToken).ConfigureAwait(false);

                    if (remoteChapters.Count > 0)
                    {
                        chapters = remoteChapters;
                    }
                }
                catch (Exception ex)
                {
                    _logger.ErrorException("Error downloading chapters", ex);
                }

                if (chapters.Count == 0 && mediaStreams.Any(i => i.Type == MediaStreamType.Video))
                {
                    AddDummyChapters(video, chapters);
                }

                NormalizeChapterNames(chapters);

                await _encodingManager.RefreshChapterImages(new ChapterImageRefreshOptions
                {
                    Chapters      = chapters,
                    Video         = video,
                    ExtractImages = chapterOptions.ExtractDuringLibraryScan,
                    SaveChapters  = false
                }, cancellationToken).ConfigureAwait(false);

                await _chapterManager.SaveChapters(video.Id.ToString(), chapters, cancellationToken).ConfigureAwait(false);
            }
        }
Example #25
0
 public Task <ItemUpdateType> FetchAsync(Movie item, MetadataRefreshOptions options, CancellationToken cancellationToken)
 {
     return(FetchVideoInfo(item, options, cancellationToken));
 }
Example #26
0
        public async Task <ItemUpdateType> ProbeVideo <T>(T item,
                                                          MetadataRefreshOptions options,
                                                          CancellationToken cancellationToken)
            where T : Video
        {
            if (item.IsArchive)
            {
                var ext = Path.GetExtension(item.Path) ?? string.Empty;
                item.Container = ext.TrimStart('.');
                return(ItemUpdateType.MetadataImport);
            }

            var isoMount = await MountIsoIfNeeded(item, cancellationToken).ConfigureAwait(false);

            BlurayDiscInfo blurayDiscInfo = null;

            try
            {
                if (item.VideoType == VideoType.BluRay || (item.IsoType.HasValue && item.IsoType == IsoType.BluRay))
                {
                    var inputPath = isoMount != null ? isoMount.MountedPath : item.Path;

                    blurayDiscInfo = GetBDInfo(inputPath);
                }

                OnPreFetch(item, isoMount, blurayDiscInfo);

                // If we didn't find any satisfying the min length, just take them all
                if (item.VideoType == VideoType.Dvd || (item.IsoType.HasValue && item.IsoType == IsoType.Dvd))
                {
                    if (item.PlayableStreamFileNames.Count == 0)
                    {
                        _logger.Error("No playable vobs found in dvd structure, skipping ffprobe.");
                        return(ItemUpdateType.MetadataImport);
                    }
                }

                if (item.VideoType == VideoType.BluRay || (item.IsoType.HasValue && item.IsoType == IsoType.BluRay))
                {
                    if (item.PlayableStreamFileNames.Count == 0)
                    {
                        _logger.Error("No playable vobs found in bluray structure, skipping ffprobe.");
                        return(ItemUpdateType.MetadataImport);
                    }
                }

                var result = await GetMediaInfo(item, isoMount, cancellationToken).ConfigureAwait(false);

                cancellationToken.ThrowIfCancellationRequested();

                await Fetch(item, cancellationToken, result, isoMount, blurayDiscInfo, options).ConfigureAwait(false);
            }
            finally
            {
                if (isoMount != null)
                {
                    isoMount.Dispose();
                }
            }

            return(ItemUpdateType.MetadataImport);
        }
Example #27
0
        /// <summary>
        /// Compare our current children (presumably just read from the repo) with the current state of the file system and adjust for any changes
        /// ***Currently does not contain logic to maintain items that are unavailable in the file system***
        /// </summary>
        /// <param name="progress">The progress.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <param name="recursive">if set to <c>true</c> [recursive].</param>
        /// <param name="refreshChildMetadata">if set to <c>true</c> [refresh child metadata].</param>
        /// <param name="refreshOptions">The refresh options.</param>
        /// <param name="directoryService">The directory service.</param>
        /// <returns>Task.</returns>
        protected override Task ValidateChildrenInternal(IProgress <double> progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService)
        {
            var list = PhysicalLocationsList.ToList();

            CreateResolveArgs(directoryService);

            if (!list.SequenceEqual(PhysicalLocationsList))
            {
                return(UpdateToRepository(ItemUpdateType.MetadataImport, cancellationToken));
            }

            return(Task.FromResult(true));
        }
        protected override async Task ValidateChildrenInternal(IProgress <double> progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService)
        {
            ClearCache();

            await base.ValidateChildrenInternal(progress, cancellationToken, recursive, refreshChildMetadata, refreshOptions, directoryService)
            .ConfigureAwait(false);

            ClearCache();
        }
Example #29
0
        /// <summary>
        /// Gets the providers.
        /// </summary>
        /// <param name="item">The item.</param>
        /// <param name="status">The status.</param>
        /// <param name="options">The options.</param>
        /// <returns>IEnumerable{`0}.</returns>
        protected IEnumerable <IMetadataProvider> GetProviders(IHasMetadata item, MetadataStatus status, MetadataRefreshOptions options)
        {
            // Get providers to refresh
            var providers = ((ProviderManager)ProviderManager).GetMetadataProviders <TItemType>(item).ToList();

            // Run all if either of these flags are true
            var runAllProviders = options.ReplaceAllMetadata || options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || !status.DateLastMetadataRefresh.HasValue;

            if (!runAllProviders)
            {
                // Avoid implicitly captured closure
                var currentItem = item;

                var providersWithChanges = providers
                                           .Where(i =>
                {
                    var hasChangeMonitor = i as IHasChangeMonitor;
                    if (hasChangeMonitor != null)
                    {
                        return(HasChanged(item, hasChangeMonitor, currentItem.DateLastSaved, options.DirectoryService));
                    }

                    var hasFileChangeMonitor = i as IHasItemChangeMonitor;
                    if (hasFileChangeMonitor != null)
                    {
                        return(HasChanged(item, hasFileChangeMonitor, status, options.DirectoryService));
                    }

                    return(false);
                })
                                           .ToList();

                if (providersWithChanges.Count == 0)
                {
                    providers = new List <IMetadataProvider <TItemType> >();
                }
                else
                {
                    providers = providers.Where(i =>
                    {
                        // If any provider reports a change, always run local ones as well
                        if (i is ILocalMetadataProvider)
                        {
                            return(true);
                        }

                        var anyRemoteProvidersChanged = providersWithChanges.OfType <IRemoteMetadataProvider>()
                                                        .Any();

                        // If any remote providers changed, run them all so that priorities can be honored
                        if (i is IRemoteMetadataProvider)
                        {
                            return(anyRemoteProvidersChanged);
                        }

                        // Run custom providers if they report a change or any remote providers change
                        return(anyRemoteProvidersChanged || providersWithChanges.Contains(i));
                    }).ToList();
                }
            }

            return(providers);
        }
Example #30
0
 /// <summary>
 /// Validates that the children of the folder still exist
 /// </summary>
 /// <param name="progress">The progress.</param>
 /// <param name="cancellationToken">The cancellation token.</param>
 /// <param name="metadataRefreshOptions">The metadata refresh options.</param>
 /// <param name="recursive">if set to <c>true</c> [recursive].</param>
 /// <returns>Task.</returns>
 public Task ValidateChildren(IProgress <double> progress, CancellationToken cancellationToken, MetadataRefreshOptions metadataRefreshOptions, bool recursive = true)
 {
     return(ValidateChildrenInternal(progress, cancellationToken, recursive, true, metadataRefreshOptions, metadataRefreshOptions.DirectoryService));
 }
Example #31
0
        protected virtual async Task <RefreshResult> RefreshWithProviders(MetadataResult <TItemType> metadata,
                                                                          TIdType id,
                                                                          MetadataRefreshOptions options,
                                                                          List <IMetadataProvider> providers,
                                                                          ItemImageProvider imageService,
                                                                          CancellationToken cancellationToken)
        {
            var refreshResult = new RefreshResult
            {
                UpdateType = ItemUpdateType.None,
                Providers  = providers.Select(i => i.GetType().FullName.GetMD5()).ToList()
            };

            var item = metadata.Item;

            var customProviders = providers.OfType <ICustomMetadataProvider <TItemType> >().ToList();
            var logName         = item.LocationType == LocationType.Remote ? item.Name ?? item.Path : item.Path ?? item.Name;

            foreach (var provider in customProviders.Where(i => i is IPreRefreshProvider))
            {
                await RunCustomProvider(provider, item, logName, options, refreshResult, cancellationToken).ConfigureAwait(false);
            }

            var temp = new MetadataResult <TItemType>
            {
                Item = CreateNew()
            };

            temp.Item.Path = item.Path;
            var successfulProviderCount = 0;
            var failedProviderCount     = 0;

            var userDataList = new List <UserItemData>();

            // If replacing all metadata, run internet providers first
            if (options.ReplaceAllMetadata)
            {
                var remoteResult = await ExecuteRemoteProviders(temp, logName, id, providers.OfType <IRemoteMetadataProvider <TItemType, TIdType> >(), cancellationToken)
                                   .ConfigureAwait(false);

                refreshResult.UpdateType   = refreshResult.UpdateType | remoteResult.UpdateType;
                refreshResult.Status       = remoteResult.Status;
                refreshResult.ErrorMessage = remoteResult.ErrorMessage;
                successfulProviderCount   += remoteResult.Successes;
                failedProviderCount       += remoteResult.Failures;
            }

            var hasLocalMetadata = false;

            foreach (var provider in providers.OfType <ILocalMetadataProvider <TItemType> >().ToList())
            {
                var providerName = provider.GetType().Name;
                Logger.Debug("Running {0} for {1}", providerName, logName);

                var itemInfo = new ItemInfo(item);

                try
                {
                    var localItem = await provider.GetMetadata(itemInfo, options.DirectoryService, cancellationToken).ConfigureAwait(false);

                    if (localItem.HasMetadata)
                    {
                        if (imageService.MergeImages(item, localItem.Images))
                        {
                            refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.ImageUpdate;
                        }

                        if (localItem.UserDataList != null)
                        {
                            userDataList.AddRange(localItem.UserDataList);
                        }

                        MergeData(localItem, temp, new List <MetadataFields>(), !options.ReplaceAllMetadata, true);
                        refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.MetadataImport;

                        // Only one local provider allowed per item
                        if (IsFullLocalMetadata(localItem.Item))
                        {
                            hasLocalMetadata = true;
                        }
                        successfulProviderCount++;
                        break;
                    }

                    Logger.Debug("{0} returned no metadata for {1}", providerName, logName);
                }
                catch (OperationCanceledException)
                {
                    throw;
                }
                catch (Exception ex)
                {
                    failedProviderCount++;

                    Logger.ErrorException("Error in {0}", ex, provider.Name);

                    // If a local provider fails, consider that a failure
                    refreshResult.Status       = ProviderRefreshStatus.Failure;
                    refreshResult.ErrorMessage = ex.Message;

                    if (options.MetadataRefreshMode != MetadataRefreshMode.FullRefresh)
                    {
                        // If the local provider fails don't continue with remote providers because the user's saved metadata could be lost
                        return(refreshResult);
                    }
                }
            }

            // Local metadata is king - if any is found don't run remote providers
            if (!options.ReplaceAllMetadata && (!hasLocalMetadata || options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh))
            {
                var remoteResult = await ExecuteRemoteProviders(temp, logName, id, providers.OfType <IRemoteMetadataProvider <TItemType, TIdType> >(), cancellationToken)
                                   .ConfigureAwait(false);

                refreshResult.UpdateType = refreshResult.UpdateType | remoteResult.UpdateType;
                if (remoteResult.Status != ProviderRefreshStatus.Success)
                {
                    refreshResult.Status       = remoteResult.Status;
                    refreshResult.ErrorMessage = remoteResult.ErrorMessage;
                }
                successfulProviderCount += remoteResult.Successes;
            }

            if (providers.Any(i => !(i is ICustomMetadataProvider)))
            {
                if (refreshResult.UpdateType > ItemUpdateType.None)
                {
                    // If no local metadata, take data from item itself
                    if (!hasLocalMetadata)
                    {
                        // TODO: If the new metadata from above has some blank data, this can cause old data to get filled into those empty fields
                        MergeData(metadata, temp, new List <MetadataFields>(), false, true);
                    }

                    MergeData(temp, metadata, item.LockedFields, true, true);
                }
            }

            var isUnidentified = failedProviderCount > 0 && successfulProviderCount == 0;

            if (item.IsUnidentified != isUnidentified)
            {
                item.IsUnidentified      = isUnidentified;
                refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.MetadataImport;
            }

            foreach (var provider in customProviders.Where(i => !(i is IPreRefreshProvider)))
            {
                await RunCustomProvider(provider, item, logName, options, refreshResult, cancellationToken).ConfigureAwait(false);
            }

            await ImportUserData(item, userDataList, cancellationToken).ConfigureAwait(false);

            return(refreshResult);
        }
Example #32
0
        protected override Task ValidateChildrenInternal(IProgress <double> progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService)
        {
            if (IsAccessedByName)
            {
                // Should never get in here anyway
                return(_cachedTask);
            }

            return(base.ValidateChildrenInternal(progress, cancellationToken, recursive, refreshChildMetadata, refreshOptions, directoryService));
        }
Example #33
0
        public async Task <ItemUpdateType> RefreshMetadata(IHasMetadata item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken)
        {
            var itemOfType = (TItemType)item;
            var config     = ProviderManager.GetMetadataOptions(item);

            var updateType    = ItemUpdateType.None;
            var refreshResult = GetLastResult(item);

            refreshResult.LastErrorMessage = string.Empty;
            refreshResult.LastStatus       = ProviderRefreshStatus.Success;

            var itemImageProvider = new ItemImageProvider(Logger, ProviderManager, ServerConfigurationManager, FileSystem);
            var localImagesFailed = false;

            var allImageProviders = ((ProviderManager)ProviderManager).GetImageProviders(item).ToList();

            // Start by validating images
            try
            {
                // Always validate images and check for new locally stored ones.
                if (itemImageProvider.ValidateImages(item, allImageProviders.OfType <ILocalImageProvider>(), refreshOptions.DirectoryService))
                {
                    updateType = updateType | ItemUpdateType.ImageUpdate;
                }
            }
            catch (Exception ex)
            {
                localImagesFailed = true;
                Logger.ErrorException("Error validating images for {0}", ex, item.Path ?? item.Name ?? "Unknown name");
                refreshResult.AddStatus(ProviderRefreshStatus.Failure, ex.Message);
            }

            var metadataResult = new MetadataResult <TItemType>
            {
                Item = itemOfType
            };

            // Next run metadata providers
            if (refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None)
            {
                var providers = GetProviders(item, refreshResult, refreshOptions)
                                .ToList();

                if (providers.Count > 0 || !refreshResult.DateLastMetadataRefresh.HasValue)
                {
                    if (item.BeforeMetadataRefresh())
                    {
                        updateType = updateType | ItemUpdateType.MetadataImport;
                    }
                }

                if (providers.Count > 0)
                {
                    var id = await CreateInitialLookupInfo(itemOfType, cancellationToken).ConfigureAwait(false);

                    var result = await RefreshWithProviders(metadataResult, id, refreshOptions, providers, itemImageProvider, cancellationToken).ConfigureAwait(false);

                    updateType = updateType | result.UpdateType;
                    refreshResult.AddStatus(result.Status, result.ErrorMessage);
                    refreshResult.SetDateLastMetadataRefresh(DateTime.UtcNow);

                    MergeIdentities(itemOfType, id);
                }
            }

            // Next run remote image providers, but only if local image providers didn't throw an exception
            if (!localImagesFailed && refreshOptions.ImageRefreshMode != ImageRefreshMode.ValidationOnly)
            {
                var providers = GetNonLocalImageProviders(item, allImageProviders, refreshResult, refreshOptions).ToList();

                if (providers.Count > 0)
                {
                    var result = await itemImageProvider.RefreshImages(itemOfType, providers, refreshOptions, config, cancellationToken).ConfigureAwait(false);

                    updateType = updateType | result.UpdateType;
                    refreshResult.AddStatus(result.Status, result.ErrorMessage);
                    refreshResult.SetDateLastImagesRefresh(DateTime.UtcNow);
                }
            }

            var beforeSaveResult = await BeforeSave(itemOfType, item.DateLastSaved == default(DateTime) || refreshOptions.ReplaceAllMetadata || refreshOptions.MetadataRefreshMode == MetadataRefreshMode.FullRefresh, updateType).ConfigureAwait(false);

            updateType = updateType | beforeSaveResult;

            // Save if changes were made, or it's never been saved before
            if (refreshOptions.ForceSave || updateType > ItemUpdateType.None || item.DateLastSaved == default(DateTime) || refreshOptions.ReplaceAllMetadata)
            {
                // If any of these properties are set then make sure the updateType is not None, just to force everything to save
                if (refreshOptions.ForceSave || refreshOptions.ReplaceAllMetadata)
                {
                    updateType = updateType | ItemUpdateType.MetadataDownload;
                }

                // Save to database
                await SaveItem(metadataResult, updateType, cancellationToken).ConfigureAwait(false);
            }

            if (updateType > ItemUpdateType.None || refreshResult.IsDirty)
            {
                await SaveProviderResult(itemOfType, refreshResult, refreshOptions.DirectoryService).ConfigureAwait(false);
            }

            await AfterMetadataRefresh(itemOfType, refreshOptions, cancellationToken).ConfigureAwait(false);

            return(updateType);
        }
Example #34
0
        public override bool RefreshMetadata(MetadataRefreshOptions options)
        {
            bool changed = false;
            string path = Plugin.proxy == null ? this.Path : Plugin.proxy.ProxyUrl(this);
            if (this.Path != path)
            {
                this.Path = path;
                changed = true;
            }

            MediaType = MediaTypeResolver.DetermineType(Path);

            if ((options & MetadataRefreshOptions.FastOnly) != MetadataRefreshOptions.FastOnly &&
                Plugin.PluginOptions.Instance.FetchBackdrops && string.IsNullOrEmpty(this.BackdropImagePath))
            {
                if (this.BackdropImagePath == null)
                {
                    // use our own movieDBProvider to grab just the backdrops
                    var provider = new BackdropProvider();
                    provider.Item = (Movie)Serializer.Clone(this);
                    provider.Fetch();
                    this.BackdropImagePaths = provider.Item.BackdropImagePaths;
                    foreach (var image in this.BackdropImages)
                    {
                        try
                        {
                            if (image != null)
                            {
                                image.ClearLocalImages();
                                MediaBrowser.Library.Factories.LibraryImageFactory.Instance.ClearCache(image.Path);
                                var ignore = image.GetLocalImagePath();
                            }
                        }
                        catch (Exception ex)
                        {
                            MediaBrowser.Library.Logging.Logger.ReportException("Failed to clear local image (its probably in use)", ex);
                        }
                    }
                    changed = true;
                }

            }

            if ((options & MetadataRefreshOptions.Force) == MetadataRefreshOptions.Force)
            {
                //force images to refresh
                var images = new List<MediaBrowser.Library.ImageManagement.LibraryImage>();
                images.Add(PrimaryImage);
                images.Add(SecondaryImage);
                images.Add(BannerImage);

                foreach (var image in images)
                {
                    try
                    {
                        if (image != null)
                        {
                            image.ClearLocalImages();
                            MediaBrowser.Library.Factories.LibraryImageFactory.Instance.ClearCache(image.Path);
                        }
                    }
                    catch (Exception ex)
                    {
                        MediaBrowser.Library.Logging.Logger.ReportException("Failed to clear local image (its probably in use)", ex);
                    }
                }
                changed = true;
            }
            if (changed) MediaBrowser.Library.Kernel.Instance.ItemRepository.SaveItem(this);
            return changed;
        }
Example #35
0
 protected virtual Task AfterMetadataRefresh(TItemType item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken)
 {
     item.AfterMetadataRefresh();
     return(Task.CompletedTask);
 }
Example #36
0
        bool FullRefresh(AggregateFolder folder, MetadataRefreshOptions options, ServiceRefreshOptions manualOptions)
        {
            int phases = manualOptions.AnyImageOptionsSelected || manualOptions.MigrateOption ? 3 : 2;
            double totalIterations = 0;
            UpdateProgress("Determining Library Size", 0);
            //this will trap any circular references in the library tree
            try
            {
                Async.RunWithTimeout(() => { totalIterations = folder.AllRecursiveChildren.Count() * phases; }, 600000);
            }
            catch (TimeoutException)
            {
                Logger.ReportError("ERROR DURING REFRESH.  Timed out attempting to retrieve count of all items.  Most likely there is a circular reference in your library.  Look for *.lnk files that might be pointing back to a parent of the folder that contatians that link.");
                _config.RefreshFailed = true;
                _config.Save();
                return false;
            }

            if (totalIterations == 0) return true; //nothing to do

            int currentIteration = 0;

            UpdateProgress("Validating Root", 0);

            folder.RefreshMetadata(options);

            using (new Profiler(Kernel.Instance.GetString("FullValidationProf")))
            {
                if (!RunActionRecursively(folder, item =>
                {
                    currentIteration++;
                    UpdateProgress("Validating",currentIteration / totalIterations);
                    var f = item as Folder;
                    if (f != null)
                    {
                        f.ValidateChildren();
                    }

                })) return false;
            }

            string msg = (options & MetadataRefreshOptions.FastOnly) == MetadataRefreshOptions.FastOnly ?
                "Not allowing internet and other slow providers." :
                "Allowing internet and other slow providers.";
            Logger.ReportInfo(msg);
            var processedItems = new HashSet<Guid>();
            using (new Profiler(Kernel.Instance.GetString("SlowRefresh")))
            {
                if (!RunActionRecursively(folder, item =>
                {
                    currentIteration++;
                    UpdateProgress("All Metadata",(currentIteration / totalIterations));
                    if (!processedItems.Contains(item.Id)) //only process any given item once (could be multiple refs to same item)
                    {
                        if (manualOptions.MigrateOption)
                        {
                            MigratePlayState(item);
                        }
                        item.RefreshMetadata(options);
                        processedItems.Add(item.Id);
                        //Logger.ReportInfo(item.Name + " id: " + item.Id);
                        //test
                        //throw new InvalidOperationException("Test Error...");
                    }
                })) return false;
            }

            if (manualOptions.AnyImageOptionsSelected || manualOptions.MigrateOption)
            {
                processedItems.Clear();
                using (new Profiler(Kernel.Instance.GetString("ImageRefresh")))
                {
                    var studiosProcessed = new List<string>();
                    var genresProcessed = new List<string>();
                    var peopleProcessed = new List<string>();
                    var yearsProcessed = new List<string>();

                    if (!RunActionRecursively(folder, item =>
                    {
                        currentIteration++;
                        UpdateProgress("Images",(currentIteration / totalIterations));
                        if (!processedItems.Contains(item.Id))
                        {
                            if (manualOptions.IncludeImagesOption) //main images
                            {
                                Logger.ReportInfo("Caching all images for " + item.Name );
                                item.ReCacheAllImages();
                            }
                            if (manualOptions.MigrateOption) //migrate main images
                            {
                                item.MigrateAllImages();
                            }
                            // optionally cause genre, poeple, year and studio images to cache as well
                            if (item is Show)
                            {
                                var show = item as Show;
                                if ((manualOptions.IncludeGenresOption || manualOptions.MigrateOption) && show.Genres != null)
                                {
                                    foreach (var genre in show.Genres)
                                    {
                                        if (!genresProcessed.Contains(genre))
                                        {
                                            Genre g = Genre.GetGenre(genre);
                                            g.RefreshMetadata();
                                            if (g.PrimaryImage != null)
                                            {
                                                if (manualOptions.IncludeGenresOption)
                                                {
                                                    Logger.ReportInfo("Caching image for genre: " + genre);
                                                    g.PrimaryImage.ClearLocalImages();
                                                    g.PrimaryImage.GetLocalImagePath();
                                                }
                                                if (manualOptions.MigrateOption)
                                                {
                                                    Logger.ReportInfo("Migrating image for genre: " + genre);
                                                    g.PrimaryImage.MigrateFromOldID();
                                                }

                                            }
                                            foreach (MediaBrowser.Library.ImageManagement.LibraryImage image in g.BackdropImages)
                                            {
                                                if (manualOptions.IncludeGenresOption)
                                                {
                                                    image.GetLocalImagePath();
                                                }
                                                if (manualOptions.MigrateOption)
                                                {
                                                    image.MigrateFromOldID();
                                                }
                                            }
                                            genresProcessed.Add(genre);
                                        }
                                    }
                                }
                                if ((manualOptions.IncludeStudiosOption || manualOptions.MigrateOption) && show.Studios != null)
                                {
                                    foreach (var studio in show.Studios)
                                    {
                                        if (!studiosProcessed.Contains(studio))
                                        {
                                                Logger.ReportInfo("Caching image for studio: " + studio);
                                                Studio st = Studio.GetStudio(studio);
                                                st.RefreshMetadata();
                                                if (st.PrimaryImage != null)
                                                {
                                                    if (manualOptions.IncludeStudiosOption)
                                                    {
                                                        Logger.ReportInfo("Caching image for studio: " + studio);
                                                        st.PrimaryImage.ClearLocalImages();
                                                        st.PrimaryImage.GetLocalImagePath();
                                                    }
                                                    if (manualOptions.MigrateOption)
                                                    {
                                                        Logger.ReportInfo("Migrating image for studio: " + studio);
                                                        st.PrimaryImage.MigrateFromOldID();
                                                    }
                                                }

                                            foreach (MediaBrowser.Library.ImageManagement.LibraryImage image in st.BackdropImages)
                                            {
                                                if (manualOptions.IncludeStudiosOption)
                                                {
                                                    image.ClearLocalImages();
                                                    image.GetLocalImagePath();
                                                }
                                                if (manualOptions.MigrateOption)
                                                {
                                                    image.MigrateFromOldID();
                                                }
                                            }
                                            studiosProcessed.Add(studio);
                                        }
                                    }
                                }
                                if ((manualOptions.IncludePeopleOption || manualOptions.MigrateOption) && show.Actors != null)
                                {
                                    foreach (var actor in show.Actors)
                                    {
                                        if (!peopleProcessed.Contains(actor.Name))
                                        {
                                            Person p = Person.GetPerson(actor.Name);
                                            p.RefreshMetadata();
                                            if (p.PrimaryImage != null)
                                            {
                                                if (manualOptions.IncludePeopleOption)
                                                {
                                                    Logger.ReportInfo("Caching image for person: " + actor.Name);
                                                    p.PrimaryImage.ClearLocalImages();
                                                    p.PrimaryImage.GetLocalImagePath();
                                                }
                                                if (manualOptions.MigrateOption)
                                                {
                                                    Logger.ReportInfo("Migrating image for person: " + actor.Name);
                                                    p.PrimaryImage.MigrateFromOldID();
                                                }

                                            }
                                            foreach (MediaBrowser.Library.ImageManagement.LibraryImage image in p.BackdropImages)
                                            {
                                                if (manualOptions.IncludePeopleOption)
                                                {
                                                    image.ClearLocalImages();
                                                    image.GetLocalImagePath();
                                                }
                                                if (manualOptions.MigrateOption)
                                                {
                                                    image.MigrateFromOldID();
                                                }
                                            }
                                            peopleProcessed.Add(actor.Name);
                                        }
                                    }
                                }
                                if (manualOptions.IncludePeopleOption && show.Directors != null)
                                {
                                    foreach (var director in show.Directors)
                                    {
                                        if (!peopleProcessed.Contains(director))
                                        {
                                            Person p = Person.GetPerson(director);
                                            p.RefreshMetadata();
                                            if (p.PrimaryImage != null)
                                            {
                                                Logger.ReportInfo("Caching image for person: " + director);
                                                p.PrimaryImage.ClearLocalImages();
                                                p.PrimaryImage.GetLocalImagePath();
                                            }
                                            foreach (MediaBrowser.Library.ImageManagement.LibraryImage image in p.BackdropImages)
                                            {
                                                image.ClearLocalImages();
                                                image.GetLocalImagePath();
                                            }
                                            peopleProcessed.Add(director);
                                        }
                                    }
                                }
                                if (manualOptions.IncludeYearOption && show.ProductionYear != null)
                                {
                                    if (!yearsProcessed.Contains(show.ProductionYear.ToString()))
                                    {
                                        Year yr = Year.GetYear(show.ProductionYear.ToString());
                                        yr.RefreshMetadata();
                                        if (yr.PrimaryImage != null)
                                        {
                                            Logger.ReportInfo("Caching image for year: " + yr);
                                            yr.PrimaryImage.ClearLocalImages();
                                            yr.PrimaryImage.GetLocalImagePath();
                                        }
                                        foreach (MediaBrowser.Library.ImageManagement.LibraryImage image in yr.BackdropImages)
                                        {
                                            image.ClearLocalImages();
                                            image.GetLocalImagePath();
                                        }
                                        yearsProcessed.Add(show.ProductionYear.ToString());
                                    }

                                }
                            }
                            processedItems.Add(item.Id);
                        }
                        else Logger.ReportInfo("Not processing " + item.Name + " again.");
                    })) return false;
                }
            }
            return true;
        }
        static void FullRefresh(Folder folder, MetadataRefreshOptions options)
        {
            int totalItems = 0;
            int fastMetadataChanged = 0;
            int slowMetadataChanged = 0;
            folder.RefreshMetadata(options);
            Console.Out.WriteLine();
            Console.Out.WriteLine("===[Validate]============================================");
            Console.Out.WriteLine();
            var validationTime = TimeAction(() =>
            {
                RunActionRecursively("validate", folder, item =>
                {
                    Folder f = item as Folder;
                    if (f != null) f.ValidateChildren();
                });
            });
            Console.Out.WriteLine();
            Console.Out.WriteLine("===[Fast Metadata]=======================================");
            Console.Out.WriteLine();
            var fastMetadataTime = TimeAction(() =>
            {
                RunActionRecursively("fast metadata", folder, item => {
                    fastMetadataChanged += item.RefreshMetadata(MetadataRefreshOptions.FastOnly) ? 1 : 0;
                    totalItems++;

                });
            });
            if (rebuildImageCache)
            {
                Console.Out.WriteLine();
                Console.Out.WriteLine("===[Recreate ImageCache]=================================");
                Console.Out.WriteLine();
                Console.Out.WriteLine("/i specified - Clearing Image Cache for re-build..");
                Console.Out.WriteLine();
                try
                {
                    Console.Out.WriteLine("Deleting ImageCache folder.");
                    Directory.Delete(ApplicationPaths.AppImagePath, true);
                }
                catch (Exception ex)
                {
                    Console.Out.WriteLine("Error trying to delete ImageCache folder. " + ex.Message);
                }
                Console.Out.WriteLine("Sleeping 2 seconds.");
                System.Threading.Thread.Sleep(2000); // give it time to fully delete
                Console.Out.WriteLine("Continuing.");
                try
                {
                    Console.Out.WriteLine("Creating ImageCache folder.");
                    Directory.CreateDirectory(ApplicationPaths.AppImagePath);
                }
                catch (Exception ex)
                {
                    Console.Out.WriteLine("Error trying to create ImageCache folder. " + ex.Message);
                }
                Console.Out.WriteLine("Sleeping 2 seconds.");
                System.Threading.Thread.Sleep(2000); // give it time to fully create
                Console.Out.WriteLine("Continuing.");
            }
            Console.Out.WriteLine();
            Console.Out.WriteLine("===[Slow Metadata]=======================================");
            Console.Out.WriteLine();
            var slowMetadataTime = TimeAction(() =>
            {
                RunActionRecursively("slow metadata", folder, item =>
                {
                    slowMetadataChanged += item.RefreshMetadata(MetadataRefreshOptions.Default) ? 1 : 0;

                    if (rebuildImageCache)
                    {
                        //touch all the images - causing them to be re-cached
                        Console.Out.WriteLine("Caching images for " + item.Name);
                        string ignore = null;
                        if (item.PrimaryImage != null)
                            ignore = item.PrimaryImage.GetLocalImagePath();
                        if (item.SecondaryImage != null)
                            ignore = item.SecondaryImage.GetLocalImagePath();
                        if (item.BackdropImages != null)
                            foreach (var image in item.BackdropImages)
                            {
                                ignore = image.GetLocalImagePath();
                            }
                        if (item.BannerImage != null)
                            ignore = item.BannerImage.GetLocalImagePath();
                    }
                });
            });
            Console.Out.WriteLine();
            Console.Out.WriteLine("===[Saving LastFullRefresh]==============================");
            Console.Out.WriteLine();
            Console.Out.WriteLine("Saving LastFullRefresh in config");
            Kernel.Instance.ConfigData.LastFullRefresh = DateTime.Now;
            Kernel.Instance.ConfigData.Save();
            Console.Out.WriteLine();
            Console.Out.WriteLine("===[Results]=============================================");
            Console.Out.WriteLine();
            Console.Out.WriteLine("We are done");
            Console.Out.WriteLine();
            Console.Out.WriteLine("Validation took:              " + (new DateTime(validationTime.Ticks)).ToString("HH:mm:ss"));
            Console.Out.WriteLine("Fast metadata took:           " + (new DateTime(fastMetadataTime.Ticks)).ToString("HH:mm:ss"));
            Console.Out.WriteLine("Slow metadata took:           " + (new DateTime(slowMetadataTime.Ticks)).ToString("HH:mm:ss"));
            Console.Out.WriteLine("Total items in your library:  {0}", totalItems);
            Console.Out.WriteLine();
            Console.Out.WriteLine("Fast metadata changed on {0} item's", fastMetadataChanged);
            Console.Out.WriteLine("Slow metadata changed on {0} item's", slowMetadataChanged);
            Console.Out.WriteLine();
            Console.Out.WriteLine("===[EOF]==================================================");
        }