diff options
Diffstat (limited to 'Src/playlist/PlaylistManager.cpp')
-rw-r--r-- | Src/playlist/PlaylistManager.cpp | 630 |
1 files changed, 630 insertions, 0 deletions
diff --git a/Src/playlist/PlaylistManager.cpp b/Src/playlist/PlaylistManager.cpp new file mode 100644 index 00000000..948048c8 --- /dev/null +++ b/Src/playlist/PlaylistManager.cpp @@ -0,0 +1,630 @@ +#include <strsafe.h> +#include <shlwapi.h> +#include <algorithm> + + +#include "main.h" +#include "resource.h" +#include "PlaylistManager.h" +#include "ifc_playlistloader.h" +#include "M3ULoader.h" +#include "M3UWriter.h" +#include "PLSWriter.h" +#include "M3U8Writer.h" +#include "B4SWriter.h" +#include "../nu/AutoChar.h" +#include "Playlist.h" +#include "../playlist/svc_playlisthandler.h" +#include "../playlist/Handler.h" +#include "../nu/AutoWide.h" +#include "Playlist.h" +#include "api/service/services.h" +#include "api__playlist.h" +#include "api/service/waservicefactory.h" +#include "PlaylistCounter.h" +#include "ifc_playlistloadercallback.h" +#include "ifc_playlistdirectorycallback.h" + +#include "..\WAT\WAT.h" + +class NoRecurseCallback : public ifc_playlistdirectorycallback +{ +public: + NoRecurseCallback( ifc_playlistdirectorycallback *_callback ) : callback( _callback ) {} + bool ShouldRecurse( const wchar_t *path ) { return false; } + bool ShouldLoad( const wchar_t *filename ) { return callback->ShouldLoad( filename ); } + + ifc_playlistdirectorycallback *callback; + +protected: + RECVS_DISPATCH; +}; + +#define CBCLASS NoRecurseCallback +START_DISPATCH; +CB( IFC_PLAYLISTDIRECTORYCALLBACK_SHOULDRECURSE, ShouldRecurse ) +CB( IFC_PLAYLISTDIRECTORYCALLBACK_SHOULDLOAD, ShouldLoad ) +END_DISPATCH; +#undef CBCLASS + + +static void MakeRelativePathName( const wchar_t *filename, wchar_t *outFile, size_t cch, const wchar_t *path ) +{ + wchar_t outPath[ MAX_PATH ] = { 0 }; + + int common = PathCommonPrefixW( path, filename, outPath ); + if ( common && common == wcslen( path ) ) + { + PathAddBackslashW( outPath ); + const wchar_t *p = filename + wcslen( outPath ); + lstrcpynW( outFile, p, (int)cch ); + } + else if ( !PathIsUNCW( filename ) && PathIsSameRootW( filename, path ) ) + { + if ( outFile[ 1 ] == ':' ) + lstrcpynW( outFile, filename + 2, (int)cch ); + } +} + +static void PlayList_makerelative( const wchar_t *base, wchar_t *filename, size_t cch ) +{ + MakeRelativePathName( filename, filename, cch, base ); +} + + +PlaylistManager playlistManager; + +struct LoaderPair +{ + ifc_playlistloader *loader; + svc_playlisthandler *handler; +}; + +static LoaderPair CreateLoader( const wchar_t *filename ) +{ + LoaderPair ret = { 0, 0 }; + int n = 0; + waServiceFactory *sf = 0; + + while ( sf = WASABI_API_SVC->service_enumService( WaSvc::PLAYLISTHANDLER, n++ ) ) + { + svc_playlisthandler *handler = static_cast<svc_playlisthandler *>( sf->getInterface() ); + if ( handler ) + { + if ( handler->SupportedFilename( filename ) == SVC_PLAYLISTHANDLER_SUCCESS ) + { + ret.loader = handler->CreateLoader( filename ); + ret.handler = handler; + break; + } + else + { + sf->releaseInterface( handler ); + } + } + } + + // TODO: sniff file if no one claims it + return ret; +} + +void DestroyLoader( LoaderPair &loader ) +{ + loader.handler->ReleaseLoader( loader.loader ); +} + +// a simple loader... +int PlaylistManager::Load( const wchar_t *filename, ifc_playlistloadercallback *playlist ) +{ + LoaderPair loaderPair = CreateLoader( filename ); + ifc_playlistloader *loader = loaderPair.loader; + + if ( !loader ) + return PLAYLISTMANAGER_LOAD_NO_LOADER; // failed to find a loader + + // TODO: make our own ifc_playlistloadercallback, so we can handle nested playlists + int res = loader->Load( filename, playlist ); + DestroyLoader( loaderPair ); + + if ( res != IFC_PLAYLISTLOADER_SUCCESS ) // TODO: switch on the error code and return a more specific error + return PLAYLISTMANAGER_LOAD_LOADER_OPEN_FAILED; + + return PLAYLISTMANAGER_SUCCESS; +} + +int PlaylistManager::LoadAs( const wchar_t *filename, const wchar_t *ext, ifc_playlistloadercallback *playlist ) +{ + LoaderPair loaderPair = CreateLoader( ext ); + ifc_playlistloader *loader = loaderPair.loader; + + if ( !loader ) + return PLAYLISTMANAGER_LOAD_NO_LOADER; // failed to find a loader + + // TODO: make our own ifc_playlistloadercallback, so we can handle nested playlists + int res = loader->Load( filename, playlist ); + DestroyLoader( loaderPair ); + + if ( res != IFC_PLAYLISTLOADER_SUCCESS ) // TODO: switch on the error code and return a more specific error + return PLAYLISTMANAGER_LOAD_LOADER_OPEN_FAILED; + + return PLAYLISTMANAGER_SUCCESS; +} + +int PlaylistManager::LoadFromDialog( const wchar_t *fns, ifc_playlistloadercallback *playlist ) +{ + wchar_t buf[ MAX_PATH ] = { 0 }; + const wchar_t *path = fns; + fns += wcslen( fns ) + 1; + + while ( fns && *fns ) + { + if ( *path ) + PathCombineW( buf, path, fns ); + else + StringCchCopyW( buf, MAX_PATH, fns ); + + if ( Load( buf, playlist ) != PLAYLISTMANAGER_SUCCESS ) + { + if ( playlist->OnFile( buf, 0, -1, 0 ) != ifc_playlistloadercallback::LOAD_CONTINUE ) + return PLAYLIST_SUCCESS; + } + + fns += wcslen( fns ) + 1; + } + return PLAYLIST_SUCCESS; +} + +int PlaylistManager::LoadFromANSIDialog( const char *fns, ifc_playlistloadercallback *playlist ) +{ + char buf[ MAX_PATH ] = { 0 }; + const char *path = fns; + fns += lstrlenA( fns ) + 1; + + while ( fns && *fns ) + { + if ( *path ) + PathCombineA( buf, path, fns ); + else + lstrcpynA( buf, fns, MAX_PATH ); + + AutoWide wideFn( buf ); + if ( Load( wideFn, playlist ) != PLAYLISTMANAGER_SUCCESS ) + { + if ( playlist->OnFile( wideFn, 0, -1, 0 ) != ifc_playlistloadercallback::LOAD_CONTINUE ) + return PLAYLIST_SUCCESS; + } + + fns += lstrlenA( fns ) + 1; + } + return PLAYLIST_SUCCESS; +} + + +int PlaylistManager::Save( const wchar_t *filename, ifc_playlist *playlist ) +{ + const wchar_t *ext = PathFindExtensionW( filename ); + PlaylistWriter *writer = 0; + + if ( !lstrcmpiW( ext, L".M3U" ) ) + writer = new M3UWriter; + else if ( !lstrcmpiW( ext, L".M3U8" ) ) + writer = new M3U8Writer; + else if ( !lstrcmpiW( ext, L".PLS" ) ) + writer = new PLSWriter; + else if ( !lstrcmpiW( ext, L".B4S" ) ) + writer = new B4SWriter; + else + return PLAYLISTMANAGER_FAILED; + + wchar_t base[ MAX_PATH ] = { 0 }; + StringCchCopyW( base, MAX_PATH, filename ); + PathRemoveFileSpecW( base ); + PathRemoveBackslashW( base ); + + if ( !writer->Open( filename ) ) + { + delete writer; + + return PLAYLISTMANAGER_FAILED; + } + + size_t numItems = playlist->GetNumItems(); + wchar_t itemname[ FILENAME_SIZE ] = { 0 }; + wchar_t title[ FILETITLE_SIZE ] = { 0 }; + wchar_t cloud_info[ 512 ] = { 0 }; + int length = 0; + + wchar_t l_tvg_id[ 10 ] = { 0 }; + wchar_t l_tvg_name[ FILETITLE_SIZE ] = { 0 }; + wchar_t l_tvg_logo[ 512 ] = { 0 }; + wchar_t l_group_title[ 64 ] = { 0 }; + + wchar_t l_ext[ 10 ] = { 0 }; + + wa::strings::wa_string l_extented_infos_line( "" ); + + for ( size_t i = 0; i != numItems; i++ ) + { + if ( playlist->GetItem( i, itemname, FILENAME_SIZE ) ) + { + //PlayList_makerelative( base, itemname, FILENAME_SIZE ); + + // this is used to preserve 'cloud' specific data in playlists + // and should only get a response from a cloud-based ml_playlist + if ( playlist->GetItemExtendedInfo( i, L"cloud", cloud_info, 512 ) ) + { + writer->Write( cloud_info ); + } + + + l_extented_infos_line.clear(); + + if ( playlist->GetItemExtendedInfo( i, L"tvg-name", l_tvg_name, FILETITLE_SIZE ) ) + { + playlist->GetItemExtendedInfo( i, L"tvg-id", l_tvg_id, 10 ); + playlist->GetItemExtendedInfo( i, L"tvg-logo", l_tvg_logo, 512 ); + playlist->GetItemExtendedInfo( i, L"group-title", l_group_title, 64 ); + + l_extented_infos_line = L"tvg-id"; + l_extented_infos_line.append( L"=\"" ); + l_extented_infos_line.append( l_tvg_id ); + l_extented_infos_line.append( L"\" " ); + + l_extented_infos_line.append( L"tvg-name" ); + l_extented_infos_line.append( L"=\"" ); + l_extented_infos_line.append( l_tvg_name ); + l_extented_infos_line.append( L"\" " ); + + l_extented_infos_line.append( L"tvg-logo" ); + l_extented_infos_line.append( L"=\"" ); + l_extented_infos_line.append( l_tvg_logo ); + l_extented_infos_line.append( L"\" " ); + + l_extented_infos_line.append( L"group-title" ); + l_extented_infos_line.append( L"=\"" ); + l_extented_infos_line.append( l_group_title ); + l_extented_infos_line.append( L"\" " ); + } + + wa::strings::wa_string l_item_name( itemname ); + + if ( l_item_name.contains( "://" ) && playlist->GetItemExtendedInfo(i, L"ext", l_ext, 10) ) + { + l_extented_infos_line = L"ext"; + l_extented_infos_line.append( L"=\"" ); + l_extented_infos_line.append( l_ext ); + l_extented_infos_line.append( L"\"" ); + } + + if ( playlist->GetItemTitle( i, title, FILETITLE_SIZE ) ) + { + length = playlist->GetItemLengthMilliseconds( i ); + + if ( l_extented_infos_line.empty() ) + writer->Write( itemname, title, length / 1000 ); + else + writer->Write( itemname, title, l_extented_infos_line.GetW().c_str(), length / 1000); + } + else + writer->Write( itemname ); + } + } + + writer->Close(); + delete writer; + + return PLAYLISTMANAGER_SUCCESS; +} + + +size_t PlaylistManager::Copy( const wchar_t *destFn, const wchar_t *srcFn ) +{ + Playlist copy; + + Load( srcFn, © ); + Save( destFn, © ); + + return copy.GetNumItems(); +} + + +size_t PlaylistManager::CountItems( const wchar_t *filename ) +{ + LoaderPair loaderPair = CreateLoader( filename ); + ifc_playlistloader *loader = loaderPair.loader; + + if ( !loader ) + return 0; + + PlaylistCounter counter; + loader->Load( filename, &counter ); + + DestroyLoader( loaderPair ); + return counter.count; +} + + +int PlaylistManager::GetLengthMilliseconds( const wchar_t *filename ) +{ + LoaderPair loaderPair = CreateLoader( filename ); + ifc_playlistloader *loader = loaderPair.loader; + + if ( !loader ) + return 0; + + PlaylistCounter counter; + loader->Load( filename, &counter ); + DestroyLoader( loaderPair ); + return (int)counter.length; +} + +uint64_t PlaylistManager::GetLongLengthMilliseconds( const wchar_t *filename ) +{ + LoaderPair loaderPair = CreateLoader( filename ); + ifc_playlistloader *loader = loaderPair.loader; + + if ( !loader ) + return 0; + + PlaylistCounter counter; + loader->Load( filename, &counter ); + DestroyLoader( loaderPair ); + return counter.length; +} + + +void PlaylistManager::Randomize( ifc_playlist *playlist ) +{ + if ( playlist->Randomize( warand ) == PLAYLIST_UNIMPLEMENTED ) + { + // TODO: do it the hard way + } +} + +void PlaylistManager::Reverse( ifc_playlist *playlist ) +{ + if ( playlist->Reverse() == PLAYLIST_UNIMPLEMENTED ) + { + // TODO: do it the hard way + } +} + + +void PlaylistManager::LoadDirectory( const wchar_t *directory, ifc_playlistloadercallback *callback, ifc_playlistdirectorycallback *dirCallback ) +{ + WIN32_FIND_DATAW found = { 0 }; + wchar_t filespec[ MAX_PATH ] = { 0 }; + PathCombineW( filespec, directory, L"*.*" ); + + HANDLE i = FindFirstFileW( filespec, &found ); + if ( i != INVALID_HANDLE_VALUE ) + { + do + { + // if it's another folder, then we might want to recurse into it + if ( ( found.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) // if it's a directory + && wcscmp( found.cFileName, L"." ) && wcscmp( found.cFileName, L".." ) // but not . or .. + && ( !dirCallback || dirCallback->ShouldRecurse( found.cFileName ) ) ) // and we're allowed to recurse + { + if ( PathCombineW( filespec, directory, found.cFileName ) ) + { + LoadDirectory( filespec, callback, dirCallback ); + } + } + + if ( !( found.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) ) + { + const wchar_t *ext = PathFindExtensionW( found.cFileName ); + if ( ext[ 0 ] ) + { + if ( !_wcsicmp( ext, L".lnk" ) ) + { + wchar_t thisf[ MAX_PATH ] = { 0 }; + wchar_t temp2[ MAX_PATH ] = { 0 }; + PathCombineW( temp2, directory, found.cFileName ); + if ( ResolveShortCut( NULL, temp2, thisf ) && GetLongPathNameW( thisf, temp2, MAX_PATH ) && lstrcmpiW( temp2, directory ) ) + { + WIN32_FIND_DATAW d2 = { 0 }; + if ( IsUrl( temp2 ) && ( !dirCallback || dirCallback->ShouldLoad( temp2 ) ) ) + { + if ( callback->OnFile( temp2, 0, -1, 0 ) != ifc_playlistloadercallback::LOAD_CONTINUE ) + break; + } + else + { + HANDLE h2 = FindFirstFileW( temp2, &d2 ); + if ( h2 != INVALID_HANDLE_VALUE ) + { + if ( !( d2.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) ) + { + if ( !dirCallback || dirCallback->ShouldLoad( temp2 ) ) + { + if ( callback->OnFile( temp2, 0, -1, 0 ) != ifc_playlistloadercallback::LOAD_CONTINUE ) + { + FindClose( h2 ); + break; + } + } + } + else + { + // recursively load a shortcut w/o fear of infinite recursion + NoRecurseCallback noRecurse( dirCallback ); + LoadDirectory( temp2, callback, &noRecurse ); + } + FindClose( h2 ); + } + } + } + } + else // !shortcut + { + + if ( PathCombineW( filespec, directory, found.cFileName ) && + ( !dirCallback || dirCallback->ShouldLoad( filespec ) ) ) + { + if ( callback->OnFile( filespec, 0, -1, 0 ) != ifc_playlistloadercallback::LOAD_CONTINUE ) + break; + } + } + } + } + } while ( FindNextFileW( i, &found ) ); + FindClose( i ); + } +} + +bool PlaylistManager::CanLoad( const wchar_t *filename ) +{ + int n = 0; + waServiceFactory *sf = 0; + while ( sf = WASABI_API_SVC->service_enumService( WaSvc::PLAYLISTHANDLER, n++ ) ) + { + svc_playlisthandler *handler = static_cast<svc_playlisthandler *>( sf->getInterface() ); + if ( handler ) + { + if ( handler->SupportedFilename( filename ) == SVC_PLAYLISTHANDLER_SUCCESS ) + { + sf->releaseInterface( handler ); + return true; + } + else + { + sf->releaseInterface( handler ); + } + } + } + return false; +} + +void PlaylistManager::GetExtensionList( wchar_t *extensionList, size_t extensionListCch ) +{ + extensionList[ 0 ] = 0; + + bool first = true; + + int n = 0, extListCch = (int)extensionListCch; + wchar_t *extList = extensionList; + waServiceFactory *sf = 0; + while ( sf = WASABI_API_SVC->service_enumService( WaSvc::PLAYLISTHANDLER, n++ ) ) + { + svc_playlisthandler *handler = static_cast<svc_playlisthandler *>( sf->getInterface() ); + if ( handler ) + { + const wchar_t *ext = 0; + int k = 0; + while ( ext = handler->EnumerateExtensions( k++ ) ) + { + if ( first ) + StringCchCatExW( extensionList, extensionListCch, L"*.", &extensionList, &extensionListCch, 0 ); + else + StringCchCatExW( extensionList, extensionListCch, L";*.", &extensionList, &extensionListCch, 0 ); + + first = false; + + StringCchCatExW( extensionList, extensionListCch, ext, &extensionList, &extensionListCch, 0 ); + } + sf->releaseInterface( handler ); + } + } + CharUpperBuffW( extList, extListCch ); +} + +void PlaylistManager::GetFilterList( wchar_t *extensionList, size_t extensionListCch ) +{ + extensionListCch--; // this needs to be DOUBLE null terminated, so we'll make sure there's room + + StringCchCopyExW( extensionList, extensionListCch, WASABI_API_LNGSTRINGW( IDS_ALL_PLAYLIST_TYPES ), &extensionList, &extensionListCch, 0 ); + extensionListCch--; + extensionList++; + + GetExtensionList( extensionList, extensionListCch ); + + extensionListCch -= ( wcslen( extensionList ) + 1 ); + extensionList += wcslen( extensionList ) + 1; + + int n = 0; + waServiceFactory *sf = 0; + while ( sf = WASABI_API_SVC->service_enumService( WaSvc::PLAYLISTHANDLER, n++ ) ) + { + svc_playlisthandler *handler = static_cast<svc_playlisthandler *>( sf->getInterface() ); + if ( handler ) + { + const wchar_t *name = handler->GetName(); + if ( !name ) + name = WASABI_API_LNGSTRINGW( IDS_PLAYLIST ); + + StringCchCopyExW( extensionList, extensionListCch, name, &extensionList, &extensionListCch, 0 ); + extensionList++; + extensionListCch--; + + bool first = true; + const wchar_t *ext = 0; + int k = 0; + while ( ext = handler->EnumerateExtensions( k++ ) ) + { + if ( first ) + StringCchCopyExW( extensionList, extensionListCch, L"*.", &extensionList, &extensionListCch, 0 ); + else + StringCchCatExW( extensionList, extensionListCch, L";*.", &extensionList, &extensionListCch, 0 ); + + first = false; + + StringCchCatExW( extensionList, extensionListCch, ext, &extensionList, &extensionListCch, 0 ); + } + extensionList++; + extensionListCch--; + + sf->releaseInterface( handler ); + } + } + + extensionList[ 0 ] = 0; // ok because we reserved the room for it above +} + +const wchar_t *PlaylistManager::EnumExtensions( size_t num ) +{ + int n = 0; + int total = 0; + waServiceFactory *sf = 0; + while ( sf = WASABI_API_SVC->service_enumService( WaSvc::PLAYLISTHANDLER, n++ ) ) + { + svc_playlisthandler *handler = static_cast<svc_playlisthandler *>( sf->getInterface() ); + if ( handler ) + { + const wchar_t *ext = 0; + int k = 0; + while ( ext = handler->EnumerateExtensions( k++ ) ) + { + if ( total++ == num ) + return ext; + } + + sf->releaseInterface( handler ); + } + } + + return 0; +} + + +#define CBCLASS PlaylistManager +START_DISPATCH; +CB( API_PLAYLISTMANAGER_LOAD, Load ) +CB( API_PLAYLISTMANAGER_LOADAS, LoadAs ) +CB( API_PLAYLISTMANAGER_LOADNULLDELIMITED, LoadFromDialog ) +CB( API_PLAYLISTMANAGER_LOADNULLDELIMITED_ANSI, LoadFromANSIDialog ) +CB( API_PLAYLISTMANAGER_SAVE, Save ) +CB( API_PLAYLISTMANAGER_COPY, Copy ) +CB( API_PLAYLISTMANAGER_COUNT, CountItems ) +CB( API_PLAYLISTMANAGER_GETLENGTH, GetLengthMilliseconds ) +CB( API_PLAYLISTMANAGER_GETLONGLENGTH, GetLongLengthMilliseconds ) +VCB( API_PLAYLISTMANAGER_RANDOMIZE, Randomize ) +VCB( API_PLAYLISTMANAGER_REVERSE, Reverse ) +VCB( API_PLAYLISTMANAGER_LOADDIRECTORY, LoadDirectory ) +CB( API_PLAYLISTMANAGER_CANLOAD, CanLoad ) +VCB( API_PLAYLISTMANAGER_GETEXTENSIONLIST, GetExtensionList ) +VCB( API_PLAYLISTMANAGER_GETFILTERLIST, GetFilterList ) +CB( API_PLAYLISTMANAGER_ENUMEXTENSION, EnumExtensions ) +END_DISPATCH; +#undef CBCLASS
\ No newline at end of file |