diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
index 0af4972f7..bde4d0f45 100644
--- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
+++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
@@ -98,7 +98,9 @@ namespace MediaBrowser.Controller.Entities
public bool? IsCurrentSchema { get; set; }
public bool? HasDeadParentId { get; set; }
-
+ public bool? IsOffline { get; set; }
+ public LocationType? LocationType { get; set; }
+
public InternalItemsQuery()
{
Tags = new string[] { };
diff --git a/MediaBrowser.Controller/Persistence/IItemRepository.cs b/MediaBrowser.Controller/Persistence/IItemRepository.cs
index a4b9bf120..8fc0aedd3 100644
--- a/MediaBrowser.Controller/Persistence/IItemRepository.cs
+++ b/MediaBrowser.Controller/Persistence/IItemRepository.cs
@@ -169,6 +169,13 @@ namespace MediaBrowser.Controller.Persistence
/// The query.
/// List<System.String>.
List GetPeopleNames(InternalPeopleQuery query);
+
+ ///
+ /// Gets the item ids with path.
+ ///
+ /// The query.
+ /// QueryResult<Tuple<Guid, System.String>>.
+ QueryResult> GetItemIdsWithPath(InternalItemsQuery query);
}
}
diff --git a/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs b/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs
index 0b45c468a..22b2c7f33 100644
--- a/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs
@@ -1,15 +1,18 @@
-using MediaBrowser.Common.Progress;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Progress;
using MediaBrowser.Common.ScheduledTasks;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Controller.Entities.Audio;
namespace MediaBrowser.Server.Implementations.Persistence
{
@@ -19,13 +22,15 @@ namespace MediaBrowser.Server.Implementations.Persistence
private readonly IItemRepository _itemRepo;
private readonly ILogger _logger;
private readonly IServerConfigurationManager _config;
+ private readonly IFileSystem _fileSystem;
- public CleanDatabaseScheduledTask(ILibraryManager libraryManager, IItemRepository itemRepo, ILogger logger, IServerConfigurationManager config)
+ public CleanDatabaseScheduledTask(ILibraryManager libraryManager, IItemRepository itemRepo, ILogger logger, IServerConfigurationManager config, IFileSystem fileSystem)
{
_libraryManager = libraryManager;
_itemRepo = itemRepo;
_logger = logger;
_config = config;
+ _fileSystem = fileSystem;
}
public string Name
@@ -46,15 +51,18 @@ namespace MediaBrowser.Server.Implementations.Persistence
public async Task Execute(CancellationToken cancellationToken, IProgress progress)
{
var innerProgress = new ActionableProgress();
- innerProgress.RegisterAction(p => progress.Report(.95 * p));
+ innerProgress.RegisterAction(p => progress.Report(.4 * p));
await UpdateToLatestSchema(cancellationToken, innerProgress).ConfigureAwait(false);
innerProgress = new ActionableProgress();
- innerProgress.RegisterAction(p => progress.Report(95 + (.05 * p)));
-
+ innerProgress.RegisterAction(p => progress.Report(40 + (.05 * p)));
await CleanDeadItems(cancellationToken, innerProgress).ConfigureAwait(false);
+ progress.Report(45);
+ innerProgress = new ActionableProgress();
+ innerProgress.RegisterAction(p => progress.Report(45 + (.55 * p)));
+ await CleanDeletedItems(cancellationToken, innerProgress).ConfigureAwait(false);
progress.Report(100);
}
@@ -153,11 +161,60 @@ namespace MediaBrowser.Server.Implementations.Persistence
progress.Report(100);
}
+ private async Task CleanDeletedItems(CancellationToken cancellationToken, IProgress progress)
+ {
+ var result = _itemRepo.GetItemIdsWithPath(new InternalItemsQuery
+ {
+ IsOffline = false,
+ LocationType = LocationType.FileSystem,
+ //Limit = limit,
+
+ // These have their own cleanup routines
+ ExcludeItemTypes = new[] { typeof(Person).Name, typeof(Genre).Name, typeof(MusicGenre).Name, typeof(GameGenre).Name, typeof(Studio).Name, typeof(Year).Name }
+ });
+
+ var numComplete = 0;
+ var numItems = result.Items.Length;
+
+ foreach (var item in result.Items)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ var path = item.Item2;
+
+ try
+ {
+ if (!_fileSystem.FileExists(path) && !_fileSystem.DirectoryExists(path))
+ {
+ var libraryItem = _libraryManager.GetItemById(item.Item1);
+
+ await _libraryManager.DeleteItem(libraryItem, new DeleteOptions
+ {
+ DeleteFileLocation = false
+ });
+ }
+ }
+ 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);
+ }
+ }
+
public IEnumerable GetDefaultTriggers()
{
return new ITaskTrigger[]
{
- new IntervalTrigger{ Interval = TimeSpan.FromDays(1)}
+ new IntervalTrigger{ Interval = TimeSpan.FromDays(7)}
};
}
}
diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs
index bd128a16e..3c06973b4 100644
--- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs
@@ -72,7 +72,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
private IDbCommand _deletePeopleCommand;
private IDbCommand _savePersonCommand;
- private const int LatestSchemaVersion = 6;
+ private const int LatestSchemaVersion = 7;
///
/// Initializes a new instance of the class.
@@ -175,6 +175,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
_connection.AddColumn(_logger, "TypedBaseItems", "ForcedSortName", "Text");
_connection.AddColumn(_logger, "TypedBaseItems", "IsOffline", "BIT");
+ _connection.AddColumn(_logger, "TypedBaseItems", "LocationType", "Text");
PrepareStatements();
@@ -245,7 +246,8 @@ namespace MediaBrowser.Server.Implementations.Persistence
"DateCreated",
"DateModified",
"ForcedSortName",
- "IsOffline"
+ "IsOffline",
+ "LocationType"
};
_saveItemCommand = _connection.CreateCommand();
_saveItemCommand.CommandText = "replace into TypedBaseItems (" + string.Join(",", saveColumns.ToArray()) + ") values (";
@@ -415,6 +417,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
_saveItemCommand.GetParameter(index++).Value = item.ForcedSortName;
_saveItemCommand.GetParameter(index++).Value = item.IsOffline;
+ _saveItemCommand.GetParameter(index++).Value = item.LocationType.ToString();
_saveItemCommand.Transaction = transaction;
@@ -617,7 +620,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
/// Task.
public Task SaveCriticReviews(Guid itemId, IEnumerable criticReviews)
{
- Directory.CreateDirectory(_criticReviewsPath);
+ Directory.CreateDirectory(_criticReviewsPath);
var path = Path.Combine(_criticReviewsPath, itemId + ".json");
@@ -950,6 +953,75 @@ namespace MediaBrowser.Server.Implementations.Persistence
}
}
+ public QueryResult> GetItemIdsWithPath(InternalItemsQuery query)
+ {
+ if (query == null)
+ {
+ throw new ArgumentNullException("query");
+ }
+
+ CheckDisposed();
+
+ using (var cmd = _connection.CreateCommand())
+ {
+ cmd.CommandText = "select guid,path from TypedBaseItems";
+
+ var whereClauses = GetWhereClauses(query, cmd, false);
+
+ var whereTextWithoutPaging = whereClauses.Count == 0 ?
+ string.Empty :
+ " where " + string.Join(" AND ", whereClauses.ToArray());
+
+ whereClauses = GetWhereClauses(query, cmd, true);
+
+ var whereText = whereClauses.Count == 0 ?
+ string.Empty :
+ " where " + string.Join(" AND ", whereClauses.ToArray());
+
+ cmd.CommandText += whereText;
+
+ cmd.CommandText += GetOrderByText(query);
+
+ if (query.Limit.HasValue)
+ {
+ cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(CultureInfo.InvariantCulture);
+ }
+
+ cmd.CommandText += "; select count (guid) from TypedBaseItems" + whereTextWithoutPaging;
+
+ var list = new List>();
+ var count = 0;
+
+ _logger.Debug(cmd.CommandText);
+
+ using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
+ {
+ while (reader.Read())
+ {
+ var id = reader.GetGuid(0);
+ string path = null;
+
+ if (!reader.IsDBNull(1))
+ {
+ path = reader.GetString(1);
+ }
+ list.Add(new Tuple(id, path));
+ }
+
+ if (reader.NextResult() && reader.Read())
+ {
+ count = reader.GetInt32(0);
+ }
+ }
+
+ return new QueryResult>()
+ {
+ Items = list.ToArray(),
+ TotalRecordCount = count
+ };
+ }
+ }
+
public QueryResult GetItemIds(InternalItemsQuery query)
{
if (query == null)
@@ -1028,6 +1100,16 @@ namespace MediaBrowser.Server.Implementations.Persistence
}
cmd.Parameters.Add(cmd, "@SchemaVersion", DbType.Int32).Value = LatestSchemaVersion;
}
+ if (query.IsOffline.HasValue)
+ {
+ whereClauses.Add("IsOffline=@IsOffline");
+ cmd.Parameters.Add(cmd, "@IsOffline", DbType.Boolean).Value = query.IsOffline;
+ }
+ if (query.LocationType.HasValue)
+ {
+ whereClauses.Add("LocationType=@LocationType");
+ cmd.Parameters.Add(cmd, "@LocationType", DbType.String).Value = query.LocationType.Value;
+ }
if (query.IsMovie.HasValue)
{
whereClauses.Add("IsMovie=@IsMovie");