2018-11-02 14:55:34 +11:00
using System ;
using System.Linq ;
using System.Collections.Generic ;
using System.Collections.ObjectModel ;
2018-11-05 17:20:26 +11:00
using System.Collections.Specialized ;
2018-11-02 14:55:34 +11:00
namespace Umbraco.Core.Models
{
2018-11-07 19:42:49 +11:00
public class ContentScheduleCollection : INotifyCollectionChanged , IDeepCloneable , IEquatable < ContentScheduleCollection >
2018-11-02 14:55:34 +11:00
{
2018-11-08 13:15:56 +11:00
//underlying storage for the collection backed by a sorted list so that the schedule is always in order of date and that duplicate dates per culture are not allowed
2018-11-06 21:33:24 +11:00
private readonly Dictionary < string , SortedList < DateTime , ContentSchedule >> _schedule
= new Dictionary < string , SortedList < DateTime , ContentSchedule >>( StringComparer . InvariantCultureIgnoreCase );
2018-11-02 14:55:34 +11:00
2018-11-05 17:20:26 +11:00
public event NotifyCollectionChangedEventHandler CollectionChanged ;
private void OnCollectionChanged ( NotifyCollectionChangedEventArgs args )
{
CollectionChanged ?. Invoke ( this , args );
}
2018-11-02 14:55:34 +11:00
/// <summary>
/// Add an existing schedule
/// </summary>
/// <param name="schedule"></param>
public void Add ( ContentSchedule schedule )
{
if (! _schedule . TryGetValue ( schedule . Culture , out var changes ))
{
changes = new SortedList < DateTime , ContentSchedule >();
_schedule [ schedule . Culture ] = changes ;
}
2019-01-27 01:17:32 -05:00
// TODO: Below will throw if there are duplicate dates added, validate/return bool?
2018-11-02 14:55:34 +11:00
changes . Add ( schedule . Date , schedule );
2018-11-05 17:20:26 +11:00
OnCollectionChanged ( new NotifyCollectionChangedEventArgs ( NotifyCollectionChangedAction . Add , schedule ));
2018-11-02 14:55:34 +11:00
}
/// <summary>
/// Adds a new schedule for invariant content
/// </summary>
/// <param name="releaseDate"></param>
/// <param name="expireDate"></param>
2018-11-13 14:31:37 +11:00
public bool Add ( DateTime ? releaseDate , DateTime ? expireDate )
2018-11-02 14:55:34 +11:00
{
2018-11-13 14:31:37 +11:00
return Add ( string . Empty , releaseDate , expireDate );
2018-11-02 14:55:34 +11:00
}
/// <summary>
/// Adds a new schedule for a culture
/// </summary>
/// <param name="culture"></param>
/// <param name="releaseDate"></param>
/// <param name="expireDate"></param>
2018-11-13 14:31:37 +11:00
/// <returns>true if successfully added, false if validation fails</returns>
public bool Add ( string culture , DateTime ? releaseDate , DateTime ? expireDate )
2018-11-02 14:55:34 +11:00
{
if ( culture == null ) throw new ArgumentNullException ( nameof ( culture ));
if ( releaseDate . HasValue && expireDate . HasValue && releaseDate >= expireDate )
2018-11-13 14:31:37 +11:00
return false ;
2018-11-02 14:55:34 +11:00
2018-11-13 14:31:37 +11:00
if (! releaseDate . HasValue && ! expireDate . HasValue ) return false ;
2018-11-02 14:55:34 +11:00
2019-01-27 01:17:32 -05:00
// TODO: Do we allow passing in a release or expiry date that is before now?
2018-11-02 14:55:34 +11:00
if (! _schedule . TryGetValue ( culture , out var changes ))
{
changes = new SortedList < DateTime , ContentSchedule >();
_schedule [ culture ] = changes ;
2018-11-08 16:33:19 +01:00
}
2018-11-02 14:55:34 +11:00
2019-01-27 01:17:32 -05:00
// TODO: Below will throw if there are duplicate dates added, should validate/return bool?
2018-11-02 14:55:34 +11:00
// but the bool won't indicate which date was in error, maybe have 2 diff methods to schedule start/end?
if ( releaseDate . HasValue )
2018-11-05 17:20:26 +11:00
{
2018-11-14 13:59:53 +01:00
var entry = new ContentSchedule ( culture , releaseDate . Value , ContentScheduleAction . Release );
2018-11-05 17:20:26 +11:00
changes . Add ( releaseDate . Value , entry );
OnCollectionChanged ( new NotifyCollectionChangedEventArgs ( NotifyCollectionChangedAction . Add , entry ));
}
2018-11-08 16:33:19 +01:00
2018-11-02 14:55:34 +11:00
if ( expireDate . HasValue )
2018-11-05 17:20:26 +11:00
{
2018-11-14 13:59:53 +01:00
var entry = new ContentSchedule ( culture , expireDate . Value , ContentScheduleAction . Expire );
2018-11-05 17:20:26 +11:00
changes . Add ( expireDate . Value , entry );
OnCollectionChanged ( new NotifyCollectionChangedEventArgs ( NotifyCollectionChangedAction . Add , entry ));
}
2018-11-13 14:31:37 +11:00
return true ;
2018-11-02 14:55:34 +11:00
}
/// <summary>
/// Remove a scheduled change
/// </summary>
/// <param name="change"></param>
public void Remove ( ContentSchedule change )
{
if ( _schedule . TryGetValue ( change . Culture , out var s ))
{
2018-11-05 17:20:26 +11:00
var removed = s . Remove ( change . Date );
if ( removed )
{
OnCollectionChanged ( new NotifyCollectionChangedEventArgs ( NotifyCollectionChangedAction . Remove , change ));
if ( s . Count == 0 )
_schedule . Remove ( change . Culture );
}
2018-11-08 16:33:19 +01:00
2018-11-02 14:55:34 +11:00
}
}
/// <summary>
/// Clear all of the scheduled change type for invariant content
/// </summary>
2018-11-14 09:16:22 +01:00
/// <param name="action"></param>
2018-11-07 19:42:49 +11:00
/// <param name="changeDate">If specified, will clear all entries with dates less than or equal to the value</param>
2018-11-14 09:16:22 +01:00
public void Clear ( ContentScheduleAction action , DateTime ? changeDate = null )
2018-11-02 14:55:34 +11:00
{
2018-11-14 09:16:22 +01:00
Clear ( string . Empty , action , changeDate );
2018-11-02 14:55:34 +11:00
}
/// <summary>
/// Clear all of the scheduled change type for the culture
/// </summary>
/// <param name="culture"></param>
2018-11-14 09:16:22 +01:00
/// <param name="action"></param>
/// <param name="date">If specified, will clear all entries with dates less than or equal to the value</param>
public void Clear ( string culture , ContentScheduleAction action , DateTime ? date = null )
2018-11-02 14:55:34 +11:00
{
2018-11-14 09:16:22 +01:00
if (! _schedule . TryGetValue ( culture , out var schedules ))
return ;
2018-11-08 16:33:19 +01:00
2018-11-14 09:16:22 +01:00
var removes = schedules . Where ( x => x . Value . Action == action && (! date . HasValue || x . Value . Date <= date . Value )). ToList ();
foreach ( var remove in removes )
{
var removed = schedules . Remove ( remove . Value . Date );
if (! removed )
continue ;
OnCollectionChanged ( new NotifyCollectionChangedEventArgs ( NotifyCollectionChangedAction . Remove , remove . Value ));
2018-11-02 14:55:34 +11:00
}
2018-11-14 09:16:22 +01:00
if ( schedules . Count == 0 )
_schedule . Remove ( culture );
2018-11-02 14:55:34 +11:00
}
2018-11-07 19:42:49 +11:00
/// <summary>
/// Returns all pending schedules based on the date and type provided
/// </summary>
2018-11-14 09:16:22 +01:00
/// <param name="action"></param>
2018-11-07 19:42:49 +11:00
/// <param name="date"></param>
/// <returns></returns>
2018-11-14 09:16:22 +01:00
public IReadOnlyList < ContentSchedule > GetPending ( ContentScheduleAction action , DateTime date )
2018-11-07 19:42:49 +11:00
{
2018-11-07 21:32:12 +11:00
return _schedule . Values . SelectMany ( x => x . Values ). Where ( x => x . Date <= date ). ToList ();
2018-11-07 19:42:49 +11:00
}
2018-11-02 14:55:34 +11:00
/// <summary>
/// Gets the schedule for invariant content
/// </summary>
/// <returns></returns>
2018-11-14 09:16:22 +01:00
public IEnumerable < ContentSchedule > GetSchedule ( ContentScheduleAction ? action = null )
2018-11-02 14:55:34 +11:00
{
2018-11-14 09:16:22 +01:00
return GetSchedule ( string . Empty , action );
2018-11-02 14:55:34 +11:00
}
/// <summary>
/// Gets the schedule for a culture
/// </summary>
/// <param name="culture"></param>
2018-12-07 12:49:20 +11:00
/// <param name="action"></param>
2018-11-02 14:55:34 +11:00
/// <returns></returns>
2018-11-14 09:16:22 +01:00
public IEnumerable < ContentSchedule > GetSchedule ( string culture , ContentScheduleAction ? action = null )
2018-11-02 14:55:34 +11:00
{
if ( _schedule . TryGetValue ( culture , out var changes ))
2018-11-14 09:16:22 +01:00
return action == null ? changes . Values : changes . Values . Where ( x => x . Action == action . Value );
2018-11-05 13:59:55 +11:00
return Enumerable . Empty < ContentSchedule >();
2018-11-02 14:55:34 +11:00
}
/// <summary>
2018-11-08 13:15:56 +11:00
/// Returns all schedules registered
2018-11-02 14:55:34 +11:00
/// </summary>
/// <returns></returns>
2018-11-08 13:15:56 +11:00
public IReadOnlyList < ContentSchedule > FullSchedule => _schedule . SelectMany ( x => x . Value . Values ). ToList ();
2018-11-05 17:20:26 +11:00
public object DeepClone ()
{
var clone = new ContentScheduleCollection ();
foreach ( var cultureSched in _schedule )
{
var list = new SortedList < DateTime , ContentSchedule >();
foreach ( var schedEntry in cultureSched . Value )
list . Add ( schedEntry . Key , ( ContentSchedule ) schedEntry . Value . DeepClone ());
clone . _schedule [ cultureSched . Key ] = list ;
}
return clone ;
}
2018-11-07 19:42:49 +11:00
public override bool Equals ( object obj )
2018-11-08 16:33:19 +01:00
=> obj is ContentScheduleCollection other && Equals ( other );
2018-11-07 19:42:49 +11:00
public bool Equals ( ContentScheduleCollection other )
{
2018-11-08 16:33:19 +01:00
if ( other == null ) return false ;
var thisSched = _schedule ;
2018-11-08 13:15:56 +11:00
var thatSched = other . _schedule ;
2018-11-07 19:42:49 +11:00
2018-11-08 16:33:19 +01:00
if ( thisSched . Count != thatSched . Count )
return false ;
foreach ( var ( culture , thisList ) in thisSched )
2018-11-07 19:42:49 +11:00
{
2018-11-08 16:33:19 +01:00
// if culture is missing, or actions differ, false
if (! thatSched . TryGetValue ( culture , out var thatList ) || ! thatList . SequenceEqual ( thisList ))
return false ;
2018-11-07 19:42:49 +11:00
}
2018-11-08 16:33:19 +01:00
return true ;
2018-11-07 19:42:49 +11:00
}
2018-11-02 14:55:34 +11:00
}
}