/Application/Core/PlaylistManager.cs
C# | 790 lines | 522 code | 85 blank | 183 comment | 157 complexity | 8bbcc12f209d821197eaab786449d690 MD5 | raw file
- /**
- * PlaylistManager.cs
- *
- * Takes care of managing the playlists.
- *
- * * * * * * * * *
- *
- * Copyright 2012 Simplare
- *
- * This code is part of the Stoffi Music Player Project.
- * Visit our website at: stoffiplayer.com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version
- * 3 of the License, or (at your option) any later version.
- *
- * See stoffiplayer.com/license for more information.
- **/
-
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using System.Collections.ObjectModel;
- using System.Collections.Specialized;
- using System.IO;
- using System.Linq;
- using System.Text;
- using System.Threading;
- using System.Xml;
-
- namespace Stoffi
- {
- /// <summary>
- /// Represents a manager that takes care of the playlist logic
- /// </summary>
- public static class PlaylistManager
- {
- #region Members
-
- private static String supportedFileFormats = "m3u;pls";
-
- #endregion
-
- #region Properties
-
- /// <summary>
- /// The currently active playlist that is being played.
- /// An empty string if no playlist is active.
- /// </summary>
- public static String CurrentPlaylist { get; set; }
-
- #endregion
-
- #region Methods
-
- #region Public
-
- /// <summary>
- /// Initializes the playlist manager
- /// </summary>
- public static void Initialize()
- {
- CurrentPlaylist = "";
-
- ThreadStart GUIThread = delegate()
- {
- foreach (PlaylistData playlist in SettingsManager.Playlists)
- {
- // fix null tracks
- bool allTracksNull = true;
- foreach (TrackData t in playlist.Tracks)
- if (t != null)
- {
- allTracksNull = false;
- break;
- }
-
- if (allTracksNull) playlist.Tracks.Clear();
- playlist.Tracks.CollectionChanged += TracksChanged;
- DispatchPlaylistModified(playlist, ModifyType.Created);
- }
- };
-
- if (SettingsManager.CurrentActiveNavigation.StartsWith("Playlist:"))
- CurrentPlaylist = SettingsManager.CurrentActiveNavigation.Split(new[]{':'},2)[1];
-
- Thread thread = new Thread(GUIThread);
- thread.Name = "Playlist Thread";
- thread.Priority = ThreadPriority.BelowNormal;
- thread.Start();
- }
-
- /// <summary>
- /// Add tracks to a playlist
- /// </summary>
- /// <param name="tracks">The list of tracks to be added</param>
- /// <param name="playlistName">The name of the playlist to add the tracks to</param>
- /// <param name="pos">The position to insert the track at (-1 means at the end)</param>
- public static void AddToPlaylist(List<object> tracks, String playlistName, int pos = -1)
- {
- PlaylistData playlist = FindPlaylist(playlistName);
- if (playlist == null) return;
- foreach (TrackData track in tracks)
- {
- if (!playlist.Tracks.Contains(track))
- {
- if (pos < 0 || pos >= playlist.Tracks.Count)
- playlist.Tracks.Add(track);
- else
- playlist.Tracks.Insert(pos, track);
- track.Source = "Playlist:" + playlist.Name;
- }
- }
- }
-
- /// <summary>
- /// Add tracks to a playlist
- /// </summary>
- /// <param name="tracks">The list of tracks to be added</param>
- /// <param name="playlistName">The name of the playlist to add the tracks to</param>
- /// <param name="pos">The position to insert the track at (-1 means at the end)</param>
- public static void AddToPlaylist(ObservableCollection<TrackData> tracks, String playlistName, int pos = -1)
- {
- PlaylistData playlist = FindPlaylist(playlistName);
- if (playlist == null) return;
- foreach (TrackData track in tracks)
- {
- if (!playlist.Tracks.Contains(track))
- {
- if (pos < 0 || pos >= playlist.Tracks.Count)
- playlist.Tracks.Add(track);
- else
- playlist.Tracks.Insert(pos, track);
- track.Source = "Playlist:" + playlist.Name;
- }
- }
- }
-
- /// <summary>
- /// Add tracks to a playlist
- /// </summary>
- /// <param name="tracks">The list of tracks to be added</param>
- /// <param name="playlistName">The name of the playlist to add the tracks to</param>
- /// <param name="pos">The position to insert the track at (-1 means at the end)</param>
- public static void AddToPlaylist(List<TrackData> tracks, String playlistName, int pos = -1)
- {
- PlaylistData playlist = FindPlaylist(playlistName);
- if (playlist == null) return;
- foreach (TrackData track in tracks)
- {
- // insert
- if (!playlist.Tracks.Contains(track))
- {
- if (pos < 0 || pos >= playlist.Tracks.Count)
- playlist.Tracks.Add(track);
- else
- playlist.Tracks.Insert(pos, track);
- track.Source = "Playlist:" + playlist.Name;
- }
-
- // move
- else if (pos != playlist.Tracks.IndexOf(track))
- {
- if (pos < 0 || pos >= playlist.Tracks.Count)
- {
- playlist.Tracks.Remove(track);
- playlist.Tracks.Add(track);
- }
- else if (pos < playlist.Tracks.IndexOf(track))
- {
- playlist.Tracks.Remove(track);
- playlist.Tracks.Insert(pos, track);
- }
- else
- {
- playlist.Tracks.Remove(track);
- playlist.Tracks.Insert(pos-1, track);
- }
- }
- }
- }
-
- /// <summary>
- /// Check to see if a file is a supported playlist file
- /// </summary>
- /// <param name="path">The filename to check</param>
- /// <returns>true of the file can be opened by Stoffi, otherwise false</returns>
- public static bool IsSupported(String path)
- {
- return path.StartsWith("playlist://") ||
- (supportedFileFormats.Split(';').Contains<string>(Path.GetExtension(path)));
- }
-
- /// <summary>
- /// Remove tracks from a playlist if they are found inside the playlist
- /// </summary>
- /// <param name="tracks">The list of t to be removed</param>
- /// <param name="playlistName">The name of the playlist to remove the t from</param>
- public static void RemoveFromPlaylist(List<TrackData> tracks, String playlistName)
- {
- PlaylistData playlist = FindPlaylist(playlistName);
- if (playlist != null)
- {
- foreach (TrackData track in tracks)
- foreach (TrackData trackInPlaylist in playlist.Tracks)
- if (trackInPlaylist.Path == track.Path)
- {
- playlist.Tracks.Remove(trackInPlaylist);
- break;
- }
- }
- }
-
- /// <summary>
- /// Creates a new playlist
- /// </summary>
- /// <param name="name">The name of the new playlist (this will be appended with a number if neccessary)</param>
- /// <returns>The newly created PlaylistData for the playlist</returns>
- public static PlaylistData CreatePlaylist(String name)
- {
- name = U.CleanXMLString(name);
-
- if (FindPlaylist(name) != null)
- {
- int pExt = 1;
- while (FindPlaylist(name + pExt) != null)
- pExt++;
- name = name + pExt;
- }
-
- PlaylistData playlist = new PlaylistData();
- playlist.Name = name;
- playlist.Time = 0;
- playlist.Tracks = new ObservableCollection<TrackData>();
- playlist.Tracks.CollectionChanged += TracksChanged;
- SettingsManager.Playlists.Add(playlist);
-
- DispatchPlaylistModified(playlist, ModifyType.Created);
-
- return playlist;
- }
-
- /// <summary>
- /// 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.
- /// </summary>
- /// <param name="oldName">The current name of the playlist to be renamed</param>
- /// <param name="newName">The new name of the playlist</param>
- public static void RenamePlaylist(String oldName, String newName)
- {
- newName = U.CleanXMLString(newName);
-
- PlaylistData pl = FindPlaylist(oldName);
-
- if (FindPlaylist(newName) != null)
- {
- int pExt = 1;
- while (FindPlaylist(newName + pExt) != null)
- pExt++;
- newName = newName + pExt;
- }
-
- if (pl != null && newName != "" && newName.ToLower() != U.T("NavigationCreateNew").ToLower())
- DispatchPlaylistRenamed(pl, oldName, newName);
- }
-
- /// <summary>
- /// Saves a playlist as a file
- /// </summary>
- /// <param name="path">The path of the saved playlist</param>
- /// <param name="name">The name of the playlist to save</param>
- public static void SavePlaylist(String path, String name)
- {
- PlaylistData pl = FindPlaylist(name);
- if (pl != null)
- {
- string ext = Path.GetExtension(path);
- System.IO.StreamWriter sw = System.IO.File.AppendText(path);
-
- if (ext == ".pls")
- {
- sw.WriteLine("[playlist]");
- sw.WriteLine("");
- int i = 0;
- foreach (TrackData track in pl.Tracks)
- {
- i++;
- sw.WriteLine(String.Format("File{0}={1}", i, track.Path));
- sw.WriteLine(String.Format("Title{0}={1}", i, track.Title));
- sw.WriteLine(String.Format("Length{0}={1}", i, (int)track.RawLength));
- sw.WriteLine("");
- }
- sw.WriteLine("NumberOfEntries=" + i);
- sw.WriteLine("Version=2");
- }
- else if (ext == ".m3u")
- {
- sw.WriteLine("#EXTM3U");
- sw.WriteLine("");
- foreach (TrackData track in pl.Tracks)
- {
- sw.WriteLine(String.Format("#EXTINF:{0},{1} - {2}", (int)track.RawLength, track.Artist, track.Title));
- sw.WriteLine(track.Path);
- sw.WriteLine("");
- }
- }
- sw.Close();
- }
- }
-
- /// <summary>
- /// Reads a file and creates a playlist using the name of the file.
- /// </summary>
- /// <param name="filename">The file to read</param>
- /// <returns>The PlaylistData of the newly created playlist</returns>
- public static PlaylistData LoadPlaylist(String filename)
- {
- //try
- //{
- string pName = Path.GetFileNameWithoutExtension(filename);
- if (FindPlaylist(pName) != null)
- {
- int pExt = 1;
- while (FindPlaylist(pName + pExt) != null)
- pExt++;
- pName = pName + pExt;
- }
- PlaylistData pl = CreatePlaylist(pName);
- StreamReader sr = new StreamReader(filename);
- string line;
- int nr = 0;
- if (Path.GetExtension(filename) == ".m3u")
- {
- bool ext = false;
- string inf = "";
- while ((line = sr.ReadLine()) != null)
- {
- nr++;
- if (line == "#EXTM3U")
- ext = true;
- else if (ext && line.StartsWith("#EXTINF:"))
- inf = line.Substring(8);
- else if (line.StartsWith("#") || line == "")
- continue;
- else
- {
- string path = line;
-
- if (!File.Exists(path) && File.Exists(Path.Combine(Path.GetDirectoryName(filename), path)))
- path = Path.Combine(Path.GetDirectoryName(filename), path);
-
- if (File.Exists(path))
- {
- string length = "";
- string artist = "";
- string title = "";
- if (inf != "")
- {
- if (!inf.Contains(','))
- {
- U.L(LogLevel.Warning, "PLAYLIST", "Bad format in " + filename + " on line "
- + nr + ": expecting ','");
- continue;
- }
- string[] split = inf.Split(',');
- length = split[0];
- if (split[1].Contains('-'))
- {
- artist = split[1].Split('-')[0];
- title = split[1].Split('-')[1];
- }
- else
- title = split[1];
- }
- if (!FilesystemManager.PathIsAdded(path))
- FilesystemManager.AddSource(new SourceData()
- {
- Data = path,
- HumanType = U.T("SourcesTypeFile"),
- Type = SourceType.File,
- Include = true
- });
- foreach (TrackData t in SettingsManager.FileTracks)
- if (t.Path == path)
- {
- if (!pl.Tracks.Contains(t))
- pl.Tracks.Add(t);
- break;
- }
- inf = "";
- }
- else if (YouTubeManager.IsYouTube(path))
- {
- string yid = YouTubeManager.GetYouTubeID(path);
- TrackData t = YouTubeManager.CreateTrack(yid);
- if (!pl.Tracks.Contains(t))
- pl.Tracks.Add(t);
- }
- }
- }
- }
- else if (Path.GetExtension(filename) == ".pls")
- {
- bool hdr = false;
- string version = "";
- int noe = 0;
- while ((line = sr.ReadLine()) != null)
- {
- nr++;
- if (line == "[playlist]")
- hdr = true;
- else if (!hdr)
- U.L(LogLevel.Warning, "PLAYLIST", "Bad format in " + filename + " on line "
- + nr + ": expecting '[playlist]'");
- else if (line.StartsWith("NumberOfEntries="))
- noe = Convert.ToInt32(line.Split('=')[1]);
- else if (line.StartsWith("Version="))
- version = line.Split('=')[1];
- }
-
- if (!hdr)
- U.L(LogLevel.Warning, "PLAYLIST", "Error in " + filename + ": No header found");
- else if (version != "2")
- U.L(LogLevel.Warning, "PLAYLIST", "Error in " + filename + ": Unsupported version '" +
- version + "'");
- else
- {
- sr.Close();
- sr = new StreamReader(filename);
- string[,] tracks = new string[noe, 3];
- nr = 0;
- while ((line = sr.ReadLine()) != null)
- {
- if (line.StartsWith("File") || line.StartsWith("Title") || line.StartsWith("Length"))
- {
- int tmp = 4;
- int index = 0;
- if (line.StartsWith("Title")) { tmp = 5; index = 1; }
- else if (line.StartsWith("Length")) { tmp = 6; index = 2; }
-
- string[] split = line.Split('=');
- int number = Convert.ToInt32(split[0].Substring(tmp));
-
- if (number > noe)
- U.L(LogLevel.Warning, "PLAYLIST", "Bad format in " + filename + " on line "
- + nr + ": entry number is '" + number + "' but NumberOfEntries is '" + noe + "'");
- else
- tracks[number-1, index] = split[1];
- }
- else if (!line.StartsWith("NumberOfEntries") && line != "[playlist]" && !line.StartsWith("Version="))
- {
- U.L(LogLevel.Warning, "PLAYLIST", "Bad format in " + filename + " on line "
- + nr + ": unexpected '" + line + "'");
- }
- }
- for (int i = 0; i < noe; i++)
- {
- string path = tracks[i, 0];
-
- if (!File.Exists(path) && File.Exists(Path.Combine(Path.GetDirectoryName(filename), path)))
- path = Path.Combine(Path.GetDirectoryName(filename), path);
-
- if (File.Exists(path))
- {
- if (!FilesystemManager.PathIsAdded(path))
- FilesystemManager.AddSource(new SourceData()
- {
- Data = path,
- HumanType = U.T("SourcesTypeFile"),
- Type = SourceType.File,
- Include = true
- });
- foreach (TrackData t in SettingsManager.FileTracks)
- if (t.Path == path)
- {
- if (!pl.Tracks.Contains(t))
- pl.Tracks.Add(t);
- break;
- }
- }
- else if (YouTubeManager.IsYouTube(path))
- {
- string yid = YouTubeManager.GetYouTubeID(path);
- TrackData t = YouTubeManager.CreateTrack(yid);
- if (!pl.Tracks.Contains(t))
- pl.Tracks.Add(t);
- }
- }
- }
- }
- sr.Close();
- return pl;
-
-
- // TODO: Replace with dispatching a custom event
-
- //}
- //catch (Exception e)
- //{
- // MessageBox.Show("Error trying to load playlist:\n" + e.Message, "Couldn't Load Playlist", MessageBoxButton.OK,
- // MessageBoxImage.Error);
- // return null;
- //}
- }
-
- /// <summary>
- /// Deletes a playlist
- /// </summary>
- /// <param name="name">The name of the playlist to delete</param>
- public static void RemovePlaylist(String name)
- {
- PlaylistData pl = FindPlaylist(name);
- if (pl != null)
- {
- DispatchPlaylistModified(pl, ModifyType.Removed);
-
- // and finally remove the playlist altogther (undo?)
- SettingsManager.Playlists.Remove(pl);
- }
- }
-
- /// <summary>
- /// Tries to find a playlist with a given name
- /// </summary>
- /// <param name="name">The name of the playlist to look for</param>
- /// <returns>The PlaylistData of the playlist with the name <paramref name="name"/> of such a playlist could be found, otherwise null.</returns>
- public static PlaylistData FindPlaylist(String name)
- {
- foreach (PlaylistData p in SettingsManager.Playlists)
- if (p.Name == name) return p;
- return null;
- }
-
- /// <summary>
- /// Update the "Last Played" and "Play Count" information of a given track
- /// </summary>
- /// <param name="RefTrack">The track that was just played</param>
- public static void TrackWasPlayed(TrackData RefTrack)
- {
- if (RefTrack == null) return;
-
- uint pc = RefTrack.PlayCount + 1;
- foreach (TrackData track in SettingsManager.FileTracks)
- {
- if (track.Path == RefTrack.Path)
- {
- track.PlayCount = pc;
- track.LastPlayed = DateTime.Now.ToString("g");
- track.RawLastPlayed = DateTime.Now.ToString("yyyyMMddHHmmss");
- }
- }
-
- foreach (TrackData track in SettingsManager.QueueTracks)
- {
- if (track.Path == RefTrack.Path)
- {
- track.PlayCount = pc;
- track.LastPlayed = DateTime.Now.ToString("g");
- track.RawLastPlayed = DateTime.Now.ToString("yyyyMMddHHmmss");
- }
- }
-
- foreach (TrackData track in SettingsManager.HistoryTracks)
- if (track.Path == RefTrack.Path)
- track.PlayCount = pc;
-
- foreach (PlaylistData playlist in SettingsManager.Playlists)
- {
- if (playlist.Name == CurrentPlaylist)
- {
- foreach (TrackData track in playlist.Tracks)
- {
- if (track.Path == RefTrack.Path)
- {
- track.PlayCount = pc;
- track.LastPlayed = DateTime.Now.ToString("g");
- track.RawLastPlayed = DateTime.Now.ToString("yyyyMMddHHmmss");
- }
- }
- }
- }
- }
-
- /// <summary>
- /// Finds all playlists that contains a given track.
- /// </summary>
- /// <param name="track">The track to look for</param>
- /// <returns>All playlists containing <paramref name="track"/></returns>
- public static List<PlaylistData> Has(TrackData track)
- {
- List<PlaylistData> has = new List<PlaylistData>();
- foreach (PlaylistData p in SettingsManager.Playlists)
- if (Contains(p, track))
- has.Add(p);
- return has;
- }
-
- /// <summary>
- /// Checks whether a given playlist contains a given track.
- /// </summary>
- /// <param name="playlist">The playlist to search in</param>
- /// <param name="track">The track to search for</param>
- /// <returns>True of <paramref name="playlist"/> contains <paramref name="track"/>, otherwise false</returns>
- public static bool Contains(PlaylistData playlist, TrackData track)
- {
- foreach (TrackData t in playlist.Tracks)
- if (t.Path == track.Path)
- return true;
- return false;
- }
-
- /// <summary>
- /// Checks whether a given playlist contains any of a given list of track.
- /// </summary>
- /// <param name="playlist">The playlist to search in</param>
- /// <param name="tracks">The tracks to search for</param>
- /// <returns>True of <paramref name="playlist"/> contains any of <paramref name="tracks"/>, otherwise false</returns>
- public static bool ContainsAny(PlaylistData playlist, List<TrackData> tracks)
- {
- foreach (TrackData t1 in playlist.Tracks)
- foreach (TrackData t2 in tracks)
- if (t1.Path == t2.Path)
- return true;
- return false;
- }
-
- /// <summary>
- /// Checks whether a given playlist contains all of a given list of track.
- /// </summary>
- /// <param name="playlist">The playlist to search in</param>
- /// <param name="tracks">The tracks to search for</param>
- /// <returns>True of <paramref name="playlist"/> contains all of <paramref name="tracks"/>, otherwise false</returns>
- public static bool ContainsAll(PlaylistData playlist, List<TrackData> tracks)
- {
- foreach (TrackData t1 in playlist.Tracks)
- foreach (TrackData t2 in tracks)
- if (t1.Path != t2.Path)
- return false;
- return playlist.Tracks.Count != 0;
- }
-
- #endregion
-
- #region Event handlers
-
- /// <summary>
- /// Updates the total time of all tracks of a playlist.
- /// </summary>
- /// <param name="sender">The sender of the event</param>
- /// <param name="e">The event data</param>
- private static void TracksChanged(object sender, NotifyCollectionChangedEventArgs e)
- {
- ObservableCollection<TrackData> tracks = sender as ObservableCollection<TrackData>;
-
- // find the playlist containing the track that sent the event
- PlaylistData pl = null;
- foreach (PlaylistData p in SettingsManager.Playlists)
- {
- if (p.Tracks == tracks)
- {
- pl = p;
- break;
- }
- }
-
- // no playlist found (weird!)
- if (pl == null) return;
-
- pl.Time = 0;
- foreach (TrackData t in pl.Tracks)
- pl.Time += t.RawLength;
-
- if (pl.Time < 0) pl.Time = 0;
-
- //// update gui if neccessary
- //if (ParentWindow.NavigationPane.CurrentSelectedNavigation == "Playlist:" + pl.Name)
- //{
- // ParentWindow.InfoPaneTracks.Text = pl.Tracks.Count.ToString() + " songs";
- // ParentWindow.InfoPaneDuration.Text = U.TimeSpanToLongString(new TimeSpan(0, 0, (int)pl.Time));
- //}
- }
-
- #endregion
-
- #region Dispatchers
-
- /// <summary>
- /// The dispatcher of the <see cref="PlaylistRenamed"/> event
- /// </summary>
- /// <param name="playlist">The playlist that was renamed</param>
- /// <param name="oldName">The name of the playlist before the change</param>
- /// <param name="newName">The name of the playlist after the change</param>
- private static void DispatchPlaylistRenamed(PlaylistData playlist, string oldName, string newName)
- {
- if (PlaylistRenamed != null)
- PlaylistRenamed(playlist, new RenamedEventArgs(WatcherChangeTypes.Renamed, "playlist", newName, oldName));
- }
-
- /// <summary>
- /// The dispatcher of the <see cref="PlaylistModified"/> event
- /// </summary>
- /// <param name="playlist">The playlist that was modified</param>
- /// <param name="type">The type of modification that occured</param>
- private static void DispatchPlaylistModified(PlaylistData playlist, ModifyType type)
- {
- if (PlaylistModified != null)
- PlaylistModified(playlist, new ModifiedEventArgs(type, null));
- }
-
- #endregion
-
- #endregion
-
- #region Events
-
- /// <summary>
- /// Occurs when a playlist has been renamed
- /// </summary>
- public static event RenamedEventHandler PlaylistRenamed;
-
- /// <summary>
- /// Occurs when a playlist has been created, removed or changed
- /// </summary>
- public static event ModifiedEventHandler PlaylistModified;
-
- #endregion
- }
-
- #region Delegates
-
- public delegate void ModifiedEventHandler(object sender, ModifiedEventArgs e);
-
- #endregion
-
- #region Event arguments
-
- /// <summary>
- /// Provides data for the events where something has been modified
- /// </summary>
- public class ModifiedEventArgs
- {
- /// <summary>
- /// Gets or sets the type of modification that occured
- /// </summary>
- public ModifyType Type { get; set; }
-
- /// <summary>
- /// Gets or sets the data of the modification
- /// </summary>
- public object Data { get; set; }
-
- /// <summary>
- /// Creates an instance of the ModifiedEventArgs class
- /// </summary>
- /// <param name="type">The type of modification that occured</param>
- /// <param name="data">The data of the modification</param>
- public ModifiedEventArgs(ModifyType type, object data)
- {
- Type = type;
- Data = data;
- }
- }
-
- #endregion
-
- #region Enums
-
- /// <summary>
- /// Represents the type of modification that can occur
- /// </summary>
- public enum ModifyType
- {
- /// <summary>
- /// The object was created
- /// </summary>
- Created,
-
- /// <summary>
- /// The object was removed
- /// </summary>
- Removed,
-
- /// <summary>
- /// The object was changed
- /// </summary>
- Changed
- }
-
- #endregion
- }