aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--MediaBrowser.Api/ApiService.cs2
-rw-r--r--MediaBrowser.Api/HttpHandlers/GenreHandler.cs2
-rw-r--r--MediaBrowser.Api/HttpHandlers/GenresHandler.cs2
-rw-r--r--MediaBrowser.Api/HttpHandlers/PersonHandler.cs2
-rw-r--r--MediaBrowser.Api/HttpHandlers/StudioHandler.cs2
-rw-r--r--MediaBrowser.Api/HttpHandlers/StudiosHandler.cs2
-rw-r--r--MediaBrowser.Api/HttpHandlers/YearHandler.cs2
-rw-r--r--MediaBrowser.Api/HttpHandlers/YearsHandler.cs2
-rw-r--r--MediaBrowser.Common/Extensions/BaseExtensions.cs63
-rw-r--r--MediaBrowser.Common/MediaBrowser.Common.csproj1
-rw-r--r--MediaBrowser.Common/MediaBrowser.Common.csproj.orig108
-rw-r--r--MediaBrowser.Controller/Entities/BaseEntity.cs69
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs37
-rw-r--r--MediaBrowser.Controller/Entities/Folder.cs363
-rw-r--r--MediaBrowser.Controller/Entities/TV/Season.cs8
-rw-r--r--MediaBrowser.Controller/IO/DirectoryWatchers.cs67
-rw-r--r--MediaBrowser.Controller/IO/FileSystemHelper.cs132
-rw-r--r--MediaBrowser.Controller/IO/Shortcut.cs2
-rw-r--r--MediaBrowser.Controller/Kernel.cs119
-rw-r--r--MediaBrowser.Controller/Library/ChildrenChangedEventArgs.cs34
-rw-r--r--MediaBrowser.Controller/Library/ItemController.cs161
-rw-r--r--MediaBrowser.Controller/Library/ItemResolveEventArgs.cs49
-rw-r--r--MediaBrowser.Controller/MediaBrowser.Controller.csproj4
-rw-r--r--MediaBrowser.Controller/Providers/BaseItemXmlParser.cs1
-rw-r--r--MediaBrowser.Controller/Providers/BaseMetadataProvider.cs63
-rw-r--r--MediaBrowser.Controller/Providers/BaseProviderInfo.cs15
-rw-r--r--MediaBrowser.Controller/Providers/Movies/MovieProviderFromXml.cs7
-rw-r--r--MediaBrowser.Controller/Resolvers/BaseItemResolver.cs3
-rw-r--r--MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs70
-rw-r--r--MediaBrowser.Controller/Resolvers/Movies/MovieResolver.cs31
-rw-r--r--MediaBrowser.Controller/Resolvers/TV/SeasonResolver.cs3
-rw-r--r--MediaBrowser.Controller/Resolvers/TV/TVUtils.cs2
-rw-r--r--MediaBrowser.Controller/Resolvers/VideoResolver.cs36
-rw-r--r--MediaBrowser.Model/Entities/VideoType.cs3
-rw-r--r--MediaBrowser.sln27
35 files changed, 1000 insertions, 494 deletions
diff --git a/MediaBrowser.Api/ApiService.cs b/MediaBrowser.Api/ApiService.cs
index 20f8ce29b..0fef1cb57 100644
--- a/MediaBrowser.Api/ApiService.cs
+++ b/MediaBrowser.Api/ApiService.cs
@@ -288,7 +288,7 @@ namespace MediaBrowser.Api
if (folder != null)
{
- IEnumerable<BaseItem> children = folder.GetParentalAllowedChildren(user);
+ IEnumerable<BaseItem> children = folder.GetChildren(user);
dto.Children = await Task.WhenAll(children.Select(c => GetDtoBaseItem(c, user, false, false))).ConfigureAwait(false);
}
diff --git a/MediaBrowser.Api/HttpHandlers/GenreHandler.cs b/MediaBrowser.Api/HttpHandlers/GenreHandler.cs
index 1b7e5cfd8..7cca2aea7 100644
--- a/MediaBrowser.Api/HttpHandlers/GenreHandler.cs
+++ b/MediaBrowser.Api/HttpHandlers/GenreHandler.cs
@@ -40,7 +40,7 @@ namespace MediaBrowser.Api.HttpHandlers
int count = 0;
// Get all the allowed recursive children
- IEnumerable<BaseItem> allItems = parent.GetParentalAllowedRecursiveChildren(user);
+ IEnumerable<BaseItem> allItems = parent.GetRecursiveChildren(user);
foreach (var item in allItems)
{
diff --git a/MediaBrowser.Api/HttpHandlers/GenresHandler.cs b/MediaBrowser.Api/HttpHandlers/GenresHandler.cs
index 6f84b2723..4c5a9f4b7 100644
--- a/MediaBrowser.Api/HttpHandlers/GenresHandler.cs
+++ b/MediaBrowser.Api/HttpHandlers/GenresHandler.cs
@@ -35,7 +35,7 @@ namespace MediaBrowser.Api.HttpHandlers
var data = new Dictionary<string, int>();
// Get all the allowed recursive children
- IEnumerable<BaseItem> allItems = parent.GetParentalAllowedRecursiveChildren(user);
+ IEnumerable<BaseItem> allItems = parent.GetRecursiveChildren(user);
foreach (var item in allItems)
{
diff --git a/MediaBrowser.Api/HttpHandlers/PersonHandler.cs b/MediaBrowser.Api/HttpHandlers/PersonHandler.cs
index 05d75b222..fbbd88a11 100644
--- a/MediaBrowser.Api/HttpHandlers/PersonHandler.cs
+++ b/MediaBrowser.Api/HttpHandlers/PersonHandler.cs
@@ -38,7 +38,7 @@ namespace MediaBrowser.Api.HttpHandlers
int count = 0;
// Get all the allowed recursive children
- IEnumerable<BaseItem> allItems = parent.GetParentalAllowedRecursiveChildren(user);
+ IEnumerable<BaseItem> allItems = parent.GetRecursiveChildren(user);
foreach (var item in allItems)
{
diff --git a/MediaBrowser.Api/HttpHandlers/StudioHandler.cs b/MediaBrowser.Api/HttpHandlers/StudioHandler.cs
index cc8f6623f..6576e2cfe 100644
--- a/MediaBrowser.Api/HttpHandlers/StudioHandler.cs
+++ b/MediaBrowser.Api/HttpHandlers/StudioHandler.cs
@@ -40,7 +40,7 @@ namespace MediaBrowser.Api.HttpHandlers
int count = 0;
// Get all the allowed recursive children
- IEnumerable<BaseItem> allItems = parent.GetParentalAllowedRecursiveChildren(user);
+ IEnumerable<BaseItem> allItems = parent.GetRecursiveChildren(user);
foreach (var item in allItems)
{
diff --git a/MediaBrowser.Api/HttpHandlers/StudiosHandler.cs b/MediaBrowser.Api/HttpHandlers/StudiosHandler.cs
index d6957011d..4377a0f43 100644
--- a/MediaBrowser.Api/HttpHandlers/StudiosHandler.cs
+++ b/MediaBrowser.Api/HttpHandlers/StudiosHandler.cs
@@ -35,7 +35,7 @@ namespace MediaBrowser.Api.HttpHandlers
var data = new Dictionary<string, int>();
// Get all the allowed recursive children
- IEnumerable<BaseItem> allItems = parent.GetParentalAllowedRecursiveChildren(user);
+ IEnumerable<BaseItem> allItems = parent.GetRecursiveChildren(user);
foreach (var item in allItems)
{
diff --git a/MediaBrowser.Api/HttpHandlers/YearHandler.cs b/MediaBrowser.Api/HttpHandlers/YearHandler.cs
index 38405f0c5..dbd1d25be 100644
--- a/MediaBrowser.Api/HttpHandlers/YearHandler.cs
+++ b/MediaBrowser.Api/HttpHandlers/YearHandler.cs
@@ -38,7 +38,7 @@ namespace MediaBrowser.Api.HttpHandlers
int count = 0;
// Get all the allowed recursive children
- IEnumerable<BaseItem> allItems = parent.GetParentalAllowedRecursiveChildren(user);
+ IEnumerable<BaseItem> allItems = parent.GetRecursiveChildren(user);
foreach (var item in allItems)
{
diff --git a/MediaBrowser.Api/HttpHandlers/YearsHandler.cs b/MediaBrowser.Api/HttpHandlers/YearsHandler.cs
index a3f668f4a..7c90768e8 100644
--- a/MediaBrowser.Api/HttpHandlers/YearsHandler.cs
+++ b/MediaBrowser.Api/HttpHandlers/YearsHandler.cs
@@ -35,7 +35,7 @@ namespace MediaBrowser.Api.HttpHandlers
var data = new Dictionary<int, int>();
// Get all the allowed recursive children
- IEnumerable<BaseItem> allItems = parent.GetParentalAllowedRecursiveChildren(user);
+ IEnumerable<BaseItem> allItems = parent.GetRecursiveChildren(user);
foreach (var item in allItems)
{
diff --git a/MediaBrowser.Common/Extensions/BaseExtensions.cs b/MediaBrowser.Common/Extensions/BaseExtensions.cs
new file mode 100644
index 000000000..77eb9fbb4
--- /dev/null
+++ b/MediaBrowser.Common/Extensions/BaseExtensions.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Security.Cryptography;
+
+namespace MediaBrowser.Common.Extensions
+{
+ public static class BaseExtensions
+ {
+ static MD5CryptoServiceProvider md5Provider = new MD5CryptoServiceProvider();
+
+ public static Guid GetMD5(this string str)
+ {
+ lock (md5Provider)
+ {
+ return new Guid(md5Provider.ComputeHash(Encoding.Unicode.GetBytes(str)));
+ }
+ }
+
+ /// <summary>
+ /// Examine a list of strings assumed to be file paths to see if it contains a parent of
+ /// the provided path.
+ /// </summary>
+ /// <param name="lst"></param>
+ /// <param name="path"></param>
+ /// <returns></returns>
+ public static bool ContainsParentFolder(this List<string> lst, string path)
+ {
+ path = path.TrimEnd('\\');
+ foreach (var str in lst)
+ {
+ //this should be a little quicker than examining each actual parent folder...
+ var compare = str.TrimEnd('\\');
+ if (path.Equals(compare,StringComparison.OrdinalIgnoreCase)
+ || (path.StartsWith(compare, StringComparison.OrdinalIgnoreCase) && path[compare.Length] == '\\')) return true;
+ }
+ return false;
+ }
+
+ /// <summary>
+ /// Helper method for Dictionaries since they throw on not-found keys
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <typeparam name="U"></typeparam>
+ /// <param name="dictionary"></param>
+ /// <param name="key"></param>
+ /// <param name="defaultValue"></param>
+ /// <returns></returns>
+ public static U GetValueOrDefault<T, U>(this Dictionary<T, U> dictionary, T key, U defaultValue)
+ {
+ U val;
+ if (!dictionary.TryGetValue(key, out val))
+ {
+ val = defaultValue;
+ }
+ return val;
+
+ }
+
+ }
+}
diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj
index 78d1fc98c..c08716614 100644
--- a/MediaBrowser.Common/MediaBrowser.Common.csproj
+++ b/MediaBrowser.Common/MediaBrowser.Common.csproj
@@ -81,6 +81,7 @@
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup>
+ <Compile Include="Extensions\BaseExtensions.cs" />
<Compile Include="Events\GenericEventArgs.cs" />
<Compile Include="Kernel\BaseApplicationPaths.cs" />
<Compile Include="Logging\BaseLogger.cs" />
diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj.orig b/MediaBrowser.Common/MediaBrowser.Common.csproj.orig
deleted file mode 100644
index 40e2094e6..000000000
--- a/MediaBrowser.Common/MediaBrowser.Common.csproj.orig
+++ /dev/null
@@ -1,108 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
- <PropertyGroup>
- <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
- <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
- <ProjectGuid>{9142EEFA-7570-41E1-BFCC-468BB571AF2F}</ProjectGuid>
- <OutputType>Library</OutputType>
- <AppDesignerFolder>Properties</AppDesignerFolder>
- <RootNamespace>MediaBrowser.Common</RootNamespace>
- <AssemblyName>MediaBrowser.Common</AssemblyName>
- <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
- <ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
- <FileAlignment>512</FileAlignment>
- </PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
- <DebugSymbols>true</DebugSymbols>
- <DebugType>full</DebugType>
- <Optimize>false</Optimize>
- <OutputPath>bin\Debug\</OutputPath>
- <DefineConstants>DEBUG;TRACE</DefineConstants>
- <ErrorReport>prompt</ErrorReport>
- <WarningLevel>4</WarningLevel>
- </PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
- <DebugType>pdbonly</DebugType>
- <Optimize>true</Optimize>
- <OutputPath>bin\Release\</OutputPath>
- <DefineConstants>TRACE</DefineConstants>
- <ErrorReport>prompt</ErrorReport>
- <WarningLevel>4</WarningLevel>
- </PropertyGroup>
- <ItemGroup>
- <Reference Include="PresentationCore" />
- <Reference Include="PresentationFramework" />
- <Reference Include="ServiceStack.Text, Version=3.9.3.0, Culture=neutral, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
- <HintPath>..\packages\ServiceStack.Text.3.9.3\lib\net35\ServiceStack.Text.dll</HintPath>
- </Reference>
- <Reference Include="System" />
- <Reference Include="System.ComponentModel.Composition" />
- <Reference Include="System.Configuration" />
- <Reference Include="System.Core" />
- <Reference Include="System.Reactive, Version=1.0.10621.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
- <HintPath>..\packages\Rx-Main.1.0.11226\lib\Net4\System.Reactive.dll</HintPath>
- </Reference>
- <Reference Include="System.Runtime.Remoting" />
- <Reference Include="System.Xaml" />
- <Reference Include="System.Xml.Linq" />
- <Reference Include="System.Data.DataSetExtensions" />
- <Reference Include="Microsoft.CSharp" />
- <Reference Include="System.Data" />
- <Reference Include="System.Xml" />
- <Reference Include="WindowsBase" />
- </ItemGroup>
- <ItemGroup>
- <Compile Include="Configuration\ApplicationPaths.cs" />
- <Compile Include="Configuration\BaseApplicationConfiguration.cs" />
- <Compile Include="Events\GenericItemEventArgs.cs" />
- <Compile Include="Serialization\JsonSerializer.cs" />
- <Compile Include="Kernel\BaseKernel.cs" />
- <Compile Include="Kernel\KernelContext.cs" />
- <Compile Include="Logging\BaseLogger.cs" />
- <Compile Include="Logging\Logger.cs" />
- <Compile Include="Logging\LogRow.cs" />
- <Compile Include="Logging\LogSeverity.cs" />
- <Compile Include="Logging\StreamLogger.cs" />
- <Compile Include="Net\CollectionExtensions.cs" />
- <Compile Include="Net\Handlers\BaseEmbeddedResourceHandler.cs" />
- <Compile Include="Net\Handlers\BaseHandler.cs" />
- <Compile Include="Net\Handlers\BaseJsonHandler.cs" />
- <Compile Include="Net\HttpServer.cs" />
- <Compile Include="Net\Request.cs" />
- <Compile Include="Net\RequestContext.cs" />
- <Compile Include="Net\StreamExtensions.cs" />
- <Compile Include="Plugins\BasePlugin.cs" />
- <Compile Include="Properties\AssemblyInfo.cs" />
- <Compile Include="UI\Splash.xaml.cs">
- <DependentUpon>Splash.xaml</DependentUpon>
- </Compile>
- <Compile Include="UI\SingleInstance.cs" />
- </ItemGroup>
- <ItemGroup>
- <None Include="packages.config" />
- </ItemGroup>
- <ItemGroup>
- <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
- <Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
- <Name>MediaBrowser.Model</Name>
- </ProjectReference>
- </ItemGroup>
- <ItemGroup>
- <Page Include="UI\Splash.xaml">
- <SubType>Designer</SubType>
- <Generator>MSBuild:Compile</Generator>
- </Page>
- </ItemGroup>
- <ItemGroup />
- <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
- <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
- Other similar extension points exist, see Microsoft.Common.targets.
- <Target Name="BeforeBuild">
- </Target>
- <Target Name="AfterBuild">
- </Target>
- -->
-</Project> \ No newline at end of file
diff --git a/MediaBrowser.Controller/Entities/BaseEntity.cs b/MediaBrowser.Controller/Entities/BaseEntity.cs
index 53b42da01..5b4a360c1 100644
--- a/MediaBrowser.Controller/Entities/BaseEntity.cs
+++ b/MediaBrowser.Controller/Entities/BaseEntity.cs
@@ -1,4 +1,9 @@
using System;
+using System.Collections.Generic;
+using System.Linq;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.IO;
+using MediaBrowser.Controller.Providers;
namespace MediaBrowser.Controller.Entities
{
@@ -11,6 +16,10 @@ namespace MediaBrowser.Controller.Entities
public Guid Id { get; set; }
+ public string Path { get; set; }
+
+ public Folder Parent { get; set; }
+
public string PrimaryImagePath { get; set; }
public DateTime DateCreated { get; set; }
@@ -21,5 +30,65 @@ namespace MediaBrowser.Controller.Entities
{
return Name;
}
+ protected Dictionary<Guid, BaseProviderInfo> _providerData;
+ /// <summary>
+ /// Holds persistent data for providers like last refresh date.
+ /// Providers can use this to determine if they need to refresh.
+ /// The BaseProviderInfo class can be extended to hold anything a provider may need.
+ ///
+ /// Keyed by a unique provider ID.
+ /// </summary>
+ public Dictionary<Guid, BaseProviderInfo> ProviderData
+ {
+ get
+ {
+ if (_providerData == null) _providerData = new Dictionary<Guid, BaseProviderInfo>();
+ return _providerData;
+ }
+ set
+ {
+ _providerData = value;
+ }
+ }
+
+ protected ItemResolveEventArgs _resolveArgs;
+ /// <summary>
+ /// We attach these to the item so that we only ever have to hit the file system once
+ /// (this includes the children of the containing folder)
+ /// Use ResolveArgs.FileSystemChildren to check for the existence of files instead of File.Exists
+ /// </summary>
+ public ItemResolveEventArgs ResolveArgs
+ {
+ get
+ {
+ if (_resolveArgs == null)
+ {
+ _resolveArgs = new ItemResolveEventArgs()
+ {
+ FileInfo = FileData.GetFileData(this.Path),
+ Parent = this.Parent,
+ Cancel = false,
+ Path = this.Path
+ };
+ _resolveArgs = FileSystemHelper.FilterChildFileSystemEntries(_resolveArgs, (this.Parent != null && this.Parent.IsRoot));
+ }
+ return _resolveArgs;
+ }
+ set
+ {
+ _resolveArgs = value;
+ }
+ }
+
+ /// <summary>
+ /// Refresh metadata on us by execution our provider chain
+ /// </summary>
+ /// <returns>true if a provider reports we changed</returns>
+ public bool RefreshMetadata()
+ {
+ Kernel.Instance.ExecuteMetadataProviders(this).ConfigureAwait(false);
+ return true;
+ }
+
}
}
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index 2adc4ecb9..4c9008b22 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -1,5 +1,8 @@
using MediaBrowser.Model.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.IO;
using System;
+using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
@@ -7,6 +10,15 @@ namespace MediaBrowser.Controller.Entities
{
public abstract class BaseItem : BaseEntity, IHasProviderIds
{
+
+ public IEnumerable<string> PhysicalLocations
+ {
+ get
+ {
+ return _resolveArgs.PhysicalLocations;
+ }
+ }
+
public string SortName { get; set; }
/// <summary>
@@ -14,10 +26,6 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
public DateTime? PremiereDate { get; set; }
- public string Path { get; set; }
-
- public Folder Parent { get; set; }
-
public string LogoImagePath { get; set; }
public string ArtImagePath { get; set; }
@@ -135,6 +143,18 @@ namespace MediaBrowser.Controller.Entities
}
/// <summary>
+ /// Determine if we have changed vs the passed in copy
+ /// </summary>
+ /// <param name="copy"></param>
+ /// <returns></returns>
+ public virtual bool IsChanged(BaseItem copy)
+ {
+ bool changed = copy.DateModified != this.DateModified;
+ if (changed) MediaBrowser.Common.Logging.Logger.LogDebugInfo(this.Name + " changed - original creation: " + this.DateCreated + " new creation: " + copy.DateCreated + " original modified: " + this.DateModified + " new modified: " + copy.DateModified);
+ return changed;
+ }
+
+ /// <summary>
/// Determines if the item is considered new based on user settings
/// </summary>
public bool IsRecentlyAdded(User user)
@@ -169,5 +189,14 @@ namespace MediaBrowser.Controller.Entities
data.PlaybackPositionTicks = 0;
}
}
+
+ /// <summary>
+ /// Do whatever refreshing is necessary when the filesystem pertaining to this item has changed.
+ /// </summary>
+ /// <returns></returns>
+ public virtual Task ChangedExternally()
+ {
+ return Task.Run(() => RefreshMetadata());
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs
index b9a72e727..07529c80f 100644
--- a/MediaBrowser.Controller/Entities/Folder.cs
+++ b/MediaBrowser.Controller/Entities/Folder.cs
@@ -1,5 +1,10 @@
using MediaBrowser.Model.Entities;
+using MediaBrowser.Controller.IO;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Common.Logging;
+using MediaBrowser.Controller.Resolvers;
using System;
+using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
@@ -7,6 +12,26 @@ namespace MediaBrowser.Controller.Entities
{
public class Folder : BaseItem
{
+ #region Events
+ /// <summary>
+ /// Fires whenever a validation routine updates our children. The added and removed children are properties of the args.
+ /// *** Will fire asynchronously. ***
+ /// </summary>
+ public event EventHandler<ChildrenChangedEventArgs> ChildrenChanged;
+ protected void OnChildrenChanged(ChildrenChangedEventArgs args)
+ {
+ if (ChildrenChanged != null)
+ {
+ Task.Run( () =>
+ {
+ ChildrenChanged(this, args);
+ Kernel.Instance.OnLibraryChanged(args);
+ });
+ }
+ }
+
+ #endregion
+
public override bool IsFolder
{
get
@@ -24,23 +49,270 @@ namespace MediaBrowser.Controller.Entities
return Parent != null && Parent.IsRoot;
}
}
+ protected object childLock = new object();
+ protected List<BaseItem> children;
+ protected virtual List<BaseItem> ActualChildren
+ {
+ get
+ {
+ if (children == null)
+ {
+ LoadChildren();
+ }
+ return children;
+ }
+
+ set
+ {
+ children = value;
+ }
+ }
+
+ /// <summary>
+ /// thread-safe access to the actual children of this folder - without regard to user
+ /// </summary>
+ public IEnumerable<BaseItem> Children
+ {
+ get
+ {
+ lock (childLock)
+ return ActualChildren.ToList();
+ }
+ }
+
+ /// <summary>
+ /// thread-safe access to all recursive children of this folder - without regard to user
+ /// </summary>
+ public IEnumerable<BaseItem> RecursiveChildren
+ {
+ get
+ {
+ foreach (var item in Children)
+ {
+ yield return item;
+
+ var subFolder = item as Folder;
+
+ if (subFolder != null)
+ {
+ foreach (var subitem in subFolder.RecursiveChildren)
+ {
+ yield return subitem;
+ }
+ }
+ }
+ }
+ }
+
+
+ /// <summary>
+ /// Loads and validates our children
+ /// </summary>
+ protected virtual void LoadChildren()
+ {
+ //first - load our children from the repo
+ lock (childLock)
+ children = GetCachedChildren();
+
+ //then kick off a validation against the actual file system
+ Task.Run(() => ValidateChildren());
+ }
+
+ protected bool ChildrenValidating = false;
+
+ /// <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>
+ /// <returns></returns>
+ protected async virtual void ValidateChildren()
+ {
+ if (ChildrenValidating) return; //only ever want one of these going at once and don't want them to fire off in sequence so don't use lock
+ ChildrenValidating = true;
+ bool changed = false; //this will save us a little time at the end if nothing changes
+ var changedArgs = new ChildrenChangedEventArgs(this);
+ //get the current valid children from filesystem (or wherever)
+ var nonCachedChildren = await GetNonCachedChildren();
+ if (nonCachedChildren == null) return; //nothing to validate
+ //build a dictionary of the current children we have now by Id so we can compare quickly and easily
+ Dictionary<Guid, BaseItem> currentChildren;
+ lock (childLock)
+ currentChildren = ActualChildren.ToDictionary(i => i.Id);
+
+ //create a list for our validated children
+ var validChildren = new List<BaseItem>();
+ //now traverse the valid children and find any changed or new items
+ foreach (var child in nonCachedChildren)
+ {
+ BaseItem currentChild;
+ currentChildren.TryGetValue(child.Id, out currentChild);
+ if (currentChild == null)
+ {
+ //brand new item - needs to be added
+ changed = true;
+ changedArgs.ItemsAdded.Add(child);
+ //refresh it
+ child.RefreshMetadata();
+ Logger.LogInfo("New Item Added to Library: ("+child.GetType().Name+") "+ child.Name + " (" + child.Path + ")");
+ //save it in repo...
+
+ //and add it to our valid children
+ validChildren.Add(child);
+ //fire an added event...?
+ //if it is a folder we need to validate its children as well
+ Folder folder = child as Folder;
+ if (folder != null)
+ {
+ folder.ValidateChildren();
+ //probably need to refresh too...
+ }
+ }
+ else
+ {
+ //existing item - check if it has changed
+ if (currentChild.IsChanged(child))
+ {
+ changed = true;
+ //update resolve args and refresh meta
+ // Note - we are refreshing the existing child instead of the newly found one so the "Except" operation below
+ // will identify this item as the same one
+ currentChild.ResolveArgs = child.ResolveArgs;
+ currentChild.RefreshMetadata();
+ Logger.LogInfo("Item Changed: ("+currentChild.GetType().Name+") "+ currentChild.Name + " (" + currentChild.Path + ")");
+ //save it in repo...
+ validChildren.Add(currentChild);
+ }
+ else
+ {
+ //current child that didn't change - just put it in the valid children
+ validChildren.Add(currentChild);
+ }
+ }
+ }
+
+ //that's all the new and changed ones - now see if there are any that are missing
+ changedArgs.ItemsRemoved = currentChildren.Values.Except(validChildren);
+ changed |= changedArgs.ItemsRemoved != null;
+
+ //now, if anything changed - replace our children
+ if (changed)
+ {
+ if (changedArgs.ItemsRemoved != null) foreach (var item in changedArgs.ItemsRemoved) Logger.LogDebugInfo("** " + item.Name + " Removed from library.");
+
+ lock (childLock)
+ ActualChildren = validChildren;
+ //and save children in repo...
+
+ //and fire event
+ this.OnChildrenChanged(changedArgs);
+ }
+ ChildrenValidating = false;
+
+ }
+
+ /// <summary>
+ /// Get the children of this folder from the actual file system
+ /// </summary>
+ /// <returns></returns>
+ protected async virtual Task<IEnumerable<BaseItem>> GetNonCachedChildren()
+ {
+ ItemResolveEventArgs args = new ItemResolveEventArgs()
+ {
+ FileInfo = FileData.GetFileData(this.Path),
+ Parent = this.Parent,
+ Cancel = false,
+ Path = this.Path
+ };
+
+ // Gather child folder and files
+ if (args.IsDirectory)
+ {
+ args.FileSystemChildren = FileData.GetFileSystemEntries(this.Path, "*").ToArray();
+
+ bool isVirtualFolder = Parent != null && Parent.IsRoot;
+ args = FileSystemHelper.FilterChildFileSystemEntries(args, isVirtualFolder);
+ }
+ else
+ {
+ Logger.LogError("Folder has a path that is not a directory: " + this.Path);
+ return null;
+ }
+
+ if (!EntityResolutionHelper.ShouldResolvePathContents(args))
+ {
+ return null;
+ }
+ return (await Task.WhenAll<BaseItem>(GetChildren(args.FileSystemChildren)).ConfigureAwait(false))
+ .Where(i => i != null).OrderBy(f =>
+ {
+ return string.IsNullOrEmpty(f.SortName) ? f.Name : f.SortName;
+
+ });
+
+ }
+
+ /// <summary>
+ /// Resolves a path into a BaseItem
+ /// </summary>
+ protected async Task<BaseItem> GetChild(string path, WIN32_FIND_DATA? fileInfo = null)
+ {
+ ItemResolveEventArgs args = new ItemResolveEventArgs()
+ {
+ FileInfo = fileInfo ?? FileData.GetFileData(path),
+ Parent = this,
+ Cancel = false,
+ Path = path
+ };
+
+ args.FileSystemChildren = FileData.GetFileSystemEntries(path, "*").ToArray();
+ args = FileSystemHelper.FilterChildFileSystemEntries(args, false);
+
+ return Kernel.Instance.ResolveItem(args);
+
+ }
+
+ /// <summary>
+ /// Finds child BaseItems for us
+ /// </summary>
+ protected Task<BaseItem>[] GetChildren(WIN32_FIND_DATA[] fileSystemChildren)
+ {
+ Task<BaseItem>[] tasks = new Task<BaseItem>[fileSystemChildren.Length];
+
+ for (int i = 0; i < fileSystemChildren.Length; i++)
+ {
+ var child = fileSystemChildren[i];
+
+ tasks[i] = GetChild(child.Path, child);
+ }
+
+ return tasks;
+ }
- public IEnumerable<BaseItem> Children { get; set; }
+
+ /// <summary>
+ /// Get our children from the repo - stubbed for now
+ /// </summary>
+ /// <returns></returns>
+ protected virtual List<BaseItem> GetCachedChildren()
+ {
+ return new List<BaseItem>();
+ }
/// <summary>
/// Gets allowed children of an item
/// </summary>
- public IEnumerable<BaseItem> GetParentalAllowedChildren(User user)
+ public IEnumerable<BaseItem> GetChildren(User user)
{
- return Children.Where(c => c.IsParentalAllowed(user));
+ lock(childLock)
+ return ActualChildren.Where(c => c.IsParentalAllowed(user));
}
/// <summary>
/// Gets allowed recursive children of an item
/// </summary>
- public IEnumerable<BaseItem> GetParentalAllowedRecursiveChildren(User user)
+ public IEnumerable<BaseItem> GetRecursiveChildren(User user)
{
- foreach (var item in GetParentalAllowedChildren(user))
+ foreach (var item in GetChildren(user))
{
yield return item;
@@ -48,7 +320,7 @@ namespace MediaBrowser.Controller.Entities
if (subFolder != null)
{
- foreach (var subitem in subFolder.GetParentalAllowedRecursiveChildren(user))
+ foreach (var subitem in subFolder.GetRecursiveChildren(user))
{
yield return subitem;
}
@@ -57,13 +329,33 @@ namespace MediaBrowser.Controller.Entities
}
/// <summary>
+ /// Folders need to validate and refresh
+ /// </summary>
+ /// <returns></returns>
+ public override Task ChangedExternally()
+ {
+ return Task.Run(() =>
+ {
+ if (this.IsRoot)
+ {
+ Kernel.Instance.ReloadRoot().ConfigureAwait(false);
+ }
+ else
+ {
+ RefreshMetadata();
+ ValidateChildren();
+ }
+ });
+ }
+
+ /// <summary>
/// Since it can be slow to make all of these calculations at once, this method will provide a way to get them all back together
/// </summary>
public ItemSpecialCounts GetSpecialCounts(User user)
{
var counts = new ItemSpecialCounts();
- IEnumerable<BaseItem> recursiveChildren = GetParentalAllowedRecursiveChildren(user);
+ IEnumerable<BaseItem> recursiveChildren = GetRecursiveChildren(user);
var recentlyAddedItems = GetRecentlyAddedItems(recursiveChildren, user);
@@ -80,7 +372,7 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
public IEnumerable<BaseItem> GetItemsWithGenre(string genre, User user)
{
- return GetParentalAllowedRecursiveChildren(user).Where(f => f.Genres != null && f.Genres.Any(s => s.Equals(genre, StringComparison.OrdinalIgnoreCase)));
+ return GetRecursiveChildren(user).Where(f => f.Genres != null && f.Genres.Any(s => s.Equals(genre, StringComparison.OrdinalIgnoreCase)));
}
/// <summary>
@@ -88,7 +380,7 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
public IEnumerable<BaseItem> GetItemsWithYear(int year, User user)
{
- return GetParentalAllowedRecursiveChildren(user).Where(f => f.ProductionYear.HasValue && f.ProductionYear == year);
+ return GetRecursiveChildren(user).Where(f => f.ProductionYear.HasValue && f.ProductionYear == year);
}
/// <summary>
@@ -96,7 +388,7 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
public IEnumerable<BaseItem> GetItemsWithStudio(string studio, User user)
{
- return GetParentalAllowedRecursiveChildren(user).Where(f => f.Studios != null && f.Studios.Any(s => s.Equals(studio, StringComparison.OrdinalIgnoreCase)));
+ return GetRecursiveChildren(user).Where(f => f.Studios != null && f.Studios.Any(s => s.Equals(studio, StringComparison.OrdinalIgnoreCase)));
}
/// <summary>
@@ -104,7 +396,7 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
public IEnumerable<BaseItem> GetFavoriteItems(User user)
{
- return GetParentalAllowedRecursiveChildren(user).Where(c =>
+ return GetRecursiveChildren(user).Where(c =>
{
UserItemData data = c.GetUserData(user, false);
@@ -122,7 +414,7 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
public IEnumerable<BaseItem> GetItemsWithPerson(string person, User user)
{
- return GetParentalAllowedRecursiveChildren(user).Where(c =>
+ return GetRecursiveChildren(user).Where(c =>
{
if (c.People != null)
{
@@ -139,7 +431,7 @@ namespace MediaBrowser.Controller.Entities
/// <param name="personType">Specify this to limit results to a specific PersonType</param>
public IEnumerable<BaseItem> GetItemsWithPerson(string person, string personType, User user)
{
- return GetParentalAllowedRecursiveChildren(user).Where(c =>
+ return GetRecursiveChildren(user).Where(c =>
{
if (c.People != null)
{
@@ -155,7 +447,7 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
public List<BaseItem> GetRecentlyAddedItems(User user)
{
- return GetRecentlyAddedItems(GetParentalAllowedRecursiveChildren(user), user);
+ return GetRecentlyAddedItems(GetRecursiveChildren(user), user);
}
/// <summary>
@@ -163,7 +455,7 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
public List<BaseItem> GetRecentlyAddedUnplayedItems(User user)
{
- return GetRecentlyAddedUnplayedItems(GetParentalAllowedRecursiveChildren(user), user);
+ return GetRecentlyAddedUnplayedItems(GetRecursiveChildren(user), user);
}
/// <summary>
@@ -171,7 +463,7 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
public List<BaseItem> GetInProgressItems(User user)
{
- return GetInProgressItems(GetParentalAllowedRecursiveChildren(user), user);
+ return GetInProgressItems(GetRecursiveChildren(user), user);
}
/// <summary>
@@ -288,7 +580,7 @@ namespace MediaBrowser.Controller.Entities
base.SetPlayedStatus(user, wasPlayed);
// Now sweep through recursively and update status
- foreach (BaseItem item in GetParentalAllowedChildren(user))
+ foreach (BaseItem item in GetChildren(user))
{
item.SetPlayedStatus(user, wasPlayed);
}
@@ -306,17 +598,8 @@ namespace MediaBrowser.Controller.Entities
return result;
}
- foreach (BaseItem item in Children)
- {
- result = item.FindItemById(id);
-
- if (result != null)
- {
- return result;
- }
- }
-
- return null;
+ //this should be functionally equivilent to what was here since it is IEnum and works on a thread-safe copy
+ return RecursiveChildren.FirstOrDefault(i => i.Id == id);
}
/// <summary>
@@ -324,31 +607,13 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
public BaseItem FindByPath(string path)
{
- if (Path.Equals(path, StringComparison.OrdinalIgnoreCase))
+ if (PhysicalLocations.Contains(path, StringComparer.OrdinalIgnoreCase))
{
return this;
}
- foreach (BaseItem item in Children)
- {
- var folder = item as Folder;
-
- if (folder != null)
- {
- var foundItem = folder.FindByPath(path);
-
- if (foundItem != null)
- {
- return foundItem;
- }
- }
- else if (item.Path.Equals(path, StringComparison.OrdinalIgnoreCase))
- {
- return item;
- }
- }
-
- return null;
+ //this should be functionally equivilent to what was here since it is IEnum and works on a thread-safe copy
+ return RecursiveChildren.FirstOrDefault(i => i.PhysicalLocations.Contains(path, StringComparer.OrdinalIgnoreCase));
}
}
}
diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs
index 98ad31220..f9c7fecb3 100644
--- a/MediaBrowser.Controller/Entities/TV/Season.cs
+++ b/MediaBrowser.Controller/Entities/TV/Season.cs
@@ -7,7 +7,13 @@ namespace MediaBrowser.Controller.Entities.TV
/// <summary>
/// Store these to reduce disk access in Episode Resolver
/// </summary>
- public string[] MetadataFiles { get; set; }
+ public string[] MetadataFiles
+ {
+ get
+ {
+ return ResolveArgs.MetadataFiles ?? new string[] { };
+ }
+ }
/// <summary>
/// Determines if the metafolder contains a given file
diff --git a/MediaBrowser.Controller/IO/DirectoryWatchers.cs b/MediaBrowser.Controller/IO/DirectoryWatchers.cs
index ce7f65d39..eb1358e16 100644
--- a/MediaBrowser.Controller/IO/DirectoryWatchers.cs
+++ b/MediaBrowser.Controller/IO/DirectoryWatchers.cs
@@ -1,4 +1,6 @@
using MediaBrowser.Controller.Entities;
+using MediaBrowser.Common.Logging;
+using MediaBrowser.Common.Extensions;
using System;
using System.Collections.Generic;
using System.IO;
@@ -14,7 +16,7 @@ namespace MediaBrowser.Controller.IO
private Timer updateTimer;
private List<string> affectedPaths = new List<string>();
- private const int TimerDelayInSeconds = 5;
+ private const int TimerDelayInSeconds = 30;
public void Start()
{
@@ -26,32 +28,29 @@ namespace MediaBrowser.Controller.IO
foreach (Folder folder in rootFolder.Children.OfType<Folder>())
{
- foreach (Folder subFolder in folder.Children.OfType<Folder>())
+ foreach (string path in folder.PhysicalLocations)
{
- if (Path.IsPathRooted(subFolder.Path))
+ if (Path.IsPathRooted(path) && !pathsToWatch.ContainsParentFolder(path))
{
- string parent = Path.GetDirectoryName(subFolder.Path);
-
- if (!pathsToWatch.Contains(parent))
- {
- pathsToWatch.Add(parent);
- }
+ pathsToWatch.Add(path);
}
}
}
foreach (string path in pathsToWatch)
{
- var watcher = new FileSystemWatcher(path, "*") { };
+ Logger.LogInfo("Watching directory " + path + " for changes.");
+ var watcher = new FileSystemWatcher(path, "*") { };
watcher.IncludeSubdirectories = true;
- watcher.Changed += watcher_Changed;
+ //watcher.Changed += watcher_Changed;
// All the others seem to trigger change events on the parent, so let's keep it simple for now.
- //watcher.Created += watcher_Changed;
- //watcher.Deleted += watcher_Changed;
- //watcher.Renamed += watcher_Changed;
+ // Actually, we really need to only watch created, deleted and renamed as changed fires too much -ebr
+ watcher.Created += watcher_Changed;
+ watcher.Deleted += watcher_Changed;
+ watcher.Renamed += watcher_Changed;
watcher.EnableRaisingEvents = true;
FileSystemWatchers.Add(watcher);
@@ -60,9 +59,30 @@ namespace MediaBrowser.Controller.IO
void watcher_Changed(object sender, FileSystemEventArgs e)
{
- if (!affectedPaths.Contains(e.FullPath))
+ Logger.LogDebugInfo("****** Watcher sees change of type " + e.ChangeType.ToString() + " to " + e.FullPath);
+ lock (affectedPaths)
{
- affectedPaths.Add(e.FullPath);
+ //Since we're watching created, deleted and renamed we always want the parent of the item to be the affected path
+ var affectedPath = Path.GetDirectoryName(e.FullPath);
+
+ if (e.ChangeType == WatcherChangeTypes.Renamed)
+ {
+ var renamedArgs = e as RenamedEventArgs;
+ if (affectedPaths.Contains(renamedArgs.OldFullPath))
+ {
+ Logger.LogDebugInfo("****** Removing " + renamedArgs.OldFullPath + " from affected paths.");
+ affectedPaths.Remove(renamedArgs.OldFullPath);
+ }
+ }
+
+ //If anything underneath this path was already marked as affected - remove it as it will now get captured by this one
+ affectedPaths.RemoveAll(p => p.StartsWith(e.FullPath, StringComparison.OrdinalIgnoreCase));
+
+ if (!affectedPaths.ContainsParentFolder(affectedPath))
+ {
+ Logger.LogDebugInfo("****** Adding " + affectedPath + " to affected paths.");
+ affectedPaths.Add(affectedPath);
+ }
}
if (updateTimer == null)
@@ -79,9 +99,12 @@ namespace MediaBrowser.Controller.IO
{
updateTimer.Dispose();
updateTimer = null;
-
- List<string> paths = affectedPaths;
- affectedPaths = new List<string>();
+ List<string> paths;
+ lock (affectedPaths)
+ {
+ paths = affectedPaths;
+ affectedPaths = new List<string>();
+ }
await ProcessPathChanges(paths).ConfigureAwait(false);
}
@@ -108,14 +131,16 @@ namespace MediaBrowser.Controller.IO
return Kernel.Instance.ReloadRoot();
}
- return Task.WhenAll(itemsToRefresh.Select(i => Kernel.Instance.ReloadItem(i)));
+ foreach (var p in paths) Logger.LogDebugInfo("********* "+ p + " reports change.");
+ foreach (var i in itemsToRefresh) Logger.LogDebugInfo("********* "+i.Name + " ("+ i.Path + ") will be refreshed.");
+ return Task.WhenAll(itemsToRefresh.Select(i => i.ChangedExternally()));
}
private BaseItem GetAffectedBaseItem(string path)
{
BaseItem item = null;
- while (item == null)
+ while (item == null && !string.IsNullOrEmpty(path))
{
item = Kernel.Instance.RootFolder.FindByPath(path);
diff --git a/MediaBrowser.Controller/IO/FileSystemHelper.cs b/MediaBrowser.Controller/IO/FileSystemHelper.cs
new file mode 100644
index 000000000..732cf0803
--- /dev/null
+++ b/MediaBrowser.Controller/IO/FileSystemHelper.cs
@@ -0,0 +1,132 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.IO;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Resolvers;
+using MediaBrowser.Controller.Library;
+
+namespace MediaBrowser.Controller.IO
+{
+ public static class FileSystemHelper
+ {
+ /// <summary>
+ /// Transforms shortcuts into their actual paths and filters out items that should be ignored
+ /// </summary>
+ public static ItemResolveEventArgs FilterChildFileSystemEntries(ItemResolveEventArgs args, bool flattenShortcuts)
+ {
+
+ List<WIN32_FIND_DATA> returnChildren = new List<WIN32_FIND_DATA>();
+ List<WIN32_FIND_DATA> resolvedShortcuts = new List<WIN32_FIND_DATA>();
+
+ foreach (var file in args.FileSystemChildren)
+ {
+ // If it's a shortcut, resolve it
+ if (Shortcut.IsShortcut(file.Path))
+ {
+ string newPath = Shortcut.ResolveShortcut(file.Path);
+ WIN32_FIND_DATA newPathData = FileData.GetFileData(newPath);
+
+ // Find out if the shortcut is pointing to a directory or file
+ if (newPathData.IsDirectory)
+ {
+ // add to our physical locations
+ args.AdditionalLocations.Add(newPath);
+
+ // If we're flattening then get the shortcut's children
+ if (flattenShortcuts)
+ {
+ returnChildren.Add(file);
+ ItemResolveEventArgs newArgs = new ItemResolveEventArgs()
+ {
+ FileSystemChildren = FileData.GetFileSystemEntries(newPath, "*").ToArray()
+ };
+
+ resolvedShortcuts.AddRange(FilterChildFileSystemEntries(newArgs, false).FileSystemChildren);
+ }
+ else
+ {
+ returnChildren.Add(newPathData);
+ }
+ }
+ else
+ {
+ returnChildren.Add(newPathData);
+ }
+ }
+ else
+ {
+ //not a shortcut check to see if we should filter it out
+ if (EntityResolutionHelper.ShouldResolvePath(file))
+ {
+ returnChildren.Add(file);
+ }
+ else
+ {
+ //filtered - see if it is one of our "indicator" folders and mark it now - no reason to search for it again
+ args.IsBDFolder |= file.cFileName.Equals("bdmv", StringComparison.OrdinalIgnoreCase);
+ args.IsDVDFolder |= file.cFileName.Equals("video_ts", StringComparison.OrdinalIgnoreCase);
+ args.IsHDDVDFolder |= file.cFileName.Equals("hvdvd_ts", StringComparison.OrdinalIgnoreCase);
+
+ //and check to see if it is a metadata folder and collect contents now if so
+ if (IsMetadataFolder(file.cFileName))
+ {
+ args.MetadataFiles = Directory.GetFiles(Path.Combine(args.Path, "metadata"), "*", SearchOption.TopDirectoryOnly);
+ }
+ }
+ }
+ }
+
+ if (resolvedShortcuts.Count > 0)
+ {
+ resolvedShortcuts.InsertRange(0, returnChildren);
+ args.FileSystemChildren = resolvedShortcuts.ToArray();
+ }
+ else
+ {
+ args.FileSystemChildren = returnChildren.ToArray();
+ }
+ return args;
+ }
+
+ public static bool IsMetadataFolder(string path)
+ {
+ return path.TrimEnd('\\').EndsWith("metadata", StringComparison.OrdinalIgnoreCase);
+ }
+
+ public static bool IsVideoFile(string path)
+ {
+ string extension = System.IO.Path.GetExtension(path).ToLower();
+
+ switch (extension)
+ {
+ case ".mkv":
+ case ".m2ts":
+ case ".iso":
+ case ".ts":
+ case ".rmvb":
+ case ".mov":
+ case ".avi":
+ case ".mpg":
+ case ".mpeg":
+ case ".wmv":
+ case ".mp4":
+ case ".divx":
+ case ".dvr-ms":
+ case ".wtv":
+ case ".ogm":
+ case ".ogv":
+ case ".asf":
+ case ".m4v":
+ case ".flv":
+ case ".f4v":
+ case ".3gp":
+ return true;
+
+ default:
+ return false;
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/IO/Shortcut.cs b/MediaBrowser.Controller/IO/Shortcut.cs
index 683f44573..e9ea21f17 100644
--- a/MediaBrowser.Controller/IO/Shortcut.cs
+++ b/MediaBrowser.Controller/IO/Shortcut.cs
@@ -179,7 +179,7 @@ namespace MediaBrowser.Controller.IO
public static bool IsShortcut(string filename)
{
- return Path.GetExtension(filename).EndsWith("lnk", StringComparison.OrdinalIgnoreCase);
+ return filename != null ? Path.GetExtension(filename).EndsWith("lnk", StringComparison.OrdinalIgnoreCase) : false;
}
}
}
diff --git a/MediaBrowser.Controller/Kernel.cs b/MediaBrowser.Controller/Kernel.cs
index 73f87d341..2430260dd 100644
--- a/MediaBrowser.Controller/Kernel.cs
+++ b/MediaBrowser.Controller/Kernel.cs
@@ -10,6 +10,7 @@ using MediaBrowser.Controller.Weather;
using MediaBrowser.Model.Authentication;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Progress;
+using MediaBrowser.Common.Extensions;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
@@ -24,6 +25,21 @@ namespace MediaBrowser.Controller
{
public class Kernel : BaseKernel<ServerConfiguration, ServerApplicationPaths>
{
+ #region Events
+ /// <summary>
+ /// Fires whenever any validation routine adds or removes items. The added and removed items are properties of the args.
+ /// *** Will fire asynchronously. ***
+ /// </summary>
+ public event EventHandler<ChildrenChangedEventArgs> LibraryChanged;
+ public void OnLibraryChanged(ChildrenChangedEventArgs args)
+ {
+ if (LibraryChanged != null)
+ {
+ Task.Run(() => LibraryChanged(this, args));
+ }
+ }
+
+ #endregion
public static Kernel Instance { get; private set; }
public ItemController ItemController { get; private set; }
@@ -95,8 +111,6 @@ namespace MediaBrowser.Controller
ItemController = new ItemController();
DirectoryWatchers = new DirectoryWatchers();
- ItemController.PreBeginResolvePath += ItemController_PreBeginResolvePath;
- ItemController.BeginResolvePath += ItemController_BeginResolvePath;
ExtractFFMpeg();
}
@@ -114,6 +128,7 @@ namespace MediaBrowser.Controller
ReportProgress(progress, "Loading Media Library");
await ReloadRoot(allowInternetProviders: false).ConfigureAwait(false);
+
}
/// <summary>
@@ -125,8 +140,6 @@ namespace MediaBrowser.Controller
DirectoryWatchers.Stop();
- ItemController.PreBeginResolvePath -= ItemController_PreBeginResolvePath;
- ItemController.BeginResolvePath -= ItemController_BeginResolvePath;
}
protected override void OnComposablePartsLoaded()
@@ -141,46 +154,21 @@ namespace MediaBrowser.Controller
MetadataProviders = MetadataProvidersEnumerable.OrderBy(e => e.Priority).ToArray();
}
- /// <summary>
- /// Fires when a path is about to be resolved, but before child folders and files
- /// have been collected from the file system.
- /// This gives us a chance to cancel it if needed, resulting in the path being ignored
- /// </summary>
- void ItemController_PreBeginResolvePath(object sender, PreBeginResolveEventArgs e)
+ public BaseItem ResolveItem(ItemResolveEventArgs args)
{
- // Ignore hidden files and folders
- if (e.IsHidden || e.IsSystemFile)
+ // Try first priority resolvers
+ for (int i = 0; i < EntityResolvers.Length; i++)
{
- e.Cancel = true;
- }
+ var item = EntityResolvers[i].ResolvePath(args);
- // Ignore any folders named "trailers"
- else if (Path.GetFileName(e.Path).Equals("trailers", StringComparison.OrdinalIgnoreCase))
- {
- e.Cancel = true;
- }
-
- // Don't try and resolve files within the season metadata folder
- else if (Path.GetFileName(e.Path).Equals("metadata", StringComparison.OrdinalIgnoreCase) && e.IsDirectory)
- {
- if (e.Parent is Season || e.Parent is Series)
+ if (item != null)
{
- e.Cancel = true;
+ item.ResolveArgs = args;
+ return item;
}
}
- }
- /// <summary>
- /// Fires when a path is about to be resolved, but after child folders and files
- /// This gives us a chance to cancel it if needed, resulting in the path being ignored
- /// </summary>
- void ItemController_BeginResolvePath(object sender, ItemResolveEventArgs e)
- {
- if (e.ContainsFile(".ignore"))
- {
- // Ignore any folders containing a file called .ignore
- e.Cancel = true;
- }
+ return null;
}
private void ReloadUsers()
@@ -201,16 +189,24 @@ namespace MediaBrowser.Controller
DirectoryWatchers.Stop();
RootFolder = await ItemController.GetItem(MediaRootFolderPath, allowInternetProviders: allowInternetProviders).ConfigureAwait(false) as Folder;
+ RootFolder.ChildrenChanged += RootFolder_ChildrenChanged;
DirectoryWatchers.Start();
}
- public static Guid GetMD5(string str)
+ void RootFolder_ChildrenChanged(object sender, ChildrenChangedEventArgs e)
{
- using (var provider = new MD5CryptoServiceProvider())
- {
- return new Guid(provider.ComputeHash(Encoding.Unicode.GetBytes(str)));
- }
+ Logger.LogDebugInfo("Root Folder Children Changed. Added: " + e.ItemsAdded.Count + " Removed: " + e.ItemsRemoved.Count());
+ //re-start the directory watchers
+ DirectoryWatchers.Stop();
+ DirectoryWatchers.Start();
+ //Task.Delay(30000); //let's wait and see if more data gets filled in...
+ var allChildren = RootFolder.RecursiveChildren;
+ Logger.LogDebugInfo(string.Format("Loading complete. Movies: {0} Episodes: {1} Folders: {2}", allChildren.OfType<Entities.Movies.Movie>().Count(), allChildren.OfType<Entities.TV.Episode>().Count(), allChildren.Where(i => i is Folder && !(i is Series || i is Season)).Count()));
+ //foreach (var child in allChildren)
+ //{
+ // Logger.LogDebugInfo("(" + child.GetType().Name + ") " + child.Name + " (" + child.Path + ")");
+ //}
}
/// <summary>
@@ -249,7 +245,8 @@ namespace MediaBrowser.Controller
}
else
{
- result.Success = GetMD5((password ?? string.Empty)).ToString().Equals(user.Password);
+ password = password ?? string.Empty;
+ result.Success = password.GetMD5().ToString().Equals(user.Password);
}
// Update LastActivityDate and LastLoginDate, then save
@@ -262,36 +259,6 @@ namespace MediaBrowser.Controller
return result;
}
- public async Task ReloadItem(BaseItem item)
- {
- var folder = item as Folder;
-
- if (folder != null && folder.IsRoot)
- {
- await ReloadRoot().ConfigureAwait(false);
- }
- else
- {
- if (!Directory.Exists(item.Path) && !File.Exists(item.Path))
- {
- await ReloadItem(item.Parent).ConfigureAwait(false);
- return;
- }
-
- BaseItem newItem = await ItemController.GetItem(item.Path, item.Parent).ConfigureAwait(false);
-
- List<BaseItem> children = item.Parent.Children.ToList();
-
- int index = children.IndexOf(item);
-
- children.RemoveAt(index);
-
- children.Insert(index, newItem);
-
- item.Parent.Children = children.ToArray();
- }
- }
-
/// <summary>
/// Finds a library item by Id
/// </summary>
@@ -325,7 +292,7 @@ namespace MediaBrowser.Controller
user.Id = Guid.NewGuid();
user.LastLoginDate = DateTime.UtcNow.AddDays(-1);
user.LastActivityDate = DateTime.UtcNow.AddHours(-3);
- user.Password = GetMD5("1234").ToString();
+ user.Password = ("1234").GetMD5().ToString();
list.Add(user);
user = new User { };
@@ -359,7 +326,7 @@ namespace MediaBrowser.Controller
/// <summary>
/// Runs all metadata providers for an entity
/// </summary>
- internal async Task ExecuteMetadataProviders(BaseEntity item, ItemResolveEventArgs args, bool allowInternetProviders = true)
+ internal async Task ExecuteMetadataProviders(BaseEntity item, bool allowInternetProviders = true)
{
// Run them sequentially in order of priority
for (int i = 0; i < MetadataProviders.Length; i++)
@@ -380,7 +347,7 @@ namespace MediaBrowser.Controller
try
{
- await provider.FetchAsync(item, args).ConfigureAwait(false);
+ await provider.FetchIfNeededAsync(item).ConfigureAwait(false);
}
catch (Exception ex)
{
diff --git a/MediaBrowser.Controller/Library/ChildrenChangedEventArgs.cs b/MediaBrowser.Controller/Library/ChildrenChangedEventArgs.cs
new file mode 100644
index 000000000..462fcc6d6
--- /dev/null
+++ b/MediaBrowser.Controller/Library/ChildrenChangedEventArgs.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Entities;
+
+namespace MediaBrowser.Controller.Library
+{
+ public class ChildrenChangedEventArgs : EventArgs
+ {
+ public Folder Folder { get; set; }
+ public List<BaseItem> ItemsAdded { get; set; }
+ public IEnumerable<BaseItem> ItemsRemoved { get; set; }
+
+ public ChildrenChangedEventArgs()
+ {
+ //initialize the list
+ ItemsAdded = new List<BaseItem>();
+ }
+
+ /// <summary>
+ /// Create the args and set the folder property
+ /// </summary>
+ /// <param name="folder"></param>
+ public ChildrenChangedEventArgs(Folder folder)
+ {
+ //init the folder property
+ this.Folder = folder;
+ //init the list
+ ItemsAdded = new List<BaseItem>();
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/Library/ItemController.cs b/MediaBrowser.Controller/Library/ItemController.cs
index 6ac2e4f1e..54673e538 100644
--- a/MediaBrowser.Controller/Library/ItemController.cs
+++ b/MediaBrowser.Controller/Library/ItemController.cs
@@ -1,5 +1,7 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.IO;
+using MediaBrowser.Controller.Resolvers;
+using MediaBrowser.Common.Extensions;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -11,57 +13,6 @@ namespace MediaBrowser.Controller.Library
{
public class ItemController
{
- #region PreBeginResolvePath Event
- /// <summary>
- /// Fires when a path is about to be resolved, but before child folders and files
- /// have been collected from the file system.
- /// This gives listeners a chance to cancel the operation and cause the path to be ignored.
- /// </summary>
- public event EventHandler<PreBeginResolveEventArgs> PreBeginResolvePath;
- private bool OnPreBeginResolvePath(PreBeginResolveEventArgs args)
- {
- if (PreBeginResolvePath != null)
- {
- PreBeginResolvePath(this, args);
- }
-
- return !args.Cancel;
- }
- #endregion
-
- #region BeginResolvePath Event
- /// <summary>
- /// Fires when a path is about to be resolved, but after child folders and files
- /// have been collected from the file system.
- /// This gives listeners a chance to cancel the operation and cause the path to be ignored.
- /// </summary>
- public event EventHandler<ItemResolveEventArgs> BeginResolvePath;
- private bool OnBeginResolvePath(ItemResolveEventArgs args)
- {
- if (BeginResolvePath != null)
- {
- BeginResolvePath(this, args);
- }
-
- return !args.Cancel;
- }
- #endregion
-
- private BaseItem ResolveItem(ItemResolveEventArgs args)
- {
- // Try first priority resolvers
- for (int i = 0; i < Kernel.Instance.EntityResolvers.Length; i++)
- {
- var item = Kernel.Instance.EntityResolvers[i].ResolvePath(args);
-
- if (item != null)
- {
- return item;
- }
- }
-
- return null;
- }
/// <summary>
/// Resolves a path into a BaseItem
@@ -76,124 +27,32 @@ namespace MediaBrowser.Controller.Library
Path = path
};
- if (!OnPreBeginResolvePath(args))
- {
- return null;
- }
-
- WIN32_FIND_DATA[] fileSystemChildren;
-
// Gather child folder and files
if (args.IsDirectory)
{
- fileSystemChildren = FileData.GetFileSystemEntries(path, "*").ToArray();
+ args.FileSystemChildren = FileData.GetFileSystemEntries(path, "*").ToArray();
bool isVirtualFolder = parent != null && parent.IsRoot;
- fileSystemChildren = FilterChildFileSystemEntries(fileSystemChildren, isVirtualFolder);
+ args = FileSystemHelper.FilterChildFileSystemEntries(args, isVirtualFolder);
}
else
{
- fileSystemChildren = new WIN32_FIND_DATA[] { };
+ args.FileSystemChildren = new WIN32_FIND_DATA[] { };
}
- args.FileSystemChildren = fileSystemChildren;
- // Fire BeginResolvePath to see if anyone wants to cancel this operation
- if (!OnBeginResolvePath(args))
+ // Check to see if we should resolve based on our contents
+ if (!EntityResolutionHelper.ShouldResolvePathContents(args))
{
return null;
}
- BaseItem item = ResolveItem(args);
-
- if (item != null)
- {
- await Kernel.Instance.ExecuteMetadataProviders(item, args, allowInternetProviders: allowInternetProviders).ConfigureAwait(false);
-
- if (item.IsFolder)
- {
- // If it's a folder look for child entities
- (item as Folder).Children = (await Task.WhenAll(GetChildren(item as Folder, fileSystemChildren, allowInternetProviders)).ConfigureAwait(false))
- .Where(i => i != null).OrderBy(f => (string.IsNullOrEmpty(f.SortName) ? f.Name : f.SortName));
- }
- }
+ BaseItem item = Kernel.Instance.ResolveItem(args);
return item;
}
/// <summary>
- /// Finds child BaseItems for a given Folder
- /// </summary>
- private Task<BaseItem>[] GetChildren(Folder folder, WIN32_FIND_DATA[] fileSystemChildren, bool allowInternetProviders)
- {
- var tasks = new Task<BaseItem>[fileSystemChildren.Length];
-
- for (int i = 0; i < fileSystemChildren.Length; i++)
- {
- var child = fileSystemChildren[i];
-
- tasks[i] = GetItem(child.Path, folder, child, allowInternetProviders: allowInternetProviders);
- }
-
- return tasks;
- }
-
- /// <summary>
- /// Transforms shortcuts into their actual paths
- /// </summary>
- private WIN32_FIND_DATA[] FilterChildFileSystemEntries(WIN32_FIND_DATA[] fileSystemChildren, bool flattenShortcuts)
- {
- var returnArray = new WIN32_FIND_DATA[fileSystemChildren.Length];
- var resolvedShortcuts = new List<WIN32_FIND_DATA>();
-
- for (int i = 0; i < fileSystemChildren.Length; i++)
- {
- WIN32_FIND_DATA file = fileSystemChildren[i];
-
- // If it's a shortcut, resolve it
- if (Shortcut.IsShortcut(file.Path))
- {
- string newPath = Shortcut.ResolveShortcut(file.Path);
- WIN32_FIND_DATA newPathData = FileData.GetFileData(newPath);
-
- // Find out if the shortcut is pointing to a directory or file
- if (newPathData.IsDirectory)
- {
- // If we're flattening then get the shortcut's children
-
- if (flattenShortcuts)
- {
- returnArray[i] = file;
- WIN32_FIND_DATA[] newChildren = FileData.GetFileSystemEntries(newPath, "*").ToArray();
-
- resolvedShortcuts.AddRange(FilterChildFileSystemEntries(newChildren, false));
- }
- else
- {
- returnArray[i] = newPathData;
- }
- }
- else
- {
- returnArray[i] = newPathData;
- }
- }
- else
- {
- returnArray[i] = file;
- }
- }
-
- if (resolvedShortcuts.Count > 0)
- {
- resolvedShortcuts.InsertRange(0, returnArray);
- return resolvedShortcuts.ToArray();
- }
-
- return returnArray;
- }
-
- /// <summary>
/// Gets a Person
/// </summary>
public Task<Person> GetPerson(string name)
@@ -255,7 +114,7 @@ namespace MediaBrowser.Controller.Library
var item = new T { };
item.Name = name;
- item.Id = Kernel.GetMD5(path);
+ item.Id = path.GetMD5();
if (!Directory.Exists(path))
{
@@ -269,7 +128,7 @@ namespace MediaBrowser.Controller.Library
args.FileInfo = FileData.GetFileData(path);
args.FileSystemChildren = FileData.GetFileSystemEntries(path, "*").ToArray();
- await Kernel.Instance.ExecuteMetadataProviders(item, args).ConfigureAwait(false);
+ await Kernel.Instance.ExecuteMetadataProviders(item).ConfigureAwait(false);
return item;
}
diff --git a/MediaBrowser.Controller/Library/ItemResolveEventArgs.cs b/MediaBrowser.Controller/Library/ItemResolveEventArgs.cs
index 5d207de13..32b8783df 100644
--- a/MediaBrowser.Controller/Library/ItemResolveEventArgs.cs
+++ b/MediaBrowser.Controller/Library/ItemResolveEventArgs.cs
@@ -1,5 +1,7 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.IO;
+using System.Collections.Generic;
+using System.Linq;
using System;
using System.IO;
@@ -12,32 +14,45 @@ namespace MediaBrowser.Controller.Library
{
public WIN32_FIND_DATA[] FileSystemChildren { get; set; }
- public WIN32_FIND_DATA? GetFileSystemEntry(string path)
+ protected List<string> _additionalLocations = new List<string>();
+ public List<string> AdditionalLocations
{
- for (int i = 0; i < FileSystemChildren.Length; i++)
+ get
{
- WIN32_FIND_DATA entry = FileSystemChildren[i];
-
- if (entry.Path.Equals(path, StringComparison.OrdinalIgnoreCase))
- {
- return entry;
- }
+ return _additionalLocations;
+ }
+ set
+ {
+ _additionalLocations = value;
}
-
- return null;
}
- public bool ContainsFile(string name)
+ public IEnumerable<string> PhysicalLocations
{
- for (int i = 0; i < FileSystemChildren.Length; i++)
+ get
{
- if (FileSystemChildren[i].cFileName.Equals(name, StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
+ return (new List<string>() {this.Path}).Concat(AdditionalLocations);
}
+ }
- return false;
+ public bool IsBDFolder { get; set; }
+ public bool IsDVDFolder { get; set; }
+ public bool IsHDDVDFolder { get; set; }
+
+ /// <summary>
+ /// Store these to reduce disk access in Resolvers
+ /// </summary>
+ public string[] MetadataFiles { get; set; }
+
+ public WIN32_FIND_DATA? GetFileSystemEntry(string path)
+ {
+ WIN32_FIND_DATA entry = FileSystemChildren.FirstOrDefault(f => f.Path.Equals(path, StringComparison.OrdinalIgnoreCase));
+ return entry.cFileName != null ? (WIN32_FIND_DATA?)entry : null;
+ }
+
+ public bool ContainsFile(string name)
+ {
+ return FileSystemChildren.FirstOrDefault(f => f.cFileName.Equals(name, StringComparison.OrdinalIgnoreCase)).cFileName != null;
}
public bool ContainsFolder(string name)
diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
index fd2be689e..fc1e578e9 100644
--- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj
+++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
@@ -77,6 +77,9 @@
<Compile Include="Entities\UserItemData.cs" />
<Compile Include="Entities\Video.cs" />
<Compile Include="Entities\Year.cs" />
+ <Compile Include="IO\FileSystemHelper.cs" />
+ <Compile Include="Library\ChildrenChangedEventArgs.cs" />
+ <Compile Include="Providers\BaseProviderInfo.cs" />
<Compile Include="Providers\Movies\MovieProviderFromXml.cs" />
<Compile Include="Providers\Movies\MovieSpecialFeaturesProvider.cs" />
<Compile Include="Providers\TV\EpisodeImageFromMediaLocationProvider.cs" />
@@ -84,6 +87,7 @@
<Compile Include="Providers\TV\EpisodeXmlParser.cs" />
<Compile Include="Providers\TV\SeriesProviderFromXml.cs" />
<Compile Include="Providers\TV\SeriesXmlParser.cs" />
+ <Compile Include="Resolvers\EntityResolutionHelper.cs" />
<Compile Include="Resolvers\Movies\BoxSetResolver.cs" />
<Compile Include="Resolvers\Movies\MovieResolver.cs" />
<Compile Include="Resolvers\TV\EpisodeResolver.cs" />
diff --git a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs
index a30d39851..38afb2b52 100644
--- a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs
+++ b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs
@@ -1,6 +1,7 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Xml;
using MediaBrowser.Model.Entities;
+using MediaBrowser.Common.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
diff --git a/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs b/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs
index bae1ff0f9..50004be44 100644
--- a/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs
+++ b/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs
@@ -1,11 +1,23 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Common.Extensions;
using System.Threading.Tasks;
+using System;
namespace MediaBrowser.Controller.Providers
{
public abstract class BaseMetadataProvider
{
+ protected Guid _id;
+ public virtual Guid Id
+ {
+ get
+ {
+ if (_id == null) _id = this.GetType().FullName.GetMD5();
+ return _id;
+ }
+ }
+
public abstract bool Supports(BaseEntity item);
public virtual bool RequiresInternet
@@ -16,6 +28,57 @@ namespace MediaBrowser.Controller.Providers
}
}
+ /// <summary>
+ /// Returns the last refresh time of this provider for this item. Providers that care should
+ /// call SetLastRefreshed to update this value.
+ /// </summary>
+ /// <param name="item"></param>
+ /// <returns></returns>
+ protected virtual DateTime LastRefreshed(BaseEntity item)
+ {
+ return (item.ProviderData.GetValueOrDefault(this.Id, new BaseProviderInfo())).LastRefreshed;
+ }
+
+ /// <summary>
+ /// Sets the persisted last refresh date on the item for this provider.
+ /// </summary>
+ /// <param name="item"></param>
+ /// <param name="value"></param>
+ protected virtual void SetLastRefreshed(BaseEntity item, DateTime value)
+ {
+ var data = item.ProviderData.GetValueOrDefault(this.Id, new BaseProviderInfo());
+ data.LastRefreshed = value;
+ item.ProviderData[this.Id] = data;
+ }
+
+ /// <summary>
+ /// Returns whether or not this provider should be re-fetched. Default functionality can
+ /// compare a provided date with a last refresh time. This can be overridden for more complex
+ /// determinations.
+ /// </summary>
+ /// <returns></returns>
+ public virtual bool NeedsRefresh(BaseEntity item)
+ {
+ return CompareDate(item) > LastRefreshed(item);
+ }
+
+ /// <summary>
+ /// Override this to return the date that should be compared to the last refresh date
+ /// to determine if this provider should be re-fetched.
+ /// </summary>
+ protected virtual DateTime CompareDate(BaseEntity item)
+ {
+ return DateTime.MinValue.AddMinutes(1); // want this to be greater than mindate so new items will refresh
+ }
+
+ public virtual Task FetchIfNeededAsync(BaseEntity item)
+ {
+ if (this.NeedsRefresh(item))
+ return FetchAsync(item, item.ResolveArgs);
+ else
+ return new Task(() => { });
+ }
+
public abstract Task FetchAsync(BaseEntity item, ItemResolveEventArgs args);
public abstract MetadataProviderPriority Priority { get; }
diff --git a/MediaBrowser.Controller/Providers/BaseProviderInfo.cs b/MediaBrowser.Controller/Providers/BaseProviderInfo.cs
new file mode 100644
index 000000000..1538b2262
--- /dev/null
+++ b/MediaBrowser.Controller/Providers/BaseProviderInfo.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Controller.Providers
+{
+ public class BaseProviderInfo
+ {
+ public Guid ProviderId { get; set; }
+ public DateTime LastRefreshed { get; set; }
+
+ }
+}
diff --git a/MediaBrowser.Controller/Providers/Movies/MovieProviderFromXml.cs b/MediaBrowser.Controller/Providers/Movies/MovieProviderFromXml.cs
index f1d625aeb..7ef53d546 100644
--- a/MediaBrowser.Controller/Providers/Movies/MovieProviderFromXml.cs
+++ b/MediaBrowser.Controller/Providers/Movies/MovieProviderFromXml.cs
@@ -4,6 +4,7 @@ using MediaBrowser.Controller.Library;
using System.ComponentModel.Composition;
using System.IO;
using System.Threading.Tasks;
+using System;
namespace MediaBrowser.Controller.Providers.Movies
{
@@ -20,6 +21,12 @@ namespace MediaBrowser.Controller.Providers.Movies
get { return MetadataProviderPriority.First; }
}
+ protected override DateTime CompareDate(BaseEntity item)
+ {
+ var entry = item.ResolveArgs.GetFileSystemEntry(Path.Combine(item.Path, "movie.xml"));
+ return entry != null ? entry.Value.LastWriteTimeUtc : DateTime.MinValue;
+ }
+
public override async Task FetchAsync(BaseEntity item, ItemResolveEventArgs args)
{
await Task.Run(() => Fetch(item, args)).ConfigureAwait(false);
diff --git a/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs b/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs
index 1508252eb..7c9677e4e 100644
--- a/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs
+++ b/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs
@@ -1,6 +1,7 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Common.Extensions;
using System;
using System.IO;
@@ -39,7 +40,7 @@ namespace MediaBrowser.Controller.Resolvers
item.Parent = args.Parent;
}
- item.Id = Kernel.GetMD5(item.Path);
+ item.Id = (item.GetType().FullName + item.Path).GetMD5();
}
public BaseItem ResolvePath(ItemResolveEventArgs args)
diff --git a/MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs b/MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs
new file mode 100644
index 000000000..b821f8801
--- /dev/null
+++ b/MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.IO;
+using MediaBrowser.Controller.IO;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Entities.TV;
+
+namespace MediaBrowser.Controller.Resolvers
+{
+ public static class EntityResolutionHelper
+ {
+ /// <summary>
+ /// Any folder named in this list will be ignored - can be added to at runtime for extensibility
+ /// </summary>
+ public static List<string> IgnoreFolders = new List<string>()
+ {
+ "trailers",
+ "metadata",
+ "bdmv",
+ "certificate",
+ "backup",
+ "video_ts",
+ "audio_ts",
+ "ps3_update",
+ "ps3_vprm",
+ "adv_obj",
+ "hvdvd_ts"
+ };
+ /// <summary>
+ /// Determines whether a path should be resolved or ignored entirely - called before we even look at the contents
+ /// </summary>
+ /// <param name="path"></param>
+ /// <returns>false if the path should be ignored</returns>
+ public static bool ShouldResolvePath(WIN32_FIND_DATA path)
+ {
+ bool resolve = true;
+ // Ignore hidden files and folders
+ if (path.IsHidden || path.IsSystemFile)
+ {
+ resolve = false;
+ }
+
+ // Ignore any folders in our list
+ else if (path.IsDirectory && IgnoreFolders.Contains(Path.GetFileName(path.Path), StringComparer.OrdinalIgnoreCase))
+ {
+ resolve = false;
+ }
+
+ return resolve;
+ }
+
+ /// <summary>
+ /// Determines whether a path should be ignored based on its contents - called after the contents have been read
+ /// </summary>
+ public static bool ShouldResolvePathContents(ItemResolveEventArgs args)
+ {
+ bool resolve = true;
+ if (args.ContainsFile(".ignore"))
+ {
+ // Ignore any folders containing a file called .ignore
+ resolve = false;
+ }
+
+ return resolve;
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/Resolvers/Movies/MovieResolver.cs b/MediaBrowser.Controller/Resolvers/Movies/MovieResolver.cs
index 184cc3db4..825850b20 100644
--- a/MediaBrowser.Controller/Resolvers/Movies/MovieResolver.cs
+++ b/MediaBrowser.Controller/Resolvers/Movies/MovieResolver.cs
@@ -4,6 +4,7 @@ using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
using System.ComponentModel.Composition;
+using System.Collections.Generic;
namespace MediaBrowser.Controller.Resolvers.Movies
{
@@ -61,11 +62,35 @@ namespace MediaBrowser.Controller.Resolvers.Movies
private Movie GetMovie(ItemResolveEventArgs args)
{
- // Loop through each child file/folder and see if we find a video
- for (var i = 0; i < args.FileSystemChildren.Length; i++)
+ //first see if the discovery process has already determined we are a DVD or BD
+ if (args.IsDVDFolder)
+ {
+ return new Movie()
+ {
+ Path = args.Path,
+ VideoType = VideoType.Dvd
+ };
+ }
+ else if (args.IsBDFolder)
+ {
+ return new Movie()
+ {
+ Path = args.Path,
+ VideoType = VideoType.BluRay
+ };
+ }
+ else if (args.IsHDDVDFolder)
{
- var child = args.FileSystemChildren[i];
+ return new Movie()
+ {
+ Path = args.Path,
+ VideoType = VideoType.HdDvd
+ };
+ }
+ // Loop through each child file/folder and see if we find a video
+ foreach (var child in args.FileSystemChildren)
+ {
var childArgs = new ItemResolveEventArgs
{
FileInfo = child,
diff --git a/MediaBrowser.Controller/Resolvers/TV/SeasonResolver.cs b/MediaBrowser.Controller/Resolvers/TV/SeasonResolver.cs
index 528a55ae4..0ad0782e0 100644
--- a/MediaBrowser.Controller/Resolvers/TV/SeasonResolver.cs
+++ b/MediaBrowser.Controller/Resolvers/TV/SeasonResolver.cs
@@ -16,9 +16,6 @@ namespace MediaBrowser.Controller.Resolvers.TV
season.IndexNumber = TVUtils.GetSeasonNumberFromPath(args.Path);
- // Gather these now so that the episode provider classes can utilize them instead of having to make their own file system calls
- season.MetadataFiles = args.ContainsFolder("metadata") ? Directory.GetFiles(Path.Combine(args.Path, "metadata"), "*", SearchOption.TopDirectoryOnly) : new string[] { };
-
return season;
}
diff --git a/MediaBrowser.Controller/Resolvers/TV/TVUtils.cs b/MediaBrowser.Controller/Resolvers/TV/TVUtils.cs
index c40f3fa63..ec3305e16 100644
--- a/MediaBrowser.Controller/Resolvers/TV/TVUtils.cs
+++ b/MediaBrowser.Controller/Resolvers/TV/TVUtils.cs
@@ -128,7 +128,7 @@ namespace MediaBrowser.Controller.Resolvers.TV
}
else
{
- if (!string.IsNullOrEmpty(EpisodeNumberFromFile(child.Path, false)))
+ if (FileSystemHelper.IsVideoFile(child.Path) && !string.IsNullOrEmpty(EpisodeNumberFromFile(child.Path, false)))
{
return true;
}
diff --git a/MediaBrowser.Controller/Resolvers/VideoResolver.cs b/MediaBrowser.Controller/Resolvers/VideoResolver.cs
index e162fa509..bc3be5e43 100644
--- a/MediaBrowser.Controller/Resolvers/VideoResolver.cs
+++ b/MediaBrowser.Controller/Resolvers/VideoResolver.cs
@@ -1,6 +1,7 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
+using MediaBrowser.Controller.IO;
using System.ComponentModel.Composition;
using System.IO;
@@ -29,7 +30,7 @@ namespace MediaBrowser.Controller.Resolvers
// If the path is a file check for a matching extensions
if (!args.IsDirectory)
{
- if (IsVideoFile(args.Path))
+ if (FileSystemHelper.IsVideoFile(args.Path))
{
VideoType type = Path.GetExtension(args.Path).EndsWith("iso", System.StringComparison.OrdinalIgnoreCase) ? VideoType.Iso : VideoType.VideoFile;
@@ -95,38 +96,5 @@ namespace MediaBrowser.Controller.Resolvers
return null;
}
- private static bool IsVideoFile(string path)
- {
- string extension = Path.GetExtension(path).ToLower();
-
- switch (extension)
- {
- case ".mkv":
- case ".m2ts":
- case ".iso":
- case ".ts":
- case ".rmvb":
- case ".mov":
- case ".avi":
- case ".mpg":
- case ".mpeg":
- case ".wmv":
- case ".mp4":
- case ".divx":
- case ".dvr-ms":
- case ".wtv":
- case ".ogm":
- case ".ogv":
- case ".asf":
- case ".m4v":
- case ".flv":
- case ".f4v":
- case ".3gp":
- return true;
-
- default:
- return false;
- }
- }
}
}
diff --git a/MediaBrowser.Model/Entities/VideoType.cs b/MediaBrowser.Model/Entities/VideoType.cs
index b30b14690..0d46ff770 100644
--- a/MediaBrowser.Model/Entities/VideoType.cs
+++ b/MediaBrowser.Model/Entities/VideoType.cs
@@ -6,6 +6,7 @@ namespace MediaBrowser.Model.Entities
VideoFile,
Iso,
Dvd,
- BluRay
+ BluRay,
+ HdDvd
}
}
diff --git a/MediaBrowser.sln b/MediaBrowser.sln
index b3f1b7835..51f450743 100644
--- a/MediaBrowser.sln
+++ b/MediaBrowser.sln
@@ -24,6 +24,10 @@ Global
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {156EA256-AD2D-4D2F-B116-2ED4B9EFD869}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {156EA256-AD2D-4D2F-B116-2ED4B9EFD869}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {156EA256-AD2D-4D2F-B116-2ED4B9EFD869}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {156EA256-AD2D-4D2F-B116-2ED4B9EFD869}.Release|Any CPU.Build.0 = Release|Any CPU
{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -32,27 +36,20 @@ Global
{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Any CPU.Build.0 = Release|Any CPU
- {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.Build.0 = Release|Any CPU
- {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.Build.0 = Release|Any CPU
- {156EA256-AD2D-4D2F-B116-2ED4B9EFD869}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {156EA256-AD2D-4D2F-B116-2ED4B9EFD869}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {156EA256-AD2D-4D2F-B116-2ED4B9EFD869}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {156EA256-AD2D-4D2F-B116-2ED4B9EFD869}.Release|Any CPU.Build.0 = Release|Any CPU
{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
- GlobalSection(Performance) = preSolution
- HasPerformanceSessions = true
- EndGlobalSection
EndGlobal