PageRenderTime 38ms CodeModel.GetById 9ms app.highlight 22ms RepoModel.GetById 1ms app.codeStats 0ms

/Application/Core/PlaylistManager.cs

http://yet-another-music-application.googlecode.com/
C# | 790 lines | 522 code | 85 blank | 183 comment | 157 complexity | 8bbcc12f209d821197eaab786449d690 MD5 | raw file
  1/**
  2 * PlaylistManager.cs
  3 * 
  4 * Takes care of managing the playlists.
  5 * 
  6 * * * * * * * * *
  7 * 
  8 * Copyright 2012 Simplare
  9 * 
 10 * This code is part of the Stoffi Music Player Project.
 11 * Visit our website at: stoffiplayer.com
 12 *
 13 * This program is free software; you can redistribute it and/or
 14 * modify it under the terms of the GNU General Public License
 15 * as published by the Free Software Foundation; either version
 16 * 3 of the License, or (at your option) any later version.
 17 * 
 18 * See stoffiplayer.com/license for more information.
 19 **/
 20
 21using System;
 22using System.Collections;
 23using System.Collections.Generic;
 24using System.Collections.ObjectModel;
 25using System.Collections.Specialized;
 26using System.IO;
 27using System.Linq;
 28using System.Text;
 29using System.Threading;
 30using System.Xml;
 31
 32namespace Stoffi
 33{
 34	/// <summary>
 35	/// Represents a manager that takes care of the playlist logic
 36	/// </summary>
 37	public static class PlaylistManager
 38	{
 39		#region Members
 40
 41		private static String supportedFileFormats = "m3u;pls";
 42
 43		#endregion
 44
 45		#region Properties
 46
 47		/// <summary>
 48		/// The currently active playlist that is being played.
 49		/// An empty string if no playlist is active.
 50		/// </summary>
 51		public static String CurrentPlaylist { get; set; }
 52
 53		#endregion
 54
 55		#region Methods
 56
 57		#region Public
 58
 59		/// <summary>
 60		/// Initializes the playlist manager
 61		/// </summary>
 62		public static void Initialize()
 63		{
 64			CurrentPlaylist = "";
 65
 66			ThreadStart GUIThread = delegate()
 67			{
 68				foreach (PlaylistData playlist in SettingsManager.Playlists)
 69				{
 70					// fix null tracks
 71					bool allTracksNull = true;
 72					foreach (TrackData t in playlist.Tracks)
 73						if (t != null)
 74						{
 75							allTracksNull = false;
 76							break;
 77						}
 78
 79					if (allTracksNull) playlist.Tracks.Clear();
 80					playlist.Tracks.CollectionChanged += TracksChanged;
 81					DispatchPlaylistModified(playlist, ModifyType.Created);
 82				}
 83			};
 84
 85			if (SettingsManager.CurrentActiveNavigation.StartsWith("Playlist:"))
 86				CurrentPlaylist = SettingsManager.CurrentActiveNavigation.Split(new[]{':'},2)[1];
 87
 88			Thread thread = new Thread(GUIThread);
 89			thread.Name = "Playlist Thread";
 90			thread.Priority = ThreadPriority.BelowNormal;
 91			thread.Start();
 92		}
 93
 94		/// <summary>
 95		/// Add tracks to a playlist
 96		/// </summary>
 97		/// <param name="tracks">The list of tracks to be added</param>
 98		/// <param name="playlistName">The name of the playlist to add the tracks to</param>
 99		/// <param name="pos">The position to insert the track at (-1 means at the end)</param>
100		public static void AddToPlaylist(List<object> tracks, String playlistName, int pos = -1)
101		{
102			PlaylistData playlist = FindPlaylist(playlistName);
103			if (playlist == null) return;
104			foreach (TrackData track in tracks)
105			{
106				if (!playlist.Tracks.Contains(track))
107				{
108					if (pos < 0 || pos >= playlist.Tracks.Count)
109						playlist.Tracks.Add(track);
110					else
111						playlist.Tracks.Insert(pos, track);
112					track.Source = "Playlist:" + playlist.Name;
113				}
114			}
115		}
116
117		/// <summary>
118		/// Add tracks to a playlist
119		/// </summary>
120		/// <param name="tracks">The list of tracks to be added</param>
121		/// <param name="playlistName">The name of the playlist to add the tracks to</param>
122		/// <param name="pos">The position to insert the track at (-1 means at the end)</param>
123		public static void AddToPlaylist(ObservableCollection<TrackData> tracks, String playlistName, int pos = -1)
124		{
125			PlaylistData playlist = FindPlaylist(playlistName);
126			if (playlist == null) return;
127			foreach (TrackData track in tracks)
128			{
129				if (!playlist.Tracks.Contains(track))
130				{
131					if (pos < 0 || pos >= playlist.Tracks.Count)
132						playlist.Tracks.Add(track);
133					else
134						playlist.Tracks.Insert(pos, track);
135					track.Source = "Playlist:" + playlist.Name;
136				}
137			}
138		}
139
140		/// <summary>
141		/// Add tracks to a playlist
142		/// </summary>
143		/// <param name="tracks">The list of tracks to be added</param>
144		/// <param name="playlistName">The name of the playlist to add the tracks to</param>
145		/// <param name="pos">The position to insert the track at (-1 means at the end)</param>
146		public static void AddToPlaylist(List<TrackData> tracks, String playlistName, int pos = -1)
147		{
148			PlaylistData playlist = FindPlaylist(playlistName);
149			if (playlist == null) return;
150			foreach (TrackData track in tracks)
151			{
152				// insert
153				if (!playlist.Tracks.Contains(track))
154				{
155					if (pos < 0 || pos >= playlist.Tracks.Count)
156						playlist.Tracks.Add(track);
157					else
158						playlist.Tracks.Insert(pos, track);
159					track.Source = "Playlist:" + playlist.Name;
160				}
161
162				// move
163				else if (pos != playlist.Tracks.IndexOf(track))
164				{
165					if (pos < 0 || pos >= playlist.Tracks.Count)
166					{
167						playlist.Tracks.Remove(track);
168						playlist.Tracks.Add(track);
169					}
170					else if (pos < playlist.Tracks.IndexOf(track))
171					{
172						playlist.Tracks.Remove(track);
173						playlist.Tracks.Insert(pos, track);
174					}
175					else
176					{
177						playlist.Tracks.Remove(track);
178						playlist.Tracks.Insert(pos-1, track);
179					}
180				}
181			}
182		}
183
184		/// <summary>
185		/// Check to see if a file is a supported playlist file
186		/// </summary>
187		/// <param name="path">The filename to check</param>
188		/// <returns>true of the file can be opened by Stoffi, otherwise false</returns>
189		public static bool IsSupported(String path)
190		{
191			return path.StartsWith("playlist://") ||
192				(supportedFileFormats.Split(';').Contains<string>(Path.GetExtension(path)));
193		}
194
195		/// <summary>
196		/// Remove tracks from a playlist if they are found inside the playlist
197		/// </summary>
198		/// <param name="tracks">The list of t to be removed</param>
199		/// <param name="playlistName">The name of the playlist to remove the t from</param>
200		public static void RemoveFromPlaylist(List<TrackData> tracks, String playlistName)
201		{
202			PlaylistData playlist = FindPlaylist(playlistName);
203			if (playlist != null)
204			{
205				foreach (TrackData track in tracks)
206					foreach (TrackData trackInPlaylist in playlist.Tracks)
207						if (trackInPlaylist.Path == track.Path)
208						{
209							playlist.Tracks.Remove(trackInPlaylist);
210							break;
211						}
212			}
213		}
214
215		/// <summary>
216		/// Creates a new playlist
217		/// </summary>
218		/// <param name="name">The name of the new playlist (this will be appended with a number if neccessary)</param>
219		/// <returns>The newly created PlaylistData for the playlist</returns>
220		public static PlaylistData CreatePlaylist(String name)
221		{
222			name = U.CleanXMLString(name);
223
224			if (FindPlaylist(name) != null)
225			{
226				int pExt = 1;
227				while (FindPlaylist(name + pExt) != null)
228					pExt++;
229				name = name + pExt;
230			}
231
232			PlaylistData playlist = new PlaylistData();
233			playlist.Name = name;
234			playlist.Time = 0;
235			playlist.Tracks = new ObservableCollection<TrackData>();
236			playlist.Tracks.CollectionChanged += TracksChanged;
237			SettingsManager.Playlists.Add(playlist);
238
239			DispatchPlaylistModified(playlist, ModifyType.Created);
240
241			return playlist;
242		}
243
244		/// <summary>
245		/// Renames a playlist. If a playlist with the new name already exist or the new name is either "Create new" or "" it will do nothing.
246		/// </summary>
247		/// <param name="oldName">The current name of the playlist to be renamed</param>
248		/// <param name="newName">The new name of the playlist</param>
249		public static void RenamePlaylist(String oldName, String newName)
250		{
251			newName = U.CleanXMLString(newName);
252
253			PlaylistData pl = FindPlaylist(oldName);
254
255			if (FindPlaylist(newName) != null)
256			{
257				int pExt = 1;
258				while (FindPlaylist(newName + pExt) != null)
259					pExt++;
260				newName = newName + pExt;
261			}
262
263			if (pl != null && newName != "" && newName.ToLower() != U.T("NavigationCreateNew").ToLower())
264				DispatchPlaylistRenamed(pl, oldName, newName);
265		}
266
267		/// <summary>
268		/// Saves a playlist as a file
269		/// </summary>
270		/// <param name="path">The path of the saved playlist</param>
271		/// <param name="name">The name of the playlist to save</param>
272		public static void SavePlaylist(String path, String name)
273		{
274			PlaylistData pl = FindPlaylist(name);
275			if (pl != null)
276			{
277				string ext = Path.GetExtension(path);
278				System.IO.StreamWriter sw = System.IO.File.AppendText(path);
279
280				if (ext == ".pls")
281				{
282					sw.WriteLine("[playlist]");
283					sw.WriteLine("");
284					int i = 0;
285					foreach (TrackData track in pl.Tracks)
286					{
287						i++;
288						sw.WriteLine(String.Format("File{0}={1}", i, track.Path));
289						sw.WriteLine(String.Format("Title{0}={1}", i, track.Title));
290						sw.WriteLine(String.Format("Length{0}={1}", i, (int)track.RawLength));
291						sw.WriteLine("");
292					}
293					sw.WriteLine("NumberOfEntries=" + i);
294					sw.WriteLine("Version=2");
295				}
296				else if (ext == ".m3u")
297				{
298					sw.WriteLine("#EXTM3U");
299					sw.WriteLine("");
300					foreach (TrackData track in pl.Tracks)
301					{
302						sw.WriteLine(String.Format("#EXTINF:{0},{1} - {2}", (int)track.RawLength, track.Artist, track.Title));
303						sw.WriteLine(track.Path);
304						sw.WriteLine("");
305					}
306				}
307				sw.Close();
308			}
309		}
310
311		/// <summary>
312		/// Reads a file and creates a playlist using the name of the file.
313		/// </summary>
314		/// <param name="filename">The file to read</param>
315		/// <returns>The PlaylistData of the newly created playlist</returns>
316		public static PlaylistData LoadPlaylist(String filename)
317		{
318			//try
319			//{
320				string pName = Path.GetFileNameWithoutExtension(filename);
321				if (FindPlaylist(pName) != null)
322				{
323					int pExt = 1;
324					while (FindPlaylist(pName + pExt) != null)
325						pExt++;
326					pName = pName + pExt;
327				}
328				PlaylistData pl = CreatePlaylist(pName);
329				StreamReader sr = new StreamReader(filename);
330				string line;
331				int nr = 0;
332				if (Path.GetExtension(filename) == ".m3u")
333				{
334					bool ext = false;
335					string inf = "";
336					while ((line = sr.ReadLine()) != null)
337					{
338						nr++;
339						if (line == "#EXTM3U")
340							ext = true;
341						else if (ext && line.StartsWith("#EXTINF:"))
342							inf = line.Substring(8);
343						else if (line.StartsWith("#") || line == "")
344							continue;
345						else
346						{
347							string path = line;
348
349							if (!File.Exists(path) && File.Exists(Path.Combine(Path.GetDirectoryName(filename), path)))
350								path = Path.Combine(Path.GetDirectoryName(filename), path);
351
352							if (File.Exists(path))
353							{
354								string length = "";
355								string artist = "";
356								string title = "";
357								if (inf != "")
358								{
359									if (!inf.Contains(','))
360									{
361										U.L(LogLevel.Warning, "PLAYLIST", "Bad format in " + filename + " on line "
362											+ nr + ": expecting ','");
363										continue;
364									}
365									string[] split = inf.Split(',');
366									length = split[0];
367									if (split[1].Contains('-'))
368									{
369										artist = split[1].Split('-')[0];
370										title = split[1].Split('-')[1];
371									}
372									else
373										title = split[1];
374								}
375								if (!FilesystemManager.PathIsAdded(path))
376									FilesystemManager.AddSource(new SourceData() 
377									{ 
378										Data = path, 
379										HumanType = U.T("SourcesTypeFile"),
380										Type = SourceType.File,
381										Include = true
382									});
383								foreach (TrackData t in SettingsManager.FileTracks)
384									if (t.Path == path)
385									{
386										if (!pl.Tracks.Contains(t))
387											pl.Tracks.Add(t);
388										break;
389									}
390								inf = "";
391							}
392							else if (YouTubeManager.IsYouTube(path))
393							{
394								string yid = YouTubeManager.GetYouTubeID(path);
395								TrackData t = YouTubeManager.CreateTrack(yid);
396								if (!pl.Tracks.Contains(t))
397									pl.Tracks.Add(t);
398							}
399						}
400					}
401				}
402				else if (Path.GetExtension(filename) == ".pls")
403				{
404					bool hdr = false;
405					string version = "";
406					int noe = 0;
407					while ((line = sr.ReadLine()) != null)
408					{
409						nr++;
410						if (line == "[playlist]")
411							hdr = true;
412						else if (!hdr)
413							U.L(LogLevel.Warning, "PLAYLIST", "Bad format in " + filename + " on line "
414								+ nr + ": expecting '[playlist]'");
415						else if (line.StartsWith("NumberOfEntries="))
416							noe = Convert.ToInt32(line.Split('=')[1]);
417						else if (line.StartsWith("Version="))
418							version = line.Split('=')[1];
419					}
420					
421					if (!hdr)
422						U.L(LogLevel.Warning, "PLAYLIST", "Error in " + filename + ": No header found");
423					else if (version != "2")
424						U.L(LogLevel.Warning, "PLAYLIST", "Error in " + filename + ": Unsupported version '" + 
425							version + "'");
426					else
427					{
428						sr.Close();
429						sr = new StreamReader(filename);
430						string[,] tracks = new string[noe, 3];
431						nr = 0;
432						while ((line = sr.ReadLine()) != null)
433						{
434							if (line.StartsWith("File") || line.StartsWith("Title") || line.StartsWith("Length"))
435							{
436								int tmp = 4;
437								int index = 0;
438								if (line.StartsWith("Title")) { tmp = 5; index = 1; }
439								else if (line.StartsWith("Length")) { tmp = 6; index = 2; }
440
441								string[] split = line.Split('=');
442								int number = Convert.ToInt32(split[0].Substring(tmp));
443
444								if (number > noe)
445									U.L(LogLevel.Warning, "PLAYLIST", "Bad format in " + filename + " on line "
446										+ nr + ": entry number is '" + number + "' but NumberOfEntries is '" + noe + "'");
447								else
448									tracks[number-1, index] = split[1];
449							}
450							else if (!line.StartsWith("NumberOfEntries") && line != "[playlist]" && !line.StartsWith("Version="))
451							{
452								U.L(LogLevel.Warning, "PLAYLIST", "Bad format in " + filename + " on line "
453									+ nr + ": unexpected '" + line + "'");
454							}
455						}
456						for (int i = 0; i < noe; i++)
457						{
458							string path = tracks[i, 0];
459
460							if (!File.Exists(path) && File.Exists(Path.Combine(Path.GetDirectoryName(filename), path)))
461								path = Path.Combine(Path.GetDirectoryName(filename), path);
462
463							if (File.Exists(path))
464							{
465								if (!FilesystemManager.PathIsAdded(path))
466									FilesystemManager.AddSource(new SourceData() 
467									{ 
468										Data = path, 
469										HumanType = U.T("SourcesTypeFile"),
470										Type = SourceType.File,
471										Include = true
472									});
473								foreach (TrackData t in SettingsManager.FileTracks)
474									if (t.Path == path)
475									{
476										if (!pl.Tracks.Contains(t))
477											pl.Tracks.Add(t);
478										break;
479									}
480							}
481							else if (YouTubeManager.IsYouTube(path))
482							{
483								string yid = YouTubeManager.GetYouTubeID(path);
484								TrackData t = YouTubeManager.CreateTrack(yid);
485								if (!pl.Tracks.Contains(t))
486									pl.Tracks.Add(t);
487							}
488						}
489					}
490				}
491				sr.Close();
492				return pl;
493
494
495			// TODO: Replace with dispatching a custom event
496
497			//}
498			//catch (Exception e)
499			//{
500			//    MessageBox.Show("Error trying to load playlist:\n" + e.Message, "Couldn't Load Playlist", MessageBoxButton.OK,
501			//        MessageBoxImage.Error);
502			//    return null;
503			//}
504		}
505
506		/// <summary>
507		/// Deletes a playlist
508		/// </summary>
509		/// <param name="name">The name of the playlist to delete</param>
510		public static void RemovePlaylist(String name)
511		{
512			PlaylistData pl = FindPlaylist(name);
513			if (pl != null)
514			{
515				DispatchPlaylistModified(pl, ModifyType.Removed);
516
517				// and finally remove the playlist altogther (undo?)
518				SettingsManager.Playlists.Remove(pl);
519			}
520		}
521
522		/// <summary>
523		/// Tries to find a playlist with a given name
524		/// </summary>
525		/// <param name="name">The name of the playlist to look for</param>
526		/// <returns>The PlaylistData of the playlist with the name <paramref name="name"/> of such a playlist could be found, otherwise null.</returns>
527		public static PlaylistData FindPlaylist(String name)
528		{
529			foreach (PlaylistData p in SettingsManager.Playlists)
530				if (p.Name == name) return p;
531			return null;
532		}
533
534		/// <summary>
535		/// Update the "Last Played" and "Play Count" information of a given track
536		/// </summary>
537		/// <param name="RefTrack">The track that was just played</param>
538		public static void TrackWasPlayed(TrackData RefTrack)
539		{
540			if (RefTrack == null) return;
541
542			uint pc = RefTrack.PlayCount + 1;
543			foreach (TrackData track in SettingsManager.FileTracks)
544			{
545				if (track.Path == RefTrack.Path)
546				{
547					track.PlayCount = pc;
548					track.LastPlayed = DateTime.Now.ToString("g");
549					track.RawLastPlayed = DateTime.Now.ToString("yyyyMMddHHmmss");
550				}
551			}
552
553			foreach (TrackData track in SettingsManager.QueueTracks)
554			{
555				if (track.Path == RefTrack.Path)
556				{
557					track.PlayCount = pc;
558					track.LastPlayed = DateTime.Now.ToString("g");
559					track.RawLastPlayed = DateTime.Now.ToString("yyyyMMddHHmmss");
560				}
561			}
562
563			foreach (TrackData track in SettingsManager.HistoryTracks)
564				if (track.Path == RefTrack.Path)
565					track.PlayCount = pc;
566
567			foreach (PlaylistData playlist in SettingsManager.Playlists)
568			{
569				if (playlist.Name == CurrentPlaylist)
570				{
571					foreach (TrackData track in playlist.Tracks)
572					{
573						if (track.Path == RefTrack.Path)
574						{
575							track.PlayCount = pc;
576							track.LastPlayed = DateTime.Now.ToString("g");
577							track.RawLastPlayed = DateTime.Now.ToString("yyyyMMddHHmmss");
578						}
579					}
580				}
581			}
582		}
583
584        /// <summary>
585        /// Finds all playlists that contains a given track.
586        /// </summary>
587        /// <param name="track">The track to look for</param>
588        /// <returns>All playlists containing <paramref name="track"/></returns>
589		public static List<PlaylistData> Has(TrackData track)
590		{
591			List<PlaylistData> has = new List<PlaylistData>();
592			foreach (PlaylistData p in SettingsManager.Playlists)
593                if (Contains(p, track))
594                    has.Add(p);
595			return has;
596		}
597
598        /// <summary>
599        /// Checks whether a given playlist contains a given track.
600        /// </summary>
601        /// <param name="playlist">The playlist to search in</param>
602        /// <param name="track">The track to search for</param>
603        /// <returns>True of <paramref name="playlist"/> contains <paramref name="track"/>, otherwise false</returns>
604        public static bool Contains(PlaylistData playlist, TrackData track)
605        {
606            foreach (TrackData t in playlist.Tracks)
607                if (t.Path == track.Path)
608                    return true;
609            return false;
610        }
611
612        /// <summary>
613        /// Checks whether a given playlist contains any of a given list of track.
614        /// </summary>
615        /// <param name="playlist">The playlist to search in</param>
616        /// <param name="tracks">The tracks to search for</param>
617        /// <returns>True of <paramref name="playlist"/> contains any of <paramref name="tracks"/>, otherwise false</returns>
618        public static bool ContainsAny(PlaylistData playlist, List<TrackData> tracks)
619        {
620            foreach (TrackData t1 in playlist.Tracks)
621                foreach (TrackData t2 in tracks)
622                    if (t1.Path == t2.Path)
623                        return true;
624            return false;
625        }
626
627        /// <summary>
628        /// Checks whether a given playlist contains all of a given list of track.
629        /// </summary>
630        /// <param name="playlist">The playlist to search in</param>
631        /// <param name="tracks">The tracks to search for</param>
632        /// <returns>True of <paramref name="playlist"/> contains all of <paramref name="tracks"/>, otherwise false</returns>
633        public static bool ContainsAll(PlaylistData playlist, List<TrackData> tracks)
634        {
635            foreach (TrackData t1 in playlist.Tracks)
636                foreach (TrackData t2 in tracks)
637                    if (t1.Path != t2.Path)
638                        return false;
639            return playlist.Tracks.Count != 0;
640        }
641
642		#endregion
643
644		#region Event handlers
645
646		/// <summary>
647		/// Updates the total time of all tracks of a playlist.
648		/// </summary>
649		/// <param name="sender">The sender of the event</param>
650		/// <param name="e">The event data</param>
651		private static void TracksChanged(object sender, NotifyCollectionChangedEventArgs e)
652		{
653			ObservableCollection<TrackData> tracks = sender as ObservableCollection<TrackData>;
654
655			// find the playlist containing the track that sent the event
656			PlaylistData pl = null;
657			foreach (PlaylistData p in SettingsManager.Playlists)
658			{
659				if (p.Tracks == tracks)
660				{
661					pl = p;
662					break;
663				}
664			}
665
666			// no playlist found (weird!)
667			if (pl == null) return;
668
669			pl.Time = 0;
670			foreach (TrackData t in pl.Tracks)
671				pl.Time += t.RawLength;
672
673			if (pl.Time < 0) pl.Time = 0;
674
675			//// update gui if neccessary
676			//if (ParentWindow.NavigationPane.CurrentSelectedNavigation == "Playlist:" + pl.Name)
677			//{
678			//    ParentWindow.InfoPaneTracks.Text = pl.Tracks.Count.ToString() + " songs";
679			//    ParentWindow.InfoPaneDuration.Text = U.TimeSpanToLongString(new TimeSpan(0, 0, (int)pl.Time));
680			//}
681		}
682
683		#endregion
684
685		#region Dispatchers
686
687		/// <summary>
688		/// The dispatcher of the <see cref="PlaylistRenamed"/> event
689		/// </summary>
690		/// <param name="playlist">The playlist that was renamed</param>
691		/// <param name="oldName">The name of the playlist before the change</param>
692		/// <param name="newName">The name of the playlist after the change</param>
693		private static void DispatchPlaylistRenamed(PlaylistData playlist, string oldName, string newName)
694		{
695			if (PlaylistRenamed != null)
696				PlaylistRenamed(playlist, new RenamedEventArgs(WatcherChangeTypes.Renamed, "playlist", newName, oldName));
697		}
698
699		/// <summary>
700		/// The dispatcher of the <see cref="PlaylistModified"/> event
701		/// </summary>
702		/// <param name="playlist">The playlist that was modified</param>
703		/// <param name="type">The type of modification that occured</param>
704		private static void DispatchPlaylistModified(PlaylistData playlist, ModifyType type)
705		{
706			if (PlaylistModified != null)
707				PlaylistModified(playlist, new ModifiedEventArgs(type, null));
708		}
709
710		#endregion
711
712		#endregion
713
714		#region Events
715
716		/// <summary>
717		/// Occurs when a playlist has been renamed
718		/// </summary>
719		public static event RenamedEventHandler PlaylistRenamed;
720
721		/// <summary>
722		/// Occurs when a playlist has been created, removed or changed
723		/// </summary>
724		public static event ModifiedEventHandler PlaylistModified;
725
726		#endregion
727	}
728
729	#region Delegates
730
731	public delegate void ModifiedEventHandler(object sender, ModifiedEventArgs e);
732
733	#endregion
734
735	#region Event arguments
736
737	/// <summary>
738	/// Provides data for the events where something has been modified
739	/// </summary>
740	public class ModifiedEventArgs
741	{
742		/// <summary>
743		/// Gets or sets the type of modification that occured
744		/// </summary>
745		public ModifyType Type { get; set; }
746
747		/// <summary>
748		/// Gets or sets the data of the modification
749		/// </summary>
750		public object Data { get; set; }
751
752		/// <summary>
753		/// Creates an instance of the ModifiedEventArgs class
754		/// </summary>
755		/// <param name="type">The type of modification that occured</param>
756		/// <param name="data">The data of the modification</param>
757		public ModifiedEventArgs(ModifyType type, object data)
758		{
759			Type = type;
760			Data = data;
761		}
762	}
763
764	#endregion
765
766	#region Enums
767
768	/// <summary>
769	/// Represents the type of modification that can occur
770	/// </summary>
771	public enum ModifyType
772	{
773		/// <summary>
774		/// The object was created
775		/// </summary>
776		Created,
777
778		/// <summary>
779		/// The object was removed
780		/// </summary>
781		Removed,
782
783		/// <summary>
784		/// The object was changed
785		/// </summary>
786		Changed
787	}
788
789	#endregion
790}