2012-08-13 13:26:06 -01:00
using System ;
2012-11-07 09:30:40 +05:00
using System.Collections.Concurrent ;
2012-08-13 13:26:06 -01:00
using System.Collections.Generic ;
using System.Configuration ;
using System.Linq ;
2012-11-07 09:30:40 +05:00
using System.Reflection ;
2012-08-13 13:26:06 -01:00
using Umbraco.Core.Configuration ;
2017-02-03 20:01:43 +01:00
using Umbraco.Core.Scoping ;
2012-08-13 13:26:06 -01:00
namespace Umbraco.Core.IO
2017-02-03 20:01:43 +01:00
{
2013-05-14 14:18:41 -10:00
public class FileSystemProviderManager
2012-08-13 13:26:06 -01:00
{
private readonly FileSystemProvidersSection _config ;
2016-09-22 08:33:11 +02:00
private readonly WeakSet < ShadowWrapper > _wrappers = new WeakSet < ShadowWrapper >();
2012-08-13 13:26:06 -01:00
2016-09-16 16:18:59 +02:00
// actual well-known filesystems returned by properties
private readonly IFileSystem2 _macroPartialFileSystem ;
private readonly IFileSystem2 _partialViewsFileSystem ;
private readonly IFileSystem2 _stylesheetsFileSystem ;
private readonly IFileSystem2 _scriptsFileSystem ;
private readonly IFileSystem2 _xsltFileSystem ;
private readonly IFileSystem2 _masterPagesFileSystem ;
private readonly IFileSystem2 _mvcViewsFileSystem ;
// when shadowing is enabled, above filesystems, as wrappers
2016-09-22 08:33:11 +02:00
private readonly ShadowWrapper _macroPartialFileSystemWrapper ;
private readonly ShadowWrapper _partialViewsFileSystemWrapper ;
private readonly ShadowWrapper _stylesheetsFileSystemWrapper ;
private readonly ShadowWrapper _scriptsFileSystemWrapper ;
private readonly ShadowWrapper _xsltFileSystemWrapper ;
private readonly ShadowWrapper _masterPagesFileSystemWrapper ;
private readonly ShadowWrapper _mvcViewsFileSystemWrapper ;
2016-09-16 16:18:59 +02:00
#region Singleton & Constructor
2012-08-13 13:26:06 -01:00
private static readonly FileSystemProviderManager Instance = new FileSystemProviderManager ();
public static FileSystemProviderManager Current
{
get { return Instance ; }
}
2017-02-03 20:01:43 +01:00
private IScopeProviderInternal ScopeProvider
{
// fixme - 'course this is bad, but enough for now
get { return ApplicationContext . Current == null ? null : ApplicationContext . Current . ScopeProvider as IScopeProviderInternal ; }
}
2013-05-14 14:18:41 -10:00
internal FileSystemProviderManager ()
2012-08-13 13:26:06 -01:00
{
2016-09-16 16:18:59 +02:00
_config = ( FileSystemProvidersSection ) ConfigurationManager . GetSection ( "umbracoConfiguration/FileSystemProviders" );
_macroPartialFileSystem = new PhysicalFileSystem ( SystemDirectories . MacroPartials );
_partialViewsFileSystem = new PhysicalFileSystem ( SystemDirectories . PartialViews );
_stylesheetsFileSystem = new PhysicalFileSystem ( SystemDirectories . Css );
_scriptsFileSystem = new PhysicalFileSystem ( SystemDirectories . Scripts );
_xsltFileSystem = new PhysicalFileSystem ( SystemDirectories . Xslt );
_masterPagesFileSystem = new PhysicalFileSystem ( SystemDirectories . Masterpages );
_mvcViewsFileSystem = new PhysicalFileSystem ( SystemDirectories . MvcViews );
2017-02-03 20:01:43 +01:00
_macroPartialFileSystem = _macroPartialFileSystemWrapper = new ShadowWrapper ( _macroPartialFileSystem , "Views/MacroPartials" , ScopeProvider );
_partialViewsFileSystem = _partialViewsFileSystemWrapper = new ShadowWrapper ( _partialViewsFileSystem , "Views/Partials" , ScopeProvider );
_stylesheetsFileSystem = _stylesheetsFileSystemWrapper = new ShadowWrapper ( _stylesheetsFileSystem , "css" , ScopeProvider );
_scriptsFileSystem = _scriptsFileSystemWrapper = new ShadowWrapper ( _scriptsFileSystem , "scripts" , ScopeProvider );
_xsltFileSystem = _xsltFileSystemWrapper = new ShadowWrapper ( _xsltFileSystem , "xslt" , ScopeProvider );
_masterPagesFileSystem = _masterPagesFileSystemWrapper = new ShadowWrapper ( _masterPagesFileSystem , "masterpages" , ScopeProvider );
_mvcViewsFileSystem = _mvcViewsFileSystemWrapper = new ShadowWrapper ( _mvcViewsFileSystem , "Views" , ScopeProvider );
2016-09-16 16:18:59 +02:00
2016-09-22 08:33:11 +02:00
// filesystems obtained from GetFileSystemProvider are already wrapped and do not need to be wrapped again
2016-09-16 16:18:59 +02:00
MediaFileSystem = GetFileSystemProvider < MediaFileSystem >();
2012-08-13 13:26:06 -01:00
}
#endregion
2016-09-16 16:18:59 +02:00
#region Well - Known FileSystems
public IFileSystem2 MacroPartialsFileSystem { get { return _macroPartialFileSystem ; } }
public IFileSystem2 PartialViewsFileSystem { get { return _partialViewsFileSystem ; } }
public IFileSystem2 StylesheetsFileSystem { get { return _stylesheetsFileSystem ; } }
public IFileSystem2 ScriptsFileSystem { get { return _scriptsFileSystem ; } }
public IFileSystem2 XsltFileSystem { get { return _xsltFileSystem ; } }
public IFileSystem2 MasterPagesFileSystem { get { return _masterPagesFileSystem ; } }
public IFileSystem2 MvcViewsFileSystem { get { return _mvcViewsFileSystem ; } }
public MediaFileSystem MediaFileSystem { get ; private set ; }
#endregion
#region Providers
/// <summary>
/// used to cache the lookup of how to construct this object so we don't have to reflect each time.
/// </summary>
private class ProviderConstructionInfo
2012-11-07 09:30:40 +05:00
{
public object [] Parameters { get ; set ; }
public ConstructorInfo Constructor { get ; set ; }
2016-09-16 16:18:59 +02:00
//public string ProviderAlias { get; set; }
2012-11-07 09:30:40 +05:00
}
private readonly ConcurrentDictionary < string , ProviderConstructionInfo > _providerLookup = new ConcurrentDictionary < string , ProviderConstructionInfo >();
2017-02-03 20:01:43 +01:00
private readonly ConcurrentDictionary < Type , string > _aliases = new ConcurrentDictionary < Type , string >();
2012-11-07 09:30:40 +05:00
2013-05-14 14:18:41 -10:00
/// <summary>
2016-09-16 16:18:59 +02:00
/// Gets an underlying (non-typed) filesystem supporting a strongly-typed filesystem.
2013-05-14 14:18:41 -10:00
/// </summary>
2016-09-16 16:18:59 +02:00
/// <param name="alias">The alias of the strongly-typed filesystem.</param>
/// <returns>The non-typed filesystem supporting the strongly-typed filesystem with the specified alias.</returns>
/// <remarks>This method should not be used directly, used <see cref="GetFileSystemProvider{TFileSystem}"/> instead.</remarks>
2013-05-14 14:18:41 -10:00
public IFileSystem GetUnderlyingFileSystemProvider ( string alias )
2012-08-13 13:26:06 -01:00
{
2016-09-16 16:18:59 +02:00
// either get the constructor info from cache or create it and add to cache
2012-11-07 09:30:40 +05:00
var ctorInfo = _providerLookup . GetOrAdd ( alias , s =>
{
2016-09-16 16:18:59 +02:00
// get config
2012-11-07 09:30:40 +05:00
var providerConfig = _config . Providers [ s ];
if ( providerConfig == null )
2016-09-16 16:18:59 +02:00
throw new ArgumentException ( string . Format ( "No provider found with alias {0}." , s ));
2012-08-13 13:26:06 -01:00
2016-09-16 16:18:59 +02:00
// get the filesystem type
2012-11-07 09:30:40 +05:00
var providerType = Type . GetType ( providerConfig . Type );
if ( providerType == null )
2016-09-16 16:18:59 +02:00
throw new InvalidOperationException ( string . Format ( "Could not find type {0}." , providerConfig . Type ));
2012-08-13 13:26:06 -01:00
2016-09-16 16:18:59 +02:00
// ensure it implements IFileSystem
2012-11-07 09:30:40 +05:00
if ( providerType . IsAssignableFrom ( typeof ( IFileSystem )))
2016-09-16 16:18:59 +02:00
throw new InvalidOperationException ( string . Format ( "Type {0} does not implement IFileSystem." , providerType . FullName ));
2012-08-13 13:26:06 -01:00
2016-09-16 16:18:59 +02:00
// find a ctor matching the config parameters
2012-11-07 09:30:40 +05:00
var paramCount = providerConfig . Parameters != null ? providerConfig . Parameters . Count : 0 ;
2017-02-03 20:01:43 +01:00
var constructor = providerType . GetConstructors (). SingleOrDefault ( x
2016-09-16 16:18:59 +02:00
=> x . GetParameters (). Length == paramCount && x . GetParameters (). All ( y => providerConfig . Parameters . AllKeys . Contains ( y . Name )));
2012-11-07 09:30:40 +05:00
if ( constructor == null )
2016-09-16 16:18:59 +02:00
throw new InvalidOperationException ( string . Format ( "Type {0} has no ctor matching the {1} configuration parameter(s)." , providerType . FullName , paramCount ));
2012-08-13 13:26:06 -01:00
2012-11-07 09:30:40 +05:00
var parameters = new object [ paramCount ];
2016-09-16 16:18:59 +02:00
if ( providerConfig . Parameters != null ) // keeps ReSharper happy
for ( var i = 0 ; i < paramCount ; i ++)
2017-02-03 20:01:43 +01:00
parameters [ i ] = providerConfig . Parameters [ providerConfig . Parameters . AllKeys [ i ]]. Value ;
2012-08-13 13:26:06 -01:00
2016-09-16 16:18:59 +02:00
return new ProviderConstructionInfo
2012-11-07 09:30:40 +05:00
{
Constructor = constructor ,
Parameters = parameters ,
2016-09-16 16:18:59 +02:00
//ProviderAlias = s
2012-11-07 09:30:40 +05:00
};
});
2016-09-16 16:18:59 +02:00
// create the fs and return
return ( IFileSystem ) ctorInfo . Constructor . Invoke ( ctorInfo . Parameters );
2012-08-13 13:26:06 -01:00
}
2012-08-20 09:42:32 -01:00
2013-05-14 14:18:41 -10:00
/// <summary>
2016-09-16 16:18:59 +02:00
/// Gets a strongly-typed filesystem.
2013-05-14 14:18:41 -10:00
/// </summary>
2016-09-16 16:18:59 +02:00
/// <typeparam name="TFileSystem">The type of the filesystem.</typeparam>
/// <returns>A strongly-typed filesystem of the specified type.</returns>
public TFileSystem GetFileSystemProvider < TFileSystem >()
where TFileSystem : FileSystemWrapper
2012-08-20 09:42:32 -01:00
{
2016-09-16 16:18:59 +02:00
// deal with known types - avoid infinite loops!
if ( typeof ( TFileSystem ) == typeof ( MediaFileSystem ) && MediaFileSystem != null )
return MediaFileSystem as TFileSystem ; // else create and return
// get/cache the alias for the filesystem type
var alias = _aliases . GetOrAdd ( typeof ( TFileSystem ), fsType =>
2012-11-07 09:30:40 +05:00
{
2016-09-16 16:18:59 +02:00
// validate the ctor
2017-02-03 20:01:43 +01:00
var constructor = fsType . GetConstructors (). SingleOrDefault ( x
2016-09-16 16:18:59 +02:00
=> x . GetParameters (). Length == 1 && TypeHelper . IsTypeAssignableFrom < IFileSystem >( x . GetParameters (). Single (). ParameterType ));
2012-11-07 09:30:40 +05:00
if ( constructor == null )
2016-09-16 16:18:59 +02:00
throw new InvalidOperationException ( "Type " + fsType . FullName + " must inherit from FileSystemWrapper and have a constructor that accepts one parameter of type " + typeof ( IFileSystem ). FullName + "." );
2012-08-20 09:42:32 -01:00
2016-09-16 16:18:59 +02:00
// find the attribute and get the alias
var attr = ( FileSystemProviderAttribute ) fsType . GetCustomAttributes ( typeof ( FileSystemProviderAttribute ), false ). SingleOrDefault ();
2012-11-07 09:30:40 +05:00
if ( attr == null )
2016-09-16 16:18:59 +02:00
throw new InvalidOperationException ( "Type " + fsType . FullName + "is missing the required FileSystemProviderAttribute." );
2012-11-07 09:30:40 +05:00
return attr . Alias ;
});
2016-09-22 08:33:11 +02:00
2016-09-16 16:18:59 +02:00
// gets the inner fs, create the strongly-typed fs wrapping the inner fs, register & return
2016-09-22 08:33:11 +02:00
// so we are double-wrapping here
// could be optimized by having FileSystemWrapper inherit from ShadowWrapper, maybe
2013-05-14 14:18:41 -10:00
var innerFs = GetUnderlyingFileSystemProvider ( alias );
2017-02-03 20:01:43 +01:00
var shadowWrapper = new ShadowWrapper ( innerFs , "typed/" + alias , ScopeProvider );
var fs = ( TFileSystem ) Activator . CreateInstance ( typeof ( TFileSystem ), shadowWrapper );
2016-09-22 08:33:11 +02:00
_wrappers . Add ( shadowWrapper ); // keeping a weak reference to the wrapper
2016-09-16 16:18:59 +02:00
return fs ;
}
#endregion
#region Shadow
2017-02-03 20:01:43 +01:00
internal ICompletable Shadow ( Guid id )
2016-09-16 16:18:59 +02:00
{
2016-09-22 08:33:11 +02:00
var typed = _wrappers . ToArray ();
var wrappers = new ShadowWrapper [ typed . Length + 7 ];
var i = 0 ;
while ( i < typed . Length ) wrappers [ i ] = typed [ i ++];
wrappers [ i ++] = _macroPartialFileSystemWrapper ;
wrappers [ i ++] = _partialViewsFileSystemWrapper ;
wrappers [ i ++] = _stylesheetsFileSystemWrapper ;
wrappers [ i ++] = _scriptsFileSystemWrapper ;
wrappers [ i ++] = _xsltFileSystemWrapper ;
wrappers [ i ++] = _masterPagesFileSystemWrapper ;
wrappers [ i ] = _mvcViewsFileSystemWrapper ;
2016-09-16 16:18:59 +02:00
2017-02-03 20:01:43 +01:00
return new ShadowFileSystems ( id , wrappers );
2016-09-16 16:18:59 +02:00
}
#endregion
private class WeakSet < T >
where T : class
{
private readonly HashSet < WeakReference < T >> _set = new HashSet < WeakReference < T >>();
public void Add ( T item )
{
lock ( _set )
{
_set . Add ( new WeakReference < T >( item ));
CollectLocked ();
}
}
public T [] ToArray ()
{
lock ( _set )
{
CollectLocked ();
return _set . Select ( x =>
{
T target ;
return x . TryGetTarget ( out target ) ? target : null ;
}). WhereNotNull (). ToArray ();
}
}
private void CollectLocked ()
{
_set . RemoveWhere ( x =>
{
T target ;
return x . TryGetTarget ( out target ) == false ;
});
}
2012-08-20 09:42:32 -01:00
}
2012-08-13 13:26:06 -01:00
}
}