2015-10-16 18:11:11 +00:00
using MediaBrowser.Common.Progress ;
2015-08-27 01:31:54 +00:00
using MediaBrowser.Controller.Configuration ;
2015-08-26 05:10:04 +00:00
using MediaBrowser.Controller.Entities ;
using MediaBrowser.Controller.Library ;
using MediaBrowser.Controller.Persistence ;
2015-09-16 17:16:39 +00:00
using MediaBrowser.Model.Entities ;
2015-08-27 01:31:54 +00:00
using MediaBrowser.Model.Logging ;
2015-08-26 05:10:04 +00:00
using System ;
using System.Collections.Generic ;
2016-02-01 19:54:49 +00:00
using System.Globalization ;
2016-02-03 22:42:33 +00:00
using System.Linq ;
2015-08-26 05:10:04 +00:00
using System.Threading ;
using System.Threading.Tasks ;
2016-10-25 19:02:04 +00:00
using MediaBrowser.Common.IO ;
using MediaBrowser.Model.IO ;
2015-11-13 20:53:29 +00:00
using MediaBrowser.Controller.Channels ;
2015-09-16 17:16:39 +00:00
using MediaBrowser.Controller.Entities.Audio ;
2016-10-25 19:02:04 +00:00
using MediaBrowser.Controller.IO ;
2016-06-17 13:06:13 +00:00
using MediaBrowser.Controller.LiveTv ;
2016-02-01 19:54:49 +00:00
using MediaBrowser.Controller.Net ;
2016-10-24 02:45:23 +00:00
using MediaBrowser.Model.Globalization ;
2016-10-23 19:14:57 +00:00
using MediaBrowser.Model.Tasks ;
2016-11-03 06:37:52 +00:00
using Emby.Server.Implementations.ScheduledTasks ;
2015-08-26 05:10:04 +00:00
2016-11-03 06:37:52 +00:00
namespace Emby.Server.Implementations.Persistence
2015-08-26 05:10:04 +00:00
{
2015-10-16 18:11:11 +00:00
public class CleanDatabaseScheduledTask : IScheduledTask
2015-08-26 05:10:04 +00:00
{
private readonly ILibraryManager _libraryManager ;
private readonly IItemRepository _itemRepo ;
private readonly ILogger _logger ;
2015-08-27 01:31:54 +00:00
private readonly IServerConfigurationManager _config ;
2015-09-16 17:16:39 +00:00
private readonly IFileSystem _fileSystem ;
2016-02-01 19:54:49 +00:00
private readonly IHttpServer _httpServer ;
private readonly ILocalizationManager _localization ;
2016-02-03 17:27:00 +00:00
private readonly ITaskManager _taskManager ;
2015-08-26 05:10:04 +00:00
2016-04-02 16:47:23 +00:00
public const int MigrationVersion = 23 ;
2016-02-01 19:54:49 +00:00
public static bool EnableUnavailableMessage = false ;
2016-11-03 06:37:52 +00:00
const int LatestSchemaVersion = 109 ;
2015-11-13 20:53:29 +00:00
2016-02-03 17:27:00 +00:00
public CleanDatabaseScheduledTask ( ILibraryManager libraryManager , IItemRepository itemRepo , ILogger logger , IServerConfigurationManager config , IFileSystem fileSystem , IHttpServer httpServer , ILocalizationManager localization , ITaskManager taskManager )
2015-08-26 05:10:04 +00:00
{
_libraryManager = libraryManager ;
_itemRepo = itemRepo ;
_logger = logger ;
2015-08-27 01:31:54 +00:00
_config = config ;
2015-09-16 17:16:39 +00:00
_fileSystem = fileSystem ;
2016-02-01 19:54:49 +00:00
_httpServer = httpServer ;
_localization = localization ;
2016-02-03 17:27:00 +00:00
_taskManager = taskManager ;
2015-08-26 05:10:04 +00:00
}
public string Name
{
get { return "Clean Database" ; }
}
public string Description
{
get { return "Deletes obsolete content from the database." ; }
}
public string Category
{
get { return "Library" ; }
}
public async Task Execute ( CancellationToken cancellationToken , IProgress < double > progress )
{
2016-02-03 21:56:00 +00:00
OnProgress ( 0 ) ;
2016-02-04 18:04:04 +00:00
// Ensure these objects are lazy loaded.
// Without this there is a deadlock that will need to be investigated
2016-02-03 22:42:33 +00:00
var rootChildren = _libraryManager . RootFolder . Children . ToList ( ) ;
rootChildren = _libraryManager . GetUserRootFolder ( ) . Children . ToList ( ) ;
2015-08-26 05:10:04 +00:00
var innerProgress = new ActionableProgress < double > ( ) ;
2016-02-01 19:54:49 +00:00
innerProgress . RegisterAction ( p = >
{
double newPercentCommplete = . 4 * p ;
2016-02-03 02:51:00 +00:00
OnProgress ( newPercentCommplete ) ;
2016-02-01 19:54:49 +00:00
progress . Report ( newPercentCommplete ) ;
} ) ;
2015-08-26 05:10:04 +00:00
await UpdateToLatestSchema ( cancellationToken , innerProgress ) . ConfigureAwait ( false ) ;
2015-08-28 04:19:08 +00:00
innerProgress = new ActionableProgress < double > ( ) ;
2016-02-03 02:51:00 +00:00
innerProgress . RegisterAction ( p = >
{
2016-03-27 21:11:27 +00:00
double newPercentCommplete = 40 + . 05 * p ;
2016-02-03 02:51:00 +00:00
OnProgress ( newPercentCommplete ) ;
progress . Report ( newPercentCommplete ) ;
} ) ;
2015-09-03 04:16:31 +00:00
await CleanDeadItems ( cancellationToken , innerProgress ) . ConfigureAwait ( false ) ;
2015-09-16 17:16:39 +00:00
progress . Report ( 45 ) ;
2015-08-28 04:19:08 +00:00
2015-09-16 17:16:39 +00:00
innerProgress = new ActionableProgress < double > ( ) ;
2016-02-03 02:51:00 +00:00
innerProgress . RegisterAction ( p = >
{
2016-03-27 21:11:27 +00:00
double newPercentCommplete = 45 + . 55 * p ;
2016-02-03 02:51:00 +00:00
OnProgress ( newPercentCommplete ) ;
progress . Report ( newPercentCommplete ) ;
} ) ;
2015-09-16 17:16:39 +00:00
await CleanDeletedItems ( cancellationToken , innerProgress ) . ConfigureAwait ( false ) ;
2015-08-28 04:19:08 +00:00
progress . Report ( 100 ) ;
2015-11-13 20:53:29 +00:00
await _itemRepo . UpdateInheritedValues ( cancellationToken ) . ConfigureAwait ( false ) ;
2016-02-01 19:54:49 +00:00
2016-02-03 20:52:45 +00:00
if ( _config . Configuration . MigrationVersion < MigrationVersion )
{
_config . Configuration . MigrationVersion = MigrationVersion ;
_config . SaveConfiguration ( ) ;
}
2016-11-03 06:37:52 +00:00
if ( _config . Configuration . SchemaVersion < LatestSchemaVersion )
2016-05-01 22:11:24 +00:00
{
2016-11-03 06:37:52 +00:00
_config . Configuration . SchemaVersion = LatestSchemaVersion ;
2016-05-01 22:11:24 +00:00
_config . SaveConfiguration ( ) ;
}
2016-02-01 19:54:49 +00:00
if ( EnableUnavailableMessage )
{
EnableUnavailableMessage = false ;
_httpServer . GlobalResponse = null ;
2016-02-03 17:27:00 +00:00
_taskManager . QueueScheduledTask < RefreshMediaLibraryTask > ( ) ;
2016-02-01 19:54:49 +00:00
}
2016-02-03 20:52:45 +00:00
_taskManager . SuspendTriggers = false ;
2015-08-26 05:10:04 +00:00
}
2016-02-03 02:51:00 +00:00
private void OnProgress ( double newPercentCommplete )
{
if ( EnableUnavailableMessage )
{
var html = "<!doctype html><html><head><title>Emby</title></head><body>" ;
var text = _localization . GetLocalizedString ( "DbUpgradeMessage" ) ;
html + = string . Format ( text , newPercentCommplete . ToString ( "N2" , CultureInfo . InvariantCulture ) ) ;
html + = "<script>setTimeout(function(){window.location.reload(true);}, 5000);</script>" ;
html + = "</body></html>" ;
_httpServer . GlobalResponse = html ;
}
}
2016-07-07 15:55:39 +00:00
private async Task UpdateToLatestSchema ( CancellationToken cancellationToken , IProgress < double > progress )
2015-08-26 05:10:04 +00:00
{
2016-07-07 15:55:39 +00:00
var itemIds = _libraryManager . GetItemIds ( new InternalItemsQuery
2016-07-06 05:13:26 +00:00
{
2016-07-07 15:55:39 +00:00
IsCurrentSchema = false ,
ExcludeItemTypes = new [ ] { typeof ( LiveTvProgram ) . Name }
} ) ;
2016-07-06 05:13:26 +00:00
2016-07-07 15:55:39 +00:00
var numComplete = 0 ;
var numItems = itemIds . Count ;
2015-08-26 05:10:04 +00:00
_logger . Debug ( "Upgrading schema for {0} items" , numItems ) ;
2016-07-08 03:22:02 +00:00
var list = new List < BaseItem > ( ) ;
2016-07-07 15:55:39 +00:00
foreach ( var itemId in itemIds )
2015-08-26 05:10:04 +00:00
{
2016-07-07 15:55:39 +00:00
cancellationToken . ThrowIfCancellationRequested ( ) ;
if ( itemId ! = Guid . Empty )
2016-07-06 05:13:26 +00:00
{
2016-07-07 15:55:39 +00:00
// Somehow some invalid data got into the db. It probably predates the boundary checking
var item = _libraryManager . GetItemById ( itemId ) ;
if ( item ! = null )
{
2016-07-08 03:22:02 +00:00
list . Add ( item ) ;
}
}
if ( list . Count > = 1000 )
{
try
{
await _itemRepo . SaveItems ( list , cancellationToken ) . ConfigureAwait ( false ) ;
}
catch ( OperationCanceledException )
{
throw ;
}
catch ( Exception ex )
{
_logger . ErrorException ( "Error saving item" , ex ) ;
2016-07-07 15:55:39 +00:00
}
2016-07-08 03:22:02 +00:00
list . Clear ( ) ;
2015-08-26 05:10:04 +00:00
}
2016-07-07 15:55:39 +00:00
numComplete + + ;
double percent = numComplete ;
2015-08-26 05:10:04 +00:00
percent / = numItems ;
progress . Report ( percent * 100 ) ;
2016-07-06 05:13:26 +00:00
}
2016-07-07 15:55:39 +00:00
2016-07-08 03:22:02 +00:00
if ( list . Count > 0 )
{
try
{
await _itemRepo . SaveItems ( list , cancellationToken ) . ConfigureAwait ( false ) ;
}
catch ( OperationCanceledException )
{
throw ;
}
catch ( Exception ex )
{
_logger . ErrorException ( "Error saving item" , ex ) ;
}
}
2016-07-07 15:55:39 +00:00
progress . Report ( 100 ) ;
2015-08-26 05:10:04 +00:00
}
2015-08-28 04:19:08 +00:00
private async Task CleanDeadItems ( CancellationToken cancellationToken , IProgress < double > progress )
{
var itemIds = _libraryManager . GetItemIds ( new InternalItemsQuery
{
HasDeadParentId = true
} ) ;
var numComplete = 0 ;
var numItems = itemIds . Count ;
_logger . Debug ( "Cleaning {0} items with dead parent links" , numItems ) ;
foreach ( var itemId in itemIds )
{
cancellationToken . ThrowIfCancellationRequested ( ) ;
var item = _libraryManager . GetItemById ( itemId ) ;
if ( item ! = null )
{
2015-11-13 20:54:04 +00:00
_logger . Info ( "Cleaning item {0} type: {1} path: {2}" , item . Name , item . GetType ( ) . Name , item . Path ? ? string . Empty ) ;
2015-08-28 04:19:08 +00:00
2016-02-12 04:54:00 +00:00
await item . Delete ( new DeleteOptions
2015-08-28 04:19:08 +00:00
{
DeleteFileLocation = false
2016-03-20 19:53:22 +00:00
2016-02-12 04:54:00 +00:00
} ) . ConfigureAwait ( false ) ;
2015-08-28 04:19:08 +00:00
}
numComplete + + ;
double percent = numComplete ;
percent / = numItems ;
progress . Report ( percent * 100 ) ;
}
progress . Report ( 100 ) ;
}
2015-09-16 17:16:39 +00:00
private async Task CleanDeletedItems ( CancellationToken cancellationToken , IProgress < double > progress )
{
var result = _itemRepo . GetItemIdsWithPath ( new InternalItemsQuery
{
2016-03-20 19:53:22 +00:00
LocationTypes = new [ ] { LocationType . FileSystem } ,
2015-09-16 17:16:39 +00:00
//Limit = limit,
// These have their own cleanup routines
2015-11-13 20:53:29 +00:00
ExcludeItemTypes = new [ ]
{
2016-06-17 13:06:13 +00:00
typeof ( Person ) . Name ,
typeof ( Genre ) . Name ,
typeof ( MusicGenre ) . Name ,
typeof ( GameGenre ) . Name ,
typeof ( Studio ) . Name ,
typeof ( Year ) . Name ,
typeof ( Channel ) . Name ,
typeof ( AggregateFolder ) . Name ,
2016-03-08 02:59:21 +00:00
typeof ( CollectionFolder ) . Name
2015-11-13 20:53:29 +00:00
}
2015-09-16 17:16:39 +00:00
} ) ;
var numComplete = 0 ;
var numItems = result . Items . Length ;
foreach ( var item in result . Items )
{
cancellationToken . ThrowIfCancellationRequested ( ) ;
var path = item . Item2 ;
try
{
2015-09-21 15:43:10 +00:00
if ( _fileSystem . FileExists ( path ) | | _fileSystem . DirectoryExists ( path ) )
2015-09-16 17:16:39 +00:00
{
2015-09-21 15:43:10 +00:00
continue ;
}
var libraryItem = _libraryManager . GetItemById ( item . Item1 ) ;
2015-09-16 17:16:39 +00:00
2015-11-14 19:56:56 +00:00
if ( libraryItem . IsTopParent )
{
continue ;
}
2016-03-17 05:50:15 +00:00
var hasDualAccess = libraryItem as IHasDualAccess ;
if ( hasDualAccess ! = null & & hasDualAccess . IsAccessedByName )
{
continue ;
}
var libraryItemPath = libraryItem . Path ;
if ( ! string . Equals ( libraryItemPath , path , StringComparison . OrdinalIgnoreCase ) )
{
_logger . Error ( "CleanDeletedItems aborting delete for item {0}-{1} because paths don't match. {2}---{3}" , libraryItem . Id , libraryItem . Name , libraryItem . Path ? ? string . Empty , path ? ? string . Empty ) ;
continue ;
}
2015-09-21 15:43:10 +00:00
if ( Folder . IsPathOffline ( path ) )
{
2016-08-24 06:13:15 +00:00
await libraryItem . UpdateIsOffline ( true ) . ConfigureAwait ( false ) ;
2015-09-21 15:43:10 +00:00
continue ;
2015-09-16 17:16:39 +00:00
}
2015-09-21 15:43:10 +00:00
2016-03-17 05:50:15 +00:00
_logger . Info ( "Deleting item from database {0} because path no longer exists. type: {1} path: {2}" , libraryItem . Name , libraryItem . GetType ( ) . Name , libraryItemPath ? ? string . Empty ) ;
2015-11-13 20:54:04 +00:00
2016-03-08 02:59:21 +00:00
await libraryItem . OnFileDeleted ( ) . ConfigureAwait ( false ) ;
2015-09-16 17:16:39 +00:00
}
catch ( OperationCanceledException )
{
throw ;
}
catch ( Exception ex )
{
_logger . ErrorException ( "Error in CleanDeletedItems. File {0}" , ex , path ) ;
}
numComplete + + ;
double percent = numComplete ;
percent / = numItems ;
progress . Report ( percent * 100 ) ;
}
}
2016-10-23 19:14:57 +00:00
/// <summary>
/// Creates the triggers that define when the task will run
/// </summary>
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
public IEnumerable < TaskTriggerInfo > GetDefaultTriggers ( )
2015-08-26 05:10:04 +00:00
{
2016-10-23 19:14:57 +00:00
return new [ ] {
// Every so often
new TaskTriggerInfo { Type = TaskTriggerInfo . TriggerInterval , IntervalTicks = TimeSpan . FromHours ( 24 ) . Ticks }
2015-08-26 05:10:04 +00:00
} ;
}
2016-10-23 19:14:57 +00:00
public string Key
{
get { return "CleanDatabase" ; }
}
2015-08-26 05:10:04 +00:00
}
2015-11-13 20:53:29 +00:00
}