2013-03-04 05:43:06 +00:00
using MediaBrowser.Common.Configuration ;
2013-03-07 05:34:00 +00:00
using MediaBrowser.Common.Events ;
2013-03-04 05:43:06 +00:00
using MediaBrowser.Common.Implementations.Logging ;
using MediaBrowser.Common.Implementations.NetworkManagement ;
using MediaBrowser.Common.Implementations.ScheduledTasks ;
using MediaBrowser.Common.Implementations.Security ;
using MediaBrowser.Common.Implementations.Serialization ;
2013-02-27 17:07:12 +00:00
using MediaBrowser.Common.Implementations.Updates ;
2013-02-27 16:46:48 +00:00
using MediaBrowser.Common.Net ;
2013-03-03 02:47:04 +00:00
using MediaBrowser.Common.Plugins ;
2013-02-27 16:46:48 +00:00
using MediaBrowser.Common.ScheduledTasks ;
2013-03-04 05:43:06 +00:00
using MediaBrowser.Common.Security ;
2013-02-27 14:39:11 +00:00
using MediaBrowser.Common.Updates ;
2013-02-26 03:43:04 +00:00
using MediaBrowser.Model.Logging ;
using MediaBrowser.Model.Serialization ;
2013-03-04 05:43:06 +00:00
using MediaBrowser.Model.Updates ;
2013-02-26 03:43:04 +00:00
using SimpleInjector ;
using System ;
using System.Collections.Generic ;
2013-02-27 04:19:05 +00:00
using System.IO ;
using System.Linq ;
using System.Reflection ;
2013-02-26 03:43:04 +00:00
using System.Threading ;
2013-03-04 05:43:06 +00:00
using System.Threading.Tasks ;
2013-02-26 03:43:04 +00:00
namespace MediaBrowser.Common.Implementations
{
2013-03-10 06:45:16 +00:00
/// <summary>
/// Class BaseApplicationHost
/// </summary>
/// <typeparam name="TApplicationPathsType">The type of the T application paths type.</typeparam>
2013-03-04 05:43:06 +00:00
public abstract class BaseApplicationHost < TApplicationPathsType > : IApplicationHost
where TApplicationPathsType : class , IApplicationPaths , new ( )
2013-02-26 03:43:04 +00:00
{
2013-03-07 05:34:00 +00:00
/// <summary>
/// Occurs when [has pending restart changed].
/// </summary>
public event EventHandler HasPendingRestartChanged ;
/// <summary>
/// Occurs when [application updated].
/// </summary>
public event EventHandler < GenericEventArgs < Version > > ApplicationUpdated ;
/// <summary>
/// Gets or sets a value indicating whether this instance has changes that require the entire application to restart.
/// </summary>
/// <value><c>true</c> if this instance has pending application restart; otherwise, <c>false</c>.</value>
public bool HasPendingRestart { get ; private set ; }
2013-02-26 03:43:04 +00:00
/// <summary>
/// Gets or sets the logger.
/// </summary>
/// <value>The logger.</value>
2013-03-04 05:43:06 +00:00
protected ILogger Logger { get ; private set ; }
2013-02-26 03:43:04 +00:00
2013-03-03 02:47:04 +00:00
/// <summary>
/// Gets or sets the plugins.
/// </summary>
/// <value>The plugins.</value>
public IEnumerable < IPlugin > Plugins { get ; protected set ; }
2013-02-26 17:21:18 +00:00
/// <summary>
/// Gets or sets the log manager.
/// </summary>
/// <value>The log manager.</value>
public ILogManager LogManager { get ; protected set ; }
/// <summary>
/// Gets the application paths.
/// </summary>
/// <value>The application paths.</value>
2013-03-04 05:43:06 +00:00
protected TApplicationPathsType ApplicationPaths = new TApplicationPathsType ( ) ;
2013-02-26 17:21:18 +00:00
2013-02-26 03:43:04 +00:00
/// <summary>
/// The container
/// </summary>
protected readonly Container Container = new Container ( ) ;
2013-03-03 07:27:40 +00:00
2013-03-04 05:43:06 +00:00
/// <summary>
/// The json serializer
/// </summary>
2013-03-08 05:08:27 +00:00
public readonly IJsonSerializer JsonSerializer = new JsonSerializer ( ) ;
2013-03-04 05:43:06 +00:00
/// <summary>
/// The _XML serializer
/// </summary>
protected readonly IXmlSerializer XmlSerializer = new XmlSerializer ( ) ;
2013-02-26 03:43:04 +00:00
/// <summary>
/// Gets assemblies that failed to load
/// </summary>
2013-03-10 06:45:16 +00:00
/// <value>The failed assemblies.</value>
2013-02-26 03:43:04 +00:00
public List < string > FailedAssemblies { get ; protected set ; }
/// <summary>
/// Gets all types within all running assemblies
/// </summary>
/// <value>All types.</value>
public Type [ ] AllTypes { get ; protected set ; }
/// <summary>
/// Gets all concrete types.
/// </summary>
/// <value>All concrete types.</value>
public Type [ ] AllConcreteTypes { get ; protected set ; }
/// <summary>
/// The disposable parts
/// </summary>
protected readonly List < IDisposable > DisposableParts = new List < IDisposable > ( ) ;
2013-02-26 17:21:18 +00:00
/// <summary>
/// Gets a value indicating whether this instance is first run.
/// </summary>
/// <value><c>true</c> if this instance is first run; otherwise, <c>false</c>.</value>
public bool IsFirstRun { get ; private set ; }
2013-03-04 05:43:06 +00:00
/// <summary>
/// Gets the kernel.
/// </summary>
/// <value>The kernel.</value>
protected ITaskManager TaskManager { get ; private set ; }
2013-03-10 06:45:16 +00:00
/// <summary>
/// Gets the security manager.
/// </summary>
/// <value>The security manager.</value>
2013-03-04 05:43:06 +00:00
protected ISecurityManager SecurityManager { get ; private set ; }
2013-03-10 06:45:16 +00:00
/// <summary>
/// Gets the package manager.
/// </summary>
/// <value>The package manager.</value>
2013-03-05 04:25:27 +00:00
protected IPackageManager PackageManager { get ; private set ; }
2013-03-10 06:45:16 +00:00
/// <summary>
/// Gets the HTTP client.
/// </summary>
/// <value>The HTTP client.</value>
2013-03-05 04:25:27 +00:00
protected IHttpClient HttpClient { get ; private set ; }
2013-03-10 06:45:16 +00:00
/// <summary>
/// Gets the network manager.
/// </summary>
/// <value>The network manager.</value>
2013-03-07 05:34:00 +00:00
protected INetworkManager NetworkManager { get ; private set ; }
2013-03-04 05:43:06 +00:00
2013-03-10 06:45:16 +00:00
/// <summary>
/// Gets the configuration manager.
/// </summary>
/// <value>The configuration manager.</value>
2013-03-04 05:43:06 +00:00
protected IConfigurationManager ConfigurationManager { get ; private set ; }
2013-02-26 03:43:04 +00:00
/// <summary>
/// Initializes a new instance of the <see cref="BaseApplicationHost" /> class.
/// </summary>
protected BaseApplicationHost ( )
{
FailedAssemblies = new List < string > ( ) ;
2013-03-04 05:43:06 +00:00
LogManager = new NlogManager ( ApplicationPaths . LogDirectoryPath , LogFilePrefixName ) ;
ConfigurationManager = GetConfigurationManager ( ) ;
2013-03-03 07:27:40 +00:00
}
2013-02-26 17:21:18 +00:00
2013-03-03 07:27:40 +00:00
/// <summary>
/// Inits this instance.
/// </summary>
/// <returns>Task.</returns>
2013-03-04 18:13:47 +00:00
public virtual async Task Init ( )
2013-03-03 07:27:40 +00:00
{
2013-03-04 18:13:47 +00:00
IsFirstRun = ! ConfigurationManager . CommonConfiguration . IsStartupWizardCompleted ;
2013-02-26 17:21:18 +00:00
2013-03-04 18:13:47 +00:00
Logger = LogManager . GetLogger ( "App" ) ;
2013-03-04 05:43:06 +00:00
2013-03-04 18:13:47 +00:00
LogManager . ReloadLogger ( ConfigurationManager . CommonConfiguration . EnableDebugLevelLogging ? LogSeverity . Debug : LogSeverity . Info ) ;
2013-03-04 05:43:06 +00:00
2013-03-05 23:11:32 +00:00
DiscoverTypes ( ) ;
2013-03-04 18:13:47 +00:00
Logger . Info ( "Version {0} initializing" , ApplicationVersion ) ;
2013-03-04 05:43:06 +00:00
2013-03-04 18:13:47 +00:00
await RegisterResources ( ) . ConfigureAwait ( false ) ;
2013-03-04 05:43:06 +00:00
2013-03-04 18:13:47 +00:00
FindParts ( ) ;
2013-03-04 05:43:06 +00:00
2013-03-10 06:45:16 +00:00
await RunStartupTasks ( ) . ConfigureAwait ( false ) ;
}
/// <summary>
/// Runs the startup tasks.
/// </summary>
/// <returns>Task.</returns>
protected virtual Task RunStartupTasks ( )
{
return Task . Run ( ( ) = >
{
Resolve < ITaskManager > ( ) . AddTasks ( GetExports < IScheduledTask > ( false ) ) ;
Task . Run ( ( ) = > ConfigureAutoRunAtStartup ( ) ) ;
2013-03-13 22:33:09 +00:00
ConfigurationManager . ConfigurationUpdated + = ConfigurationManager_ConfigurationUpdated ;
2013-03-10 06:45:16 +00:00
} ) ;
2013-02-26 03:43:04 +00:00
}
/// <summary>
/// Gets the composable part assemblies.
/// </summary>
/// <returns>IEnumerable{Assembly}.</returns>
protected abstract IEnumerable < Assembly > GetComposablePartAssemblies ( ) ;
2013-02-26 17:21:18 +00:00
/// <summary>
2013-03-04 05:43:06 +00:00
/// Gets the name of the log file prefix.
2013-02-26 17:21:18 +00:00
/// </summary>
2013-03-04 05:43:06 +00:00
/// <value>The name of the log file prefix.</value>
protected abstract string LogFilePrefixName { get ; }
2013-02-26 17:21:18 +00:00
2013-03-10 06:45:16 +00:00
/// <summary>
/// Gets the configuration manager.
/// </summary>
/// <returns>IConfigurationManager.</returns>
2013-03-04 05:43:06 +00:00
protected abstract IConfigurationManager GetConfigurationManager ( ) ;
2013-02-27 16:46:48 +00:00
/// <summary>
/// Finds the parts.
/// </summary>
protected virtual void FindParts ( )
{
2013-03-03 02:47:04 +00:00
Plugins = GetExports < IPlugin > ( ) ;
2013-02-27 16:46:48 +00:00
}
2013-03-03 07:27:40 +00:00
2013-02-26 03:43:04 +00:00
/// <summary>
/// Discovers the types.
/// </summary>
protected void DiscoverTypes ( )
{
FailedAssemblies . Clear ( ) ;
2013-03-05 23:11:32 +00:00
var assemblies = GetComposablePartAssemblies ( ) . ToArray ( ) ;
AllTypes = assemblies . SelectMany ( GetTypes ) . ToArray ( ) ;
2013-02-26 03:43:04 +00:00
AllConcreteTypes = AllTypes . Where ( t = > t . IsClass & & ! t . IsAbstract & & ! t . IsInterface & & ! t . IsGenericType ) . ToArray ( ) ;
}
2013-02-27 16:46:48 +00:00
/// <summary>
/// Registers resources that classes will depend on
/// </summary>
2013-03-10 06:45:16 +00:00
/// <returns>Task.</returns>
2013-03-04 18:13:47 +00:00
protected virtual Task RegisterResources ( )
2013-02-27 16:46:48 +00:00
{
2013-03-04 18:13:47 +00:00
return Task . Run ( ( ) = >
{
RegisterSingleInstance ( ConfigurationManager ) ;
RegisterSingleInstance < IApplicationHost > ( this ) ;
2013-03-04 05:43:06 +00:00
2013-03-04 18:13:47 +00:00
RegisterSingleInstance < IApplicationPaths > ( ApplicationPaths ) ;
2013-03-04 05:43:06 +00:00
2013-03-07 05:34:00 +00:00
TaskManager = new TaskManager ( ApplicationPaths , JsonSerializer , Logger ) ;
2013-03-04 05:43:06 +00:00
2013-03-04 18:13:47 +00:00
RegisterSingleInstance ( JsonSerializer ) ;
RegisterSingleInstance ( XmlSerializer ) ;
2013-03-04 05:43:06 +00:00
2013-03-04 18:13:47 +00:00
RegisterSingleInstance ( LogManager ) ;
RegisterSingleInstance ( Logger ) ;
2013-02-27 16:46:48 +00:00
2013-03-04 18:13:47 +00:00
RegisterSingleInstance ( TaskManager ) ;
2013-02-27 16:46:48 +00:00
2013-03-05 04:25:27 +00:00
HttpClient = new HttpClientManager . HttpClientManager ( ApplicationPaths , Logger ) ;
RegisterSingleInstance ( HttpClient ) ;
2013-03-04 05:43:06 +00:00
2013-03-07 05:34:00 +00:00
NetworkManager = new NetworkManager ( ) ;
RegisterSingleInstance ( NetworkManager ) ;
SecurityManager = new PluginSecurityManager ( this , HttpClient , JsonSerializer , ApplicationPaths ) ;
2013-03-04 18:13:47 +00:00
RegisterSingleInstance ( SecurityManager ) ;
2013-03-04 05:43:06 +00:00
2013-03-07 05:34:00 +00:00
PackageManager = new PackageManager ( SecurityManager , NetworkManager , HttpClient , ApplicationPaths , JsonSerializer , Logger ) ;
2013-03-05 04:25:27 +00:00
RegisterSingleInstance ( PackageManager ) ;
2013-03-04 18:13:47 +00:00
} ) ;
2013-02-27 16:46:48 +00:00
}
2013-03-03 07:27:40 +00:00
2013-02-26 03:43:04 +00:00
/// <summary>
/// Gets a list of types within an assembly
/// This will handle situations that would normally throw an exception - such as a type within the assembly that depends on some other non-existant reference
/// </summary>
/// <param name="assembly">The assembly.</param>
/// <returns>IEnumerable{Type}.</returns>
/// <exception cref="System.ArgumentNullException">assembly</exception>
protected IEnumerable < Type > GetTypes ( Assembly assembly )
{
if ( assembly = = null )
{
throw new ArgumentNullException ( "assembly" ) ;
}
try
{
return assembly . GetTypes ( ) ;
}
catch ( ReflectionTypeLoadException ex )
{
// If it fails we can still get a list of the Types it was able to resolve
return ex . Types . Where ( t = > t ! = null ) ;
}
}
/// <summary>
/// Creates an instance of type and resolves all constructor dependancies
/// </summary>
/// <param name="type">The type.</param>
/// <returns>System.Object.</returns>
public object CreateInstance ( Type type )
{
try
{
return Container . GetInstance ( type ) ;
}
catch
{
Logger . Error ( "Error creating {0}" , type . Name ) ;
throw ;
}
}
/// <summary>
/// Registers the specified obj.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="obj">The obj.</param>
2013-02-26 16:10:55 +00:00
/// <param name="manageLifetime">if set to <c>true</c> [manage lifetime].</param>
protected void RegisterSingleInstance < T > ( T obj , bool manageLifetime = true )
2013-02-26 03:43:04 +00:00
where T : class
{
2013-04-07 22:09:48 +00:00
Logger . Info ( "Registering " + obj . GetType ( ) . Name ) ;
2013-02-26 03:43:04 +00:00
Container . RegisterSingle ( obj ) ;
2013-02-26 16:10:55 +00:00
if ( manageLifetime )
{
var disposable = obj as IDisposable ;
if ( disposable ! = null )
{
DisposableParts . Add ( disposable ) ;
}
}
2013-02-26 03:43:04 +00:00
}
/// <summary>
/// Registers the single instance.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="func">The func.</param>
protected void RegisterSingleInstance < T > ( Func < T > func )
where T : class
{
Container . RegisterSingle ( func ) ;
}
/// <summary>
/// Resolves this instance.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns>``0.</returns>
public T Resolve < T > ( )
{
return ( T ) Container . GetRegistration ( typeof ( T ) , true ) . GetInstance ( ) ;
}
/// <summary>
/// Resolves this instance.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns>``0.</returns>
public T TryResolve < T > ( )
{
var result = Container . GetRegistration ( typeof ( T ) , false ) ;
if ( result = = null )
{
return default ( T ) ;
}
return ( T ) result . GetInstance ( ) ;
}
/// <summary>
/// Loads the assembly.
/// </summary>
/// <param name="file">The file.</param>
/// <returns>Assembly.</returns>
protected Assembly LoadAssembly ( string file )
{
try
{
return Assembly . Load ( File . ReadAllBytes ( ( file ) ) ) ;
}
catch ( Exception ex )
{
FailedAssemblies . Add ( file ) ;
Logger . ErrorException ( "Error loading assembly {0}" , ex , file ) ;
return null ;
}
}
/// <summary>
/// Gets the exports.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="manageLiftime">if set to <c>true</c> [manage liftime].</param>
/// <returns>IEnumerable{``0}.</returns>
public IEnumerable < T > GetExports < T > ( bool manageLiftime = true )
{
var currentType = typeof ( T ) ;
Logger . Info ( "Composing instances of " + currentType . Name ) ;
2013-03-04 05:43:06 +00:00
var parts = AllConcreteTypes . AsParallel ( ) . Where ( currentType . IsAssignableFrom ) . Select ( CreateInstance ) . Cast < T > ( ) . ToArray ( ) ;
2013-02-26 03:43:04 +00:00
if ( manageLiftime )
{
2013-03-15 01:40:38 +00:00
lock ( DisposableParts )
{
DisposableParts . AddRange ( parts . OfType < IDisposable > ( ) ) ;
}
2013-02-26 03:43:04 +00:00
}
return parts ;
}
/// <summary>
/// Gets the current application version
/// </summary>
/// <value>The application version.</value>
public Version ApplicationVersion
{
get
{
return GetType ( ) . Assembly . GetName ( ) . Version ;
}
}
2013-03-12 19:28:06 +00:00
/// <summary>
/// Defines the full path to our shortcut in the start menu
/// </summary>
protected abstract string ProductShortcutPath { get ; }
2013-03-13 22:33:09 +00:00
/// <summary>
/// Handles the ConfigurationUpdated event of the ConfigurationManager control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
/// <exception cref="System.NotImplementedException"></exception>
void ConfigurationManager_ConfigurationUpdated ( object sender , EventArgs e )
{
ConfigureAutoRunAtStartup ( ) ;
}
2013-02-27 21:07:51 +00:00
/// <summary>
/// Configures the auto run at startup.
/// </summary>
2013-03-04 05:43:06 +00:00
private void ConfigureAutoRunAtStartup ( )
2013-02-27 21:07:51 +00:00
{
2013-03-12 19:28:06 +00:00
if ( ConfigurationManager . CommonConfiguration . RunAtStartup )
{
//Copy our shortut into the startup folder for this user
2013-03-14 18:22:58 +00:00
File . Copy ( ProductShortcutPath , Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . Startup ) , Path . GetFileName ( ProductShortcutPath ) ? ? "MBstartup.lnk" ) , true ) ;
2013-03-12 19:28:06 +00:00
}
else
{
//Remove our shortcut from the startup folder for this user
try
{
2013-03-14 18:22:58 +00:00
File . Delete ( Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . Startup ) , Path . GetFileName ( ProductShortcutPath ) ? ? "MBstartup.lnk" ) ) ;
2013-03-12 19:28:06 +00:00
}
catch ( FileNotFoundException )
{
//This is okay - trying to remove it anyway
}
}
2013-02-27 21:07:51 +00:00
}
2013-03-03 02:47:04 +00:00
/// <summary>
/// Removes the plugin.
/// </summary>
/// <param name="plugin">The plugin.</param>
public void RemovePlugin ( IPlugin plugin )
{
var list = Plugins . ToList ( ) ;
list . Remove ( plugin ) ;
Plugins = list ;
}
2013-03-07 05:34:00 +00:00
/// <summary>
/// Performs the pending restart.
/// </summary>
/// <returns>Task.</returns>
public void PerformPendingRestart ( )
{
if ( HasPendingRestart )
{
Logger . Info ( "Restarting the application" ) ;
Restart ( ) ;
}
else
{
Logger . Info ( "PerformPendingRestart - not needed" ) ;
}
}
/// <summary>
/// Notifies that the kernel that a change has been made that requires a restart
/// </summary>
public void NotifyPendingRestart ( )
{
HasPendingRestart = true ;
EventHelper . QueueEventIfNotNull ( HasPendingRestartChanged , this , EventArgs . Empty , Logger ) ;
}
2013-02-26 03:43:04 +00:00
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose ( )
{
Dispose ( true ) ;
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose ( bool dispose )
{
2013-02-27 04:19:05 +00:00
if ( dispose )
{
var type = GetType ( ) ;
2013-02-26 16:10:55 +00:00
2013-02-27 04:19:05 +00:00
Logger . Info ( "Disposing " + type . Name ) ;
2013-02-26 16:10:55 +00:00
2013-02-27 04:19:05 +00:00
var parts = DisposableParts . Distinct ( ) . Where ( i = > i . GetType ( ) ! = type ) . ToList ( ) ;
DisposableParts . Clear ( ) ;
2013-02-26 16:10:55 +00:00
2013-02-27 04:19:05 +00:00
foreach ( var part in parts )
{
Logger . Info ( "Disposing " + part . GetType ( ) . Name ) ;
2013-02-26 03:43:04 +00:00
2013-02-27 04:19:05 +00:00
part . Dispose ( ) ;
}
}
2013-02-26 03:43:04 +00:00
}
2013-03-04 05:43:06 +00:00
2013-03-10 06:45:16 +00:00
/// <summary>
/// Restarts this instance.
/// </summary>
2013-03-04 05:43:06 +00:00
public abstract void Restart ( ) ;
2013-03-10 06:45:16 +00:00
/// <summary>
/// Gets or sets a value indicating whether this instance can self update.
/// </summary>
/// <value><c>true</c> if this instance can self update; otherwise, <c>false</c>.</value>
2013-03-04 05:43:06 +00:00
public abstract bool CanSelfUpdate { get ; }
2013-03-10 06:45:16 +00:00
/// <summary>
/// Checks for update.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="progress">The progress.</param>
/// <returns>Task{CheckForUpdateResult}.</returns>
2013-03-04 05:43:06 +00:00
public abstract Task < CheckForUpdateResult > CheckForApplicationUpdate ( CancellationToken cancellationToken , IProgress < double > progress ) ;
2013-03-07 05:34:00 +00:00
/// <summary>
/// Updates the application.
/// </summary>
/// <param name="package">The package that contains the update</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="progress">The progress.</param>
/// <returns>Task.</returns>
public async Task UpdateApplication ( PackageVersionInfo package , CancellationToken cancellationToken , IProgress < double > progress )
{
2013-03-08 05:08:27 +00:00
await PackageManager . InstallPackage ( progress , package , cancellationToken ) . ConfigureAwait ( false ) ;
2013-03-07 05:34:00 +00:00
EventHelper . QueueEventIfNotNull ( ApplicationUpdated , this , new GenericEventArgs < Version > { Argument = package . version } , Logger ) ;
2013-03-11 19:36:32 +00:00
NotifyPendingRestart ( ) ;
2013-03-07 05:34:00 +00:00
}
2013-03-04 05:43:06 +00:00
2013-03-10 06:45:16 +00:00
/// <summary>
/// Shuts down.
/// </summary>
2013-03-04 05:43:06 +00:00
public abstract void Shutdown ( ) ;
2013-02-26 03:43:04 +00:00
}
}