2014-08-02 02:34:45 +00:00
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
using System.Threading ;
using System.Threading.Tasks ;
2017-05-21 07:25:49 +00:00
using MediaBrowser.Controller.Dto ;
2019-01-13 19:22:00 +00:00
using MediaBrowser.Controller.Entities ;
using MediaBrowser.Controller.Entities.Audio ;
using MediaBrowser.Controller.Library ;
using MediaBrowser.Controller.Playlists ;
using MediaBrowser.Controller.Providers ;
using MediaBrowser.Model.Entities ;
2016-10-25 19:02:04 +00:00
using MediaBrowser.Model.IO ;
2019-01-13 19:22:00 +00:00
using MediaBrowser.Model.Playlists ;
using Microsoft.Extensions.Logging ;
2018-09-12 17:26:21 +00:00
using PlaylistsNET.Content ;
using PlaylistsNET.Models ;
2014-08-02 02:34:45 +00:00
2016-11-02 21:05:17 +00:00
namespace Emby.Server.Implementations.Playlists
2014-08-02 02:34:45 +00:00
{
public class PlaylistManager : IPlaylistManager
{
private readonly ILibraryManager _libraryManager ;
private readonly IFileSystem _fileSystem ;
private readonly ILibraryMonitor _iLibraryMonitor ;
private readonly ILogger _logger ;
private readonly IUserManager _userManager ;
2015-07-24 15:20:11 +00:00
private readonly IProviderManager _providerManager ;
2014-08-02 02:34:45 +00:00
2015-07-24 15:20:11 +00:00
public PlaylistManager ( ILibraryManager libraryManager , IFileSystem fileSystem , ILibraryMonitor iLibraryMonitor , ILogger logger , IUserManager userManager , IProviderManager providerManager )
2014-08-02 02:34:45 +00:00
{
_libraryManager = libraryManager ;
_fileSystem = fileSystem ;
_iLibraryMonitor = iLibraryMonitor ;
_logger = logger ;
_userManager = userManager ;
2015-07-24 15:20:11 +00:00
_providerManager = providerManager ;
2014-08-02 02:34:45 +00:00
}
2018-09-12 17:26:21 +00:00
public IEnumerable < Playlist > GetPlaylists ( Guid userId )
2014-08-02 02:34:45 +00:00
{
2014-09-14 15:10:51 +00:00
var user = _userManager . GetUserById ( userId ) ;
2014-08-02 02:34:45 +00:00
return GetPlaylistsFolder ( userId ) . GetChildren ( user , true ) . OfType < Playlist > ( ) ;
}
2014-08-21 15:55:35 +00:00
public async Task < PlaylistCreationResult > CreatePlaylist ( PlaylistCreationRequest options )
2014-08-02 02:34:45 +00:00
{
var name = options . Name ;
var folderName = _fileSystem . GetValidFilename ( name ) + " [playlist]" ;
2018-09-12 17:26:21 +00:00
var parentFolder = GetPlaylistsFolder ( Guid . Empty ) ;
2014-08-02 02:34:45 +00:00
if ( parentFolder = = null )
{
throw new ArgumentException ( ) ;
}
2018-09-12 17:26:21 +00:00
if ( string . IsNullOrEmpty ( options . MediaType ) )
2014-08-03 02:16:37 +00:00
{
foreach ( var itemId in options . ItemIdList )
{
var item = _libraryManager . GetItemById ( itemId ) ;
if ( item = = null )
{
throw new ArgumentException ( "No item exists with the supplied Id" ) ;
}
2018-09-12 17:26:21 +00:00
if ( ! string . IsNullOrEmpty ( item . MediaType ) )
2014-08-03 02:16:37 +00:00
{
options . MediaType = item . MediaType ;
}
else if ( item is MusicArtist | | item is MusicAlbum | | item is MusicGenre )
{
options . MediaType = MediaType . Audio ;
}
else if ( item is Genre )
{
options . MediaType = MediaType . Video ;
}
else
{
var folder = item as Folder ;
if ( folder ! = null )
{
2015-01-25 06:34:50 +00:00
options . MediaType = folder . GetRecursiveChildren ( i = > ! i . IsFolder & & i . SupportsAddingToPlaylist )
2014-08-03 02:16:37 +00:00
. Select ( i = > i . MediaType )
2018-09-12 17:26:21 +00:00
. FirstOrDefault ( i = > ! string . IsNullOrEmpty ( i ) ) ;
2014-08-03 02:16:37 +00:00
}
}
2018-09-12 17:26:21 +00:00
if ( ! string . IsNullOrEmpty ( options . MediaType ) )
2014-08-03 02:16:37 +00:00
{
break ;
}
}
}
2018-09-12 17:26:21 +00:00
if ( string . IsNullOrEmpty ( options . MediaType ) )
2014-08-03 02:16:37 +00:00
{
2017-01-14 03:48:42 +00:00
options . MediaType = "Audio" ;
2014-08-03 02:16:37 +00:00
}
2014-09-14 15:10:51 +00:00
var user = _userManager . GetUserById ( options . UserId ) ;
2014-08-22 02:24:38 +00:00
2014-08-02 02:34:45 +00:00
var path = Path . Combine ( parentFolder . Path , folderName ) ;
2014-08-03 02:16:37 +00:00
path = GetTargetPath ( path ) ;
2014-08-02 02:34:45 +00:00
_iLibraryMonitor . ReportFileSystemChangeBeginning ( path ) ;
try
{
2015-10-15 15:51:00 +00:00
_fileSystem . CreateDirectory ( path ) ;
2014-08-02 02:34:45 +00:00
2014-08-03 02:16:37 +00:00
var playlist = new Playlist
2014-08-02 02:34:45 +00:00
{
Name = name ,
2018-09-12 17:26:21 +00:00
Path = path ,
Shares = new [ ]
{
new Share
{
UserId = options . UserId . Equals ( Guid . Empty ) ? null : options . UserId . ToString ( "N" ) ,
CanEdit = true
}
}
2014-08-02 02:34:45 +00:00
} ;
2014-08-03 02:16:37 +00:00
playlist . SetMediaType ( options . MediaType ) ;
2017-08-27 00:32:33 +00:00
parentFolder . AddChild ( playlist , CancellationToken . None ) ;
2014-08-02 02:34:45 +00:00
2018-12-14 19:17:29 +00:00
await playlist . RefreshMetadata ( new MetadataRefreshOptions ( new DirectoryService ( _logger , _fileSystem ) ) { ForceSave = true } , CancellationToken . None )
2014-08-02 02:34:45 +00:00
. ConfigureAwait ( false ) ;
2017-08-19 19:43:35 +00:00
if ( options . ItemIdList . Length > 0 )
2014-08-02 02:34:45 +00:00
{
2018-09-12 17:26:21 +00:00
AddToPlaylistInternal ( playlist . Id . ToString ( "N" ) , options . ItemIdList , user , new DtoOptions ( false )
2017-05-21 07:25:49 +00:00
{
EnableImages = true
} ) ;
2014-08-02 02:34:45 +00:00
}
2014-08-21 15:55:35 +00:00
return new PlaylistCreationResult
{
Id = playlist . Id . ToString ( "N" )
} ;
2014-08-02 02:34:45 +00:00
}
finally
{
// Refresh handled internally
_iLibraryMonitor . ReportFileSystemChangeComplete ( path , false ) ;
}
}
2014-08-03 02:16:37 +00:00
private string GetTargetPath ( string path )
{
2015-10-15 15:51:00 +00:00
while ( _fileSystem . DirectoryExists ( path ) )
2014-08-03 02:16:37 +00:00
{
path + = "1" ;
}
return path ;
}
2018-09-12 17:26:21 +00:00
private List < BaseItem > GetPlaylistItems ( IEnumerable < Guid > itemIds , string playlistMediaType , User user , DtoOptions options )
2014-08-03 02:16:37 +00:00
{
var items = itemIds . Select ( i = > _libraryManager . GetItemById ( i ) ) . Where ( i = > i ! = null ) ;
2017-05-21 07:25:49 +00:00
return Playlist . GetPlaylistItems ( playlistMediaType , items , user , options ) ;
2014-08-22 02:24:38 +00:00
}
2018-09-12 17:26:21 +00:00
public void AddToPlaylist ( string playlistId , IEnumerable < Guid > itemIds , Guid userId )
2014-08-22 02:24:38 +00:00
{
2018-09-12 17:26:21 +00:00
var user = userId . Equals ( Guid . Empty ) ? null : _userManager . GetUserById ( userId ) ;
2014-08-22 02:24:38 +00:00
2018-09-12 17:26:21 +00:00
AddToPlaylistInternal ( playlistId , itemIds , user , new DtoOptions ( false )
2017-05-21 07:25:49 +00:00
{
EnableImages = true
} ) ;
2014-08-03 02:16:37 +00:00
}
2018-09-12 17:26:21 +00:00
private void AddToPlaylistInternal ( string playlistId , IEnumerable < Guid > itemIds , User user , DtoOptions options )
2014-08-02 02:34:45 +00:00
{
2014-08-03 02:16:37 +00:00
var playlist = _libraryManager . GetItemById ( playlistId ) as Playlist ;
2014-08-02 02:34:45 +00:00
2014-08-03 02:16:37 +00:00
if ( playlist = = null )
2014-08-02 02:34:45 +00:00
{
throw new ArgumentException ( "No Playlist exists with the supplied Id" ) ;
}
var list = new List < LinkedChild > ( ) ;
2017-05-26 06:48:54 +00:00
var items = ( GetPlaylistItems ( itemIds , playlist . MediaType , user , options ) )
2014-08-22 02:24:38 +00:00
. Where ( i = > i . SupportsAddingToPlaylist )
. ToList ( ) ;
2014-08-02 02:34:45 +00:00
2014-08-07 02:51:09 +00:00
foreach ( var item in items )
{
2014-08-03 02:16:37 +00:00
list . Add ( LinkedChild . Create ( item ) ) ;
2014-08-02 02:34:45 +00:00
}
2017-08-10 18:01:31 +00:00
var newList = playlist . LinkedChildren . ToList ( ) ;
newList . AddRange ( list ) ;
2018-12-28 15:48:26 +00:00
playlist . LinkedChildren = newList . ToArray ( ) ;
2014-08-03 02:16:37 +00:00
2017-10-03 18:39:37 +00:00
playlist . UpdateToRepository ( ItemUpdateType . MetadataEdit , CancellationToken . None ) ;
2015-07-24 15:20:11 +00:00
2018-09-12 17:26:21 +00:00
if ( playlist . IsFile )
{
SavePlaylistFile ( playlist ) ;
}
2018-12-14 19:17:29 +00:00
_providerManager . QueueRefresh ( playlist . Id , new MetadataRefreshOptions ( new DirectoryService ( _logger , _fileSystem ) )
2014-08-21 15:55:35 +00:00
{
2014-08-03 02:16:37 +00:00
ForceSave = true
2017-04-30 02:37:51 +00:00
} , RefreshPriority . High ) ;
2014-08-02 02:34:45 +00:00
}
2018-09-12 17:26:21 +00:00
public void RemoveFromPlaylist ( string playlistId , IEnumerable < string > entryIds )
2014-08-02 02:34:45 +00:00
{
2014-08-06 04:18:13 +00:00
var playlist = _libraryManager . GetItemById ( playlistId ) as Playlist ;
if ( playlist = = null )
{
throw new ArgumentException ( "No Playlist exists with the supplied Id" ) ;
}
2014-08-11 23:41:11 +00:00
var children = playlist . GetManageableItems ( ) . ToList ( ) ;
2014-08-06 04:18:13 +00:00
var idList = entryIds . ToList ( ) ;
2014-08-11 23:41:11 +00:00
var removals = children . Where ( i = > idList . Contains ( i . Item1 . Id ) ) ;
2014-08-06 04:18:13 +00:00
playlist . LinkedChildren = children . Except ( removals )
2014-08-11 23:41:11 +00:00
. Select ( i = > i . Item1 )
2017-08-10 18:01:31 +00:00
. ToArray ( ) ;
2014-08-06 04:18:13 +00:00
2017-10-03 18:39:37 +00:00
playlist . UpdateToRepository ( ItemUpdateType . MetadataEdit , CancellationToken . None ) ;
2015-07-24 15:20:11 +00:00
2018-09-12 17:26:21 +00:00
if ( playlist . IsFile )
{
SavePlaylistFile ( playlist ) ;
}
2018-12-14 19:17:29 +00:00
_providerManager . QueueRefresh ( playlist . Id , new MetadataRefreshOptions ( new DirectoryService ( _logger , _fileSystem ) )
2014-08-06 04:18:13 +00:00
{
ForceSave = true
2017-04-30 02:37:51 +00:00
} , RefreshPriority . High ) ;
2014-08-02 02:34:45 +00:00
}
2018-09-12 17:26:21 +00:00
public void MoveItem ( string playlistId , string entryId , int newIndex )
2015-10-15 05:48:03 +00:00
{
var playlist = _libraryManager . GetItemById ( playlistId ) as Playlist ;
if ( playlist = = null )
{
throw new ArgumentException ( "No Playlist exists with the supplied Id" ) ;
}
var children = playlist . GetManageableItems ( ) . ToList ( ) ;
var oldIndex = children . FindIndex ( i = > string . Equals ( entryId , i . Item1 . Id , StringComparison . OrdinalIgnoreCase ) ) ;
2015-10-15 15:51:00 +00:00
if ( oldIndex = = newIndex )
{
return ;
}
2015-10-15 05:48:03 +00:00
var item = playlist . LinkedChildren [ oldIndex ] ;
2017-08-10 18:01:31 +00:00
var newList = playlist . LinkedChildren . ToList ( ) ;
2016-07-16 18:02:39 +00:00
2017-08-10 18:01:31 +00:00
newList . Remove ( item ) ;
if ( newIndex > = newList . Count )
2016-07-16 18:02:39 +00:00
{
2017-08-10 18:01:31 +00:00
newList . Add ( item ) ;
2016-07-16 18:02:39 +00:00
}
else
{
2017-08-10 18:01:31 +00:00
newList . Insert ( newIndex , item ) ;
2016-07-16 18:02:39 +00:00
}
2015-10-15 05:48:03 +00:00
2018-12-28 15:48:26 +00:00
playlist . LinkedChildren = newList . ToArray ( ) ;
2017-08-10 18:01:31 +00:00
2017-10-03 18:39:37 +00:00
playlist . UpdateToRepository ( ItemUpdateType . MetadataEdit , CancellationToken . None ) ;
2018-09-12 17:26:21 +00:00
if ( playlist . IsFile )
{
SavePlaylistFile ( playlist ) ;
}
}
private void SavePlaylistFile ( Playlist item )
{
// This is probably best done as a metatata provider, but saving a file over itself will first require some core work to prevent this from happening when not needed
var playlistPath = item . Path ;
var extension = Path . GetExtension ( playlistPath ) ;
if ( string . Equals ( ".wpl" , extension , StringComparison . OrdinalIgnoreCase ) )
{
var playlist = new WplPlaylist ( ) ;
foreach ( var child in item . GetLinkedChildren ( ) )
{
var entry = new WplPlaylistEntry ( )
{
Path = NormalizeItemPath ( playlistPath , child . Path ) ,
TrackTitle = child . Name ,
AlbumTitle = child . Album
} ;
var hasAlbumArtist = child as IHasAlbumArtist ;
if ( hasAlbumArtist ! = null )
{
entry . AlbumArtist = hasAlbumArtist . AlbumArtists . FirstOrDefault ( ) ;
}
var hasArtist = child as IHasArtist ;
if ( hasArtist ! = null )
{
entry . TrackArtist = hasArtist . Artists . FirstOrDefault ( ) ;
}
if ( child . RunTimeTicks . HasValue )
{
entry . Duration = TimeSpan . FromTicks ( child . RunTimeTicks . Value ) ;
}
playlist . PlaylistEntries . Add ( entry ) ;
}
_fileSystem . WriteAllText ( playlistPath , new WplContent ( ) . ToText ( playlist ) ) ;
}
if ( string . Equals ( ".zpl" , extension , StringComparison . OrdinalIgnoreCase ) )
{
var playlist = new ZplPlaylist ( ) ;
foreach ( var child in item . GetLinkedChildren ( ) )
{
var entry = new ZplPlaylistEntry ( )
{
Path = NormalizeItemPath ( playlistPath , child . Path ) ,
TrackTitle = child . Name ,
AlbumTitle = child . Album
} ;
var hasAlbumArtist = child as IHasAlbumArtist ;
if ( hasAlbumArtist ! = null )
{
entry . AlbumArtist = hasAlbumArtist . AlbumArtists . FirstOrDefault ( ) ;
}
var hasArtist = child as IHasArtist ;
if ( hasArtist ! = null )
{
entry . TrackArtist = hasArtist . Artists . FirstOrDefault ( ) ;
}
if ( child . RunTimeTicks . HasValue )
{
entry . Duration = TimeSpan . FromTicks ( child . RunTimeTicks . Value ) ;
}
playlist . PlaylistEntries . Add ( entry ) ;
}
_fileSystem . WriteAllText ( playlistPath , new ZplContent ( ) . ToText ( playlist ) ) ;
}
if ( string . Equals ( ".m3u" , extension , StringComparison . OrdinalIgnoreCase ) )
{
var playlist = new M3uPlaylist ( ) ;
playlist . IsExtended = true ;
foreach ( var child in item . GetLinkedChildren ( ) )
{
var entry = new M3uPlaylistEntry ( )
{
Path = NormalizeItemPath ( playlistPath , child . Path ) ,
Title = child . Name ,
Album = child . Album
} ;
var hasAlbumArtist = child as IHasAlbumArtist ;
if ( hasAlbumArtist ! = null )
{
entry . AlbumArtist = hasAlbumArtist . AlbumArtists . FirstOrDefault ( ) ;
}
if ( child . RunTimeTicks . HasValue )
{
entry . Duration = TimeSpan . FromTicks ( child . RunTimeTicks . Value ) ;
}
playlist . PlaylistEntries . Add ( entry ) ;
}
_fileSystem . WriteAllText ( playlistPath , new M3uContent ( ) . ToText ( playlist ) ) ;
}
if ( string . Equals ( ".m3u8" , extension , StringComparison . OrdinalIgnoreCase ) )
{
var playlist = new M3uPlaylist ( ) ;
playlist . IsExtended = true ;
foreach ( var child in item . GetLinkedChildren ( ) )
{
var entry = new M3uPlaylistEntry ( )
{
Path = NormalizeItemPath ( playlistPath , child . Path ) ,
Title = child . Name ,
Album = child . Album
} ;
var hasAlbumArtist = child as IHasAlbumArtist ;
if ( hasAlbumArtist ! = null )
{
entry . AlbumArtist = hasAlbumArtist . AlbumArtists . FirstOrDefault ( ) ;
}
if ( child . RunTimeTicks . HasValue )
{
entry . Duration = TimeSpan . FromTicks ( child . RunTimeTicks . Value ) ;
}
playlist . PlaylistEntries . Add ( entry ) ;
}
_fileSystem . WriteAllText ( playlistPath , new M3u8Content ( ) . ToText ( playlist ) ) ;
}
if ( string . Equals ( ".pls" , extension , StringComparison . OrdinalIgnoreCase ) )
{
var playlist = new PlsPlaylist ( ) ;
foreach ( var child in item . GetLinkedChildren ( ) )
{
var entry = new PlsPlaylistEntry ( )
{
Path = NormalizeItemPath ( playlistPath , child . Path ) ,
Title = child . Name
} ;
if ( child . RunTimeTicks . HasValue )
{
entry . Length = TimeSpan . FromTicks ( child . RunTimeTicks . Value ) ;
}
playlist . PlaylistEntries . Add ( entry ) ;
}
_fileSystem . WriteAllText ( playlistPath , new PlsContent ( ) . ToText ( playlist ) ) ;
}
}
private string NormalizeItemPath ( string playlistPath , string itemPath )
{
return MakeRelativePath ( _fileSystem . GetDirectoryName ( playlistPath ) , itemPath ) ;
}
2019-01-06 20:50:43 +00:00
private static string MakeRelativePath ( string folderPath , string fileAbsolutePath )
2018-09-12 17:26:21 +00:00
{
2019-01-06 20:50:43 +00:00
if ( string . IsNullOrEmpty ( folderPath ) )
{
2019-01-12 20:41:08 +00:00
throw new ArgumentException ( "Folder path was null or empty." , nameof ( folderPath ) ) ;
2019-01-06 20:50:43 +00:00
}
if ( string . IsNullOrEmpty ( fileAbsolutePath ) )
{
throw new ArgumentException ( "File absolute path was null or empty." , nameof ( fileAbsolutePath ) ) ;
}
2018-09-12 17:26:21 +00:00
if ( ! folderPath . EndsWith ( Path . DirectorySeparatorChar . ToString ( ) ) )
{
folderPath = folderPath + Path . DirectorySeparatorChar ;
}
2019-01-13 20:37:13 +00:00
var folderUri = new Uri ( folderPath ) ;
var fileAbsoluteUri = new Uri ( fileAbsolutePath ) ;
2018-09-12 17:26:21 +00:00
if ( folderUri . Scheme ! = fileAbsoluteUri . Scheme ) { return fileAbsolutePath ; } // path can't be made relative.
2019-01-13 20:37:13 +00:00
var relativeUri = folderUri . MakeRelativeUri ( fileAbsoluteUri ) ;
2019-01-06 20:50:43 +00:00
string relativePath = Uri . UnescapeDataString ( relativeUri . ToString ( ) ) ;
2018-09-12 17:26:21 +00:00
if ( fileAbsoluteUri . Scheme . Equals ( "file" , StringComparison . CurrentCultureIgnoreCase ) )
{
relativePath = relativePath . Replace ( Path . AltDirectorySeparatorChar , Path . DirectorySeparatorChar ) ;
}
return relativePath ;
}
private static string UnEscape ( string content )
{
if ( content = = null ) return content ;
return content . Replace ( "&" , "&" ) . Replace ( "'" , "'" ) . Replace ( """ , "\"" ) . Replace ( ">" , ">" ) . Replace ( "<" , "<" ) ;
}
private static string Escape ( string content )
{
if ( content = = null ) return null ;
return content . Replace ( "&" , "&" ) . Replace ( "'" , "'" ) . Replace ( "\"" , """ ) . Replace ( ">" , ">" ) . Replace ( "<" , "<" ) ;
2015-10-15 05:48:03 +00:00
}
2018-09-12 17:26:21 +00:00
public Folder GetPlaylistsFolder ( Guid userId )
2014-08-02 02:34:45 +00:00
{
2016-11-02 21:05:17 +00:00
var typeName = "PlaylistsFolder" ;
2017-05-26 06:48:54 +00:00
return _libraryManager . RootFolder . Children . OfType < Folder > ( ) . FirstOrDefault ( i = > string . Equals ( i . GetType ( ) . Name , typeName , StringComparison . Ordinal ) ) ? ?
2016-11-02 21:05:17 +00:00
_libraryManager . GetUserRootFolder ( ) . Children . OfType < Folder > ( ) . FirstOrDefault ( i = > string . Equals ( i . GetType ( ) . Name , typeName , StringComparison . Ordinal ) ) ;
2014-08-02 02:34:45 +00:00
}
}
}