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 ;
namespace Umbraco.Core.IO
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 ; }
}
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 ) ;
2016-09-22 08:33:11 +02:00
_macroPartialFileSystem = _macroPartialFileSystemWrapper = new ShadowWrapper ( _macroPartialFileSystem , "Views/MacroPartials" ) ;
_partialViewsFileSystem = _partialViewsFileSystemWrapper = new ShadowWrapper ( _partialViewsFileSystem , "Views/Partials" ) ;
_stylesheetsFileSystem = _stylesheetsFileSystemWrapper = new ShadowWrapper ( _stylesheetsFileSystem , "css" ) ;
_scriptsFileSystem = _scriptsFileSystemWrapper = new ShadowWrapper ( _scriptsFileSystem , "scripts" ) ;
_xsltFileSystem = _xsltFileSystemWrapper = new ShadowWrapper ( _xsltFileSystem , "xslt" ) ;
_masterPagesFileSystem = _masterPagesFileSystemWrapper = new ShadowWrapper ( _masterPagesFileSystem , "masterpages" ) ;
_mvcViewsFileSystem = _mvcViewsFileSystemWrapper = new ShadowWrapper ( _mvcViewsFileSystem , "Views" ) ;
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
}
2012-08-13 13:26:06 -01:00
2012-11-07 09:30:40 +05:00
private readonly ConcurrentDictionary < string , ProviderConstructionInfo > _providerLookup = new ConcurrentDictionary < string , ProviderConstructionInfo > ( ) ;
2016-09-16 16:18:59 +02:00
private readonly ConcurrentDictionary < Type , string > _aliases = new ConcurrentDictionary < Type , string > ( ) ;
2012-08-13 13:26:06 -01: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-11-07 09:30:40 +05: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-11-07 09:30:40 +05: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-11-07 09:30:40 +05: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-11-07 09:30:40 +05: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 ;
2016-09-16 16:18:59 +02:00
var constructor = providerType . GetConstructors ( ) . SingleOrDefault ( x
= > 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-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 + + )
parameters [ i ] = providerConfig . Parameters [ providerConfig . Parameters . AllKeys [ i ] ] . Value ;
2012-11-07 09:30:40 +05: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
var constructor = fsType . GetConstructors ( ) . SingleOrDefault ( x
= > 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-11-07 09:30:40 +05: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 ) ;
2016-09-22 08:33:11 +02:00
var shadowWrapper = new ShadowWrapper ( innerFs , "typed/" + alias ) ;
2016-09-16 16:18:59 +02:00
var fs = ( TFileSystem ) Activator . CreateInstance ( typeof ( TFileSystem ) , innerFs ) ;
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
// note
// shadowing is thread-safe, but entering and exiting shadow mode is not, and there is only one
// global shadow for the entire application, so great care should be taken to ensure that the
// application is *not* doing anything else when using a shadow.
// shadow applies to well-known filesystems *only* - at the moment, any other filesystem that would
// be created directly (via ctor) or via GetFileSystemProvider<T> is *not* shadowed.
// shadow must be enabled in an app event handler before anything else ie before any filesystem
// is actually created and used - after, it is too late - enabling shadow has a neglictible perfs
// impact.
// NO! by the time an app event handler is instanciated it is already too late, see note in ctor.
//internal void EnableShadow()
//{
// if (_mvcViewsFileSystem != null) // test one of the fs...
// throw new InvalidOperationException("Cannot enable shadow once filesystems have been created.");
// _shadowEnabled = true;
//}
2016-09-22 08:33:11 +02:00
public ShadowFileSystemsScope 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 ;
return ShadowFileSystemsScope . CreateScope ( 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
}
}