summaryrefslogtreecommitdiffstats
path: root/xbmc/filesystem
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 18:07:22 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 18:07:22 +0000
commitc04dcc2e7d834218ef2d4194331e383402495ae1 (patch)
tree7333e38d10d75386e60f336b80c2443c1166031d /xbmc/filesystem
parentInitial commit. (diff)
downloadkodi-c04dcc2e7d834218ef2d4194331e383402495ae1.tar.xz
kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.zip
Adding upstream version 2:20.4+dfsg.upstream/2%20.4+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--xbmc/filesystem/AddonsDirectory.cpp953
-rw-r--r--xbmc/filesystem/AddonsDirectory.h72
-rw-r--r--xbmc/filesystem/AudioBookFileDirectory.cpp168
-rw-r--r--xbmc/filesystem/AudioBookFileDirectory.h30
-rw-r--r--xbmc/filesystem/BlurayCallback.cpp159
-rw-r--r--xbmc/filesystem/BlurayCallback.h31
-rw-r--r--xbmc/filesystem/BlurayDirectory.cpp344
-rw-r--r--xbmc/filesystem/BlurayDirectory.h56
-rw-r--r--xbmc/filesystem/BlurayFile.cpp35
-rw-r--r--xbmc/filesystem/BlurayFile.h25
-rw-r--r--xbmc/filesystem/CDDADirectory.cpp71
-rw-r--r--xbmc/filesystem/CDDADirectory.h24
-rw-r--r--xbmc/filesystem/CDDAFile.cpp227
-rw-r--r--xbmc/filesystem/CDDAFile.h44
-rw-r--r--xbmc/filesystem/CMakeLists.txt177
-rw-r--r--xbmc/filesystem/CacheStrategy.cpp447
-rw-r--r--xbmc/filesystem/CacheStrategy.h133
-rw-r--r--xbmc/filesystem/CircularCache.cpp281
-rw-r--r--xbmc/filesystem/CircularCache.h55
-rw-r--r--xbmc/filesystem/CurlFile.cpp2141
-rw-r--r--xbmc/filesystem/CurlFile.h202
-rw-r--r--xbmc/filesystem/DAVCommon.cpp73
-rw-r--r--xbmc/filesystem/DAVCommon.h21
-rw-r--r--xbmc/filesystem/DAVDirectory.cpp230
-rw-r--r--xbmc/filesystem/DAVDirectory.h33
-rw-r--r--xbmc/filesystem/DAVFile.cpp145
-rw-r--r--xbmc/filesystem/DAVFile.h31
-rw-r--r--xbmc/filesystem/Directorization.h139
-rw-r--r--xbmc/filesystem/Directory.cpp446
-rw-r--r--xbmc/filesystem/Directory.h82
-rw-r--r--xbmc/filesystem/DirectoryCache.cpp266
-rw-r--r--xbmc/filesystem/DirectoryCache.h73
-rw-r--r--xbmc/filesystem/DirectoryFactory.cpp197
-rw-r--r--xbmc/filesystem/DirectoryFactory.h39
-rw-r--r--xbmc/filesystem/DirectoryHistory.cpp156
-rw-r--r--xbmc/filesystem/DirectoryHistory.h67
-rw-r--r--xbmc/filesystem/DllLibCurl.cpp306
-rw-r--r--xbmc/filesystem/DllLibCurl.h106
-rw-r--r--xbmc/filesystem/EventsDirectory.cpp69
-rw-r--r--xbmc/filesystem/EventsDirectory.h40
-rw-r--r--xbmc/filesystem/FTPDirectory.cpp113
-rw-r--r--xbmc/filesystem/FTPDirectory.h25
-rw-r--r--xbmc/filesystem/FTPParse.cpp504
-rw-r--r--xbmc/filesystem/FTPParse.h33
-rw-r--r--xbmc/filesystem/FavouritesDirectory.cpp43
-rw-r--r--xbmc/filesystem/FavouritesDirectory.h28
-rw-r--r--xbmc/filesystem/File.cpp1224
-rw-r--r--xbmc/filesystem/File.h206
-rw-r--r--xbmc/filesystem/FileCache.cpp630
-rw-r--r--xbmc/filesystem/FileCache.h79
-rw-r--r--xbmc/filesystem/FileDirectoryFactory.cpp250
-rw-r--r--xbmc/filesystem/FileDirectoryFactory.h26
-rw-r--r--xbmc/filesystem/FileFactory.cpp180
-rw-r--r--xbmc/filesystem/FileFactory.h29
-rw-r--r--xbmc/filesystem/HTTPDirectory.cpp318
-rw-r--r--xbmc/filesystem/HTTPDirectory.h26
-rw-r--r--xbmc/filesystem/IDirectory.cpp177
-rw-r--r--xbmc/filesystem/IDirectory.h175
-rw-r--r--xbmc/filesystem/IFile.cpp91
-rw-r--r--xbmc/filesystem/IFile.h146
-rw-r--r--xbmc/filesystem/IFileDirectory.h22
-rw-r--r--xbmc/filesystem/IFileTypes.h108
-rw-r--r--xbmc/filesystem/ISO9660Directory.cpp81
-rw-r--r--xbmc/filesystem/ISO9660Directory.h25
-rw-r--r--xbmc/filesystem/ISO9660File.cpp127
-rw-r--r--xbmc/filesystem/ISO9660File.h49
-rw-r--r--xbmc/filesystem/ImageFile.cpp106
-rw-r--r--xbmc/filesystem/ImageFile.h34
-rw-r--r--xbmc/filesystem/LibraryDirectory.cpp182
-rw-r--r--xbmc/filesystem/LibraryDirectory.h40
-rw-r--r--xbmc/filesystem/MultiPathDirectory.cpp309
-rw-r--r--xbmc/filesystem/MultiPathDirectory.h42
-rw-r--r--xbmc/filesystem/MultiPathFile.cpp84
-rw-r--r--xbmc/filesystem/MultiPathFile.h27
-rw-r--r--xbmc/filesystem/MusicDatabaseDirectory.cpp344
-rw-r--r--xbmc/filesystem/MusicDatabaseDirectory.h37
-rw-r--r--xbmc/filesystem/MusicDatabaseDirectory/CMakeLists.txt39
-rw-r--r--xbmc/filesystem/MusicDatabaseDirectory/DirectoryNode.cpp284
-rw-r--r--xbmc/filesystem/MusicDatabaseDirectory/DirectoryNode.h92
-rw-r--r--xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbum.cpp52
-rw-r--r--xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbum.h29
-rw-r--r--xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyAdded.cpp65
-rw-r--r--xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyAdded.h29
-rw-r--r--xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyAddedSong.cpp33
-rw-r--r--xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyAddedSong.h27
-rw-r--r--xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyPlayed.cpp65
-rw-r--r--xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyPlayed.h29
-rw-r--r--xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyPlayedSong.cpp33
-rw-r--r--xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyPlayedSong.h27
-rw-r--r--xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumTop100.cpp63
-rw-r--r--xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumTop100.h29
-rw-r--r--xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumTop100Song.cpp33
-rw-r--r--xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumTop100Song.h27
-rw-r--r--xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeArtist.cpp55
-rw-r--r--xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeArtist.h29
-rw-r--r--xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeDiscs.cpp57
-rw-r--r--xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeDiscs.h28
-rw-r--r--xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeGrouped.cpp61
-rw-r--r--xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeGrouped.h32
-rw-r--r--xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeOverview.cpp87
-rw-r--r--xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeOverview.h29
-rw-r--r--xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeRoot.cpp22
-rw-r--r--xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeRoot.h27
-rw-r--r--xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeSingles.cpp32
-rw-r--r--xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeSingles.h27
-rw-r--r--xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeSong.cpp37
-rw-r--r--xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeSong.h27
-rw-r--r--xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeSongTop100.cpp33
-rw-r--r--xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeSongTop100.h27
-rw-r--r--xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeTop100.cpp57
-rw-r--r--xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeTop100.h29
-rw-r--r--xbmc/filesystem/MusicDatabaseDirectory/QueryParams.cpp58
-rw-r--r--xbmc/filesystem/MusicDatabaseDirectory/QueryParams.h43
-rw-r--r--xbmc/filesystem/MusicDatabaseFile.cpp90
-rw-r--r--xbmc/filesystem/MusicDatabaseFile.h35
-rw-r--r--xbmc/filesystem/MusicFileDirectory.cpp79
-rw-r--r--xbmc/filesystem/MusicFileDirectory.h33
-rw-r--r--xbmc/filesystem/MusicSearchDirectory.cpp52
-rw-r--r--xbmc/filesystem/MusicSearchDirectory.h24
-rw-r--r--xbmc/filesystem/NFSDirectory.cpp382
-rw-r--r--xbmc/filesystem/NFSDirectory.h34
-rw-r--r--xbmc/filesystem/NFSFile.cpp962
-rw-r--r--xbmc/filesystem/NFSFile.h148
-rw-r--r--xbmc/filesystem/NptXbmcFile.cpp534
-rw-r--r--xbmc/filesystem/OverrideDirectory.cpp41
-rw-r--r--xbmc/filesystem/OverrideDirectory.h28
-rw-r--r--xbmc/filesystem/OverrideFile.cpp116
-rw-r--r--xbmc/filesystem/OverrideFile.h43
-rw-r--r--xbmc/filesystem/PVRDirectory.cpp56
-rw-r--r--xbmc/filesystem/PVRDirectory.h35
-rw-r--r--xbmc/filesystem/PipeFile.cpp217
-rw-r--r--xbmc/filesystem/PipeFile.h77
-rw-r--r--xbmc/filesystem/PipesManager.cpp314
-rw-r--r--xbmc/filesystem/PipesManager.h120
-rw-r--r--xbmc/filesystem/PlaylistDirectory.cpp47
-rw-r--r--xbmc/filesystem/PlaylistDirectory.h23
-rw-r--r--xbmc/filesystem/PlaylistFileDirectory.cpp67
-rw-r--r--xbmc/filesystem/PlaylistFileDirectory.h25
-rw-r--r--xbmc/filesystem/PluginDirectory.cpp548
-rw-r--r--xbmc/filesystem/PluginDirectory.h89
-rw-r--r--xbmc/filesystem/PluginFile.cpp59
-rw-r--r--xbmc/filesystem/PluginFile.h31
-rw-r--r--xbmc/filesystem/RSSDirectory.cpp623
-rw-r--r--xbmc/filesystem/RSSDirectory.h39
-rw-r--r--xbmc/filesystem/ResourceDirectory.cpp53
-rw-r--r--xbmc/filesystem/ResourceDirectory.h26
-rw-r--r--xbmc/filesystem/ResourceFile.cpp71
-rw-r--r--xbmc/filesystem/ResourceFile.h27
-rw-r--r--xbmc/filesystem/ShoutcastFile.cpp362
-rw-r--r--xbmc/filesystem/ShoutcastFile.h76
-rw-r--r--xbmc/filesystem/SmartPlaylistDirectory.cpp359
-rw-r--r--xbmc/filesystem/SmartPlaylistDirectory.h33
-rw-r--r--xbmc/filesystem/SourcesDirectory.cpp106
-rw-r--r--xbmc/filesystem/SourcesDirectory.h30
-rw-r--r--xbmc/filesystem/SpecialProtocol.cpp304
-rw-r--r--xbmc/filesystem/SpecialProtocol.h85
-rw-r--r--xbmc/filesystem/SpecialProtocolDirectory.cpp44
-rw-r--r--xbmc/filesystem/SpecialProtocolDirectory.h25
-rw-r--r--xbmc/filesystem/SpecialProtocolFile.cpp25
-rw-r--r--xbmc/filesystem/SpecialProtocolFile.h24
-rw-r--r--xbmc/filesystem/StackDirectory.cpp232
-rw-r--r--xbmc/filesystem/StackDirectory.h33
-rw-r--r--xbmc/filesystem/UDFBlockInput.cpp73
-rw-r--r--xbmc/filesystem/UDFBlockInput.h43
-rw-r--r--xbmc/filesystem/UDFDirectory.cpp108
-rw-r--r--xbmc/filesystem/UDFDirectory.h29
-rw-r--r--xbmc/filesystem/UDFFile.cpp105
-rw-r--r--xbmc/filesystem/UDFFile.h48
-rw-r--r--xbmc/filesystem/UPnPDirectory.cpp358
-rw-r--r--xbmc/filesystem/UPnPDirectory.h37
-rw-r--r--xbmc/filesystem/UPnPFile.cpp71
-rw-r--r--xbmc/filesystem/UPnPFile.h30
-rw-r--r--xbmc/filesystem/VideoDatabaseDirectory.cpp377
-rw-r--r--xbmc/filesystem/VideoDatabaseDirectory.h36
-rw-r--r--xbmc/filesystem/VideoDatabaseDirectory/CMakeLists.txt37
-rw-r--r--xbmc/filesystem/VideoDatabaseDirectory/DirectoryNode.cpp271
-rw-r--r--xbmc/filesystem/VideoDatabaseDirectory/DirectoryNode.h99
-rw-r--r--xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeEpisodes.cpp52
-rw-r--r--xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeEpisodes.h26
-rw-r--r--xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeGrouped.cpp127
-rw-r--r--xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeGrouped.h33
-rw-r--r--xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeInProgressTvShows.cpp49
-rw-r--r--xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeInProgressTvShows.h29
-rw-r--r--xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeMoviesOverview.cpp80
-rw-r--r--xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeMoviesOverview.h29
-rw-r--r--xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeMusicVideosOverview.cpp75
-rw-r--r--xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeMusicVideosOverview.h29
-rw-r--r--xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeOverview.cpp106
-rw-r--r--xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeOverview.h29
-rw-r--r--xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeRecentlyAddedEpisodes.cpp36
-rw-r--r--xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeRecentlyAddedEpisodes.h27
-rw-r--r--xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeRecentlyAddedMovies.cpp36
-rw-r--r--xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeRecentlyAddedMovies.h27
-rw-r--r--xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeRecentlyAddedMusicVideos.cpp37
-rw-r--r--xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeRecentlyAddedMusicVideos.h27
-rw-r--r--xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeRoot.cpp22
-rw-r--r--xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeRoot.h27
-rw-r--r--xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeSeasons.cpp81
-rw-r--r--xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeSeasons.h36
-rw-r--r--xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTitleMovies.cpp44
-rw-r--r--xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTitleMovies.h25
-rw-r--r--xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTitleMusicVideos.cpp43
-rw-r--r--xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTitleMusicVideos.h25
-rw-r--r--xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTitleTvShows.cpp56
-rw-r--r--xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTitleTvShows.h29
-rw-r--r--xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTvShowsOverview.cpp74
-rw-r--r--xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTvShowsOverview.h29
-rw-r--r--xbmc/filesystem/VideoDatabaseDirectory/QueryParams.cpp97
-rw-r--r--xbmc/filesystem/VideoDatabaseDirectory/QueryParams.h59
-rw-r--r--xbmc/filesystem/VideoDatabaseFile.cpp99
-rw-r--r--xbmc/filesystem/VideoDatabaseFile.h31
-rw-r--r--xbmc/filesystem/VirtualDirectory.cpp204
-rw-r--r--xbmc/filesystem/VirtualDirectory.h65
-rw-r--r--xbmc/filesystem/XbtDirectory.cpp67
-rw-r--r--xbmc/filesystem/XbtDirectory.h33
-rw-r--r--xbmc/filesystem/XbtFile.cpp368
-rw-r--r--xbmc/filesystem/XbtFile.h67
-rw-r--r--xbmc/filesystem/XbtManager.cpp127
-rw-r--r--xbmc/filesystem/XbtManager.h58
-rw-r--r--xbmc/filesystem/ZeroconfDirectory.cpp232
-rw-r--r--xbmc/filesystem/ZeroconfDirectory.h28
-rw-r--r--xbmc/filesystem/ZipDirectory.cpp79
-rw-r--r--xbmc/filesystem/ZipDirectory.h24
-rw-r--r--xbmc/filesystem/ZipFile.cpp531
-rw-r--r--xbmc/filesystem/ZipFile.h61
-rw-r--r--xbmc/filesystem/ZipManager.cpp320
-rw-r--r--xbmc/filesystem/ZipManager.h82
-rw-r--r--xbmc/filesystem/test/CMakeLists.txt15
-rw-r--r--xbmc/filesystem/test/TestDirectory.cpp56
-rw-r--r--xbmc/filesystem/test/TestFile.cpp212
-rw-r--r--xbmc/filesystem/test/TestFileFactory.cpp161
-rw-r--r--xbmc/filesystem/test/TestHTTPDirectory.cpp301
-rw-r--r--xbmc/filesystem/test/TestNfsFile.cpp84
-rw-r--r--xbmc/filesystem/test/TestZipFile.cpp211
-rw-r--r--xbmc/filesystem/test/TestZipManager.cpp30
-rw-r--r--xbmc/filesystem/test/data/httpdirectory/apache-default.html15
-rw-r--r--xbmc/filesystem/test/data/httpdirectory/apache-fancy.html15
-rw-r--r--xbmc/filesystem/test/data/httpdirectory/apache-html.html19
-rw-r--r--xbmc/filesystem/test/data/httpdirectory/basic-multiline.html16
-rw-r--r--xbmc/filesystem/test/data/httpdirectory/basic.html13
-rw-r--r--xbmc/filesystem/test/data/httpdirectory/lighttp-default.html211
-rw-r--r--xbmc/filesystem/test/data/httpdirectory/nginx-default.html11
-rw-r--r--xbmc/filesystem/test/data/httpdirectory/nginx-fancyindex.html34
-rw-r--r--xbmc/filesystem/test/extendedlocalheader.zipbin0 -> 15352 bytes
-rw-r--r--xbmc/filesystem/test/refRARnormal.rarbin0 -> 3267 bytes
-rw-r--r--xbmc/filesystem/test/refRARstored.rarbin0 -> 5430 bytes
-rw-r--r--xbmc/filesystem/test/reffile.txt25
-rw-r--r--xbmc/filesystem/test/reffile.txt.rarbin0 -> 967 bytes
-rw-r--r--xbmc/filesystem/test/reffile.txt.zipbin0 -> 1023 bytes
249 files changed, 29881 insertions, 0 deletions
diff --git a/xbmc/filesystem/AddonsDirectory.cpp b/xbmc/filesystem/AddonsDirectory.cpp
new file mode 100644
index 0000000..1439ae0
--- /dev/null
+++ b/xbmc/filesystem/AddonsDirectory.cpp
@@ -0,0 +1,953 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "AddonsDirectory.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "addons/AddonDatabase.h"
+#include "addons/AddonInstaller.h"
+#include "addons/AddonManager.h"
+#include "addons/AddonRepos.h"
+#include "addons/AddonSystemSettings.h"
+#include "addons/PluginSource.h"
+#include "addons/RepositoryUpdater.h"
+#include "addons/addoninfo/AddonInfo.h"
+#include "addons/addoninfo/AddonType.h"
+#include "games/GameUtils.h"
+#include "games/addons/GameClient.h"
+#include "guilib/LocalizeStrings.h"
+#include "guilib/TextureManager.h"
+#include "interfaces/generic/ScriptInvocationManager.h"
+#include "messaging/helpers/DialogOKHelper.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+
+#include <algorithm>
+#include <array>
+#include <functional>
+#include <set>
+
+using namespace KODI;
+using namespace ADDON;
+using namespace KODI::MESSAGING;
+
+namespace XFILE
+{
+
+CAddonsDirectory::CAddonsDirectory(void) = default;
+
+CAddonsDirectory::~CAddonsDirectory(void) = default;
+
+const auto CATEGORY_INFO_PROVIDERS = "category.infoproviders";
+const auto CATEGORY_LOOK_AND_FEEL = "category.lookandfeel";
+const auto CATEGORY_GAME_ADDONS = "category.gameaddons";
+const auto CATEGORY_EMULATORS = "category.emulators";
+const auto CATEGORY_STANDALONE_GAMES = "category.standalonegames";
+const auto CATEGORY_GAME_PROVIDERS = "category.gameproviders";
+const auto CATEGORY_GAME_RESOURCES = "category.gameresources";
+const auto CATEGORY_GAME_SUPPORT_ADDONS = "category.gamesupport";
+
+const std::set<AddonType> infoProviderTypes = {
+ AddonType::SCRAPER_ALBUMS, AddonType::SCRAPER_ARTISTS, AddonType::SCRAPER_MOVIES,
+ AddonType::SCRAPER_MUSICVIDEOS, AddonType::SCRAPER_TVSHOWS,
+};
+
+const std::set<AddonType> lookAndFeelTypes = {
+ AddonType::SKIN,
+ AddonType::SCREENSAVER,
+ AddonType::RESOURCE_IMAGES,
+ AddonType::RESOURCE_LANGUAGE,
+ AddonType::RESOURCE_UISOUNDS,
+ AddonType::RESOURCE_FONT,
+ AddonType::VISUALIZATION,
+};
+
+const std::set<AddonType> gameTypes = {
+ AddonType::GAME_CONTROLLER,
+ AddonType::GAMEDLL,
+ AddonType::GAME,
+ AddonType::RESOURCE_GAMES,
+};
+
+static bool IsInfoProviderType(AddonType type)
+{
+ return infoProviderTypes.find(type) != infoProviderTypes.end();
+}
+
+static bool IsInfoProviderTypeAddon(const AddonPtr& addon)
+{
+ return IsInfoProviderType(addon->Type());
+}
+
+static bool IsLookAndFeelType(AddonType type)
+{
+ return lookAndFeelTypes.find(type) != lookAndFeelTypes.end();
+}
+
+static bool IsLookAndFeelTypeAddon(const AddonPtr& addon)
+{
+ return IsLookAndFeelType(addon->Type());
+}
+
+static bool IsGameType(AddonType type)
+{
+ return gameTypes.find(type) != gameTypes.end();
+}
+
+static bool IsStandaloneGame(const AddonPtr& addon)
+{
+ return GAME::CGameUtils::IsStandaloneGame(addon);
+}
+
+static bool IsEmulator(const AddonPtr& addon)
+{
+ return addon->Type() == AddonType::GAMEDLL &&
+ std::static_pointer_cast<GAME::CGameClient>(addon)->SupportsPath();
+}
+
+static bool IsGameProvider(const AddonPtr& addon)
+{
+ return addon->Type() == AddonType::PLUGIN && addon->HasType(AddonType::GAME);
+}
+
+static bool IsGameResource(const AddonPtr& addon)
+{
+ return addon->Type() == AddonType::RESOURCE_GAMES;
+}
+
+static bool IsGameSupportAddon(const AddonPtr& addon)
+{
+ return addon->Type() == AddonType::GAMEDLL &&
+ !std::static_pointer_cast<GAME::CGameClient>(addon)->SupportsPath() &&
+ !std::static_pointer_cast<GAME::CGameClient>(addon)->SupportsStandalone();
+}
+
+static bool IsGameAddon(const AddonPtr& addon)
+{
+ return IsGameType(addon->Type()) ||
+ IsStandaloneGame(addon) ||
+ IsGameProvider(addon) ||
+ IsGameResource(addon) ||
+ IsGameSupportAddon(addon);
+}
+
+static bool IsUserInstalled(const AddonPtr& addon)
+{
+ return !CAddonType::IsDependencyType(addon->MainType());
+}
+
+// Creates categories from addon types, if we have any addons with that type.
+static void GenerateTypeListing(const CURL& path,
+ const std::set<AddonType>& types,
+ const VECADDONS& addons,
+ CFileItemList& items)
+{
+ for (const auto& type : types)
+ {
+ for (const auto& addon : addons)
+ {
+ if (addon->HasType(type))
+ {
+ CFileItemPtr item(new CFileItem(CAddonInfo::TranslateType(type, true)));
+ CURL itemPath = path;
+ itemPath.SetFileName(CAddonInfo::TranslateType(type, false));
+ item->SetPath(itemPath.Get());
+ item->m_bIsFolder = true;
+ std::string thumb = CAddonInfo::TranslateIconType(type);
+ if (!thumb.empty() && CServiceBroker::GetGUI()->GetTextureManager().HasTexture(thumb))
+ item->SetArt("thumb", thumb);
+ items.Add(item);
+ break;
+ }
+ }
+ }
+}
+
+// Creates categories for game add-ons, if we have any game add-ons
+static void GenerateGameListing(const CURL& path, const VECADDONS& addons, CFileItemList& items)
+{
+ // Game controllers
+ for (const auto& addon : addons)
+ {
+ if (addon->Type() == AddonType::GAME_CONTROLLER)
+ {
+ CFileItemPtr item(new CFileItem(CAddonInfo::TranslateType(AddonType::GAME_CONTROLLER, true)));
+ CURL itemPath = path;
+ itemPath.SetFileName(CAddonInfo::TranslateType(AddonType::GAME_CONTROLLER, false));
+ item->SetPath(itemPath.Get());
+ item->m_bIsFolder = true;
+ std::string thumb = CAddonInfo::TranslateIconType(AddonType::GAME_CONTROLLER);
+ if (!thumb.empty() && CServiceBroker::GetGUI()->GetTextureManager().HasTexture(thumb))
+ item->SetArt("thumb", thumb);
+ items.Add(item);
+ break;
+ }
+ }
+ // Emulators
+ for (const auto& addon : addons)
+ {
+ if (IsEmulator(addon))
+ {
+ CFileItemPtr item(new CFileItem(g_localizeStrings.Get(35207))); // Emulators
+ CURL itemPath = path;
+ itemPath.SetFileName(CATEGORY_EMULATORS);
+ item->SetPath(itemPath.Get());
+ item->m_bIsFolder = true;
+ std::string thumb = CAddonInfo::TranslateIconType(AddonType::GAMEDLL);
+ if (!thumb.empty() && CServiceBroker::GetGUI()->GetTextureManager().HasTexture(thumb))
+ item->SetArt("thumb", thumb);
+ items.Add(item);
+ break;
+ }
+ }
+ // Standalone games
+ for (const auto& addon : addons)
+ {
+ if (IsStandaloneGame(addon))
+ {
+ CFileItemPtr item(new CFileItem(g_localizeStrings.Get(35208))); // Standalone games
+ CURL itemPath = path;
+ itemPath.SetFileName(CATEGORY_STANDALONE_GAMES);
+ item->SetPath(itemPath.Get());
+ item->m_bIsFolder = true;
+ std::string thumb = CAddonInfo::TranslateIconType(AddonType::GAMEDLL);
+ if (!thumb.empty() && CServiceBroker::GetGUI()->GetTextureManager().HasTexture(thumb))
+ item->SetArt("thumb", thumb);
+ items.Add(item);
+ break;
+ }
+ }
+ // Game providers
+ for (const auto& addon : addons)
+ {
+ if (IsGameProvider(addon))
+ {
+ CFileItemPtr item(new CFileItem(g_localizeStrings.Get(35220))); // Game providers
+ CURL itemPath = path;
+ itemPath.SetFileName(CATEGORY_GAME_PROVIDERS);
+ item->SetPath(itemPath.Get());
+ item->m_bIsFolder = true;
+ std::string thumb = CAddonInfo::TranslateIconType(AddonType::GAMEDLL);
+ if (!thumb.empty() && CServiceBroker::GetGUI()->GetTextureManager().HasTexture(thumb))
+ item->SetArt("thumb", thumb);
+ items.Add(item);
+ break;
+ }
+ }
+ // Game resources
+ for (const auto& addon : addons)
+ {
+ if (IsGameResource(addon))
+ {
+ CFileItemPtr item(new CFileItem(g_localizeStrings.Get(35209))); // Game resources
+ CURL itemPath = path;
+ itemPath.SetFileName(CATEGORY_GAME_RESOURCES);
+ item->SetPath(itemPath.Get());
+ item->m_bIsFolder = true;
+ std::string thumb = CAddonInfo::TranslateIconType(AddonType::GAMEDLL);
+ if (!thumb.empty() && CServiceBroker::GetGUI()->GetTextureManager().HasTexture(thumb))
+ item->SetArt("thumb", thumb);
+ items.Add(item);
+ break;
+ }
+ }
+ // Game support add-ons
+ for (const auto& addon : addons)
+ {
+ if (IsGameSupportAddon(addon))
+ {
+ CFileItemPtr item(new CFileItem(g_localizeStrings.Get(35216))); // Support add-ons
+ CURL itemPath = path;
+ itemPath.SetFileName(CATEGORY_GAME_SUPPORT_ADDONS);
+ item->SetPath(itemPath.Get());
+ item->m_bIsFolder = true;
+ std::string thumb = CAddonInfo::TranslateIconType(AddonType::GAMEDLL);
+ if (!thumb.empty() && CServiceBroker::GetGUI()->GetTextureManager().HasTexture(thumb))
+ item->SetArt("thumb", thumb);
+ items.Add(item);
+ break;
+ }
+ }
+}
+
+//Creates the top-level category list
+static void GenerateMainCategoryListing(const CURL& path, const VECADDONS& addons,
+ CFileItemList& items)
+{
+ if (std::any_of(addons.begin(), addons.end(), IsInfoProviderTypeAddon))
+ {
+ CFileItemPtr item(new CFileItem(g_localizeStrings.Get(24993)));
+ item->SetPath(URIUtils::AddFileToFolder(path.Get(), CATEGORY_INFO_PROVIDERS));
+ item->m_bIsFolder = true;
+ const std::string thumb = "DefaultAddonInfoProvider.png";
+ if (CServiceBroker::GetGUI()->GetTextureManager().HasTexture(thumb))
+ item->SetArt("thumb", thumb);
+ items.Add(item);
+ }
+ if (std::any_of(addons.begin(), addons.end(), IsLookAndFeelTypeAddon))
+ {
+ CFileItemPtr item(new CFileItem(g_localizeStrings.Get(24997)));
+ item->SetPath(URIUtils::AddFileToFolder(path.Get(), CATEGORY_LOOK_AND_FEEL));
+ item->m_bIsFolder = true;
+ const std::string thumb = "DefaultAddonLookAndFeel.png";
+ if (CServiceBroker::GetGUI()->GetTextureManager().HasTexture(thumb))
+ item->SetArt("thumb", thumb);
+ items.Add(item);
+ }
+ if (std::any_of(addons.begin(), addons.end(), IsGameAddon))
+ {
+ CFileItemPtr item(new CFileItem(CAddonInfo::TranslateType(AddonType::GAME, true)));
+ item->SetPath(URIUtils::AddFileToFolder(path.Get(), CATEGORY_GAME_ADDONS));
+ item->m_bIsFolder = true;
+ const std::string thumb = CAddonInfo::TranslateIconType(AddonType::GAME);
+ if (CServiceBroker::GetGUI()->GetTextureManager().HasTexture(thumb))
+ item->SetArt("thumb", thumb);
+ items.Add(item);
+ }
+
+ std::set<AddonType> uncategorized;
+ for (unsigned int i = static_cast<unsigned int>(AddonType::UNKNOWN) + 1;
+ i < static_cast<unsigned int>(AddonType::MAX_TYPES) - 1; ++i)
+ {
+ const AddonType type = static_cast<AddonType>(i);
+ /*
+ * Check and prevent insert for this cases:
+ * - By a provider, look and feel, dependency and game becomes given to
+ * subdirectory to control the types
+ * - By ADDON_SCRIPT and ADDON_PLUGIN, them contains one of the possible
+ * subtypes (audio, video, app or/and game) and not needed to show
+ * together in a Script or Plugin list
+ */
+ if (!IsInfoProviderType(type) && !IsLookAndFeelType(type) &&
+ !CAddonType::IsDependencyType(type) && !IsGameType(type) && type != AddonType::SCRIPT &&
+ type != AddonType::PLUGIN)
+ uncategorized.insert(type);
+ }
+ GenerateTypeListing(path, uncategorized, addons, items);
+}
+
+//Creates sub-categories or addon list for a category
+static void GenerateCategoryListing(const CURL& path, VECADDONS& addons,
+ CFileItemList& items)
+{
+ const std::string& category = path.GetFileName();
+ if (category == CATEGORY_INFO_PROVIDERS)
+ {
+ items.SetProperty("addoncategory", g_localizeStrings.Get(24993));
+ items.SetLabel(g_localizeStrings.Get(24993));
+ GenerateTypeListing(path, infoProviderTypes, addons, items);
+ }
+ else if (category == CATEGORY_LOOK_AND_FEEL)
+ {
+ items.SetProperty("addoncategory", g_localizeStrings.Get(24997));
+ items.SetLabel(g_localizeStrings.Get(24997));
+ GenerateTypeListing(path, lookAndFeelTypes, addons, items);
+ }
+ else if (category == CATEGORY_GAME_ADDONS)
+ {
+ items.SetProperty("addoncategory", CAddonInfo::TranslateType(AddonType::GAME, true));
+ items.SetLabel(CAddonInfo::TranslateType(AddonType::GAME, true));
+ GenerateGameListing(path, addons, items);
+ }
+ else if (category == CATEGORY_EMULATORS)
+ {
+ items.SetProperty("addoncategory", g_localizeStrings.Get(35207)); // Emulators
+ addons.erase(std::remove_if(addons.begin(), addons.end(),
+ [](const AddonPtr& addon){ return !IsEmulator(addon); }), addons.end());
+ CAddonsDirectory::GenerateAddonListing(path, addons, items, g_localizeStrings.Get(35207)); // Emulators
+ }
+ else if (category == CATEGORY_STANDALONE_GAMES)
+ {
+ items.SetProperty("addoncategory", g_localizeStrings.Get(35208)); // Standalone games
+ addons.erase(std::remove_if(addons.begin(), addons.end(),
+ [](const AddonPtr& addon){ return !IsStandaloneGame(addon); }), addons.end());
+ CAddonsDirectory::GenerateAddonListing(path, addons, items, g_localizeStrings.Get(35208)); // Standalone games
+ }
+ else if (category == CATEGORY_GAME_PROVIDERS)
+ {
+ items.SetProperty("addoncategory", g_localizeStrings.Get(35220)); // Game providers
+ addons.erase(std::remove_if(addons.begin(), addons.end(),
+ [](const AddonPtr& addon){ return !IsGameProvider(addon); }), addons.end());
+ CAddonsDirectory::GenerateAddonListing(path, addons, items, g_localizeStrings.Get(35220)); // Game providers
+ }
+ else if (category == CATEGORY_GAME_RESOURCES)
+ {
+ items.SetProperty("addoncategory", g_localizeStrings.Get(35209)); // Game resources
+ addons.erase(std::remove_if(addons.begin(), addons.end(),
+ [](const AddonPtr& addon){ return !IsGameResource(addon); }), addons.end());
+ CAddonsDirectory::GenerateAddonListing(path, addons, items, g_localizeStrings.Get(35209)); // Game resources
+ }
+ else if (category == CATEGORY_GAME_SUPPORT_ADDONS)
+ {
+ items.SetProperty("addoncategory", g_localizeStrings.Get(35216)); // Support add-ons
+ addons.erase(std::remove_if(addons.begin(), addons.end(),
+ [](const AddonPtr& addon) { return !IsGameSupportAddon(addon); }), addons.end());
+ CAddonsDirectory::GenerateAddonListing(path, addons, items, g_localizeStrings.Get(35216)); // Support add-ons
+ }
+ else
+ { // fallback to addon type
+ AddonType type = CAddonInfo::TranslateType(category);
+ items.SetProperty("addoncategory", CAddonInfo::TranslateType(type, true));
+ addons.erase(std::remove_if(addons.begin(), addons.end(),
+ [type](const AddonPtr& addon) { return !addon->HasType(type); }),
+ addons.end());
+ CAddonsDirectory::GenerateAddonListing(path, addons, items, CAddonInfo::TranslateType(type, true));
+ }
+}
+
+bool CAddonsDirectory::GetSearchResults(const CURL& path, CFileItemList &items)
+{
+ std::string search(path.GetFileName());
+ if (search.empty() && !GetKeyboardInput(16017, search))
+ return false;
+
+ CAddonDatabase database;
+ database.Open();
+
+ VECADDONS addons;
+ database.Search(search, addons);
+ CAddonsDirectory::GenerateAddonListing(path, addons, items, g_localizeStrings.Get(283));
+ CURL searchPath(path);
+ searchPath.SetFileName(search);
+ items.SetPath(searchPath.Get());
+ return true;
+}
+
+static void UserInstalledAddons(const CURL& path, CFileItemList &items)
+{
+ items.ClearItems();
+ items.SetLabel(g_localizeStrings.Get(24998));
+
+ VECADDONS addons;
+ CServiceBroker::GetAddonMgr().GetInstalledAddons(addons);
+ addons.erase(std::remove_if(addons.begin(), addons.end(),
+ [](const AddonPtr& addon) { return !IsUserInstalled(addon); }), addons.end());
+
+ if (addons.empty())
+ return;
+
+ const std::string& category = path.GetFileName();
+ if (category.empty())
+ {
+ GenerateMainCategoryListing(path, addons, items);
+
+ //"All" node
+ CFileItemPtr item(new CFileItem());
+ item->m_bIsFolder = true;
+ CURL itemPath = path;
+ itemPath.SetFileName("all");
+ item->SetPath(itemPath.Get());
+ item->SetLabel(g_localizeStrings.Get(593));
+ item->SetSpecialSort(SortSpecialOnTop);
+ items.Add(item);
+ }
+ else if (category == "all")
+ CAddonsDirectory::GenerateAddonListing(path, addons, items, g_localizeStrings.Get(24998));
+ else
+ GenerateCategoryListing(path, addons, items);
+}
+
+static void DependencyAddons(const CURL& path, CFileItemList &items)
+{
+ VECADDONS all;
+ CServiceBroker::GetAddonMgr().GetInstalledAddons(all);
+
+ VECADDONS deps;
+ std::copy_if(all.begin(), all.end(), std::back_inserter(deps),
+ [&](const AddonPtr& _){ return !IsUserInstalled(_); });
+
+ CAddonsDirectory::GenerateAddonListing(path, deps, items, g_localizeStrings.Get(24996));
+
+ //Set orphaned status
+ std::set<std::string> orphaned;
+ for (const auto& addon : deps)
+ {
+ if (CServiceBroker::GetAddonMgr().IsOrphaned(addon, all))
+ orphaned.insert(addon->ID());
+ }
+
+ for (int i = 0; i < items.Size(); ++i)
+ {
+ if (orphaned.find(items[i]->GetProperty("Addon.ID").asString()) != orphaned.end())
+ {
+ items[i]->SetProperty("Addon.Status", g_localizeStrings.Get(24995));
+ items[i]->SetProperty("Addon.Orphaned", true);
+ }
+ }
+}
+
+static void OutdatedAddons(const CURL& path, CFileItemList &items)
+{
+ VECADDONS addons = CServiceBroker::GetAddonMgr().GetAvailableUpdates();
+ CAddonsDirectory::GenerateAddonListing(path, addons, items, g_localizeStrings.Get(24043));
+
+ if (!items.IsEmpty())
+ {
+ if (CAddonSystemSettings::GetInstance().GetAddonAutoUpdateMode() == AUTO_UPDATES_ON)
+ {
+ const CFileItemPtr itemUpdateAllowed(
+ std::make_shared<CFileItem>("addons://update_allowed/", false));
+ itemUpdateAllowed->SetLabel(g_localizeStrings.Get(24137));
+ itemUpdateAllowed->SetSpecialSort(SortSpecialOnTop);
+ items.Add(itemUpdateAllowed);
+ }
+
+ const CFileItemPtr itemUpdateAll(std::make_shared<CFileItem>("addons://update_all/", false));
+ itemUpdateAll->SetLabel(g_localizeStrings.Get(24122));
+ itemUpdateAll->SetSpecialSort(SortSpecialOnTop);
+ items.Add(itemUpdateAll);
+ }
+}
+
+static void RunningAddons(const CURL& path, CFileItemList &items)
+{
+ VECADDONS addons;
+ CServiceBroker::GetAddonMgr().GetAddons(addons, AddonType::SERVICE);
+
+ addons.erase(std::remove_if(addons.begin(), addons.end(),
+ [](const AddonPtr& addon){ return !CScriptInvocationManager::GetInstance().IsRunning(addon->LibPath()); }), addons.end());
+ CAddonsDirectory::GenerateAddonListing(path, addons, items, g_localizeStrings.Get(24994));
+}
+
+static bool Browse(const CURL& path, CFileItemList &items)
+{
+ const std::string& repoId = path.GetHostName();
+
+ VECADDONS addons;
+ items.SetPath(path.Get());
+ if (repoId == "all")
+ {
+ CAddonRepos addonRepos;
+ if (!addonRepos.IsValid())
+ return false;
+
+ // get all latest addon versions by repo
+ addonRepos.GetLatestAddonVersionsFromAllRepos(addons);
+
+ items.SetProperty("reponame", g_localizeStrings.Get(24087));
+ items.SetLabel(g_localizeStrings.Get(24087));
+ }
+ else
+ {
+ AddonPtr repoAddon;
+ if (!CServiceBroker::GetAddonMgr().GetAddon(repoId, repoAddon, AddonType::REPOSITORY,
+ OnlyEnabled::CHOICE_YES))
+ {
+ return false;
+ }
+
+ CAddonRepos addonRepos(repoAddon);
+ if (!addonRepos.IsValid())
+ return false;
+
+ // get all addons from the single repository
+ addonRepos.GetLatestAddonVersions(addons);
+
+ items.SetProperty("reponame", repoAddon->Name());
+ items.SetLabel(repoAddon->Name());
+ }
+
+ const std::string& category = path.GetFileName();
+ if (category.empty())
+ GenerateMainCategoryListing(path, addons, items);
+ else
+ GenerateCategoryListing(path, addons, items);
+ return true;
+}
+
+static bool GetRecentlyUpdatedAddons(VECADDONS& addons)
+{
+ if (!CServiceBroker::GetAddonMgr().GetInstalledAddons(addons))
+ return false;
+
+ auto limit = CDateTime::GetCurrentDateTime() - CDateTimeSpan(14, 0, 0, 0);
+ auto isOld = [limit](const AddonPtr& addon){ return addon->LastUpdated() < limit; };
+ addons.erase(std::remove_if(addons.begin(), addons.end(), isOld), addons.end());
+ return true;
+}
+
+static bool HasRecentlyUpdatedAddons()
+{
+ VECADDONS addons;
+ return GetRecentlyUpdatedAddons(addons) && !addons.empty();
+}
+
+static bool Repos(const CURL& path, CFileItemList &items)
+{
+ items.SetLabel(g_localizeStrings.Get(24033));
+
+ VECADDONS addons;
+ CServiceBroker::GetAddonMgr().GetAddons(addons, AddonType::REPOSITORY);
+ if (addons.empty())
+ return true;
+ else if (addons.size() == 1)
+ return Browse(CURL("addons://" + addons[0]->ID()), items);
+ CFileItemPtr item(new CFileItem("addons://all/", true));
+ item->SetLabel(g_localizeStrings.Get(24087));
+ item->SetSpecialSort(SortSpecialOnTop);
+ items.Add(item);
+ for (const auto& repo : addons)
+ {
+ CFileItemPtr item = CAddonsDirectory::FileItemFromAddon(repo, "addons://" + repo->ID(), true);
+ items.Add(item);
+ }
+ items.SetContent("addons");
+ return true;
+}
+
+static void RootDirectory(CFileItemList& items)
+{
+ items.SetLabel(g_localizeStrings.Get(10040));
+ {
+ CFileItemPtr item(new CFileItem("addons://user/", true));
+ item->SetLabel(g_localizeStrings.Get(24998));
+ item->SetArt("icon", "DefaultAddonsInstalled.png");
+ items.Add(item);
+ }
+ if (CServiceBroker::GetAddonMgr().HasAvailableUpdates())
+ {
+ CFileItemPtr item(new CFileItem("addons://outdated/", true));
+ item->SetLabel(g_localizeStrings.Get(24043));
+ item->SetArt("icon", "DefaultAddonsUpdates.png");
+ items.Add(item);
+ }
+ if (CAddonInstaller::GetInstance().IsDownloading())
+ {
+ CFileItemPtr item(new CFileItem("addons://downloading/", true));
+ item->SetLabel(g_localizeStrings.Get(24067));
+ item->SetArt("icon", "DefaultNetwork.png");
+ items.Add(item);
+ }
+ if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_ADDONS_AUTOUPDATES) == ADDON::AUTO_UPDATES_ON
+ && HasRecentlyUpdatedAddons())
+ {
+ CFileItemPtr item(new CFileItem("addons://recently_updated/", true));
+ item->SetLabel(g_localizeStrings.Get(24004));
+ item->SetArt("icon", "DefaultAddonsRecentlyUpdated.png");
+ items.Add(item);
+ }
+ if (CServiceBroker::GetAddonMgr().HasAddons(AddonType::REPOSITORY))
+ {
+ CFileItemPtr item(new CFileItem("addons://repos/", true));
+ item->SetLabel(g_localizeStrings.Get(24033));
+ item->SetArt("icon", "DefaultAddonsRepo.png");
+ items.Add(item);
+ }
+ {
+ CFileItemPtr item(new CFileItem("addons://install/", false));
+ item->SetLabel(g_localizeStrings.Get(24041));
+ item->SetArt("icon", "DefaultAddonsZip.png");
+ items.Add(item);
+ }
+ {
+ CFileItemPtr item(new CFileItem("addons://search/", true));
+ item->SetLabel(g_localizeStrings.Get(137));
+ item->SetArt("icon", "DefaultAddonsSearch.png");
+ items.Add(item);
+ }
+}
+
+bool CAddonsDirectory::GetDirectory(const CURL& url, CFileItemList &items)
+{
+ std::string tmp(url.Get());
+ URIUtils::RemoveSlashAtEnd(tmp);
+ CURL path(tmp);
+ const std::string& endpoint = path.GetHostName();
+ items.ClearItems();
+ items.ClearProperties();
+ items.SetCacheToDisc(CFileItemList::CACHE_NEVER);
+ items.SetPath(path.Get());
+
+ if (endpoint.empty())
+ {
+ RootDirectory(items);
+ return true;
+ }
+ else if (endpoint == "user")
+ {
+ UserInstalledAddons(path, items);
+ return true;
+ }
+ else if (endpoint == "dependencies")
+ {
+ DependencyAddons(path, items);
+ return true;
+ }
+ // PVR hardcodes this view so keep for compatibility
+ else if (endpoint == "disabled")
+ {
+ VECADDONS addons;
+ AddonType type;
+
+ if (path.GetFileName() == "kodi.pvrclient")
+ type = AddonType::PVRDLL;
+ else if (path.GetFileName() == "kodi.vfs")
+ type = AddonType::VFS;
+ else
+ type = AddonType::UNKNOWN;
+
+ if (type != AddonType::UNKNOWN &&
+ CServiceBroker::GetAddonMgr().GetInstalledAddons(addons, type))
+ {
+ CAddonsDirectory::GenerateAddonListing(path, addons, items, CAddonInfo::TranslateType(type, true));
+ return true;
+ }
+ return false;
+ }
+ else if (endpoint == "outdated")
+ {
+ OutdatedAddons(path, items);
+ return true;
+ }
+ else if (endpoint == "running")
+ {
+ RunningAddons(path, items);
+ return true;
+ }
+ else if (endpoint == "repos")
+ {
+ return Repos(path, items);
+ }
+ else if (endpoint == "sources")
+ {
+ return GetScriptsAndPlugins(path.GetFileName(), items);
+ }
+ else if (endpoint == "search")
+ {
+ return GetSearchResults(path, items);
+ }
+ else if (endpoint == "recently_updated")
+ {
+ VECADDONS addons;
+ if (!GetRecentlyUpdatedAddons(addons))
+ return false;
+
+ CAddonsDirectory::GenerateAddonListing(path, addons, items, g_localizeStrings.Get(24004));
+ return true;
+ }
+ else if (endpoint == "downloading")
+ {
+ VECADDONS addons;
+ CAddonInstaller::GetInstance().GetInstallList(addons);
+ CAddonsDirectory::GenerateAddonListing(path, addons, items, g_localizeStrings.Get(24067));
+ return true;
+ }
+ else if (endpoint == "more")
+ {
+ const std::string& type = path.GetFileName();
+ if (type == "video" || type == "audio" || type == "image" || type == "executable")
+ return Browse(CURL("addons://all/xbmc.addon." + type), items);
+ else if (type == "game")
+ return Browse(CURL("addons://all/category.gameaddons"), items);
+ return false;
+ }
+ else
+ {
+ return Browse(path, items);
+ }
+}
+
+bool CAddonsDirectory::IsRepoDirectory(const CURL& url)
+{
+ if (url.GetHostName().empty() || !url.IsProtocol("addons"))
+ return false;
+
+ AddonPtr tmp;
+ return url.GetHostName() == "repos" || url.GetHostName() == "all" ||
+ url.GetHostName() == "search" ||
+ CServiceBroker::GetAddonMgr().GetAddon(url.GetHostName(), tmp, AddonType::REPOSITORY,
+ OnlyEnabled::CHOICE_YES);
+}
+
+void CAddonsDirectory::GenerateAddonListing(const CURL& path,
+ const VECADDONS& addons,
+ CFileItemList& items,
+ const std::string& label)
+{
+ std::map<std::string, AddonWithUpdate> addonsWithUpdate =
+ CServiceBroker::GetAddonMgr().GetAddonsWithAvailableUpdate();
+
+ items.ClearItems();
+ items.SetContent("addons");
+ items.SetLabel(label);
+ for (const auto& addon : addons)
+ {
+ CURL itemPath = path;
+ itemPath.SetFileName(addon->ID());
+ CFileItemPtr pItem = FileItemFromAddon(addon, itemPath.Get(), false);
+
+ bool installed = CServiceBroker::GetAddonMgr().IsAddonInstalled(addon->ID(), addon->Origin(),
+ addon->Version());
+ bool disabled = CServiceBroker::GetAddonMgr().IsAddonDisabled(addon->ID());
+
+ std::function<bool(bool)> CheckOutdatedOrUpdate = [&](bool checkOutdated) -> bool {
+ auto mapEntry = addonsWithUpdate.find(addon->ID());
+ if (mapEntry != addonsWithUpdate.end())
+ {
+ const std::shared_ptr<IAddon>& checkedObject =
+ checkOutdated ? mapEntry->second.m_installed : mapEntry->second.m_update;
+
+ return (checkedObject->Origin() == addon->Origin() &&
+ checkedObject->Version() == addon->Version());
+ }
+ return false;
+ };
+
+ bool isUpdate = CheckOutdatedOrUpdate(false); // check if it's an available update
+ bool hasUpdate = CheckOutdatedOrUpdate(true); // check if it's an outdated addon
+
+ std::string validUpdateVersion;
+ std::string validUpdateOrigin;
+ if (hasUpdate)
+ {
+ auto mapEntry = addonsWithUpdate.find(addon->ID());
+ validUpdateVersion = mapEntry->second.m_update->Version().asString();
+ validUpdateOrigin = mapEntry->second.m_update->Origin();
+ }
+
+ bool fromOfficialRepo = CAddonRepos::IsFromOfficialRepo(addon, CheckAddonPath::CHOICE_NO);
+
+ pItem->SetProperty("Addon.IsInstalled", installed);
+ pItem->SetProperty("Addon.IsEnabled", installed && !disabled);
+ pItem->SetProperty("Addon.HasUpdate", hasUpdate);
+ pItem->SetProperty("Addon.IsUpdate", isUpdate);
+ pItem->SetProperty("Addon.ValidUpdateVersion", validUpdateVersion);
+ pItem->SetProperty("Addon.ValidUpdateOrigin", validUpdateOrigin);
+ pItem->SetProperty("Addon.IsFromOfficialRepo", fromOfficialRepo);
+ pItem->SetProperty("Addon.IsBinary", addon->IsBinary());
+
+ if (installed)
+ pItem->SetProperty("Addon.Status", g_localizeStrings.Get(305));
+ if (disabled)
+ pItem->SetProperty("Addon.Status", g_localizeStrings.Get(24023));
+ if (hasUpdate)
+ pItem->SetProperty("Addon.Status", g_localizeStrings.Get(24068));
+ else if (addon->LifecycleState() == AddonLifecycleState::BROKEN)
+ pItem->SetProperty("Addon.Status", g_localizeStrings.Get(24098));
+ else if (addon->LifecycleState() == AddonLifecycleState::DEPRECATED)
+ pItem->SetProperty("Addon.Status", g_localizeStrings.Get(24170));
+
+ items.Add(pItem);
+ }
+}
+
+CFileItemPtr CAddonsDirectory::FileItemFromAddon(const AddonPtr &addon,
+ const std::string& path, bool folder)
+{
+ if (!addon)
+ return CFileItemPtr();
+
+ CFileItemPtr item(new CFileItem(addon));
+ item->m_bIsFolder = folder;
+ item->SetPath(path);
+
+ std::string strLabel(addon->Name());
+ if (CURL(path).GetHostName() == "search")
+ strLabel = StringUtils::Format("{} - {}", CAddonInfo::TranslateType(addon->Type(), true),
+ addon->Name());
+ item->SetLabel(strLabel);
+ item->SetArt(addon->Art());
+ item->SetArt("thumb", addon->Icon());
+ item->SetArt("icon", "DefaultAddon.png");
+
+ //! @todo fix hacks that depends on these
+ item->SetProperty("Addon.ID", addon->ID());
+ item->SetProperty("Addon.Name", addon->Name());
+ item->SetCanQueue(false);
+ const auto it = addon->ExtraInfo().find("language");
+ if (it != addon->ExtraInfo().end())
+ item->SetProperty("Addon.Language", it->second);
+
+ return item;
+}
+
+bool CAddonsDirectory::GetScriptsAndPlugins(const std::string &content, VECADDONS &addons)
+{
+ CPluginSource::Content type = CPluginSource::Translate(content);
+ if (type == CPluginSource::UNKNOWN)
+ return false;
+
+ VECADDONS tempAddons;
+ CServiceBroker::GetAddonMgr().GetAddons(tempAddons, AddonType::PLUGIN);
+ for (unsigned i=0; i<tempAddons.size(); i++)
+ {
+ const auto plugin = std::dynamic_pointer_cast<CPluginSource>(tempAddons[i]);
+ if (plugin && plugin->Provides(type))
+ addons.push_back(tempAddons[i]);
+ }
+ tempAddons.clear();
+ CServiceBroker::GetAddonMgr().GetAddons(tempAddons, AddonType::SCRIPT);
+ for (unsigned i=0; i<tempAddons.size(); i++)
+ {
+ const auto plugin = std::dynamic_pointer_cast<CPluginSource>(tempAddons[i]);
+ if (plugin && plugin->Provides(type))
+ addons.push_back(tempAddons[i]);
+ }
+ tempAddons.clear();
+
+ if (type == CPluginSource::GAME)
+ {
+ CServiceBroker::GetAddonMgr().GetAddons(tempAddons, AddonType::GAMEDLL);
+ for (auto& addon : tempAddons)
+ {
+ if (IsStandaloneGame(addon))
+ addons.push_back(addon);
+ }
+ }
+
+ return true;
+}
+
+bool CAddonsDirectory::GetScriptsAndPlugins(const std::string &content, CFileItemList &items)
+{
+ VECADDONS addons;
+ if (!GetScriptsAndPlugins(content, addons))
+ return false;
+
+ for (AddonPtr& addon : addons)
+ {
+ const bool bIsFolder = (addon->Type() == AddonType::PLUGIN);
+
+ std::string path;
+ if (addon->HasType(AddonType::PLUGIN))
+ {
+ path = "plugin://" + addon->ID();
+ const auto plugin = std::dynamic_pointer_cast<CPluginSource>(addon);
+ if (plugin && plugin->ProvidesSeveral())
+ {
+ CURL url(path);
+ std::string opt = StringUtils::Format("?content_type={}", content);
+ url.SetOptions(opt);
+ path = url.Get();
+ }
+ }
+ else if (addon->HasType(AddonType::SCRIPT))
+ {
+ path = "script://" + addon->ID();
+ }
+ else if (addon->HasType(AddonType::GAMEDLL))
+ {
+ // Kodi fails to launch games with empty path from home screen
+ path = "game://" + addon->ID();
+ }
+
+ items.Add(FileItemFromAddon(addon, path, bIsFolder));
+ }
+
+ items.SetContent("addons");
+ items.SetLabel(g_localizeStrings.Get(24001)); // Add-ons
+
+ return true;
+}
+
+}
+
diff --git a/xbmc/filesystem/AddonsDirectory.h b/xbmc/filesystem/AddonsDirectory.h
new file mode 100644
index 0000000..0ff0c11
--- /dev/null
+++ b/xbmc/filesystem/AddonsDirectory.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IDirectory.h"
+
+#include <memory>
+#include <vector>
+
+class CFileItem;
+class CFileItemList;
+class CURL;
+typedef std::shared_ptr<CFileItem> CFileItemPtr;
+
+namespace ADDON
+{
+class IAddon;
+using VECADDONS = std::vector<std::shared_ptr<IAddon>>;
+} // namespace ADDON
+
+namespace XFILE
+{
+
+ /*!
+ \ingroup windows
+ \brief Get access to shares and it's directories.
+ */
+ class CAddonsDirectory : public IDirectory
+ {
+ public:
+ CAddonsDirectory(void);
+ ~CAddonsDirectory(void) override;
+ bool GetDirectory(const CURL& url, CFileItemList &items) override;
+ bool Create(const CURL& url) override { return true; }
+ bool Exists(const CURL& url) override { return true; }
+ bool AllowAll() const override { return true; }
+
+ /*! \brief Fetch script and plugin addons of a given content type
+ \param content the content type to fetch
+ \param addons the list of addons to fill with scripts and plugin content
+ \return true if content is valid, false if it's invalid.
+ */
+ static bool GetScriptsAndPlugins(const std::string &content, ADDON::VECADDONS &addons);
+
+ /*! \brief Fetch scripts and plugins of a given content type
+ \param content the content type to fetch
+ \param items the list to fill with scripts and content
+ \return true if more than one item is found, false otherwise.
+ */
+ static bool GetScriptsAndPlugins(const std::string &content, CFileItemList &items);
+
+ static void GenerateAddonListing(const CURL& path,
+ const ADDON::VECADDONS& addons,
+ CFileItemList& items,
+ const std::string& label);
+ static CFileItemPtr FileItemFromAddon(const std::shared_ptr<ADDON::IAddon>& addon,
+ const std::string& path,
+ bool folder = false);
+
+ /*! \brief Returns true if `path` is a path or subpath of the repository directory, otherwise false */
+ static bool IsRepoDirectory(const CURL& path);
+
+ private:
+ bool GetSearchResults(const CURL& path, CFileItemList &items);
+ };
+}
diff --git a/xbmc/filesystem/AudioBookFileDirectory.cpp b/xbmc/filesystem/AudioBookFileDirectory.cpp
new file mode 100644
index 0000000..04d1c14
--- /dev/null
+++ b/xbmc/filesystem/AudioBookFileDirectory.cpp
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2014 Arne Morten Kvarving
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "AudioBookFileDirectory.h"
+
+#include "FileItem.h"
+#include "TextureDatabase.h"
+#include "URL.h"
+#include "Util.h"
+#include "filesystem/File.h"
+#include "guilib/LocalizeStrings.h"
+#include "music/tags/MusicInfoTag.h"
+#include "utils/StringUtils.h"
+
+using namespace XFILE;
+
+static int cfile_file_read(void *h, uint8_t* buf, int size)
+{
+ CFile* pFile = static_cast<CFile*>(h);
+ return pFile->Read(buf, size);
+}
+
+static int64_t cfile_file_seek(void *h, int64_t pos, int whence)
+{
+ CFile* pFile = static_cast<CFile*>(h);
+ if(whence == AVSEEK_SIZE)
+ return pFile->GetLength();
+ else
+ return pFile->Seek(pos, whence & ~AVSEEK_FORCE);
+}
+
+CAudioBookFileDirectory::~CAudioBookFileDirectory(void)
+{
+ if (m_fctx)
+ avformat_close_input(&m_fctx);
+ if (m_ioctx)
+ {
+ av_free(m_ioctx->buffer);
+ av_free(m_ioctx);
+ }
+}
+
+bool CAudioBookFileDirectory::GetDirectory(const CURL& url,
+ CFileItemList &items)
+{
+ if (!m_fctx && !ContainsFiles(url))
+ return true;
+
+ std::string title;
+ std::string author;
+ std::string album;
+
+ AVDictionaryEntry* tag=nullptr;
+ while ((tag = av_dict_get(m_fctx->metadata, "", tag, AV_DICT_IGNORE_SUFFIX)))
+ {
+ if (StringUtils::CompareNoCase(tag->key, "title") == 0)
+ title = tag->value;
+ else if (StringUtils::CompareNoCase(tag->key, "album") == 0)
+ album = tag->value;
+ else if (StringUtils::CompareNoCase(tag->key, "artist") == 0)
+ author = tag->value;
+ }
+
+ std::string thumb;
+ if (m_fctx->nb_chapters > 1)
+ thumb = CTextureUtils::GetWrappedImageURL(url.Get(), "music");
+
+ for (size_t i=0;i<m_fctx->nb_chapters;++i)
+ {
+ tag=nullptr;
+ std::string chaptitle = StringUtils::Format(g_localizeStrings.Get(25010), i + 1);
+ std::string chapauthor;
+ std::string chapalbum;
+ while ((tag=av_dict_get(m_fctx->chapters[i]->metadata, "", tag, AV_DICT_IGNORE_SUFFIX)))
+ {
+ if (StringUtils::CompareNoCase(tag->key, "title") == 0)
+ chaptitle = tag->value;
+ else if (StringUtils::CompareNoCase(tag->key, "artist") == 0)
+ chapauthor = tag->value;
+ else if (StringUtils::CompareNoCase(tag->key, "album") == 0)
+ chapalbum = tag->value;
+ }
+ CFileItemPtr item(new CFileItem(url.Get(),false));
+ item->GetMusicInfoTag()->SetTrackNumber(i+1);
+ item->GetMusicInfoTag()->SetLoaded(true);
+ item->GetMusicInfoTag()->SetTitle(chaptitle);
+ if (album.empty())
+ item->GetMusicInfoTag()->SetAlbum(title);
+ else if (chapalbum.empty())
+ item->GetMusicInfoTag()->SetAlbum(album);
+ else
+ item->GetMusicInfoTag()->SetAlbum(chapalbum);
+ if (chapauthor.empty())
+ item->GetMusicInfoTag()->SetArtist(author);
+ else
+ item->GetMusicInfoTag()->SetArtist(chapauthor);
+
+ item->SetLabel(StringUtils::Format("{0:02}. {1} - {2}", i + 1,
+ item->GetMusicInfoTag()->GetAlbum(),
+ item->GetMusicInfoTag()->GetTitle()));
+ item->SetStartOffset(CUtil::ConvertSecsToMilliSecs(m_fctx->chapters[i]->start *
+ av_q2d(m_fctx->chapters[i]->time_base)));
+ item->SetEndOffset(m_fctx->chapters[i]->end * av_q2d(m_fctx->chapters[i]->time_base));
+ int compare = m_fctx->duration / (AV_TIME_BASE);
+ if (item->GetEndOffset() < 0 || item->GetEndOffset() > compare)
+ {
+ if (i < m_fctx->nb_chapters-1)
+ item->SetEndOffset(m_fctx->chapters[i + 1]->start *
+ av_q2d(m_fctx->chapters[i + 1]->time_base));
+ else
+ item->SetEndOffset(compare);
+ }
+ item->SetEndOffset(CUtil::ConvertSecsToMilliSecs(item->GetEndOffset()));
+ item->GetMusicInfoTag()->SetDuration(
+ CUtil::ConvertMilliSecsToSecsInt(item->GetEndOffset() - item->GetStartOffset()));
+ item->SetProperty("item_start", item->GetStartOffset());
+ if (!thumb.empty())
+ item->SetArt("thumb", thumb);
+ items.Add(item);
+ }
+
+ return true;
+}
+
+bool CAudioBookFileDirectory::Exists(const CURL& url)
+{
+ return CFile::Exists(url) && ContainsFiles(url);
+}
+
+bool CAudioBookFileDirectory::ContainsFiles(const CURL& url)
+{
+ CFile file;
+ if (!file.Open(url))
+ return false;
+
+ uint8_t* buffer = (uint8_t*)av_malloc(32768);
+ m_ioctx = avio_alloc_context(buffer, 32768, 0, &file, cfile_file_read,
+ nullptr, cfile_file_seek);
+
+ m_fctx = avformat_alloc_context();
+ m_fctx->pb = m_ioctx;
+
+ if (file.IoControl(IOCTRL_SEEK_POSSIBLE, nullptr) == 0)
+ m_ioctx->seekable = 0;
+
+ m_ioctx->max_packet_size = 32768;
+
+ AVInputFormat* iformat=nullptr;
+ av_probe_input_buffer(m_ioctx, &iformat, url.Get().c_str(), nullptr, 0, 0);
+
+ bool contains = false;
+ if (avformat_open_input(&m_fctx, url.Get().c_str(), iformat, nullptr) < 0)
+ {
+ if (m_fctx)
+ avformat_close_input(&m_fctx);
+ av_free(m_ioctx->buffer);
+ av_free(m_ioctx);
+ return false;
+ }
+
+ contains = m_fctx->nb_chapters > 1;
+
+ return contains;
+}
diff --git a/xbmc/filesystem/AudioBookFileDirectory.h b/xbmc/filesystem/AudioBookFileDirectory.h
new file mode 100644
index 0000000..ccc01cd
--- /dev/null
+++ b/xbmc/filesystem/AudioBookFileDirectory.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2014 Arne Morten Kvarving
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IFileDirectory.h"
+extern "C" {
+#include <libavformat/avformat.h>
+}
+
+namespace XFILE
+{
+ class CAudioBookFileDirectory : public IFileDirectory
+ {
+ public:
+ ~CAudioBookFileDirectory(void) override;
+ bool GetDirectory(const CURL& url, CFileItemList &items) override;
+ bool Exists(const CURL& url) override;
+ bool ContainsFiles(const CURL& url) override;
+ bool IsAllowed(const CURL& url) const override { return true; }
+
+ protected:
+ AVIOContext* m_ioctx = nullptr;
+ AVFormatContext* m_fctx = nullptr;
+ };
+}
diff --git a/xbmc/filesystem/BlurayCallback.cpp b/xbmc/filesystem/BlurayCallback.cpp
new file mode 100644
index 0000000..fbf9e7c
--- /dev/null
+++ b/xbmc/filesystem/BlurayCallback.cpp
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "BlurayCallback.h"
+
+#include "FileItem.h"
+#include "filesystem/Directory.h"
+#include "filesystem/File.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+
+using namespace XFILE;
+
+struct SDirState
+{
+ CFileItemList list;
+ int curr = 0;
+};
+
+void CBlurayCallback::bluray_logger(const char* msg)
+{
+ CLog::Log(LOGDEBUG, "CBlurayCallback::Logger - {}", msg);
+}
+
+void CBlurayCallback::dir_close(BD_DIR_H *dir)
+{
+ if (dir)
+ {
+ CLog::Log(LOGDEBUG, "CBlurayCallback - Closed dir ({})", fmt::ptr(dir));
+ delete static_cast<SDirState*>(dir->internal);
+ delete dir;
+ }
+}
+
+BD_DIR_H* CBlurayCallback::dir_open(void *handle, const char* rel_path)
+{
+ std::string strRelPath(rel_path);
+ std::string* strBasePath = reinterpret_cast<std::string*>(handle);
+ if (!strBasePath)
+ {
+ CLog::Log(LOGDEBUG, "CBlurayCallback - Error opening dir, null handle!");
+ return nullptr;
+ }
+
+ std::string strDirname = URIUtils::AddFileToFolder(*strBasePath, strRelPath);
+ if (URIUtils::HasSlashAtEnd(strDirname))
+ URIUtils::RemoveSlashAtEnd(strDirname);
+
+ CLog::Log(LOGDEBUG, "CBlurayCallback - Opening dir {}", CURL::GetRedacted(strDirname));
+
+ SDirState *st = new SDirState();
+ if (!CDirectory::GetDirectory(strDirname, st->list, "", DIR_FLAG_DEFAULTS))
+ {
+ if (!CFile::Exists(strDirname))
+ CLog::Log(LOGDEBUG, "CBlurayCallback - Error opening dir! ({})",
+ CURL::GetRedacted(strDirname));
+ delete st;
+ return nullptr;
+ }
+
+ BD_DIR_H *dir = new BD_DIR_H;
+ dir->close = dir_close;
+ dir->read = dir_read;
+ dir->internal = (void*)st;
+
+ return dir;
+}
+
+int CBlurayCallback::dir_read(BD_DIR_H *dir, BD_DIRENT *entry)
+{
+ SDirState* state = static_cast<SDirState*>(dir->internal);
+
+ if (state->curr >= state->list.Size())
+ return 1;
+
+ strncpy(entry->d_name, state->list[state->curr]->GetLabel().c_str(), sizeof(entry->d_name));
+ entry->d_name[sizeof(entry->d_name) - 1] = 0;
+ state->curr++;
+
+ return 0;
+}
+
+void CBlurayCallback::file_close(BD_FILE_H *file)
+{
+ if (file)
+ {
+ delete static_cast<CFile*>(file->internal);
+ delete file;
+ }
+}
+
+int CBlurayCallback::file_eof(BD_FILE_H *file)
+{
+ if (static_cast<CFile*>(file->internal)->GetPosition() == static_cast<CFile*>(file->internal)->GetLength())
+ return 1;
+ else
+ return 0;
+}
+
+BD_FILE_H * CBlurayCallback::file_open(void *handle, const char *rel_path)
+{
+ std::string strRelPath(rel_path);
+ std::string* strBasePath = reinterpret_cast<std::string*>(handle);
+ if (!strBasePath)
+ {
+ CLog::Log(LOGDEBUG, "CBlurayCallback - Error opening dir, null handle!");
+ return nullptr;
+ }
+
+ std::string strFilename = URIUtils::AddFileToFolder(*strBasePath, strRelPath);
+
+ BD_FILE_H *file = new BD_FILE_H;
+
+ file->close = file_close;
+ file->seek = file_seek;
+ file->read = file_read;
+ file->write = file_write;
+ file->tell = file_tell;
+ file->eof = file_eof;
+
+ CFile* fp = new CFile();
+ if (fp->Open(strFilename))
+ {
+ file->internal = (void*)fp;
+ return file;
+ }
+
+ CLog::Log(LOGDEBUG, "CBlurayCallback - Error opening file! ({})", CURL::GetRedacted(strFilename));
+
+ delete fp;
+ delete file;
+
+ return nullptr;
+}
+
+int64_t CBlurayCallback::file_seek(BD_FILE_H *file, int64_t offset, int32_t origin)
+{
+ return static_cast<CFile*>(file->internal)->Seek(offset, origin);
+}
+
+int64_t CBlurayCallback::file_tell(BD_FILE_H *file)
+{
+ return static_cast<CFile*>(file->internal)->GetPosition();
+}
+
+int64_t CBlurayCallback::file_read(BD_FILE_H *file, uint8_t *buf, int64_t size)
+{
+ return static_cast<int64_t>(static_cast<CFile*>(file->internal)->Read(buf, static_cast<size_t>(size)));
+}
+
+int64_t CBlurayCallback::file_write(BD_FILE_H *file, const uint8_t *buf, int64_t size)
+{
+ return static_cast<int64_t>(static_cast<CFile*>(file->internal)->Write(buf, static_cast<size_t>(size)));
+}
diff --git a/xbmc/filesystem/BlurayCallback.h b/xbmc/filesystem/BlurayCallback.h
new file mode 100644
index 0000000..181b056
--- /dev/null
+++ b/xbmc/filesystem/BlurayCallback.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <libbluray/filesystem.h>
+
+class CBlurayCallback
+{
+public:
+ static void bluray_logger(const char* msg);
+ static void dir_close(BD_DIR_H* dir);
+ static BD_DIR_H* dir_open(void* handle, const char* rel_path);
+ static int dir_read(BD_DIR_H* dir, BD_DIRENT* entry);
+ static void file_close(BD_FILE_H* file);
+ static int file_eof(BD_FILE_H* file);
+ static BD_FILE_H* file_open(void* handle, const char* rel_path);
+ static int64_t file_read(BD_FILE_H* file, uint8_t* buf, int64_t size);
+ static int64_t file_seek(BD_FILE_H* file, int64_t offset, int32_t origin);
+ static int64_t file_tell(BD_FILE_H* file);
+ static int64_t file_write(BD_FILE_H* file, const uint8_t* buf, int64_t size);
+
+private:
+ CBlurayCallback() = default;
+ ~CBlurayCallback() = default;
+};
diff --git a/xbmc/filesystem/BlurayDirectory.cpp b/xbmc/filesystem/BlurayDirectory.cpp
new file mode 100644
index 0000000..dbfb41b
--- /dev/null
+++ b/xbmc/filesystem/BlurayDirectory.cpp
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+#include "BlurayDirectory.h"
+
+#include "File.h"
+#include "FileItem.h"
+#include "LangInfo.h"
+#include "URL.h"
+#include "filesystem/BlurayCallback.h"
+#include "filesystem/Directory.h"
+#include "guilib/LocalizeStrings.h"
+#include "utils/LangCodeExpander.h"
+#include "utils/RegExp.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+#include "video/VideoInfoTag.h"
+
+#include <array>
+#include <cassert>
+#include <climits>
+#include <stdlib.h>
+#include <string>
+
+#include <libbluray/bluray-version.h>
+#include <libbluray/bluray.h>
+#include <libbluray/filesystem.h>
+#include <libbluray/log_control.h>
+
+namespace XFILE
+{
+
+#define MAIN_TITLE_LENGTH_PERCENT 70 /** Minimum length of main titles, based on longest title */
+
+CBlurayDirectory::~CBlurayDirectory()
+{
+ Dispose();
+}
+
+void CBlurayDirectory::Dispose()
+{
+ if(m_bd)
+ {
+ bd_close(m_bd);
+ m_bd = nullptr;
+ }
+}
+
+std::string CBlurayDirectory::GetBlurayTitle()
+{
+ return GetDiscInfoString(DiscInfo::TITLE);
+}
+
+std::string CBlurayDirectory::GetBlurayID()
+{
+ return GetDiscInfoString(DiscInfo::ID);
+}
+
+std::string CBlurayDirectory::GetDiscInfoString(DiscInfo info)
+{
+ switch (info)
+ {
+ case XFILE::CBlurayDirectory::DiscInfo::TITLE:
+ {
+ if (!m_blurayInitialized)
+ return "";
+ const BLURAY_DISC_INFO* disc_info = bd_get_disc_info(m_bd);
+ if (!disc_info || !disc_info->bluray_detected)
+ return "";
+
+ std::string title = "";
+
+#if (BLURAY_VERSION > BLURAY_VERSION_CODE(1,0,0))
+ title = disc_info->disc_name ? disc_info->disc_name : "";
+#endif
+
+ return title;
+ }
+ case XFILE::CBlurayDirectory::DiscInfo::ID:
+ {
+ if (!m_blurayInitialized)
+ return "";
+
+ const BLURAY_DISC_INFO* disc_info = bd_get_disc_info(m_bd);
+ if (!disc_info || !disc_info->bluray_detected)
+ return "";
+
+ std::string id = "";
+
+#if (BLURAY_VERSION > BLURAY_VERSION_CODE(1,0,0))
+ id = disc_info->udf_volume_id ? disc_info->udf_volume_id : "";
+
+ if (id.empty())
+ {
+ id = HexToString(disc_info->disc_id, 20);
+ }
+#endif
+
+ return id;
+ }
+ default:
+ break;
+ }
+
+ return "";
+}
+
+std::shared_ptr<CFileItem> CBlurayDirectory::GetTitle(const BLURAY_TITLE_INFO* title,
+ const std::string& label)
+{
+ std::string buf;
+ std::string chap;
+ CFileItemPtr item(new CFileItem("", false));
+ CURL path(m_url);
+ buf = StringUtils::Format("BDMV/PLAYLIST/{:05}.mpls", title->playlist);
+ path.SetFileName(buf);
+ item->SetPath(path.Get());
+ int duration = (int)(title->duration / 90000);
+ item->GetVideoInfoTag()->SetDuration(duration);
+ item->GetVideoInfoTag()->m_iTrack = title->playlist;
+ buf = StringUtils::Format(label, title->playlist);
+ item->m_strTitle = buf;
+ item->SetLabel(buf);
+ chap = StringUtils::Format(g_localizeStrings.Get(25007), title->chapter_count,
+ StringUtils::SecondsToTimeString(duration));
+ item->SetLabel2(chap);
+ item->m_dwSize = 0;
+ item->SetArt("icon", "DefaultVideo.png");
+ for(unsigned int i = 0; i < title->clip_count; ++i)
+ item->m_dwSize += title->clips[i].pkt_count * 192;
+
+ return item;
+}
+
+void CBlurayDirectory::GetTitles(bool main, CFileItemList &items)
+{
+ std::vector<BLURAY_TITLE_INFO*> titleList;
+ uint64_t minDuration = 0;
+
+ // Searching for a user provided list of playlists.
+ if (main)
+ titleList = GetUserPlaylists();
+
+ if (!main || titleList.empty())
+ {
+ uint32_t numTitles = bd_get_titles(m_bd, TITLES_RELEVANT, 0);
+
+ for (uint32_t i = 0; i < numTitles; i++)
+ {
+ BLURAY_TITLE_INFO* t = bd_get_title_info(m_bd, i, 0);
+
+ if (!t)
+ {
+ CLog::Log(LOGDEBUG, "CBlurayDirectory - unable to get title {}", i);
+ continue;
+ }
+
+ if (main && t->duration > minDuration)
+ minDuration = t->duration;
+
+ titleList.emplace_back(t);
+ }
+ }
+
+ minDuration = minDuration * MAIN_TITLE_LENGTH_PERCENT / 100;
+
+ for (auto& title : titleList)
+ {
+ if (title->duration < minDuration)
+ continue;
+
+ items.Add(GetTitle(title, main ? g_localizeStrings.Get(25004) /* Main Title */ : g_localizeStrings.Get(25005) /* Title */));
+ bd_free_title_info(title);
+ }
+}
+
+void CBlurayDirectory::GetRoot(CFileItemList &items)
+{
+ GetTitles(true, items);
+
+ CURL path(m_url);
+ CFileItemPtr item;
+
+ path.SetFileName(URIUtils::AddFileToFolder(m_url.GetFileName(), "titles"));
+ item.reset(new CFileItem());
+ item->SetPath(path.Get());
+ item->m_bIsFolder = true;
+ item->SetLabel(g_localizeStrings.Get(25002) /* All titles */);
+ item->SetArt("icon", "DefaultVideoPlaylists.png");
+ items.Add(item);
+
+ const BLURAY_DISC_INFO* disc_info = bd_get_disc_info(m_bd);
+ if (disc_info && disc_info->no_menu_support)
+ {
+ CLog::Log(LOGDEBUG, "CBlurayDirectory::GetRoot - no menu support, skipping menu entry");
+ return;
+ }
+
+ path.SetFileName("menu");
+ item.reset(new CFileItem());
+ item->SetPath(path.Get());
+ item->m_bIsFolder = false;
+ item->SetLabel(g_localizeStrings.Get(25003) /* Menus */);
+ item->SetArt("icon", "DefaultProgram.png");
+ items.Add(item);
+}
+
+bool CBlurayDirectory::GetDirectory(const CURL& url, CFileItemList &items)
+{
+ Dispose();
+ m_url = url;
+ std::string root = m_url.GetHostName();
+ std::string file = m_url.GetFileName();
+ URIUtils::RemoveSlashAtEnd(file);
+ URIUtils::RemoveSlashAtEnd(root);
+
+ if (!InitializeBluray(root))
+ return false;
+
+ if(file == "root")
+ GetRoot(items);
+ else if(file == "root/titles")
+ GetTitles(false, items);
+ else
+ {
+ CURL url2 = GetUnderlyingCURL(url);
+ CDirectory::CHints hints;
+ hints.flags = m_flags;
+ if (!CDirectory::GetDirectory(url2, items, hints))
+ return false;
+ }
+
+ items.AddSortMethod(SortByTrackNumber, 554, LABEL_MASKS("%L", "%D", "%L", "")); // FileName, Duration | Foldername, empty
+ items.AddSortMethod(SortBySize, 553, LABEL_MASKS("%L", "%I", "%L", "%I")); // FileName, Size | Foldername, Size
+
+ return true;
+}
+
+CURL CBlurayDirectory::GetUnderlyingCURL(const CURL& url)
+{
+ assert(url.IsProtocol("bluray"));
+ std::string host = url.GetHostName();
+ const std::string& filename = url.GetFileName();
+ return CURL(host.append(filename));
+}
+
+bool CBlurayDirectory::InitializeBluray(const std::string &root)
+{
+ bd_set_debug_handler(CBlurayCallback::bluray_logger);
+ bd_set_debug_mask(DBG_CRIT | DBG_BLURAY | DBG_NAV);
+
+ m_bd = bd_init();
+
+ if (!m_bd)
+ {
+ CLog::Log(LOGERROR, "CBlurayDirectory::InitializeBluray - failed to initialize libbluray");
+ return false;
+ }
+
+ std::string langCode;
+ g_LangCodeExpander.ConvertToISO6392T(g_langInfo.GetDVDMenuLanguage(), langCode);
+ bd_set_player_setting_str(m_bd, BLURAY_PLAYER_SETTING_MENU_LANG, langCode.c_str());
+
+ if (!bd_open_files(m_bd, const_cast<std::string*>(&root), CBlurayCallback::dir_open, CBlurayCallback::file_open))
+ {
+ CLog::Log(LOGERROR, "CBlurayDirectory::InitializeBluray - failed to open {}",
+ CURL::GetRedacted(root));
+ return false;
+ }
+ m_blurayInitialized = true;
+
+ return true;
+}
+
+std::string CBlurayDirectory::HexToString(const uint8_t *buf, int count)
+{
+ std::array<char, 42> tmp;
+
+ for (int i = 0; i < count; i++)
+ {
+ sprintf(tmp.data() + (i * 2), "%02x", buf[i]);
+ }
+
+ return std::string(std::begin(tmp), std::end(tmp));
+}
+
+std::vector<BLURAY_TITLE_INFO*> CBlurayDirectory::GetUserPlaylists()
+{
+ std::string root = m_url.GetHostName();
+ std::string discInfPath = URIUtils::AddFileToFolder(root, "disc.inf");
+ std::vector<BLURAY_TITLE_INFO*> userTitles;
+ CFile file;
+ char buffer[1025];
+
+ if (file.Open(discInfPath))
+ {
+ CLog::Log(LOGDEBUG, "CBlurayDirectory::GetTitles - disc.inf found");
+
+ CRegExp pl(true);
+ if (!pl.RegComp("(\\d+)"))
+ {
+ file.Close();
+ return userTitles;
+ }
+
+ uint8_t maxLines = 100;
+ while ((maxLines > 0) && file.ReadString(buffer, 1024))
+ {
+ maxLines--;
+ if (StringUtils::StartsWithNoCase(buffer, "playlists"))
+ {
+ int pos = 0;
+ while ((pos = pl.RegFind(buffer, static_cast<unsigned int>(pos))) >= 0)
+ {
+ std::string playlist = pl.GetMatch(0);
+ uint32_t len = static_cast<uint32_t>(playlist.length());
+
+ if (len <= 5)
+ {
+ unsigned long int plNum = strtoul(playlist.c_str(), nullptr, 10);
+
+ BLURAY_TITLE_INFO* t = bd_get_playlist_info(m_bd, static_cast<uint32_t>(plNum), 0);
+ if (t)
+ userTitles.emplace_back(t);
+ }
+
+ if (static_cast<int64_t>(pos) + static_cast<int64_t>(len) > INT_MAX)
+ break;
+ else
+ pos += len;
+ }
+ }
+ }
+ file.Close();
+ }
+ return userTitles;
+}
+
+} /* namespace XFILE */
diff --git a/xbmc/filesystem/BlurayDirectory.h b/xbmc/filesystem/BlurayDirectory.h
new file mode 100644
index 0000000..f4b2be6
--- /dev/null
+++ b/xbmc/filesystem/BlurayDirectory.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IDirectory.h"
+#include "URL.h"
+
+#include <memory>
+
+class CFileItem;
+class CFileItemList;
+
+typedef struct bluray BLURAY;
+typedef struct bd_title_info BLURAY_TITLE_INFO;
+
+namespace XFILE
+{
+
+class CBlurayDirectory : public IDirectory
+{
+public:
+ CBlurayDirectory() = default;
+ ~CBlurayDirectory() override;
+ bool GetDirectory(const CURL& url, CFileItemList &items) override;
+
+ bool InitializeBluray(const std::string &root);
+ std::string GetBlurayTitle();
+ std::string GetBlurayID();
+
+private:
+ enum class DiscInfo
+ {
+ TITLE,
+ ID
+ };
+
+ void Dispose();
+ std::string GetDiscInfoString(DiscInfo info);
+ void GetRoot (CFileItemList &items);
+ void GetTitles(bool main, CFileItemList &items);
+ std::vector<BLURAY_TITLE_INFO*> GetUserPlaylists();
+ std::shared_ptr<CFileItem> GetTitle(const BLURAY_TITLE_INFO* title, const std::string& label);
+ CURL GetUnderlyingCURL(const CURL& url);
+ std::string HexToString(const uint8_t * buf, int count);
+ CURL m_url;
+ BLURAY* m_bd = nullptr;
+ bool m_blurayInitialized = false;
+};
+
+}
diff --git a/xbmc/filesystem/BlurayFile.cpp b/xbmc/filesystem/BlurayFile.cpp
new file mode 100644
index 0000000..34d2732
--- /dev/null
+++ b/xbmc/filesystem/BlurayFile.cpp
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "BlurayFile.h"
+
+#include "URL.h"
+
+#include <assert.h>
+
+namespace XFILE
+{
+
+ CBlurayFile::CBlurayFile(void)
+ : COverrideFile(false)
+ { }
+
+ CBlurayFile::~CBlurayFile(void) = default;
+
+ std::string CBlurayFile::TranslatePath(const CURL& url)
+ {
+ assert(url.IsProtocol("bluray"));
+
+ std::string host = url.GetHostName();
+ const std::string& filename = url.GetFileName();
+ if (host.empty() || filename.empty())
+ return "";
+
+ return host.append(filename);
+ }
+} /* namespace XFILE */
diff --git a/xbmc/filesystem/BlurayFile.h b/xbmc/filesystem/BlurayFile.h
new file mode 100644
index 0000000..1d43936
--- /dev/null
+++ b/xbmc/filesystem/BlurayFile.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "filesystem/OverrideFile.h"
+
+namespace XFILE
+{
+
+ class CBlurayFile : public COverrideFile
+ {
+ public:
+ CBlurayFile();
+ ~CBlurayFile() override;
+
+ protected:
+ std::string TranslatePath(const CURL& url) override;
+ };
+}
diff --git a/xbmc/filesystem/CDDADirectory.cpp b/xbmc/filesystem/CDDADirectory.cpp
new file mode 100644
index 0000000..9b6beaa
--- /dev/null
+++ b/xbmc/filesystem/CDDADirectory.cpp
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "CDDADirectory.h"
+
+#include "File.h"
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "music/MusicDatabase.h"
+#include "storage/MediaManager.h"
+#include "utils/StringUtils.h"
+
+using namespace XFILE;
+using namespace MEDIA_DETECT;
+
+CCDDADirectory::CCDDADirectory(void) = default;
+
+CCDDADirectory::~CCDDADirectory(void) = default;
+
+
+bool CCDDADirectory::GetDirectory(const CURL& url, CFileItemList &items)
+{
+ // Reads the tracks from an audio cd
+ std::string strPath = url.Get();
+
+ if (!CServiceBroker::GetMediaManager().IsDiscInDrive(strPath))
+ return false;
+
+ // Get information for the inserted disc
+ CCdInfo* pCdInfo = CServiceBroker::GetMediaManager().GetCdInfo(strPath);
+ if (pCdInfo == NULL)
+ return false;
+
+ // Preload CDDB info
+ CMusicDatabase musicdatabase;
+ musicdatabase.LookupCDDBInfo();
+
+ // If the disc has no tracks, we are finished here.
+ int nTracks = pCdInfo->GetTrackCount();
+ if (nTracks <= 0)
+ return false;
+
+ // Generate fileitems
+ for (int i = 1;i <= nTracks;++i)
+ {
+ // Skip Datatracks for display,
+ // but needed to query cddb
+ if (!pCdInfo->IsAudio(i))
+ continue;
+
+ // Format standard cdda item label
+ std::string strLabel = StringUtils::Format("Track {:02}", i);
+
+ CFileItemPtr pItem(new CFileItem(strLabel));
+ pItem->m_bIsFolder = false;
+ std::string path = StringUtils::Format("cdda://local/{:02}.cdda", i);
+ pItem->SetPath(path);
+
+ struct __stat64 s64;
+ if (CFile::Stat(pItem->GetPath(), &s64) == 0)
+ pItem->m_dwSize = s64.st_size;
+
+ items.Add(pItem);
+ }
+ return true;
+}
diff --git a/xbmc/filesystem/CDDADirectory.h b/xbmc/filesystem/CDDADirectory.h
new file mode 100644
index 0000000..12e5d42
--- /dev/null
+++ b/xbmc/filesystem/CDDADirectory.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IDirectory.h"
+
+namespace XFILE
+{
+
+class CCDDADirectory :
+ public IDirectory
+{
+public:
+ CCDDADirectory(void);
+ ~CCDDADirectory(void) override;
+ bool GetDirectory(const CURL& url, CFileItemList &items) override;
+};
+}
diff --git a/xbmc/filesystem/CDDAFile.cpp b/xbmc/filesystem/CDDAFile.cpp
new file mode 100644
index 0000000..cd98b08
--- /dev/null
+++ b/xbmc/filesystem/CDDAFile.cpp
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "CDDAFile.h"
+
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "storage/MediaManager.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+
+#include <algorithm>
+
+#include <sys/stat.h>
+
+using namespace MEDIA_DETECT;
+using namespace XFILE;
+
+CFileCDDA::CFileCDDA(void)
+{
+ m_pCdIo = NULL;
+ m_cdio = CLibcdio::GetInstance();
+ m_iSectorCount = 52;
+}
+
+CFileCDDA::~CFileCDDA(void)
+{
+ Close();
+}
+
+bool CFileCDDA::Open(const CURL& url)
+{
+ std::string strURL = url.GetWithoutFilename();
+
+ if (!CServiceBroker::GetMediaManager().IsDiscInDrive(strURL) || !IsValidFile(url))
+ return false;
+
+ // Open the dvd drive
+#ifdef TARGET_POSIX
+ m_pCdIo = m_cdio->cdio_open(CServiceBroker::GetMediaManager().TranslateDevicePath(strURL).c_str(),
+ DRIVER_UNKNOWN);
+#elif defined(TARGET_WINDOWS)
+ m_pCdIo = m_cdio->cdio_open_win32(
+ CServiceBroker::GetMediaManager().TranslateDevicePath(strURL, true).c_str());
+#endif
+ if (!m_pCdIo)
+ {
+ CLog::Log(LOGERROR, "file cdda: Opening the dvd drive failed");
+ return false;
+ }
+
+ int iTrack = GetTrackNum(url);
+
+ m_lsnStart = m_cdio->cdio_get_track_lsn(m_pCdIo, iTrack);
+ m_lsnEnd = m_cdio->cdio_get_track_last_lsn(m_pCdIo, iTrack);
+ m_lsnCurrent = m_lsnStart;
+
+ if (m_lsnStart == CDIO_INVALID_LSN || m_lsnEnd == CDIO_INVALID_LSN)
+ {
+ m_cdio->cdio_destroy(m_pCdIo);
+ m_pCdIo = NULL;
+ return false;
+ }
+
+ return true;
+}
+
+bool CFileCDDA::Exists(const CURL& url)
+{
+ if (!IsValidFile(url))
+ return false;
+
+ int iTrack = GetTrackNum(url);
+
+ if (!Open(url))
+ return false;
+
+ int iLastTrack = m_cdio->cdio_get_last_track_num(m_pCdIo);
+ if (iLastTrack == CDIO_INVALID_TRACK)
+ return false;
+
+ return (iTrack > 0 && iTrack <= iLastTrack);
+}
+
+int CFileCDDA::Stat(const CURL& url, struct __stat64* buffer)
+{
+ if (Open(url) && buffer)
+ {
+ *buffer = {};
+ buffer->st_size = GetLength();
+ buffer->st_mode = _S_IFREG;
+ Close();
+ return 0;
+ }
+ errno = ENOENT;
+ return -1;
+}
+
+ssize_t CFileCDDA::Read(void* lpBuf, size_t uiBufSize)
+{
+ if (!m_pCdIo || !CServiceBroker::GetMediaManager().IsDiscInDrive())
+ return -1;
+
+ if (uiBufSize > SSIZE_MAX)
+ uiBufSize = SSIZE_MAX;
+
+ // limit number of sectors that fits in buffer by m_iSectorCount
+ int iSectorCount = std::min((int)uiBufSize / CDIO_CD_FRAMESIZE_RAW, m_iSectorCount);
+
+ if (iSectorCount <= 0)
+ return -1;
+
+ // Are there enough sectors left to read
+ if (m_lsnCurrent + iSectorCount > m_lsnEnd)
+ iSectorCount = m_lsnEnd - m_lsnCurrent;
+
+ // The loop tries to solve read error problem by lowering number of sectors to read (iSectorCount).
+ // When problem is solved the proper number of sectors is stored in m_iSectorCount
+ int big_iSectorCount = iSectorCount;
+ while (iSectorCount > 0)
+ {
+ int iret = m_cdio->cdio_read_audio_sectors(m_pCdIo, lpBuf, m_lsnCurrent, iSectorCount);
+
+ if (iret == DRIVER_OP_SUCCESS)
+ {
+ // If lower iSectorCount solved the problem limit it's value
+ if (iSectorCount < big_iSectorCount)
+ {
+ m_iSectorCount = iSectorCount;
+ }
+ break;
+ }
+
+ // iSectorCount is low so it cannot solve read problem
+ if (iSectorCount <= 10)
+ {
+ CLog::Log(LOGERROR,
+ "file cdda: Reading {} sectors of audio data starting at lsn {} failed with error "
+ "code {}",
+ iSectorCount, m_lsnCurrent, iret);
+ return -1;
+ }
+
+ iSectorCount = 10;
+ }
+ m_lsnCurrent += iSectorCount;
+
+ return iSectorCount*CDIO_CD_FRAMESIZE_RAW;
+}
+
+int64_t CFileCDDA::Seek(int64_t iFilePosition, int iWhence /*=SEEK_SET*/)
+{
+ if (!m_pCdIo)
+ return -1;
+
+ lsn_t lsnPosition = (int)iFilePosition / CDIO_CD_FRAMESIZE_RAW;
+
+ switch (iWhence)
+ {
+ case SEEK_SET:
+ // cur = pos
+ m_lsnCurrent = m_lsnStart + lsnPosition;
+ break;
+ case SEEK_CUR:
+ // cur += pos
+ m_lsnCurrent += lsnPosition;
+ break;
+ case SEEK_END:
+ // end += pos
+ m_lsnCurrent = m_lsnEnd + lsnPosition;
+ break;
+ default:
+ return -1;
+ }
+
+ return ((int64_t)(m_lsnCurrent -m_lsnStart)*CDIO_CD_FRAMESIZE_RAW);
+}
+
+void CFileCDDA::Close()
+{
+ if (m_pCdIo)
+ {
+ m_cdio->cdio_destroy(m_pCdIo);
+ m_pCdIo = NULL;
+ }
+}
+
+int64_t CFileCDDA::GetPosition()
+{
+ if (!m_pCdIo)
+ return 0;
+
+ return ((int64_t)(m_lsnCurrent -m_lsnStart)*CDIO_CD_FRAMESIZE_RAW);
+}
+
+int64_t CFileCDDA::GetLength()
+{
+ if (!m_pCdIo)
+ return 0;
+
+ return ((int64_t)(m_lsnEnd -m_lsnStart)*CDIO_CD_FRAMESIZE_RAW);
+}
+
+bool CFileCDDA::IsValidFile(const CURL& url)
+{
+ // Only .cdda files are supported
+ return URIUtils::HasExtension(url.Get(), ".cdda");
+}
+
+int CFileCDDA::GetTrackNum(const CURL& url)
+{
+ std::string strFileName = url.Get();
+
+ // get track number from "cdda://local/01.cdda"
+ return atoi(strFileName.substr(13, strFileName.size() - 13 - 5).c_str());
+}
+
+#define SECTOR_COUNT 52 // max. sectors that can be read at once
+int CFileCDDA::GetChunkSize()
+{
+ return SECTOR_COUNT*CDIO_CD_FRAMESIZE_RAW;
+}
diff --git a/xbmc/filesystem/CDDAFile.h b/xbmc/filesystem/CDDAFile.h
new file mode 100644
index 0000000..654e0f9
--- /dev/null
+++ b/xbmc/filesystem/CDDAFile.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IFile.h"
+#include "storage/cdioSupport.h"
+
+namespace XFILE
+{
+class CFileCDDA : public IFile
+{
+public:
+ CFileCDDA(void);
+ ~CFileCDDA(void) override;
+ bool Open(const CURL& url) override;
+ bool Exists(const CURL& url) override;
+ int Stat(const CURL& url, struct __stat64* buffer) override;
+
+ ssize_t Read(void* lpBuf, size_t uiBufSize) override;
+ int64_t Seek(int64_t iFilePosition, int iWhence = SEEK_SET) override;
+ void Close() override;
+ int64_t GetPosition() override;
+ int64_t GetLength() override;
+ int GetChunkSize() override;
+
+protected:
+ bool IsValidFile(const CURL& url);
+ int GetTrackNum(const CURL& url);
+
+protected:
+ CdIo_t* m_pCdIo;
+ lsn_t m_lsnStart = CDIO_INVALID_LSN; // Start of m_iTrack in logical sector number
+ lsn_t m_lsnCurrent = CDIO_INVALID_LSN; // Position inside the track in logical sector number
+ lsn_t m_lsnEnd = CDIO_INVALID_LSN; // End of m_iTrack in logical sector number
+ int m_iSectorCount; // max number of sectors to read at once
+ std::shared_ptr<MEDIA_DETECT::CLibcdio> m_cdio;
+};
+}
diff --git a/xbmc/filesystem/CMakeLists.txt b/xbmc/filesystem/CMakeLists.txt
new file mode 100644
index 0000000..8bbaa44
--- /dev/null
+++ b/xbmc/filesystem/CMakeLists.txt
@@ -0,0 +1,177 @@
+set(SOURCES AddonsDirectory.cpp
+ AudioBookFileDirectory.cpp
+ CacheStrategy.cpp
+ CircularCache.cpp
+ CurlFile.cpp
+ DAVCommon.cpp
+ DAVDirectory.cpp
+ DAVFile.cpp
+ DirectoryCache.cpp
+ Directory.cpp
+ DirectoryFactory.cpp
+ DirectoryHistory.cpp
+ DllLibCurl.cpp
+ EventsDirectory.cpp
+ FavouritesDirectory.cpp
+ FileCache.cpp
+ File.cpp
+ FileDirectoryFactory.cpp
+ FileFactory.cpp
+ FTPDirectory.cpp
+ FTPParse.cpp
+ HTTPDirectory.cpp
+ IDirectory.cpp
+ IFile.cpp
+ ImageFile.cpp
+ LibraryDirectory.cpp
+ MultiPathDirectory.cpp
+ MultiPathFile.cpp
+ MusicDatabaseDirectory.cpp
+ MusicDatabaseFile.cpp
+ MusicFileDirectory.cpp
+ MusicSearchDirectory.cpp
+ OverrideDirectory.cpp
+ OverrideFile.cpp
+ PipeFile.cpp
+ PipesManager.cpp
+ PlaylistDirectory.cpp
+ PlaylistFileDirectory.cpp
+ PluginDirectory.cpp
+ PluginFile.cpp
+ PVRDirectory.cpp
+ ResourceDirectory.cpp
+ ResourceFile.cpp
+ RSSDirectory.cpp
+ ShoutcastFile.cpp
+ SmartPlaylistDirectory.cpp
+ SourcesDirectory.cpp
+ SpecialProtocol.cpp
+ SpecialProtocolDirectory.cpp
+ SpecialProtocolFile.cpp
+ StackDirectory.cpp
+ VideoDatabaseDirectory.cpp
+ VideoDatabaseFile.cpp
+ VirtualDirectory.cpp
+ XbtDirectory.cpp
+ XbtFile.cpp
+ XbtManager.cpp
+ ZeroconfDirectory.cpp
+ ZipDirectory.cpp
+ ZipFile.cpp
+ ZipManager.cpp)
+
+set(HEADERS AddonsDirectory.h
+ CacheStrategy.h
+ CircularCache.h
+ CurlFile.h
+ DAVCommon.h
+ DAVDirectory.h
+ DAVFile.h
+ Directorization.h
+ Directory.h
+ DirectoryCache.h
+ DirectoryFactory.h
+ DirectoryHistory.h
+ DllLibCurl.h
+ EventsDirectory.h
+ FTPDirectory.h
+ FTPParse.h
+ FavouritesDirectory.h
+ File.h
+ FileCache.h
+ FileDirectoryFactory.h
+ FileFactory.h
+ HTTPDirectory.h
+ IDirectory.h
+ IFile.h
+ IFileDirectory.h
+ IFileTypes.h
+ ImageFile.h
+ LibraryDirectory.h
+ MultiPathDirectory.h
+ MultiPathFile.h
+ MusicDatabaseDirectory.h
+ MusicDatabaseFile.h
+ MusicFileDirectory.h
+ MusicSearchDirectory.h
+ OverrideDirectory.h
+ OverrideFile.h
+ PVRDirectory.h
+ PipeFile.h
+ PipesManager.h
+ PlaylistDirectory.h
+ PlaylistFileDirectory.h
+ PluginDirectory.h
+ PluginFile.h
+ RSSDirectory.h
+ ResourceDirectory.h
+ ResourceFile.h
+ ShoutcastFile.h
+ SmartPlaylistDirectory.h
+ SourcesDirectory.h
+ SpecialProtocol.h
+ SpecialProtocolDirectory.h
+ SpecialProtocolFile.h
+ StackDirectory.h
+ VideoDatabaseDirectory.h
+ VideoDatabaseFile.h
+ VirtualDirectory.h
+ XbtDirectory.h
+ XbtFile.h
+ XbtManager.h
+ ZeroconfDirectory.h
+ ZipDirectory.h
+ ZipFile.h
+ ZipManager.h)
+
+if(ISO9660PP_FOUND)
+ list(APPEND SOURCES ISO9660Directory.cpp
+ ISO9660File.cpp)
+ list(APPEND HEADERS ISO9660Directory.h
+ ISO9660File.h)
+endif()
+
+if(UDFREAD_FOUND)
+ list(APPEND SOURCES UDFBlockInput.cpp
+ UDFDirectory.cpp
+ UDFFile.cpp)
+ list(APPEND HEADERS UDFBlockInput.h
+ UDFDirectory.h
+ UDFFile.h)
+endif()
+
+if(BLURAY_FOUND)
+ list(APPEND SOURCES BlurayCallback.cpp
+ BlurayDirectory.cpp
+ BlurayFile.cpp)
+ list(APPEND HEADERS BlurayCallback.h
+ BlurayDirectory.h
+ BlurayFile.h)
+endif()
+
+if(ENABLE_OPTICAL)
+ list(APPEND SOURCES CDDADirectory.cpp
+ CDDAFile.cpp)
+ list(APPEND HEADERS CDDADirectory.h
+ CDDAFile.h)
+endif()
+
+if(NFS_FOUND)
+ list(APPEND SOURCES NFSDirectory.cpp
+ NFSFile.cpp)
+ list(APPEND HEADERS NFSDirectory.h
+ NFSFile.h)
+endif()
+
+if(ENABLE_UPNP)
+ list(APPEND SOURCES NptXbmcFile.cpp
+ UPnPDirectory.cpp
+ UPnPFile.cpp)
+ list(APPEND HEADERS UPnPDirectory.h
+ UPnPFile.h)
+endif()
+
+core_add_library(filesystem)
+if(ENABLE_STATIC_LIBS AND ENABLE_UPNP)
+ target_link_libraries(filesystem PRIVATE upnp)
+endif()
diff --git a/xbmc/filesystem/CacheStrategy.cpp b/xbmc/filesystem/CacheStrategy.cpp
new file mode 100644
index 0000000..7aaeec9
--- /dev/null
+++ b/xbmc/filesystem/CacheStrategy.cpp
@@ -0,0 +1,447 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "threads/SystemClock.h"
+#include "CacheStrategy.h"
+#include "IFile.h"
+#ifdef TARGET_POSIX
+#include "PlatformDefs.h"
+#include "platform/posix/ConvUtils.h"
+#endif
+#include "Util.h"
+#include "utils/log.h"
+#include "SpecialProtocol.h"
+#include "URL.h"
+#if defined(TARGET_POSIX)
+#include "platform/posix/filesystem/PosixFile.h"
+#define CacheLocalFile CPosixFile
+#elif defined(TARGET_WINDOWS)
+#include "platform/win32/filesystem/Win32File.h"
+#define CacheLocalFile CWin32File
+#endif // TARGET_WINDOWS
+
+#include <cassert>
+#include <algorithm>
+
+using namespace XFILE;
+
+using namespace std::chrono_literals;
+
+CCacheStrategy::~CCacheStrategy() = default;
+
+void CCacheStrategy::EndOfInput() {
+ m_bEndOfInput = true;
+}
+
+bool CCacheStrategy::IsEndOfInput()
+{
+ return m_bEndOfInput;
+}
+
+void CCacheStrategy::ClearEndOfInput()
+{
+ m_bEndOfInput = false;
+}
+
+CSimpleFileCache::CSimpleFileCache()
+ : m_cacheFileRead(new CacheLocalFile())
+ , m_cacheFileWrite(new CacheLocalFile())
+ , m_hDataAvailEvent(NULL)
+{
+}
+
+CSimpleFileCache::~CSimpleFileCache()
+{
+ Close();
+ delete m_cacheFileRead;
+ delete m_cacheFileWrite;
+}
+
+int CSimpleFileCache::Open()
+{
+ Close();
+
+ m_hDataAvailEvent = new CEvent;
+
+ m_filename = CSpecialProtocol::TranslatePath(
+ CUtil::GetNextFilename("special://temp/filecache{:03}.cache", 999));
+ if (m_filename.empty())
+ {
+ CLog::Log(LOGERROR, "CSimpleFileCache::{} - Unable to generate a new filename", __FUNCTION__);
+ Close();
+ return CACHE_RC_ERROR;
+ }
+
+ CURL fileURL(m_filename);
+
+ if (!m_cacheFileWrite->OpenForWrite(fileURL, false))
+ {
+ CLog::Log(LOGERROR, "CSimpleFileCache::{} - Failed to create file \"{}\" for writing",
+ __FUNCTION__, m_filename);
+ Close();
+ return CACHE_RC_ERROR;
+ }
+
+ if (!m_cacheFileRead->Open(fileURL))
+ {
+ CLog::Log(LOGERROR, "CSimpleFileCache::{} - Failed to open file \"{}\" for reading",
+ __FUNCTION__, m_filename);
+ Close();
+ return CACHE_RC_ERROR;
+ }
+
+ return CACHE_RC_OK;
+}
+
+void CSimpleFileCache::Close()
+{
+ if (m_hDataAvailEvent)
+ delete m_hDataAvailEvent;
+
+ m_hDataAvailEvent = NULL;
+
+ m_cacheFileWrite->Close();
+ m_cacheFileRead->Close();
+
+ if (!m_filename.empty() && !m_cacheFileRead->Delete(CURL(m_filename)))
+ CLog::Log(LOGWARNING, "SimpleFileCache::{} - Failed to delete cache file \"{}\"", __FUNCTION__,
+ m_filename);
+
+ m_filename.clear();
+}
+
+size_t CSimpleFileCache::GetMaxWriteSize(const size_t& iRequestSize)
+{
+ return iRequestSize; // Can always write since it's on disk
+}
+
+int CSimpleFileCache::WriteToCache(const char *pBuffer, size_t iSize)
+{
+ size_t written = 0;
+ while (iSize > 0)
+ {
+ const ssize_t lastWritten =
+ m_cacheFileWrite->Write(pBuffer, std::min(iSize, static_cast<size_t>(SSIZE_MAX)));
+ if (lastWritten <= 0)
+ {
+ CLog::Log(LOGERROR, "SimpleFileCache::{} - <{}> Failed to write to cache", __FUNCTION__,
+ m_filename);
+ return CACHE_RC_ERROR;
+ }
+ m_nWritePosition += lastWritten;
+ iSize -= lastWritten;
+ written += lastWritten;
+ }
+
+ // when reader waits for data it will wait on the event.
+ m_hDataAvailEvent->Set();
+
+ return written;
+}
+
+int64_t CSimpleFileCache::GetAvailableRead()
+{
+ return m_nWritePosition - m_nReadPosition;
+}
+
+int CSimpleFileCache::ReadFromCache(char *pBuffer, size_t iMaxSize)
+{
+ int64_t iAvailable = GetAvailableRead();
+ if ( iAvailable <= 0 )
+ return m_bEndOfInput ? 0 : CACHE_RC_WOULD_BLOCK;
+
+ size_t toRead = std::min(iMaxSize, static_cast<size_t>(iAvailable));
+
+ size_t readBytes = 0;
+ while (toRead > 0)
+ {
+ const ssize_t lastRead =
+ m_cacheFileRead->Read(pBuffer, std::min(toRead, static_cast<size_t>(SSIZE_MAX)));
+
+ if (lastRead == 0)
+ break;
+ if (lastRead < 0)
+ {
+ CLog::Log(LOGERROR, "CSimpleFileCache::{} - <{}> Failed to read from cache", __FUNCTION__,
+ m_filename);
+ return CACHE_RC_ERROR;
+ }
+ m_nReadPosition += lastRead;
+ toRead -= lastRead;
+ readBytes += lastRead;
+ }
+
+ if (readBytes > 0)
+ m_space.Set();
+
+ return readBytes;
+}
+
+int64_t CSimpleFileCache::WaitForData(uint32_t iMinAvail, std::chrono::milliseconds timeout)
+{
+ if (timeout == 0ms || IsEndOfInput())
+ return GetAvailableRead();
+
+ XbmcThreads::EndTime<> endTime{timeout};
+ while (!IsEndOfInput())
+ {
+ int64_t iAvail = GetAvailableRead();
+ if (iAvail >= iMinAvail)
+ return iAvail;
+
+ if (!m_hDataAvailEvent->Wait(endTime.GetTimeLeft()))
+ return CACHE_RC_TIMEOUT;
+ }
+ return GetAvailableRead();
+}
+
+int64_t CSimpleFileCache::Seek(int64_t iFilePosition)
+{
+ int64_t iTarget = iFilePosition - m_nStartPosition;
+
+ if (iTarget < 0)
+ {
+ CLog::Log(LOGDEBUG, "CSimpleFileCache::{} - <{}> Request seek to {} before start of cache",
+ __FUNCTION__, iFilePosition, m_filename);
+ return CACHE_RC_ERROR;
+ }
+
+ int64_t nDiff = iTarget - m_nWritePosition;
+ if (nDiff > 500000)
+ {
+ CLog::Log(LOGDEBUG,
+ "CSimpleFileCache::{} - <{}> Requested position {} is beyond cached data ({})",
+ __FUNCTION__, m_filename, iFilePosition, m_nWritePosition);
+ return CACHE_RC_ERROR;
+ }
+
+ if (nDiff > 0 &&
+ WaitForData(static_cast<uint32_t>(iTarget - m_nReadPosition), 5s) == CACHE_RC_TIMEOUT)
+ {
+ CLog::Log(LOGDEBUG, "CSimpleFileCache::{} - <{}> Wait for position {} failed. Ended up at {}",
+ __FUNCTION__, m_filename, iFilePosition, m_nWritePosition);
+ return CACHE_RC_ERROR;
+ }
+
+ m_nReadPosition = m_cacheFileRead->Seek(iTarget, SEEK_SET);
+ if (m_nReadPosition != iTarget)
+ {
+ CLog::Log(LOGERROR, "CSimpleFileCache::{} - <{}> Can't seek cache file for position {}",
+ __FUNCTION__, iFilePosition, m_filename);
+ return CACHE_RC_ERROR;
+ }
+
+ m_space.Set();
+
+ return iFilePosition;
+}
+
+bool CSimpleFileCache::Reset(int64_t iSourcePosition)
+{
+ if (IsCachedPosition(iSourcePosition))
+ {
+ m_nReadPosition = m_cacheFileRead->Seek(iSourcePosition - m_nStartPosition, SEEK_SET);
+ return false;
+ }
+
+ m_nStartPosition = iSourcePosition;
+ m_nWritePosition = m_cacheFileWrite->Seek(0, SEEK_SET);
+ m_nReadPosition = m_cacheFileRead->Seek(0, SEEK_SET);
+ return true;
+}
+
+void CSimpleFileCache::EndOfInput()
+{
+ CCacheStrategy::EndOfInput();
+ m_hDataAvailEvent->Set();
+}
+
+int64_t CSimpleFileCache::CachedDataEndPosIfSeekTo(int64_t iFilePosition)
+{
+ if (iFilePosition >= m_nStartPosition && iFilePosition <= m_nStartPosition + m_nWritePosition)
+ return m_nStartPosition + m_nWritePosition;
+ return iFilePosition;
+}
+
+int64_t CSimpleFileCache::CachedDataStartPos()
+{
+ return m_nStartPosition;
+}
+
+int64_t CSimpleFileCache::CachedDataEndPos()
+{
+ return m_nStartPosition + m_nWritePosition;
+}
+
+bool CSimpleFileCache::IsCachedPosition(int64_t iFilePosition)
+{
+ return iFilePosition >= m_nStartPosition && iFilePosition <= m_nStartPosition + m_nWritePosition;
+}
+
+CCacheStrategy *CSimpleFileCache::CreateNew()
+{
+ return new CSimpleFileCache();
+}
+
+
+CDoubleCache::CDoubleCache(CCacheStrategy *impl)
+{
+ assert(NULL != impl);
+ m_pCache = impl;
+ m_pCacheOld = NULL;
+}
+
+CDoubleCache::~CDoubleCache()
+{
+ delete m_pCache;
+ delete m_pCacheOld;
+}
+
+int CDoubleCache::Open()
+{
+ return m_pCache->Open();
+}
+
+void CDoubleCache::Close()
+{
+ m_pCache->Close();
+ if (m_pCacheOld)
+ {
+ delete m_pCacheOld;
+ m_pCacheOld = NULL;
+ }
+}
+
+size_t CDoubleCache::GetMaxWriteSize(const size_t& iRequestSize)
+{
+ return m_pCache->GetMaxWriteSize(iRequestSize); // NOTE: Check the active cache only
+}
+
+int CDoubleCache::WriteToCache(const char *pBuffer, size_t iSize)
+{
+ return m_pCache->WriteToCache(pBuffer, iSize);
+}
+
+int CDoubleCache::ReadFromCache(char *pBuffer, size_t iMaxSize)
+{
+ return m_pCache->ReadFromCache(pBuffer, iMaxSize);
+}
+
+int64_t CDoubleCache::WaitForData(uint32_t iMinAvail, std::chrono::milliseconds timeout)
+{
+ return m_pCache->WaitForData(iMinAvail, timeout);
+}
+
+int64_t CDoubleCache::Seek(int64_t iFilePosition)
+{
+ /* Check whether position is NOT in our current cache but IS in our old cache.
+ * This is faster/more efficient than having to possibly wait for data in the
+ * Seek() call below
+ */
+ if (!m_pCache->IsCachedPosition(iFilePosition) &&
+ m_pCacheOld && m_pCacheOld->IsCachedPosition(iFilePosition))
+ {
+ // Return error to trigger a seek event which will swap the caches:
+ return CACHE_RC_ERROR;
+ }
+
+ return m_pCache->Seek(iFilePosition); // Normal seek
+}
+
+bool CDoubleCache::Reset(int64_t iSourcePosition)
+{
+ /* Check if we should (not) swap the caches. Note that when both caches have the
+ * requested position, we prefer the cache that has the most forward data
+ */
+ if (m_pCache->IsCachedPosition(iSourcePosition) &&
+ (!m_pCacheOld || !m_pCacheOld->IsCachedPosition(iSourcePosition) ||
+ m_pCache->CachedDataEndPos() >= m_pCacheOld->CachedDataEndPos()))
+ {
+ // No swap: Just use current cache
+ return m_pCache->Reset(iSourcePosition);
+ }
+
+ // Need to swap caches
+ CCacheStrategy* pCacheTmp;
+ if (!m_pCacheOld)
+ {
+ pCacheTmp = m_pCache->CreateNew();
+ if (pCacheTmp->Open() != CACHE_RC_OK)
+ {
+ delete pCacheTmp;
+ return m_pCache->Reset(iSourcePosition);
+ }
+ }
+ else
+ {
+ pCacheTmp = m_pCacheOld;
+ }
+
+ // Perform actual swap:
+ m_pCacheOld = m_pCache;
+ m_pCache = pCacheTmp;
+
+ // If new active cache still doesn't have this position, log it
+ if (!m_pCache->IsCachedPosition(iSourcePosition))
+ {
+ CLog::Log(LOGDEBUG, "CDoubleCache::{} - ({}) Cache miss for {} with new={}-{} and old={}-{}",
+ __FUNCTION__, fmt::ptr(this), iSourcePosition, m_pCache->CachedDataStartPos(),
+ m_pCache->CachedDataEndPos(), m_pCacheOld->CachedDataStartPos(),
+ m_pCacheOld->CachedDataEndPos());
+ }
+
+ return m_pCache->Reset(iSourcePosition);
+}
+
+void CDoubleCache::EndOfInput()
+{
+ m_pCache->EndOfInput();
+}
+
+bool CDoubleCache::IsEndOfInput()
+{
+ return m_pCache->IsEndOfInput();
+}
+
+void CDoubleCache::ClearEndOfInput()
+{
+ m_pCache->ClearEndOfInput();
+}
+
+int64_t CDoubleCache::CachedDataStartPos()
+{
+ return m_pCache->CachedDataStartPos();
+}
+
+int64_t CDoubleCache::CachedDataEndPos()
+{
+ return m_pCache->CachedDataEndPos();
+}
+
+int64_t CDoubleCache::CachedDataEndPosIfSeekTo(int64_t iFilePosition)
+{
+ /* Return the position on source we would end up after a cache-seek(/reset)
+ * Note that we select the cache that has the most forward data already cached
+ * for this position
+ */
+ int64_t ret = m_pCache->CachedDataEndPosIfSeekTo(iFilePosition);
+ if (m_pCacheOld)
+ return std::max(ret, m_pCacheOld->CachedDataEndPosIfSeekTo(iFilePosition));
+ return ret;
+}
+
+bool CDoubleCache::IsCachedPosition(int64_t iFilePosition)
+{
+ return m_pCache->IsCachedPosition(iFilePosition) || (m_pCacheOld && m_pCacheOld->IsCachedPosition(iFilePosition));
+}
+
+CCacheStrategy *CDoubleCache::CreateNew()
+{
+ return new CDoubleCache(m_pCache->CreateNew());
+}
+
diff --git a/xbmc/filesystem/CacheStrategy.h b/xbmc/filesystem/CacheStrategy.h
new file mode 100644
index 0000000..76c66bb
--- /dev/null
+++ b/xbmc/filesystem/CacheStrategy.h
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "threads/Event.h"
+
+#include <stdint.h>
+#include <string>
+
+namespace XFILE {
+
+#define CACHE_RC_OK 0
+#define CACHE_RC_ERROR -1
+#define CACHE_RC_WOULD_BLOCK -2
+#define CACHE_RC_TIMEOUT -3
+
+class IFile; // forward declaration
+
+class CCacheStrategy{
+public:
+ virtual ~CCacheStrategy();
+
+ virtual int Open() = 0;
+ virtual void Close() = 0;
+
+ virtual size_t GetMaxWriteSize(const size_t& iRequestSize) = 0;
+ virtual int WriteToCache(const char *pBuffer, size_t iSize) = 0;
+ virtual int ReadFromCache(char *pBuffer, size_t iMaxSize) = 0;
+ virtual int64_t WaitForData(uint32_t iMinAvail, std::chrono::milliseconds timeout) = 0;
+
+ virtual int64_t Seek(int64_t iFilePosition) = 0;
+
+ /*!
+ \brief Reset cache position
+ \param iSourcePosition position to reset to
+ \return Whether a full reset was performed, or not (e.g. only cache swap)
+ \sa CCacheStrategy
+ */
+ virtual bool Reset(int64_t iSourcePosition) = 0;
+
+ virtual void EndOfInput(); // mark the end of the input stream so that Read will know when to return EOF
+ virtual bool IsEndOfInput();
+ virtual void ClearEndOfInput();
+
+ virtual int64_t CachedDataEndPosIfSeekTo(int64_t iFilePosition) = 0;
+ virtual int64_t CachedDataStartPos() = 0;
+ virtual int64_t CachedDataEndPos() = 0;
+ virtual bool IsCachedPosition(int64_t iFilePosition) = 0;
+
+ virtual CCacheStrategy *CreateNew() = 0;
+
+ CEvent m_space;
+protected:
+ bool m_bEndOfInput = false;
+};
+
+/**
+*/
+class CSimpleFileCache : public CCacheStrategy {
+public:
+ CSimpleFileCache();
+ ~CSimpleFileCache() override;
+
+ int Open() override;
+ void Close() override;
+
+ size_t GetMaxWriteSize(const size_t& iRequestSize) override;
+ int WriteToCache(const char *pBuffer, size_t iSize) override;
+ int ReadFromCache(char *pBuffer, size_t iMaxSize) override;
+ int64_t WaitForData(uint32_t iMinAvail, std::chrono::milliseconds timeout) override;
+
+ int64_t Seek(int64_t iFilePosition) override;
+ bool Reset(int64_t iSourcePosition) override;
+ void EndOfInput() override;
+
+ int64_t CachedDataEndPosIfSeekTo(int64_t iFilePosition) override;
+ int64_t CachedDataStartPos() override;
+ int64_t CachedDataEndPos() override;
+ bool IsCachedPosition(int64_t iFilePosition) override;
+
+ CCacheStrategy *CreateNew() override;
+
+ int64_t GetAvailableRead();
+
+protected:
+ std::string m_filename;
+ IFile* m_cacheFileRead;
+ IFile* m_cacheFileWrite;
+ CEvent* m_hDataAvailEvent;
+ volatile int64_t m_nStartPosition = 0;
+ volatile int64_t m_nWritePosition = 0;
+ volatile int64_t m_nReadPosition = 0;
+};
+
+class CDoubleCache : public CCacheStrategy{
+public:
+ explicit CDoubleCache(CCacheStrategy *impl);
+ ~CDoubleCache() override;
+
+ int Open() override;
+ void Close() override;
+
+ size_t GetMaxWriteSize(const size_t& iRequestSize) override;
+ int WriteToCache(const char *pBuffer, size_t iSize) override;
+ int ReadFromCache(char *pBuffer, size_t iMaxSize) override;
+ int64_t WaitForData(uint32_t iMinAvail, std::chrono::milliseconds timeout) override;
+
+ int64_t Seek(int64_t iFilePosition) override;
+ bool Reset(int64_t iSourcePosition) override;
+ void EndOfInput() override;
+ bool IsEndOfInput() override;
+ void ClearEndOfInput() override;
+
+ int64_t CachedDataEndPosIfSeekTo(int64_t iFilePosition) override;
+ int64_t CachedDataStartPos() override;
+ int64_t CachedDataEndPos() override;
+ bool IsCachedPosition(int64_t iFilePosition) override;
+
+ CCacheStrategy *CreateNew() override;
+
+protected:
+ CCacheStrategy *m_pCache;
+ CCacheStrategy *m_pCacheOld;
+};
+
+}
+
diff --git a/xbmc/filesystem/CircularCache.cpp b/xbmc/filesystem/CircularCache.cpp
new file mode 100644
index 0000000..443736a
--- /dev/null
+++ b/xbmc/filesystem/CircularCache.cpp
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "CircularCache.h"
+
+#include "threads/SystemClock.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <mutex>
+#include <string.h>
+
+using namespace XFILE;
+using namespace std::chrono_literals;
+
+CCircularCache::CCircularCache(size_t front, size_t back)
+ : CCacheStrategy()
+ , m_beg(0)
+ , m_end(0)
+ , m_cur(0)
+ , m_buf(NULL)
+ , m_size(front + back)
+ , m_size_back(back)
+#ifdef TARGET_WINDOWS
+ , m_handle(NULL)
+#endif
+{
+}
+
+CCircularCache::~CCircularCache()
+{
+ Close();
+}
+
+int CCircularCache::Open()
+{
+#ifdef TARGET_WINDOWS
+ m_handle = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, m_size, NULL);
+ if(m_handle == NULL)
+ return CACHE_RC_ERROR;
+ m_buf = (uint8_t*)MapViewOfFile(m_handle, FILE_MAP_ALL_ACCESS, 0, 0, 0);
+#else
+ m_buf = new uint8_t[m_size];
+#endif
+ if (m_buf == NULL)
+ return CACHE_RC_ERROR;
+ m_beg = 0;
+ m_end = 0;
+ m_cur = 0;
+ return CACHE_RC_OK;
+}
+
+void CCircularCache::Close()
+{
+#ifdef TARGET_WINDOWS
+ if (m_buf != NULL)
+ UnmapViewOfFile(m_buf);
+ if (m_handle != NULL)
+ CloseHandle(m_handle);
+ m_handle = NULL;
+#else
+ delete[] m_buf;
+#endif
+ m_buf = NULL;
+}
+
+size_t CCircularCache::GetMaxWriteSize(const size_t& iRequestSize)
+{
+ std::unique_lock<CCriticalSection> lock(m_sync);
+
+ size_t back = (size_t)(m_cur - m_beg); // Backbuffer size
+ size_t front = (size_t)(m_end - m_cur); // Frontbuffer size
+ size_t limit = m_size - std::min(back, m_size_back) - front;
+
+ // Never return more than limit and size requested by caller
+ return std::min(iRequestSize, limit);
+}
+
+/**
+ * Function will write to m_buf at m_end % m_size location
+ * it will write at maximum m_size, but it will only write
+ * as much it can without wrapping around in the buffer
+ *
+ * It will always leave m_size_back of the backbuffer intact
+ * but if the back buffer is less than that, that space is
+ * usable to write.
+ *
+ * If back buffer is filled to an larger extent than
+ * m_size_back, it will allow it to be overwritten
+ * until only m_size_back data remains.
+ *
+ * The following always apply:
+ * * m_end <= m_cur <= m_end
+ * * m_end - m_beg <= m_size
+ *
+ * Multiple calls may be needed to fill buffer completely.
+ */
+int CCircularCache::WriteToCache(const char *buf, size_t len)
+{
+ std::unique_lock<CCriticalSection> lock(m_sync);
+
+ // where are we in the buffer
+ size_t pos = m_end % m_size;
+ size_t back = (size_t)(m_cur - m_beg);
+ size_t front = (size_t)(m_end - m_cur);
+
+ size_t limit = m_size - std::min(back, m_size_back) - front;
+ size_t wrap = m_size - pos;
+
+ // limit by max forward size
+ if(len > limit)
+ len = limit;
+
+ // limit to wrap point
+ if(len > wrap)
+ len = wrap;
+
+ if(len == 0)
+ return 0;
+
+ if (m_buf == NULL)
+ return 0;
+
+ // write the data
+ memcpy(m_buf + pos, buf, len);
+ m_end += len;
+
+ // drop history that was overwritten
+ if(m_end - m_beg > (int64_t)m_size)
+ m_beg = m_end - m_size;
+
+ m_written.Set();
+
+ return len;
+}
+
+/**
+ * Reads data from cache. Will only read up till
+ * the buffer wrap point. So multiple calls
+ * may be needed to empty the whole cache
+ */
+int CCircularCache::ReadFromCache(char *buf, size_t len)
+{
+ std::unique_lock<CCriticalSection> lock(m_sync);
+
+ size_t pos = m_cur % m_size;
+ size_t front = (size_t)(m_end - m_cur);
+ size_t avail = std::min(m_size - pos, front);
+
+ if(avail == 0)
+ {
+ if(IsEndOfInput())
+ return 0;
+ else
+ return CACHE_RC_WOULD_BLOCK;
+ }
+
+ if(len > avail)
+ len = avail;
+
+ if(len == 0)
+ return 0;
+
+ if (m_buf == NULL)
+ return 0;
+
+ memcpy(buf, m_buf + pos, len);
+ m_cur += len;
+
+ m_space.Set();
+
+ return len;
+}
+
+/* Wait "millis" milliseconds for "minimum" amount of data to come in.
+ * Note that caller needs to make sure there's sufficient space in the forward
+ * buffer for "minimum" bytes else we may block the full timeout time
+ */
+int64_t CCircularCache::WaitForData(uint32_t minimum, std::chrono::milliseconds timeout)
+{
+ std::unique_lock<CCriticalSection> lock(m_sync);
+ int64_t avail = m_end - m_cur;
+
+ if (timeout == 0ms || IsEndOfInput())
+ return avail;
+
+ if(minimum > m_size - m_size_back)
+ minimum = m_size - m_size_back;
+
+ XbmcThreads::EndTime<> endtime{timeout};
+ while (!IsEndOfInput() && avail < minimum && !endtime.IsTimePast() )
+ {
+ lock.unlock();
+ m_written.Wait(50ms); // may miss the deadline. shouldn't be a problem.
+ lock.lock();
+ avail = m_end - m_cur;
+ }
+
+ return avail;
+}
+
+int64_t CCircularCache::Seek(int64_t pos)
+{
+ std::unique_lock<CCriticalSection> lock(m_sync);
+
+ // if seek is a bit over what we have, try to wait a few seconds for the data to be available.
+ // we try to avoid a (heavy) seek on the source
+ if (pos >= m_end && pos < m_end + 100000)
+ {
+ /* Make everything in the cache (back & forward) back-cache, to make sure
+ * there's sufficient forward space. Increasing it with only 100000 may not be
+ * sufficient due to variable filesystem chunksize
+ */
+ m_cur = m_end;
+
+ lock.unlock();
+ WaitForData((size_t)(pos - m_cur), 5s);
+ lock.lock();
+
+ if (pos < m_beg || pos > m_end)
+ CLog::Log(LOGDEBUG,
+ "CCircularCache::{} - ({}) Wait for data failed for pos {}, ended up at {}",
+ __FUNCTION__, fmt::ptr(this), pos, m_cur);
+ }
+
+ if (pos >= m_beg && pos <= m_end)
+ {
+ m_cur = pos;
+ return pos;
+ }
+
+ return CACHE_RC_ERROR;
+}
+
+bool CCircularCache::Reset(int64_t pos)
+{
+ std::unique_lock<CCriticalSection> lock(m_sync);
+ if (IsCachedPosition(pos))
+ {
+ m_cur = pos;
+ return false;
+ }
+ m_end = pos;
+ m_beg = pos;
+ m_cur = pos;
+
+ return true;
+}
+
+int64_t CCircularCache::CachedDataEndPosIfSeekTo(int64_t iFilePosition)
+{
+ if (IsCachedPosition(iFilePosition))
+ return m_end;
+ return iFilePosition;
+}
+
+int64_t CCircularCache::CachedDataStartPos()
+{
+ return m_beg;
+}
+
+int64_t CCircularCache::CachedDataEndPos()
+{
+ return m_end;
+}
+
+bool CCircularCache::IsCachedPosition(int64_t iFilePosition)
+{
+ return iFilePosition >= m_beg && iFilePosition <= m_end;
+}
+
+CCacheStrategy *CCircularCache::CreateNew()
+{
+ return new CCircularCache(m_size - m_size_back, m_size_back);
+}
+
diff --git a/xbmc/filesystem/CircularCache.h b/xbmc/filesystem/CircularCache.h
new file mode 100644
index 0000000..21d3e6b
--- /dev/null
+++ b/xbmc/filesystem/CircularCache.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "CacheStrategy.h"
+#include "threads/CriticalSection.h"
+#include "threads/Event.h"
+
+namespace XFILE {
+
+class CCircularCache : public CCacheStrategy
+{
+public:
+ CCircularCache(size_t front, size_t back);
+ ~CCircularCache() override;
+
+ int Open() override;
+ void Close() override;
+
+ size_t GetMaxWriteSize(const size_t& iRequestSize) override;
+ int WriteToCache(const char *buf, size_t len) override;
+ int ReadFromCache(char *buf, size_t len) override;
+ int64_t WaitForData(uint32_t minimum, std::chrono::milliseconds timeout) override;
+
+ int64_t Seek(int64_t pos) override;
+ bool Reset(int64_t pos) override;
+
+ int64_t CachedDataEndPosIfSeekTo(int64_t iFilePosition) override;
+ int64_t CachedDataStartPos() override;
+ int64_t CachedDataEndPos() override;
+ bool IsCachedPosition(int64_t iFilePosition) override;
+
+ CCacheStrategy *CreateNew() override;
+protected:
+ int64_t m_beg; /**< index in file (not buffer) of beginning of valid data */
+ int64_t m_end; /**< index in file (not buffer) of end of valid data */
+ int64_t m_cur; /**< current reading index in file */
+ uint8_t *m_buf; /**< buffer holding data */
+ size_t m_size; /**< size of data buffer used (m_buf) */
+ size_t m_size_back; /**< guaranteed size of back buffer (actual size can be smaller, or larger if front buffer doesn't need it) */
+ CCriticalSection m_sync;
+ CEvent m_written;
+#ifdef TARGET_WINDOWS
+ HANDLE m_handle;
+#endif
+};
+
+} // namespace XFILE
+
diff --git a/xbmc/filesystem/CurlFile.cpp b/xbmc/filesystem/CurlFile.cpp
new file mode 100644
index 0000000..11a5272
--- /dev/null
+++ b/xbmc/filesystem/CurlFile.cpp
@@ -0,0 +1,2141 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "CurlFile.h"
+
+#include "File.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "filesystem/SpecialProtocol.h"
+#include "network/DNSNameCache.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "threads/SystemClock.h"
+#include "utils/Base64.h"
+#include "utils/XTimeUtils.h"
+
+#include <algorithm>
+#include <cassert>
+#include <climits>
+#include <vector>
+
+#ifdef TARGET_POSIX
+#include <errno.h>
+#include <inttypes.h>
+#include "platform/posix/ConvUtils.h"
+#endif
+
+#include "DllLibCurl.h"
+#include "ShoutcastFile.h"
+#include "utils/CharsetConverter.h"
+#include "utils/log.h"
+#include "utils/StringUtils.h"
+
+using namespace XFILE;
+using namespace XCURL;
+
+using namespace std::chrono_literals;
+
+#define FITS_INT(a) (((a) <= INT_MAX) && ((a) >= INT_MIN))
+
+curl_proxytype proxyType2CUrlProxyType[] = {
+ CURLPROXY_HTTP, CURLPROXY_SOCKS4, CURLPROXY_SOCKS4A,
+ CURLPROXY_SOCKS5, CURLPROXY_SOCKS5_HOSTNAME, CURLPROXY_HTTPS,
+};
+
+#define FILLBUFFER_OK 0
+#define FILLBUFFER_NO_DATA 1
+#define FILLBUFFER_FAIL 2
+
+// curl calls this routine to debug
+extern "C" int debug_callback(CURL_HANDLE *handle, curl_infotype info, char *output, size_t size, void *data)
+{
+ if (info == CURLINFO_DATA_IN || info == CURLINFO_DATA_OUT)
+ return 0;
+
+ if (!CServiceBroker::GetLogging().CanLogComponent(LOGCURL))
+ return 0;
+
+ std::string strLine;
+ strLine.append(output, size);
+ std::vector<std::string> vecLines;
+ StringUtils::Tokenize(strLine, vecLines, "\r\n");
+ std::vector<std::string>::const_iterator it = vecLines.begin();
+
+ const char *infotype;
+ switch(info)
+ {
+ case CURLINFO_TEXT : infotype = "TEXT: "; break;
+ case CURLINFO_HEADER_IN : infotype = "HEADER_IN: "; break;
+ case CURLINFO_HEADER_OUT : infotype = "HEADER_OUT: "; break;
+ case CURLINFO_SSL_DATA_IN : infotype = "SSL_DATA_IN: "; break;
+ case CURLINFO_SSL_DATA_OUT : infotype = "SSL_DATA_OUT: "; break;
+ case CURLINFO_END : infotype = "END: "; break;
+ default : infotype = ""; break;
+ }
+
+ while (it != vecLines.end())
+ {
+ CLog::Log(LOGDEBUG, "Curl::Debug - {}{}", infotype, (*it));
+ ++it;
+ }
+ return 0;
+}
+
+/* curl calls this routine to get more data */
+extern "C" size_t write_callback(char *buffer,
+ size_t size,
+ size_t nitems,
+ void *userp)
+{
+ if(userp == NULL) return 0;
+
+ CCurlFile::CReadState *state = (CCurlFile::CReadState *)userp;
+ return state->WriteCallback(buffer, size, nitems);
+}
+
+extern "C" size_t read_callback(char *buffer,
+ size_t size,
+ size_t nitems,
+ void *userp)
+{
+ if(userp == NULL) return 0;
+
+ CCurlFile::CReadState *state = (CCurlFile::CReadState *)userp;
+ return state->ReadCallback(buffer, size, nitems);
+}
+
+extern "C" size_t header_callback(void *ptr, size_t size, size_t nmemb, void *stream)
+{
+ CCurlFile::CReadState *state = (CCurlFile::CReadState *)stream;
+ return state->HeaderCallback(ptr, size, nmemb);
+}
+
+/* used only by CCurlFile::Stat to bail out of unwanted transfers */
+extern "C" int transfer_abort_callback(void *clientp,
+ curl_off_t dltotal,
+ curl_off_t dlnow,
+ curl_off_t ultotal,
+ curl_off_t ulnow)
+{
+ if(dlnow > 0)
+ return 1;
+ else
+ return 0;
+}
+
+/* fix for silly behavior of realloc */
+static inline void* realloc_simple(void *ptr, size_t size)
+{
+ void *ptr2 = realloc(ptr, size);
+ if(ptr && !ptr2 && size > 0)
+ {
+ free(ptr);
+ return NULL;
+ }
+ else
+ return ptr2;
+}
+
+static constexpr int CURL_OFF = 0L;
+static constexpr int CURL_ON = 1L;
+
+size_t CCurlFile::CReadState::HeaderCallback(void *ptr, size_t size, size_t nmemb)
+{
+ std::string inString;
+ // libcurl doc says that this info is not always \0 terminated
+ const char* strBuf = (const char*)ptr;
+ const size_t iSize = size * nmemb;
+ if (strBuf[iSize - 1] == 0)
+ inString.assign(strBuf, iSize - 1); // skip last char if it's zero
+ else
+ inString.append(strBuf, iSize);
+
+ m_httpheader.Parse(inString);
+
+ return iSize;
+}
+
+size_t CCurlFile::CReadState::ReadCallback(char *buffer, size_t size, size_t nitems)
+{
+ if (m_fileSize == 0)
+ return 0;
+
+ if (m_filePos >= m_fileSize)
+ {
+ m_isPaused = true;
+ return CURL_READFUNC_PAUSE;
+ }
+
+ int64_t retSize = std::min(m_fileSize - m_filePos, int64_t(nitems * size));
+ memcpy(buffer, m_readBuffer + m_filePos, retSize);
+ m_filePos += retSize;
+
+ return retSize;
+}
+
+size_t CCurlFile::CReadState::WriteCallback(char *buffer, size_t size, size_t nitems)
+{
+ unsigned int amount = size * nitems;
+ if (m_overflowSize)
+ {
+ // we have our overflow buffer - first get rid of as much as we can
+ unsigned int maxWriteable = std::min(m_buffer.getMaxWriteSize(), m_overflowSize);
+ if (maxWriteable)
+ {
+ if (!m_buffer.WriteData(m_overflowBuffer, maxWriteable))
+ {
+ CLog::Log(LOGERROR, "CCurlFile::CReadState::{} - ({}) Unable to write to buffer",
+ __FUNCTION__, fmt::ptr(this));
+ return 0;
+ }
+
+ if (maxWriteable < m_overflowSize)
+ {
+ // still have some more - copy it down
+ memmove(m_overflowBuffer, m_overflowBuffer + maxWriteable, m_overflowSize - maxWriteable);
+ }
+ m_overflowSize -= maxWriteable;
+
+ // Shrink memory:
+ m_overflowBuffer = (char*)realloc_simple(m_overflowBuffer, m_overflowSize);
+ }
+ }
+ // ok, now copy the data into our ring buffer
+ unsigned int maxWriteable = std::min(m_buffer.getMaxWriteSize(), amount);
+ if (maxWriteable)
+ {
+ if (!m_buffer.WriteData(buffer, maxWriteable))
+ {
+ CLog::Log(LOGERROR,
+ "CCurlFile::CReadState::{} - ({}) Unable to write to buffer with {} bytes",
+ __FUNCTION__, fmt::ptr(this), maxWriteable);
+ return 0;
+ }
+ else
+ {
+ amount -= maxWriteable;
+ buffer += maxWriteable;
+ }
+ }
+ if (amount)
+ {
+ //! @todo Limit max. amount of the overflowbuffer
+ m_overflowBuffer = (char*)realloc_simple(m_overflowBuffer, amount + m_overflowSize);
+ if(m_overflowBuffer == NULL)
+ {
+ CLog::Log(LOGWARNING,
+ "CCurlFile::CReadState::{} - ({}) Failed to grow overflow buffer from {} bytes to "
+ "{} bytes",
+ __FUNCTION__, fmt::ptr(this), m_overflowSize, amount + m_overflowSize);
+ return 0;
+ }
+ memcpy(m_overflowBuffer + m_overflowSize, buffer, amount);
+ m_overflowSize += amount;
+ }
+ return size * nitems;
+}
+
+CCurlFile::CReadState::CReadState()
+{
+ m_easyHandle = NULL;
+ m_multiHandle = NULL;
+ m_overflowBuffer = NULL;
+ m_overflowSize = 0;
+ m_stillRunning = 0;
+ m_filePos = 0;
+ m_fileSize = 0;
+ m_bufferSize = 0;
+ m_cancelled = false;
+ m_bFirstLoop = true;
+ m_sendRange = true;
+ m_bLastError = false;
+ m_readBuffer = 0;
+ m_isPaused = false;
+ m_bRetry = true;
+ m_curlHeaderList = NULL;
+ m_curlAliasList = NULL;
+}
+
+CCurlFile::CReadState::~CReadState()
+{
+ Disconnect();
+
+ if(m_easyHandle)
+ g_curlInterface.easy_release(&m_easyHandle, &m_multiHandle);
+}
+
+bool CCurlFile::CReadState::Seek(int64_t pos)
+{
+ if(pos == m_filePos)
+ return true;
+
+ if(FITS_INT(pos - m_filePos) && m_buffer.SkipBytes((int)(pos - m_filePos)))
+ {
+ m_filePos = pos;
+ return true;
+ }
+
+ if(pos > m_filePos && pos < m_filePos + m_bufferSize)
+ {
+ int len = m_buffer.getMaxReadSize();
+ m_filePos += len;
+ m_buffer.SkipBytes(len);
+ if (FillBuffer(m_bufferSize) != FILLBUFFER_OK)
+ {
+ if(!m_buffer.SkipBytes(-len))
+ CLog::Log(LOGERROR,
+ "CCurlFile::CReadState::{} - ({}) Failed to restore position after failed fill",
+ __FUNCTION__, fmt::ptr(this));
+ else
+ m_filePos -= len;
+ return false;
+ }
+
+ if(!FITS_INT(pos - m_filePos) || !m_buffer.SkipBytes((int)(pos - m_filePos)))
+ {
+ CLog::Log(
+ LOGERROR,
+ "CCurlFile::CReadState::{} - ({}) Failed to skip to position after having filled buffer",
+ __FUNCTION__, fmt::ptr(this));
+ if(!m_buffer.SkipBytes(-len))
+ CLog::Log(LOGERROR,
+ "CCurlFile::CReadState::{} - ({}) Failed to restore position after failed seek",
+ __FUNCTION__, fmt::ptr(this));
+ else
+ m_filePos -= len;
+ return false;
+ }
+ m_filePos = pos;
+ return true;
+ }
+ return false;
+}
+
+void CCurlFile::CReadState::SetResume(void)
+{
+ /*
+ * Explicitly set RANGE header when filepos=0 as some http servers require us to always send the range
+ * request header. If we don't the server may provide different content causing seeking to fail.
+ * This only affects HTTP-like items, for FTP it's a null operation.
+ */
+ if (m_sendRange && m_filePos == 0)
+ g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_RANGE, "0-");
+ else
+ {
+ g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_RANGE, NULL);
+ m_sendRange = false;
+ }
+
+ g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_RESUME_FROM_LARGE, m_filePos);
+}
+
+long CCurlFile::CReadState::Connect(unsigned int size)
+{
+ if (m_filePos != 0)
+ CLog::Log(LOGDEBUG, "CurlFile::CReadState::{} - ({}) Resume from position {}", __FUNCTION__,
+ fmt::ptr(this), m_filePos);
+
+ SetResume();
+ g_curlInterface.multi_add_handle(m_multiHandle, m_easyHandle);
+
+ m_bufferSize = size;
+ m_buffer.Destroy();
+ m_buffer.Create(size * 3);
+ m_httpheader.Clear();
+
+ // read some data in to try and obtain the length
+ // maybe there's a better way to get this info??
+ m_stillRunning = 1;
+
+ // (Try to) fill buffer
+ if (FillBuffer(1) != FILLBUFFER_OK)
+ {
+ // Check response code
+ long response;
+ if (CURLE_OK == g_curlInterface.easy_getinfo(m_easyHandle, CURLINFO_RESPONSE_CODE, &response))
+ return response;
+ else
+ return -1;
+ }
+
+ double length;
+ if (CURLE_OK == g_curlInterface.easy_getinfo(m_easyHandle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length))
+ {
+ if (length < 0)
+ length = 0.0;
+ m_fileSize = m_filePos + (int64_t)length;
+ }
+
+ long response;
+ if (CURLE_OK == g_curlInterface.easy_getinfo(m_easyHandle, CURLINFO_RESPONSE_CODE, &response))
+ return response;
+
+ return -1;
+}
+
+void CCurlFile::CReadState::Disconnect()
+{
+ if(m_multiHandle && m_easyHandle)
+ g_curlInterface.multi_remove_handle(m_multiHandle, m_easyHandle);
+
+ m_buffer.Clear();
+ free(m_overflowBuffer);
+ m_overflowBuffer = NULL;
+ m_overflowSize = 0;
+ m_filePos = 0;
+ m_fileSize = 0;
+ m_bufferSize = 0;
+ m_readBuffer = 0;
+
+ /* cleanup */
+ if( m_curlHeaderList )
+ g_curlInterface.slist_free_all(m_curlHeaderList);
+ m_curlHeaderList = NULL;
+
+ if( m_curlAliasList )
+ g_curlInterface.slist_free_all(m_curlAliasList);
+ m_curlAliasList = NULL;
+}
+
+
+CCurlFile::~CCurlFile()
+{
+ Close();
+ delete m_state;
+ delete m_oldState;
+}
+
+CCurlFile::CCurlFile()
+ : m_overflowBuffer(NULL)
+{
+ m_opened = false;
+ m_forWrite = false;
+ m_inError = false;
+ m_multisession = true;
+ m_seekable = true;
+ m_connecttimeout = 0;
+ m_redirectlimit = 5;
+ m_lowspeedtime = 0;
+ m_ftppasvip = false;
+ m_bufferSize = 32768;
+ m_postdataset = false;
+ m_state = new CReadState();
+ m_oldState = NULL;
+ m_skipshout = false;
+ m_httpresponse = -1;
+ m_acceptCharset = "UTF-8,*;q=0.8"; /* prefer UTF-8 if available */
+ m_allowRetry = true;
+ m_acceptencoding = "all"; /* Accept all supported encoding by default */
+}
+
+//Has to be called before Open()
+void CCurlFile::SetBufferSize(unsigned int size)
+{
+ m_bufferSize = size;
+}
+
+void CCurlFile::Close()
+{
+ if (m_opened && m_forWrite && !m_inError)
+ Write(NULL, 0);
+
+ m_state->Disconnect();
+ delete m_oldState;
+ m_oldState = NULL;
+
+ m_url.clear();
+ m_referer.clear();
+ m_cookie.clear();
+
+ m_opened = false;
+ m_forWrite = false;
+ m_inError = false;
+
+ if (m_dnsCacheList)
+ g_curlInterface.slist_free_all(m_dnsCacheList);
+ m_dnsCacheList = nullptr;
+}
+
+void CCurlFile::SetCommonOptions(CReadState* state, bool failOnError /* = true */)
+{
+ CURL_HANDLE* h = state->m_easyHandle;
+
+ g_curlInterface.easy_reset(h);
+
+ g_curlInterface.easy_setopt(h, CURLOPT_DEBUGFUNCTION, debug_callback);
+
+ if( CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_logLevel >= LOG_LEVEL_DEBUG )
+ g_curlInterface.easy_setopt(h, CURLOPT_VERBOSE, CURL_ON);
+ else
+ g_curlInterface.easy_setopt(h, CURLOPT_VERBOSE, CURL_OFF);
+
+ g_curlInterface.easy_setopt(h, CURLOPT_WRITEDATA, state);
+ g_curlInterface.easy_setopt(h, CURLOPT_WRITEFUNCTION, write_callback);
+
+ g_curlInterface.easy_setopt(h, CURLOPT_READDATA, state);
+ g_curlInterface.easy_setopt(h, CURLOPT_READFUNCTION, read_callback);
+
+ // use DNS cache
+ g_curlInterface.easy_setopt(h, CURLOPT_RESOLVE, m_dnsCacheList);
+
+ // make sure headers are separated from the data stream
+ g_curlInterface.easy_setopt(h, CURLOPT_WRITEHEADER, state);
+ g_curlInterface.easy_setopt(h, CURLOPT_HEADERFUNCTION, header_callback);
+ g_curlInterface.easy_setopt(h, CURLOPT_HEADER, CURL_OFF);
+
+ g_curlInterface.easy_setopt(h, CURLOPT_FTP_USE_EPSV, 0); // turn off epsv
+
+ // Allow us to follow redirects
+ g_curlInterface.easy_setopt(h, CURLOPT_FOLLOWLOCATION, m_redirectlimit != 0);
+ g_curlInterface.easy_setopt(h, CURLOPT_MAXREDIRS, m_redirectlimit);
+
+ // Enable cookie engine for current handle
+ g_curlInterface.easy_setopt(h, CURLOPT_COOKIEFILE, "");
+
+ // Set custom cookie if requested
+ if (!m_cookie.empty())
+ g_curlInterface.easy_setopt(h, CURLOPT_COOKIE, m_cookie.c_str());
+
+ g_curlInterface.easy_setopt(h, CURLOPT_COOKIELIST, "FLUSH");
+
+ // When using multiple threads you should set the CURLOPT_NOSIGNAL option to
+ // TRUE for all handles. Everything will work fine except that timeouts are not
+ // honored during the DNS lookup - which you can work around by building libcurl
+ // with c-ares support. c-ares is a library that provides asynchronous name
+ // resolves. Unfortunately, c-ares does not yet support IPv6.
+ g_curlInterface.easy_setopt(h, CURLOPT_NOSIGNAL, CURL_ON);
+
+ if (failOnError)
+ {
+ // not interested in failed requests
+ g_curlInterface.easy_setopt(h, CURLOPT_FAILONERROR, 1);
+ }
+
+ // enable support for icecast / shoutcast streams
+ if ( NULL == state->m_curlAliasList )
+ // m_curlAliasList is used only by this one place, but SetCommonOptions can
+ // be called multiple times, only append to list if it's empty.
+ state->m_curlAliasList = g_curlInterface.slist_append(state->m_curlAliasList, "ICY 200 OK");
+ g_curlInterface.easy_setopt(h, CURLOPT_HTTP200ALIASES, state->m_curlAliasList);
+
+ if (!m_verifyPeer)
+ g_curlInterface.easy_setopt(h, CURLOPT_SSL_VERIFYPEER, 0);
+
+ g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_URL, m_url.c_str());
+ g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_TRANSFERTEXT, CURL_OFF);
+
+ // setup POST data if it is set (and it may be empty)
+ if (m_postdataset)
+ {
+ g_curlInterface.easy_setopt(h, CURLOPT_POST, 1 );
+ g_curlInterface.easy_setopt(h, CURLOPT_POSTFIELDSIZE, m_postdata.length());
+ g_curlInterface.easy_setopt(h, CURLOPT_POSTFIELDS, m_postdata.c_str());
+ }
+
+ // setup Referer header if needed
+ if (!m_referer.empty())
+ g_curlInterface.easy_setopt(h, CURLOPT_REFERER, m_referer.c_str());
+ else
+ {
+ g_curlInterface.easy_setopt(h, CURLOPT_REFERER, NULL);
+ // Do not send referer header on redirects (same behaviour as ffmpeg and browsers)
+ g_curlInterface.easy_setopt(h, CURLOPT_AUTOREFERER, CURL_OFF);
+ }
+
+ // setup any requested authentication
+ if( !m_ftpauth.empty() )
+ {
+ g_curlInterface.easy_setopt(h, CURLOPT_FTP_SSL, CURLFTPSSL_TRY);
+ if( m_ftpauth == "any" )
+ g_curlInterface.easy_setopt(h, CURLOPT_FTPSSLAUTH, CURLFTPAUTH_DEFAULT);
+ else if( m_ftpauth == "ssl" )
+ g_curlInterface.easy_setopt(h, CURLOPT_FTPSSLAUTH, CURLFTPAUTH_SSL);
+ else if( m_ftpauth == "tls" )
+ g_curlInterface.easy_setopt(h, CURLOPT_FTPSSLAUTH, CURLFTPAUTH_TLS);
+ }
+
+ // setup requested http authentication method
+ bool bAuthSet = false;
+ if(!m_httpauth.empty())
+ {
+ bAuthSet = true;
+ if( m_httpauth == "any" )
+ g_curlInterface.easy_setopt(h, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
+ else if( m_httpauth == "anysafe" )
+ g_curlInterface.easy_setopt(h, CURLOPT_HTTPAUTH, CURLAUTH_ANYSAFE);
+ else if( m_httpauth == "digest" )
+ g_curlInterface.easy_setopt(h, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
+ else if( m_httpauth == "ntlm" )
+ g_curlInterface.easy_setopt(h, CURLOPT_HTTPAUTH, CURLAUTH_NTLM);
+ else
+ bAuthSet = false;
+ }
+
+ // set username and password for current handle
+ if (!m_username.empty())
+ {
+ g_curlInterface.easy_setopt(h, CURLOPT_USERNAME, m_username.c_str());
+ if (!m_password.empty())
+ g_curlInterface.easy_setopt(h, CURLOPT_PASSWORD, m_password.c_str());
+
+ if (!bAuthSet)
+ g_curlInterface.easy_setopt(h, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
+ }
+
+ // allow passive mode for ftp
+ if( m_ftpport.length() > 0 )
+ g_curlInterface.easy_setopt(h, CURLOPT_FTPPORT, m_ftpport.c_str());
+ else
+ g_curlInterface.easy_setopt(h, CURLOPT_FTPPORT, NULL);
+
+ // allow curl to not use the ip address in the returned pasv response
+ if( m_ftppasvip )
+ g_curlInterface.easy_setopt(h, CURLOPT_FTP_SKIP_PASV_IP, 0);
+ else
+ g_curlInterface.easy_setopt(h, CURLOPT_FTP_SKIP_PASV_IP, 1);
+
+ // setup Accept-Encoding if requested
+ if (m_acceptencoding.length() > 0)
+ g_curlInterface.easy_setopt(h, CURLOPT_ACCEPT_ENCODING, m_acceptencoding == "all" ? "" : m_acceptencoding.c_str());
+
+ if (!m_acceptCharset.empty())
+ SetRequestHeader("Accept-Charset", m_acceptCharset);
+
+ if (m_userAgent.length() > 0)
+ g_curlInterface.easy_setopt(h, CURLOPT_USERAGENT, m_userAgent.c_str());
+ else /* set some default agent as shoutcast doesn't return proper stuff otherwise */
+ g_curlInterface.easy_setopt(h, CURLOPT_USERAGENT, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_userAgent.c_str());
+
+ if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlDisableIPV6)
+ g_curlInterface.easy_setopt(h, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
+
+ if (!m_proxyhost.empty())
+ {
+ g_curlInterface.easy_setopt(h, CURLOPT_PROXYTYPE, proxyType2CUrlProxyType[m_proxytype]);
+
+ const std::string hostport = m_proxyhost + StringUtils::Format(":{}", m_proxyport);
+ g_curlInterface.easy_setopt(h, CURLOPT_PROXY, hostport.c_str());
+
+ std::string userpass;
+
+ if (!m_proxyuser.empty() && !m_proxypassword.empty())
+ userpass = CURL::Encode(m_proxyuser) + ":" + CURL::Encode(m_proxypassword);
+
+ if (!userpass.empty())
+ g_curlInterface.easy_setopt(h, CURLOPT_PROXYUSERPWD, userpass.c_str());
+ }
+ if (m_customrequest.length() > 0)
+ g_curlInterface.easy_setopt(h, CURLOPT_CUSTOMREQUEST, m_customrequest.c_str());
+
+ if (m_connecttimeout == 0)
+ m_connecttimeout = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlconnecttimeout;
+
+ // set our timeouts, we abort connection after m_timeout, and reads after no data for m_timeout seconds
+ g_curlInterface.easy_setopt(h, CURLOPT_CONNECTTIMEOUT, m_connecttimeout);
+
+ // We abort in case we transfer less than 1byte/second
+ g_curlInterface.easy_setopt(h, CURLOPT_LOW_SPEED_LIMIT, 1);
+
+ if (m_lowspeedtime == 0)
+ m_lowspeedtime = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curllowspeedtime;
+
+ // Set the lowspeed time very low as it seems Curl takes much longer to detect a lowspeed condition
+ g_curlInterface.easy_setopt(h, CURLOPT_LOW_SPEED_TIME, m_lowspeedtime);
+
+ // enable tcp keepalive
+ if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlKeepAliveInterval > 0)
+ {
+ g_curlInterface.easy_setopt(h, CURLOPT_TCP_KEEPALIVE, 1L);
+ g_curlInterface.easy_setopt(
+ h, CURLOPT_TCP_KEEPIDLE,
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlKeepAliveInterval / 2);
+ g_curlInterface.easy_setopt(
+ h, CURLOPT_TCP_KEEPINTVL,
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlKeepAliveInterval);
+ }
+
+ // Setup allowed TLS/SSL ciphers. New versions of cURL may deprecate things that are still in use.
+ if (!m_cipherlist.empty())
+ g_curlInterface.easy_setopt(h, CURLOPT_SSL_CIPHER_LIST, m_cipherlist.c_str());
+
+ if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlDisableHTTP2)
+ g_curlInterface.easy_setopt(h, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ else
+ // enable HTTP2 support. default: CURL_HTTP_VERSION_1_1. Curl >= 7.62.0 defaults to CURL_HTTP_VERSION_2TLS
+ g_curlInterface.easy_setopt(h, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS);
+
+ // set CA bundle file
+ std::string caCert = CSpecialProtocol::TranslatePath(
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_caTrustFile);
+#ifdef TARGET_WINDOWS_STORE
+ // UWP Curl - Setting CURLOPT_CAINFO with a valid cacert file path is required for UWP
+ g_curlInterface.easy_setopt(h, CURLOPT_CAINFO, "system\\certs\\cacert.pem");
+#endif
+ if (!caCert.empty() && XFILE::CFile::Exists(caCert))
+ g_curlInterface.easy_setopt(h, CURLOPT_CAINFO, caCert.c_str());
+}
+
+void CCurlFile::SetRequestHeaders(CReadState* state)
+{
+ if(state->m_curlHeaderList)
+ {
+ g_curlInterface.slist_free_all(state->m_curlHeaderList);
+ state->m_curlHeaderList = NULL;
+ }
+
+ for (const auto& it : m_requestheaders)
+ {
+ std::string buffer = it.first + ": " + it.second;
+ state->m_curlHeaderList = g_curlInterface.slist_append(state->m_curlHeaderList, buffer.c_str());
+ }
+
+ // add user defined headers
+ if (state->m_easyHandle)
+ g_curlInterface.easy_setopt(state->m_easyHandle, CURLOPT_HTTPHEADER, state->m_curlHeaderList);
+}
+
+void CCurlFile::SetCorrectHeaders(CReadState* state)
+{
+ CHttpHeader& h = state->m_httpheader;
+ /* workaround for shoutcast server which doesn't set content type on standard mp3 */
+ if( h.GetMimeType().empty() )
+ {
+ if( !h.GetValue("icy-notice1").empty()
+ || !h.GetValue("icy-name").empty()
+ || !h.GetValue("icy-br").empty() )
+ h.AddParam("Content-Type", "audio/mpeg");
+ }
+
+ /* hack for google video */
+ if (StringUtils::EqualsNoCase(h.GetMimeType(),"text/html")
+ && !h.GetValue("Content-Disposition").empty() )
+ {
+ std::string strValue = h.GetValue("Content-Disposition");
+ if (strValue.find("filename=") != std::string::npos &&
+ strValue.find(".flv") != std::string::npos)
+ h.AddParam("Content-Type", "video/flv");
+ }
+}
+
+void CCurlFile::ParseAndCorrectUrl(CURL &url2)
+{
+ std::string strProtocol = url2.GetTranslatedProtocol();
+ url2.SetProtocol(strProtocol);
+
+ // lookup host in DNS cache
+ std::string resolvedHost;
+ if (CDNSNameCache::GetCached(url2.GetHostName(), resolvedHost))
+ {
+ struct curl_slist* tempCache;
+ int entryPort = url2.GetPort();
+
+ if (entryPort == 0)
+ {
+ if (strProtocol == "http")
+ entryPort = 80;
+ else if (strProtocol == "https")
+ entryPort = 443;
+ else if (strProtocol == "ftp")
+ entryPort = 21;
+ else if (strProtocol == "ftps")
+ entryPort = 990;
+ }
+
+ std::string entryString =
+ url2.GetHostName() + ":" + std::to_string(entryPort) + ":" + resolvedHost;
+ tempCache = g_curlInterface.slist_append(m_dnsCacheList, entryString.c_str());
+
+ if (tempCache)
+ m_dnsCacheList = tempCache;
+ }
+
+ if( url2.IsProtocol("ftp")
+ || url2.IsProtocol("ftps") )
+ {
+ // we was using url options for urls, keep the old code work and warning
+ if (!url2.GetOptions().empty())
+ {
+ CLog::Log(LOGWARNING,
+ "CCurlFile::{} - <{}> FTP url option is deprecated, please switch to use protocol "
+ "option (change "
+ "'?' to '|')",
+ __FUNCTION__, url2.GetRedacted());
+ url2.SetProtocolOptions(url2.GetOptions().substr(1));
+ /* ftp has no options */
+ url2.SetOptions("");
+ }
+
+ /* this is ugly, depending on where we get */
+ /* the link from, it may or may not be */
+ /* url encoded. if handed from ftpdirectory */
+ /* it won't be so let's handle that case */
+
+ std::string filename(url2.GetFileName());
+ std::vector<std::string> array;
+
+ // if server sent us the filename in non-utf8, we need send back with same encoding.
+ if (url2.GetProtocolOption("utf8") == "0")
+ g_charsetConverter.utf8ToStringCharset(filename);
+
+ //! @todo create a tokenizer that doesn't skip empty's
+ StringUtils::Tokenize(filename, array, "/");
+ filename.clear();
+ for (std::vector<std::string>::iterator it = array.begin(); it != array.end(); ++it)
+ {
+ if(it != array.begin())
+ filename += "/";
+
+ filename += CURL::Encode(*it);
+ }
+
+ /* make sure we keep slashes */
+ if(StringUtils::EndsWith(url2.GetFileName(), "/"))
+ filename += "/";
+
+ url2.SetFileName(filename);
+
+ m_ftpauth.clear();
+ if (url2.HasProtocolOption("auth"))
+ {
+ m_ftpauth = url2.GetProtocolOption("auth");
+ StringUtils::ToLower(m_ftpauth);
+ if(m_ftpauth.empty())
+ m_ftpauth = "any";
+ }
+ m_ftpport = "";
+ if (url2.HasProtocolOption("active"))
+ {
+ m_ftpport = url2.GetProtocolOption("active");
+ if(m_ftpport.empty())
+ m_ftpport = "-";
+ }
+ if (url2.HasProtocolOption("verifypeer"))
+ {
+ if (url2.GetProtocolOption("verifypeer") == "false")
+ m_verifyPeer = false;
+ }
+ m_ftppasvip = url2.HasProtocolOption("pasvip") && url2.GetProtocolOption("pasvip") != "0";
+ }
+ else if(url2.IsProtocol("http") ||
+ url2.IsProtocol("https"))
+ {
+ std::shared_ptr<CSettings> s = CServiceBroker::GetSettingsComponent()->GetSettings();
+ if (!s)
+ return;
+
+ if (!url2.IsLocalHost() &&
+ m_proxyhost.empty() &&
+ s->GetBool(CSettings::SETTING_NETWORK_USEHTTPPROXY) &&
+ !s->GetString(CSettings::SETTING_NETWORK_HTTPPROXYSERVER).empty() &&
+ s->GetInt(CSettings::SETTING_NETWORK_HTTPPROXYPORT) > 0)
+ {
+ m_proxytype = (ProxyType)s->GetInt(CSettings::SETTING_NETWORK_HTTPPROXYTYPE);
+ m_proxyhost = s->GetString(CSettings::SETTING_NETWORK_HTTPPROXYSERVER);
+ m_proxyport = s->GetInt(CSettings::SETTING_NETWORK_HTTPPROXYPORT);
+ m_proxyuser = s->GetString(CSettings::SETTING_NETWORK_HTTPPROXYUSERNAME);
+ m_proxypassword = s->GetString(CSettings::SETTING_NETWORK_HTTPPROXYPASSWORD);
+ CLog::LogFC(LOGDEBUG, LOGCURL, "<{}> Using proxy {}, type {}", url2.GetRedacted(),
+ m_proxyhost, proxyType2CUrlProxyType[m_proxytype]);
+ }
+
+ // get username and password
+ m_username = url2.GetUserName();
+ m_password = url2.GetPassWord();
+
+ // handle any protocol options
+ std::map<std::string, std::string> options;
+ url2.GetProtocolOptions(options);
+ if (!options.empty())
+ {
+ // set xbmc headers
+ for (const auto& it : options)
+ {
+ std::string name = it.first;
+ StringUtils::ToLower(name);
+ const std::string& value = it.second;
+
+ if (name == "auth")
+ {
+ m_httpauth = value;
+ StringUtils::ToLower(m_httpauth);
+ if(m_httpauth.empty())
+ m_httpauth = "any";
+ }
+ else if (name == "referer")
+ SetReferer(value);
+ else if (name == "user-agent")
+ SetUserAgent(value);
+ else if (name == "cookie")
+ SetCookie(value);
+ else if (name == "acceptencoding" || name == "encoding")
+ SetAcceptEncoding(value);
+ else if (name == "noshout" && value == "true")
+ m_skipshout = true;
+ else if (name == "seekable" && value == "0")
+ m_seekable = false;
+ else if (name == "accept-charset")
+ SetAcceptCharset(value);
+ else if (name == "sslcipherlist")
+ m_cipherlist = value;
+ else if (name == "connection-timeout")
+ m_connecttimeout = strtol(value.c_str(), NULL, 10);
+ else if (name == "failonerror")
+ m_failOnError = value == "true";
+ else if (name == "redirect-limit")
+ m_redirectlimit = strtol(value.c_str(), NULL, 10);
+ else if (name == "postdata")
+ {
+ m_postdata = Base64::Decode(value);
+ m_postdataset = true;
+ }
+ else if (name == "active-remote")// needed for DACP!
+ {
+ SetRequestHeader(it.first, value);
+ }
+ else if (name == "customrequest")
+ {
+ SetCustomRequest(value);
+ }
+ else if (name == "verifypeer")
+ {
+ if (value == "false")
+ m_verifyPeer = false;
+ }
+ else
+ {
+ if (name.length() > 0 && name[0] == '!')
+ {
+ SetRequestHeader(it.first.substr(1), value);
+ CLog::LogFC(LOGDEBUG, LOGCURL, "<{}> Adding custom header option '{}: ***********'",
+ url2.GetRedacted(), it.first.substr(1));
+ }
+ else
+ {
+ SetRequestHeader(it.first, value);
+ if (name == "authorization")
+ CLog::LogFC(LOGDEBUG, LOGCURL, "<{}> Adding custom header option '{}: ***********'",
+ url2.GetRedacted(), it.first);
+ else
+ CLog::LogFC(LOGDEBUG, LOGCURL, "<{}> Adding custom header option '{}: {}'",
+ url2.GetRedacted(), it.first, value);
+ }
+ }
+ }
+ }
+ }
+
+ // Unset the protocol options to have an url without protocol options
+ url2.SetProtocolOptions("");
+
+ if (m_username.length() > 0 && m_password.length() > 0)
+ m_url = url2.GetWithoutUserDetails();
+ else
+ m_url = url2.Get();
+}
+
+bool CCurlFile::Post(const std::string& strURL, const std::string& strPostData, std::string& strHTML)
+{
+ m_postdata = strPostData;
+ m_postdataset = true;
+ return Service(strURL, strHTML);
+}
+
+bool CCurlFile::Get(const std::string& strURL, std::string& strHTML)
+{
+ m_postdata = "";
+ m_postdataset = false;
+ return Service(strURL, strHTML);
+}
+
+bool CCurlFile::Service(const std::string& strURL, std::string& strHTML)
+{
+ const CURL pathToUrl(strURL);
+ if (Open(pathToUrl))
+ {
+ if (ReadData(strHTML))
+ {
+ Close();
+ return true;
+ }
+ }
+ Close();
+ return false;
+}
+
+bool CCurlFile::ReadData(std::string& strHTML)
+{
+ int size_read = 0;
+ strHTML = "";
+ char buffer[16384];
+ while( (size_read = Read(buffer, sizeof(buffer)-1) ) > 0 )
+ {
+ buffer[size_read] = 0;
+ strHTML.append(buffer, size_read);
+ }
+ if (m_state->m_cancelled)
+ return false;
+ return true;
+}
+
+bool CCurlFile::Download(const std::string& strURL, const std::string& strFileName, unsigned int* pdwSize)
+{
+ CLog::Log(LOGINFO, "CCurlFile::{} - {}->{}", __FUNCTION__, CURL::GetRedacted(strURL),
+ strFileName);
+
+ std::string strData;
+ if (!Get(strURL, strData))
+ return false;
+
+ XFILE::CFile file;
+ if (!file.OpenForWrite(strFileName, true))
+ {
+ CLog::Log(LOGERROR, "CCurlFile::{} - <{}> Unable to open file for write: {} ({})", __FUNCTION__,
+ CURL::GetRedacted(strURL), strFileName, GetLastError());
+ return false;
+ }
+ ssize_t written = 0;
+ if (!strData.empty())
+ written = file.Write(strData.c_str(), strData.size());
+
+ if (pdwSize != NULL)
+ *pdwSize = written > 0 ? written : 0;
+
+ return written == static_cast<ssize_t>(strData.size());
+}
+
+// Detect whether we are "online" or not! Very simple and dirty!
+bool CCurlFile::IsInternet()
+{
+ CURL url("http://www.msftconnecttest.com/connecttest.txt");
+ bool found = Exists(url);
+ if (!found)
+ {
+ // fallback
+ Close();
+ url.Parse("http://www.w3.org/");
+ found = Exists(url);
+ }
+ Close();
+
+ return found;
+}
+
+void CCurlFile::Cancel()
+{
+ m_state->m_cancelled = true;
+ while (m_opened)
+ KODI::TIME::Sleep(1ms);
+}
+
+void CCurlFile::Reset()
+{
+ m_state->m_cancelled = false;
+}
+
+void CCurlFile::SetProxy(const std::string &type, const std::string &host,
+ uint16_t port, const std::string &user, const std::string &password)
+{
+ m_proxytype = CCurlFile::PROXY_HTTP;
+ if (type == "http")
+ m_proxytype = CCurlFile::PROXY_HTTP;
+ else if (type == "https")
+ m_proxytype = CCurlFile::PROXY_HTTPS;
+ else if (type == "socks4")
+ m_proxytype = CCurlFile::PROXY_SOCKS4;
+ else if (type == "socks4a")
+ m_proxytype = CCurlFile::PROXY_SOCKS4A;
+ else if (type == "socks5")
+ m_proxytype = CCurlFile::PROXY_SOCKS5;
+ else if (type == "socks5-remote")
+ m_proxytype = CCurlFile::PROXY_SOCKS5_REMOTE;
+ else
+ CLog::Log(LOGERROR, "CCurFile::{} - <{}> Invalid proxy type \"{}\"", __FUNCTION__,
+ CURL::GetRedacted(m_url), type);
+ m_proxyhost = host;
+ m_proxyport = port;
+ m_proxyuser = user;
+ m_proxypassword = password;
+}
+
+bool CCurlFile::Open(const CURL& url)
+{
+ m_opened = true;
+ m_seekable = true;
+
+ CURL url2(url);
+ ParseAndCorrectUrl(url2);
+
+ std::string redactPath = CURL::GetRedacted(m_url);
+ CLog::Log(LOGDEBUG, "CurlFile::{} - <{}>", __FUNCTION__, redactPath);
+
+ assert(!(!m_state->m_easyHandle ^ !m_state->m_multiHandle));
+ if( m_state->m_easyHandle == NULL )
+ g_curlInterface.easy_acquire(url2.GetProtocol().c_str(),
+ url2.GetHostName().c_str(),
+ &m_state->m_easyHandle,
+ &m_state->m_multiHandle);
+
+ // setup common curl options
+ SetCommonOptions(m_state,
+ m_failOnError && !CServiceBroker::GetLogging().CanLogComponent(LOGCURL));
+ SetRequestHeaders(m_state);
+ m_state->m_sendRange = m_seekable;
+ m_state->m_bRetry = m_allowRetry;
+
+ m_httpresponse = m_state->Connect(m_bufferSize);
+
+ if (m_httpresponse <= 0 || (m_failOnError && m_httpresponse >= 400))
+ {
+ std::string error;
+ if (m_httpresponse >= 400 && CServiceBroker::GetLogging().CanLogComponent(LOGCURL))
+ {
+ error.resize(4096);
+ ReadString(&error[0], 4095);
+ }
+
+ CLog::Log(LOGERROR, "CCurlFile::{} - <{}> Failed with code {}:\n{}", __FUNCTION__,
+ redactPath, m_httpresponse, error);
+
+ return false;
+ }
+
+ SetCorrectHeaders(m_state);
+
+ // since we can't know the stream size up front if we're gzipped/deflated
+ // flag the stream with an unknown file size rather than the compressed
+ // file size.
+ if (!m_state->m_httpheader.GetValue("Content-Encoding").empty() && !StringUtils::EqualsNoCase(m_state->m_httpheader.GetValue("Content-Encoding"), "identity"))
+ m_state->m_fileSize = 0;
+
+ // check if this stream is a shoutcast stream. sometimes checking the protocol line is not enough so examine other headers as well.
+ // shoutcast streams should be handled by FileShoutcast.
+ if ((m_state->m_httpheader.GetProtoLine().substr(0, 3) == "ICY" || !m_state->m_httpheader.GetValue("icy-notice1").empty()
+ || !m_state->m_httpheader.GetValue("icy-name").empty()
+ || !m_state->m_httpheader.GetValue("icy-br").empty()) && !m_skipshout)
+ {
+ CLog::Log(LOGDEBUG, "CCurlFile::{} - <{}> File is a shoutcast stream. Re-opening", __FUNCTION__,
+ redactPath);
+ throw new CRedirectException(new CShoutcastFile);
+ }
+
+ m_multisession = false;
+ if(url2.IsProtocol("http") || url2.IsProtocol("https"))
+ {
+ m_multisession = true;
+ if(m_state->m_httpheader.GetValue("Server").find("Portable SDK for UPnP devices") != std::string::npos)
+ {
+ CLog::Log(LOGWARNING,
+ "CCurlFile::{} - <{}> Disabling multi session due to broken libupnp server",
+ __FUNCTION__, redactPath);
+ m_multisession = false;
+ }
+ }
+
+ if(StringUtils::EqualsNoCase(m_state->m_httpheader.GetValue("Transfer-Encoding"), "chunked"))
+ m_state->m_fileSize = 0;
+
+ if(m_state->m_fileSize <= 0)
+ m_seekable = false;
+ if (m_seekable)
+ {
+ if(url2.IsProtocol("http")
+ || url2.IsProtocol("https"))
+ {
+ // if server says explicitly it can't seek, respect that
+ if(StringUtils::EqualsNoCase(m_state->m_httpheader.GetValue("Accept-Ranges"),"none"))
+ m_seekable = false;
+ }
+ }
+
+ std::string efurl = GetInfoString(CURLINFO_EFFECTIVE_URL);
+ if (!efurl.empty())
+ {
+ if (m_url != efurl)
+ {
+ std::string redactEfpath = CURL::GetRedacted(efurl);
+ CLog::Log(LOGDEBUG, "CCurlFile::{} - <{}> Effective URL is {}", __FUNCTION__, redactPath,
+ redactEfpath);
+ }
+ m_url = efurl;
+ }
+
+ return true;
+}
+
+bool CCurlFile::OpenForWrite(const CURL& url, bool bOverWrite)
+{
+ if(m_opened)
+ return false;
+
+ if (Exists(url) && !bOverWrite)
+ return false;
+
+ CURL url2(url);
+ ParseAndCorrectUrl(url2);
+
+ CLog::Log(LOGDEBUG, "CCurlFile::{} - Opening {}", __FUNCTION__, CURL::GetRedacted(m_url));
+
+ assert(m_state->m_easyHandle == NULL);
+ g_curlInterface.easy_acquire(url2.GetProtocol().c_str(),
+ url2.GetHostName().c_str(),
+ &m_state->m_easyHandle,
+ &m_state->m_multiHandle);
+
+ // setup common curl options
+ SetCommonOptions(m_state);
+ SetRequestHeaders(m_state);
+
+ std::string efurl = GetInfoString(CURLINFO_EFFECTIVE_URL);
+ if (!efurl.empty())
+ m_url = efurl;
+
+ m_opened = true;
+ m_forWrite = true;
+ m_inError = false;
+ m_writeOffset = 0;
+
+ assert(m_state->m_multiHandle);
+
+ g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_UPLOAD, 1);
+
+ g_curlInterface.multi_add_handle(m_state->m_multiHandle, m_state->m_easyHandle);
+
+ m_state->SetReadBuffer(NULL, 0);
+
+ return true;
+}
+
+ssize_t CCurlFile::Write(const void* lpBuf, size_t uiBufSize)
+{
+ if (!(m_opened && m_forWrite) || m_inError)
+ return -1;
+
+ assert(m_state->m_multiHandle);
+
+ m_state->SetReadBuffer(lpBuf, uiBufSize);
+ m_state->m_isPaused = false;
+ g_curlInterface.easy_pause(m_state->m_easyHandle, CURLPAUSE_CONT);
+
+ CURLMcode result = CURLM_OK;
+
+ m_stillRunning = 1;
+ while (m_stillRunning && !m_state->m_isPaused)
+ {
+ while ((result = g_curlInterface.multi_perform(m_state->m_multiHandle, &m_stillRunning)) == CURLM_CALL_MULTI_PERFORM);
+
+ if (!m_stillRunning)
+ break;
+
+ if (result != CURLM_OK)
+ {
+ long code;
+ if(g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_RESPONSE_CODE, &code) == CURLE_OK )
+ CLog::Log(LOGERROR, "CCurlFile::{} - <{}> Unable to write curl resource with code {}",
+ __FUNCTION__, CURL::GetRedacted(m_url), code);
+ m_inError = true;
+ return -1;
+ }
+ }
+
+ m_writeOffset += m_state->m_filePos;
+ return m_state->m_filePos;
+}
+
+bool CCurlFile::CReadState::ReadString(char *szLine, int iLineLength)
+{
+ unsigned int want = (unsigned int)iLineLength;
+
+ if((m_fileSize == 0 || m_filePos < m_fileSize) && FillBuffer(want) != FILLBUFFER_OK)
+ return false;
+
+ // ensure only available data is considered
+ want = std::min(m_buffer.getMaxReadSize(), want);
+
+ /* check if we finished prematurely */
+ if (!m_stillRunning && (m_fileSize == 0 || m_filePos != m_fileSize) && !want)
+ {
+ if (m_fileSize != 0)
+ CLog::Log(
+ LOGWARNING,
+ "CCurlFile::{} - ({}) Transfer ended before entire file was retrieved pos {}, size {}",
+ __FUNCTION__, fmt::ptr(this), m_filePos, m_fileSize);
+
+ return false;
+ }
+
+ char* pLine = szLine;
+ do
+ {
+ if (!m_buffer.ReadData(pLine, 1))
+ break;
+
+ pLine++;
+ } while (((pLine - 1)[0] != '\n') && ((unsigned int)(pLine - szLine) < want));
+ pLine[0] = 0;
+ m_filePos += (pLine - szLine);
+ return (pLine - szLine) > 0;
+}
+
+bool CCurlFile::ReOpen(const CURL& url)
+{
+ Close();
+ return Open(url);
+}
+
+bool CCurlFile::Exists(const CURL& url)
+{
+ // if file is already running, get info from it
+ if( m_opened )
+ {
+ CLog::Log(LOGWARNING, "CCurlFile::{} - <{}> Exist called on open file", __FUNCTION__,
+ url.GetRedacted());
+ return true;
+ }
+
+ CURL url2(url);
+ ParseAndCorrectUrl(url2);
+
+ assert(m_state->m_easyHandle == NULL);
+ g_curlInterface.easy_acquire(url2.GetProtocol().c_str(),
+ url2.GetHostName().c_str(),
+ &m_state->m_easyHandle, NULL);
+
+ SetCommonOptions(m_state);
+ SetRequestHeaders(m_state);
+ g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_TIMEOUT, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlconnecttimeout);
+ g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_NOBODY, 1);
+ g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_WRITEDATA, NULL); /* will cause write failure*/
+
+ if(url2.IsProtocol("ftp") || url2.IsProtocol("ftps"))
+ {
+ g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_FILETIME, 1);
+ // nocwd is less standard, will return empty list for non-existed remote dir on some ftp server, avoid it.
+ if (StringUtils::EndsWith(url2.GetFileName(), "/"))
+ g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_SINGLECWD);
+ else
+ g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_NOCWD);
+ }
+
+ CURLcode result = g_curlInterface.easy_perform(m_state->m_easyHandle);
+
+ if (result == CURLE_WRITE_ERROR || result == CURLE_OK)
+ {
+ g_curlInterface.easy_release(&m_state->m_easyHandle, NULL);
+ return true;
+ }
+
+ if (result == CURLE_HTTP_RETURNED_ERROR)
+ {
+ long code;
+ if(g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_RESPONSE_CODE, &code) == CURLE_OK && code != 404 )
+ {
+ if (code == 405)
+ {
+ // If we get a Method Not Allowed response, retry with a GET Request
+ g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_NOBODY, 0);
+
+ g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_FILETIME, 1);
+ g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_XFERINFOFUNCTION, transfer_abort_callback);
+ g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_NOPROGRESS, 0);
+
+ curl_slist *list = NULL;
+ list = g_curlInterface.slist_append(list, "Range: bytes=0-1"); /* try to only request 1 byte */
+ g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_HTTPHEADER, list);
+
+ CURLcode result = g_curlInterface.easy_perform(m_state->m_easyHandle);
+ g_curlInterface.slist_free_all(list);
+
+ if (result == CURLE_WRITE_ERROR || result == CURLE_OK)
+ {
+ g_curlInterface.easy_release(&m_state->m_easyHandle, NULL);
+ return true;
+ }
+
+ if (result == CURLE_HTTP_RETURNED_ERROR)
+ {
+ if (g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_RESPONSE_CODE, &code) == CURLE_OK && code != 404 )
+ CLog::Log(LOGERROR, "CCurlFile::{} - <{}> Failed: HTTP returned error {}", __FUNCTION__,
+ url.GetRedacted(), code);
+ }
+ }
+ else
+ CLog::Log(LOGERROR, "CCurlFile::{} - <{}> Failed: HTTP returned error {}", __FUNCTION__,
+ url.GetRedacted(), code);
+ }
+ }
+ else if (result != CURLE_REMOTE_FILE_NOT_FOUND && result != CURLE_FTP_COULDNT_RETR_FILE)
+ {
+ CLog::Log(LOGERROR, "CCurlFile::{} - <{}> Failed: {}({})", __FUNCTION__, url.GetRedacted(),
+ g_curlInterface.easy_strerror(result), result);
+ }
+
+ errno = ENOENT;
+ g_curlInterface.easy_release(&m_state->m_easyHandle, NULL);
+ return false;
+}
+
+int64_t CCurlFile::Seek(int64_t iFilePosition, int iWhence)
+{
+ int64_t nextPos = m_state->m_filePos;
+
+ if(!m_seekable)
+ return -1;
+
+ switch(iWhence)
+ {
+ case SEEK_SET:
+ nextPos = iFilePosition;
+ break;
+ case SEEK_CUR:
+ nextPos += iFilePosition;
+ break;
+ case SEEK_END:
+ if (m_state->m_fileSize)
+ nextPos = m_state->m_fileSize + iFilePosition;
+ else
+ return -1;
+ break;
+ default:
+ return -1;
+ }
+
+ // We can't seek beyond EOF
+ if (m_state->m_fileSize && nextPos > m_state->m_fileSize) return -1;
+
+ if(m_state->Seek(nextPos))
+ return nextPos;
+
+ if (m_multisession)
+ {
+ if (!m_oldState)
+ {
+ CURL url(m_url);
+ m_oldState = m_state;
+ m_state = new CReadState();
+ m_state->m_fileSize = m_oldState->m_fileSize;
+ g_curlInterface.easy_acquire(url.GetProtocol().c_str(),
+ url.GetHostName().c_str(),
+ &m_state->m_easyHandle,
+ &m_state->m_multiHandle );
+ }
+ else
+ {
+ CReadState *tmp;
+ tmp = m_state;
+ m_state = m_oldState;
+ m_oldState = tmp;
+
+ if (m_state->Seek(nextPos))
+ return nextPos;
+
+ m_state->Disconnect();
+ }
+ }
+ else
+ m_state->Disconnect();
+
+ // re-setup common curl options
+ SetCommonOptions(m_state);
+
+ /* caller might have changed some headers (needed for daap)*/
+ //! @todo daap is gone. is this needed for something else?
+ SetRequestHeaders(m_state);
+
+ m_state->m_filePos = nextPos;
+ m_state->m_sendRange = true;
+ m_state->m_bRetry = m_allowRetry;
+
+ long response = m_state->Connect(m_bufferSize);
+ if(response < 0 && (m_state->m_fileSize == 0 || m_state->m_fileSize != m_state->m_filePos))
+ {
+ if(m_multisession)
+ {
+ if (m_oldState)
+ {
+ delete m_state;
+ m_state = m_oldState;
+ m_oldState = NULL;
+ }
+ // Retry without multisession
+ m_multisession = false;
+ return Seek(iFilePosition, iWhence);
+ }
+ else
+ {
+ m_seekable = false;
+ return -1;
+ }
+ }
+
+ SetCorrectHeaders(m_state);
+
+ return m_state->m_filePos;
+}
+
+int64_t CCurlFile::GetLength()
+{
+ if (!m_opened) return 0;
+ return m_state->m_fileSize;
+}
+
+int64_t CCurlFile::GetPosition()
+{
+ if (!m_opened) return 0;
+ return m_state->m_filePos;
+}
+
+int CCurlFile::Stat(const CURL& url, struct __stat64* buffer)
+{
+ // if file is already running, get info from it
+ if( m_opened )
+ {
+ CLog::Log(LOGWARNING, "CCurlFile::{} - <{}> Stat called on open file", __FUNCTION__,
+ url.GetRedacted());
+ if (buffer)
+ {
+ *buffer = {};
+ buffer->st_size = GetLength();
+ buffer->st_mode = _S_IFREG;
+ }
+ return 0;
+ }
+
+ CURL url2(url);
+ ParseAndCorrectUrl(url2);
+
+ assert(m_state->m_easyHandle == NULL);
+ g_curlInterface.easy_acquire(url2.GetProtocol().c_str(),
+ url2.GetHostName().c_str(),
+ &m_state->m_easyHandle, NULL);
+
+ SetCommonOptions(m_state);
+ SetRequestHeaders(m_state);
+ g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_TIMEOUT, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlconnecttimeout);
+ g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_NOBODY, 1);
+ g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_FILETIME , 1);
+
+ if(url2.IsProtocol("ftp"))
+ {
+ // nocwd is less standard, will return empty list for non-existed remote dir on some ftp server, avoid it.
+ if (StringUtils::EndsWith(url2.GetFileName(), "/"))
+ g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_SINGLECWD);
+ else
+ g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_NOCWD);
+ }
+
+ CURLcode result = g_curlInterface.easy_perform(m_state->m_easyHandle);
+
+ if(result == CURLE_HTTP_RETURNED_ERROR)
+ {
+ long code;
+ if(g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_RESPONSE_CODE, &code) == CURLE_OK && code == 404 )
+ {
+ g_curlInterface.easy_release(&m_state->m_easyHandle, NULL);
+ errno = ENOENT;
+ return -1;
+ }
+ }
+
+ if(result == CURLE_GOT_NOTHING
+ || result == CURLE_HTTP_RETURNED_ERROR
+ || result == CURLE_RECV_ERROR /* some silly shoutcast servers */ )
+ {
+ /* some http servers and shoutcast servers don't give us any data on a head request */
+ /* request normal and just bail out via progress meter callback after we received data */
+ /* somehow curl doesn't reset CURLOPT_NOBODY properly so reset everything */
+ SetCommonOptions(m_state);
+ SetRequestHeaders(m_state);
+ g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_TIMEOUT, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlconnecttimeout);
+ g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_FILETIME, 1);
+#if LIBCURL_VERSION_NUM >= 0x072000 // 0.7.32
+ g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_XFERINFOFUNCTION, transfer_abort_callback);
+#else
+ g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_PROGRESSFUNCTION, transfer_abort_callback);
+#endif
+ g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_NOPROGRESS, 0);
+
+ result = g_curlInterface.easy_perform(m_state->m_easyHandle);
+
+ }
+
+ if( result != CURLE_ABORTED_BY_CALLBACK && result != CURLE_OK )
+ {
+ g_curlInterface.easy_release(&m_state->m_easyHandle, NULL);
+ errno = ENOENT;
+ CLog::Log(LOGERROR, "CCurlFile::{} - <{}> Failed: {}({})", __FUNCTION__, url.GetRedacted(),
+ g_curlInterface.easy_strerror(result), result);
+ return -1;
+ }
+
+ double length;
+ result = g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length);
+ if (result != CURLE_OK || length < 0.0)
+ {
+ if (url.IsProtocol("ftp"))
+ {
+ g_curlInterface.easy_release(&m_state->m_easyHandle, NULL);
+ CLog::Log(LOGINFO, "CCurlFile::{} - <{}> Content length failed: {}({})", __FUNCTION__,
+ url.GetRedacted(), g_curlInterface.easy_strerror(result), result);
+ errno = ENOENT;
+ return -1;
+ }
+ else
+ length = 0.0;
+ }
+
+ SetCorrectHeaders(m_state);
+
+ if (buffer)
+ {
+ *buffer = {};
+ buffer->st_size = static_cast<int64_t>(length);
+
+ // Note: CURLINFO_CONTENT_TYPE returns the last received content-type response header value.
+ // In case there is authentication required there might be multiple requests involved and if
+ // the last request which actually returns the data does not return a content-type header, but
+ // one of the preceding requests, CURLINFO_CONTENT_TYPE returns not the content type of the
+ // actual resource requested! m_state contains only the values of the last request, which is
+ // what we want here.
+ const std::string mimeType = m_state->m_httpheader.GetMimeType();
+ if (mimeType.find("text/html") != std::string::npos) // consider html files directories
+ buffer->st_mode = _S_IFDIR;
+ else
+ buffer->st_mode = _S_IFREG;
+
+ long filetime;
+ result = g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_FILETIME, &filetime);
+ if (result != CURLE_OK)
+ {
+ CLog::Log(LOGINFO, "CCurlFile::{} - <{}> Filetime failed: {}({})", __FUNCTION__,
+ url.GetRedacted(), g_curlInterface.easy_strerror(result), result);
+ }
+ else
+ {
+ if (filetime != -1)
+ buffer->st_mtime = filetime;
+ }
+ }
+ g_curlInterface.easy_release(&m_state->m_easyHandle, NULL);
+ return 0;
+}
+
+ssize_t CCurlFile::CReadState::Read(void* lpBuf, size_t uiBufSize)
+{
+ /* only request 1 byte, for truncated reads (only if not eof) */
+ if (m_fileSize == 0 || m_filePos < m_fileSize)
+ {
+ int8_t result = FillBuffer(1);
+ if (result == FILLBUFFER_FAIL)
+ return -1; // Fatal error
+
+ if (result == FILLBUFFER_NO_DATA)
+ return 0;
+ }
+
+ /* ensure only available data is considered */
+ unsigned int want = std::min<unsigned int>(m_buffer.getMaxReadSize(), uiBufSize);
+
+ /* xfer data to caller */
+ if (m_buffer.ReadData((char *)lpBuf, want))
+ {
+ m_filePos += want;
+ return want;
+ }
+
+ /* check if we finished prematurely */
+ if (!m_stillRunning && (m_fileSize == 0 || m_filePos != m_fileSize))
+ {
+ CLog::Log(LOGWARNING,
+ "CCurlFile::CReadState::{} - ({}) Transfer ended before entire file was retrieved "
+ "pos {}, size {}",
+ __FUNCTION__, fmt::ptr(this), m_filePos, m_fileSize);
+ return -1;
+ }
+
+ return 0;
+}
+
+/* use to attempt to fill the read buffer up to requested number of bytes */
+int8_t CCurlFile::CReadState::FillBuffer(unsigned int want)
+{
+ int retry = 0;
+ fd_set fdread;
+ fd_set fdwrite;
+ fd_set fdexcep;
+
+ // only attempt to fill buffer if transactions still running and buffer
+ // doesn't exceed required size already
+ while (m_buffer.getMaxReadSize() < want && m_buffer.getMaxWriteSize() > 0 )
+ {
+ if (m_cancelled)
+ return FILLBUFFER_NO_DATA;
+
+ /* if there is data in overflow buffer, try to use that first */
+ if (m_overflowSize)
+ {
+ unsigned amount = std::min(m_buffer.getMaxWriteSize(), m_overflowSize);
+ m_buffer.WriteData(m_overflowBuffer, amount);
+
+ if (amount < m_overflowSize)
+ memmove(m_overflowBuffer, m_overflowBuffer + amount, m_overflowSize - amount);
+
+ m_overflowSize -= amount;
+ // Shrink memory:
+ m_overflowBuffer = (char*)realloc_simple(m_overflowBuffer, m_overflowSize);
+ continue;
+ }
+
+ CURLMcode result = g_curlInterface.multi_perform(m_multiHandle, &m_stillRunning);
+ if (!m_stillRunning)
+ {
+ if (result == CURLM_OK)
+ {
+ /* if we still have stuff in buffer, we are fine */
+ if (m_buffer.getMaxReadSize())
+ return FILLBUFFER_OK;
+
+ // check for errors
+ int msgs;
+ CURLMsg* msg;
+ bool bRetryNow = true;
+ bool bError = false;
+ while ((msg = g_curlInterface.multi_info_read(m_multiHandle, &msgs)))
+ {
+ if (msg->msg == CURLMSG_DONE)
+ {
+ if (msg->data.result == CURLE_OK)
+ return FILLBUFFER_OK;
+
+ long httpCode = 0;
+ if (msg->data.result == CURLE_HTTP_RETURNED_ERROR)
+ {
+ g_curlInterface.easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &httpCode);
+
+ // Don't log 404 not-found errors to prevent log-spam
+ if (httpCode != 404)
+ CLog::Log(LOGERROR,
+ "CCurlFile::CReadState::{} - ({}) Failed: HTTP returned code {}",
+ __FUNCTION__, fmt::ptr(this), httpCode);
+ }
+ else
+ {
+ CLog::Log(LOGERROR, "CCurlFile::CReadState::{} - ({}) Failed: {}({})", __FUNCTION__,
+ fmt::ptr(this), g_curlInterface.easy_strerror(msg->data.result),
+ msg->data.result);
+ }
+
+ if ( (msg->data.result == CURLE_OPERATION_TIMEDOUT ||
+ msg->data.result == CURLE_PARTIAL_FILE ||
+ msg->data.result == CURLE_COULDNT_CONNECT ||
+ msg->data.result == CURLE_RECV_ERROR) &&
+ !m_bFirstLoop)
+ {
+ bRetryNow = false; // Leave it to caller whether the operation is retried
+ bError = true;
+ }
+ else if ( (msg->data.result == CURLE_HTTP_RANGE_ERROR ||
+ httpCode == 416 /* = Requested Range Not Satisfiable */ ||
+ httpCode == 406 /* = Not Acceptable (fixes issues with non compliant HDHomerun servers */) &&
+ m_bFirstLoop &&
+ m_filePos == 0 &&
+ m_sendRange)
+ {
+ // If server returns a (possible) range error, disable range and retry (handled below)
+ bRetryNow = true;
+ bError = true;
+ m_sendRange = false;
+ }
+ else
+ {
+ // For all other errors, abort the operation
+ return FILLBUFFER_FAIL;
+ }
+ }
+ }
+
+ // Check for an actual error, if not, just return no-data
+ if (!bError && !m_bLastError)
+ return FILLBUFFER_NO_DATA;
+
+ // Close handle
+ if (m_multiHandle && m_easyHandle)
+ g_curlInterface.multi_remove_handle(m_multiHandle, m_easyHandle);
+
+ // Reset all the stuff like we would in Disconnect()
+ m_buffer.Clear();
+ free(m_overflowBuffer);
+ m_overflowBuffer = NULL;
+ m_overflowSize = 0;
+ m_bLastError = true; // Flag error for the next run
+
+ // Retry immediately or leave it up to the caller?
+ if ((m_bRetry && retry < CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlretries) || (bRetryNow && retry == 0))
+ {
+ retry++;
+
+ // Connect + seek to current position (again)
+ SetResume();
+ g_curlInterface.multi_add_handle(m_multiHandle, m_easyHandle);
+
+ CLog::Log(LOGWARNING, "CCurlFile::CReadState::{} - ({}) Reconnect, (re)try {}",
+ __FUNCTION__, fmt::ptr(this), retry);
+
+ // Return to the beginning of the loop:
+ continue;
+ }
+
+ return FILLBUFFER_NO_DATA; // We failed but flag no data to caller, so it can retry the operation
+ }
+ return FILLBUFFER_FAIL;
+ }
+
+ // We've finished out first loop
+ if(m_bFirstLoop && m_buffer.getMaxReadSize() > 0)
+ m_bFirstLoop = false;
+
+ // No error this run
+ m_bLastError = false;
+
+ switch (result)
+ {
+ case CURLM_OK:
+ {
+ int maxfd = -1;
+ FD_ZERO(&fdread);
+ FD_ZERO(&fdwrite);
+ FD_ZERO(&fdexcep);
+
+ // get file descriptors from the transfers
+ g_curlInterface.multi_fdset(m_multiHandle, &fdread, &fdwrite, &fdexcep, &maxfd);
+
+ long timeout = 0;
+ if (CURLM_OK != g_curlInterface.multi_timeout(m_multiHandle, &timeout) || timeout == -1 || timeout < 200)
+ timeout = 200;
+
+ XbmcThreads::EndTime<> endTime{std::chrono::milliseconds(timeout)};
+ int rc;
+
+ do
+ {
+ /* On success the value of maxfd is guaranteed to be >= -1. We call
+ * select(maxfd + 1, ...); specially in case of (maxfd == -1) there are
+ * no fds ready yet so we call select(0, ...) --or Sleep() on Windows--
+ * to sleep 100ms, which is the minimum suggested value in the
+ * curl_multi_fdset() doc.
+ */
+ if (maxfd == -1)
+ {
+#ifdef TARGET_WINDOWS
+ /* Windows does not support using select() for sleeping without a dummy
+ * socket. Instead use Windows' Sleep() and sleep for 100ms which is the
+ * minimum suggested value in the curl_multi_fdset() doc.
+ */
+ KODI::TIME::Sleep(100ms);
+ rc = 0;
+#else
+ /* Portable sleep for platforms other than Windows. */
+ struct timeval wait = { 0, 100 * 1000 }; /* 100ms */
+ rc = select(0, NULL, NULL, NULL, &wait);
+#endif
+ }
+ else
+ {
+ unsigned int time_left = endTime.GetTimeLeft().count();
+ struct timeval wait = { (int)time_left / 1000, ((int)time_left % 1000) * 1000 };
+ rc = select(maxfd + 1, &fdread, &fdwrite, &fdexcep, &wait);
+ }
+#ifdef TARGET_WINDOWS
+ } while(rc == SOCKET_ERROR && WSAGetLastError() == WSAEINTR);
+#else
+ } while(rc == SOCKET_ERROR && errno == EINTR);
+#endif
+
+ if(rc == SOCKET_ERROR)
+ {
+#ifdef TARGET_WINDOWS
+ char buf[256];
+ strerror_s(buf, 256, WSAGetLastError());
+ CLog::Log(LOGERROR, "CCurlFile::CReadState::{} - ({}) Failed with socket error:{}",
+ __FUNCTION__, fmt::ptr(this), buf);
+#else
+ char const * str = strerror(errno);
+ CLog::Log(LOGERROR, "CCurlFile::CReadState::{} - ({}) Failed with socket error:{}",
+ __FUNCTION__, fmt::ptr(this), str);
+#endif
+
+ return FILLBUFFER_FAIL;
+ }
+ }
+ break;
+ case CURLM_CALL_MULTI_PERFORM:
+ {
+ // we don't keep calling here as that can easily overwrite our buffer which we want to avoid
+ // docs says we should call it soon after, but as long as we are reading data somewhere
+ // this aught to be soon enough. should stay in socket otherwise
+ continue;
+ }
+ break;
+ default:
+ {
+ CLog::Log(LOGERROR,
+ "CCurlFile::CReadState::{} - ({}) Multi perform failed with code {}, aborting",
+ __FUNCTION__, fmt::ptr(this), result);
+ return FILLBUFFER_FAIL;
+ }
+ break;
+ }
+ }
+ return FILLBUFFER_OK;
+}
+
+void CCurlFile::CReadState::SetReadBuffer(const void* lpBuf, int64_t uiBufSize)
+{
+ m_readBuffer = const_cast<char*>((const char*)lpBuf);
+ m_fileSize = uiBufSize;
+ m_filePos = 0;
+}
+
+void CCurlFile::ClearRequestHeaders()
+{
+ m_requestheaders.clear();
+}
+
+void CCurlFile::SetRequestHeader(const std::string& header, const std::string& value)
+{
+ m_requestheaders[header] = value;
+}
+
+void CCurlFile::SetRequestHeader(const std::string& header, long value)
+{
+ m_requestheaders[header] = std::to_string(value);
+}
+
+std::string CCurlFile::GetURL(void)
+{
+ return m_url;
+}
+
+std::string CCurlFile::GetRedirectURL()
+{
+ return GetInfoString(CURLINFO_REDIRECT_URL);
+}
+
+std::string CCurlFile::GetInfoString(int infoType)
+{
+ char* info{};
+ CURLcode result = g_curlInterface.easy_getinfo(m_state->m_easyHandle, static_cast<CURLINFO> (infoType), &info);
+ if (result != CURLE_OK)
+ {
+ CLog::Log(LOGERROR,
+ "CCurlFile::{} - <{}> Info string request for type {} failed with result code {}",
+ __FUNCTION__, CURL::GetRedacted(m_url), infoType, result);
+ return "";
+ }
+ return (info ? info : "");
+}
+
+/* STATIC FUNCTIONS */
+bool CCurlFile::GetHttpHeader(const CURL &url, CHttpHeader &headers)
+{
+ try
+ {
+ CCurlFile file;
+ if(file.Stat(url, NULL) == 0)
+ {
+ headers = file.GetHttpHeader();
+ return true;
+ }
+ return false;
+ }
+ catch(...)
+ {
+ CLog::Log(LOGERROR, "CCurlFile::{} - <{}> Exception thrown while trying to retrieve header",
+ __FUNCTION__, url.GetRedacted());
+ return false;
+ }
+}
+
+bool CCurlFile::GetMimeType(const CURL &url, std::string &content, const std::string &useragent)
+{
+ CCurlFile file;
+ if (!useragent.empty())
+ file.SetUserAgent(useragent);
+
+ struct __stat64 buffer;
+ std::string redactUrl = url.GetRedacted();
+ if( file.Stat(url, &buffer) == 0 )
+ {
+ if (buffer.st_mode == _S_IFDIR)
+ content = "x-directory/normal";
+ else
+ content = file.GetProperty(XFILE::FILE_PROPERTY_MIME_TYPE);
+ CLog::Log(LOGDEBUG, "CCurlFile::{} - <{}> -> {}", __FUNCTION__, redactUrl, content);
+ return true;
+ }
+ CLog::Log(LOGDEBUG, "CCurlFile::{} - <{}> -> failed", __FUNCTION__, redactUrl);
+ content.clear();
+ return false;
+}
+
+bool CCurlFile::GetContentType(const CURL &url, std::string &content, const std::string &useragent)
+{
+ CCurlFile file;
+ if (!useragent.empty())
+ file.SetUserAgent(useragent);
+
+ struct __stat64 buffer;
+ std::string redactUrl = url.GetRedacted();
+ if (file.Stat(url, &buffer) == 0)
+ {
+ if (buffer.st_mode == _S_IFDIR)
+ content = "x-directory/normal";
+ else
+ content = file.GetProperty(XFILE::FILE_PROPERTY_CONTENT_TYPE, "");
+ CLog::Log(LOGDEBUG, "CCurlFile::{} - <{}> -> {}", __FUNCTION__, redactUrl, content);
+ return true;
+ }
+ CLog::Log(LOGDEBUG, "CCurlFile::{} - <{}> -> failed", __FUNCTION__, redactUrl);
+ content.clear();
+ return false;
+}
+
+bool CCurlFile::GetCookies(const CURL &url, std::string &cookies)
+{
+ std::string cookiesStr;
+ curl_slist* curlCookies;
+ CURL_HANDLE* easyHandle;
+ CURLM* multiHandle;
+
+ // get the cookies list
+ g_curlInterface.easy_acquire(url.GetProtocol().c_str(),
+ url.GetHostName().c_str(),
+ &easyHandle, &multiHandle);
+ if (CURLE_OK == g_curlInterface.easy_getinfo(easyHandle, CURLINFO_COOKIELIST, &curlCookies))
+ {
+ // iterate over each cookie and format it into an RFC 2109 formatted Set-Cookie string
+ curl_slist* curlCookieIter = curlCookies;
+ while(curlCookieIter)
+ {
+ // tokenize the CURL cookie string
+ std::vector<std::string> valuesVec;
+ StringUtils::Tokenize(curlCookieIter->data, valuesVec, "\t");
+
+ // ensure the length is valid
+ if (valuesVec.size() < 7)
+ {
+ CLog::Log(LOGERROR, "CCurlFile::{} - <{}> Invalid cookie: '{}'", __FUNCTION__,
+ url.GetRedacted(), curlCookieIter->data);
+ curlCookieIter = curlCookieIter->next;
+ continue;
+ }
+
+ // create a http-header formatted cookie string
+ std::string cookieStr = valuesVec[5] + "=" + valuesVec[6] +
+ "; path=" + valuesVec[2] +
+ "; domain=" + valuesVec[0];
+
+ // append this cookie to the string containing all cookies
+ if (!cookiesStr.empty())
+ cookiesStr += "\n";
+ cookiesStr += cookieStr;
+
+ // move on to the next cookie
+ curlCookieIter = curlCookieIter->next;
+ }
+
+ // free the curl cookies
+ g_curlInterface.slist_free_all(curlCookies);
+
+ // release our handles
+ g_curlInterface.easy_release(&easyHandle, &multiHandle);
+
+ // if we have a non-empty cookie string, return it
+ if (!cookiesStr.empty())
+ {
+ cookies = cookiesStr;
+ return true;
+ }
+ }
+
+ // no cookies to return
+ return false;
+}
+
+int CCurlFile::IoControl(EIoControl request, void* param)
+{
+ if (request == IOCTRL_SEEK_POSSIBLE)
+ return m_seekable ? 1 : 0;
+
+ if (request == IOCTRL_SET_RETRY)
+ {
+ m_allowRetry = *(bool*) param;
+ return 0;
+ }
+
+ return -1;
+}
+
+const std::string CCurlFile::GetProperty(XFILE::FileProperty type, const std::string &name) const
+{
+ switch (type)
+ {
+ case FILE_PROPERTY_RESPONSE_PROTOCOL:
+ return m_state->m_httpheader.GetProtoLine();
+ case FILE_PROPERTY_RESPONSE_HEADER:
+ return m_state->m_httpheader.GetValue(name);
+ case FILE_PROPERTY_CONTENT_TYPE:
+ return m_state->m_httpheader.GetValue("content-type");
+ case FILE_PROPERTY_CONTENT_CHARSET:
+ return m_state->m_httpheader.GetCharset();
+ case FILE_PROPERTY_MIME_TYPE:
+ return m_state->m_httpheader.GetMimeType();
+ case FILE_PROPERTY_EFFECTIVE_URL:
+ {
+ char *url = nullptr;
+ g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_EFFECTIVE_URL, &url);
+ return url ? url : "";
+ }
+ default:
+ return "";
+ }
+}
+
+const std::vector<std::string> CCurlFile::GetPropertyValues(XFILE::FileProperty type, const std::string &name) const
+{
+ if (type == FILE_PROPERTY_RESPONSE_HEADER)
+ {
+ return m_state->m_httpheader.GetValues(name);
+ }
+ std::vector<std::string> values;
+ std::string value = GetProperty(type, name);
+ if (!value.empty())
+ {
+ values.emplace_back(value);
+ }
+ return values;
+}
+
+double CCurlFile::GetDownloadSpeed()
+{
+#if LIBCURL_VERSION_NUM >= 0x073a00 // 0.7.58.0
+ double speed = 0.0;
+ if (g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_SPEED_DOWNLOAD, &speed) == CURLE_OK)
+ return speed;
+#else
+ double time = 0.0, size = 0.0;
+ if (g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_TOTAL_TIME, &time) == CURLE_OK
+ && g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_SIZE_DOWNLOAD, &size) == CURLE_OK
+ && time > 0.0)
+ {
+ return size / time;
+ }
+#endif
+ return 0.0;
+}
diff --git a/xbmc/filesystem/CurlFile.h b/xbmc/filesystem/CurlFile.h
new file mode 100644
index 0000000..88f2923
--- /dev/null
+++ b/xbmc/filesystem/CurlFile.h
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IFile.h"
+#include "utils/HttpHeader.h"
+#include "utils/RingBuffer.h"
+
+#include <map>
+#include <string>
+
+typedef void CURL_HANDLE;
+typedef void CURLM;
+struct curl_slist;
+
+namespace XFILE
+{
+ class CCurlFile : public IFile
+ {
+ private:
+ typedef enum
+ {
+ PROXY_HTTP = 0,
+ PROXY_SOCKS4,
+ PROXY_SOCKS4A,
+ PROXY_SOCKS5,
+ PROXY_SOCKS5_REMOTE,
+ PROXY_HTTPS,
+ } ProxyType;
+
+ public:
+ CCurlFile();
+ ~CCurlFile() override;
+ bool Open(const CURL& url) override;
+ bool OpenForWrite(const CURL& url, bool bOverWrite = false) override;
+ bool ReOpen(const CURL& url) override;
+ bool Exists(const CURL& url) override;
+ int64_t Seek(int64_t iFilePosition, int iWhence=SEEK_SET) override;
+ int64_t GetPosition() override;
+ int64_t GetLength() override;
+ int Stat(const CURL& url, struct __stat64* buffer) override;
+ void Close() override;
+ bool ReadString(char *szLine, int iLineLength) override { return m_state->ReadString(szLine, iLineLength); }
+ ssize_t Read(void* lpBuf, size_t uiBufSize) override { return m_state->Read(lpBuf, uiBufSize); }
+ ssize_t Write(const void* lpBuf, size_t uiBufSize) override;
+ const std::string GetProperty(XFILE::FileProperty type, const std::string &name = "") const override;
+ const std::vector<std::string> GetPropertyValues(XFILE::FileProperty type, const std::string &name = "") const override;
+ int IoControl(EIoControl request, void* param) override;
+ double GetDownloadSpeed() override;
+
+ bool Post(const std::string& strURL, const std::string& strPostData, std::string& strHTML);
+ bool Get(const std::string& strURL, std::string& strHTML);
+ bool ReadData(std::string& strHTML);
+ bool Download(const std::string& strURL, const std::string& strFileName, unsigned int* pdwSize = NULL);
+ bool IsInternet();
+ void Cancel();
+ void Reset();
+ void SetUserAgent(const std::string& sUserAgent) { m_userAgent = sUserAgent; }
+ void SetProxy(const std::string &type, const std::string &host, uint16_t port,
+ const std::string &user, const std::string &password);
+ void SetCustomRequest(const std::string &request) { m_customrequest = request; }
+ void SetAcceptEncoding(const std::string& encoding) { m_acceptencoding = encoding; }
+ void SetAcceptCharset(const std::string& charset) { m_acceptCharset = charset; }
+ void SetTimeout(int connecttimeout) { m_connecttimeout = connecttimeout; }
+ void SetLowSpeedTime(int lowspeedtime) { m_lowspeedtime = lowspeedtime; }
+ void SetPostData(const std::string& postdata) { m_postdata = postdata; }
+ void SetReferer(const std::string& referer) { m_referer = referer; }
+ void SetCookie(const std::string& cookie) { m_cookie = cookie; }
+ void SetMimeType(const std::string& mimetype) { SetRequestHeader("Content-Type", mimetype); }
+ void SetRequestHeader(const std::string& header, const std::string& value);
+ void SetRequestHeader(const std::string& header, long value);
+
+ void ClearRequestHeaders();
+ void SetBufferSize(unsigned int size);
+
+ const CHttpHeader& GetHttpHeader() const { return m_state->m_httpheader; }
+ std::string GetURL(void);
+ std::string GetRedirectURL();
+
+ /* static function that will get content type of a file */
+ static bool GetHttpHeader(const CURL &url, CHttpHeader &headers);
+ static bool GetMimeType(const CURL &url, std::string &content, const std::string &useragent="");
+ static bool GetContentType(const CURL &url, std::string &content, const std::string &useragent = "");
+
+ /* static function that will get cookies stored by CURL in RFC 2109 format */
+ static bool GetCookies(const CURL &url, std::string &cookies);
+
+ class CReadState
+ {
+ public:
+ CReadState();
+ ~CReadState();
+ CURL_HANDLE* m_easyHandle;
+ CURLM* m_multiHandle;
+
+ CRingBuffer m_buffer; // our ringhold buffer
+ unsigned int m_bufferSize;
+
+ char* m_overflowBuffer; // in the rare case we would overflow the above buffer
+ unsigned int m_overflowSize; // size of the overflow buffer
+ int m_stillRunning; // Is background url fetch still in progress
+ bool m_cancelled;
+ int64_t m_fileSize;
+ int64_t m_filePos;
+ bool m_bFirstLoop;
+ bool m_isPaused;
+ bool m_sendRange;
+ bool m_bLastError;
+ bool m_bRetry;
+
+ char* m_readBuffer;
+
+ /* returned http header */
+ CHttpHeader m_httpheader;
+ bool IsHeaderDone(void) { return m_httpheader.IsHeaderDone(); }
+
+ curl_slist* m_curlHeaderList;
+ curl_slist* m_curlAliasList;
+
+ size_t ReadCallback(char *buffer, size_t size, size_t nitems);
+ size_t WriteCallback(char *buffer, size_t size, size_t nitems);
+ size_t HeaderCallback(void *ptr, size_t size, size_t nmemb);
+
+ bool Seek(int64_t pos);
+ ssize_t Read(void* lpBuf, size_t uiBufSize);
+ bool ReadString(char *szLine, int iLineLength);
+ int8_t FillBuffer(unsigned int want);
+ void SetReadBuffer(const void* lpBuf, int64_t uiBufSize);
+
+ void SetResume(void);
+ long Connect(unsigned int size);
+ void Disconnect();
+ };
+
+ protected:
+ void ParseAndCorrectUrl(CURL &url);
+ void SetCommonOptions(CReadState* state, bool failOnError = true);
+ void SetRequestHeaders(CReadState* state);
+ void SetCorrectHeaders(CReadState* state);
+ bool Service(const std::string& strURL, std::string& strHTML);
+ std::string GetInfoString(int infoType);
+
+ protected:
+ CReadState* m_state;
+ CReadState* m_oldState;
+ unsigned int m_bufferSize;
+ int64_t m_writeOffset = 0;
+
+ std::string m_url;
+ std::string m_userAgent;
+ ProxyType m_proxytype = PROXY_HTTP;
+ std::string m_proxyhost;
+ uint16_t m_proxyport = 3128;
+ std::string m_proxyuser;
+ std::string m_proxypassword;
+ std::string m_customrequest;
+ std::string m_acceptencoding;
+ std::string m_acceptCharset;
+ std::string m_ftpauth;
+ std::string m_ftpport;
+ std::string m_binary;
+ std::string m_postdata;
+ std::string m_referer;
+ std::string m_cookie;
+ std::string m_username;
+ std::string m_password;
+ std::string m_httpauth;
+ std::string m_cipherlist;
+ bool m_ftppasvip;
+ int m_connecttimeout;
+ int m_redirectlimit;
+ int m_lowspeedtime;
+ bool m_opened;
+ bool m_forWrite;
+ bool m_inError;
+ bool m_seekable;
+ bool m_multisession;
+ bool m_skipshout;
+ bool m_postdataset;
+ bool m_allowRetry;
+ bool m_verifyPeer = true;
+ bool m_failOnError = true;
+ curl_slist* m_dnsCacheList = nullptr;
+
+ CRingBuffer m_buffer; // our ringhold buffer
+ char* m_overflowBuffer; // in the rare case we would overflow the above buffer
+ unsigned int m_overflowSize = 0; // size of the overflow buffer
+
+ int m_stillRunning; // Is background url fetch still in progress?
+
+ typedef std::map<std::string, std::string> MAPHTTPHEADERS;
+ MAPHTTPHEADERS m_requestheaders;
+
+ long m_httpresponse;
+ };
+}
diff --git a/xbmc/filesystem/DAVCommon.cpp b/xbmc/filesystem/DAVCommon.cpp
new file mode 100644
index 0000000..f200609
--- /dev/null
+++ b/xbmc/filesystem/DAVCommon.cpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DAVCommon.h"
+
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+using namespace XFILE;
+
+/*
+ * Return true if pElement value is equal value without namespace.
+ *
+ * if pElement is <DAV:foo> and value is foo then ValueWithoutNamespace is true
+ */
+bool CDAVCommon::ValueWithoutNamespace(const TiXmlNode *pNode, const std::string& value)
+{
+ const TiXmlElement *pElement;
+
+ if (!pNode)
+ {
+ return false;
+ }
+
+ pElement = pNode->ToElement();
+
+ if (!pElement)
+ {
+ return false;
+ }
+
+ std::vector<std::string> tag = StringUtils::Split(pElement->ValueStr(), ":", 2);
+
+ if (tag.size() == 1 && tag[0] == value)
+ {
+ return true;
+ }
+ else if (tag.size() == 2 && tag[1] == value)
+ {
+ return true;
+ }
+ else if (tag.size() > 2)
+ {
+ CLog::Log(LOGERROR, "{} - Splitting {} failed, size(): {}, value: {}", __FUNCTION__,
+ pElement->Value(), (unsigned long int)tag.size(), value);
+ }
+
+ return false;
+}
+
+/*
+ * Search for <status> and return its content
+ */
+std::string CDAVCommon::GetStatusTag(const TiXmlElement *pElement)
+{
+ const TiXmlElement *pChild;
+
+ for (pChild = pElement->FirstChildElement(); pChild != 0; pChild = pChild->NextSiblingElement())
+ {
+ if (ValueWithoutNamespace(pChild, "status"))
+ {
+ return pChild->NoChildren() ? "" : pChild->FirstChild()->ValueStr();
+ }
+ }
+
+ return "";
+}
+
diff --git a/xbmc/filesystem/DAVCommon.h b/xbmc/filesystem/DAVCommon.h
new file mode 100644
index 0000000..483de3b
--- /dev/null
+++ b/xbmc/filesystem/DAVCommon.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "utils/XBMCTinyXML.h"
+
+namespace XFILE
+{
+ class CDAVCommon
+ {
+ public:
+ static bool ValueWithoutNamespace(const TiXmlNode *pNode, const std::string& value);
+ static std::string GetStatusTag(const TiXmlElement *pElement);
+ };
+}
diff --git a/xbmc/filesystem/DAVDirectory.cpp b/xbmc/filesystem/DAVDirectory.cpp
new file mode 100644
index 0000000..48d4865
--- /dev/null
+++ b/xbmc/filesystem/DAVDirectory.cpp
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DAVDirectory.h"
+
+#include "CurlFile.h"
+#include "DAVCommon.h"
+#include "DAVFile.h"
+#include "FileItem.h"
+#include "URL.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+
+using namespace XFILE;
+
+CDAVDirectory::CDAVDirectory(void) = default;
+CDAVDirectory::~CDAVDirectory(void) = default;
+
+/*
+ * Parses a <response>
+ *
+ * <!ELEMENT response (href, ((href*, status)|(propstat+)), responsedescription?) >
+ * <!ELEMENT propstat (prop, status, responsedescription?) >
+ *
+ */
+void CDAVDirectory::ParseResponse(const TiXmlElement *pElement, CFileItem &item)
+{
+ const TiXmlElement *pResponseChild;
+ const TiXmlNode *pPropstatChild;
+ const TiXmlElement *pPropChild;
+
+ /* Iterate response children elements */
+ for (pResponseChild = pElement->FirstChildElement(); pResponseChild != 0; pResponseChild = pResponseChild->NextSiblingElement())
+ {
+ if (CDAVCommon::ValueWithoutNamespace(pResponseChild, "href") && !pResponseChild->NoChildren())
+ {
+ std::string path(pResponseChild->FirstChild()->ValueStr());
+ URIUtils::RemoveSlashAtEnd(path);
+ item.SetPath(path);
+ }
+ else
+ if (CDAVCommon::ValueWithoutNamespace(pResponseChild, "propstat"))
+ {
+ if (CDAVCommon::GetStatusTag(pResponseChild->ToElement()).find("200 OK") != std::string::npos)
+ {
+ /* Iterate propstat children elements */
+ for (pPropstatChild = pResponseChild->FirstChild(); pPropstatChild != 0; pPropstatChild = pPropstatChild->NextSibling())
+ {
+ if (CDAVCommon::ValueWithoutNamespace(pPropstatChild, "prop"))
+ {
+ /* Iterate all properties available */
+ for (pPropChild = pPropstatChild->FirstChildElement(); pPropChild != 0; pPropChild = pPropChild->NextSiblingElement())
+ {
+ if (CDAVCommon::ValueWithoutNamespace(pPropChild, "getcontentlength") && !pPropChild->NoChildren())
+ {
+ item.m_dwSize = strtoll(pPropChild->FirstChild()->Value(), NULL, 10);
+ }
+ else
+ if (CDAVCommon::ValueWithoutNamespace(pPropChild, "getlastmodified") && !pPropChild->NoChildren())
+ {
+ struct tm timeDate = {};
+ strptime(pPropChild->FirstChild()->Value(), "%a, %d %b %Y %T", &timeDate);
+ item.m_dateTime = mktime(&timeDate);
+ }
+ else
+ if (CDAVCommon::ValueWithoutNamespace(pPropChild, "displayname") && !pPropChild->NoChildren())
+ {
+ item.SetLabel(CURL::Decode(pPropChild->FirstChild()->ValueStr()));
+ }
+ else
+ if (!item.m_dateTime.IsValid() && CDAVCommon::ValueWithoutNamespace(pPropChild, "creationdate") && !pPropChild->NoChildren())
+ {
+ struct tm timeDate = {};
+ strptime(pPropChild->FirstChild()->Value(), "%Y-%m-%dT%T", &timeDate);
+ item.m_dateTime = mktime(&timeDate);
+ }
+ else
+ if (CDAVCommon::ValueWithoutNamespace(pPropChild, "resourcetype"))
+ {
+ if (CDAVCommon::ValueWithoutNamespace(pPropChild->FirstChild(), "collection"))
+ {
+ item.m_bIsFolder = true;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+bool CDAVDirectory::GetDirectory(const CURL& url, CFileItemList &items)
+{
+ CCurlFile dav;
+ std::string strRequest = "PROPFIND";
+
+ dav.SetCustomRequest(strRequest);
+ dav.SetMimeType("text/xml; charset=\"utf-8\"");
+ dav.SetRequestHeader("depth", 1);
+ dav.SetPostData(
+ "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
+ " <D:propfind xmlns:D=\"DAV:\">"
+ " <D:prop>"
+ " <D:resourcetype/>"
+ " <D:getcontentlength/>"
+ " <D:getlastmodified/>"
+ " <D:creationdate/>"
+ " <D:displayname/>"
+ " </D:prop>"
+ " </D:propfind>");
+
+ if (!dav.Open(url))
+ {
+ CLog::Log(LOGERROR, "{} - Unable to get dav directory ({})", __FUNCTION__, url.GetRedacted());
+ return false;
+ }
+
+ std::string strResponse;
+ dav.ReadData(strResponse);
+
+ std::string fileCharset(dav.GetProperty(XFILE::FILE_PROPERTY_CONTENT_CHARSET));
+ CXBMCTinyXML davResponse;
+ davResponse.Parse(strResponse, fileCharset);
+
+ if (!davResponse.Parse(strResponse))
+ {
+ CLog::Log(LOGERROR, "{} - Unable to process dav directory ({})", __FUNCTION__,
+ url.GetRedacted());
+ dav.Close();
+ return false;
+ }
+
+ TiXmlNode *pChild;
+ // Iterate over all responses
+ for (pChild = davResponse.RootElement()->FirstChild(); pChild != 0; pChild = pChild->NextSibling())
+ {
+ if (CDAVCommon::ValueWithoutNamespace(pChild, "response"))
+ {
+ CFileItem item;
+ ParseResponse(pChild->ToElement(), item);
+ const CURL& url2(url);
+ CURL url3(item.GetPath());
+
+ std::string itemPath(URIUtils::AddFileToFolder(url2.GetWithoutFilename(), url3.GetFileName()));
+
+ if (item.GetLabel().empty())
+ {
+ std::string name(itemPath);
+ URIUtils::RemoveSlashAtEnd(name);
+ item.SetLabel(CURL::Decode(URIUtils::GetFileName(name)));
+ }
+
+ if (item.m_bIsFolder)
+ URIUtils::AddSlashAtEnd(itemPath);
+
+ // Add back protocol options
+ if (!url2.GetProtocolOptions().empty())
+ itemPath += "|" + url2.GetProtocolOptions();
+ item.SetPath(itemPath);
+
+ if (!item.IsURL(url))
+ {
+ CFileItemPtr pItem(new CFileItem(item));
+ items.Add(pItem);
+ }
+ }
+ }
+
+ dav.Close();
+
+ return true;
+}
+
+bool CDAVDirectory::Create(const CURL& url)
+{
+ CDAVFile dav;
+ std::string strRequest = "MKCOL";
+
+ dav.SetCustomRequest(strRequest);
+
+ if (!dav.Execute(url))
+ {
+ CLog::Log(LOGERROR, "{} - Unable to create dav directory ({}) - {}", __FUNCTION__,
+ url.GetRedacted(), dav.GetLastResponseCode());
+ return false;
+ }
+
+ dav.Close();
+
+ return true;
+}
+
+bool CDAVDirectory::Exists(const CURL& url)
+{
+ CCurlFile dav;
+
+ // Set the PROPFIND custom request else we may not find folders, depending
+ // on the server's configuration
+ std::string strRequest = "PROPFIND";
+ dav.SetCustomRequest(strRequest);
+ dav.SetRequestHeader("depth", 0);
+
+ return dav.Exists(url);
+}
+
+bool CDAVDirectory::Remove(const CURL& url)
+{
+ CDAVFile dav;
+ std::string strRequest = "DELETE";
+
+ dav.SetCustomRequest(strRequest);
+
+ if (!dav.Execute(url))
+ {
+ CLog::Log(LOGERROR, "{} - Unable to delete dav directory ({}) - {}", __FUNCTION__,
+ url.GetRedacted(), dav.GetLastResponseCode());
+ return false;
+ }
+
+ dav.Close();
+
+ return true;
+}
diff --git a/xbmc/filesystem/DAVDirectory.h b/xbmc/filesystem/DAVDirectory.h
new file mode 100644
index 0000000..d442dc3
--- /dev/null
+++ b/xbmc/filesystem/DAVDirectory.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IDirectory.h"
+
+class CFileItem;
+class CFileItemList;
+class TiXmlElement;
+
+namespace XFILE
+{
+ class CDAVDirectory : public IDirectory
+ {
+ public:
+ CDAVDirectory(void);
+ ~CDAVDirectory(void) override;
+ bool GetDirectory(const CURL& url, CFileItemList &items) override;
+ bool Create(const CURL& url) override;
+ bool Exists(const CURL& url) override;
+ bool Remove(const CURL& url) override;
+ DIR_CACHE_TYPE GetCacheType(const CURL& url) const override { return DIR_CACHE_ONCE; }
+
+ private:
+ void ParseResponse(const TiXmlElement *pElement, CFileItem &item);
+ };
+}
diff --git a/xbmc/filesystem/DAVFile.cpp b/xbmc/filesystem/DAVFile.cpp
new file mode 100644
index 0000000..a0ff2fd
--- /dev/null
+++ b/xbmc/filesystem/DAVFile.cpp
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DAVFile.h"
+
+#include "DAVCommon.h"
+#include "DllLibCurl.h"
+#include "URL.h"
+#include "utils/RegExp.h"
+#include "utils/XBMCTinyXML.h"
+#include "utils/log.h"
+
+using namespace XFILE;
+using namespace XCURL;
+
+CDAVFile::CDAVFile(void)
+ : CCurlFile()
+{
+}
+
+CDAVFile::~CDAVFile(void) = default;
+
+bool CDAVFile::Execute(const CURL& url)
+{
+ CURL url2(url);
+ ParseAndCorrectUrl(url2);
+
+ CLog::Log(LOGDEBUG, "CDAVFile::Execute({}) {}", fmt::ptr(this), m_url);
+
+ assert(!(!m_state->m_easyHandle ^ !m_state->m_multiHandle));
+ if( m_state->m_easyHandle == NULL )
+ g_curlInterface.easy_acquire(url2.GetProtocol().c_str(),
+ url2.GetHostName().c_str(),
+ &m_state->m_easyHandle,
+ &m_state->m_multiHandle);
+
+ // setup common curl options
+ SetCommonOptions(m_state);
+ SetRequestHeaders(m_state);
+
+ m_lastResponseCode = m_state->Connect(m_bufferSize);
+ if (m_lastResponseCode < 0 || m_lastResponseCode >= 400)
+ return false;
+
+ char* efurl;
+ if (CURLE_OK == g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_EFFECTIVE_URL,&efurl) && efurl)
+ m_url = efurl;
+
+ if (m_lastResponseCode == 207)
+ {
+ std::string strResponse;
+ ReadData(strResponse);
+
+ CXBMCTinyXML davResponse;
+ davResponse.Parse(strResponse);
+
+ if (!davResponse.Parse(strResponse))
+ {
+ CLog::Log(LOGERROR, "CDAVFile::Execute - Unable to process dav response ({})",
+ CURL(m_url).GetRedacted());
+ Close();
+ return false;
+ }
+
+ TiXmlNode *pChild;
+ // Iterate over all responses
+ for (pChild = davResponse.RootElement()->FirstChild(); pChild != 0; pChild = pChild->NextSibling())
+ {
+ if (CDAVCommon::ValueWithoutNamespace(pChild, "response"))
+ {
+ std::string sRetCode = CDAVCommon::GetStatusTag(pChild->ToElement());
+ CRegExp rxCode;
+ rxCode.RegComp("HTTP/.+\\s(\\d+)\\s.*");
+ if (rxCode.RegFind(sRetCode) >= 0)
+ {
+ if (rxCode.GetSubCount())
+ {
+ m_lastResponseCode = atoi(rxCode.GetMatch(1).c_str());
+ if (m_lastResponseCode < 0 || m_lastResponseCode >= 400)
+ return false;
+ }
+ }
+
+ }
+ }
+ }
+
+ return true;
+}
+
+bool CDAVFile::Delete(const CURL& url)
+{
+ if (m_opened)
+ return false;
+
+ CDAVFile dav;
+ std::string strRequest = "DELETE";
+
+ dav.SetCustomRequest(strRequest);
+
+ CLog::Log(LOGDEBUG, "CDAVFile::Delete - Execute DELETE ({})", url.GetRedacted());
+ if (!dav.Execute(url))
+ {
+ CLog::Log(LOGERROR, "CDAVFile::Delete - Unable to delete dav resource ({})", url.GetRedacted());
+ return false;
+ }
+
+ dav.Close();
+
+ return true;
+}
+
+bool CDAVFile::Rename(const CURL& url, const CURL& urlnew)
+{
+ if (m_opened)
+ return false;
+
+ CDAVFile dav;
+
+ CURL url2(urlnew);
+ std::string strProtocol = url2.GetTranslatedProtocol();
+ url2.SetProtocol(strProtocol);
+
+ std::string strRequest = "MOVE";
+ dav.SetCustomRequest(strRequest);
+ dav.SetRequestHeader("Destination", url2.GetWithoutUserDetails());
+
+ CLog::Log(LOGDEBUG, "CDAVFile::Rename - Execute MOVE ({} -> {})", url.GetRedacted(),
+ url2.GetRedacted());
+ if (!dav.Execute(url))
+ {
+ CLog::Log(LOGERROR, "CDAVFile::Rename - Unable to rename dav resource ({} -> {})",
+ url.GetRedacted(), url2.GetRedacted());
+ return false;
+ }
+
+ dav.Close();
+
+ return true;
+}
diff --git a/xbmc/filesystem/DAVFile.h b/xbmc/filesystem/DAVFile.h
new file mode 100644
index 0000000..2103a45
--- /dev/null
+++ b/xbmc/filesystem/DAVFile.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "CurlFile.h"
+
+namespace XFILE
+{
+ class CDAVFile : public CCurlFile
+ {
+ public:
+ CDAVFile(void);
+ ~CDAVFile(void) override;
+
+ virtual bool Execute(const CURL& url);
+
+ bool Delete(const CURL& url) override;
+ bool Rename(const CURL& url, const CURL& urlnew) override;
+
+ virtual int GetLastResponseCode() { return m_lastResponseCode; }
+
+ private:
+ int m_lastResponseCode = 0;
+ };
+}
diff --git a/xbmc/filesystem/Directorization.h b/xbmc/filesystem/Directorization.h
new file mode 100644
index 0000000..f413635
--- /dev/null
+++ b/xbmc/filesystem/Directorization.h
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2015-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "FileItem.h"
+#include "URL.h"
+#include "utils/CharsetConverter.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace XFILE
+{
+ /**
+ * \brief Method definition to convert an entry to a CFileItemPtr.
+ *
+ * \param entry The entry to convert to a CFileItemPtr
+ * \param label The label of the entry
+ * \param path The path of the entry
+ * \param isFolder Whether the entry is a folder or not
+ * \return The CFileItemPtr object created from the given entry and data.
+ */
+ template<class TEntry>
+ using DirectorizeEntryToFileItemFunction = CFileItemPtr(*)(const TEntry& entry, const std::string& label, const std::string& path, bool isFolder);
+
+ template<class TEntry>
+ using DirectorizeEntry = std::pair<std::string, TEntry>;
+ template<class TEntry>
+ using DirectorizeEntries = std::vector<DirectorizeEntry<TEntry>>;
+
+ /**
+ * \brief Analyzes the given entry list from the given URL and turns them into files and directories on one directory hierarchy.
+ *
+ * \param url URL of the directory hierarchy to build
+ * \param entries Entries to analyze and turn into files and directories
+ * \param converter Converter function to convert an entry into a CFileItemPtr
+ * \param items Resulting item list
+ */
+ template<class TEntry>
+ static void Directorize(const CURL& url, const DirectorizeEntries<TEntry>& entries, DirectorizeEntryToFileItemFunction<TEntry> converter, CFileItemList& items)
+ {
+ if (url.Get().empty() || entries.empty())
+ return;
+
+ const std::string& options = url.GetOptions();
+ const std::string& filePath = url.GetFileName();
+
+ CURL baseUrl(url);
+ baseUrl.SetOptions(""); // delete options to have a clean path to add stuff too
+ baseUrl.SetFileName(""); // delete filename too as our names later will contain it
+
+ std::string basePath = baseUrl.Get();
+ URIUtils::AddSlashAtEnd(basePath);
+
+ std::vector<std::string> filePathTokens;
+ if (!filePath.empty())
+ StringUtils::Tokenize(filePath, filePathTokens, "/");
+
+ bool fastLookup = items.GetFastLookup();
+ items.SetFastLookup(true);
+ for (const auto& entry : entries)
+ {
+ std::string entryPath = entry.first;
+ std::string entryFileName = entryPath;
+ StringUtils::Replace(entryFileName, '\\', '/');
+
+ // skip the requested entry
+ if (entryFileName == filePath)
+ continue;
+
+ // Disregard Apple Resource Fork data
+ std::size_t found = entryPath.find("__MACOSX");
+ if (found != std::string::npos)
+ continue;
+
+ std::vector<std::string> pathTokens;
+ StringUtils::Tokenize(entryFileName, pathTokens, "/");
+
+ // ignore any entries in lower directory hierarchies
+ if (pathTokens.size() < filePathTokens.size() + 1)
+ continue;
+
+ // ignore any entries in different directory hierarchies
+ bool ignoreItem = false;
+ entryFileName.clear();
+ for (auto filePathToken = filePathTokens.begin(); filePathToken != filePathTokens.end(); ++filePathToken)
+ {
+ if (*filePathToken != pathTokens[std::distance(filePathTokens.begin(), filePathToken)])
+ {
+ ignoreItem = true;
+ break;
+ }
+ entryFileName = URIUtils::AddFileToFolder(entryFileName, *filePathToken);
+ }
+ if (ignoreItem)
+ continue;
+
+ entryFileName = URIUtils::AddFileToFolder(entryFileName, pathTokens[filePathTokens.size()]);
+ char c = entryPath[entryFileName.size()];
+ if (c == '/' || c == '\\')
+ URIUtils::AddSlashAtEnd(entryFileName);
+
+ std::string itemPath = URIUtils::AddFileToFolder(basePath, entryFileName) + options;
+ bool isFolder = false;
+ if (URIUtils::HasSlashAtEnd(entryFileName)) // this is a directory
+ {
+ // check if the directory has already been added
+ if (items.Contains(itemPath)) // already added
+ continue;
+
+ isFolder = true;
+ URIUtils::AddSlashAtEnd(itemPath);
+ }
+
+ // determine the entry's filename
+ std::string label = pathTokens[filePathTokens.size()];
+ g_charsetConverter.unknownToUTF8(label);
+
+ // convert the entry into a CFileItem
+ CFileItemPtr item = converter(entry.second, label, itemPath, isFolder);
+ item->SetPath(itemPath);
+ item->m_bIsFolder = isFolder;
+ if (isFolder)
+ item->m_dwSize = 0;
+
+ items.Add(item);
+ }
+ items.SetFastLookup(fastLookup);
+ }
+}
diff --git a/xbmc/filesystem/Directory.cpp b/xbmc/filesystem/Directory.cpp
new file mode 100644
index 0000000..5436fd9
--- /dev/null
+++ b/xbmc/filesystem/Directory.cpp
@@ -0,0 +1,446 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "Directory.h"
+
+#include "DirectoryCache.h"
+#include "DirectoryFactory.h"
+#include "FileDirectoryFactory.h"
+#include "FileItem.h"
+#include "PasswordManager.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "commons/Exception.h"
+#include "dialogs/GUIDialogBusy.h"
+#include "guilib/GUIWindowManager.h"
+#include "messaging/ApplicationMessenger.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/Job.h"
+#include "utils/JobManager.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+
+using namespace XFILE;
+using namespace std::chrono_literals;
+
+#define TIME_TO_BUSY_DIALOG 500
+
+class CGetDirectory
+{
+private:
+
+ struct CResult
+ {
+ CResult(const CURL& dir, const CURL& listDir) : m_event(true), m_dir(dir), m_listDir(listDir), m_result(false) {}
+ CEvent m_event;
+ CFileItemList m_list;
+ CURL m_dir;
+ CURL m_listDir;
+ bool m_result;
+ };
+
+ struct CGetJob
+ : CJob
+ {
+ CGetJob(std::shared_ptr<IDirectory>& imp
+ , std::shared_ptr<CResult>& result)
+ : m_result(result)
+ , m_imp(imp)
+ {}
+ public:
+ bool DoWork() override
+ {
+ m_result->m_list.SetURL(m_result->m_listDir);
+ m_result->m_result = m_imp->GetDirectory(m_result->m_dir, m_result->m_list);
+ m_result->m_event.Set();
+ return m_result->m_result;
+ }
+
+ std::shared_ptr<CResult> m_result;
+ std::shared_ptr<IDirectory> m_imp;
+ };
+
+public:
+
+ CGetDirectory(std::shared_ptr<IDirectory>& imp, const CURL& dir, const CURL& listDir)
+ : m_result(new CResult(dir, listDir))
+ {
+ m_id = CServiceBroker::GetJobManager()->AddJob(new CGetJob(imp, m_result), nullptr,
+ CJob::PRIORITY_HIGH);
+ if (m_id == 0)
+ {
+ CGetJob job(imp, m_result);
+ job.DoWork();
+ }
+ }
+ ~CGetDirectory() { CServiceBroker::GetJobManager()->CancelJob(m_id); }
+
+ CEvent& GetEvent()
+ {
+ return m_result->m_event;
+ }
+
+ bool Wait(unsigned int timeout)
+ {
+ return m_result->m_event.Wait(std::chrono::milliseconds(timeout));
+ }
+
+ bool GetDirectory(CFileItemList& list)
+ {
+ /* if it was not finished or failed, return failure */
+ if (!m_result->m_event.Wait(0ms) || !m_result->m_result)
+ {
+ list.Clear();
+ return false;
+ }
+
+ list.Copy(m_result->m_list);
+ return true;
+ }
+ std::shared_ptr<CResult> m_result;
+ unsigned int m_id;
+};
+
+
+CDirectory::CDirectory() = default;
+
+CDirectory::~CDirectory() = default;
+
+bool CDirectory::GetDirectory(const std::string& strPath, CFileItemList &items, const std::string &strMask, int flags)
+{
+ CHints hints;
+ hints.flags = flags;
+ hints.mask = strMask;
+ const CURL pathToUrl(strPath);
+ return GetDirectory(pathToUrl, items, hints);
+}
+
+bool CDirectory::GetDirectory(const std::string& strPath,
+ const std::shared_ptr<IDirectory>& pDirectory,
+ CFileItemList& items,
+ const std::string& strMask,
+ int flags)
+{
+ CHints hints;
+ hints.flags = flags;
+ hints.mask = strMask;
+ const CURL pathToUrl(strPath);
+ return GetDirectory(pathToUrl, pDirectory, items, hints);
+}
+
+bool CDirectory::GetDirectory(const std::string& strPath, CFileItemList &items, const CHints &hints)
+{
+ const CURL pathToUrl(strPath);
+ return GetDirectory(pathToUrl, items, hints);
+}
+
+bool CDirectory::GetDirectory(const CURL& url, CFileItemList &items, const std::string &strMask, int flags)
+{
+ CHints hints;
+ hints.flags = flags;
+ hints.mask = strMask;
+ return GetDirectory(url, items, hints);
+}
+
+bool CDirectory::GetDirectory(const CURL& url, CFileItemList &items, const CHints &hints)
+{
+ CURL realURL = URIUtils::SubstitutePath(url);
+ std::shared_ptr<IDirectory> pDirectory(CDirectoryFactory::Create(realURL));
+ return CDirectory::GetDirectory(url, pDirectory, items, hints);
+}
+
+bool CDirectory::GetDirectory(const CURL& url,
+ const std::shared_ptr<IDirectory>& pDirectory,
+ CFileItemList& items,
+ const CHints& hints)
+{
+ try
+ {
+ CURL realURL = URIUtils::SubstitutePath(url);
+ if (!pDirectory)
+ return false;
+
+ // check our cache for this path
+ if (g_directoryCache.GetDirectory(realURL.Get(), items, (hints.flags & DIR_FLAG_READ_CACHE) == DIR_FLAG_READ_CACHE))
+ items.SetURL(url);
+ else
+ {
+ // need to clear the cache (in case the directory fetch fails)
+ // and (re)fetch the folder
+ if (!(hints.flags & DIR_FLAG_BYPASS_CACHE))
+ g_directoryCache.ClearDirectory(realURL.Get());
+
+ pDirectory->SetFlags(hints.flags);
+
+ bool result = false;
+ CURL authUrl = realURL;
+
+ while (!result)
+ {
+ const std::string pathToUrl(url.Get());
+
+ // don't change auth if it's set explicitly
+ if (CPasswordManager::GetInstance().IsURLSupported(authUrl) && authUrl.GetUserName().empty())
+ CPasswordManager::GetInstance().AuthenticateURL(authUrl);
+
+ items.SetURL(url);
+ result = pDirectory->GetDirectory(authUrl, items);
+
+ if (!result)
+ {
+ // @TODO ProcessRequirements() can bring up the keyboard input dialog
+ // filesystem must not depend on GUI
+ if (CServiceBroker::GetAppMessenger()->IsProcessThread() &&
+ pDirectory->ProcessRequirements())
+ {
+ authUrl.SetDomain("");
+ authUrl.SetUserName("");
+ authUrl.SetPassword("");
+ continue;
+ }
+
+ CLog::Log(LOGERROR, "{} - Error getting {}", __FUNCTION__, url.GetRedacted());
+ return false;
+ }
+ }
+
+ // hide credentials if necessary
+ if (CPasswordManager::GetInstance().IsURLSupported(realURL))
+ {
+ bool hide = false;
+ // for explicitly credentials
+ if (!realURL.GetUserName().empty())
+ {
+ // credentials was changed i.e. were stored in the password
+ // manager, in this case we can hide them from an item URL,
+ // otherwise we have to keep credentials in an item URL
+ if ( realURL.GetUserName() != authUrl.GetUserName()
+ || realURL.GetPassWord() != authUrl.GetPassWord()
+ || realURL.GetDomain() != authUrl.GetDomain())
+ {
+ hide = true;
+ }
+ }
+ else
+ {
+ // hide credentials in any other cases
+ hide = true;
+ }
+
+ if (hide)
+ {
+ for (int i = 0; i < items.Size(); ++i)
+ {
+ CFileItemPtr item = items[i];
+ CURL itemUrl = item->GetURL();
+ itemUrl.SetDomain("");
+ itemUrl.SetUserName("");
+ itemUrl.SetPassword("");
+ item->SetPath(itemUrl.Get());
+ }
+ }
+ }
+
+ // cache the directory, if necessary
+ if (!(hints.flags & DIR_FLAG_BYPASS_CACHE))
+ g_directoryCache.SetDirectory(realURL.Get(), items, pDirectory->GetCacheType(url));
+ }
+
+ // now filter for allowed files
+ if (!pDirectory->AllowAll())
+ {
+ pDirectory->SetMask(hints.mask);
+ for (int i = 0; i < items.Size(); ++i)
+ {
+ CFileItemPtr item = items[i];
+ if (!item->m_bIsFolder && !pDirectory->IsAllowed(item->GetURL()))
+ {
+ items.Remove(i);
+ i--; // don't confuse loop
+ }
+ }
+ }
+ // filter hidden files
+ //! @todo we shouldn't be checking the gui setting here, callers should use getHidden instead
+ if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_SHOWHIDDEN) && !(hints.flags & DIR_FLAG_GET_HIDDEN))
+ {
+ for (int i = 0; i < items.Size(); ++i)
+ {
+ if (items[i]->GetProperty("file:hidden").asBoolean())
+ {
+ items.Remove(i);
+ i--; // don't confuse loop
+ }
+ }
+ }
+
+ // Should any of the files we read be treated as a directory?
+ // Disable for database folders, as they already contain the extracted items
+ if (!(hints.flags & DIR_FLAG_NO_FILE_DIRS) && !items.IsMusicDb() && !items.IsVideoDb() && !items.IsSmartPlayList())
+ FilterFileDirectories(items, hints.mask);
+
+ // Correct items for path substitution
+ const std::string pathToUrl(url.Get());
+ const std::string pathToUrl2(realURL.Get());
+ if (pathToUrl != pathToUrl2)
+ {
+ for (int i = 0; i < items.Size(); ++i)
+ {
+ CFileItemPtr item = items[i];
+ item->SetPath(URIUtils::SubstitutePath(item->GetPath(), true));
+ }
+ }
+
+ return true;
+ }
+ XBMCCOMMONS_HANDLE_UNCHECKED
+ catch (...) { CLog::Log(LOGERROR, "{} - Unhandled exception", __FUNCTION__); }
+ CLog::Log(LOGERROR, "{} - Error getting {}", __FUNCTION__, url.GetRedacted());
+ return false;
+}
+
+bool CDirectory::Create(const std::string& strPath)
+{
+ const CURL pathToUrl(strPath);
+ return Create(pathToUrl);
+}
+
+bool CDirectory::Create(const CURL& url)
+{
+ try
+ {
+ CURL realURL = URIUtils::SubstitutePath(url);
+
+ if (CPasswordManager::GetInstance().IsURLSupported(realURL) && realURL.GetUserName().empty())
+ CPasswordManager::GetInstance().AuthenticateURL(realURL);
+
+ std::unique_ptr<IDirectory> pDirectory(CDirectoryFactory::Create(realURL));
+ if (pDirectory)
+ if(pDirectory->Create(realURL))
+ return true;
+ }
+ XBMCCOMMONS_HANDLE_UNCHECKED
+ catch (...) { CLog::Log(LOGERROR, "{} - Unhandled exception", __FUNCTION__); }
+ CLog::Log(LOGERROR, "{} - Error creating {}", __FUNCTION__, url.GetRedacted());
+ return false;
+}
+
+bool CDirectory::Exists(const std::string& strPath, bool bUseCache /* = true */)
+{
+ const CURL pathToUrl(strPath);
+ return Exists(pathToUrl, bUseCache);
+}
+
+bool CDirectory::Exists(const CURL& url, bool bUseCache /* = true */)
+{
+ try
+ {
+ CURL realURL = URIUtils::SubstitutePath(url);
+ if (bUseCache)
+ {
+ bool bPathInCache;
+ std::string realPath(realURL.Get());
+ URIUtils::AddSlashAtEnd(realPath);
+ if (g_directoryCache.FileExists(realPath, bPathInCache))
+ return true;
+ if (bPathInCache)
+ return false;
+ }
+
+ if (CPasswordManager::GetInstance().IsURLSupported(realURL) && realURL.GetUserName().empty())
+ CPasswordManager::GetInstance().AuthenticateURL(realURL);
+
+ std::unique_ptr<IDirectory> pDirectory(CDirectoryFactory::Create(realURL));
+ if (pDirectory)
+ return pDirectory->Exists(realURL);
+ }
+ XBMCCOMMONS_HANDLE_UNCHECKED
+ catch (...) { CLog::Log(LOGERROR, "{} - Unhandled exception", __FUNCTION__); }
+ CLog::Log(LOGERROR, "{} - Error checking for {}", __FUNCTION__, url.GetRedacted());
+ return false;
+}
+
+bool CDirectory::Remove(const std::string& strPath)
+{
+ const CURL pathToUrl(strPath);
+ return Remove(pathToUrl);
+}
+
+bool CDirectory::RemoveRecursive(const std::string& strPath)
+{
+ return RemoveRecursive(CURL{ strPath });
+}
+
+bool CDirectory::Remove(const CURL& url)
+{
+ try
+ {
+ CURL realURL = URIUtils::SubstitutePath(url);
+ CURL authUrl = realURL;
+ if (CPasswordManager::GetInstance().IsURLSupported(authUrl) && authUrl.GetUserName().empty())
+ CPasswordManager::GetInstance().AuthenticateURL(authUrl);
+
+ std::unique_ptr<IDirectory> pDirectory(CDirectoryFactory::Create(realURL));
+ if (pDirectory)
+ if(pDirectory->Remove(authUrl))
+ {
+ g_directoryCache.ClearFile(realURL.Get());
+ return true;
+ }
+ }
+ XBMCCOMMONS_HANDLE_UNCHECKED
+ catch (...) { CLog::Log(LOGERROR, "{} - Unhandled exception", __FUNCTION__); }
+ CLog::Log(LOGERROR, "{} - Error removing {}", __FUNCTION__, url.GetRedacted());
+ return false;
+}
+
+bool CDirectory::RemoveRecursive(const CURL& url)
+{
+ try
+ {
+ CURL realURL = URIUtils::SubstitutePath(url);
+ CURL authUrl = realURL;
+ if (CPasswordManager::GetInstance().IsURLSupported(authUrl) && authUrl.GetUserName().empty())
+ CPasswordManager::GetInstance().AuthenticateURL(authUrl);
+
+ std::unique_ptr<IDirectory> pDirectory(CDirectoryFactory::Create(realURL));
+ if (pDirectory)
+ if(pDirectory->RemoveRecursive(authUrl))
+ {
+ g_directoryCache.ClearFile(realURL.Get());
+ return true;
+ }
+ }
+ XBMCCOMMONS_HANDLE_UNCHECKED
+ catch (...) { CLog::Log(LOGERROR, "{} - Unhandled exception", __FUNCTION__); }
+ CLog::Log(LOGERROR, "{} - Error removing {}", __FUNCTION__, url.GetRedacted());
+ return false;
+}
+
+void CDirectory::FilterFileDirectories(CFileItemList &items, const std::string &mask,
+ bool expandImages)
+{
+ for (int i=0; i< items.Size(); ++i)
+ {
+ CFileItemPtr pItem=items[i];
+ auto mode = expandImages && pItem->IsDiscImage() ? EFILEFOLDER_TYPE_ONBROWSE : EFILEFOLDER_TYPE_ALWAYS;
+ if (!pItem->m_bIsFolder && pItem->IsFileFolder(mode))
+ {
+ std::unique_ptr<IFileDirectory> pDirectory(CFileDirectoryFactory::Create(pItem->GetURL(),pItem.get(),mask));
+ if (pDirectory)
+ pItem->m_bIsFolder = true;
+ else
+ if (pItem->m_bIsFolder)
+ {
+ items.Remove(i);
+ i--; // don't confuse loop
+ }
+ }
+ }
+}
diff --git a/xbmc/filesystem/Directory.h b/xbmc/filesystem/Directory.h
new file mode 100644
index 0000000..0af92f3
--- /dev/null
+++ b/xbmc/filesystem/Directory.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IDirectory.h"
+
+#include <memory>
+#include <string>
+
+namespace XFILE
+{
+/*!
+ \ingroup filesystem
+ \brief Wrappers for \e IDirectory
+ */
+class CDirectory
+{
+public:
+ CDirectory(void);
+ virtual ~CDirectory(void);
+
+ class CHints
+ {
+ public:
+ std::string mask;
+ int flags = DIR_FLAG_DEFAULTS;
+ };
+
+ static bool GetDirectory(const CURL& url
+ , CFileItemList &items
+ , const std::string &strMask
+ , int flags);
+
+ static bool GetDirectory(const CURL& url,
+ const std::shared_ptr<IDirectory>& pDirectory,
+ CFileItemList& items,
+ const CHints& hints);
+
+ static bool GetDirectory(const CURL& url
+ , CFileItemList &items
+ , const CHints &hints);
+
+ static bool Create(const CURL& url);
+ static bool Exists(const CURL& url, bool bUseCache = true);
+ static bool Remove(const CURL& url);
+ static bool RemoveRecursive(const CURL& url);
+
+ static bool GetDirectory(const std::string& strPath
+ , CFileItemList &items
+ , const std::string &strMask
+ , int flags);
+
+ static bool GetDirectory(const std::string& strPath,
+ const std::shared_ptr<IDirectory>& pDirectory,
+ CFileItemList& items,
+ const std::string& strMask,
+ int flags);
+
+ static bool GetDirectory(const std::string& strPath
+ , CFileItemList &items
+ , const CHints &hints);
+
+ static bool Create(const std::string& strPath);
+ static bool Exists(const std::string& strPath, bool bUseCache = true);
+ static bool Remove(const std::string& strPath);
+ static bool RemoveRecursive(const std::string& strPath);
+
+ /*! \brief Filter files that act like directories from the list, replacing them with their directory counterparts
+ \param items The item list to filter
+ \param mask The mask to apply when filtering files
+ \param expandImages True to include disc images in file directory expansion
+ */
+ static void FilterFileDirectories(CFileItemList &items, const std::string &mask,
+ bool expandImages=false);
+};
+}
diff --git a/xbmc/filesystem/DirectoryCache.cpp b/xbmc/filesystem/DirectoryCache.cpp
new file mode 100644
index 0000000..d46f11b
--- /dev/null
+++ b/xbmc/filesystem/DirectoryCache.cpp
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DirectoryCache.h"
+
+#include "Directory.h"
+#include "FileItem.h"
+#include "URL.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <climits>
+#include <mutex>
+
+// Maximum number of directories to keep in our cache
+#define MAX_CACHED_DIRS 50
+
+using namespace XFILE;
+
+CDirectoryCache::CDir::CDir(DIR_CACHE_TYPE cacheType)
+{
+ m_cacheType = cacheType;
+ m_lastAccess = 0;
+ m_Items = std::make_unique<CFileItemList>();
+ m_Items->SetIgnoreURLOptions(true);
+ m_Items->SetFastLookup(true);
+}
+
+CDirectoryCache::CDir::~CDir() = default;
+
+void CDirectoryCache::CDir::SetLastAccess(unsigned int &accessCounter)
+{
+ m_lastAccess = accessCounter++;
+}
+
+CDirectoryCache::CDirectoryCache(void)
+{
+ m_accessCounter = 0;
+#ifdef _DEBUG
+ m_cacheHits = 0;
+ m_cacheMisses = 0;
+#endif
+}
+
+CDirectoryCache::~CDirectoryCache(void) = default;
+
+bool CDirectoryCache::GetDirectory(const std::string& strPath, CFileItemList &items, bool retrieveAll)
+{
+ std::unique_lock<CCriticalSection> lock(m_cs);
+
+ // Get rid of any URL options, else the compare may be wrong
+ std::string storedPath = CURL(strPath).GetWithoutOptions();
+ URIUtils::RemoveSlashAtEnd(storedPath);
+
+ auto i = m_cache.find(storedPath);
+ if (i != m_cache.end())
+ {
+ CDir& dir = i->second;
+ if (dir.m_cacheType == XFILE::DIR_CACHE_ALWAYS ||
+ (dir.m_cacheType == XFILE::DIR_CACHE_ONCE && retrieveAll))
+ {
+ items.Copy(*dir.m_Items);
+ dir.SetLastAccess(m_accessCounter);
+#ifdef _DEBUG
+ m_cacheHits+=items.Size();
+#endif
+ return true;
+ }
+ }
+ return false;
+}
+
+void CDirectoryCache::SetDirectory(const std::string& strPath, const CFileItemList &items, DIR_CACHE_TYPE cacheType)
+{
+ if (cacheType == DIR_CACHE_NEVER)
+ return; // nothing to do
+
+ // caches the given directory using a copy of the items, rather than the items
+ // themselves. The reason we do this is because there is often some further
+ // processing on the items (stacking, transparent rars/zips for instance) that
+ // alters the URL of the items. If we shared the pointers, we'd have problems
+ // as the URLs in the cache would have changed, so things such as
+ // CDirectoryCache::FileExists() would fail for files that really do exist (just their
+ // URL's have been altered). This is called from CFile::Exists() which causes
+ // all sorts of hassles.
+ // IDEALLY, any further processing on the item would actually create a new item
+ // instead of altering it, but we can't really enforce that in an easy way, so
+ // this is the best solution for now.
+ std::unique_lock<CCriticalSection> lock(m_cs);
+
+ // Get rid of any URL options, else the compare may be wrong
+ std::string storedPath = CURL(strPath).GetWithoutOptions();
+ URIUtils::RemoveSlashAtEnd(storedPath);
+
+ ClearDirectory(storedPath);
+
+ CheckIfFull();
+
+ CDir dir(cacheType);
+ dir.m_Items->Copy(items);
+ dir.SetLastAccess(m_accessCounter);
+ m_cache.emplace(std::make_pair(storedPath, std::move(dir)));
+}
+
+void CDirectoryCache::ClearFile(const std::string& strFile)
+{
+ // Get rid of any URL options, else the compare may be wrong
+ std::string strFile2 = CURL(strFile).GetWithoutOptions();
+ URIUtils::RemoveSlashAtEnd(strFile2);
+
+ ClearDirectory(URIUtils::GetDirectory(strFile2));
+}
+
+void CDirectoryCache::ClearDirectory(const std::string& strPath)
+{
+ std::unique_lock<CCriticalSection> lock(m_cs);
+
+ // Get rid of any URL options, else the compare may be wrong
+ std::string storedPath = CURL(strPath).GetWithoutOptions();
+ URIUtils::RemoveSlashAtEnd(storedPath);
+
+ m_cache.erase(storedPath);
+}
+
+void CDirectoryCache::ClearSubPaths(const std::string& strPath)
+{
+ std::unique_lock<CCriticalSection> lock(m_cs);
+
+ // Get rid of any URL options, else the compare may be wrong
+ std::string storedPath = CURL(strPath).GetWithoutOptions();
+
+ auto i = m_cache.begin();
+ while (i != m_cache.end())
+ {
+ if (URIUtils::PathHasParent(i->first, storedPath))
+ m_cache.erase(i++);
+ else
+ i++;
+ }
+}
+
+void CDirectoryCache::AddFile(const std::string& strFile)
+{
+ std::unique_lock<CCriticalSection> lock(m_cs);
+
+ // Get rid of any URL options, else the compare may be wrong
+ std::string strPath = URIUtils::GetDirectory(CURL(strFile).GetWithoutOptions());
+ URIUtils::RemoveSlashAtEnd(strPath);
+
+ auto i = m_cache.find(strPath);
+ if (i != m_cache.end())
+ {
+ CDir& dir = i->second;
+ CFileItemPtr item(new CFileItem(strFile, false));
+ dir.m_Items->Add(item);
+ dir.SetLastAccess(m_accessCounter);
+ }
+}
+
+bool CDirectoryCache::FileExists(const std::string& strFile, bool& bInCache)
+{
+ std::unique_lock<CCriticalSection> lock(m_cs);
+ bInCache = false;
+
+ // Get rid of any URL options, else the compare may be wrong
+ std::string strPath = CURL(strFile).GetWithoutOptions();
+ URIUtils::RemoveSlashAtEnd(strPath);
+ std::string storedPath = URIUtils::GetDirectory(strPath);
+ URIUtils::RemoveSlashAtEnd(storedPath);
+
+ auto i = m_cache.find(storedPath);
+ if (i != m_cache.end())
+ {
+ bInCache = true;
+ CDir& dir = i->second;
+ dir.SetLastAccess(m_accessCounter);
+#ifdef _DEBUG
+ m_cacheHits++;
+#endif
+ return (URIUtils::PathEquals(strPath, storedPath) || dir.m_Items->Contains(strFile));
+ }
+#ifdef _DEBUG
+ m_cacheMisses++;
+#endif
+ return false;
+}
+
+void CDirectoryCache::Clear()
+{
+ // this routine clears everything
+ std::unique_lock<CCriticalSection> lock(m_cs);
+ m_cache.clear();
+}
+
+void CDirectoryCache::InitCache(const std::set<std::string>& dirs)
+{
+ for (const std::string& strDir : dirs)
+ {
+ CFileItemList items;
+ CDirectory::GetDirectory(strDir, items, "", DIR_FLAG_NO_FILE_DIRS);
+ items.Clear();
+ }
+}
+
+void CDirectoryCache::ClearCache(std::set<std::string>& dirs)
+{
+ auto i = m_cache.begin();
+ while (i != m_cache.end())
+ {
+ if (dirs.find(i->first) != dirs.end())
+ m_cache.erase(i++);
+ else
+ i++;
+ }
+}
+
+void CDirectoryCache::CheckIfFull()
+{
+ std::unique_lock<CCriticalSection> lock(m_cs);
+
+ // find the last accessed folder, and remove if the number of cached folders is too many
+ auto lastAccessed = m_cache.end();
+ unsigned int numCached = 0;
+ for (auto i = m_cache.begin(); i != m_cache.end(); i++)
+ {
+ // ensure dirs that are always cached aren't cleared
+ if (i->second.m_cacheType != DIR_CACHE_ALWAYS)
+ {
+ if (lastAccessed == m_cache.end() ||
+ i->second.GetLastAccess() < lastAccessed->second.GetLastAccess())
+ lastAccessed = i;
+ numCached++;
+ }
+ }
+ if (lastAccessed != m_cache.end() && numCached >= MAX_CACHED_DIRS)
+ m_cache.erase(lastAccessed);
+}
+
+#ifdef _DEBUG
+void CDirectoryCache::PrintStats() const
+{
+ std::unique_lock<CCriticalSection> lock(m_cs);
+ CLog::Log(LOGDEBUG, "{} - total of {} cache hits, and {} cache misses", __FUNCTION__, m_cacheHits,
+ m_cacheMisses);
+ // run through and find the oldest and the number of items cached
+ unsigned int oldest = UINT_MAX;
+ unsigned int numItems = 0;
+ unsigned int numDirs = 0;
+ for (auto i = m_cache.begin(); i != m_cache.end(); i++)
+ {
+ const CDir& dir = i->second;
+ oldest = std::min(oldest, dir.GetLastAccess());
+ numItems += dir.m_Items->Size();
+ numDirs++;
+ }
+ CLog::Log(LOGDEBUG, "{} - {} folders cached, with {} items total. Oldest is {}, current is {}",
+ __FUNCTION__, numDirs, numItems, oldest, m_accessCounter);
+}
+#endif
diff --git a/xbmc/filesystem/DirectoryCache.h b/xbmc/filesystem/DirectoryCache.h
new file mode 100644
index 0000000..2058c78
--- /dev/null
+++ b/xbmc/filesystem/DirectoryCache.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IDirectory.h"
+#include "threads/CriticalSection.h"
+
+#include <map>
+#include <memory>
+#include <set>
+
+class CFileItem;
+
+namespace XFILE
+{
+ class CDirectoryCache
+ {
+ class CDir
+ {
+ public:
+ explicit CDir(DIR_CACHE_TYPE cacheType);
+ CDir(CDir&& dir) = default;
+ CDir& operator=(CDir&& dir) = default;
+ virtual ~CDir();
+
+ void SetLastAccess(unsigned int &accessCounter);
+ unsigned int GetLastAccess() const { return m_lastAccess; }
+
+ std::unique_ptr<CFileItemList> m_Items;
+ DIR_CACHE_TYPE m_cacheType;
+ private:
+ CDir(const CDir&) = delete;
+ CDir& operator=(const CDir&) = delete;
+ unsigned int m_lastAccess;
+ };
+ public:
+ CDirectoryCache(void);
+ virtual ~CDirectoryCache(void);
+ bool GetDirectory(const std::string& strPath, CFileItemList &items, bool retrieveAll = false);
+ void SetDirectory(const std::string& strPath, const CFileItemList &items, DIR_CACHE_TYPE cacheType);
+ void ClearDirectory(const std::string& strPath);
+ void ClearFile(const std::string& strFile);
+ void ClearSubPaths(const std::string& strPath);
+ void Clear();
+ void AddFile(const std::string& strFile);
+ bool FileExists(const std::string& strPath, bool& bInCache);
+#ifdef _DEBUG
+ void PrintStats() const;
+#endif
+ protected:
+ void InitCache(const std::set<std::string>& dirs);
+ void ClearCache(std::set<std::string>& dirs);
+ void CheckIfFull();
+
+ std::map<std::string, CDir> m_cache;
+
+ mutable CCriticalSection m_cs;
+
+ unsigned int m_accessCounter;
+
+#ifdef _DEBUG
+ unsigned int m_cacheHits;
+ unsigned int m_cacheMisses;
+#endif
+ };
+}
+extern XFILE::CDirectoryCache g_directoryCache;
diff --git a/xbmc/filesystem/DirectoryFactory.cpp b/xbmc/filesystem/DirectoryFactory.cpp
new file mode 100644
index 0000000..a3016f6
--- /dev/null
+++ b/xbmc/filesystem/DirectoryFactory.cpp
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include <stdlib.h>
+#include "network/Network.h"
+#include "DirectoryFactory.h"
+#include "SpecialProtocolDirectory.h"
+#include "MultiPathDirectory.h"
+#include "StackDirectory.h"
+#include "FileDirectoryFactory.h"
+#include "PlaylistDirectory.h"
+#include "MusicDatabaseDirectory.h"
+#include "MusicSearchDirectory.h"
+#include "VideoDatabaseDirectory.h"
+#include "FavouritesDirectory.h"
+#include "LibraryDirectory.h"
+#include "EventsDirectory.h"
+#include "AddonsDirectory.h"
+#include "SourcesDirectory.h"
+#include "FTPDirectory.h"
+#include "HTTPDirectory.h"
+#include "DAVDirectory.h"
+#if defined(HAS_UDFREAD)
+#include "UDFDirectory.h"
+#endif
+#include "utils/log.h"
+#include "network/WakeOnAccess.h"
+
+#ifdef TARGET_POSIX
+#include "platform/posix/filesystem/PosixDirectory.h"
+#elif defined(TARGET_WINDOWS)
+#include "platform/win32/filesystem/Win32Directory.h"
+#ifdef TARGET_WINDOWS_STORE
+#include "platform/win10/filesystem/WinLibraryDirectory.h"
+#endif
+#endif
+#ifdef HAS_FILESYSTEM_SMB
+#ifdef TARGET_WINDOWS
+#include "platform/win32/filesystem/Win32SMBDirectory.h"
+#else
+#include "platform/posix/filesystem/SMBDirectory.h"
+#endif
+#endif
+#include "CDDADirectory.h"
+#include "PluginDirectory.h"
+#if defined(HAS_ISO9660PP)
+#include "ISO9660Directory.h"
+#endif
+#ifdef HAS_UPNP
+#include "UPnPDirectory.h"
+#endif
+#include "PVRDirectory.h"
+#if defined(TARGET_ANDROID)
+#include "platform/android/filesystem/APKDirectory.h"
+#elif defined(TARGET_DARWIN_TVOS)
+#include "platform/darwin/tvos/filesystem/TVOSDirectory.h"
+#endif
+#include "XbtDirectory.h"
+#include "ZipDirectory.h"
+#include "FileItem.h"
+#include "URL.h"
+#include "RSSDirectory.h"
+#ifdef HAS_ZEROCONF
+#include "ZeroconfDirectory.h"
+#endif
+#ifdef HAS_FILESYSTEM_NFS
+#include "NFSDirectory.h"
+#endif
+#ifdef HAVE_LIBBLURAY
+#include "BlurayDirectory.h"
+#endif
+#if defined(TARGET_ANDROID)
+#include "platform/android/filesystem/AndroidAppDirectory.h"
+#endif
+#include "ResourceDirectory.h"
+#include "ServiceBroker.h"
+#include "addons/VFSEntry.h"
+#include "utils/StringUtils.h"
+
+using namespace ADDON;
+
+using namespace XFILE;
+
+/*!
+ \brief Create a IDirectory object of the share type specified in \e strPath .
+ \param strPath Specifies the share type to access, can be a share or share with path.
+ \return IDirectory object to access the directories on the share.
+ \sa IDirectory
+ */
+IDirectory* CDirectoryFactory::Create(const CURL& url)
+{
+ if (!CWakeOnAccess::GetInstance().WakeUpHost(url))
+ return NULL;
+
+ CFileItem item(url.Get(), true);
+ IFileDirectory* pDir = CFileDirectoryFactory::Create(url, &item);
+ if (pDir)
+ return pDir;
+
+ if (!url.GetProtocol().empty() && CServiceBroker::IsAddonInterfaceUp())
+ {
+ for (const auto& vfsAddon : CServiceBroker::GetVFSAddonCache().GetAddonInstances())
+ {
+ auto prots = StringUtils::Split(vfsAddon->GetProtocols(), "|");
+
+ if (vfsAddon->HasDirectories() && std::find(prots.begin(), prots.end(), url.GetProtocol()) != prots.end())
+ return new CVFSEntryIDirectoryWrapper(vfsAddon);
+ }
+ }
+
+#ifdef TARGET_POSIX
+ if (url.GetProtocol().empty() || url.IsProtocol("file"))
+ {
+#if defined(TARGET_DARWIN_TVOS)
+ if (CTVOSDirectory::WantsDirectory(url))
+ return new CTVOSDirectory();
+#endif
+ return new CPosixDirectory();
+ }
+#elif defined(TARGET_WINDOWS)
+ if (url.GetProtocol().empty() || url.IsProtocol("file")) return new CWin32Directory();
+#else
+#error Local directory access is not implemented for this platform
+#endif
+ if (url.IsProtocol("special")) return new CSpecialProtocolDirectory();
+ if (url.IsProtocol("sources")) return new CSourcesDirectory();
+ if (url.IsProtocol("addons")) return new CAddonsDirectory();
+#if defined(HAS_DVD_DRIVE)
+ if (url.IsProtocol("cdda")) return new CCDDADirectory();
+#endif
+#if defined(HAS_ISO9660PP)
+ if (url.IsProtocol("iso9660")) return new CISO9660Directory();
+#endif
+#if defined(HAS_UDFREAD)
+ if (url.IsProtocol("udf")) return new CUDFDirectory();
+#endif
+ if (url.IsProtocol("plugin")) return new CPluginDirectory();
+#if defined(TARGET_ANDROID)
+ if (url.IsProtocol("apk")) return new CAPKDirectory();
+#endif
+ if (url.IsProtocol("zip")) return new CZipDirectory();
+ if (url.IsProtocol("xbt")) return new CXbtDirectory();
+ if (url.IsProtocol("multipath")) return new CMultiPathDirectory();
+ if (url.IsProtocol("stack")) return new CStackDirectory();
+ if (url.IsProtocol("playlistmusic")) return new CPlaylistDirectory();
+ if (url.IsProtocol("playlistvideo")) return new CPlaylistDirectory();
+ if (url.IsProtocol("musicdb")) return new CMusicDatabaseDirectory();
+ if (url.IsProtocol("musicsearch")) return new CMusicSearchDirectory();
+ if (url.IsProtocol("videodb")) return new CVideoDatabaseDirectory();
+ if (url.IsProtocol("library")) return new CLibraryDirectory();
+ if (url.IsProtocol("favourites")) return new CFavouritesDirectory();
+#if defined(TARGET_ANDROID)
+ if (url.IsProtocol("androidapp")) return new CAndroidAppDirectory();
+#endif
+#ifdef HAVE_LIBBLURAY
+ if (url.IsProtocol("bluray")) return new CBlurayDirectory();
+#endif
+ if (url.IsProtocol("resource")) return new CResourceDirectory();
+ if (url.IsProtocol("events")) return new CEventsDirectory();
+#ifdef TARGET_WINDOWS_STORE
+ if (CWinLibraryDirectory::IsValid(url)) return new CWinLibraryDirectory();
+#endif
+
+ if (url.IsProtocol("ftp") || url.IsProtocol("ftps")) return new CFTPDirectory();
+ if (url.IsProtocol("http") || url.IsProtocol("https")) return new CHTTPDirectory();
+ if (url.IsProtocol("dav") || url.IsProtocol("davs")) return new CDAVDirectory();
+#ifdef HAS_FILESYSTEM_SMB
+#ifdef TARGET_WINDOWS
+ if (url.IsProtocol("smb")) return new CWin32SMBDirectory();
+#else
+ if (url.IsProtocol("smb")) return new CSMBDirectory();
+#endif
+#endif
+#ifdef HAS_UPNP
+ if (url.IsProtocol("upnp")) return new CUPnPDirectory();
+#endif
+ if (url.IsProtocol("rss") || url.IsProtocol("rsss")) return new CRSSDirectory();
+#ifdef HAS_ZEROCONF
+ if (url.IsProtocol("zeroconf")) return new CZeroconfDirectory();
+#endif
+#ifdef HAS_FILESYSTEM_NFS
+ if (url.IsProtocol("nfs")) return new CNFSDirectory();
+#endif
+
+ if (url.IsProtocol("pvr"))
+ return new CPVRDirectory();
+
+ CLog::Log(LOGWARNING, "{} - unsupported protocol({}) in {}", __FUNCTION__, url.GetProtocol(),
+ url.GetRedacted());
+ return NULL;
+}
+
diff --git a/xbmc/filesystem/DirectoryFactory.h b/xbmc/filesystem/DirectoryFactory.h
new file mode 100644
index 0000000..71d5f6f
--- /dev/null
+++ b/xbmc/filesystem/DirectoryFactory.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IDirectory.h"
+
+namespace XFILE
+{
+/*!
+ \ingroup filesystem
+ \brief Get access to a directory of a file system.
+
+ The Factory can be used to create a directory object
+ for every file system accessable. \n
+ \n
+ Example:
+
+ \verbatim
+ std::string strShare="iso9660://";
+
+ IDirectory* pDir=CDirectoryFactory::Create(strShare);
+ \endverbatim
+ The \e pDir pointer can be used to access a directory and retrieve it's content.
+
+ When different types of shares have to be accessed use CVirtualDirectory.
+ \sa IDirectory
+ */
+class CDirectoryFactory
+{
+public:
+ static IDirectory* Create(const CURL& url);
+};
+}
diff --git a/xbmc/filesystem/DirectoryHistory.cpp b/xbmc/filesystem/DirectoryHistory.cpp
new file mode 100644
index 0000000..aecf285
--- /dev/null
+++ b/xbmc/filesystem/DirectoryHistory.cpp
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DirectoryHistory.h"
+
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+
+#include <algorithm>
+
+const std::string& CDirectoryHistory::CPathHistoryItem::GetPath(bool filter /* = false */) const
+{
+ if (filter && !m_strFilterPath.empty())
+ return m_strFilterPath;
+
+ return m_strPath;
+}
+
+CDirectoryHistory::~CDirectoryHistory()
+{
+ m_vecHistory.clear();
+ m_vecPathHistory.clear();
+}
+
+void CDirectoryHistory::RemoveSelectedItem(const std::string& strDirectory)
+{
+ HistoryMap::iterator iter = m_vecHistory.find(preparePath(strDirectory));
+ if (iter != m_vecHistory.end())
+ m_vecHistory.erase(iter);
+}
+
+void CDirectoryHistory::SetSelectedItem(const std::string& strSelectedItem, const std::string& strDirectory)
+{
+ if (strSelectedItem.empty())
+ return;
+
+ std::string strDir = preparePath(strDirectory);
+ std::string strItem = preparePath(strSelectedItem, false);
+
+ HistoryMap::iterator iter = m_vecHistory.find(strDir);
+ if (iter != m_vecHistory.end())
+ {
+ iter->second.m_strItem = strItem;
+ return;
+ }
+
+ CHistoryItem item;
+ item.m_strItem = strItem;
+ item.m_strDirectory = strDir;
+ m_vecHistory[strDir] = item;
+}
+
+const std::string& CDirectoryHistory::GetSelectedItem(const std::string& strDirectory) const
+{
+ HistoryMap::const_iterator iter = m_vecHistory.find(preparePath(strDirectory));
+ if (iter != m_vecHistory.end())
+ return iter->second.m_strItem;
+
+ return StringUtils::Empty;
+}
+
+void CDirectoryHistory::AddPath(const std::string& strPath, const std::string &strFilterPath /* = "" */)
+{
+ if (!m_vecPathHistory.empty() && m_vecPathHistory.back().m_strPath == strPath)
+ {
+ if (!strFilterPath.empty())
+ m_vecPathHistory.back().m_strFilterPath = strFilterPath;
+ return;
+ }
+
+ CPathHistoryItem item;
+ item.m_strPath = strPath;
+ item.m_strFilterPath = strFilterPath;
+ m_vecPathHistory.push_back(item);
+}
+
+void CDirectoryHistory::AddPathFront(const std::string& strPath, const std::string &strFilterPath /* = "" */)
+{
+ CPathHistoryItem item;
+ item.m_strPath = strPath;
+ item.m_strFilterPath = strFilterPath;
+ m_vecPathHistory.insert(m_vecPathHistory.begin(), item);
+}
+
+std::string CDirectoryHistory::GetParentPath(bool filter /* = false */)
+{
+ if (m_vecPathHistory.empty())
+ return "";
+
+ return m_vecPathHistory.back().GetPath(filter);
+}
+
+bool CDirectoryHistory::IsInHistory(const std::string &path) const
+{
+ std::string slashEnded(path);
+ URIUtils::AddSlashAtEnd(slashEnded);
+ for (std::vector<CPathHistoryItem>::const_iterator i = m_vecPathHistory.begin(); i != m_vecPathHistory.end(); ++i)
+ {
+ std::string testPath(i->GetPath());
+ URIUtils::AddSlashAtEnd(testPath);
+ if (slashEnded == testPath)
+ return true;
+ }
+ return false;
+}
+
+std::string CDirectoryHistory::RemoveParentPath(bool filter /* = false */)
+{
+ if (m_vecPathHistory.empty())
+ return "";
+
+ std::string strParent = GetParentPath(filter);
+ m_vecPathHistory.pop_back();
+ return strParent;
+}
+
+void CDirectoryHistory::ClearPathHistory()
+{
+ m_vecPathHistory.clear();
+}
+
+bool CDirectoryHistory::IsMusicSearchUrl(CPathHistoryItem &i)
+{
+ return StringUtils::StartsWith(i.GetPath(), "musicsearch://");
+}
+
+void CDirectoryHistory::ClearSearchHistory()
+{
+ m_vecPathHistory.erase(remove_if(m_vecPathHistory.begin(), m_vecPathHistory.end(), IsMusicSearchUrl), m_vecPathHistory.end());
+}
+
+void CDirectoryHistory::DumpPathHistory()
+{
+ // debug log
+ CLog::Log(LOGDEBUG,"Current m_vecPathHistory:");
+ for (int i = 0; i < (int)m_vecPathHistory.size(); ++i)
+ CLog::Log(LOGDEBUG, " {:02}.[{}; {}]", i, m_vecPathHistory[i].m_strPath,
+ m_vecPathHistory[i].m_strFilterPath);
+}
+
+std::string CDirectoryHistory::preparePath(const std::string &strDirectory, bool tolower /* = true */)
+{
+ std::string strDir = strDirectory;
+ if (tolower)
+ StringUtils::ToLower(strDir);
+
+ URIUtils::RemoveSlashAtEnd(strDir);
+
+ return strDir;
+}
diff --git a/xbmc/filesystem/DirectoryHistory.h b/xbmc/filesystem/DirectoryHistory.h
new file mode 100644
index 0000000..49f1873
--- /dev/null
+++ b/xbmc/filesystem/DirectoryHistory.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <map>
+#include <string>
+#include <vector>
+
+class CDirectoryHistory
+{
+public:
+ class CHistoryItem
+ {
+ public:
+ CHistoryItem() = default;
+ virtual ~CHistoryItem() = default;
+ std::string m_strItem;
+ std::string m_strDirectory;
+ };
+
+ class CPathHistoryItem
+ {
+ public:
+ CPathHistoryItem() = default;
+ virtual ~CPathHistoryItem() = default;
+
+ const std::string& GetPath(bool filter = false) const;
+
+ std::string m_strPath;
+ std::string m_strFilterPath;
+ };
+
+ CDirectoryHistory() = default;
+ virtual ~CDirectoryHistory();
+
+ void SetSelectedItem(const std::string& strSelectedItem, const std::string& strDirectory);
+ const std::string& GetSelectedItem(const std::string& strDirectory) const;
+ void RemoveSelectedItem(const std::string& strDirectory);
+
+ void AddPath(const std::string& strPath, const std::string &m_strFilterPath = "");
+ void AddPathFront(const std::string& strPath, const std::string &m_strFilterPath = "");
+ std::string GetParentPath(bool filter = false);
+ std::string RemoveParentPath(bool filter = false);
+ void ClearPathHistory();
+ void ClearSearchHistory();
+ void DumpPathHistory();
+
+ /*! \brief Returns whether a path is in the history.
+ \param path to test
+ \return true if the path is in the history, false otherwise.
+ */
+ bool IsInHistory(const std::string &path) const;
+
+private:
+ static std::string preparePath(const std::string &strDirectory, bool tolower = true);
+
+ typedef std::map<std::string, CHistoryItem> HistoryMap;
+ HistoryMap m_vecHistory;
+ std::vector<CPathHistoryItem> m_vecPathHistory; ///< History of traversed directories
+ static bool IsMusicSearchUrl(CPathHistoryItem &i);
+};
diff --git a/xbmc/filesystem/DllLibCurl.cpp b/xbmc/filesystem/DllLibCurl.cpp
new file mode 100644
index 0000000..8367e9d
--- /dev/null
+++ b/xbmc/filesystem/DllLibCurl.cpp
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DllLibCurl.h"
+
+#include "threads/SystemClock.h"
+#include "utils/log.h"
+
+#include <assert.h>
+#include <mutex>
+
+namespace XCURL
+{
+CURLcode DllLibCurl::global_init(long flags)
+{
+ return curl_global_init(flags);
+}
+
+void DllLibCurl::global_cleanup()
+{
+ curl_global_cleanup();
+}
+
+CURL_HANDLE* DllLibCurl::easy_init()
+{
+ return curl_easy_init();
+}
+
+CURLcode DllLibCurl::easy_perform(CURL_HANDLE* handle)
+{
+ return curl_easy_perform(handle);
+}
+
+CURLcode DllLibCurl::easy_pause(CURL_HANDLE* handle, int bitmask)
+{
+ return curl_easy_pause(handle, bitmask);
+}
+
+void DllLibCurl::easy_reset(CURL_HANDLE* handle)
+{
+ curl_easy_reset(handle);
+}
+
+void DllLibCurl::easy_cleanup(CURL_HANDLE* handle)
+{
+ curl_easy_cleanup(handle);
+}
+
+CURL_HANDLE* DllLibCurl::easy_duphandle(CURL_HANDLE* handle)
+{
+ return curl_easy_duphandle(handle);
+}
+
+CURLM* DllLibCurl::multi_init()
+{
+ return curl_multi_init();
+}
+
+CURLMcode DllLibCurl::multi_add_handle(CURLM* multi_handle, CURL_HANDLE* easy_handle)
+{
+ return curl_multi_add_handle(multi_handle, easy_handle);
+}
+
+CURLMcode DllLibCurl::multi_perform(CURLM* multi_handle, int* running_handles)
+{
+ return curl_multi_perform(multi_handle, running_handles);
+}
+
+CURLMcode DllLibCurl::multi_remove_handle(CURLM* multi_handle, CURL_HANDLE* easy_handle)
+{
+ return curl_multi_remove_handle(multi_handle, easy_handle);
+}
+
+CURLMcode DllLibCurl::multi_fdset(
+ CURLM* multi_handle, fd_set* read_fd_set, fd_set* write_fd_set, fd_set* exc_fd_set, int* max_fd)
+{
+ return curl_multi_fdset(multi_handle, read_fd_set, write_fd_set, exc_fd_set, max_fd);
+}
+
+CURLMcode DllLibCurl::multi_timeout(CURLM* multi_handle, long* timeout)
+{
+ return curl_multi_timeout(multi_handle, timeout);
+}
+
+CURLMsg* DllLibCurl::multi_info_read(CURLM* multi_handle, int* msgs_in_queue)
+{
+ return curl_multi_info_read(multi_handle, msgs_in_queue);
+}
+
+CURLMcode DllLibCurl::multi_cleanup(CURLM* handle)
+{
+ return curl_multi_cleanup(handle);
+}
+
+curl_slist* DllLibCurl::slist_append(curl_slist* list, const char* to_append)
+{
+ return curl_slist_append(list, to_append);
+}
+
+void DllLibCurl::slist_free_all(curl_slist* list)
+{
+ curl_slist_free_all(list);
+}
+
+const char* DllLibCurl::easy_strerror(CURLcode code)
+{
+ return curl_easy_strerror(code);
+}
+
+DllLibCurlGlobal::DllLibCurlGlobal()
+{
+ /* we handle this ourself */
+ if (curl_global_init(CURL_GLOBAL_ALL))
+ {
+ CLog::Log(LOGERROR, "Error initializing libcurl");
+ }
+}
+
+DllLibCurlGlobal::~DllLibCurlGlobal()
+{
+ // close libcurl
+ curl_global_cleanup();
+}
+
+void DllLibCurlGlobal::CheckIdle()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ /* 20 seconds idle time before closing handle */
+ const unsigned int idletime = 30000;
+
+ VEC_CURLSESSIONS::iterator it = m_sessions.begin();
+ while (it != m_sessions.end())
+ {
+ auto now = std::chrono::steady_clock::now();
+ auto duration =
+ std::chrono::duration_cast<std::chrono::milliseconds>(now - it->m_idletimestamp);
+
+ if (!it->m_busy && duration.count() > idletime)
+ {
+ CLog::Log(LOGDEBUG, "{} - Closing session to {}://{} (easy={}, multi={})", __FUNCTION__,
+ it->m_protocol, it->m_hostname, fmt::ptr(it->m_easy), fmt::ptr(it->m_multi));
+
+ if (it->m_multi && it->m_easy)
+ multi_remove_handle(it->m_multi, it->m_easy);
+ if (it->m_easy)
+ easy_cleanup(it->m_easy);
+ if (it->m_multi)
+ multi_cleanup(it->m_multi);
+
+ it = m_sessions.erase(it);
+ continue;
+ }
+ ++it;
+ }
+}
+
+void DllLibCurlGlobal::easy_acquire(const char* protocol,
+ const char* hostname,
+ CURL_HANDLE** easy_handle,
+ CURLM** multi_handle)
+{
+ assert(easy_handle != NULL);
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ for (auto& it : m_sessions)
+ {
+ if (!it.m_busy)
+ {
+ /* allow reuse of requester is trying to connect to same host */
+ /* curl will take care of any differences in username/password */
+ if (it.m_protocol.compare(protocol) == 0 && it.m_hostname.compare(hostname) == 0)
+ {
+ it.m_busy = true;
+ if (easy_handle)
+ {
+ if (!it.m_easy)
+ it.m_easy = easy_init();
+
+ *easy_handle = it.m_easy;
+ }
+
+ if (multi_handle)
+ {
+ if (!it.m_multi)
+ it.m_multi = multi_init();
+
+ *multi_handle = it.m_multi;
+ }
+
+ return;
+ }
+ }
+ }
+
+ SSession session = {};
+ session.m_busy = true;
+ session.m_protocol = protocol;
+ session.m_hostname = hostname;
+
+ if (easy_handle)
+ {
+ session.m_easy = easy_init();
+ *easy_handle = session.m_easy;
+ }
+
+ if (multi_handle)
+ {
+ session.m_multi = multi_init();
+ *multi_handle = session.m_multi;
+ }
+
+ m_sessions.push_back(session);
+
+ CLog::Log(LOGDEBUG, "{} - Created session to {}://{}", __FUNCTION__, protocol, hostname);
+}
+
+void DllLibCurlGlobal::easy_release(CURL_HANDLE** easy_handle, CURLM** multi_handle)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ CURL_HANDLE* easy = NULL;
+ CURLM* multi = NULL;
+
+ if (easy_handle)
+ {
+ easy = *easy_handle;
+ *easy_handle = NULL;
+ }
+
+ if (multi_handle)
+ {
+ multi = *multi_handle;
+ *multi_handle = NULL;
+ }
+
+ for (auto& it : m_sessions)
+ {
+ if (it.m_easy == easy && (multi == nullptr || it.m_multi == multi))
+ {
+ /* reset session so next caller doesn't reuse options, only connections */
+ /* will reset verbose too so it won't print that it closed connections on cleanup*/
+ easy_reset(easy);
+ it.m_busy = false;
+ it.m_idletimestamp = std::chrono::steady_clock::now();
+ return;
+ }
+ }
+}
+
+CURL_HANDLE* DllLibCurlGlobal::easy_duphandle(CURL_HANDLE* easy_handle)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ for (const auto& it : m_sessions)
+ {
+ if (it.m_easy == easy_handle)
+ {
+ SSession session = it;
+ session.m_easy = DllLibCurl::easy_duphandle(easy_handle);
+ m_sessions.push_back(session);
+ return session.m_easy;
+ }
+ }
+ return DllLibCurl::easy_duphandle(easy_handle);
+}
+
+void DllLibCurlGlobal::easy_duplicate(CURL_HANDLE* easy,
+ const CURLM* multi,
+ CURL_HANDLE** easy_out,
+ CURLM** multi_out)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ if (easy_out && easy)
+ *easy_out = DllLibCurl::easy_duphandle(easy);
+
+ if (multi_out && multi)
+ *multi_out = DllLibCurl::multi_init();
+
+ for (const auto& it : m_sessions)
+ {
+ if (it.m_easy == easy)
+ {
+ SSession session = it;
+ if (easy_out && easy)
+ session.m_easy = *easy_out;
+ else
+ session.m_easy = NULL;
+
+ if (multi_out && multi)
+ session.m_multi = *multi_out;
+ else
+ session.m_multi = NULL;
+
+ m_sessions.push_back(session);
+ return;
+ }
+ }
+}
+} // namespace XCURL
diff --git a/xbmc/filesystem/DllLibCurl.h b/xbmc/filesystem/DllLibCurl.h
new file mode 100644
index 0000000..760f28d
--- /dev/null
+++ b/xbmc/filesystem/DllLibCurl.h
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "threads/CriticalSection.h"
+
+#include <stdio.h>
+#include <string>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <type_traits>
+#include <vector>
+
+#define CURL CURL_HANDLE
+#include <curl/curl.h>
+#undef CURL
+
+namespace XCURL
+{
+
+class DllLibCurl
+{
+public:
+ virtual ~DllLibCurl() = default;
+
+ CURLcode global_init(long flags);
+ void global_cleanup();
+ CURL_HANDLE* easy_init();
+ template<typename... Args>
+ CURLcode easy_setopt(CURL_HANDLE* handle, CURLoption option, Args... args)
+ {
+ return curl_easy_setopt(handle, option, std::forward<Args>(args)...);
+ }
+ CURLcode easy_perform(CURL_HANDLE* handle);
+ CURLcode easy_pause(CURL_HANDLE* handle, int bitmask);
+ void easy_reset(CURL_HANDLE* handle);
+ template<typename... Args>
+ CURLcode easy_getinfo(CURL_HANDLE* curl, CURLINFO info, Args... args)
+ {
+ return curl_easy_getinfo(curl, info, std::forward<Args>(args)...);
+ }
+ void easy_cleanup(CURL_HANDLE* handle);
+ virtual CURL_HANDLE* easy_duphandle(CURL_HANDLE* handle);
+ CURLM* multi_init(void);
+ CURLMcode multi_add_handle(CURLM* multi_handle, CURL_HANDLE* easy_handle);
+ CURLMcode multi_perform(CURLM* multi_handle, int* running_handles);
+ CURLMcode multi_remove_handle(CURLM* multi_handle, CURL_HANDLE* easy_handle);
+ CURLMcode multi_fdset(CURLM* multi_handle,
+ fd_set* read_fd_set,
+ fd_set* write_fd_set,
+ fd_set* exc_fd_set,
+ int* max_fd);
+ CURLMcode multi_timeout(CURLM* multi_handle, long* timeout);
+ CURLMsg* multi_info_read(CURLM* multi_handle, int* msgs_in_queue);
+ CURLMcode multi_cleanup(CURLM* handle);
+ curl_slist* slist_append(curl_slist* list, const char* to_append);
+ void slist_free_all(curl_slist* list);
+ const char* easy_strerror(CURLcode code);
+};
+
+class DllLibCurlGlobal : public DllLibCurl
+{
+public:
+ DllLibCurlGlobal();
+ ~DllLibCurlGlobal();
+ /* extend interface with buffered functions */
+ void easy_acquire(const char* protocol,
+ const char* hostname,
+ CURL_HANDLE** easy_handle,
+ CURLM** multi_handle);
+ void easy_release(CURL_HANDLE** easy_handle, CURLM** multi_handle);
+ void easy_duplicate(CURL_HANDLE* easy,
+ const CURLM* multi,
+ CURL_HANDLE** easy_out,
+ CURLM** multi_out);
+ CURL_HANDLE* easy_duphandle(CURL_HANDLE* easy_handle) override;
+ void CheckIdle();
+
+ /* overloaded load and unload with reference counter */
+
+ /* structure holding a session info */
+ typedef struct SSession
+ {
+ std::chrono::time_point<std::chrono::steady_clock>
+ m_idletimestamp; // timestamp of when this object when idle
+ std::string m_protocol;
+ std::string m_hostname;
+ bool m_busy;
+ CURL_HANDLE* m_easy;
+ CURLM* m_multi;
+ } SSession;
+
+ typedef std::vector<SSession> VEC_CURLSESSIONS;
+
+ VEC_CURLSESSIONS m_sessions;
+ CCriticalSection m_critSection;
+};
+} // namespace XCURL
+
+extern XCURL::DllLibCurlGlobal g_curlInterface;
diff --git a/xbmc/filesystem/EventsDirectory.cpp b/xbmc/filesystem/EventsDirectory.cpp
new file mode 100644
index 0000000..d533475
--- /dev/null
+++ b/xbmc/filesystem/EventsDirectory.cpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2015-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+
+#include "EventsDirectory.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "events/EventLog.h"
+#include "utils/StringUtils.h"
+
+using namespace XFILE;
+
+bool CEventsDirectory::GetDirectory(const CURL& url, CFileItemList &items)
+{
+ items.ClearProperties();
+ items.SetContent("events");
+
+ auto log = CServiceBroker::GetEventLog();
+ Events events;
+
+ std::string hostname = url.GetHostName();
+ if (hostname.empty())
+ events = log->Get();
+ else
+ {
+ bool includeHigherLevels = false;
+ // check if we should only retrieve events from a specific level or
+ // also from all higher levels
+ if (StringUtils::EndsWith(hostname, "+"))
+ {
+ includeHigherLevels = true;
+
+ // remove the "+" from the end of the hostname
+ hostname = hostname.substr(0, hostname.size() - 1);
+ }
+
+ EventLevel level = CEventLog::EventLevelFromString(hostname);
+
+ // get the events of the specified level(s)
+ events = log->Get(level, includeHigherLevels);
+ }
+
+ for (const auto& eventItem : events)
+ items.Add(EventToFileItem(eventItem));
+
+ return true;
+}
+
+std::shared_ptr<CFileItem> CEventsDirectory::EventToFileItem(
+ const std::shared_ptr<const IEvent>& eventItem)
+{
+ if (!eventItem)
+ return CFileItemPtr();
+
+ CFileItemPtr item(new CFileItem(eventItem));
+
+ item->SetProperty(PROPERTY_EVENT_IDENTIFIER, eventItem->GetIdentifier());
+ item->SetProperty(PROPERTY_EVENT_LEVEL, CEventLog::EventLevelToString(eventItem->GetLevel()));
+ item->SetProperty(PROPERTY_EVENT_DESCRIPTION, eventItem->GetDescription());
+
+ return item;
+}
diff --git a/xbmc/filesystem/EventsDirectory.h b/xbmc/filesystem/EventsDirectory.h
new file mode 100644
index 0000000..adb26c3
--- /dev/null
+++ b/xbmc/filesystem/EventsDirectory.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "filesystem/IDirectory.h"
+
+#include <memory>
+
+class CFileItem;
+class CFileItemList;
+class IEvent;
+
+#define PROPERTY_EVENT_IDENTIFIER "Event.ID"
+#define PROPERTY_EVENT_LEVEL "Event.Level"
+#define PROPERTY_EVENT_DESCRIPTION "Event.Description"
+
+namespace XFILE
+{
+ class CEventsDirectory : public IDirectory
+ {
+ public:
+ CEventsDirectory() = default;
+ ~CEventsDirectory() override = default;
+
+ // implementations of IDirectory
+ bool GetDirectory(const CURL& url, CFileItemList& items) override;
+ bool Create(const CURL& url) override { return true; }
+ bool Exists(const CURL& url) override { return true; }
+ bool AllowAll() const override { return true; }
+
+ static std::shared_ptr<CFileItem> EventToFileItem(
+ const std::shared_ptr<const IEvent>& activity);
+ };
+}
diff --git a/xbmc/filesystem/FTPDirectory.cpp b/xbmc/filesystem/FTPDirectory.cpp
new file mode 100644
index 0000000..46d3d7d
--- /dev/null
+++ b/xbmc/filesystem/FTPDirectory.cpp
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "FTPDirectory.h"
+
+#include "CurlFile.h"
+#include "FTPParse.h"
+#include "FileItem.h"
+#include "URL.h"
+#include "utils/CharsetConverter.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+
+#include <climits>
+
+using namespace XFILE;
+
+CFTPDirectory::CFTPDirectory(void) = default;
+CFTPDirectory::~CFTPDirectory(void) = default;
+
+bool CFTPDirectory::GetDirectory(const CURL& url2, CFileItemList &items)
+{
+ CCurlFile reader;
+
+ CURL url(url2);
+
+ std::string path = url.GetFileName();
+ if( !path.empty() && !StringUtils::EndsWith(path, "/") )
+ {
+ path += "/";
+ url.SetFileName(path);
+ }
+
+ if (!reader.Open(url))
+ return false;
+
+ bool serverNotUseUTF8 = url.GetProtocolOption("utf8") == "0";
+
+ char buffer[MAX_PATH + 1024];
+ while( reader.ReadString(buffer, sizeof(buffer)) )
+ {
+ std::string strBuffer = buffer;
+
+ StringUtils::RemoveCRLF(strBuffer);
+
+ CFTPParse parse;
+ if (parse.FTPParse(strBuffer))
+ {
+ if( parse.getName().length() == 0 )
+ continue;
+
+ if( parse.getFlagtrycwd() == 0 && parse.getFlagtryretr() == 0 )
+ continue;
+
+ /* buffer name */
+ std::string name;
+ name.assign(parse.getName());
+
+ if( name == ".." || name == "." )
+ continue;
+
+ // server returned filename could in utf8 or non-utf8 encoding
+ // we need utf8, so convert it to utf8 anyway
+ g_charsetConverter.unknownToUTF8(name);
+
+ // convert got empty result, ignore it
+ if (name.empty())
+ continue;
+
+ if (serverNotUseUTF8 || name != parse.getName())
+ // non-utf8 name path, tag it with protocol option.
+ // then we can talk to server with the same encoding in CurlFile according to this tag.
+ url.SetProtocolOption("utf8", "0");
+ else
+ url.RemoveProtocolOption("utf8");
+
+ CFileItemPtr pItem(new CFileItem(name));
+
+ pItem->m_bIsFolder = parse.getFlagtrycwd() != 0;
+ std::string filePath = path + name;
+ if (pItem->m_bIsFolder)
+ URIUtils::AddSlashAtEnd(filePath);
+
+ /* qualify the url with host and all */
+ url.SetFileName(filePath);
+ pItem->SetPath(url.Get());
+
+ pItem->m_dwSize = parse.getSize();
+ pItem->m_dateTime=parse.getTime();
+
+ items.Add(pItem);
+ }
+ }
+
+ return true;
+}
+
+bool CFTPDirectory::Exists(const CURL& url)
+{
+ // make sure ftp dir ends with slash,
+ // curl need to known it's a dir to check ftp directory existence.
+ std::string file = url.Get();
+ URIUtils::AddSlashAtEnd(file);
+
+ CCurlFile ftp;
+ CURL url2(file);
+ return ftp.Exists(url2);
+}
diff --git a/xbmc/filesystem/FTPDirectory.h b/xbmc/filesystem/FTPDirectory.h
new file mode 100644
index 0000000..0a0c6db
--- /dev/null
+++ b/xbmc/filesystem/FTPDirectory.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IDirectory.h"
+
+namespace XFILE
+{
+ class CFTPDirectory : public IDirectory
+ {
+ public:
+ CFTPDirectory(void);
+ ~CFTPDirectory(void) override;
+ bool GetDirectory(const CURL& url, CFileItemList &items) override;
+ bool Exists(const CURL& url) override;
+ private:
+ };
+}
+
diff --git a/xbmc/filesystem/FTPParse.cpp b/xbmc/filesystem/FTPParse.cpp
new file mode 100644
index 0000000..fb5e035
--- /dev/null
+++ b/xbmc/filesystem/FTPParse.cpp
@@ -0,0 +1,504 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "FTPParse.h"
+
+#include <cmath>
+
+#include <pcrecpp.h>
+
+CFTPParse::CFTPParse()
+{
+ m_flagtrycwd = 0;
+ m_flagtryretr = 0;
+ m_size = 0;
+}
+
+std::string CFTPParse::getName()
+{
+ return m_name;
+}
+
+int CFTPParse::getFlagtrycwd()
+{
+ return m_flagtrycwd;
+}
+
+int CFTPParse::getFlagtryretr()
+{
+ return m_flagtryretr;
+}
+
+uint64_t CFTPParse::getSize()
+{
+ return m_size;
+}
+
+time_t CFTPParse::getTime()
+{
+ return m_time;
+}
+
+void CFTPParse::setTime(const std::string& str)
+{
+ /* Variables used to capture patterns via the regexes */
+ std::string month;
+ std::string day;
+ std::string year;
+ std::string hour;
+ std::string minute;
+ std::string second;
+ std::string am_or_pm;
+
+ /* time struct used to set the time_t variable */
+ struct tm time_struct = {};
+
+ /* Regex to read Unix, NetWare and NetPresenz time format */
+ pcrecpp::RE unix_re("^([A-Za-z]{3})" // month
+ "\\s+(\\d{1,2})" // day of month
+ "\\s+([:\\d]{4,5})$" // time of day or year
+ );
+
+ /* Regex to read MultiNet time format */
+ pcrecpp::RE multinet_re("^(\\d{1,2})" // day of month
+ "-([A-Za-z]{3})" // month
+ "-(\\d{4})" // year
+ "\\s+(\\d{2})" // hour
+ ":(\\d{2})" // minute
+ "(:(\\d{2}))?$" // second
+ );
+
+ /* Regex to read MSDOS time format */
+ pcrecpp::RE msdos_re("^(\\d{2})" // month
+ "-(\\d{2})" // day of month
+ "-(\\d{2})" // year
+ "\\s+(\\d{2})" // hour
+ ":(\\d{2})" // minute
+ "([AP]M)$" // AM or PM
+ );
+
+ if (unix_re.FullMatch(str, &month, &day, &year))
+ {
+ /* set the month */
+ if (pcrecpp::RE("jan",
+ pcrecpp::RE_Options().set_caseless(true)).FullMatch(month))
+ time_struct.tm_mon = 0;
+ else if (pcrecpp::RE("feb",
+ pcrecpp::RE_Options().set_caseless(true)).FullMatch(month))
+ time_struct.tm_mon = 1;
+ else if (pcrecpp::RE("mar",
+ pcrecpp::RE_Options().set_caseless(true)).FullMatch(month))
+ time_struct.tm_mon = 2;
+ else if (pcrecpp::RE("apr",
+ pcrecpp::RE_Options().set_caseless(true)).FullMatch(month))
+ time_struct.tm_mon = 3;
+ else if (pcrecpp::RE("may",
+ pcrecpp::RE_Options().set_caseless(true)).FullMatch(month))
+ time_struct.tm_mon = 4;
+ else if (pcrecpp::RE("jun",
+ pcrecpp::RE_Options().set_caseless(true)).FullMatch(month))
+ time_struct.tm_mon = 5;
+ else if (pcrecpp::RE("jul",
+ pcrecpp::RE_Options().set_caseless(true)).FullMatch(month))
+ time_struct.tm_mon = 6;
+ else if (pcrecpp::RE("aug",
+ pcrecpp::RE_Options().set_caseless(true)).FullMatch(month))
+ time_struct.tm_mon = 7;
+ else if (pcrecpp::RE("sep",
+ pcrecpp::RE_Options().set_caseless(true)).FullMatch(month))
+ time_struct.tm_mon = 8;
+ else if (pcrecpp::RE("oct",
+ pcrecpp::RE_Options().set_caseless(true)).FullMatch(month))
+ time_struct.tm_mon = 9;
+ else if (pcrecpp::RE("nov",
+ pcrecpp::RE_Options().set_caseless(true)).FullMatch(month))
+ time_struct.tm_mon = 10;
+ else if (pcrecpp::RE("dec",
+ pcrecpp::RE_Options().set_caseless(true)).FullMatch(month))
+ time_struct.tm_mon = 11;
+
+ /* set the day of the month */
+ time_struct.tm_mday = atoi(day.c_str());
+
+ time_t t = time(NULL);
+ struct tm *current_time;
+#ifdef LOCALTIME_R
+ struct tm result = {};
+ current_time = localtime_r(&t, &result);
+#else
+ current_time = localtime(&t);
+#endif
+ if (pcrecpp::RE("(\\d{2}):(\\d{2})").FullMatch(year, &hour, &minute))
+ {
+ /* set the hour and minute */
+ time_struct.tm_hour = atoi(hour.c_str());
+ time_struct.tm_min = atoi(minute.c_str());
+
+ /* set the year */
+ if ((current_time->tm_mon - time_struct.tm_mon < 0) ||
+ ((current_time->tm_mon - time_struct.tm_mon == 0) &&
+ (current_time->tm_mday - time_struct.tm_mday < 0)))
+ time_struct.tm_year = current_time->tm_year - 1;
+ else
+ time_struct.tm_year = current_time->tm_year;
+ }
+ else
+ {
+ /* set the year */
+ time_struct.tm_year = atoi(year.c_str()) - 1900;
+ }
+
+ /* set the day of the week */
+ time_struct.tm_wday = getDayOfWeek(time_struct.tm_mon + 1,
+ time_struct.tm_mday,
+ time_struct.tm_year + 1900);
+ }
+ else if (multinet_re.FullMatch(str, &day, &month, &year,
+ &hour, &minute, (void*)NULL, &second))
+ {
+ /* set the month */
+ if (pcrecpp::RE("jan",
+ pcrecpp::RE_Options().set_caseless(true)).FullMatch(month))
+ time_struct.tm_mon = 0;
+ else if (pcrecpp::RE("feb",
+ pcrecpp::RE_Options().set_caseless(true)).FullMatch(month))
+ time_struct.tm_mon = 1;
+ else if (pcrecpp::RE("mar",
+ pcrecpp::RE_Options().set_caseless(true)).FullMatch(month))
+ time_struct.tm_mon = 2;
+ else if (pcrecpp::RE("apr",
+ pcrecpp::RE_Options().set_caseless(true)).FullMatch(month))
+ time_struct.tm_mon = 3;
+ else if (pcrecpp::RE("may",
+ pcrecpp::RE_Options().set_caseless(true)).FullMatch(month))
+ time_struct.tm_mon = 4;
+ else if (pcrecpp::RE("jun",
+ pcrecpp::RE_Options().set_caseless(true)).FullMatch(month))
+ time_struct.tm_mon = 5;
+ else if (pcrecpp::RE("jul",
+ pcrecpp::RE_Options().set_caseless(true)).FullMatch(month))
+ time_struct.tm_mon = 6;
+ else if (pcrecpp::RE("aug",
+ pcrecpp::RE_Options().set_caseless(true)).FullMatch(month))
+ time_struct.tm_mon = 7;
+ else if (pcrecpp::RE("sep",
+ pcrecpp::RE_Options().set_caseless(true)).FullMatch(month))
+ time_struct.tm_mon = 8;
+ else if (pcrecpp::RE("oct",
+ pcrecpp::RE_Options().set_caseless(true)).FullMatch(month))
+ time_struct.tm_mon = 9;
+ else if (pcrecpp::RE("nov",
+ pcrecpp::RE_Options().set_caseless(true)).FullMatch(month))
+ time_struct.tm_mon = 10;
+ else if (pcrecpp::RE("dec",
+ pcrecpp::RE_Options().set_caseless(true)).FullMatch(month))
+ time_struct.tm_mon = 11;
+
+ /* set the day of the month and year */
+ time_struct.tm_mday = atoi(day.c_str());
+ time_struct.tm_year = atoi(year.c_str()) - 1900;
+
+ /* set the hour and minute */
+ time_struct.tm_hour = atoi(hour.c_str());
+ time_struct.tm_min = atoi(minute.c_str());
+
+ /* set the second if given*/
+ if (second.length() > 0)
+ time_struct.tm_sec = atoi(second.c_str());
+
+ /* set the day of the week */
+ time_struct.tm_wday = getDayOfWeek(time_struct.tm_mon + 1,
+ time_struct.tm_mday,
+ time_struct.tm_year + 1900);
+ }
+ else if (msdos_re.FullMatch(str, &month, &day,
+ &year, &hour, &minute, &am_or_pm))
+ {
+ /* set the month and the day of the month */
+ time_struct.tm_mon = atoi(month.c_str()) - 1;
+ time_struct.tm_mday = atoi(day.c_str());
+
+ /* set the year */
+ time_struct.tm_year = atoi(year.c_str());
+ if (time_struct.tm_year < 70)
+ time_struct.tm_year += 100;
+
+ /* set the hour */
+ time_struct.tm_hour = atoi(hour.c_str());
+ if (time_struct.tm_hour == 12)
+ time_struct.tm_hour -= 12;
+ if (pcrecpp::RE("PM").FullMatch(am_or_pm))
+ time_struct.tm_hour += 12;
+
+ /* set the minute */
+ time_struct.tm_min = atoi(minute.c_str());
+
+ /* set the day of the week */
+ time_struct.tm_wday = getDayOfWeek(time_struct.tm_mon + 1,
+ time_struct.tm_mday,
+ time_struct.tm_year + 1900);
+ }
+
+ /* now set m_time */
+ m_time = mktime(&time_struct);
+}
+
+int CFTPParse::getDayOfWeek(int month, int date, int year)
+{
+ /* Here we use the Doomsday rule to calculate the day of the week */
+
+ /* First determine the anchor day */
+ int anchor;
+ if (year >= 1900 && year < 2000)
+ anchor = 3;
+ else if (year >= 2000 && year < 2100)
+ anchor = 2;
+ else if (year >= 2100 && year < 2200)
+ anchor = 0;
+ else if (year >= 2200 && year < 2300)
+ anchor = 5;
+ else // must have been given an invalid year :-/
+ return -1;
+
+ /* Now determine the doomsday */
+ int y = year % 100;
+ int dday =
+ ((y/12 + (y % 12) + ((y % 12)/4)) % 7) + anchor;
+
+ /* Determine if the given year is a leap year */
+ int leap_year = 0;
+ if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0))
+ leap_year = 1;
+
+ /* Now select a doomsday for the given month */
+ int mdday = 1;
+ if (month == 1)
+ {
+ if (leap_year)
+ mdday = 4;
+ else
+ mdday = 3;
+ }
+ if (month == 2)
+ {
+ if (leap_year)
+ mdday = 1;
+ else
+ mdday = 7;
+ }
+ if (month == 3)
+ mdday = 7;
+ if (month == 4)
+ mdday = 4;
+ if (month == 5)
+ mdday = 9;
+ if (month == 6)
+ mdday = 6;
+ if (month == 7)
+ mdday = 11;
+ if (month == 8)
+ mdday = 8;
+ if (month == 9)
+ mdday = 5;
+ if (month == 10)
+ mdday = 10;
+ if (month == 11)
+ mdday = 9;
+ if (month == 12)
+ mdday = 12;
+
+ /* Now calculate the day of the week
+ * Sunday = 0, Monday = 1, Tuesday = 2, Wednesday = 3, Thursday = 4,
+ * Friday = 5, Saturday = 6.
+ */
+ int day_of_week = ((date - 1) % 7) - ((mdday - 1) % 7) + dday;
+ if (day_of_week >= 7)
+ day_of_week -= 7;
+
+ return day_of_week;
+}
+
+int CFTPParse::FTPParse(const std::string& str)
+{
+ /* Various variable to capture patterns via the regexes */
+ std::string permissions;
+ std::string link_count;
+ std::string owner;
+ std::string group;
+ std::string size;
+ std::string date;
+ std::string name;
+ std::string type;
+ std::string stuff;
+ std::string facts;
+ std::string version;
+ std::string file_id;
+
+ /* Regex for standard Unix listing formats */
+ pcrecpp::RE unix_re("^([-bcdlps])" // type
+ "([-rwxXsStT]{9})" // permissions
+ "\\s+(\\d+)" // hard link count
+ "\\s+(\\w+)" // owner
+ "\\s+(\\w+)" // group
+ "\\s+(\\d+)" // size
+ "\\s+([A-Za-z]{3}\\s+\\d{1,2}\\s+[:\\d]{4,5})" // modification date
+ "\\s+(.+)$" // name
+ );
+
+ /* Regex for NetWare listing formats */
+ /* See http://www.novell.com/documentation/oes/ftp_enu/data/a3ep22p.html#fbhbaijf */
+ pcrecpp::RE netware_re("^([-d])" // type
+ "\\s+(\\[[-SRWCIEMFA]{8}\\])" // rights
+ "\\s+(\\w+)" // owner
+ "\\s+(\\d+)" // size
+ "\\s+([A-Za-z]{3}\\s+\\d{1,2}\\s+[:\\d]{4,5})" // time
+ "\\s+(.+)$" // name
+ );
+
+ /* Regex for NetPresenz */
+ /* See http://files.stairways.com/other/ftp-list-specs-info.txt */
+ /* Here we will capture permissions and size if given */
+ pcrecpp::RE netpresenz_re("^([-dl])" // type
+ "([-rwx]{9}|)" // permissions
+ "\\s+(.*)" // stuff
+ "\\s+(\\d+|)" // size
+ "\\s+([A-Za-z]{3}\\s+\\d{1,2}\\s+[:\\d]{4,5})" // modification date
+ "\\s+(.+)$" // name
+ );
+
+ /* Regex for EPLF */
+ /* See http://cr.yp.to/ftp/list/eplf.html */
+ /* SAVE: "(/,|r,|s\\d+,|m\\d+,|i[\\d!#@$%^&*()]+(\\.[\\d!#@$%^&*()]+|),)+" */
+ pcrecpp::RE eplf_re("^\\+" // initial "plus" sign
+ "([^\\s]+)" // facts
+ "\\s(.+)$" // name
+ );
+
+ /* Regex for MultiNet */
+ /* Best documentation found was
+ * http://www-sld.slac.stanford.edu/SLDWWW/workbook/vms_files.html */
+ pcrecpp::RE multinet_re("^([^;]+)" // name
+ ";(\\d+)" // version
+ "\\s+([\\d/]+)" // file id
+ "\\s+(\\d{1,2}-[A-Za-z]{3}-\\d{4}\\s+\\d{2}:\\d{2}(:\\d{2})?)" // date
+ "\\s+\\[([^\\]]+)\\]" // owner,group
+ "\\s+\\(([^\\)]+)\\)$" // permissions
+ );
+
+ /* Regex for MSDOS */
+ pcrecpp::RE msdos_re("^(\\d{2}-\\d{2}-\\d{2}\\s+\\d{2}:\\d{2}[AP]M)" // date
+ "\\s+(<DIR>|[\\d]+)" // dir or size
+ "\\s+(.+)$" // name
+ );
+
+ if (unix_re.FullMatch(str, &type, &permissions, &link_count, &owner, &group, &size, &date, &name))
+ {
+ m_name = name;
+ m_size = (uint64_t)strtod(size.c_str(), NULL);
+ if (pcrecpp::RE("d").FullMatch(type))
+ m_flagtrycwd = 1;
+ if (pcrecpp::RE("-").FullMatch(type))
+ m_flagtryretr = 1;
+ if (pcrecpp::RE("l").FullMatch(type))
+ {
+ m_flagtrycwd = m_flagtryretr = 1;
+ // handle symlink
+ size_t found = m_name.find(" -> ");
+ if (found != std::string::npos)
+ m_name = m_name.substr(0, found);
+ }
+ setTime(date);
+
+ return 1;
+ }
+ if (netware_re.FullMatch(str, &type, &permissions, &owner, &size, &date, &name))
+ {
+ m_name = name;
+ m_size = (uint64_t)strtod(size.c_str(), NULL);
+ if (pcrecpp::RE("d").FullMatch(type))
+ m_flagtrycwd = 1;
+ if (pcrecpp::RE("-").FullMatch(type))
+ m_flagtryretr = 1;
+ setTime(date);
+
+ return 1;
+ }
+ if (netpresenz_re.FullMatch(str, &type, &permissions, &stuff, &size, &date, &name))
+ {
+ m_name = name;
+ m_size = (uint64_t)strtod(size.c_str(), NULL);
+ if (pcrecpp::RE("d").FullMatch(type))
+ m_flagtrycwd = 1;
+ if (pcrecpp::RE("-").FullMatch(type))
+ m_flagtryretr = 1;
+ if (pcrecpp::RE("l").FullMatch(type))
+ {
+ m_flagtrycwd = m_flagtryretr = 1;
+ // handle symlink
+ size_t found = m_name.find(" -> ");
+ if (found != std::string::npos)
+ m_name = m_name.substr(0, found);
+ }
+ setTime(date);
+
+ return 1;
+ }
+ if (eplf_re.FullMatch(str, &facts, &name))
+ {
+ /* Get the type, size, and date from the facts */
+ pcrecpp::RE("(\\+|,)(r|/),").PartialMatch(facts, (void*)NULL, &type);
+ pcrecpp::RE("(\\+|,)s(\\d+),").PartialMatch(facts, (void*)NULL, &size);
+ pcrecpp::RE("(\\+|,)m(\\d+),").PartialMatch(facts, (void*)NULL, &date);
+
+ m_name = name;
+ m_size = (uint64_t)strtod(size.c_str(), NULL);
+ if (pcrecpp::RE("/").FullMatch(type))
+ m_flagtrycwd = 1;
+ if (pcrecpp::RE("r").FullMatch(type))
+ m_flagtryretr = 1;
+ /* eplf stores the date in time_t format already */
+ m_time = atoi(date.c_str());
+
+ return 1;
+ }
+ if (multinet_re.FullMatch(str, &name, &version, &file_id, &date, (void*)NULL, &owner, &permissions))
+ {
+ if (pcrecpp::RE("\\.DIR$").PartialMatch(name))
+ {
+ name.resize(name.size() - 4);
+ m_flagtrycwd = 1;
+ }
+ else
+ m_flagtryretr = 1;
+ m_name = name;
+ setTime(date);
+ /* Multinet doesn't provide a size */
+ m_size = 0;
+
+ return 1;
+ }
+ if (msdos_re.FullMatch(str, &date, &size, &name))
+ {
+ m_name = name;
+ if (pcrecpp::RE("<DIR>").FullMatch(size))
+ {
+ m_flagtrycwd = 1;
+ m_size = 0;
+ }
+ else
+ {
+ m_flagtryretr = 1;
+ m_size = (uint64_t)strtod(size.c_str(), NULL);
+ }
+ setTime(date);
+
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/xbmc/filesystem/FTPParse.h b/xbmc/filesystem/FTPParse.h
new file mode 100644
index 0000000..5f56981
--- /dev/null
+++ b/xbmc/filesystem/FTPParse.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <ctime>
+#include <stdint.h>
+#include <string>
+
+class CFTPParse
+{
+public:
+ CFTPParse();
+ int FTPParse(const std::string& str);
+ std::string getName();
+ int getFlagtrycwd();
+ int getFlagtryretr();
+ uint64_t getSize();
+ time_t getTime();
+private:
+ std::string m_name; // not necessarily 0-terminated
+ int m_flagtrycwd; // 0 if cwd is definitely pointless, 1 otherwise
+ int m_flagtryretr; // 0 if retr is definitely pointless, 1 otherwise
+ uint64_t m_size; // number of octets
+ time_t m_time = 0; // modification time
+ void setTime(const std::string& str); // Method used to set m_time from a string
+ int getDayOfWeek(int month, int date, int year); // Method to get day of week
+};
diff --git a/xbmc/filesystem/FavouritesDirectory.cpp b/xbmc/filesystem/FavouritesDirectory.cpp
new file mode 100644
index 0000000..fe46874
--- /dev/null
+++ b/xbmc/filesystem/FavouritesDirectory.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "FavouritesDirectory.h"
+
+#include "Directory.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "favourites/FavouritesService.h"
+#include "profiles/ProfileManager.h"
+#include "utils/FileUtils.h"
+#include "utils/URIUtils.h"
+
+namespace XFILE
+{
+
+bool CFavouritesDirectory::GetDirectory(const CURL& url, CFileItemList &items)
+{
+ items.Clear();
+ CServiceBroker::GetFavouritesService().GetAll(items);
+ return true;
+}
+
+bool CFavouritesDirectory::Exists(const CURL& url)
+{
+ if (url.IsProtocol("favourites"))
+ {
+ if (CFileUtils::Exists("special://xbmc/system/favourites.xml"))
+ return true;
+
+ const std::string favouritesXml = URIUtils::AddFileToFolder(m_profileManager->GetProfileUserDataFolder(), "favourites.xml");
+
+ return CFileUtils::Exists(favouritesXml);
+ }
+
+ return XFILE::CDirectory::Exists(url);
+}
+} // namespace XFILE
diff --git a/xbmc/filesystem/FavouritesDirectory.h b/xbmc/filesystem/FavouritesDirectory.h
new file mode 100644
index 0000000..fe7d5b6
--- /dev/null
+++ b/xbmc/filesystem/FavouritesDirectory.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IDirectory.h"
+
+class CFileItemList;
+class CFileItem;
+
+namespace XFILE
+{
+
+ class CFavouritesDirectory : public IDirectory
+ {
+ public:
+ CFavouritesDirectory() = default;
+
+ bool GetDirectory(const CURL& url, CFileItemList &items) override;
+ bool Exists(const CURL& url) override;
+ };
+
+}
diff --git a/xbmc/filesystem/File.cpp b/xbmc/filesystem/File.cpp
new file mode 100644
index 0000000..9e8a266
--- /dev/null
+++ b/xbmc/filesystem/File.cpp
@@ -0,0 +1,1224 @@
+/*
+ * Copyright (c) 2002 Frodo
+ * Portions Copyright (c) by the authors of ffmpeg and xvid
+ * Copyright (C) 2002-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "File.h"
+
+#include "Directory.h"
+#include "DirectoryCache.h"
+#include "FileCache.h"
+#include "FileFactory.h"
+#include "IFile.h"
+#include "PasswordManager.h"
+#include "ServiceBroker.h"
+#include "application/ApplicationComponents.h"
+#include "application/ApplicationPowerHandling.h"
+#include "commons/Exception.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/BitstreamStats.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+
+using namespace XFILE;
+
+//////////////////////////////////////////////////////////////////////
+// Construction/Destruction
+//////////////////////////////////////////////////////////////////////
+#ifndef __GNUC__
+#pragma warning (disable:4244)
+#endif
+
+//*********************************************************************************************
+CFile::CFile() = default;
+
+//*********************************************************************************************
+CFile::~CFile()
+{
+ Close();
+}
+
+//*********************************************************************************************
+
+bool CFile::Copy(const std::string& strFileName, const std::string& strDest, XFILE::IFileCallback* pCallback, void* pContext)
+{
+ const CURL pathToUrl(strFileName);
+ const CURL pathToUrlDest(strDest);
+ return Copy(pathToUrl, pathToUrlDest, pCallback, pContext);
+}
+
+bool CFile::Copy(const CURL& url2, const CURL& dest, XFILE::IFileCallback* pCallback, void* pContext)
+{
+ CFile file;
+
+ const std::string pathToUrl(dest.Get());
+ if (pathToUrl.empty())
+ return false;
+
+ // special case for zips - ignore caching
+ CURL url(url2);
+ if (StringUtils::StartsWith(url.Get(), "zip://") || URIUtils::IsInAPK(url.Get()))
+ url.SetOptions("?cache=no");
+ if (file.Open(url.Get(), READ_TRUNCATED | READ_CHUNKED))
+ {
+
+ CFile newFile;
+ if (URIUtils::IsHD(pathToUrl)) // create possible missing dirs
+ {
+ std::vector<std::string> tokens;
+ std::string strDirectory = URIUtils::GetDirectory(pathToUrl);
+ URIUtils::RemoveSlashAtEnd(strDirectory); // for the test below
+ if (!(strDirectory.size() == 2 && strDirectory[1] == ':'))
+ {
+ CURL url(strDirectory);
+ std::string pathsep;
+#ifndef TARGET_POSIX
+ pathsep = "\\";
+#else
+ pathsep = "/";
+#endif
+ // Try to use the recursive creation first, if it fails
+ // it might not be implemented for that subsystem so let's
+ // fall back to the old method in that case
+ if (!CDirectory::Create(url))
+ {
+ StringUtils::Tokenize(url.GetFileName(), tokens, pathsep);
+ std::string strCurrPath;
+ // Handle special
+ if (!url.GetProtocol().empty())
+ {
+ pathsep = "/";
+ strCurrPath += url.GetProtocol() + "://";
+ } // If the directory has a / at the beginning, don't forget it
+ else if (strDirectory[0] == pathsep[0])
+ strCurrPath += pathsep;
+
+ for (const std::string& iter : tokens)
+ {
+ strCurrPath += iter + pathsep;
+ CDirectory::Create(strCurrPath);
+ }
+ }
+ }
+ }
+ if (CFile::Exists(dest))
+ CFile::Delete(dest);
+ if (!newFile.OpenForWrite(dest, true)) // overwrite always
+ {
+ file.Close();
+ return false;
+ }
+
+ int iBufferSize = DetermineChunkSize(file.GetChunkSize(), 128 * 1024);
+
+ std::vector<char> buffer(iBufferSize);
+ ssize_t iRead, iWrite;
+
+ unsigned long long llFileSize = file.GetLength();
+ unsigned long long llPos = 0;
+
+ CStopWatch timer;
+ timer.StartZero();
+ float start = 0.0f;
+ auto& components = CServiceBroker::GetAppComponents();
+ const auto appPower = components.GetComponent<CApplicationPowerHandling>();
+ while (true)
+ {
+ appPower->ResetScreenSaver();
+
+ iRead = file.Read(buffer.data(), buffer.size());
+ if (iRead == 0) break;
+ else if (iRead < 0)
+ {
+ CLog::Log(LOGERROR, "{} - Failed read from file {}", __FUNCTION__, url.GetRedacted());
+ llFileSize = (uint64_t)-1;
+ break;
+ }
+
+ /* write data and make sure we managed to write it all */
+ iWrite = 0;
+ while(iWrite < iRead)
+ {
+ ssize_t iWrite2 = newFile.Write(buffer.data() + iWrite, iRead - iWrite);
+ if(iWrite2 <=0)
+ break;
+ iWrite+=iWrite2;
+ }
+
+ if (iWrite != iRead)
+ {
+ CLog::Log(LOGERROR, "{} - Failed write to file {}", __FUNCTION__, dest.GetRedacted());
+ llFileSize = (uint64_t)-1;
+ break;
+ }
+
+ llPos += iRead;
+
+ // calculate the current and average speeds
+ float end = timer.GetElapsedSeconds();
+
+ if (pCallback && end - start > 0.5f && end)
+ {
+ start = end;
+
+ float averageSpeed = llPos / end;
+ int ipercent = 0;
+ if(llFileSize)
+ ipercent = 100 * llPos / llFileSize;
+
+ if(!pCallback->OnFileCallback(pContext, ipercent, averageSpeed))
+ {
+ CLog::Log(LOGERROR, "{} - User aborted copy", __FUNCTION__);
+ llFileSize = (uint64_t)-1;
+ break;
+ }
+ }
+ }
+
+ /* close both files */
+ newFile.Close();
+ file.Close();
+
+ /* verify that we managed to completed the file */
+ if (llFileSize && llPos != llFileSize)
+ {
+ CFile::Delete(dest);
+ return false;
+ }
+ return true;
+ }
+ return false;
+}
+
+//*********************************************************************************************
+
+bool CFile::CURLCreate(const std::string &url)
+{
+ m_curl.Parse(url);
+ return true;
+}
+
+bool CFile::CURLAddOption(XFILE::CURLOPTIONTYPE type, const char* name, const char * value)
+{
+ switch (type){
+ case XFILE::CURL_OPTION_CREDENTIALS:
+ {
+ m_curl.SetUserName(name);
+ m_curl.SetPassword(value);
+ break;
+ }
+ case XFILE::CURL_OPTION_PROTOCOL:
+ case XFILE::CURL_OPTION_HEADER:
+ {
+ m_curl.SetProtocolOption(name, value);
+ break;
+ }
+ case XFILE::CURL_OPTION_OPTION:
+ {
+ m_curl.SetOption(name, value);
+ break;
+ }
+ default:
+ return false;
+ }
+ return true;
+}
+
+bool CFile::CURLOpen(unsigned int flags)
+{
+ return Open(m_curl, flags);
+}
+
+bool CFile::Open(const std::string& strFileName, const unsigned int flags)
+{
+ const CURL pathToUrl(strFileName);
+ return Open(pathToUrl, flags);
+}
+
+bool CFile::Open(const CURL& file, const unsigned int flags)
+{
+ if (m_pFile)
+ {
+ if ((flags & READ_REOPEN) == 0)
+ {
+ CLog::Log(LOGERROR, "File::Open - already open: {}", file.GetRedacted());
+ return false;
+ }
+ else
+ {
+ return m_pFile->ReOpen(URIUtils::SubstitutePath(file));
+ }
+ }
+
+ m_flags = flags;
+ try
+ {
+ bool bPathInCache;
+
+ CURL url(URIUtils::SubstitutePath(file)), url2(url);
+
+ if (url2.IsProtocol("apk") || url2.IsProtocol("zip") )
+ url2.SetOptions("");
+
+ if (!g_directoryCache.FileExists(url2.Get(), bPathInCache) )
+ {
+ if (bPathInCache)
+ return false;
+ }
+
+ /*
+ * There are 5 buffer modes available (configurable in as.xml)
+ * 0) Buffer all internet filesystems (like 2 but additionally also ftp, webdav, etc.)
+ * 1) Buffer all filesystems (including local)
+ * 2) Only buffer true internet filesystems (streams) (http, etc.)
+ * 3) No buffer
+ * 4) Buffer all remote (non-local) filesystems
+ */
+ if (!(m_flags & READ_NO_CACHE))
+ {
+ const std::string pathToUrl(url.Get());
+ if (URIUtils::IsDVD(pathToUrl) || URIUtils::IsBluray(pathToUrl) ||
+ (m_flags & READ_AUDIO_VIDEO))
+ {
+ const unsigned int iCacheBufferMode =
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_cacheBufferMode;
+ if ((iCacheBufferMode == CACHE_BUFFER_MODE_INTERNET &&
+ URIUtils::IsInternetStream(pathToUrl, true)) ||
+ (iCacheBufferMode == CACHE_BUFFER_MODE_TRUE_INTERNET &&
+ URIUtils::IsInternetStream(pathToUrl, false)) ||
+ (iCacheBufferMode == CACHE_BUFFER_MODE_NETWORK &&
+ URIUtils::IsNetworkFilesystem(pathToUrl)) ||
+ (iCacheBufferMode == CACHE_BUFFER_MODE_ALL &&
+ (URIUtils::IsNetworkFilesystem(pathToUrl) || URIUtils::IsHD(pathToUrl))))
+ {
+ m_flags |= READ_CACHED;
+ }
+ }
+
+ if (m_flags & READ_CACHED)
+ {
+ m_pFile = std::make_unique<CFileCache>(m_flags);
+
+ if (!m_pFile)
+ return false;
+
+ return m_pFile->Open(url);
+ }
+ }
+
+ m_pFile.reset(CFileFactory::CreateLoader(url));
+
+ if (!m_pFile)
+ return false;
+
+ CURL authUrl(url);
+ if (CPasswordManager::GetInstance().IsURLSupported(authUrl) && authUrl.GetUserName().empty())
+ CPasswordManager::GetInstance().AuthenticateURL(authUrl);
+
+ try
+ {
+ if (!m_pFile->Open(authUrl))
+ return false;
+ }
+ catch (CRedirectException *pRedirectEx)
+ {
+ // the file implementation decided this item should use a different implementation.
+ // the exception will contain the new implementation.
+ CLog::Log(LOGDEBUG, "File::Open - redirecting implementation for {}", file.GetRedacted());
+ if (pRedirectEx && pRedirectEx->m_pNewFileImp)
+ {
+ std::unique_ptr<CURL> pNewUrl(pRedirectEx->m_pNewUrl);
+ m_pFile.reset(pRedirectEx->m_pNewFileImp);
+ delete pRedirectEx;
+
+ if (pNewUrl)
+ {
+ CURL newAuthUrl(*pNewUrl);
+ if (CPasswordManager::GetInstance().IsURLSupported(newAuthUrl) && newAuthUrl.GetUserName().empty())
+ CPasswordManager::GetInstance().AuthenticateURL(newAuthUrl);
+
+ if (!m_pFile->Open(newAuthUrl))
+ return false;
+ }
+ else
+ {
+ if (!m_pFile->Open(authUrl))
+ return false;
+ }
+ }
+ }
+ catch (...)
+ {
+ CLog::Log(LOGERROR, "File::Open - unknown exception when opening {}", file.GetRedacted());
+ return false;
+ }
+
+ if (m_pFile->GetChunkSize() && !(m_flags & READ_CHUNKED))
+ {
+ m_pBuffer = std::make_unique<CFileStreamBuffer>(0);
+ m_pBuffer->Attach(m_pFile.get());
+ }
+
+ if (m_flags & READ_BITRATE)
+ {
+ m_bitStreamStats = std::make_unique<BitstreamStats>();
+ m_bitStreamStats->Start();
+ }
+
+ return true;
+ }
+ XBMCCOMMONS_HANDLE_UNCHECKED
+ catch (...) { CLog::Log(LOGERROR, "{} - Unhandled exception", __FUNCTION__); }
+ CLog::Log(LOGERROR, "{} - Error opening {}", __FUNCTION__, file.GetRedacted());
+ return false;
+}
+
+bool CFile::OpenForWrite(const std::string& strFileName, bool bOverWrite)
+{
+ const CURL pathToUrl(strFileName);
+ return OpenForWrite(pathToUrl, bOverWrite);
+}
+
+bool CFile::OpenForWrite(const CURL& file, bool bOverWrite)
+{
+ try
+ {
+ CURL url = URIUtils::SubstitutePath(file);
+ CURL authUrl = url;
+ if (CPasswordManager::GetInstance().IsURLSupported(authUrl) && authUrl.GetUserName().empty())
+ CPasswordManager::GetInstance().AuthenticateURL(authUrl);
+
+ m_pFile.reset(CFileFactory::CreateLoader(url));
+
+ if (m_pFile && m_pFile->OpenForWrite(authUrl, bOverWrite))
+ {
+ // add this file to our directory cache (if it's stored)
+ g_directoryCache.AddFile(url.Get());
+ return true;
+ }
+ return false;
+ }
+ XBMCCOMMONS_HANDLE_UNCHECKED
+ catch(...)
+ {
+ CLog::Log(LOGERROR, "{} - Unhandled exception opening {}", __FUNCTION__, file.GetRedacted());
+ }
+ CLog::Log(LOGERROR, "{} - Error opening {}", __FUNCTION__, file.GetRedacted());
+ return false;
+}
+
+int CFile::DetermineChunkSize(const int srcChunkSize, const int reqChunkSize)
+{
+ // Determine cache chunk size: if source chunk size is bigger than 1
+ // use source chunk size else use requested chunk size
+ return (srcChunkSize > 1 ? srcChunkSize : reqChunkSize);
+}
+
+bool CFile::Exists(const std::string& strFileName, bool bUseCache /* = true */)
+{
+ const CURL pathToUrl(strFileName);
+ return Exists(pathToUrl, bUseCache);
+}
+
+bool CFile::Exists(const CURL& file, bool bUseCache /* = true */)
+{
+ CURL url(URIUtils::SubstitutePath(file));
+ CURL authUrl = url;
+ if (CPasswordManager::GetInstance().IsURLSupported(authUrl) && authUrl.GetUserName().empty())
+ CPasswordManager::GetInstance().AuthenticateURL(authUrl);
+
+ try
+ {
+ if (bUseCache)
+ {
+ bool bPathInCache;
+ if (g_directoryCache.FileExists(url.Get(), bPathInCache))
+ return true;
+ if (bPathInCache)
+ return false;
+ }
+
+ std::unique_ptr<IFile> pFile(CFileFactory::CreateLoader(url));
+ if (!pFile)
+ return false;
+
+ return pFile->Exists(authUrl);
+ }
+ XBMCCOMMONS_HANDLE_UNCHECKED
+ catch (CRedirectException *pRedirectEx)
+ {
+ // the file implementation decided this item should use a different implementation.
+ // the exception will contain the new implementation and optional a redirected URL.
+ CLog::Log(LOGDEBUG, "File::Exists - redirecting implementation for {}", file.GetRedacted());
+ if (pRedirectEx && pRedirectEx->m_pNewFileImp)
+ {
+ std::unique_ptr<IFile> pImp(pRedirectEx->m_pNewFileImp);
+ std::unique_ptr<CURL> pNewUrl(pRedirectEx->m_pNewUrl);
+ delete pRedirectEx;
+
+ if (pImp)
+ {
+ if (pNewUrl)
+ {
+ if (bUseCache)
+ {
+ bool bPathInCache;
+ if (g_directoryCache.FileExists(pNewUrl->Get(), bPathInCache))
+ return true;
+ if (bPathInCache)
+ return false;
+ }
+ CURL newAuthUrl = *pNewUrl;
+ if (CPasswordManager::GetInstance().IsURLSupported(newAuthUrl) && newAuthUrl.GetUserName().empty())
+ CPasswordManager::GetInstance().AuthenticateURL(newAuthUrl);
+
+ return pImp->Exists(newAuthUrl);
+ }
+ else
+ {
+ return pImp->Exists(authUrl);
+ }
+ }
+ }
+ }
+ catch (...) { CLog::Log(LOGERROR, "{} - Unhandled exception", __FUNCTION__); }
+ CLog::Log(LOGERROR, "{} - Error checking for {}", __FUNCTION__, file.GetRedacted());
+ return false;
+}
+
+int CFile::Stat(struct __stat64 *buffer)
+{
+ if (!buffer)
+ return -1;
+
+ if (!m_pFile)
+ {
+ *buffer = {};
+ errno = ENOENT;
+ return -1;
+ }
+
+ return m_pFile->Stat(buffer);
+}
+
+int CFile::Stat(const std::string& strFileName, struct __stat64* buffer)
+{
+ const CURL pathToUrl(strFileName);
+ return Stat(pathToUrl, buffer);
+}
+
+int CFile::Stat(const CURL& file, struct __stat64* buffer)
+{
+ if (!buffer)
+ return -1;
+
+ CURL url(URIUtils::SubstitutePath(file));
+ CURL authUrl = url;
+ if (CPasswordManager::GetInstance().IsURLSupported(authUrl) && authUrl.GetUserName().empty())
+ CPasswordManager::GetInstance().AuthenticateURL(authUrl);
+
+ try
+ {
+ std::unique_ptr<IFile> pFile(CFileFactory::CreateLoader(url));
+ if (!pFile)
+ return -1;
+ return pFile->Stat(authUrl, buffer);
+ }
+ XBMCCOMMONS_HANDLE_UNCHECKED
+ catch (CRedirectException *pRedirectEx)
+ {
+ // the file implementation decided this item should use a different implementation.
+ // the exception will contain the new implementation and optional a redirected URL.
+ CLog::Log(LOGDEBUG, "File::Stat - redirecting implementation for {}", file.GetRedacted());
+ if (pRedirectEx && pRedirectEx->m_pNewFileImp)
+ {
+ std::unique_ptr<IFile> pImp(pRedirectEx->m_pNewFileImp);
+ std::unique_ptr<CURL> pNewUrl(pRedirectEx->m_pNewUrl);
+ delete pRedirectEx;
+
+ if (pNewUrl)
+ {
+ if (pImp)
+ {
+ CURL newAuthUrl = *pNewUrl;
+ if (CPasswordManager::GetInstance().IsURLSupported(newAuthUrl) && newAuthUrl.GetUserName().empty())
+ CPasswordManager::GetInstance().AuthenticateURL(newAuthUrl);
+
+ if (!pImp->Stat(newAuthUrl, buffer))
+ {
+ return 0;
+ }
+ }
+ }
+ else
+ {
+ if (pImp.get() && !pImp->Stat(authUrl, buffer))
+ {
+ return 0;
+ }
+ }
+ }
+ }
+ catch (...) { CLog::Log(LOGERROR, "{} - Unhandled exception", __FUNCTION__); }
+ CLog::Log(LOGERROR, "{} - Error statting {}", __FUNCTION__, file.GetRedacted());
+ return -1;
+}
+
+ssize_t CFile::Read(void *lpBuf, size_t uiBufSize)
+{
+ if (!m_pFile)
+ return -1;
+ if (lpBuf == NULL && uiBufSize != 0)
+ return -1;
+
+ if (uiBufSize > SSIZE_MAX)
+ uiBufSize = SSIZE_MAX;
+
+ if (uiBufSize == 0)
+ {
+ // "test" read with zero size
+ // some VFSs don't handle correctly null buffer pointer
+ // provide valid buffer pointer for them
+ char dummy;
+ return m_pFile->Read(&dummy, 0);
+ }
+
+ if(m_pBuffer)
+ {
+ if(m_flags & READ_TRUNCATED)
+ {
+ const ssize_t nBytes = m_pBuffer->sgetn(
+ (char *)lpBuf, std::min<std::streamsize>((std::streamsize)uiBufSize,
+ m_pBuffer->in_avail()));
+ if (m_bitStreamStats && nBytes>0)
+ m_bitStreamStats->AddSampleBytes(nBytes);
+ return nBytes;
+ }
+ else
+ {
+ const ssize_t nBytes = m_pBuffer->sgetn((char*)lpBuf, uiBufSize);
+ if (m_bitStreamStats && nBytes>0)
+ m_bitStreamStats->AddSampleBytes(nBytes);
+ return nBytes;
+ }
+ }
+
+ try
+ {
+ if(m_flags & READ_TRUNCATED)
+ {
+ const ssize_t nBytes = m_pFile->Read(lpBuf, uiBufSize);
+ if (m_bitStreamStats && nBytes>0)
+ m_bitStreamStats->AddSampleBytes(nBytes);
+ return nBytes;
+ }
+ else
+ {
+ ssize_t done = 0;
+ while((uiBufSize-done) > 0)
+ {
+ const ssize_t curr = m_pFile->Read((char*)lpBuf+done, uiBufSize-done);
+ if (curr <= 0)
+ {
+ if (curr < 0 && done == 0)
+ return -1;
+
+ break;
+ }
+ done+=curr;
+ }
+ if (m_bitStreamStats && done > 0)
+ m_bitStreamStats->AddSampleBytes(done);
+ return done;
+ }
+ }
+ XBMCCOMMONS_HANDLE_UNCHECKED
+ catch(...)
+ {
+ CLog::Log(LOGERROR, "{} - Unhandled exception", __FUNCTION__);
+ return -1;
+ }
+ return 0;
+}
+
+//*********************************************************************************************
+void CFile::Close()
+{
+ try
+ {
+ if (m_pFile)
+ m_pFile->Close();
+
+ m_pBuffer.reset();
+ m_pFile.reset();
+ }
+ XBMCCOMMONS_HANDLE_UNCHECKED
+ catch (...) { CLog::Log(LOGERROR, "{} - Unhandled exception", __FUNCTION__); }
+}
+
+void CFile::Flush()
+{
+ try
+ {
+ if (m_pFile)
+ m_pFile->Flush();
+ }
+ XBMCCOMMONS_HANDLE_UNCHECKED
+ catch (...) { CLog::Log(LOGERROR, "{} - Unhandled exception", __FUNCTION__); }
+}
+
+//*********************************************************************************************
+int64_t CFile::Seek(int64_t iFilePosition, int iWhence)
+{
+ if (!m_pFile)
+ return -1;
+
+ if (m_pBuffer)
+ {
+ if(iWhence == SEEK_CUR)
+ return m_pBuffer->pubseekoff(iFilePosition, std::ios_base::cur);
+ else if(iWhence == SEEK_END)
+ return m_pBuffer->pubseekoff(iFilePosition, std::ios_base::end);
+ else if(iWhence == SEEK_SET)
+ return m_pBuffer->pubseekoff(iFilePosition, std::ios_base::beg);
+ }
+
+ try
+ {
+ return m_pFile->Seek(iFilePosition, iWhence);
+ }
+ XBMCCOMMONS_HANDLE_UNCHECKED
+ catch (...) { CLog::Log(LOGERROR, "{} - Unhandled exception", __FUNCTION__); }
+ return -1;
+}
+
+//*********************************************************************************************
+int CFile::Truncate(int64_t iSize)
+{
+ if (!m_pFile)
+ return -1;
+
+ try
+ {
+ return m_pFile->Truncate(iSize);
+ }
+ XBMCCOMMONS_HANDLE_UNCHECKED
+ catch (...) { CLog::Log(LOGERROR, "{} - Unhandled exception", __FUNCTION__); }
+ return -1;
+}
+
+//*********************************************************************************************
+int64_t CFile::GetLength()
+{
+ try
+ {
+ if (m_pFile)
+ return m_pFile->GetLength();
+ return 0;
+ }
+ XBMCCOMMONS_HANDLE_UNCHECKED
+ catch (...) { CLog::Log(LOGERROR, "{} - Unhandled exception", __FUNCTION__); }
+ return 0;
+}
+
+//*********************************************************************************************
+int64_t CFile::GetPosition() const
+{
+ if (!m_pFile)
+ return -1;
+
+ if (m_pBuffer)
+ return m_pBuffer->pubseekoff(0, std::ios_base::cur);
+
+ try
+ {
+ return m_pFile->GetPosition();
+ }
+ XBMCCOMMONS_HANDLE_UNCHECKED
+ catch (...) { CLog::Log(LOGERROR, "{} - Unhandled exception", __FUNCTION__); }
+ return -1;
+}
+
+
+//*********************************************************************************************
+bool CFile::ReadString(char *szLine, int iLineLength)
+{
+ if (!m_pFile || !szLine)
+ return false;
+
+ if (m_pBuffer)
+ {
+ typedef CFileStreamBuffer::traits_type traits;
+ CFileStreamBuffer::int_type aByte = m_pBuffer->sgetc();
+
+ if(aByte == traits::eof())
+ return false;
+
+ while(iLineLength>0)
+ {
+ aByte = m_pBuffer->sbumpc();
+
+ if(aByte == traits::eof())
+ break;
+
+ if(aByte == traits::to_int_type('\n'))
+ {
+ if(m_pBuffer->sgetc() == traits::to_int_type('\r'))
+ m_pBuffer->sbumpc();
+ break;
+ }
+
+ if(aByte == traits::to_int_type('\r'))
+ {
+ if(m_pBuffer->sgetc() == traits::to_int_type('\n'))
+ m_pBuffer->sbumpc();
+ break;
+ }
+
+ *szLine = traits::to_char_type(aByte);
+ szLine++;
+ iLineLength--;
+ }
+
+ // if we have no space for terminating character we failed
+ if(iLineLength==0)
+ return false;
+
+ *szLine = 0;
+
+ return true;
+ }
+
+ try
+ {
+ return m_pFile->ReadString(szLine, iLineLength);
+ }
+ XBMCCOMMONS_HANDLE_UNCHECKED
+ catch (...) { CLog::Log(LOGERROR, "{} - Unhandled exception", __FUNCTION__); }
+ return false;
+}
+
+ssize_t CFile::Write(const void* lpBuf, size_t uiBufSize)
+{
+ if (!m_pFile)
+ return -1;
+ if (lpBuf == NULL && uiBufSize != 0)
+ return -1;
+
+ try
+ {
+ if (uiBufSize == 0 && lpBuf == NULL)
+ { // "test" write with zero size
+ // some VFSs don't handle correctly null buffer pointer
+ // provide valid buffer pointer for them
+ const char dummyBuf = 0;
+ return m_pFile->Write(&dummyBuf, 0);
+ }
+
+ return m_pFile->Write(lpBuf, uiBufSize);
+ }
+ XBMCCOMMONS_HANDLE_UNCHECKED
+ catch (...) { CLog::Log(LOGERROR, "{} - Unhandled exception", __FUNCTION__); }
+ return -1;
+}
+
+bool CFile::Delete(const std::string& strFileName)
+{
+ const CURL pathToUrl(strFileName);
+ return Delete(pathToUrl);
+}
+
+bool CFile::Delete(const CURL& file)
+{
+ try
+ {
+ CURL url(URIUtils::SubstitutePath(file));
+ CURL authUrl = url;
+ if (CPasswordManager::GetInstance().IsURLSupported(authUrl) && authUrl.GetUserName().empty())
+ CPasswordManager::GetInstance().AuthenticateURL(authUrl);
+
+ std::unique_ptr<IFile> pFile(CFileFactory::CreateLoader(url));
+ if (!pFile)
+ return false;
+
+ if(pFile->Delete(authUrl))
+ {
+ g_directoryCache.ClearFile(url.Get());
+ return true;
+ }
+ }
+ XBMCCOMMONS_HANDLE_UNCHECKED
+ catch (...) { CLog::Log(LOGERROR, "{} - Unhandled exception", __FUNCTION__); }
+ if (Exists(file))
+ CLog::Log(LOGERROR, "{} - Error deleting file {}", __FUNCTION__, file.GetRedacted());
+ return false;
+}
+
+bool CFile::Rename(const std::string& strFileName, const std::string& strNewFileName)
+{
+ const CURL pathToUrl(strFileName);
+ const CURL pathToUrlNew(strNewFileName);
+ return Rename(pathToUrl, pathToUrlNew);
+}
+
+bool CFile::Rename(const CURL& file, const CURL& newFile)
+{
+ try
+ {
+ CURL url(URIUtils::SubstitutePath(file));
+ CURL urlnew(URIUtils::SubstitutePath(newFile));
+
+ CURL authUrl = url;
+ if (CPasswordManager::GetInstance().IsURLSupported(authUrl) && authUrl.GetUserName().empty())
+ CPasswordManager::GetInstance().AuthenticateURL(authUrl);
+ CURL authUrlNew = urlnew;
+ if (CPasswordManager::GetInstance().IsURLSupported(authUrlNew) && authUrlNew.GetUserName().empty())
+ CPasswordManager::GetInstance().AuthenticateURL(authUrlNew);
+
+ std::unique_ptr<IFile> pFile(CFileFactory::CreateLoader(url));
+ if (!pFile)
+ return false;
+
+ if(pFile->Rename(authUrl, authUrlNew))
+ {
+ g_directoryCache.ClearFile(url.Get());
+ g_directoryCache.AddFile(urlnew.Get());
+ return true;
+ }
+ }
+ XBMCCOMMONS_HANDLE_UNCHECKED
+ catch (...) { CLog::Log(LOGERROR, "{} - Unhandled exception ", __FUNCTION__); }
+ CLog::Log(LOGERROR, "{} - Error renaming file {}", __FUNCTION__, file.GetRedacted());
+ return false;
+}
+
+bool CFile::SetHidden(const std::string& fileName, bool hidden)
+{
+ const CURL pathToUrl(fileName);
+ return SetHidden(pathToUrl, hidden);
+}
+
+bool CFile::SetHidden(const CURL& file, bool hidden)
+{
+ try
+ {
+ CURL url(URIUtils::SubstitutePath(file));
+ CURL authUrl = url;
+ if (CPasswordManager::GetInstance().IsURLSupported(authUrl) && authUrl.GetUserName().empty())
+ CPasswordManager::GetInstance().AuthenticateURL(authUrl);
+
+ std::unique_ptr<IFile> pFile(CFileFactory::CreateLoader(url));
+ if (!pFile)
+ return false;
+
+ return pFile->SetHidden(authUrl, hidden);
+ }
+ catch(...)
+ {
+ CLog::Log(LOGERROR, "{}({}) - Unhandled exception", __FUNCTION__, file.GetRedacted());
+ }
+ return false;
+}
+
+int CFile::IoControl(EIoControl request, void* param)
+{
+ int result = -1;
+ if (!m_pFile)
+ return -1;
+ result = m_pFile->IoControl(request, param);
+
+ if(result == -1 && request == IOCTRL_SEEK_POSSIBLE)
+ {
+ if(m_pFile->GetLength() >= 0 && m_pFile->Seek(0, SEEK_CUR) >= 0)
+ return 1;
+ else
+ return 0;
+ }
+
+ return result;
+}
+
+int CFile::GetChunkSize()
+{
+ if (m_pFile)
+ return m_pFile->GetChunkSize();
+ return 0;
+}
+
+const std::string CFile::GetProperty(XFILE::FileProperty type, const std::string &name) const
+{
+ if (!m_pFile)
+ return "";
+ return m_pFile->GetProperty(type, name);
+}
+
+const std::vector<std::string> CFile::GetPropertyValues(XFILE::FileProperty type, const std::string &name) const
+{
+ if (!m_pFile)
+ {
+ return std::vector<std::string>();
+ }
+ return m_pFile->GetPropertyValues(type, name);
+}
+
+ssize_t CFile::LoadFile(const std::string& filename, std::vector<uint8_t>& outputBuffer)
+{
+ const CURL pathToUrl(filename);
+ return LoadFile(pathToUrl, outputBuffer);
+}
+
+ssize_t CFile::LoadFile(const CURL& file, std::vector<uint8_t>& outputBuffer)
+{
+ static const size_t max_file_size = 0x7FFFFFFF;
+ static const size_t min_chunk_size = 64 * 1024U;
+ static const size_t max_chunk_size = 2048 * 1024U;
+
+ outputBuffer.clear();
+
+ if (!Open(file, READ_TRUNCATED))
+ return 0;
+
+ /*
+ GetLength() will typically return values that fall into three cases:
+ 1. The real filesize. This is the typical case.
+ 2. Zero. This is the case for some http:// streams for example.
+ 3. Some value smaller than the real filesize. This is the case for an expanding file.
+
+ In order to handle all three cases, we read the file in chunks, relying on Read()
+ returning 0 at EOF. To minimize (re)allocation of the buffer, the chunksize in
+ cases 1 and 3 is set to one byte larger than the value returned by GetLength().
+ The chunksize in case 2 is set to the lowest value larger than min_chunk_size aligned
+ to GetChunkSize().
+
+ We fill the buffer entirely before reallocation. Thus, reallocation never occurs in case 1
+ as the buffer is larger than the file, so we hit EOF before we hit the end of buffer.
+
+ To minimize reallocation, we double the chunksize each read while chunksize is lower
+ than max_chunk_size.
+ */
+ int64_t filesize = GetLength();
+ if (filesize > (int64_t)max_file_size)
+ return 0; /* file is too large for this function */
+
+ size_t chunksize = (filesize > 0) ? static_cast<size_t>(filesize + 1)
+ : static_cast<size_t>(DetermineChunkSize(GetChunkSize(),
+ min_chunk_size));
+ size_t total_read = 0;
+ while (true)
+ {
+ if (total_read == outputBuffer.size())
+ { // (re)alloc
+ if (outputBuffer.size() + chunksize > max_file_size)
+ {
+ outputBuffer.clear();
+ return -1;
+ }
+ outputBuffer.resize(outputBuffer.size() + chunksize);
+ if (chunksize < max_chunk_size)
+ chunksize *= 2;
+ }
+ ssize_t read = Read(outputBuffer.data() + total_read, outputBuffer.size() - total_read);
+ if (read < 0)
+ {
+ outputBuffer.clear();
+ return -1;
+ }
+ total_read += read;
+ if (!read)
+ break;
+ }
+
+ outputBuffer.resize(total_read);
+
+ return total_read;
+}
+
+double CFile::GetDownloadSpeed()
+{
+ if (m_pFile)
+ return m_pFile->GetDownloadSpeed();
+ return 0.0;
+}
+
+//*********************************************************************************************
+//*************** Stream IO for CFile objects *************************************************
+//*********************************************************************************************
+CFileStreamBuffer::~CFileStreamBuffer()
+{
+ sync();
+ Detach();
+}
+
+CFileStreamBuffer::CFileStreamBuffer(int backsize)
+ : std::streambuf()
+ , m_file(NULL)
+ , m_buffer(NULL)
+ , m_backsize(backsize)
+{
+}
+
+void CFileStreamBuffer::Attach(IFile *file)
+{
+ m_file = file;
+
+ m_frontsize = CFile::DetermineChunkSize(m_file->GetChunkSize(), 64 * 1024);
+
+ m_buffer = new char[m_frontsize+m_backsize];
+ setg(0,0,0);
+ setp(0,0);
+}
+
+void CFileStreamBuffer::Detach()
+{
+ setg(0,0,0);
+ setp(0,0);
+ delete[] m_buffer;
+ m_buffer = NULL;
+}
+
+CFileStreamBuffer::int_type CFileStreamBuffer::underflow()
+{
+ if(gptr() < egptr())
+ return traits_type::to_int_type(*gptr());
+
+ if(!m_file)
+ return traits_type::eof();
+
+ size_t backsize = 0;
+ if(m_backsize)
+ {
+ backsize = (size_t)std::min<ptrdiff_t>((ptrdiff_t)m_backsize, egptr()-eback());
+ memmove(m_buffer, egptr()-backsize, backsize);
+ }
+
+ ssize_t size = m_file->Read(m_buffer+backsize, m_frontsize);
+
+ if (size == 0)
+ return traits_type::eof();
+ else if (size < 0)
+ {
+ CLog::LogF(LOGWARNING, "Error reading file - assuming eof");
+ return traits_type::eof();
+ }
+
+ setg(m_buffer, m_buffer+backsize, m_buffer+backsize+size);
+ return traits_type::to_int_type(*gptr());
+}
+
+CFileStreamBuffer::pos_type CFileStreamBuffer::seekoff(
+ off_type offset,
+ std::ios_base::seekdir way,
+ std::ios_base::openmode mode)
+{
+ // calculate relative offset
+ off_type aheadbytes = (egptr() - gptr());
+ off_type pos = m_file->GetPosition() - aheadbytes;
+ off_type offset2;
+ if(way == std::ios_base::cur)
+ offset2 = offset;
+ else if(way == std::ios_base::beg)
+ offset2 = offset - pos;
+ else if(way == std::ios_base::end)
+ offset2 = offset + m_file->GetLength() - pos;
+ else
+ return std::streampos(-1);
+
+ // a non seek shouldn't modify our buffer
+ if(offset2 == 0)
+ return pos;
+
+ // try to seek within buffer
+ if(gptr()+offset2 >= eback() && gptr()+offset2 < egptr())
+ {
+ gbump(offset2);
+ return pos + offset2;
+ }
+
+ // reset our buffer pointer, will
+ // start buffering on next read
+ setg(0,0,0);
+ setp(0,0);
+
+ int64_t position = -1;
+ if(way == std::ios_base::cur)
+ position = m_file->Seek(offset - aheadbytes, SEEK_CUR);
+ else if(way == std::ios_base::end)
+ position = m_file->Seek(offset, SEEK_END);
+ else
+ position = m_file->Seek(offset, SEEK_SET);
+
+ if(position<0)
+ return std::streampos(-1);
+
+ return position;
+}
+
+CFileStreamBuffer::pos_type CFileStreamBuffer::seekpos(
+ pos_type pos,
+ std::ios_base::openmode mode)
+{
+ return seekoff(pos, std::ios_base::beg, mode);
+}
+
+std::streamsize CFileStreamBuffer::showmanyc()
+{
+ underflow();
+ return egptr() - gptr();
+}
+
+CFileStream::CFileStream(int backsize /*= 0*/) : std::istream(&m_buffer), m_buffer(backsize)
+{
+}
+
+CFileStream::~CFileStream()
+{
+ Close();
+}
+
+
+bool CFileStream::Open(const CURL& filename)
+{
+ Close();
+
+ CURL url(URIUtils::SubstitutePath(filename));
+ m_file.reset(CFileFactory::CreateLoader(url));
+
+ CURL authUrl = url;
+ if (CPasswordManager::GetInstance().IsURLSupported(authUrl) && authUrl.GetUserName().empty())
+ CPasswordManager::GetInstance().AuthenticateURL(authUrl);
+
+ if(m_file && m_file->Open(authUrl))
+ {
+ m_buffer.Attach(m_file.get());
+ return true;
+ }
+
+ setstate(failbit);
+ return false;
+}
+
+int64_t CFileStream::GetLength()
+{
+ return m_file->GetLength();
+}
+
+void CFileStream::Close()
+{
+ if(!m_file)
+ return;
+
+ m_buffer.Detach();
+}
+
+bool CFileStream::Open(const std::string& filename)
+{
+ const CURL pathToUrl(filename);
+ return Open(pathToUrl);
+}
diff --git a/xbmc/filesystem/File.h b/xbmc/filesystem/File.h
new file mode 100644
index 0000000..a5f991a
--- /dev/null
+++ b/xbmc/filesystem/File.h
@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) 2002 Frodo
+ * Portions Copyright (c) by the authors of ffmpeg and xvid
+ * Copyright (C) 2002-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+// File.h: interface for the CFile class.
+//
+//////////////////////////////////////////////////////////////////////
+
+#include "IFileTypes.h"
+#include "URL.h"
+
+#include <iostream>
+#include <memory>
+#include <stdio.h>
+#include <string>
+#include <vector>
+
+#include "PlatformDefs.h"
+
+class BitstreamStats;
+
+namespace XFILE
+{
+
+class IFile;
+
+class CFileStreamBuffer;
+
+class CFile
+{
+public:
+ CFile();
+ ~CFile();
+
+ bool CURLCreate(const std::string &url);
+ bool CURLAddOption(XFILE::CURLOPTIONTYPE type, const char* name, const char * value);
+ bool CURLOpen(unsigned int flags);
+
+ /**
+ * Attempt to open an IFile instance.
+ * @param file reference to CCurl file description
+ * @param flags see IFileTypes.h
+ * @return true on success, false otherwise
+ *
+ * Remarks: Open can only be called once. Calling
+ * Open() on an already opened file will fail
+ * except if flag READ_REOPEN is set and the underlying
+ * file has an implementation of ReOpen().
+ */
+ bool Open(const CURL& file, const unsigned int flags = 0);
+ bool Open(const std::string& strFileName, const unsigned int flags = 0);
+
+ bool OpenForWrite(const CURL& file, bool bOverWrite = false);
+ bool OpenForWrite(const std::string& strFileName, bool bOverWrite = false);
+
+ ssize_t LoadFile(const CURL& file, std::vector<uint8_t>& outputBuffer);
+
+ /**
+ * Attempt to read bufSize bytes from currently opened file into buffer bufPtr.
+ * @param bufPtr pointer to buffer
+ * @param bufSize size of the buffer
+ * @return number of successfully read bytes if any bytes were read and stored in
+ * buffer, zero if no bytes are available to read (end of file was reached)
+ * or undetectable error occur, -1 in case of any explicit error
+ */
+ ssize_t Read(void* bufPtr, size_t bufSize);
+ bool ReadString(char *szLine, int iLineLength);
+ /**
+ * Attempt to write bufSize bytes from buffer bufPtr into currently opened file.
+ * @param bufPtr pointer to buffer
+ * @param bufSize size of the buffer
+ * @return number of successfully written bytes if any bytes were written,
+ * zero if no bytes were written and no detectable error occur,
+ * -1 in case of any explicit error
+ */
+ ssize_t Write(const void* bufPtr, size_t bufSize);
+ void Flush();
+ int64_t Seek(int64_t iFilePosition, int iWhence = SEEK_SET);
+ int Truncate(int64_t iSize);
+ int64_t GetPosition() const;
+ int64_t GetLength();
+ void Close();
+ int GetChunkSize();
+ const std::string GetProperty(XFILE::FileProperty type, const std::string &name = "") const;
+ const std::vector<std::string> GetPropertyValues(XFILE::FileProperty type, const std::string &name = "") const;
+ ssize_t LoadFile(const std::string& filename, std::vector<uint8_t>& outputBuffer);
+
+ static int DetermineChunkSize(const int srcChunkSize, const int reqChunkSize);
+
+ const std::unique_ptr<BitstreamStats>& GetBitstreamStats() const { return m_bitStreamStats; }
+
+ int IoControl(EIoControl request, void* param);
+
+ IFile* GetImplementation() const { return m_pFile.get(); }
+
+ // CURL interface
+ static bool Exists(const CURL& file, bool bUseCache = true);
+ static bool Delete(const CURL& file);
+ /**
+ * Fills struct __stat64 with information about file specified by filename
+ * For st_mode function will set correctly _S_IFDIR (directory) flag and may set
+ * _S_IREAD (read permission), _S_IWRITE (write permission) flags if such
+ * information is available. Function may set st_size (file size), st_atime,
+ * st_mtime, st_ctime (access, modification, creation times).
+ * Any other flags and members of __stat64 that didn't updated with actual file
+ * information will be set to zero (st_nlink can be set ether to 1 or zero).
+ * @param file specifies requested file
+ * @param buffer pointer to __stat64 buffer to receive information about file
+ * @return zero of success, -1 otherwise.
+ */
+ static int Stat(const CURL& file, struct __stat64* buffer);
+ static bool Rename(const CURL& file, const CURL& urlNew);
+ static bool Copy(const CURL& file, const CURL& dest, XFILE::IFileCallback* pCallback = NULL, void* pContext = NULL);
+ static bool SetHidden(const CURL& file, bool hidden);
+
+ // string interface
+ static bool Exists(const std::string& strFileName, bool bUseCache = true);
+ /**
+ * Fills struct __stat64 with information about file specified by filename
+ * For st_mode function will set correctly _S_IFDIR (directory) flag and may set
+ * _S_IREAD (read permission), _S_IWRITE (write permission) flags if such
+ * information is available. Function may set st_size (file size), st_atime,
+ * st_mtime, st_ctime (access, modification, creation times).
+ * Any other flags and members of __stat64 that didn't updated with actual file
+ * information will be set to zero (st_nlink can be set ether to 1 or zero).
+ * @param strFileName specifies requested file
+ * @param buffer pointer to __stat64 buffer to receive information about file
+ * @return zero of success, -1 otherwise.
+ */
+ static int Stat(const std::string& strFileName, struct __stat64* buffer);
+ /**
+ * Fills struct __stat64 with information about currently open file
+ * For st_mode function will set correctly _S_IFDIR (directory) flag and may set
+ * _S_IREAD (read permission), _S_IWRITE (write permission) flags if such
+ * information is available. Function may set st_size (file size), st_atime,
+ * st_mtime, st_ctime (access, modification, creation times).
+ * Any other flags and members of __stat64 that didn't updated with actual file
+ * information will be set to zero (st_nlink can be set ether to 1 or zero).
+ * @param buffer pointer to __stat64 buffer to receive information about file
+ * @return zero of success, -1 otherwise.
+ */
+ int Stat(struct __stat64 *buffer);
+ static bool Delete(const std::string& strFileName);
+ static bool Rename(const std::string& strFileName, const std::string& strNewFileName);
+ static bool Copy(const std::string& strFileName, const std::string& strDest, XFILE::IFileCallback* pCallback = NULL, void* pContext = NULL);
+ static bool SetHidden(const std::string& fileName, bool hidden);
+ double GetDownloadSpeed();
+
+private:
+ unsigned int m_flags = 0;
+ CURL m_curl;
+ std::unique_ptr<IFile> m_pFile;
+ std::unique_ptr<CFileStreamBuffer> m_pBuffer;
+ std::unique_ptr<BitstreamStats> m_bitStreamStats;
+};
+
+// streambuf for file io, only supports buffered input currently
+class CFileStreamBuffer
+ : public std::streambuf
+{
+public:
+ ~CFileStreamBuffer() override;
+ explicit CFileStreamBuffer(int backsize = 0);
+
+ void Attach(IFile *file);
+ void Detach();
+
+private:
+ int_type underflow() override;
+ std::streamsize showmanyc() override;
+ pos_type seekoff(off_type, std::ios_base::seekdir,std::ios_base::openmode = std::ios_base::in | std::ios_base::out) override;
+ pos_type seekpos(pos_type, std::ios_base::openmode = std::ios_base::in | std::ios_base::out) override;
+
+ IFile* m_file;
+ char* m_buffer;
+ int m_backsize;
+ int m_frontsize = 0;
+};
+
+// very basic file input stream
+class CFileStream
+ : public std::istream
+{
+public:
+ explicit CFileStream(int backsize = 0);
+ ~CFileStream() override;
+
+ bool Open(const std::string& filename);
+ bool Open(const CURL& filename);
+ void Close();
+
+ int64_t GetLength();
+private:
+ CFileStreamBuffer m_buffer;
+ std::unique_ptr<IFile> m_file;
+};
+
+}
diff --git a/xbmc/filesystem/FileCache.cpp b/xbmc/filesystem/FileCache.cpp
new file mode 100644
index 0000000..4f2e07b
--- /dev/null
+++ b/xbmc/filesystem/FileCache.cpp
@@ -0,0 +1,630 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "FileCache.h"
+
+#include "CircularCache.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/SettingsComponent.h"
+#include "threads/Thread.h"
+#include "utils/log.h"
+
+#include <mutex>
+
+#if !defined(TARGET_WINDOWS)
+#include "platform/posix/ConvUtils.h"
+#endif
+
+#include <algorithm>
+#include <cassert>
+#include <chrono>
+#include <inttypes.h>
+#include <memory>
+
+#ifdef TARGET_POSIX
+#include "platform/posix/ConvUtils.h"
+#endif
+
+using namespace XFILE;
+using namespace std::chrono_literals;
+
+class CWriteRate
+{
+public:
+ CWriteRate()
+ {
+ m_stamp = std::chrono::steady_clock::now();
+ m_pos = 0;
+ m_size = 0;
+ m_time = std::chrono::milliseconds(0);
+ }
+
+ void Reset(int64_t pos, bool bResetAll = true)
+ {
+ m_stamp = std::chrono::steady_clock::now();
+ m_pos = pos;
+
+ if (bResetAll)
+ {
+ m_size = 0;
+ m_time = std::chrono::milliseconds(0);
+ }
+ }
+
+ uint32_t Rate(int64_t pos, uint32_t time_bias = 0)
+ {
+ auto ts = std::chrono::steady_clock::now();
+
+ m_size += (pos - m_pos);
+ m_time += std::chrono::duration_cast<std::chrono::milliseconds>(ts - m_stamp);
+ m_pos = pos;
+ m_stamp = ts;
+
+ if (m_time == std::chrono::milliseconds(0))
+ return 0;
+
+ return static_cast<uint32_t>(1000 * (m_size / (m_time.count() + time_bias)));
+ }
+
+private:
+ std::chrono::time_point<std::chrono::steady_clock> m_stamp;
+ int64_t m_pos;
+ std::chrono::milliseconds m_time;
+ int64_t m_size;
+};
+
+
+CFileCache::CFileCache(const unsigned int flags)
+ : CThread("FileCache"),
+ m_seekPossible(0),
+ m_nSeekResult(0),
+ m_seekPos(0),
+ m_readPos(0),
+ m_writePos(0),
+ m_chunkSize(0),
+ m_writeRate(0),
+ m_writeRateActual(0),
+ m_writeRateLowSpeed(0),
+ m_forwardCacheSize(0),
+ m_bFilling(false),
+ m_fileSize(0),
+ m_flags(flags)
+{
+}
+
+CFileCache::~CFileCache()
+{
+ Close();
+}
+
+IFile *CFileCache::GetFileImp()
+{
+ return m_source.GetImplementation();
+}
+
+bool CFileCache::Open(const CURL& url)
+{
+ Close();
+
+ std::unique_lock<CCriticalSection> lock(m_sync);
+
+ m_sourcePath = url.GetRedacted();
+
+ CLog::Log(LOGDEBUG, "CFileCache::{} - <{}> opening", __FUNCTION__, m_sourcePath);
+
+ // opening the source file.
+ if (!m_source.Open(url.Get(), READ_NO_CACHE | READ_TRUNCATED | READ_CHUNKED))
+ {
+ CLog::Log(LOGERROR, "CFileCache::{} - <{}> failed to open", __FUNCTION__, m_sourcePath);
+ Close();
+ return false;
+ }
+
+ m_source.IoControl(IOCTRL_SET_CACHE, this);
+
+ bool retry = false;
+ m_source.IoControl(IOCTRL_SET_RETRY, &retry); // We already handle retrying ourselves
+
+ // check if source can seek
+ m_seekPossible = m_source.IoControl(IOCTRL_SEEK_POSSIBLE, NULL);
+
+ // Determine the best chunk size we can use
+ m_chunkSize = CFile::DetermineChunkSize(
+ m_source.GetChunkSize(),
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_cacheChunkSize);
+ CLog::Log(LOGDEBUG,
+ "CFileCache::{} - <{}> source chunk size is {}, setting cache chunk size to {}",
+ __FUNCTION__, m_sourcePath, m_source.GetChunkSize(), m_chunkSize);
+
+ m_fileSize = m_source.GetLength();
+
+ if (!m_pCache)
+ {
+ if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_cacheMemSize == 0)
+ {
+ // Use cache on disk
+ m_pCache = std::unique_ptr<CSimpleFileCache>(new CSimpleFileCache()); // C++14 - Replace with std::make_unique
+ m_forwardCacheSize = 0;
+ }
+ else
+ {
+ size_t cacheSize;
+ if (m_fileSize > 0 && m_fileSize < CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_cacheMemSize && !(m_flags & READ_AUDIO_VIDEO))
+ {
+ // Cap cache size by filesize, but not for audio/video files as those may grow.
+ // We don't need to take into account READ_MULTI_STREAM here as that's only used for audio/video
+ cacheSize = m_fileSize;
+
+ // Cap chunk size by cache size
+ if (m_chunkSize > cacheSize)
+ m_chunkSize = cacheSize;
+ }
+ else
+ {
+ cacheSize = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_cacheMemSize;
+
+ // NOTE: READ_MULTI_STREAM is only used with READ_AUDIO_VIDEO
+ if (m_flags & READ_MULTI_STREAM)
+ {
+ // READ_MULTI_STREAM requires double buffering, so use half the amount of memory for each buffer
+ cacheSize /= 2;
+ }
+
+ // Make sure cache can at least hold 2 chunks
+ if (cacheSize < m_chunkSize * 2)
+ cacheSize = m_chunkSize * 2;
+ }
+
+ if (m_flags & READ_MULTI_STREAM)
+ CLog::Log(LOGDEBUG, "CFileCache::{} - <{}> using double memory cache each sized {} bytes",
+ __FUNCTION__, m_sourcePath, cacheSize);
+ else
+ CLog::Log(LOGDEBUG, "CFileCache::{} - <{}> using single memory cache sized {} bytes",
+ __FUNCTION__, m_sourcePath, cacheSize);
+
+ const size_t back = cacheSize / 4;
+ const size_t front = cacheSize - back;
+
+ m_pCache = std::unique_ptr<CCircularCache>(new CCircularCache(front, back)); // C++14 - Replace with std::make_unique
+ m_forwardCacheSize = front;
+ }
+
+ if (m_flags & READ_MULTI_STREAM)
+ {
+ // If READ_MULTI_STREAM flag is set: Double buffering is required
+ m_pCache = std::unique_ptr<CDoubleCache>(new CDoubleCache(m_pCache.release())); // C++14 - Replace with std::make_unique
+ }
+ }
+
+ // open cache strategy
+ if (!m_pCache || m_pCache->Open() != CACHE_RC_OK)
+ {
+ CLog::Log(LOGERROR, "CFileCache::{} - <{}> failed to open cache", __FUNCTION__, m_sourcePath);
+ Close();
+ return false;
+ }
+
+ m_readPos = 0;
+ m_writePos = 0;
+ m_writeRate = 1024 * 1024;
+ m_writeRateActual = 0;
+ m_writeRateLowSpeed = 0;
+ m_bFilling = true;
+ m_seekEvent.Reset();
+ m_seekEnded.Reset();
+
+ CThread::Create(false);
+
+ return true;
+}
+
+void CFileCache::Process()
+{
+ if (!m_pCache)
+ {
+ CLog::Log(LOGERROR, "CFileCache::{} - <{}> sanity failed. no cache strategy", __FUNCTION__,
+ m_sourcePath);
+ return;
+ }
+
+ // create our read buffer
+ std::unique_ptr<char[]> buffer(new char[m_chunkSize]);
+ if (buffer == nullptr)
+ {
+ CLog::Log(LOGERROR, "CFileCache::{} - <{}> failed to allocate read buffer", __FUNCTION__,
+ m_sourcePath);
+ return;
+ }
+
+ CWriteRate limiter;
+ CWriteRate average;
+
+ while (!m_bStop)
+ {
+ // Update filesize
+ m_fileSize = m_source.GetLength();
+
+ // check for seek events
+ if (m_seekEvent.Wait(0ms))
+ {
+ m_seekEvent.Reset();
+ const int64_t cacheMaxPos = m_pCache->CachedDataEndPosIfSeekTo(m_seekPos);
+ const bool cacheReachEOF = (cacheMaxPos == m_fileSize);
+
+ bool sourceSeekFailed = false;
+ if (!cacheReachEOF)
+ {
+ m_nSeekResult = m_source.Seek(cacheMaxPos, SEEK_SET);
+ if (m_nSeekResult != cacheMaxPos)
+ {
+ CLog::Log(LOGERROR, "CFileCache::{} - <{}> error {} seeking. Seek returned {}",
+ __FUNCTION__, m_sourcePath, GetLastError(), m_nSeekResult);
+ m_seekPossible = m_source.IoControl(IOCTRL_SEEK_POSSIBLE, NULL);
+ sourceSeekFailed = true;
+ }
+ }
+
+ if (!sourceSeekFailed)
+ {
+ const bool bCompleteReset = m_pCache->Reset(m_seekPos);
+ m_readPos = m_seekPos;
+ m_writePos = m_pCache->CachedDataEndPos();
+ assert(m_writePos == cacheMaxPos);
+ average.Reset(m_writePos, bCompleteReset); // Can only recalculate new average from scratch after a full reset (empty cache)
+ limiter.Reset(m_writePos);
+ m_nSeekResult = m_seekPos;
+ if (bCompleteReset)
+ {
+ CLog::Log(LOGDEBUG,
+ "CFileCache::{} - <{}> cache completely reset for seek to position {}",
+ __FUNCTION__, m_sourcePath, m_seekPos);
+ m_bFilling = true;
+ m_writeRateLowSpeed = 0;
+ }
+ }
+
+ m_seekEnded.Set();
+ }
+
+ while (m_writeRate)
+ {
+ if (m_writePos - m_readPos < m_writeRate * CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_cacheReadFactor)
+ {
+ limiter.Reset(m_writePos);
+ break;
+ }
+
+ if (limiter.Rate(m_writePos) < m_writeRate * CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_cacheReadFactor)
+ break;
+
+ if (m_seekEvent.Wait(100ms))
+ {
+ if (!m_bStop)
+ m_seekEvent.Set();
+ break;
+ }
+ }
+
+ const int64_t maxWrite = m_pCache->GetMaxWriteSize(m_chunkSize);
+ int64_t maxSourceRead = m_chunkSize;
+ // Cap source read size by space available between current write position and EOF
+ if (m_fileSize != 0)
+ maxSourceRead = std::min(maxSourceRead, m_fileSize - m_writePos);
+
+ /* Only read from source if there's enough write space in the cache
+ * else we may keep disposing data and seeking back on (slow) source
+ */
+ if (maxWrite < maxSourceRead)
+ {
+ // Wait until sufficient cache write space is available
+ m_pCache->m_space.Wait(5ms);
+ continue;
+ }
+
+ ssize_t iRead = 0;
+ if (maxSourceRead > 0)
+ iRead = m_source.Read(buffer.get(), maxSourceRead);
+ if (iRead <= 0)
+ {
+ // Check for actual EOF and retry as long as we still have data in our cache
+ if (m_writePos < m_fileSize && m_pCache->WaitForData(0, 0ms) > 0)
+ {
+ CLog::Log(LOGWARNING, "CFileCache::{} - <{}> source read returned {}! Will retry",
+ __FUNCTION__, m_sourcePath, iRead);
+
+ // Wait a bit:
+ if (m_seekEvent.Wait(2000ms))
+ {
+ if (!m_bStop)
+ m_seekEvent.Set(); // hack so that later we realize seek is needed
+ }
+
+ // and retry:
+ continue; // while (!m_bStop)
+ }
+ else
+ {
+ if (iRead < 0)
+ CLog::Log(LOGERROR,
+ "{} - <{}> source read failed with {}!", __FUNCTION__, m_sourcePath, iRead);
+ else if (m_fileSize == 0)
+ CLog::Log(LOGDEBUG,
+ "CFileCache::{} - <{}> source read didn't return any data! Hit eof(?)",
+ __FUNCTION__, m_sourcePath);
+ else if (m_writePos < m_fileSize)
+ CLog::Log(LOGERROR,
+ "CFileCache::{} - <{}> source read didn't return any data before eof!",
+ __FUNCTION__, m_sourcePath);
+ else
+ CLog::Log(LOGDEBUG, "CFileCache::{} - <{}> source read hit eof", __FUNCTION__,
+ m_sourcePath);
+
+ m_pCache->EndOfInput();
+
+ // The thread event will now also cause the wait of an event to return a false.
+ if (AbortableWait(m_seekEvent) == WAIT_SIGNALED)
+ {
+ m_pCache->ClearEndOfInput();
+ if (!m_bStop)
+ m_seekEvent.Set(); // hack so that later we realize seek is needed
+ }
+ else
+ break; // while (!m_bStop)
+ }
+ }
+
+ int iTotalWrite = 0;
+ while (!m_bStop && (iTotalWrite < iRead))
+ {
+ int iWrite = 0;
+ iWrite = m_pCache->WriteToCache(buffer.get() + iTotalWrite, iRead - iTotalWrite);
+
+ // write should always work. all handling of buffering and errors should be
+ // done inside the cache strategy. only if unrecoverable error happened, WriteToCache would return error and we break.
+ if (iWrite < 0)
+ {
+ CLog::Log(LOGERROR, "CFileCache::{} - <{}> error writing to cache", __FUNCTION__,
+ m_sourcePath);
+ m_bStop = true;
+ break;
+ }
+ else if (iWrite == 0)
+ {
+ m_pCache->m_space.Wait(5ms);
+ }
+
+ iTotalWrite += iWrite;
+
+ // check if seek was asked. otherwise if cache is full we'll freeze.
+ if (m_seekEvent.Wait(0ms))
+ {
+ if (!m_bStop)
+ m_seekEvent.Set(); // make sure we get the seek event later.
+ break;
+ }
+ }
+
+ m_writePos += iTotalWrite;
+
+ // under estimate write rate by a second, to
+ // avoid uncertainty at start of caching
+ m_writeRateActual = average.Rate(m_writePos, 1000);
+
+ /* NOTE: We can only reliably test for low speed condition, when the cache is *really*
+ * filling. This is because as soon as it's full the average-
+ * rate will become approximately the current-rate which can flag false
+ * low read-rate conditions.
+ */
+ if (m_bFilling && m_forwardCacheSize != 0)
+ {
+ const int64_t forward = m_pCache->WaitForData(0, 0ms);
+ if (forward + m_chunkSize >= m_forwardCacheSize)
+ {
+ if (m_writeRateActual < m_writeRate)
+ m_writeRateLowSpeed = m_writeRateActual;
+
+ m_bFilling = false;
+ }
+ }
+ }
+}
+
+void CFileCache::OnExit()
+{
+ m_bStop = true;
+
+ // make sure cache is set to mark end of file (read may be waiting).
+ if (m_pCache)
+ m_pCache->EndOfInput();
+
+ // just in case someone's waiting...
+ m_seekEnded.Set();
+}
+
+bool CFileCache::Exists(const CURL& url)
+{
+ return CFile::Exists(url.Get());
+}
+
+int CFileCache::Stat(const CURL& url, struct __stat64* buffer)
+{
+ return CFile::Stat(url.Get(), buffer);
+}
+
+ssize_t CFileCache::Read(void* lpBuf, size_t uiBufSize)
+{
+ std::unique_lock<CCriticalSection> lock(m_sync);
+ if (!m_pCache)
+ {
+ CLog::Log(LOGERROR, "CFileCache::{} - <{}> sanity failed. no cache strategy!", __FUNCTION__,
+ m_sourcePath);
+ return -1;
+ }
+ int64_t iRc;
+
+ if (uiBufSize > SSIZE_MAX)
+ uiBufSize = SSIZE_MAX;
+
+retry:
+ // attempt to read
+ iRc = m_pCache->ReadFromCache((char *)lpBuf, uiBufSize);
+ if (iRc > 0)
+ {
+ m_readPos += iRc;
+ return (int)iRc;
+ }
+
+ if (iRc == CACHE_RC_WOULD_BLOCK)
+ {
+ // just wait for some data to show up
+ iRc = m_pCache->WaitForData(1, 10s);
+ if (iRc > 0)
+ goto retry;
+ }
+
+ if (iRc == CACHE_RC_TIMEOUT)
+ {
+ CLog::Log(LOGWARNING, "CFileCache::{} - <{}> timeout waiting for data", __FUNCTION__,
+ m_sourcePath);
+ return -1;
+ }
+
+ if (iRc == 0)
+ return 0;
+
+ // unknown error code
+ CLog::Log(LOGERROR, "CFileCache::{} - <{}> cache strategy returned unknown error code {}",
+ __FUNCTION__, m_sourcePath, (int)iRc);
+ return -1;
+}
+
+int64_t CFileCache::Seek(int64_t iFilePosition, int iWhence)
+{
+ std::unique_lock<CCriticalSection> lock(m_sync);
+
+ if (!m_pCache)
+ {
+ CLog::Log(LOGERROR, "CFileCache::{} - <{}> sanity failed. no cache strategy!", __FUNCTION__,
+ m_sourcePath);
+ return -1;
+ }
+
+ int64_t iCurPos = m_readPos;
+ int64_t iTarget = iFilePosition;
+ if (iWhence == SEEK_END)
+ iTarget = m_fileSize + iTarget;
+ else if (iWhence == SEEK_CUR)
+ iTarget = iCurPos + iTarget;
+ else if (iWhence != SEEK_SET)
+ return -1;
+
+ if (iTarget == m_readPos)
+ return m_readPos;
+
+ if ((m_nSeekResult = m_pCache->Seek(iTarget)) != iTarget)
+ {
+ if (m_seekPossible == 0)
+ return m_nSeekResult;
+
+ // Never request closer to end than one chunk. Speeds up tag reading
+ m_seekPos = std::min(iTarget, std::max((int64_t)0, m_fileSize - m_chunkSize));
+
+ m_seekEvent.Set();
+ while (!m_seekEnded.Wait(100ms))
+ {
+ // SeekEnded will never be set if FileCache thread is not running
+ if (!CThread::IsRunning())
+ return -1;
+ }
+
+ /* wait for any remaining data */
+ if(m_seekPos < iTarget)
+ {
+ CLog::Log(LOGDEBUG, "CFileCache::{} - <{}> waiting for position {}", __FUNCTION__,
+ m_sourcePath, iTarget);
+ if (m_pCache->WaitForData(static_cast<uint32_t>(iTarget - m_seekPos), 10s) <
+ iTarget - m_seekPos)
+ {
+ CLog::Log(LOGWARNING, "CFileCache::{} - <{}> failed to get remaining data", __FUNCTION__,
+ m_sourcePath);
+ return -1;
+ }
+ m_pCache->Seek(iTarget);
+ }
+ m_readPos = iTarget;
+ m_seekEvent.Reset();
+ }
+ else
+ m_readPos = iTarget;
+
+ return iTarget;
+}
+
+void CFileCache::Close()
+{
+ StopThread();
+
+ std::unique_lock<CCriticalSection> lock(m_sync);
+ if (m_pCache)
+ m_pCache->Close();
+
+ m_source.Close();
+}
+
+int64_t CFileCache::GetPosition()
+{
+ return m_readPos;
+}
+
+int64_t CFileCache::GetLength()
+{
+ return m_fileSize;
+}
+
+void CFileCache::StopThread(bool bWait /*= true*/)
+{
+ m_bStop = true;
+ //Process could be waiting for seekEvent
+ m_seekEvent.Set();
+ CThread::StopThread(bWait);
+}
+
+const std::string CFileCache::GetProperty(XFILE::FileProperty type, const std::string &name) const
+{
+ if (!m_source.GetImplementation())
+ return IFile::GetProperty(type, name);
+
+ return m_source.GetImplementation()->GetProperty(type, name);
+}
+
+int CFileCache::IoControl(EIoControl request, void* param)
+{
+ if (request == IOCTRL_CACHE_STATUS)
+ {
+ SCacheStatus* status = (SCacheStatus*)param;
+ status->forward = m_pCache->WaitForData(0, 0ms);
+ status->maxrate = m_writeRate;
+ status->currate = m_writeRateActual;
+ status->lowrate = m_writeRateLowSpeed;
+ m_writeRateLowSpeed = 0; // Reset low speed condition
+ return 0;
+ }
+
+ if (request == IOCTRL_CACHE_SETRATE)
+ {
+ m_writeRate = *static_cast<uint32_t*>(param);
+ return 0;
+ }
+
+ if (request == IOCTRL_SEEK_POSSIBLE)
+ return m_seekPossible;
+
+ return -1;
+}
diff --git a/xbmc/filesystem/FileCache.h b/xbmc/filesystem/FileCache.h
new file mode 100644
index 0000000..df7c839
--- /dev/null
+++ b/xbmc/filesystem/FileCache.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "CacheStrategy.h"
+#include "File.h"
+#include "IFile.h"
+#include "threads/CriticalSection.h"
+#include "threads/Thread.h"
+
+#include <atomic>
+#include <memory>
+
+namespace XFILE
+{
+
+ class CFileCache : public IFile, public CThread
+ {
+ public:
+ explicit CFileCache(const unsigned int flags);
+ ~CFileCache() override;
+
+ // CThread methods
+ void Process() override;
+ void OnExit() override;
+ void StopThread(bool bWait = true) override;
+
+ // IFIle methods
+ bool Open(const CURL& url) override;
+ void Close() override;
+ bool Exists(const CURL& url) override;
+ int Stat(const CURL& url, struct __stat64* buffer) override;
+
+ ssize_t Read(void* lpBuf, size_t uiBufSize) override;
+
+ int64_t Seek(int64_t iFilePosition, int iWhence) override;
+ int64_t GetPosition() override;
+ int64_t GetLength() override;
+
+ int IoControl(EIoControl request, void* param) override;
+
+ IFile *GetFileImp();
+
+ const std::string GetProperty(XFILE::FileProperty type, const std::string &name = "") const override;
+
+ const std::vector<std::string> GetPropertyValues(XFILE::FileProperty type, const std::string& name = "") const override
+ {
+ return std::vector<std::string>();
+ }
+
+ private:
+ std::unique_ptr<CCacheStrategy> m_pCache;
+ int m_seekPossible;
+ CFile m_source;
+ std::string m_sourcePath;
+ CEvent m_seekEvent;
+ CEvent m_seekEnded;
+ int64_t m_nSeekResult;
+ int64_t m_seekPos;
+ int64_t m_readPos;
+ int64_t m_writePos;
+ unsigned m_chunkSize;
+ uint32_t m_writeRate;
+ uint32_t m_writeRateActual;
+ uint32_t m_writeRateLowSpeed;
+ int64_t m_forwardCacheSize;
+ bool m_bFilling;
+ std::atomic<int64_t> m_fileSize;
+ unsigned int m_flags;
+ CCriticalSection m_sync;
+ };
+
+}
diff --git a/xbmc/filesystem/FileDirectoryFactory.cpp b/xbmc/filesystem/FileDirectoryFactory.cpp
new file mode 100644
index 0000000..a4e0f3f
--- /dev/null
+++ b/xbmc/filesystem/FileDirectoryFactory.cpp
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "FileDirectoryFactory.h"
+
+#if defined(HAS_ISO9660PP)
+#include "ISO9660Directory.h"
+#endif
+#if defined(HAS_UDFREAD)
+#include "UDFDirectory.h"
+#endif
+#include "RSSDirectory.h"
+#include "UDFDirectory.h"
+#include "utils/URIUtils.h"
+#if defined(TARGET_ANDROID)
+#include "platform/android/filesystem/APKDirectory.h"
+#endif
+#include "AudioBookFileDirectory.h"
+#include "Directory.h"
+#include "FileItem.h"
+#include "PlaylistFileDirectory.h"
+#include "ServiceBroker.h"
+#include "SmartPlaylistDirectory.h"
+#include "URL.h"
+#include "XbtDirectory.h"
+#include "ZipDirectory.h"
+#include "addons/AudioDecoder.h"
+#include "addons/ExtsMimeSupportList.h"
+#include "addons/VFSEntry.h"
+#include "addons/addoninfo/AddonInfo.h"
+#include "playlists/PlayListFactory.h"
+#include "playlists/SmartPlayList.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+using namespace ADDON;
+using namespace KODI::ADDONS;
+using namespace XFILE;
+using namespace PLAYLIST;
+
+CFileDirectoryFactory::CFileDirectoryFactory(void) = default;
+
+CFileDirectoryFactory::~CFileDirectoryFactory(void) = default;
+
+// return NULL + set pItem->m_bIsFolder to remove it completely from list.
+IFileDirectory* CFileDirectoryFactory::Create(const CURL& url, CFileItem* pItem, const std::string& strMask)
+{
+ if (url.IsProtocol("stack")) // disqualify stack as we need to work with each of the parts instead
+ return NULL;
+
+ /**
+ * Check available binary addons which can contain files with underlaid
+ * folders / files.
+ * Currently in vfs and audiodecoder addons.
+ *
+ * @note The file extensions are absolutely necessary for these in order to
+ * identify the associated add-on.
+ */
+ /**@{*/
+
+ // Get file extensions to find addon related to it.
+ std::string strExtension = URIUtils::GetExtension(url);
+ StringUtils::ToLower(strExtension);
+
+ if (!strExtension.empty() && CServiceBroker::IsAddonInterfaceUp())
+ {
+ /*!
+ * Scan here about audiodecoder addons.
+ *
+ * @note: Do not check audio decoder files that are already open, they cannot
+ * contain any further sub-folders.
+ */
+ if (!StringUtils::EndsWith(strExtension, KODI_ADDON_AUDIODECODER_TRACK_EXT))
+ {
+ auto addonInfos = CServiceBroker::GetExtsMimeSupportList().GetExtensionSupportedAddonInfos(
+ strExtension, CExtsMimeSupportList::FilterSelect::hasTracks);
+ for (const auto& addonInfo : addonInfos)
+ {
+ std::unique_ptr<CAudioDecoder> result = std::make_unique<CAudioDecoder>(addonInfo.second);
+ if (!result->CreateDecoder() || !result->ContainsFiles(url))
+ {
+ CLog::Log(LOGINFO,
+ "CFileDirectoryFactory::{}: Addon '{}' support extension '{}' but creation "
+ "failed (seems not supported), trying other addons and Kodi",
+ __func__, addonInfo.second->ID(), strExtension);
+ continue;
+ }
+ return result.release();
+ }
+ }
+
+ /*!
+ * Scan here about VFS addons.
+ */
+ for (const auto& vfsAddon : CServiceBroker::GetVFSAddonCache().GetAddonInstances())
+ {
+ if (vfsAddon->HasFileDirectories())
+ {
+ auto exts = StringUtils::Split(vfsAddon->GetExtensions(), "|");
+ if (std::find(exts.begin(), exts.end(), strExtension) != exts.end())
+ {
+ CVFSEntryIFileDirectoryWrapper* wrap = new CVFSEntryIFileDirectoryWrapper(vfsAddon);
+ if (wrap->ContainsFiles(url))
+ {
+ if (wrap->m_items.Size() == 1)
+ {
+ // one STORED file - collapse it down
+ *pItem = *wrap->m_items[0];
+ }
+ else
+ {
+ // compressed or more than one file -> create a dir
+ pItem->SetPath(wrap->m_items.GetPath());
+ }
+
+ // Check for folder, if yes return also wrap.
+ // Needed to fix for e.g. RAR files with only one file inside
+ pItem->m_bIsFolder = URIUtils::HasSlashAtEnd(pItem->GetPath());
+ if (pItem->m_bIsFolder)
+ return wrap;
+ }
+ else
+ {
+ pItem->m_bIsFolder = true;
+ }
+
+ delete wrap;
+ return nullptr;
+ }
+ }
+ }
+ }
+ /**@}*/
+
+ if (pItem->IsRSS())
+ return new CRSSDirectory();
+
+
+ if (pItem->IsDiscImage())
+ {
+#if defined(HAS_ISO9660PP)
+ CISO9660Directory* iso = new CISO9660Directory();
+ if (iso->Exists(pItem->GetURL()))
+ return iso;
+
+ delete iso;
+#endif
+
+#if defined(HAS_UDFREAD)
+ return new CUDFDirectory();
+#endif
+
+ return nullptr;
+ }
+
+#if defined(TARGET_ANDROID)
+ if (url.IsFileType("apk"))
+ {
+ CURL zipURL = URIUtils::CreateArchivePath("apk", url);
+
+ CFileItemList items;
+ CDirectory::GetDirectory(zipURL, items, strMask, DIR_FLAG_DEFAULTS);
+ if (items.Size() == 0) // no files
+ pItem->m_bIsFolder = true;
+ else if (items.Size() == 1 && items[0]->m_idepth == 0 && !items[0]->m_bIsFolder)
+ {
+ // one STORED file - collapse it down
+ *pItem = *items[0];
+ }
+ else
+ { // compressed or more than one file -> create a apk dir
+ pItem->SetURL(zipURL);
+ return new CAPKDirectory;
+ }
+ return NULL;
+ }
+#endif
+ if (url.IsFileType("zip"))
+ {
+ CURL zipURL = URIUtils::CreateArchivePath("zip", url);
+
+ CFileItemList items;
+ CDirectory::GetDirectory(zipURL, items, strMask, DIR_FLAG_DEFAULTS);
+ if (items.Size() == 0) // no files
+ pItem->m_bIsFolder = true;
+ else if (items.Size() == 1 && items[0]->m_idepth == 0 && !items[0]->m_bIsFolder)
+ {
+ // one STORED file - collapse it down
+ *pItem = *items[0];
+ }
+ else
+ { // compressed or more than one file -> create a zip dir
+ pItem->SetURL(zipURL);
+ return new CZipDirectory;
+ }
+ return NULL;
+ }
+ if (url.IsFileType("xbt"))
+ {
+ CURL xbtUrl = URIUtils::CreateArchivePath("xbt", url);
+ pItem->SetURL(xbtUrl);
+
+ return new CXbtDirectory();
+ }
+ if (url.IsFileType("xsp"))
+ { // XBMC Smart playlist - just XML renamed to XSP
+ // read the name of the playlist in
+ CSmartPlaylist playlist;
+ if (playlist.OpenAndReadName(url))
+ {
+ pItem->SetLabel(playlist.GetName());
+ pItem->SetLabelPreformatted(true);
+ }
+ IFileDirectory* pDir=new CSmartPlaylistDirectory;
+ return pDir; // treat as directory
+ }
+ if (CPlayListFactory::IsPlaylist(url))
+ { // Playlist file
+ // currently we only return the directory if it contains
+ // more than one file. Reason is that .pls and .m3u may be used
+ // for links to http streams etc.
+ IFileDirectory *pDir = new CPlaylistFileDirectory();
+ CFileItemList items;
+ if (pDir->GetDirectory(url, items))
+ {
+ if (items.Size() > 1)
+ return pDir;
+ }
+ delete pDir;
+ return NULL;
+ }
+
+ if (pItem->IsAudioBook())
+ {
+ if (!pItem->HasMusicInfoTag() || pItem->GetEndOffset() <= 0)
+ {
+ std::unique_ptr<CAudioBookFileDirectory> pDir(new CAudioBookFileDirectory);
+ if (pDir->ContainsFiles(url))
+ return pDir.release();
+ }
+ return NULL;
+ }
+ return NULL;
+}
+
diff --git a/xbmc/filesystem/FileDirectoryFactory.h b/xbmc/filesystem/FileDirectoryFactory.h
new file mode 100644
index 0000000..b5b4575
--- /dev/null
+++ b/xbmc/filesystem/FileDirectoryFactory.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IFileDirectory.h"
+
+#include <string>
+
+class CFileItem;
+
+namespace XFILE
+{
+class CFileDirectoryFactory
+{
+public:
+ CFileDirectoryFactory(void);
+ virtual ~CFileDirectoryFactory(void);
+ static IFileDirectory* Create(const CURL& url, CFileItem* pItem, const std::string& strMask="");
+};
+}
diff --git a/xbmc/filesystem/FileFactory.cpp b/xbmc/filesystem/FileFactory.cpp
new file mode 100644
index 0000000..b248255
--- /dev/null
+++ b/xbmc/filesystem/FileFactory.cpp
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "network/Network.h"
+#include "FileFactory.h"
+#ifdef TARGET_POSIX
+#include "platform/posix/filesystem/PosixFile.h"
+#elif defined(TARGET_WINDOWS)
+#include "platform/win32/filesystem/Win32File.h"
+#ifdef TARGET_WINDOWS_STORE
+#include "platform/win10/filesystem/WinLibraryFile.h"
+#endif
+#endif // TARGET_WINDOWS
+#include "CurlFile.h"
+#include "DAVFile.h"
+#include "ShoutcastFile.h"
+#ifdef HAS_FILESYSTEM_SMB
+#ifdef TARGET_WINDOWS
+#include "platform/win32/filesystem/Win32SMBFile.h"
+#else
+#include "platform/posix/filesystem/SMBFile.h"
+#endif
+#endif
+#include "CDDAFile.h"
+#if defined(HAS_ISO9660PP)
+#include "ISO9660File.h"
+#endif
+#if defined(TARGET_ANDROID)
+#include "platform/android/filesystem/APKFile.h"
+#endif
+#include "XbtFile.h"
+#include "ZipFile.h"
+#ifdef HAS_FILESYSTEM_NFS
+#include "NFSFile.h"
+#endif
+#if defined(TARGET_ANDROID)
+#include "platform/android/filesystem/AndroidAppFile.h"
+#endif
+#if defined(TARGET_DARWIN_TVOS)
+#include "platform/darwin/tvos/filesystem/TVOSFile.h"
+#endif // TARGET_DARWIN_TVOS
+#ifdef HAS_UPNP
+#include "UPnPFile.h"
+#endif
+#ifdef HAVE_LIBBLURAY
+#include "BlurayFile.h"
+#endif
+#include "PipeFile.h"
+#include "MusicDatabaseFile.h"
+#include "VideoDatabaseFile.h"
+#include "PluginFile.h"
+#include "SpecialProtocolFile.h"
+#include "MultiPathFile.h"
+#if defined(HAS_UDFREAD)
+#include "UDFFile.h"
+#endif
+#include "ImageFile.h"
+#include "ResourceFile.h"
+#include "URL.h"
+#include "utils/log.h"
+#include "network/WakeOnAccess.h"
+#include "utils/StringUtils.h"
+#include "ServiceBroker.h"
+#include "addons/VFSEntry.h"
+
+using namespace ADDON;
+using namespace XFILE;
+
+CFileFactory::CFileFactory() = default;
+
+CFileFactory::~CFileFactory() = default;
+
+IFile* CFileFactory::CreateLoader(const std::string& strFileName)
+{
+ CURL url(strFileName);
+ return CreateLoader(url);
+}
+
+IFile* CFileFactory::CreateLoader(const CURL& url)
+{
+ if (!CWakeOnAccess::GetInstance().WakeUpHost(url))
+ return NULL;
+
+ if (!url.GetProtocol().empty() && CServiceBroker::IsAddonInterfaceUp())
+ {
+ for (const auto& vfsAddon : CServiceBroker::GetVFSAddonCache().GetAddonInstances())
+ {
+ auto prots = StringUtils::Split(vfsAddon->GetProtocols(), "|");
+
+ if (vfsAddon->HasFiles() && std::find(prots.begin(), prots.end(), url.GetProtocol()) != prots.end())
+ return new CVFSEntryIFileWrapper(vfsAddon);
+ }
+ }
+
+#if defined(TARGET_ANDROID)
+ if (url.IsProtocol("apk")) return new CAPKFile();
+#endif
+ if (url.IsProtocol("zip")) return new CZipFile();
+ else if (url.IsProtocol("xbt")) return new CXbtFile();
+ else if (url.IsProtocol("musicdb")) return new CMusicDatabaseFile();
+ else if (url.IsProtocol("videodb")) return new CVideoDatabaseFile();
+ else if (url.IsProtocol("plugin")) return new CPluginFile();
+ else if (url.IsProtocol("library")) return nullptr;
+ else if (url.IsProtocol("pvr")) return nullptr;
+ else if (url.IsProtocol("special")) return new CSpecialProtocolFile();
+ else if (url.IsProtocol("multipath")) return new CMultiPathFile();
+ else if (url.IsProtocol("image")) return new CImageFile();
+#ifdef TARGET_POSIX
+ else if (url.IsProtocol("file") || url.GetProtocol().empty())
+ {
+#if defined(TARGET_DARWIN_TVOS)
+ if (CTVOSFile::WantsFile(url))
+ return new CTVOSFile();
+#endif
+ return new CPosixFile();
+ }
+#elif defined(TARGET_WINDOWS)
+ else if (url.IsProtocol("file") || url.GetProtocol().empty())
+ {
+#ifdef TARGET_WINDOWS_STORE
+ if (CWinLibraryFile::IsInAccessList(url))
+ return new CWinLibraryFile();
+#endif
+ return new CWin32File();
+ }
+#endif // TARGET_WINDOWS
+#if defined(HAS_DVD_DRIVE)
+ else if (url.IsProtocol("cdda")) return new CFileCDDA();
+#endif
+#if defined(HAS_ISO9660PP)
+ else if (url.IsProtocol("iso9660"))
+ return new CISO9660File();
+#endif
+#if defined(HAS_UDFREAD)
+ else if(url.IsProtocol("udf"))
+ return new CUDFFile();
+#endif
+#if defined(TARGET_ANDROID)
+ else if (url.IsProtocol("androidapp")) return new CFileAndroidApp();
+#endif
+ else if (url.IsProtocol("pipe")) return new CPipeFile();
+#ifdef HAVE_LIBBLURAY
+ else if (url.IsProtocol("bluray")) return new CBlurayFile();
+#endif
+ else if (url.IsProtocol("resource")) return new CResourceFile();
+#ifdef TARGET_WINDOWS_STORE
+ else if (CWinLibraryFile::IsValid(url)) return new CWinLibraryFile();
+#endif
+
+ if (url.IsProtocol("ftp")
+ || url.IsProtocol("ftps")
+ || url.IsProtocol("rss")
+ || url.IsProtocol("rsss")
+ || url.IsProtocol("http")
+ || url.IsProtocol("https")) return new CCurlFile();
+ else if (url.IsProtocol("dav") || url.IsProtocol("davs")) return new CDAVFile();
+ else if (url.IsProtocol("shout") || url.IsProtocol("shouts")) return new CShoutcastFile();
+#ifdef HAS_FILESYSTEM_SMB
+#ifdef TARGET_WINDOWS
+ else if (url.IsProtocol("smb")) return new CWin32SMBFile();
+#else
+ else if (url.IsProtocol("smb")) return new CSMBFile();
+#endif
+#endif
+#ifdef HAS_FILESYSTEM_NFS
+ else if (url.IsProtocol("nfs")) return new CNFSFile();
+#endif
+#ifdef HAS_UPNP
+ else if (url.IsProtocol("upnp")) return new CUPnPFile();
+#endif
+
+ CLog::Log(LOGWARNING, "{} - unsupported protocol({}) in {}", __FUNCTION__, url.GetProtocol(),
+ url.GetRedacted());
+ return NULL;
+}
diff --git a/xbmc/filesystem/FileFactory.h b/xbmc/filesystem/FileFactory.h
new file mode 100644
index 0000000..ec0fd10
--- /dev/null
+++ b/xbmc/filesystem/FileFactory.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+// FileFactory1.h: interface for the CFileFactory class.
+//
+//////////////////////////////////////////////////////////////////////
+
+#include "IFile.h"
+
+#include <string>
+
+namespace XFILE
+{
+class CFileFactory
+{
+public:
+ CFileFactory();
+ virtual ~CFileFactory();
+ static IFile* CreateLoader(const std::string& strFileName);
+ static IFile* CreateLoader(const CURL& url);
+};
+}
diff --git a/xbmc/filesystem/HTTPDirectory.cpp b/xbmc/filesystem/HTTPDirectory.cpp
new file mode 100644
index 0000000..0097b7f
--- /dev/null
+++ b/xbmc/filesystem/HTTPDirectory.cpp
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "HTTPDirectory.h"
+
+#include "CurlFile.h"
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/CharsetConverter.h"
+#include "utils/HTMLUtil.h"
+#include "utils/RegExp.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+
+#include <climits>
+
+using namespace XFILE;
+
+CHTTPDirectory::CHTTPDirectory(void) = default;
+CHTTPDirectory::~CHTTPDirectory(void) = default;
+
+bool CHTTPDirectory::GetDirectory(const CURL& url, CFileItemList &items)
+{
+ CCurlFile http;
+
+ const std::string& strBasePath = url.GetFileName();
+
+ if(!http.Open(url))
+ {
+ CLog::Log(LOGERROR, "{} - Unable to get http directory ({})", __FUNCTION__, url.GetRedacted());
+ return false;
+ }
+
+ CRegExp reItem(true); // HTML is case-insensitive
+ reItem.RegComp("<a href=\"([^\"]*)\"[^>]*>\\s*(.*?)\\s*</a>(.+?)(?=<a|</tr|$)");
+
+ CRegExp reDateTimeHtml(true);
+ reDateTimeHtml.RegComp(
+ "<td align=\"right\">([0-9]{2})-([A-Z]{3})-([0-9]{4}) ([0-9]{2}):([0-9]{2}) +</td>");
+
+ CRegExp reDateTimeLighttp(true);
+ reDateTimeLighttp.RegComp(
+ "<td class=\"m\">([0-9]{4})-([A-Z]{3})-([0-9]{2}) ([0-9]{2}):([0-9]{2}):([0-9]{2})</td>");
+
+ CRegExp reDateTimeNginx(true);
+ reDateTimeNginx.RegComp("([0-9]{2})-([A-Z]{3})-([0-9]{4}) ([0-9]{2}):([0-9]{2})");
+
+ CRegExp reDateTimeNginxFancy(true);
+ reDateTimeNginxFancy.RegComp(
+ "<td class=\"date\">([0-9]{4})-([A-Z]{3})-([0-9]{2}) ([0-9]{2}):([0-9]{2})</td>");
+
+ CRegExp reDateTimeApacheNewFormat(true);
+ reDateTimeApacheNewFormat.RegComp(
+ "<td align=\"right\">([0-9]{4})-([0-9]{2})-([0-9]{2}) ([0-9]{2}):([0-9]{2}) +</td>");
+
+ CRegExp reDateTime(true);
+ reDateTime.RegComp("([0-9]{4})-([0-9]{2})-([0-9]{2}) ([0-9]{2}):([0-9]{2})");
+
+ CRegExp reSizeHtml(true);
+ reSizeHtml.RegComp("> *([0-9.]+) *(B|K|M|G| )(iB)?</td>");
+
+ CRegExp reSize(true);
+ reSize.RegComp(" +([0-9]+)(B|K|M|G)?(?=\\s|<|$)");
+
+ /* read response from server into string buffer */
+ std::string strBuffer;
+ if (http.ReadData(strBuffer) && strBuffer.length() > 0)
+ {
+ /* if Content-Length is found and its not text/html, URL is pointing to file so don't treat URL as HTTPDirectory */
+ if (!http.GetHttpHeader().GetValue("Content-Length").empty() &&
+ !StringUtils::StartsWithNoCase(http.GetHttpHeader().GetValue("Content-type"), "text/html"))
+ {
+ return false;
+ }
+
+ std::string fileCharset(http.GetProperty(XFILE::FILE_PROPERTY_CONTENT_CHARSET));
+ if (!fileCharset.empty() && fileCharset != "UTF-8")
+ {
+ std::string converted;
+ if (g_charsetConverter.ToUtf8(fileCharset, strBuffer, converted) && !converted.empty())
+ strBuffer = converted;
+ }
+
+ unsigned int bufferOffset = 0;
+ while (bufferOffset < strBuffer.length())
+ {
+ int matchOffset = reItem.RegFind(strBuffer.c_str(), bufferOffset);
+ if (matchOffset < 0)
+ break;
+
+ bufferOffset = matchOffset + reItem.GetSubLength(0);
+
+ std::string strLink = reItem.GetMatch(1);
+ std::string strName = reItem.GetMatch(2);
+ std::string strMetadata = reItem.GetMatch(3);
+ StringUtils::Trim(strMetadata);
+
+ if(strLink[0] == '/')
+ strLink = strLink.substr(1);
+
+ std::string strNameTemp = StringUtils::Trim(strName);
+
+ std::wstring wName, wLink, wConverted;
+ if (fileCharset.empty())
+ g_charsetConverter.unknownToUTF8(strNameTemp);
+ g_charsetConverter.utf8ToW(strNameTemp, wName, false);
+ HTML::CHTMLUtil::ConvertHTMLToW(wName, wConverted);
+ g_charsetConverter.wToUTF8(wConverted, strNameTemp);
+ URIUtils::RemoveSlashAtEnd(strNameTemp);
+
+ std::string strLinkBase = strLink;
+ std::string strLinkOptions;
+
+ // split link with url options
+ size_t pos = strLinkBase.find('?');
+ if (pos != std::string::npos)
+ {
+ strLinkOptions = strLinkBase.substr(pos);
+ strLinkBase.erase(pos);
+ }
+
+ // strip url fragment from the link
+ pos = strLinkBase.find('#');
+ if (pos != std::string::npos)
+ {
+ strLinkBase.erase(pos);
+ }
+
+ // Convert any HTTP character entities (e.g.: "&amp;") to percentage encoding
+ // (e.g.: "%xx") as some web servers (Apache) put these in HTTP Directory Indexes
+ // this is also needed as CURL objects interpret them incorrectly due to the ;
+ // also being allowed as URL option separator
+ if (fileCharset.empty())
+ g_charsetConverter.unknownToUTF8(strLinkBase);
+ g_charsetConverter.utf8ToW(strLinkBase, wLink, false);
+ HTML::CHTMLUtil::ConvertHTMLToW(wLink, wConverted);
+ g_charsetConverter.wToUTF8(wConverted, strLinkBase);
+
+ // encoding + and ; to URL encode if it is not already encoded by http server used on the remote server (example: Apache)
+ // more characters may be added here when required when required by certain http servers
+ pos = strLinkBase.find_first_of("+;");
+ while (pos != std::string::npos)
+ {
+ std::stringstream convert;
+ convert << '%' << std::hex << int(strLinkBase.at(pos));
+ strLinkBase.replace(pos, 1, convert.str());
+ pos = strLinkBase.find_first_of("+;");
+ }
+
+ std::string strLinkTemp = strLinkBase;
+
+ URIUtils::RemoveSlashAtEnd(strLinkTemp);
+ strLinkTemp = CURL::Decode(strLinkTemp);
+
+ if (StringUtils::EndsWith(strNameTemp, "..>") &&
+ StringUtils::StartsWith(strLinkTemp, strNameTemp.substr(0, strNameTemp.length() - 3)))
+ strName = strNameTemp = strLinkTemp;
+
+ /* Per RFC 1808 § 5.3, relative paths containing a colon ":" should be either prefixed with
+ * "./" or escaped (as "%3A"). This handles the prefix case, the escaping should be handled by
+ * the CURL::Decode above
+ * - https://tools.ietf.org/html/rfc1808#section-5.3
+ */
+ auto NameMatchesLink([](const std::string& name, const std::string& link) -> bool
+ {
+ return (name == link) ||
+ ((std::string::npos != name.find(':')) && (std::string{"./"}.append(name) == link));
+ });
+
+ // we detect http directory items by its display name and its stripped link
+ // if same, we consider it as a valid item.
+ if (strLinkTemp != ".." && strLinkTemp != "" && NameMatchesLink(strNameTemp, strLinkTemp))
+ {
+ CFileItemPtr pItem(new CFileItem(strNameTemp));
+ pItem->SetProperty("IsHTTPDirectory", true);
+ CURL url2(url);
+
+ url2.SetFileName(strBasePath + strLinkBase);
+ url2.SetOptions(strLinkOptions);
+ pItem->SetURL(url2);
+
+ if(URIUtils::HasSlashAtEnd(pItem->GetPath(), true))
+ pItem->m_bIsFolder = true;
+
+ std::string day, month, year, hour, minute;
+ int monthNum = 0;
+
+ if (reDateTimeHtml.RegFind(strMetadata.c_str()) >= 0)
+ {
+ day = reDateTimeHtml.GetMatch(1);
+ month = reDateTimeHtml.GetMatch(2);
+ year = reDateTimeHtml.GetMatch(3);
+ hour = reDateTimeHtml.GetMatch(4);
+ minute = reDateTimeHtml.GetMatch(5);
+ }
+ else if (reDateTimeNginxFancy.RegFind(strMetadata.c_str()) >= 0)
+ {
+ day = reDateTimeNginxFancy.GetMatch(3);
+ month = reDateTimeNginxFancy.GetMatch(2);
+ year = reDateTimeNginxFancy.GetMatch(1);
+ hour = reDateTimeNginxFancy.GetMatch(4);
+ minute = reDateTimeNginxFancy.GetMatch(5);
+ }
+ else if (reDateTimeNginx.RegFind(strMetadata.c_str()) >= 0)
+ {
+ day = reDateTimeNginx.GetMatch(1);
+ month = reDateTimeNginx.GetMatch(2);
+ year = reDateTimeNginx.GetMatch(3);
+ hour = reDateTimeNginx.GetMatch(4);
+ minute = reDateTimeNginx.GetMatch(5);
+ }
+ else if (reDateTimeLighttp.RegFind(strMetadata.c_str()) >= 0)
+ {
+ day = reDateTimeLighttp.GetMatch(3);
+ month = reDateTimeLighttp.GetMatch(2);
+ year = reDateTimeLighttp.GetMatch(1);
+ hour = reDateTimeLighttp.GetMatch(4);
+ minute = reDateTimeLighttp.GetMatch(5);
+ }
+ else if (reDateTimeApacheNewFormat.RegFind(strMetadata.c_str()) >= 0)
+ {
+ day = reDateTimeApacheNewFormat.GetMatch(3);
+ monthNum = atoi(reDateTimeApacheNewFormat.GetMatch(2).c_str());
+ year = reDateTimeApacheNewFormat.GetMatch(1);
+ hour = reDateTimeApacheNewFormat.GetMatch(4);
+ minute = reDateTimeApacheNewFormat.GetMatch(5);
+ }
+ else if (reDateTime.RegFind(strMetadata.c_str()) >= 0)
+ {
+ day = reDateTime.GetMatch(3);
+ monthNum = atoi(reDateTime.GetMatch(2).c_str());
+ year = reDateTime.GetMatch(1);
+ hour = reDateTime.GetMatch(4);
+ minute = reDateTime.GetMatch(5);
+ }
+
+ if (month.length() > 0)
+ monthNum = CDateTime::MonthStringToMonthNum(month);
+
+ if (day.length() > 0 && monthNum > 0 && year.length() > 0)
+ {
+ pItem->m_dateTime = CDateTime(atoi(year.c_str()), monthNum, atoi(day.c_str()), atoi(hour.c_str()), atoi(minute.c_str()), 0);
+ }
+
+ if (!pItem->m_bIsFolder)
+ {
+ if (reSizeHtml.RegFind(strMetadata.c_str()) >= 0)
+ {
+ double Size = atof(reSizeHtml.GetMatch(1).c_str());
+ std::string strUnit(reSizeHtml.GetMatch(2));
+
+ if (strUnit == "K")
+ Size = Size * 1024;
+ else if (strUnit == "M")
+ Size = Size * 1024 * 1024;
+ else if (strUnit == "G")
+ Size = Size * 1024 * 1024 * 1024;
+
+ pItem->m_dwSize = (int64_t)Size;
+ }
+ else if (reSize.RegFind(strMetadata.c_str()) >= 0)
+ {
+ double Size = atof(reSize.GetMatch(1).c_str());
+ std::string strUnit(reSize.GetMatch(2));
+
+ if (strUnit == "K")
+ Size = Size * 1024;
+ else if (strUnit == "M")
+ Size = Size * 1024 * 1024;
+ else if (strUnit == "G")
+ Size = Size * 1024 * 1024 * 1024;
+
+ pItem->m_dwSize = (int64_t)Size;
+ }
+ else
+ if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_bHTTPDirectoryStatFilesize) // As a fallback get the size by stat-ing the file (slow)
+ {
+ CCurlFile file;
+ file.Open(url);
+ pItem->m_dwSize=file.GetLength();
+ file.Close();
+ }
+ }
+ items.Add(pItem);
+ }
+ }
+ }
+ http.Close();
+
+ items.SetProperty("IsHTTPDirectory", true);
+
+ return true;
+}
+
+bool CHTTPDirectory::Exists(const CURL &url)
+{
+ CCurlFile http;
+ struct __stat64 buffer;
+
+ if( http.Stat(url, &buffer) != 0 )
+ {
+ return false;
+ }
+
+ if (buffer.st_mode == _S_IFDIR)
+ return true;
+
+ return false;
+}
diff --git a/xbmc/filesystem/HTTPDirectory.h b/xbmc/filesystem/HTTPDirectory.h
new file mode 100644
index 0000000..02a27d9
--- /dev/null
+++ b/xbmc/filesystem/HTTPDirectory.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IDirectory.h"
+
+namespace XFILE
+{
+ class CHTTPDirectory : public IDirectory
+ {
+ public:
+ CHTTPDirectory(void);
+ ~CHTTPDirectory(void) override;
+ bool GetDirectory(const CURL& url, CFileItemList &items) override;
+ bool Exists(const CURL& url) override;
+ DIR_CACHE_TYPE GetCacheType(const CURL& url) const override { return DIR_CACHE_ONCE; }
+
+ private:
+ };
+}
diff --git a/xbmc/filesystem/IDirectory.cpp b/xbmc/filesystem/IDirectory.cpp
new file mode 100644
index 0000000..b2ad59a
--- /dev/null
+++ b/xbmc/filesystem/IDirectory.cpp
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "IDirectory.h"
+
+#include "PasswordManager.h"
+#include "URL.h"
+#include "guilib/GUIKeyboardFactory.h"
+#include "messaging/helpers/DialogOKHelper.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+
+using namespace KODI::MESSAGING;
+using namespace XFILE;
+
+const CProfileManager *IDirectory::m_profileManager = nullptr;
+
+void IDirectory::RegisterProfileManager(const CProfileManager &profileManager)
+{
+ m_profileManager = &profileManager;
+}
+
+void IDirectory::UnregisterProfileManager()
+{
+ m_profileManager = nullptr;
+}
+
+IDirectory::IDirectory()
+{
+ m_flags = DIR_FLAG_DEFAULTS;
+}
+
+IDirectory::~IDirectory(void) = default;
+
+/*!
+ \brief Test if file have an allowed extension, as specified with SetMask()
+ \param strFile File to test
+ \return \e true if file is allowed
+ \note If extension is ".ifo", filename format must be "vide_ts.ifo" or
+ "vts_##_0.ifo". If extension is ".dat", filename format must be
+ "AVSEQ##(#).DAT", "ITEM###(#).DAT" or "MUSIC##(#).DAT".
+ */
+bool IDirectory::IsAllowed(const CURL& url) const
+{
+ if (m_strFileMask.empty())
+ return true;
+
+ // Check if strFile have an allowed extension
+ if (!URIUtils::HasExtension(url, m_strFileMask))
+ return false;
+
+ // We should ignore all non dvd/vcd related ifo and dat files.
+ if (URIUtils::HasExtension(url, ".ifo"))
+ {
+ std::string fileName = URIUtils::GetFileName(url);
+
+ // Allow filenames of the form video_ts.ifo or vts_##_0.ifo
+
+ return StringUtils::EqualsNoCase(fileName, "video_ts.ifo") ||
+ (fileName.length() == 12 &&
+ StringUtils::StartsWithNoCase(fileName, "vts_") &&
+ StringUtils::EndsWithNoCase(fileName, "_0.ifo"));
+ }
+
+ if (URIUtils::HasExtension(url, ".dat"))
+ {
+ std::string fileName = URIUtils::GetFileName(url);
+ std::string folder = URIUtils::GetDirectory(fileName);
+ URIUtils::RemoveSlashAtEnd(folder);
+ folder = URIUtils::GetFileName(folder);
+ if (StringUtils::EqualsNoCase(folder, "vcd") ||
+ StringUtils::EqualsNoCase(folder, "mpegav") ||
+ StringUtils::EqualsNoCase(folder, "cdda"))
+ {
+ // Allow filenames of the form AVSEQ##(#).DAT, ITEM###(#).DAT
+ // and MUSIC##(#).DAT
+ return (fileName.length() == 11 || fileName.length() == 12) &&
+ (StringUtils::StartsWithNoCase(fileName, "AVSEQ") ||
+ StringUtils::StartsWithNoCase(fileName, "MUSIC") ||
+ StringUtils::StartsWithNoCase(fileName, "ITEM"));
+ }
+ }
+ return true;
+}
+
+/*!
+ \brief Set a mask of extensions for the files in the directory.
+ \param strMask Mask of file extensions that are allowed.
+
+ The mask has to look like the following: \n
+ \verbatim
+ .m4a|.flac|.aac|
+ \endverbatim
+ So only *.m4a, *.flac, *.aac files will be retrieved with GetDirectory().
+ */
+void IDirectory::SetMask(const std::string& strMask)
+{
+ m_strFileMask = strMask;
+ // ensure it's completed with a | so that filtering is easy.
+ StringUtils::ToLower(m_strFileMask);
+ if (m_strFileMask.size() && m_strFileMask[m_strFileMask.size() - 1] != '|')
+ m_strFileMask += '|';
+}
+
+/*!
+ \brief Set the flags for this directory handler.
+ \param flags - \sa XFILE::DIR_FLAG for a description.
+ */
+void IDirectory::SetFlags(int flags)
+{
+ m_flags = flags;
+}
+
+bool IDirectory::ProcessRequirements()
+{
+ std::string type = m_requirements["type"].asString();
+ if (type == "keyboard")
+ {
+ std::string input;
+ if (CGUIKeyboardFactory::ShowAndGetInput(input, m_requirements["heading"], false, m_requirements["hidden"].asBoolean()))
+ {
+ m_requirements["input"] = input;
+ return true;
+ }
+ }
+ else if (type == "authenticate")
+ {
+ CURL url(m_requirements["url"].asString());
+ if (CPasswordManager::GetInstance().PromptToAuthenticateURL(url))
+ {
+ m_requirements.clear();
+ return true;
+ }
+ }
+ else if (type == "error")
+ {
+ HELPERS::ShowOKDialogLines(CVariant{m_requirements["heading"]}, CVariant{m_requirements["line1"]}, CVariant{m_requirements["line2"]}, CVariant{m_requirements["line3"]});
+ }
+ m_requirements.clear();
+ return false;
+}
+
+bool IDirectory::GetKeyboardInput(const CVariant &heading, std::string &input, bool hiddenInput)
+{
+ if (!m_requirements["input"].asString().empty())
+ {
+ input = m_requirements["input"].asString();
+ return true;
+ }
+ m_requirements.clear();
+ m_requirements["type"] = "keyboard";
+ m_requirements["heading"] = heading;
+ m_requirements["hidden"] = hiddenInput;
+ return false;
+}
+
+void IDirectory::SetErrorDialog(const CVariant &heading, const CVariant &line1, const CVariant &line2, const CVariant &line3)
+{
+ m_requirements.clear();
+ m_requirements["type"] = "error";
+ m_requirements["heading"] = heading;
+ m_requirements["line1"] = line1;
+ m_requirements["line2"] = line2;
+ m_requirements["line3"] = line3;
+}
+
+void IDirectory::RequireAuthentication(const CURL &url)
+{
+ m_requirements.clear();
+ m_requirements["type"] = "authenticate";
+ m_requirements["url"] = url.Get();
+}
diff --git a/xbmc/filesystem/IDirectory.h b/xbmc/filesystem/IDirectory.h
new file mode 100644
index 0000000..5667dc3
--- /dev/null
+++ b/xbmc/filesystem/IDirectory.h
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "utils/Variant.h"
+
+#include <string>
+
+class CFileItemList;
+class CProfileManager;
+class CURL;
+
+namespace XFILE
+{
+ enum DIR_CACHE_TYPE
+ {
+ DIR_CACHE_NEVER = 0, ///< Never cache this directory to memory
+ DIR_CACHE_ONCE, ///< Cache this directory to memory for each fetch (so that FileExists() checks are fast)
+ DIR_CACHE_ALWAYS ///< Always cache this directory to memory, so that each additional fetch of this folder will utilize the cache (until it's cleared)
+ };
+
+ /*! \brief Available directory flags
+ The defaults are to allow file directories, no prompting, retrieve file information, hide hidden files, and utilise the directory cache
+ based on the implementation's wishes.
+ */
+ enum DIR_FLAG
+ {
+ DIR_FLAG_DEFAULTS = 0,
+ DIR_FLAG_NO_FILE_DIRS = (2 << 0), ///< Don't convert files (zip, rar etc.) to directories
+ DIR_FLAG_ALLOW_PROMPT = (2 << 1), ///< Allow prompting for further info (passwords etc.)
+ DIR_FLAG_NO_FILE_INFO = (2 << 2), ///< Don't read additional file info (stat for example)
+ DIR_FLAG_GET_HIDDEN = (2 << 3), ///< Get hidden files
+ DIR_FLAG_READ_CACHE = (2 << 4), ///< Force reading from the directory cache (if available)
+ DIR_FLAG_BYPASS_CACHE = (2 << 5) ///< Completely bypass the directory cache (no reading, no writing)
+ };
+/*!
+ \ingroup filesystem
+ \brief Interface to the directory on a file system.
+
+ This Interface is retrieved from CDirectoryFactory and can be used to
+ access the directories on a filesystem.
+ \sa CDirectoryFactory
+ */
+class IDirectory
+{
+public:
+ static void RegisterProfileManager(const CProfileManager &profileManager);
+ static void UnregisterProfileManager();
+
+ IDirectory();
+ virtual ~IDirectory(void);
+ /*!
+ \brief Get the \e items of the directory \e strPath.
+ \param url Directory to read.
+ \param items Retrieves the directory entries.
+ \return Returns \e true, if successful.
+ \sa CDirectoryFactory
+ */
+ virtual bool GetDirectory(const CURL& url, CFileItemList &items) = 0;
+ /*!
+ \brief Retrieve the progress of the current directory fetch (if possible).
+ \return the progress as a float in the range 0..100.
+ \sa GetDirectory, CancelDirectory
+ */
+ virtual float GetProgress() const { return 0.0f; }
+ /*!
+ \brief Cancel the current directory fetch (if possible).
+ \sa GetDirectory
+ */
+ virtual void CancelDirectory() {}
+ /*!
+ \brief Create the directory
+ \param url Directory to create.
+ \return Returns \e true, if directory is created or if it already exists
+ \sa CDirectoryFactory
+ */
+ virtual bool Create(const CURL& url) { return false; }
+ /*!
+ \brief Check for directory existence
+ \param url Directory to check.
+ \return Returns \e true, if directory exists
+ \sa CDirectoryFactory
+ */
+ virtual bool Exists(const CURL& url) { return false; }
+ /*!
+ \brief Removes the directory
+ \param url Directory to remove.
+ \return Returns \e false if not successful
+ */
+ virtual bool Remove(const CURL& url) { return false; }
+
+ /*!
+ \brief Recursively removes the directory
+ \param url Directory to remove.
+ \return Returns \e false if not successful
+ */
+ virtual bool RemoveRecursive(const CURL& url) { return false; }
+
+ /*!
+ \brief Whether this file should be listed
+ \param url File to test.
+ \return Returns \e true if the file should be listed
+ */
+ virtual bool IsAllowed(const CURL& url) const;
+
+ /*! \brief Whether to allow all files/folders to be listed.
+ \return Returns \e true if all files/folder should be listed.
+ */
+ virtual bool AllowAll() const { return false; }
+
+ /*!
+ \brief How this directory should be cached
+ \param url Directory at hand.
+ \return Returns the cache type.
+ */
+ virtual DIR_CACHE_TYPE GetCacheType(const CURL& url) const { return DIR_CACHE_ONCE; }
+
+ void SetMask(const std::string& strMask);
+ void SetFlags(int flags);
+
+ /*! \brief Process additional requirements before the directory fetch is performed.
+ Some directory fetches may require authentication, keyboard input etc. The IDirectory subclass
+ should call GetKeyboardInput, SetErrorDialog or RequireAuthentication and then return false
+ from the GetDirectory method. CDirectory will then prompt for input from the user, before
+ re-calling the GetDirectory method.
+ \sa GetKeyboardInput, SetErrorDialog, RequireAuthentication
+ */
+ bool ProcessRequirements();
+
+protected:
+ /*! \brief Prompt the user for some keyboard input
+ Call this method from the GetDirectory method to retrieve additional input from the user.
+ If this function returns false then no input has been received, and the GetDirectory call
+ should return false.
+ \param heading an integer or string heading for the keyboard dialog
+ \param input [out] the returned input (if available).
+ \return true if keyboard input has been received. False if it hasn't.
+ \sa ProcessRequirements
+ */
+ bool GetKeyboardInput(const CVariant &heading, std::string &input, bool hiddenInput = false);
+
+ /*! \brief Show an error dialog on failure of GetDirectory call
+ Call this method from the GetDirectory method to set an error message to be shown to the user
+ \param heading an integer or string heading for the error dialog.
+ \param line1 the first line to be displayed (integer or string).
+ \param line2 the first line to be displayed (integer or string).
+ \param line3 the first line to be displayed (integer or string).
+ \sa ProcessRequirements
+ */
+ void SetErrorDialog(const CVariant &heading, const CVariant &line1, const CVariant &line2 = 0, const CVariant &line3 = 0);
+
+ /*! \brief Prompt the user for authentication of a URL.
+ Call this method from the GetDirectory method when authentication is required from the user, before returning
+ false from the GetDirectory call. The user will be prompted for authentication, and GetDirectory will be
+ re-called.
+ \param url the URL to authenticate.
+ \sa ProcessRequirements
+ */
+ void RequireAuthentication(const CURL& url);
+
+ static const CProfileManager *m_profileManager;
+
+ std::string m_strFileMask; ///< Holds the file mask specified by SetMask()
+
+ int m_flags; ///< Directory flags - see DIR_FLAG
+
+ CVariant m_requirements;
+};
+}
diff --git a/xbmc/filesystem/IFile.cpp b/xbmc/filesystem/IFile.cpp
new file mode 100644
index 0000000..eddffea
--- /dev/null
+++ b/xbmc/filesystem/IFile.cpp
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2002 Frodo
+ * Portions Copyright (c) by the authors of ffmpeg and xvid
+ * Copyright (C) 2002-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "IFile.h"
+
+#include "URL.h"
+
+#include <cstring>
+#include <errno.h>
+
+using namespace XFILE;
+
+//////////////////////////////////////////////////////////////////////
+// Construction/Destruction
+//////////////////////////////////////////////////////////////////////
+
+IFile::IFile() = default;
+
+IFile::~IFile() = default;
+
+int IFile::Stat(struct __stat64* buffer)
+{
+ if (buffer)
+ *buffer = {};
+
+ errno = ENOENT;
+ return -1;
+}
+bool IFile::ReadString(char *szLine, int iLineLength)
+{
+ if(Seek(0, SEEK_CUR) < 0) return false;
+
+ int64_t iFilePos = GetPosition();
+ int iBytesRead = Read( (unsigned char*)szLine, iLineLength - 1);
+ if (iBytesRead <= 0)
+ return false;
+
+ szLine[iBytesRead] = 0;
+
+ for (int i = 0; i < iBytesRead; i++)
+ {
+ if ('\n' == szLine[i])
+ {
+ if ('\r' == szLine[i + 1])
+ {
+ szLine[i + 1] = 0;
+ Seek(iFilePos + i + 2, SEEK_SET);
+ }
+ else
+ {
+ // end of line
+ szLine[i + 1] = 0;
+ Seek(iFilePos + i + 1, SEEK_SET);
+ }
+ break;
+ }
+ else if ('\r' == szLine[i])
+ {
+ if ('\n' == szLine[i + 1])
+ {
+ szLine[i + 1] = 0;
+ Seek(iFilePos + i + 2, SEEK_SET);
+ }
+ else
+ {
+ // end of line
+ szLine[i + 1] = 0;
+ Seek(iFilePos + i + 1, SEEK_SET);
+ }
+ break;
+ }
+ }
+ return true;
+}
+
+CRedirectException::CRedirectException() :
+ m_pNewFileImp(NULL), m_pNewUrl(NULL)
+{
+}
+
+CRedirectException::CRedirectException(IFile *pNewFileImp, CURL *pNewUrl) :
+ m_pNewFileImp(pNewFileImp), m_pNewUrl(pNewUrl)
+{
+}
diff --git a/xbmc/filesystem/IFile.h b/xbmc/filesystem/IFile.h
new file mode 100644
index 0000000..d76d6ed
--- /dev/null
+++ b/xbmc/filesystem/IFile.h
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2002 Frodo
+ * Portions Copyright (c) by the authors of ffmpeg and xvid
+ * Copyright (C) 2002-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+// IFile.h: interface for the IFile class.
+//
+//////////////////////////////////////////////////////////////////////
+
+#include "PlatformDefs.h" // for __stat64, ssize_t
+
+#include <stdio.h>
+#include <stdint.h>
+#include <sys/stat.h>
+#include <string>
+#include <vector>
+
+#if !defined(SIZE_MAX) || !defined(SSIZE_MAX)
+#include <limits.h>
+#ifndef SIZE_MAX
+#define SIZE_MAX UINTPTR_MAX
+#endif // ! SIZE_MAX
+#ifndef SSIZE_MAX
+#define SSIZE_MAX INTPTR_MAX
+#endif // ! SSIZE_MAX
+#endif // ! SIZE_MAX || ! SSIZE_MAX
+
+#include "IFileTypes.h"
+
+class CURL;
+
+namespace XFILE
+{
+
+class IFile
+{
+public:
+ IFile();
+ virtual ~IFile();
+
+ virtual bool Open(const CURL& url) = 0;
+ virtual bool OpenForWrite(const CURL& url, bool bOverWrite = false) { return false; }
+ virtual bool ReOpen(const CURL& url) { return false; }
+ virtual bool Exists(const CURL& url) = 0;
+ /**
+ * Fills struct __stat64 with information about file specified by url.
+ * For st_mode function will set correctly _S_IFDIR (directory) flag and may set
+ * _S_IREAD (read permission), _S_IWRITE (write permission) flags if such
+ * information is available. Function may set st_size (file size), st_atime,
+ * st_mtime, st_ctime (access, modification, creation times).
+ * Any other flags and members of __stat64 that didn't updated with actual file
+ * information will be set to zero (st_nlink can be set ether to 1 or zero).
+ * @param url specifies requested file
+ * @param buffer pointer to __stat64 buffer to receive information about file
+ * @return zero of success, -1 otherwise.
+ */
+ virtual int Stat(const CURL& url, struct __stat64* buffer) = 0;
+ /**
+ * Fills struct __stat64 with information about currently open file
+ * For st_mode function will set correctly _S_IFDIR (directory) flag and may set
+ * _S_IREAD (read permission), _S_IWRITE (write permission) flags if such
+ * information is available. Function may set st_size (file size), st_atime,
+ * st_mtime, st_ctime (access, modification, creation times).
+ * Any other flags and members of __stat64 that didn't updated with actual file
+ * information will be set to zero (st_nlink can be set ether to 1 or zero).
+ * @param buffer pointer to __stat64 buffer to receive information about file
+ * @return zero of success, -1 otherwise.
+ */
+ virtual int Stat(struct __stat64* buffer);
+ /**
+ * Attempt to read bufSize bytes from currently opened file into buffer bufPtr.
+ * @param bufPtr pointer to buffer
+ * @param bufSize size of the buffer
+ * @return number of successfully read bytes if any bytes were read and stored in
+ * buffer, zero if no bytes are available to read (end of file was reached)
+ * or undetectable error occur, -1 in case of any explicit error
+ */
+ virtual ssize_t Read(void* bufPtr, size_t bufSize) = 0;
+ /**
+ * Attempt to write bufSize bytes from buffer bufPtr into currently opened file.
+ * @param bufPtr pointer to buffer
+ * @param bufSize size of the buffer
+ * @return number of successfully written bytes if any bytes were written,
+ * zero if no bytes were written and no detectable error occur,
+ * -1 in case of any explicit error
+ */
+ virtual ssize_t Write(const void* bufPtr, size_t bufSize) { return -1;}
+ virtual bool ReadString(char *szLine, int iLineLength);
+ virtual int64_t Seek(int64_t iFilePosition, int iWhence = SEEK_SET) = 0;
+ virtual void Close() = 0;
+ virtual int64_t GetPosition() = 0;
+ virtual int64_t GetLength() = 0;
+ virtual void Flush() { }
+ virtual int Truncate(int64_t size) { return -1; }
+
+ /* Returns the minimum size that can be read from input stream. *
+ * For example cdrom access where access could be sector based. *
+ * This will cause file system to buffer read requests, to *
+ * to meet the requirement of CFile. *
+ * It can also be used to indicate a file system is non buffered *
+ * but accepts any read size, have it return the value 1 */
+ virtual int GetChunkSize() {return 0;}
+ virtual double GetDownloadSpeed() { return 0.0; }
+
+ virtual bool Delete(const CURL& url) { return false; }
+ virtual bool Rename(const CURL& url, const CURL& urlnew) { return false; }
+ virtual bool SetHidden(const CURL& url, bool hidden) { return false; }
+
+ virtual int IoControl(EIoControl request, void* param) { return -1; }
+
+ virtual const std::string GetProperty(XFILE::FileProperty type, const std::string &name = "") const
+ {
+ return type == XFILE::FILE_PROPERTY_CONTENT_TYPE ? "application/octet-stream" : "";
+ };
+
+ virtual const std::vector<std::string> GetPropertyValues(XFILE::FileProperty type, const std::string &name = "") const
+ {
+ std::vector<std::string> values;
+ std::string value = GetProperty(type, name);
+ if (!value.empty())
+ {
+ values.emplace_back(value);
+ }
+ return values;
+ }
+};
+
+class CRedirectException
+{
+public:
+ IFile *m_pNewFileImp;
+ CURL *m_pNewUrl;
+
+ CRedirectException();
+
+ CRedirectException(IFile *pNewFileImp, CURL *pNewUrl=NULL);
+};
+
+}
diff --git a/xbmc/filesystem/IFileDirectory.h b/xbmc/filesystem/IFileDirectory.h
new file mode 100644
index 0000000..357ed2e
--- /dev/null
+++ b/xbmc/filesystem/IFileDirectory.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IDirectory.h"
+
+namespace XFILE
+{
+class IFileDirectory : public IDirectory
+{
+public:
+ ~IFileDirectory(void) override = default;
+ virtual bool ContainsFiles(const CURL& url)=0;
+};
+
+}
diff --git a/xbmc/filesystem/IFileTypes.h b/xbmc/filesystem/IFileTypes.h
new file mode 100644
index 0000000..524d871
--- /dev/null
+++ b/xbmc/filesystem/IFileTypes.h
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+namespace XFILE
+{
+
+/* indicate that caller can handle truncated reads, where function returns before entire buffer has been filled */
+ static const unsigned int READ_TRUNCATED = 0x01;
+
+/* indicate that that caller support read in the minimum defined chunk size, this disables internal cache then */
+ static const unsigned int READ_CHUNKED = 0x02;
+
+/* use cache to access this file */
+ static const unsigned int READ_CACHED = 0x04;
+
+/* open without caching. regardless to file type. */
+ static const unsigned int READ_NO_CACHE = 0x08;
+
+/* calculate bitrate for file while reading */
+ static const unsigned int READ_BITRATE = 0x10;
+
+/* indicate to the caller we will seek between multiple streams in the file frequently */
+ static const unsigned int READ_MULTI_STREAM = 0x20;
+
+/* indicate to the caller file is audio and/or video (and e.g. may grow) */
+ static const unsigned int READ_AUDIO_VIDEO = 0x40;
+
+/* indicate that caller will do write operations before reading */
+ static const unsigned int READ_AFTER_WRITE = 0x80;
+
+/* indicate that caller want to reopen a file if its already open */
+ static const unsigned int READ_REOPEN = 0x100;
+
+struct SNativeIoControl
+{
+ unsigned long int request;
+ void* param;
+};
+
+struct SCacheStatus
+{
+ uint64_t forward; /**< number of bytes cached forward of current position */
+ uint32_t maxrate; /**< maximum allowed read(fill) rate (bytes/second) */
+ uint32_t currate; /**< average read rate (bytes/second) since last position change */
+ uint32_t lowrate; /**< low speed read rate (bytes/second) (if any, else 0) */
+};
+
+typedef enum {
+ IOCTRL_NATIVE = 1, /**< SNativeIoControl structure, containing what should be passed to native ioctrl */
+ IOCTRL_SEEK_POSSIBLE = 2, /**< return 0 if known not to work, 1 if it should work */
+ IOCTRL_CACHE_STATUS = 3, /**< SCacheStatus structure */
+ IOCTRL_CACHE_SETRATE = 4, /**< unsigned int with speed limit for caching in bytes per second */
+ IOCTRL_SET_CACHE = 8, /**< CFileCache */
+ IOCTRL_SET_RETRY = 16, /**< Enable/disable retry within the protocol handler (if supported) */
+} EIoControl;
+
+enum CURLOPTIONTYPE
+{
+ CURL_OPTION_OPTION, /**< Set a general option */
+ CURL_OPTION_PROTOCOL, /**< Set a protocol option (see below) */
+ CURL_OPTION_CREDENTIALS,/**< Set User and password */
+ CURL_OPTION_HEADER /**< Add a Header */
+};
+
+/**
+ * The following names for CURL_OPTION_PROTOCOL are possible:
+ *
+ * accept-charset: Set the "accept-charset" header
+ * acceptencoding or encoding: Set the "accept-encoding" header
+ * active-remote: Set the "active-remote" header
+ * auth: Set the authentication method. Possible values: any, anysafe, digest, ntlm
+ * connection-timeout: Set the connection timeout in seconds
+ * cookie: Set the "cookie" header
+ * customrequest: Set a custom HTTP request like DELETE
+ * noshout: Set to true if kodi detects a stream as shoutcast by mistake.
+ * postdata: Set the post body (value needs to be base64 encoded). (Implicitly sets the request to POST)
+ * referer: Set the "referer" header
+ * user-agent: Set the "user-agent" header
+ * seekable: Set the stream seekable. 1: enable, 0: disable
+ * sslcipherlist: Set list of accepted SSL ciphers.
+ */
+
+enum FileProperty
+{
+ FILE_PROPERTY_RESPONSE_PROTOCOL, /**< Get response protocol line */
+ FILE_PROPERTY_RESPONSE_HEADER, /**< Get response Header value */
+ FILE_PROPERTY_CONTENT_TYPE, /**< Get file content-type */
+ FILE_PROPERTY_CONTENT_CHARSET, /**< Get file content charset */
+ FILE_PROPERTY_MIME_TYPE, /**< Get file mime type */
+ FILE_PROPERTY_EFFECTIVE_URL /**< Get effective URL for redirected streams */
+};
+
+class IFileCallback
+{
+public:
+ virtual bool OnFileCallback(void* pContext, int ipercent, float avgSpeed) = 0;
+ virtual ~IFileCallback() = default;
+};
+}
diff --git a/xbmc/filesystem/ISO9660Directory.cpp b/xbmc/filesystem/ISO9660Directory.cpp
new file mode 100644
index 0000000..894db53
--- /dev/null
+++ b/xbmc/filesystem/ISO9660Directory.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "ISO9660Directory.h"
+
+#include "FileItem.h"
+#include "URL.h"
+#include "utils/URIUtils.h"
+
+#include <cdio++/iso9660.hpp>
+
+using namespace XFILE;
+
+bool CISO9660Directory::GetDirectory(const CURL& url, CFileItemList& items)
+{
+ CURL url2(url);
+ if (!url2.IsProtocol("iso9660"))
+ {
+ url2.Reset();
+ url2.SetProtocol("iso9660");
+ url2.SetHostName(url.Get());
+ }
+
+ std::string strRoot(url2.Get());
+ std::string strSub(url2.GetFileName());
+
+ URIUtils::AddSlashAtEnd(strRoot);
+ URIUtils::AddSlashAtEnd(strSub);
+
+ std::unique_ptr<ISO9660::IFS> iso(new ISO9660::IFS);
+
+ if (!iso->open(url2.GetHostName().c_str()))
+ return false;
+
+ std::vector<ISO9660::Stat*> isoFiles;
+
+ if (iso->readdir(strSub.c_str(), isoFiles))
+ {
+ for (const auto file : isoFiles)
+ {
+ std::string filename(file->p_stat->filename);
+
+ if (file->p_stat->type == 2)
+ {
+ if (filename != "." && filename != "..")
+ {
+ CFileItemPtr pItem(new CFileItem(filename));
+ std::string strDir(strRoot + filename);
+ URIUtils::AddSlashAtEnd(strDir);
+ pItem->SetPath(strDir);
+ pItem->m_bIsFolder = true;
+ items.Add(pItem);
+ }
+ }
+ else
+ {
+ CFileItemPtr pItem(new CFileItem(filename));
+ pItem->SetPath(strRoot + filename);
+ pItem->m_bIsFolder = false;
+ pItem->m_dwSize = file->p_stat->size;
+ items.Add(pItem);
+ }
+ }
+
+ isoFiles.clear();
+ return true;
+ }
+
+ return false;
+}
+
+bool CISO9660Directory::Exists(const CURL& url)
+{
+ CFileItemList items;
+ return GetDirectory(url, items);
+}
diff --git a/xbmc/filesystem/ISO9660Directory.h b/xbmc/filesystem/ISO9660Directory.h
new file mode 100644
index 0000000..6c624ff
--- /dev/null
+++ b/xbmc/filesystem/ISO9660Directory.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IFileDirectory.h"
+
+namespace XFILE
+{
+
+class CISO9660Directory : public IFileDirectory
+{
+public:
+ CISO9660Directory() = default;
+ ~CISO9660Directory() = default;
+ bool GetDirectory(const CURL& url, CFileItemList& items) override;
+ bool Exists(const CURL& url) override;
+ bool ContainsFiles(const CURL& url) override { return true; }
+};
+}
diff --git a/xbmc/filesystem/ISO9660File.cpp b/xbmc/filesystem/ISO9660File.cpp
new file mode 100644
index 0000000..83de0cc
--- /dev/null
+++ b/xbmc/filesystem/ISO9660File.cpp
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "ISO9660File.h"
+
+#include "URL.h"
+
+#include <cmath>
+
+using namespace XFILE;
+
+CISO9660File::CISO9660File() : m_iso(new ISO9660::IFS())
+{
+}
+
+bool CISO9660File::Open(const CURL& url)
+{
+ if (m_iso && m_stat)
+ return true;
+
+ if (!m_iso->open(url.GetHostName().c_str()))
+ return false;
+
+ m_stat.reset(m_iso->stat(url.GetFileName().c_str()));
+
+ if (!m_stat)
+ return false;
+
+ if (!m_stat->p_stat)
+ return false;
+
+ m_start = m_stat->p_stat->lsn;
+ m_current = 0;
+
+ return true;
+}
+
+int CISO9660File::Stat(const CURL& url, struct __stat64* buffer)
+{
+ if (!m_iso)
+ return -1;
+
+ if (!m_stat)
+ return -1;
+
+ if (!m_stat->p_stat)
+ return -1;
+
+ if (!buffer)
+ return -1;
+
+ *buffer = {};
+ buffer->st_size = m_stat->p_stat->size;
+
+ switch (m_stat->p_stat->type)
+ {
+ case 2:
+ buffer->st_mode = S_IFDIR;
+ break;
+ case 1:
+ default:
+ buffer->st_mode = S_IFREG;
+ break;
+ }
+
+ return 0;
+}
+
+ssize_t CISO9660File::Read(void* buffer, size_t size)
+{
+ const int maxSize = std::min(size, static_cast<size_t>(GetLength()));
+ const int blocks = std::ceil(maxSize / ISO_BLOCKSIZE);
+
+ if (m_current > std::ceil(GetLength() / ISO_BLOCKSIZE))
+ return -1;
+
+ auto read = m_iso->seek_read(buffer, m_start + m_current, blocks);
+
+ m_current += blocks;
+
+ return read;
+}
+
+int64_t CISO9660File::Seek(int64_t filePosition, int whence)
+{
+ int block = std::floor(filePosition / ISO_BLOCKSIZE);
+
+ switch (whence)
+ {
+ case SEEK_SET:
+ m_current = block;
+ break;
+ case SEEK_CUR:
+ m_current += block;
+ break;
+ case SEEK_END:
+ m_current = std::ceil(GetLength() / ISO_BLOCKSIZE) + block;
+ break;
+ }
+
+ return m_current * ISO_BLOCKSIZE;
+}
+
+int64_t CISO9660File::GetLength()
+{
+ return m_stat->p_stat->size;
+}
+
+int64_t CISO9660File::GetPosition()
+{
+ return m_current * ISO_BLOCKSIZE;
+}
+
+bool CISO9660File::Exists(const CURL& url)
+{
+ return Open(url);
+}
+
+int CISO9660File::GetChunkSize()
+{
+ return ISO_BLOCKSIZE;
+}
diff --git a/xbmc/filesystem/ISO9660File.h b/xbmc/filesystem/ISO9660File.h
new file mode 100644
index 0000000..5754605
--- /dev/null
+++ b/xbmc/filesystem/ISO9660File.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IFile.h"
+
+#include <memory>
+
+#include <cdio++/iso9660.hpp>
+
+namespace XFILE
+{
+
+class CISO9660File : public IFile
+{
+public:
+ CISO9660File();
+ ~CISO9660File() override = default;
+
+ bool Open(const CURL& url) override;
+ void Close() override {}
+
+ int Stat(const CURL& url, struct __stat64* buffer) override;
+
+ ssize_t Read(void* buffer, size_t size) override;
+ int64_t Seek(int64_t filePosition, int whence) override;
+
+ int64_t GetLength() override;
+ int64_t GetPosition() override;
+
+ bool Exists(const CURL& url) override;
+
+ int GetChunkSize() override;
+
+private:
+ std::unique_ptr<ISO9660::IFS> m_iso;
+ std::unique_ptr<ISO9660::Stat> m_stat;
+
+ int32_t m_start;
+ int32_t m_current;
+};
+
+} // namespace XFILE
diff --git a/xbmc/filesystem/ImageFile.cpp b/xbmc/filesystem/ImageFile.cpp
new file mode 100644
index 0000000..bb1e23a
--- /dev/null
+++ b/xbmc/filesystem/ImageFile.cpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "ImageFile.h"
+
+#include "ServiceBroker.h"
+#include "TextureCache.h"
+#include "URL.h"
+
+using namespace XFILE;
+
+CImageFile::CImageFile(void) = default;
+
+CImageFile::~CImageFile(void)
+{
+ Close();
+}
+
+bool CImageFile::Open(const CURL& url)
+{
+ std::string file = url.Get();
+ bool needsRecaching = false;
+ std::string cachedFile =
+ CServiceBroker::GetTextureCache()->CheckCachedImage(file, needsRecaching);
+ if (cachedFile.empty())
+ { // not in the cache, so cache it
+ cachedFile = CServiceBroker::GetTextureCache()->CacheImage(file);
+ }
+ if (!cachedFile.empty())
+ { // in the cache, return what we have
+ if (m_file.Open(cachedFile))
+ return true;
+ }
+ return false;
+}
+
+bool CImageFile::Exists(const CURL& url)
+{
+ bool needsRecaching = false;
+ std::string cachedFile =
+ CServiceBroker::GetTextureCache()->CheckCachedImage(url.Get(), needsRecaching);
+ if (!cachedFile.empty())
+ {
+ if (CFile::Exists(cachedFile, false))
+ return true;
+ else
+ // Remove from cache so it gets cached again on next Open()
+ CServiceBroker::GetTextureCache()->ClearCachedImage(url.Get());
+ }
+
+ // need to check if the original can be cached on demand and that the file exists
+ if (!CTextureCache::CanCacheImageURL(url))
+ return false;
+
+ return CFile::Exists(url.GetHostName());
+}
+
+int CImageFile::Stat(const CURL& url, struct __stat64* buffer)
+{
+ bool needsRecaching = false;
+ std::string cachedFile =
+ CServiceBroker::GetTextureCache()->CheckCachedImage(url.Get(), needsRecaching);
+ if (!cachedFile.empty())
+ return CFile::Stat(cachedFile, buffer);
+
+ /*
+ Doesn't exist in the cache yet. We have 3 options here:
+ 1. Cache the file and do the Stat() on the cached file.
+ 2. Do the Stat() on the original file.
+ 3. Return -1;
+ Only 1 will return valid results, at the cost of being time consuming. ATM we do 3 under
+ the theory that the only user of this is the webinterface currently, where Stat() is not
+ required.
+ */
+ return -1;
+}
+
+ssize_t CImageFile::Read(void* lpBuf, size_t uiBufSize)
+{
+ return m_file.Read(lpBuf, uiBufSize);
+}
+
+int64_t CImageFile::Seek(int64_t iFilePosition, int iWhence /*=SEEK_SET*/)
+{
+ return m_file.Seek(iFilePosition, iWhence);
+}
+
+void CImageFile::Close()
+{
+ m_file.Close();
+}
+
+int64_t CImageFile::GetPosition()
+{
+ return m_file.GetPosition();
+}
+
+int64_t CImageFile::GetLength()
+{
+ return m_file.GetLength();
+}
diff --git a/xbmc/filesystem/ImageFile.h b/xbmc/filesystem/ImageFile.h
new file mode 100644
index 0000000..ebf6d7b
--- /dev/null
+++ b/xbmc/filesystem/ImageFile.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "File.h"
+#include "IFile.h"
+
+namespace XFILE
+{
+ class CImageFile: public IFile
+ {
+ public:
+ CImageFile();
+ ~CImageFile() override;
+ bool Open(const CURL& url) override;
+ bool Exists(const CURL& url) override;
+ int Stat(const CURL& url, struct __stat64* buffer) override;
+
+ ssize_t Read(void* lpBuf, size_t uiBufSize) override;
+ int64_t Seek(int64_t iFilePosition, int iWhence = SEEK_SET) override;
+ void Close() override;
+ int64_t GetPosition() override;
+ int64_t GetLength() override;
+
+ protected:
+ CFile m_file;
+ };
+}
diff --git a/xbmc/filesystem/LibraryDirectory.cpp b/xbmc/filesystem/LibraryDirectory.cpp
new file mode 100644
index 0000000..8f81495
--- /dev/null
+++ b/xbmc/filesystem/LibraryDirectory.cpp
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2011-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "LibraryDirectory.h"
+
+#include "Directory.h"
+#include "FileItem.h"
+#include "GUIInfoManager.h"
+#include "SmartPlaylistDirectory.h"
+#include "URL.h"
+#include "guilib/GUIControlFactory.h" // for label parsing
+#include "guilib/TextureManager.h"
+#include "playlists/SmartPlayList.h"
+#include "profiles/ProfileManager.h"
+#include "utils/FileUtils.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/XMLUtils.h"
+#include "utils/log.h"
+
+using namespace XFILE;
+
+CLibraryDirectory::CLibraryDirectory(void) = default;
+
+CLibraryDirectory::~CLibraryDirectory(void) = default;
+
+bool CLibraryDirectory::GetDirectory(const CURL& url, CFileItemList &items)
+{
+ std::string libNode = GetNode(url);
+ if (libNode.empty())
+ return false;
+
+ if (URIUtils::HasExtension(libNode, ".xml"))
+ { // a filter or folder node
+ TiXmlElement *node = LoadXML(libNode);
+ if (node)
+ {
+ std::string type = XMLUtils::GetAttribute(node, "type");
+ if (type == "filter")
+ {
+ CSmartPlaylist playlist;
+ std::string type, label;
+ XMLUtils::GetString(node, "content", type);
+ if (type.empty())
+ {
+ CLog::Log(LOGERROR, "<content> tag must not be empty for type=\"filter\" node '{}'",
+ libNode);
+ return false;
+ }
+ if (XMLUtils::GetString(node, "label", label))
+ label = CGUIControlFactory::FilterLabel(label);
+ playlist.SetType(type);
+ playlist.SetName(label);
+ if (playlist.LoadFromXML(node) &&
+ CSmartPlaylistDirectory::GetDirectory(playlist, items))
+ {
+ items.SetProperty("library.filter", "true");
+ items.SetPath(items.GetProperty("path.db").asString());
+ return true;
+ }
+ }
+ else if (type == "folder")
+ {
+ std::string label;
+ if (XMLUtils::GetString(node, "label", label))
+ label = CGUIControlFactory::FilterLabel(label);
+ items.SetLabel(label);
+ std::string path;
+ XMLUtils::GetPath(node, "path", path);
+ if (!path.empty())
+ {
+ URIUtils::AddSlashAtEnd(path);
+ return CDirectory::GetDirectory(path, items, m_strFileMask, m_flags);
+ }
+ }
+ }
+ return false;
+ }
+
+ // just a plain node - read the folder for XML nodes and other folders
+ CFileItemList nodes;
+ if (!CDirectory::GetDirectory(libNode, nodes, ".xml", DIR_FLAG_NO_FILE_DIRS))
+ return false;
+
+ // iterate over our nodes
+ std::string basePath = url.Get();
+ for (int i = 0; i < nodes.Size(); i++)
+ {
+ const TiXmlElement *node = NULL;
+ std::string xml = nodes[i]->GetPath();
+ if (nodes[i]->m_bIsFolder)
+ node = LoadXML(URIUtils::AddFileToFolder(xml, "index.xml"));
+ else
+ {
+ node = LoadXML(xml);
+ if (node && URIUtils::GetFileName(xml) == "index.xml")
+ { // set the label on our items
+ std::string label;
+ if (XMLUtils::GetString(node, "label", label))
+ label = CGUIControlFactory::FilterLabel(label);
+ items.SetLabel(label);
+ continue;
+ }
+ }
+ if (node)
+ {
+ std::string label, icon;
+ if (XMLUtils::GetString(node, "label", label))
+ label = CGUIControlFactory::FilterLabel(label);
+ XMLUtils::GetString(node, "icon", icon);
+ int order = 0;
+ node->Attribute("order", &order);
+
+ // create item
+ URIUtils::RemoveSlashAtEnd(xml);
+ std::string folder = URIUtils::GetFileName(xml);
+ CFileItemPtr item(new CFileItem(URIUtils::AddFileToFolder(basePath, folder), true));
+
+ item->SetLabel(label);
+ if (!icon.empty() && CServiceBroker::GetGUI()->GetTextureManager().HasTexture(icon))
+ item->SetArt("icon", icon);
+ item->m_iprogramCount = order;
+ items.Add(item);
+ }
+ }
+ items.Sort(SortByPlaylistOrder, SortOrderAscending);
+ return true;
+}
+
+TiXmlElement *CLibraryDirectory::LoadXML(const std::string &xmlFile)
+{
+ if (!CFileUtils::Exists(xmlFile))
+ return nullptr;
+
+ if (!m_doc.LoadFile(xmlFile))
+ return nullptr;
+
+ TiXmlElement *xml = m_doc.RootElement();
+ if (!xml || xml->ValueStr() != "node")
+ return nullptr;
+
+ // check the condition
+ std::string condition = XMLUtils::GetAttribute(xml, "visible");
+ CGUIComponent* gui = CServiceBroker::GetGUI();
+ if (condition.empty() ||
+ (gui && gui->GetInfoManager().EvaluateBool(condition, INFO::DEFAULT_CONTEXT)))
+ return xml;
+
+ return nullptr;
+}
+
+bool CLibraryDirectory::Exists(const CURL& url)
+{
+ return !GetNode(url).empty();
+}
+
+std::string CLibraryDirectory::GetNode(const CURL& url)
+{
+ std::string libDir = URIUtils::AddFileToFolder(m_profileManager->GetLibraryFolder(), url.GetHostName() + "/");
+ if (!CDirectory::Exists(libDir))
+ libDir = URIUtils::AddFileToFolder("special://xbmc/system/library/", url.GetHostName() + "/");
+
+ libDir = URIUtils::AddFileToFolder(libDir, url.GetFileName());
+
+ // is this a virtual node (aka actual folder on disk?)
+ if (CDirectory::Exists(libDir))
+ return libDir;
+
+ // maybe it's an XML node?
+ std::string xmlNode = libDir;
+ URIUtils::RemoveSlashAtEnd(xmlNode);
+
+ if (CFileUtils::Exists(xmlNode))
+ return xmlNode;
+
+ return "";
+}
diff --git a/xbmc/filesystem/LibraryDirectory.h b/xbmc/filesystem/LibraryDirectory.h
new file mode 100644
index 0000000..af76dd4
--- /dev/null
+++ b/xbmc/filesystem/LibraryDirectory.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2011-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IDirectory.h"
+#include "utils/XBMCTinyXML.h"
+
+namespace XFILE
+{
+ class CLibraryDirectory : public IDirectory
+ {
+ public:
+ CLibraryDirectory();
+ ~CLibraryDirectory() override;
+ bool GetDirectory(const CURL& url, CFileItemList &items) override;
+ bool Exists(const CURL& url) override;
+ bool AllowAll() const override { return true; }
+ private:
+ /*! \brief parse the given path and return the node corresponding to this path
+ \param path the library:// path to parse
+ \return path to the XML file or directory corresponding to this path
+ */
+ std::string GetNode(const CURL& path);
+
+ /*! \brief load the XML file and return a pointer to the <node> root element.
+ Checks visible attribute and only returns non-NULL for valid nodes that should be visible.
+ \param xmlFile the XML file to load and parse
+ \return the TiXmlElement pointer to the node, if it should be visible.
+ */
+ TiXmlElement *LoadXML(const std::string &xmlFile);
+
+ CXBMCTinyXML m_doc;
+ };
+}
diff --git a/xbmc/filesystem/MultiPathDirectory.cpp b/xbmc/filesystem/MultiPathDirectory.cpp
new file mode 100644
index 0000000..4cdcfcc
--- /dev/null
+++ b/xbmc/filesystem/MultiPathDirectory.cpp
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "MultiPathDirectory.h"
+
+#include "Directory.h"
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "Util.h"
+#include "dialogs/GUIDialogProgress.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "threads/SystemClock.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/Variant.h"
+#include "utils/log.h"
+
+using namespace XFILE;
+
+using namespace std::chrono_literals;
+
+//
+// multipath://{path1}/{path2}/{path3}/.../{path-N}
+//
+// unlike the older virtualpath:// protocol, sub-folders are combined together into a new
+// multipath:// style url.
+//
+
+CMultiPathDirectory::CMultiPathDirectory() = default;
+
+CMultiPathDirectory::~CMultiPathDirectory() = default;
+
+bool CMultiPathDirectory::GetDirectory(const CURL& url, CFileItemList &items)
+{
+ CLog::Log(LOGDEBUG, "CMultiPathDirectory::GetDirectory({})", url.GetRedacted());
+
+ std::vector<std::string> vecPaths;
+ if (!GetPaths(url, vecPaths))
+ return false;
+
+ XbmcThreads::EndTime<> progressTime(3000ms); // 3 seconds before showing progress bar
+ CGUIDialogProgress* dlgProgress = NULL;
+
+ unsigned int iFailures = 0;
+ for (unsigned int i = 0; i < vecPaths.size(); ++i)
+ {
+ // show the progress dialog if we have passed our time limit
+ if (progressTime.IsTimePast() && !dlgProgress)
+ {
+ dlgProgress = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS);
+ if (dlgProgress)
+ {
+ dlgProgress->SetHeading(CVariant{15310});
+ dlgProgress->SetLine(0, CVariant{15311});
+ dlgProgress->SetLine(1, CVariant{""});
+ dlgProgress->SetLine(2, CVariant{""});
+ dlgProgress->Open();
+ dlgProgress->ShowProgressBar(true);
+ dlgProgress->SetProgressMax((int)vecPaths.size()*2);
+ dlgProgress->Progress();
+ }
+ }
+ if (dlgProgress)
+ {
+ CURL url(vecPaths[i]);
+ dlgProgress->SetLine(1, CVariant{url.GetWithoutUserDetails()});
+ dlgProgress->SetProgressAdvance();
+ dlgProgress->Progress();
+ }
+
+ CFileItemList tempItems;
+ CLog::Log(LOGDEBUG, "Getting Directory ({})", CURL::GetRedacted(vecPaths[i]));
+ if (CDirectory::GetDirectory(vecPaths[i], tempItems, m_strFileMask, m_flags))
+ items.Append(tempItems);
+ else
+ {
+ CLog::Log(LOGERROR, "Error Getting Directory ({})", CURL::GetRedacted(vecPaths[i]));
+ iFailures++;
+ }
+
+ if (dlgProgress)
+ {
+ dlgProgress->SetProgressAdvance();
+ dlgProgress->Progress();
+ }
+ }
+
+ if (dlgProgress)
+ dlgProgress->Close();
+
+ if (iFailures == vecPaths.size())
+ return false;
+
+ // merge like-named folders into a sub multipath:// style url
+ MergeItems(items);
+
+ return true;
+}
+
+bool CMultiPathDirectory::Exists(const CURL& url)
+{
+ CLog::Log(LOGDEBUG, "Testing Existence ({})", url.GetRedacted());
+
+ std::vector<std::string> vecPaths;
+ if (!GetPaths(url, vecPaths))
+ return false;
+
+ for (unsigned int i = 0; i < vecPaths.size(); ++i)
+ {
+ CLog::Log(LOGDEBUG, "Testing Existence ({})", CURL::GetRedacted(vecPaths[i]));
+ if (CDirectory::Exists(vecPaths[i]))
+ return true;
+ }
+ return false;
+}
+
+bool CMultiPathDirectory::Remove(const CURL& url)
+{
+ std::vector<std::string> vecPaths;
+ if (!GetPaths(url, vecPaths))
+ return false;
+
+ bool success = false;
+ for (unsigned int i = 0; i < vecPaths.size(); ++i)
+ {
+ if (CDirectory::Remove(vecPaths[i]))
+ success = true;
+ }
+ return success;
+}
+
+std::string CMultiPathDirectory::GetFirstPath(const std::string &strPath)
+{
+ size_t pos = strPath.find('/', 12);
+ if (pos != std::string::npos)
+ return CURL::Decode(strPath.substr(12, pos - 12));
+ return "";
+}
+
+bool CMultiPathDirectory::GetPaths(const CURL& url, std::vector<std::string>& vecPaths)
+{
+ const std::string pathToUrl(url.Get());
+ return GetPaths(pathToUrl, vecPaths);
+}
+
+bool CMultiPathDirectory::GetPaths(const std::string& path, std::vector<std::string>& paths)
+{
+ paths.clear();
+
+ // remove multipath:// from path and any trailing / (so that the last path doesn't get any more than it originally had)
+ std::string path1 = path.substr(12);
+ path1.erase(path1.find_last_not_of('/')+1);
+
+ // split on "/"
+ std::vector<std::string> temp = StringUtils::Split(path1, '/');
+ if (temp.empty())
+ return false;
+
+ // URL decode each item
+ paths.resize(temp.size());
+ std::transform(temp.begin(), temp.end(), paths.begin(), CURL::Decode);
+ return true;
+}
+
+bool CMultiPathDirectory::HasPath(const std::string& strPath, const std::string& strPathToFind)
+{
+ // remove multipath:// from path and any trailing / (so that the last path doesn't get any more than it originally had)
+ std::string strPath1 = strPath.substr(12);
+ URIUtils::RemoveSlashAtEnd(strPath1);
+
+ // split on "/"
+ std::vector<std::string> vecTemp = StringUtils::Split(strPath1, '/');
+ if (vecTemp.empty())
+ return false;
+
+ // check each item
+ for (unsigned int i = 0; i < vecTemp.size(); i++)
+ {
+ if (CURL::Decode(vecTemp[i]) == strPathToFind)
+ return true;
+ }
+ return false;
+}
+
+std::string CMultiPathDirectory::ConstructMultiPath(const CFileItemList& items, const std::vector<int> &stack)
+{
+ // we replace all instances of comma's with double comma's, then separate
+ // the paths using " , "
+ //CLog::Log(LOGDEBUG, "Building multipath");
+ std::string newPath = "multipath://";
+ //CLog::Log(LOGDEBUG, "-- adding path: {}", strPath);
+ for (unsigned int i = 0; i < stack.size(); ++i)
+ AddToMultiPath(newPath, items[stack[i]]->GetPath());
+
+ //CLog::Log(LOGDEBUG, "Final path: {}", newPath);
+ return newPath;
+}
+
+void CMultiPathDirectory::AddToMultiPath(std::string& strMultiPath, const std::string& strPath)
+{
+ URIUtils::AddSlashAtEnd(strMultiPath);
+ //CLog::Log(LOGDEBUG, "-- adding path: {}", strPath);
+ strMultiPath += CURL::Encode(strPath);
+ strMultiPath += "/";
+}
+
+std::string CMultiPathDirectory::ConstructMultiPath(const std::vector<std::string> &vecPaths)
+{
+ // we replace all instances of comma's with double comma's, then separate
+ // the paths using " , "
+ //CLog::Log(LOGDEBUG, "Building multipath");
+ std::string newPath = "multipath://";
+ //CLog::Log(LOGDEBUG, "-- adding path: {}", strPath);
+ for (std::vector<std::string>::const_iterator path = vecPaths.begin(); path != vecPaths.end(); ++path)
+ AddToMultiPath(newPath, *path);
+ //CLog::Log(LOGDEBUG, "Final path: {}", newPath);
+ return newPath;
+}
+
+std::string CMultiPathDirectory::ConstructMultiPath(const std::set<std::string> &setPaths)
+{
+ std::string newPath = "multipath://";
+ for (const std::string& path : setPaths)
+ AddToMultiPath(newPath, path);
+
+ return newPath;
+}
+
+void CMultiPathDirectory::MergeItems(CFileItemList &items)
+{
+ CLog::Log(LOGDEBUG, "CMultiPathDirectory::MergeItems, items = {}", items.Size());
+ auto start = std::chrono::steady_clock::now();
+ if (items.Size() == 0)
+ return;
+ // sort items by label
+ // folders are before files in this sort method
+ items.Sort(SortByLabel, SortOrderAscending);
+ int i = 0;
+
+ // if first item in the sorted list is a file, just abort
+ if (!items.Get(i)->m_bIsFolder)
+ return;
+
+ while (i + 1 < items.Size())
+ {
+ // there are no more folders left, so exit the loop
+ CFileItemPtr pItem1 = items.Get(i);
+ if (!pItem1->m_bIsFolder)
+ break;
+
+ std::vector<int> stack;
+ stack.push_back(i);
+ CLog::Log(LOGDEBUG, "Testing path: [{:03}] {}", i, CURL::GetRedacted(pItem1->GetPath()));
+
+ int j = i + 1;
+ do
+ {
+ CFileItemPtr pItem2 = items.Get(j);
+ if (pItem2->GetLabel() != pItem1->GetLabel())
+ break;
+
+ // ignore any filefolders which may coincidently have
+ // the same label as a true folder
+ if (!pItem2->IsFileFolder())
+ {
+ stack.push_back(j);
+ CLog::Log(LOGDEBUG, " Adding path: [{:03}] {}", j, CURL::GetRedacted(pItem2->GetPath()));
+ }
+ j++;
+ }
+ while (j < items.Size());
+
+ // do we have anything to combine?
+ if (stack.size() > 1)
+ {
+ // we have a multipath so remove the items and add the new item
+ std::string newPath = ConstructMultiPath(items, stack);
+ for (unsigned int k = stack.size() - 1; k > 0; --k)
+ items.Remove(stack[k]);
+ pItem1->SetPath(newPath);
+ CLog::Log(LOGDEBUG, " New path: {}", CURL::GetRedacted(pItem1->GetPath()));
+ }
+
+ i++;
+ }
+
+ auto end = std::chrono::steady_clock::now();
+ auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
+
+ CLog::Log(LOGDEBUG, "CMultiPathDirectory::MergeItems, items = {}, took {} ms", items.Size(),
+ duration.count());
+}
+
+bool CMultiPathDirectory::SupportsWriteFileOperations(const std::string &strPath)
+{
+ std::vector<std::string> paths;
+ GetPaths(strPath, paths);
+ for (unsigned int i = 0; i < paths.size(); ++i)
+ if (CUtil::SupportsWriteFileOperations(paths[i]))
+ return true;
+ return false;
+}
diff --git a/xbmc/filesystem/MultiPathDirectory.h b/xbmc/filesystem/MultiPathDirectory.h
new file mode 100644
index 0000000..7f92217
--- /dev/null
+++ b/xbmc/filesystem/MultiPathDirectory.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IDirectory.h"
+
+#include <set>
+#include <string>
+#include <vector>
+
+namespace XFILE
+{
+class CMultiPathDirectory :
+ public IDirectory
+{
+public:
+ CMultiPathDirectory(void);
+ ~CMultiPathDirectory(void) override;
+ bool GetDirectory(const CURL& url, CFileItemList &items) override;
+ bool Exists(const CURL& url) override;
+ bool Remove(const CURL& url) override;
+
+ static std::string GetFirstPath(const std::string &strPath);
+ static bool SupportsWriteFileOperations(const std::string &strPath);
+ static bool GetPaths(const CURL& url, std::vector<std::string>& vecPaths);
+ static bool GetPaths(const std::string& path, std::vector<std::string>& paths);
+ static bool HasPath(const std::string& strPath, const std::string& strPathToFind);
+ static std::string ConstructMultiPath(const std::vector<std::string> &vecPaths);
+ static std::string ConstructMultiPath(const std::set<std::string> &setPaths);
+
+private:
+ void MergeItems(CFileItemList &items);
+ static void AddToMultiPath(std::string& strMultiPath, const std::string& strPath);
+ std::string ConstructMultiPath(const CFileItemList& items, const std::vector<int> &stack);
+};
+}
diff --git a/xbmc/filesystem/MultiPathFile.cpp b/xbmc/filesystem/MultiPathFile.cpp
new file mode 100644
index 0000000..1f1bf96
--- /dev/null
+++ b/xbmc/filesystem/MultiPathFile.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "MultiPathFile.h"
+
+#include "MultiPathDirectory.h"
+#include "URL.h"
+#include "utils/URIUtils.h"
+
+using namespace XFILE;
+
+CMultiPathFile::CMultiPathFile(void)
+ : COverrideFile(false)
+{ }
+
+CMultiPathFile::~CMultiPathFile(void) = default;
+
+bool CMultiPathFile::Open(const CURL& url)
+{
+ // grab the filename off the url
+ std::string path, fileName;
+ URIUtils::Split(url.Get(), path, fileName);
+ std::vector<std::string> vecPaths;
+ if (!CMultiPathDirectory::GetPaths(path, vecPaths))
+ return false;
+
+ for (unsigned int i = 0; i < vecPaths.size(); i++)
+ {
+ std::string filePath = vecPaths[i];
+ filePath = URIUtils::AddFileToFolder(filePath, fileName);
+ if (m_file.Open(filePath))
+ return true;
+ }
+ return false;
+}
+
+bool CMultiPathFile::Exists(const CURL& url)
+{
+ // grab the filename off the url
+ std::string path, fileName;
+ URIUtils::Split(url.Get(), path, fileName);
+ std::vector<std::string> vecPaths;
+ if (!CMultiPathDirectory::GetPaths(path, vecPaths))
+ return false;
+
+ for (unsigned int i = 0; i < vecPaths.size(); i++)
+ {
+ std::string filePath = vecPaths[i];
+ filePath = URIUtils::AddFileToFolder(filePath, fileName);
+ if (CFile::Exists(filePath))
+ return true;
+ }
+ return false;
+}
+
+int CMultiPathFile::Stat(const CURL& url, struct __stat64* buffer)
+{
+ // grab the filename off the url
+ std::string path, fileName;
+ URIUtils::Split(url.Get(), path, fileName);
+ std::vector<std::string> vecPaths;
+ if (!CMultiPathDirectory::GetPaths(path, vecPaths))
+ return false;
+
+ for (unsigned int i = 0; i < vecPaths.size(); i++)
+ {
+ std::string filePath = vecPaths[i];
+ filePath = URIUtils::AddFileToFolder(filePath, fileName);
+ int ret = CFile::Stat(filePath, buffer);
+ if (ret == 0)
+ return ret;
+ }
+ return -1;
+}
+
+std::string CMultiPathFile::TranslatePath(const CURL& url)
+{
+ return url.Get();
+}
diff --git a/xbmc/filesystem/MultiPathFile.h b/xbmc/filesystem/MultiPathFile.h
new file mode 100644
index 0000000..edff559
--- /dev/null
+++ b/xbmc/filesystem/MultiPathFile.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "filesystem/OverrideFile.h"
+
+namespace XFILE
+{
+ class CMultiPathFile : public COverrideFile
+ {
+ public:
+ CMultiPathFile(void);
+ ~CMultiPathFile(void) override;
+ bool Open(const CURL& url) override;
+ bool Exists(const CURL& url) override;
+ int Stat(const CURL& url, struct __stat64* buffer) override;
+
+ protected:
+ std::string TranslatePath(const CURL &url) override;
+ };
+}
diff --git a/xbmc/filesystem/MusicDatabaseDirectory.cpp b/xbmc/filesystem/MusicDatabaseDirectory.cpp
new file mode 100644
index 0000000..f998d55
--- /dev/null
+++ b/xbmc/filesystem/MusicDatabaseDirectory.cpp
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "MusicDatabaseDirectory.h"
+
+#include "FileItem.h"
+#include "MusicDatabaseDirectory/QueryParams.h"
+#include "ServiceBroker.h"
+#include "filesystem/File.h"
+#include "guilib/LocalizeStrings.h"
+#include "guilib/TextureManager.h"
+#include "music/MusicDatabase.h"
+#include "music/MusicDbUrl.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/Crc32.h"
+#include "utils/LegacyPathTranslation.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+
+using namespace XFILE;
+using namespace MUSICDATABASEDIRECTORY;
+
+CMusicDatabaseDirectory::CMusicDatabaseDirectory(void) = default;
+
+CMusicDatabaseDirectory::~CMusicDatabaseDirectory(void) = default;
+
+bool CMusicDatabaseDirectory::GetDirectory(const CURL& url, CFileItemList &items)
+{
+ std::string path = CLegacyPathTranslation::TranslateMusicDbPath(url);
+
+ // Adjust path to control navigation from albums to discs or directly to songs
+ CQueryParams params;
+ NODE_TYPE type;
+ NODE_TYPE childtype;
+ GetDirectoryNodeInfo(path, type, childtype, params);
+ if (childtype == NODE_TYPE_DISC)
+ {
+ bool bFlatten = false;
+ if (params.GetAlbumId() < 0)
+ bFlatten = true; // Showing *all albums next always songs
+ else
+ {
+ // Option to show discs for ordinary albums (not just boxed sets)
+ bFlatten = !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
+ CSettings::SETTING_MUSICLIBRARY_SHOWDISCS);
+ CMusicDatabase musicdatabase;
+ if (musicdatabase.Open())
+ {
+ if (bFlatten) // Check for boxed set
+ bFlatten = !musicdatabase.IsAlbumBoxset(params.GetAlbumId());
+ if (!bFlatten)
+ { // Check we will get more than 1 disc when path filter options applied
+ int iDiscTotal = musicdatabase.GetDiscsCount(path);
+ bFlatten = iDiscTotal <= 1;
+ }
+ }
+ musicdatabase.Close();
+ }
+ if (bFlatten)
+ { // Skip discs level and go directly to songs
+ CMusicDbUrl musicUrl;
+ if (!musicUrl.FromString(path))
+ return false;
+ musicUrl.AppendPath("-2/"); // Flattened so adjust list label etc.
+ path = musicUrl.ToString();
+ }
+ }
+
+ items.SetPath(path);
+ items.m_dwSize = -1; // No size
+
+ std::unique_ptr<CDirectoryNode> pNode(CDirectoryNode::ParseURL(path));
+
+ if (!pNode)
+ return false;
+
+ bool bResult = pNode->GetChilds(items);
+ for (int i=0;i<items.Size();++i)
+ {
+ CFileItemPtr item = items[i];
+ if (item->m_bIsFolder && !item->HasArt("icon") && !item->HasArt("thumb"))
+ {
+ std::string strImage = GetIcon(item->GetPath());
+ if (!strImage.empty() && CServiceBroker::GetGUI()->GetTextureManager().HasTexture(strImage))
+ item->SetArt("icon", strImage);
+ }
+ }
+ if (items.GetLabel().empty())
+ items.SetLabel(pNode->GetLocalizedName());
+
+ return bResult;
+}
+
+NODE_TYPE CMusicDatabaseDirectory::GetDirectoryChildType(const std::string& strPath)
+{
+ std::string path = CLegacyPathTranslation::TranslateMusicDbPath(strPath);
+ std::unique_ptr<CDirectoryNode> pNode(CDirectoryNode::ParseURL(path));
+
+ if (!pNode)
+ return NODE_TYPE_NONE;
+
+ return pNode->GetChildType();
+}
+
+NODE_TYPE CMusicDatabaseDirectory::GetDirectoryType(const std::string& strPath)
+{
+ std::string path = CLegacyPathTranslation::TranslateMusicDbPath(strPath);
+ std::unique_ptr<CDirectoryNode> pNode(CDirectoryNode::ParseURL(path));
+
+ if (!pNode)
+ return NODE_TYPE_NONE;
+
+ return pNode->GetType();
+}
+
+NODE_TYPE CMusicDatabaseDirectory::GetDirectoryParentType(const std::string& strPath)
+{
+ std::string path = CLegacyPathTranslation::TranslateMusicDbPath(strPath);
+ std::unique_ptr<CDirectoryNode> pNode(CDirectoryNode::ParseURL(path));
+
+ if (!pNode)
+ return NODE_TYPE_NONE;
+
+ CDirectoryNode* pParentNode=pNode->GetParent();
+
+ if (!pParentNode)
+ return NODE_TYPE_NONE;
+
+ return pParentNode->GetChildType();
+}
+
+bool CMusicDatabaseDirectory::GetDirectoryNodeInfo(const std::string& strPath,
+ MUSICDATABASEDIRECTORY::NODE_TYPE& type,
+ MUSICDATABASEDIRECTORY::NODE_TYPE& childtype,
+ MUSICDATABASEDIRECTORY::CQueryParams& params)
+{
+ std::string path = CLegacyPathTranslation::TranslateMusicDbPath(strPath);
+ if (!CDirectoryNode::GetNodeInfo(path, type, childtype, params))
+ return false;
+
+ return true;
+}
+
+bool CMusicDatabaseDirectory::IsArtistDir(const std::string& strDirectory)
+{
+ NODE_TYPE node=GetDirectoryType(strDirectory);
+ return (node==NODE_TYPE_ARTIST);
+}
+
+void CMusicDatabaseDirectory::ClearDirectoryCache(const std::string& strDirectory)
+{
+ std::string path = CLegacyPathTranslation::TranslateMusicDbPath(strDirectory);
+ URIUtils::RemoveSlashAtEnd(path);
+
+ uint32_t crc = Crc32::ComputeFromLowerCase(path);
+
+ std::string strFileName = StringUtils::Format("special://temp/archive_cache/{:08x}.fi", crc);
+ CFile::Delete(strFileName);
+}
+
+bool CMusicDatabaseDirectory::IsAllItem(const std::string& strDirectory)
+{
+ //Last query parameter, ignoring any appended options, is -1 or -2
+ CURL url(strDirectory);
+ if (StringUtils::EndsWith(url.GetWithoutOptions(), "/-1/") || // any albumid
+ StringUtils::EndsWith(url.GetWithoutOptions(), "/-1/-2/")) // any albumid + flattened
+ return true;
+ return false;
+}
+
+bool CMusicDatabaseDirectory::GetLabel(const std::string& strDirectory, std::string& strLabel)
+{
+ strLabel = "";
+
+ std::string path = CLegacyPathTranslation::TranslateMusicDbPath(strDirectory);
+ std::unique_ptr<CDirectoryNode> pNode(CDirectoryNode::ParseURL(path));
+ if (!pNode)
+ return false;
+
+ // first see if there's any filter criteria
+ CQueryParams params;
+ CDirectoryNode::GetDatabaseInfo(path, params);
+
+ CMusicDatabase musicdatabase;
+ if (!musicdatabase.Open())
+ return false;
+
+ // get genre
+ if (params.GetGenreId() >= 0)
+ strLabel += musicdatabase.GetGenreById(params.GetGenreId());
+
+ // get artist
+ if (params.GetArtistId() >= 0)
+ {
+ if (!strLabel.empty())
+ strLabel += " / ";
+ strLabel += musicdatabase.GetArtistById(params.GetArtistId());
+ }
+
+ // get album
+ if (params.GetAlbumId() >= 0)
+ {
+ if (!strLabel.empty())
+ strLabel += " / ";
+ strLabel += musicdatabase.GetAlbumById(params.GetAlbumId());
+ }
+
+ if (strLabel.empty())
+ {
+ switch (pNode->GetChildType())
+ {
+ case NODE_TYPE_TOP100:
+ strLabel = g_localizeStrings.Get(271); // Top 100
+ break;
+ case NODE_TYPE_GENRE:
+ strLabel = g_localizeStrings.Get(135); // Genres
+ break;
+ case NODE_TYPE_SOURCE:
+ strLabel = g_localizeStrings.Get(39030); // Sources
+ break;
+ case NODE_TYPE_ROLE:
+ strLabel = g_localizeStrings.Get(38033); // Roles
+ break;
+ case NODE_TYPE_ARTIST:
+ strLabel = g_localizeStrings.Get(133); // Artists
+ break;
+ case NODE_TYPE_ALBUM:
+ strLabel = g_localizeStrings.Get(132); // Albums
+ break;
+ case NODE_TYPE_ALBUM_RECENTLY_ADDED:
+ case NODE_TYPE_ALBUM_RECENTLY_ADDED_SONGS:
+ strLabel = g_localizeStrings.Get(359); // Recently Added Albums
+ break;
+ case NODE_TYPE_ALBUM_RECENTLY_PLAYED:
+ case NODE_TYPE_ALBUM_RECENTLY_PLAYED_SONGS:
+ strLabel = g_localizeStrings.Get(517); // Recently Played Albums
+ break;
+ case NODE_TYPE_ALBUM_TOP100:
+ case NODE_TYPE_ALBUM_TOP100_SONGS:
+ strLabel = g_localizeStrings.Get(10505); // Top 100 Albums
+ break;
+ case NODE_TYPE_SINGLES:
+ strLabel = g_localizeStrings.Get(1050); // Singles
+ break;
+ case NODE_TYPE_SONG:
+ strLabel = g_localizeStrings.Get(134); // Songs
+ break;
+ case NODE_TYPE_SONG_TOP100:
+ strLabel = g_localizeStrings.Get(10504); // Top 100 Songs
+ break;
+ case NODE_TYPE_YEAR:
+ strLabel = g_localizeStrings.Get(652); // Years
+ break;
+ case NODE_TYPE_OVERVIEW:
+ strLabel = "";
+ break;
+ default:
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool CMusicDatabaseDirectory::ContainsSongs(const std::string &path)
+{
+ MUSICDATABASEDIRECTORY::NODE_TYPE type = GetDirectoryChildType(path);
+ if (type == MUSICDATABASEDIRECTORY::NODE_TYPE_SONG) return true;
+ if (type == MUSICDATABASEDIRECTORY::NODE_TYPE_SINGLES) return true;
+ if (type == MUSICDATABASEDIRECTORY::NODE_TYPE_ALBUM_RECENTLY_ADDED_SONGS) return true;
+ if (type == MUSICDATABASEDIRECTORY::NODE_TYPE_ALBUM_RECENTLY_PLAYED_SONGS) return true;
+ if (type == MUSICDATABASEDIRECTORY::NODE_TYPE_ALBUM_TOP100_SONGS) return true;
+ if (type == MUSICDATABASEDIRECTORY::NODE_TYPE_SONG_TOP100) return true;
+ if (type == MUSICDATABASEDIRECTORY::NODE_TYPE_DISC) return true;
+ return false;
+}
+
+bool CMusicDatabaseDirectory::Exists(const CURL& url)
+{
+ std::string path = CLegacyPathTranslation::TranslateMusicDbPath(url);
+ std::unique_ptr<CDirectoryNode> pNode(CDirectoryNode::ParseURL(path));
+
+ if (!pNode)
+ return false;
+
+ if (pNode->GetChildType() == MUSICDATABASEDIRECTORY::NODE_TYPE_NONE)
+ return false;
+
+ return true;
+}
+
+bool CMusicDatabaseDirectory::CanCache(const std::string& strPath)
+{
+ std::string path = CLegacyPathTranslation::TranslateMusicDbPath(strPath);
+ std::unique_ptr<CDirectoryNode> pNode(CDirectoryNode::ParseURL(path));
+ if (!pNode)
+ return false;
+ return pNode->CanCache();
+}
+
+std::string CMusicDatabaseDirectory::GetIcon(const std::string &strDirectory)
+{
+ switch (GetDirectoryChildType(strDirectory))
+ {
+ case NODE_TYPE_ARTIST:
+ return "DefaultMusicArtists.png";
+ case NODE_TYPE_GENRE:
+ return "DefaultMusicGenres.png";
+ case NODE_TYPE_SOURCE:
+ return "DefaultMusicSources.png";
+ case NODE_TYPE_ROLE:
+ return "DefaultMusicRoles.png";
+ case NODE_TYPE_TOP100:
+ return "DefaultMusicTop100.png";
+ case NODE_TYPE_ALBUM:
+ return "DefaultMusicAlbums.png";
+ case NODE_TYPE_ALBUM_RECENTLY_ADDED:
+ case NODE_TYPE_ALBUM_RECENTLY_ADDED_SONGS:
+ return "DefaultMusicRecentlyAdded.png";
+ case NODE_TYPE_ALBUM_RECENTLY_PLAYED:
+ case NODE_TYPE_ALBUM_RECENTLY_PLAYED_SONGS:
+ return "DefaultMusicRecentlyPlayed.png";
+ case NODE_TYPE_SINGLES:
+ case NODE_TYPE_SONG:
+ return "DefaultMusicSongs.png";
+ case NODE_TYPE_ALBUM_TOP100:
+ case NODE_TYPE_ALBUM_TOP100_SONGS:
+ return "DefaultMusicTop100Albums.png";
+ case NODE_TYPE_SONG_TOP100:
+ return "DefaultMusicTop100Songs.png";
+ case NODE_TYPE_YEAR:
+ return "DefaultMusicYears.png";
+ default:
+ break;
+ }
+
+ return "";
+}
diff --git a/xbmc/filesystem/MusicDatabaseDirectory.h b/xbmc/filesystem/MusicDatabaseDirectory.h
new file mode 100644
index 0000000..dc52464
--- /dev/null
+++ b/xbmc/filesystem/MusicDatabaseDirectory.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IDirectory.h"
+#include "MusicDatabaseDirectory/DirectoryNode.h"
+#include "MusicDatabaseDirectory/QueryParams.h"
+
+namespace XFILE
+{
+ class CMusicDatabaseDirectory : public IDirectory
+ {
+ public:
+ CMusicDatabaseDirectory(void);
+ ~CMusicDatabaseDirectory(void) override;
+ bool GetDirectory(const CURL& url, CFileItemList &items) override;
+ bool AllowAll() const override { return true; }
+ bool Exists(const CURL& url) override;
+ static MUSICDATABASEDIRECTORY::NODE_TYPE GetDirectoryChildType(const std::string& strPath);
+ static MUSICDATABASEDIRECTORY::NODE_TYPE GetDirectoryType(const std::string& strPath);
+ static MUSICDATABASEDIRECTORY::NODE_TYPE GetDirectoryParentType(const std::string& strPath);
+ static bool GetDirectoryNodeInfo(const std::string& strPath, MUSICDATABASEDIRECTORY::NODE_TYPE& type, MUSICDATABASEDIRECTORY::NODE_TYPE& childtype, MUSICDATABASEDIRECTORY::CQueryParams& params);
+ bool IsArtistDir(const std::string& strDirectory);
+ void ClearDirectoryCache(const std::string& strDirectory);
+ static bool IsAllItem(const std::string& strDirectory);
+ static bool GetLabel(const std::string& strDirectory, std::string& strLabel);
+ bool ContainsSongs(const std::string &path);
+ static bool CanCache(const std::string& strPath);
+ static std::string GetIcon(const std::string& strDirectory);
+ };
+}
diff --git a/xbmc/filesystem/MusicDatabaseDirectory/CMakeLists.txt b/xbmc/filesystem/MusicDatabaseDirectory/CMakeLists.txt
new file mode 100644
index 0000000..6eafcba
--- /dev/null
+++ b/xbmc/filesystem/MusicDatabaseDirectory/CMakeLists.txt
@@ -0,0 +1,39 @@
+set(SOURCES DirectoryNodeAlbum.cpp
+ DirectoryNodeAlbumRecentlyAdded.cpp
+ DirectoryNodeAlbumRecentlyAddedSong.cpp
+ DirectoryNodeAlbumRecentlyPlayed.cpp
+ DirectoryNodeAlbumRecentlyPlayedSong.cpp
+ DirectoryNodeAlbumTop100.cpp
+ DirectoryNodeAlbumTop100Song.cpp
+ DirectoryNodeArtist.cpp
+ DirectoryNodeDiscs.cpp
+ DirectoryNode.cpp
+ DirectoryNodeGrouped.cpp
+ DirectoryNodeOverview.cpp
+ DirectoryNodeRoot.cpp
+ DirectoryNodeSingles.cpp
+ DirectoryNodeSong.cpp
+ DirectoryNodeSongTop100.cpp
+ DirectoryNodeTop100.cpp
+ QueryParams.cpp)
+
+set(HEADERS DirectoryNode.h
+ DirectoryNodeAlbum.h
+ DirectoryNodeAlbumRecentlyAdded.h
+ DirectoryNodeAlbumRecentlyAddedSong.h
+ DirectoryNodeAlbumRecentlyPlayed.h
+ DirectoryNodeAlbumRecentlyPlayedSong.h
+ DirectoryNodeAlbumTop100.h
+ DirectoryNodeAlbumTop100Song.h
+ DirectoryNodeArtist.h
+ DirectoryNodeDiscs.h
+ DirectoryNodeGrouped.h
+ DirectoryNodeOverview.h
+ DirectoryNodeRoot.h
+ DirectoryNodeSingles.h
+ DirectoryNodeSong.h
+ DirectoryNodeSongTop100.h
+ DirectoryNodeTop100.h
+ QueryParams.h)
+
+core_add_library(musicdatabasedirectory)
diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNode.cpp b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNode.cpp
new file mode 100644
index 0000000..00e1bce
--- /dev/null
+++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNode.cpp
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DirectoryNode.h"
+
+#include "DirectoryNodeAlbum.h"
+#include "DirectoryNodeAlbumRecentlyAdded.h"
+#include "DirectoryNodeAlbumRecentlyAddedSong.h"
+#include "DirectoryNodeAlbumRecentlyPlayed.h"
+#include "DirectoryNodeAlbumRecentlyPlayedSong.h"
+#include "DirectoryNodeAlbumTop100.h"
+#include "DirectoryNodeAlbumTop100Song.h"
+#include "DirectoryNodeArtist.h"
+#include "DirectoryNodeDiscs.h"
+#include "DirectoryNodeGrouped.h"
+#include "DirectoryNodeOverview.h"
+#include "DirectoryNodeRoot.h"
+#include "DirectoryNodeSingles.h"
+#include "DirectoryNodeSong.h"
+#include "DirectoryNodeSongTop100.h"
+#include "DirectoryNodeTop100.h"
+#include "FileItem.h"
+#include "QueryParams.h"
+#include "URL.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+
+using namespace XFILE::MUSICDATABASEDIRECTORY;
+
+// Constructor is protected use ParseURL()
+CDirectoryNode::CDirectoryNode(NODE_TYPE Type, const std::string& strName, CDirectoryNode* pParent)
+{
+ m_Type=Type;
+ m_strName=strName;
+ m_pParent=pParent;
+}
+
+CDirectoryNode::~CDirectoryNode()
+{
+ delete m_pParent;
+}
+
+// Parses a given path and returns the current node of the path
+CDirectoryNode* CDirectoryNode::ParseURL(const std::string& strPath)
+{
+ CURL url(strPath);
+
+ std::string strDirectory=url.GetFileName();
+ URIUtils::RemoveSlashAtEnd(strDirectory);
+
+ std::vector<std::string> Path = StringUtils::Split(strDirectory, '/');
+ Path.insert(Path.begin(), "");
+
+ CDirectoryNode* pNode = nullptr;
+ CDirectoryNode* pParent = nullptr;
+ NODE_TYPE NodeType = NODE_TYPE_ROOT;
+
+ for (int i=0; i < static_cast<int>(Path.size()); ++i)
+ {
+ pNode = CreateNode(NodeType, Path[i], pParent);
+ NodeType = pNode ? pNode->GetChildType() : NODE_TYPE_NONE;
+ pParent = pNode;
+ }
+
+ // Add all the additional URL options to the last node
+ if (pNode)
+ pNode->AddOptions(url.GetOptions());
+
+ return pNode;
+}
+
+// returns the database ids of the path,
+void CDirectoryNode::GetDatabaseInfo(const std::string& strPath, CQueryParams& params)
+{
+ std::unique_ptr<CDirectoryNode> pNode(CDirectoryNode::ParseURL(strPath));
+
+ if (!pNode)
+ return;
+
+ pNode->CollectQueryParams(params);
+}
+
+bool CDirectoryNode::GetNodeInfo(const std::string& strPath,
+ NODE_TYPE& type,
+ NODE_TYPE& childtype,
+ CQueryParams& params)
+{
+ std::unique_ptr<CDirectoryNode> pNode(CDirectoryNode::ParseURL(strPath));
+ if (!pNode)
+ return false;
+
+ type = pNode->GetType();
+ childtype = pNode->GetChildType();
+ pNode->CollectQueryParams(params);
+
+ return true;
+}
+
+// Create a node object
+CDirectoryNode* CDirectoryNode::CreateNode(NODE_TYPE Type, const std::string& strName, CDirectoryNode* pParent)
+{
+ switch (Type)
+ {
+ case NODE_TYPE_ROOT:
+ return new CDirectoryNodeRoot(strName, pParent);
+ case NODE_TYPE_OVERVIEW:
+ return new CDirectoryNodeOverview(strName, pParent);
+ case NODE_TYPE_GENRE:
+ case NODE_TYPE_SOURCE:
+ case NODE_TYPE_ROLE:
+ case NODE_TYPE_YEAR:
+ return new CDirectoryNodeGrouped(Type, strName, pParent);
+ case NODE_TYPE_DISC:
+ return new CDirectoryNodeDiscs(strName, pParent);
+ case NODE_TYPE_ARTIST:
+ return new CDirectoryNodeArtist(strName, pParent);
+ case NODE_TYPE_ALBUM:
+ return new CDirectoryNodeAlbum(strName, pParent);
+ case NODE_TYPE_SONG:
+ return new CDirectoryNodeSong(strName, pParent);
+ case NODE_TYPE_SINGLES:
+ return new CDirectoryNodeSingles(strName, pParent);
+ case NODE_TYPE_TOP100:
+ return new CDirectoryNodeTop100(strName, pParent);
+ case NODE_TYPE_ALBUM_TOP100:
+ return new CDirectoryNodeAlbumTop100(strName, pParent);
+ case NODE_TYPE_ALBUM_TOP100_SONGS:
+ return new CDirectoryNodeAlbumTop100Song(strName, pParent);
+ case NODE_TYPE_SONG_TOP100:
+ return new CDirectoryNodeSongTop100(strName, pParent);
+ case NODE_TYPE_ALBUM_RECENTLY_ADDED:
+ return new CDirectoryNodeAlbumRecentlyAdded(strName, pParent);
+ case NODE_TYPE_ALBUM_RECENTLY_ADDED_SONGS:
+ return new CDirectoryNodeAlbumRecentlyAddedSong(strName, pParent);
+ case NODE_TYPE_ALBUM_RECENTLY_PLAYED:
+ return new CDirectoryNodeAlbumRecentlyPlayed(strName, pParent);
+ case NODE_TYPE_ALBUM_RECENTLY_PLAYED_SONGS:
+ return new CDirectoryNodeAlbumRecentlyPlayedSong(strName, pParent);
+ default:
+ break;
+ }
+
+ return nullptr;
+}
+
+// Current node name
+const std::string& CDirectoryNode::GetName() const
+{
+ return m_strName;
+}
+
+int CDirectoryNode::GetID() const
+{
+ return atoi(m_strName.c_str());
+}
+
+std::string CDirectoryNode::GetLocalizedName() const
+{
+ return "";
+}
+
+// Current node type
+NODE_TYPE CDirectoryNode::GetType() const
+{
+ return m_Type;
+}
+
+// Return the parent directory node or NULL, if there is no
+CDirectoryNode* CDirectoryNode::GetParent() const
+{
+ return m_pParent;
+}
+
+void CDirectoryNode::RemoveParent()
+{
+ m_pParent = nullptr;
+}
+
+// should be overloaded by a derived class
+// to get the content of a node. Will be called
+// by GetChilds() of a parent node
+bool CDirectoryNode::GetContent(CFileItemList& items) const
+{
+ return false;
+}
+
+// Creates a musicdb url
+std::string CDirectoryNode::BuildPath() const
+{
+ std::vector<std::string> array;
+
+ if (!m_strName.empty())
+ array.insert(array.begin(), m_strName);
+
+ CDirectoryNode* pParent=m_pParent;
+ while (pParent != nullptr)
+ {
+ const std::string& strNodeName=pParent->GetName();
+ if (!strNodeName.empty())
+ array.insert(array.begin(), strNodeName);
+
+ pParent=pParent->GetParent();
+ }
+
+ std::string strPath="musicdb://";
+ for (int i = 0; i < static_cast<int>(array.size()); ++i)
+ strPath+=array[i]+"/";
+
+ std::string options = m_options.GetOptionsString();
+ if (!options.empty())
+ strPath += "?" + options;
+
+ return strPath;
+}
+
+void CDirectoryNode::AddOptions(const std::string &options)
+{
+ if (options.empty())
+ return;
+
+ m_options.AddOptions(options);
+}
+
+// Collects Query params from this and all parent nodes. If a NODE_TYPE can
+// be used as a database parameter, it will be added to the
+// params object.
+void CDirectoryNode::CollectQueryParams(CQueryParams& params) const
+{
+ params.SetQueryParam(m_Type, m_strName);
+
+ CDirectoryNode* pParent=m_pParent;
+ while (pParent != nullptr)
+ {
+ params.SetQueryParam(pParent->GetType(), pParent->GetName());
+ pParent=pParent->GetParent();
+ }
+}
+
+// Should be overloaded by a derived class.
+// Returns the NODE_TYPE of the child nodes.
+NODE_TYPE CDirectoryNode::GetChildType() const
+{
+ return NODE_TYPE_NONE;
+}
+
+// Get the child fileitems of this node
+bool CDirectoryNode::GetChilds(CFileItemList& items)
+{
+ if (CanCache() && items.Load())
+ return true;
+
+ std::unique_ptr<CDirectoryNode> pNode(CDirectoryNode::CreateNode(GetChildType(), "", this));
+
+ bool bSuccess=false;
+ if (pNode)
+ {
+ pNode->m_options = m_options;
+ bSuccess=pNode->GetContent(items);
+ if (bSuccess)
+ {
+ if (CanCache())
+ items.SetCacheToDisc(CFileItemList::CACHE_ALWAYS);
+ }
+ else
+ items.Clear();
+
+ pNode->RemoveParent();
+ }
+
+ return bSuccess;
+}
+
+
+bool CDirectoryNode::CanCache() const
+{
+ // JM: No need to cache these views, as caching is added in the mediawindow baseclass for anything that takes
+ // longer than a second
+ return false;
+}
diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNode.h b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNode.h
new file mode 100644
index 0000000..63d11d1
--- /dev/null
+++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNode.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "utils/UrlOptions.h"
+
+class CFileItemList;
+
+namespace XFILE
+{
+ namespace MUSICDATABASEDIRECTORY
+ {
+ class CQueryParams;
+
+ typedef enum _NODE_TYPE
+ {
+ NODE_TYPE_NONE=0,
+ NODE_TYPE_ROOT,
+ NODE_TYPE_OVERVIEW,
+ NODE_TYPE_TOP100,
+ NODE_TYPE_ROLE,
+ NODE_TYPE_SOURCE,
+ NODE_TYPE_GENRE,
+ NODE_TYPE_ARTIST,
+ NODE_TYPE_ALBUM,
+ NODE_TYPE_ALBUM_RECENTLY_ADDED,
+ NODE_TYPE_ALBUM_RECENTLY_ADDED_SONGS,
+ NODE_TYPE_ALBUM_RECENTLY_PLAYED,
+ NODE_TYPE_ALBUM_RECENTLY_PLAYED_SONGS,
+ NODE_TYPE_ALBUM_TOP100,
+ NODE_TYPE_ALBUM_TOP100_SONGS,
+ NODE_TYPE_SONG,
+ NODE_TYPE_SONG_TOP100,
+ NODE_TYPE_YEAR,
+ NODE_TYPE_SINGLES,
+ NODE_TYPE_DISC,
+ } NODE_TYPE;
+
+ typedef struct {
+ NODE_TYPE node;
+ std::string id;
+ int label;
+ } Node;
+
+ class CDirectoryNode
+ {
+ public:
+ static CDirectoryNode* ParseURL(const std::string& strPath);
+ static void GetDatabaseInfo(const std::string& strPath, CQueryParams& params);
+ static bool GetNodeInfo(const std::string& strPath, NODE_TYPE& type, NODE_TYPE& childtype, CQueryParams& params);
+ virtual ~CDirectoryNode();
+
+ NODE_TYPE GetType() const;
+
+ bool GetChilds(CFileItemList& items);
+ virtual NODE_TYPE GetChildType() const;
+ virtual std::string GetLocalizedName() const;
+
+ CDirectoryNode* GetParent() const;
+ virtual bool CanCache() const;
+
+ std::string BuildPath() const;
+
+ protected:
+ CDirectoryNode(NODE_TYPE Type, const std::string& strName, CDirectoryNode* pParent);
+ static CDirectoryNode* CreateNode(NODE_TYPE Type, const std::string& strName, CDirectoryNode* pParent);
+
+ void AddOptions(const std::string &options);
+ void CollectQueryParams(CQueryParams& params) const;
+
+ const std::string& GetName() const;
+ int GetID() const;
+ void RemoveParent();
+
+ virtual bool GetContent(CFileItemList& items) const;
+
+ private:
+ NODE_TYPE m_Type;
+ std::string m_strName;
+ CDirectoryNode* m_pParent;
+ CUrlOptions m_options;
+ };
+ }
+}
+
+
diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbum.cpp b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbum.cpp
new file mode 100644
index 0000000..f60a025
--- /dev/null
+++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbum.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DirectoryNodeAlbum.h"
+
+#include "QueryParams.h"
+#include "guilib/LocalizeStrings.h"
+#include "music/MusicDatabase.h"
+
+using namespace XFILE::MUSICDATABASEDIRECTORY;
+
+CDirectoryNodeAlbum::CDirectoryNodeAlbum(const std::string& strName, CDirectoryNode* pParent)
+ : CDirectoryNode(NODE_TYPE_ALBUM, strName, pParent)
+{
+
+}
+
+NODE_TYPE CDirectoryNodeAlbum::GetChildType() const
+{
+ return NODE_TYPE_DISC;
+}
+
+std::string CDirectoryNodeAlbum::GetLocalizedName() const
+{
+ if (GetID() == -1)
+ return g_localizeStrings.Get(15102); // All Albums
+ CMusicDatabase db;
+ if (db.Open())
+ return db.GetAlbumById(GetID());
+ return "";
+}
+
+bool CDirectoryNodeAlbum::GetContent(CFileItemList& items) const
+{
+ CMusicDatabase musicdatabase;
+ if (!musicdatabase.Open())
+ return false;
+
+ CQueryParams params;
+ CollectQueryParams(params);
+
+ bool bSuccess=musicdatabase.GetAlbumsNav(BuildPath(), items, params.GetGenreId(), params.GetArtistId());
+
+ musicdatabase.Close();
+
+ return bSuccess;
+}
diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbum.h b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbum.h
new file mode 100644
index 0000000..60fb233
--- /dev/null
+++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbum.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DirectoryNode.h"
+
+namespace XFILE
+{
+ namespace MUSICDATABASEDIRECTORY
+ {
+ class CDirectoryNodeAlbum : public CDirectoryNode
+ {
+ public:
+ CDirectoryNodeAlbum(const std::string& strName, CDirectoryNode* pParent);
+ protected:
+ NODE_TYPE GetChildType() const override;
+ bool GetContent(CFileItemList& items) const override;
+ std::string GetLocalizedName() const override;
+ };
+ }
+}
+
+
diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyAdded.cpp b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyAdded.cpp
new file mode 100644
index 0000000..f274b59
--- /dev/null
+++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyAdded.cpp
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DirectoryNodeAlbumRecentlyAdded.h"
+
+#include "FileItem.h"
+#include "guilib/LocalizeStrings.h"
+#include "music/MusicDatabase.h"
+#include "utils/StringUtils.h"
+
+using namespace XFILE::MUSICDATABASEDIRECTORY;
+
+CDirectoryNodeAlbumRecentlyAdded::CDirectoryNodeAlbumRecentlyAdded(const std::string& strName, CDirectoryNode* pParent)
+ : CDirectoryNode(NODE_TYPE_ALBUM_RECENTLY_ADDED, strName, pParent)
+{
+
+}
+
+NODE_TYPE CDirectoryNodeAlbumRecentlyAdded::GetChildType() const
+{
+ if (GetName()=="-1")
+ return NODE_TYPE_ALBUM_RECENTLY_ADDED_SONGS;
+
+ return NODE_TYPE_DISC;
+}
+
+std::string CDirectoryNodeAlbumRecentlyAdded::GetLocalizedName() const
+{
+ if (GetID() == -1)
+ return g_localizeStrings.Get(15102); // All Albums
+ CMusicDatabase db;
+ if (db.Open())
+ return db.GetAlbumById(GetID());
+ return "";
+}
+
+bool CDirectoryNodeAlbumRecentlyAdded::GetContent(CFileItemList& items) const
+{
+ CMusicDatabase musicdatabase;
+ if (!musicdatabase.Open())
+ return false;
+
+ VECALBUMS albums;
+ if (!musicdatabase.GetRecentlyAddedAlbums(albums))
+ {
+ musicdatabase.Close();
+ return false;
+ }
+
+ for (int i=0; i<(int)albums.size(); ++i)
+ {
+ CAlbum& album=albums[i];
+ std::string strDir = StringUtils::Format("{}{}/", BuildPath(), album.idAlbum);
+ CFileItemPtr pItem(new CFileItem(strDir, album));
+ items.Add(pItem);
+ }
+
+ musicdatabase.Close();
+ return true;
+}
diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyAdded.h b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyAdded.h
new file mode 100644
index 0000000..a1b4dc1
--- /dev/null
+++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyAdded.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DirectoryNode.h"
+
+namespace XFILE
+{
+ namespace MUSICDATABASEDIRECTORY
+ {
+ class CDirectoryNodeAlbumRecentlyAdded : public CDirectoryNode
+ {
+ public:
+ CDirectoryNodeAlbumRecentlyAdded(const std::string& strName, CDirectoryNode* pParent);
+ protected:
+ NODE_TYPE GetChildType() const override;
+ bool GetContent(CFileItemList& items) const override;
+ std::string GetLocalizedName() const override;
+ };
+ }
+}
+
+
diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyAddedSong.cpp b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyAddedSong.cpp
new file mode 100644
index 0000000..8c8d786
--- /dev/null
+++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyAddedSong.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DirectoryNodeAlbumRecentlyAddedSong.h"
+
+#include "music/MusicDatabase.h"
+
+using namespace XFILE::MUSICDATABASEDIRECTORY;
+
+CDirectoryNodeAlbumRecentlyAddedSong::CDirectoryNodeAlbumRecentlyAddedSong(const std::string& strName, CDirectoryNode* pParent)
+ : CDirectoryNode(NODE_TYPE_ALBUM_RECENTLY_ADDED_SONGS, strName, pParent)
+{
+
+}
+
+bool CDirectoryNodeAlbumRecentlyAddedSong::GetContent(CFileItemList& items) const
+{
+ CMusicDatabase musicdatabase;
+ if (!musicdatabase.Open())
+ return false;
+
+ std::string strBaseDir=BuildPath();
+ bool bSuccess=musicdatabase.GetRecentlyAddedAlbumSongs(strBaseDir, items);
+
+ musicdatabase.Close();
+
+ return bSuccess;
+}
diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyAddedSong.h b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyAddedSong.h
new file mode 100644
index 0000000..9cc46c1
--- /dev/null
+++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyAddedSong.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DirectoryNode.h"
+
+namespace XFILE
+{
+ namespace MUSICDATABASEDIRECTORY
+ {
+ class CDirectoryNodeAlbumRecentlyAddedSong : public CDirectoryNode
+ {
+ public:
+ CDirectoryNodeAlbumRecentlyAddedSong(const std::string& strName, CDirectoryNode* pParent);
+ protected:
+ bool GetContent(CFileItemList& items) const override;
+ };
+ }
+}
+
+
diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyPlayed.cpp b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyPlayed.cpp
new file mode 100644
index 0000000..77940c2
--- /dev/null
+++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyPlayed.cpp
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DirectoryNodeAlbumRecentlyPlayed.h"
+
+#include "FileItem.h"
+#include "guilib/LocalizeStrings.h"
+#include "music/MusicDatabase.h"
+#include "utils/StringUtils.h"
+
+using namespace XFILE::MUSICDATABASEDIRECTORY;
+
+CDirectoryNodeAlbumRecentlyPlayed::CDirectoryNodeAlbumRecentlyPlayed(const std::string& strName, CDirectoryNode* pParent)
+ : CDirectoryNode(NODE_TYPE_ALBUM_RECENTLY_PLAYED, strName, pParent)
+{
+
+}
+
+NODE_TYPE CDirectoryNodeAlbumRecentlyPlayed::GetChildType() const
+{
+ if (GetName()=="-1")
+ return NODE_TYPE_ALBUM_RECENTLY_PLAYED_SONGS;
+
+ return NODE_TYPE_DISC;
+}
+
+std::string CDirectoryNodeAlbumRecentlyPlayed::GetLocalizedName() const
+{
+ if (GetID() == -1)
+ return g_localizeStrings.Get(15102); // All Albums
+ CMusicDatabase db;
+ if (db.Open())
+ return db.GetAlbumById(GetID());
+ return "";
+}
+
+bool CDirectoryNodeAlbumRecentlyPlayed::GetContent(CFileItemList& items) const
+{
+ CMusicDatabase musicdatabase;
+ if (!musicdatabase.Open())
+ return false;
+
+ VECALBUMS albums;
+ if (!musicdatabase.GetRecentlyPlayedAlbums(albums))
+ {
+ musicdatabase.Close();
+ return false;
+ }
+
+ for (int i=0; i<(int)albums.size(); ++i)
+ {
+ CAlbum& album=albums[i];
+ std::string strDir = StringUtils::Format("{}{}/", BuildPath(), album.idAlbum);
+ CFileItemPtr pItem(new CFileItem(strDir, album));
+ items.Add(pItem);
+ }
+
+ musicdatabase.Close();
+ return true;
+}
diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyPlayed.h b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyPlayed.h
new file mode 100644
index 0000000..4691c89
--- /dev/null
+++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyPlayed.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DirectoryNode.h"
+
+namespace XFILE
+{
+ namespace MUSICDATABASEDIRECTORY
+ {
+ class CDirectoryNodeAlbumRecentlyPlayed : public CDirectoryNode
+ {
+ public:
+ CDirectoryNodeAlbumRecentlyPlayed(const std::string& strName, CDirectoryNode* pParent);
+ protected:
+ NODE_TYPE GetChildType() const override;
+ bool GetContent(CFileItemList& items) const override;
+ std::string GetLocalizedName() const override;
+ };
+ }
+}
+
+
diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyPlayedSong.cpp b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyPlayedSong.cpp
new file mode 100644
index 0000000..7a01af0
--- /dev/null
+++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyPlayedSong.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DirectoryNodeAlbumRecentlyPlayedSong.h"
+
+#include "music/MusicDatabase.h"
+
+using namespace XFILE::MUSICDATABASEDIRECTORY;
+
+CDirectoryNodeAlbumRecentlyPlayedSong::CDirectoryNodeAlbumRecentlyPlayedSong(const std::string& strName, CDirectoryNode* pParent)
+ : CDirectoryNode(NODE_TYPE_ALBUM_RECENTLY_PLAYED_SONGS, strName, pParent)
+{
+
+}
+
+bool CDirectoryNodeAlbumRecentlyPlayedSong::GetContent(CFileItemList& items) const
+{
+ CMusicDatabase musicdatabase;
+ if (!musicdatabase.Open())
+ return false;
+
+ std::string strBaseDir=BuildPath();
+ bool bSuccess=musicdatabase.GetRecentlyPlayedAlbumSongs(strBaseDir, items);
+
+ musicdatabase.Close();
+
+ return bSuccess;
+}
diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyPlayedSong.h b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyPlayedSong.h
new file mode 100644
index 0000000..3ffab08
--- /dev/null
+++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumRecentlyPlayedSong.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DirectoryNode.h"
+
+namespace XFILE
+{
+ namespace MUSICDATABASEDIRECTORY
+ {
+ class CDirectoryNodeAlbumRecentlyPlayedSong : public CDirectoryNode
+ {
+ public:
+ CDirectoryNodeAlbumRecentlyPlayedSong(const std::string& strName, CDirectoryNode* pParent);
+ protected:
+ bool GetContent(CFileItemList& items) const override;
+ };
+ }
+}
+
+
diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumTop100.cpp b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumTop100.cpp
new file mode 100644
index 0000000..88ffaff
--- /dev/null
+++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumTop100.cpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DirectoryNodeAlbumTop100.h"
+
+#include "FileItem.h"
+#include "music/MusicDatabase.h"
+#include "utils/StringUtils.h"
+
+using namespace XFILE::MUSICDATABASEDIRECTORY;
+
+CDirectoryNodeAlbumTop100::CDirectoryNodeAlbumTop100(const std::string& strName, CDirectoryNode* pParent)
+ : CDirectoryNode(NODE_TYPE_ALBUM_TOP100, strName, pParent)
+{
+
+}
+
+NODE_TYPE CDirectoryNodeAlbumTop100::GetChildType() const
+{
+ if (GetName()=="-1")
+ return NODE_TYPE_ALBUM_TOP100_SONGS;
+
+ return NODE_TYPE_SONG;
+}
+
+std::string CDirectoryNodeAlbumTop100::GetLocalizedName() const
+{
+ CMusicDatabase db;
+ if (db.Open())
+ return db.GetAlbumById(GetID());
+ return "";
+}
+
+bool CDirectoryNodeAlbumTop100::GetContent(CFileItemList& items) const
+{
+ CMusicDatabase musicdatabase;
+ if (!musicdatabase.Open())
+ return false;
+
+ VECALBUMS albums;
+ if (!musicdatabase.GetTop100Albums(albums))
+ {
+ musicdatabase.Close();
+ return false;
+ }
+
+ for (int i=0; i<(int)albums.size(); ++i)
+ {
+ CAlbum& album=albums[i];
+ std::string strDir = StringUtils::Format("{}{}/", BuildPath(), album.idAlbum);
+ CFileItemPtr pItem(new CFileItem(strDir, album));
+ items.Add(pItem);
+ }
+
+ musicdatabase.Close();
+
+ return true;
+}
diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumTop100.h b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumTop100.h
new file mode 100644
index 0000000..de117bd
--- /dev/null
+++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumTop100.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DirectoryNode.h"
+
+namespace XFILE
+{
+ namespace MUSICDATABASEDIRECTORY
+ {
+ class CDirectoryNodeAlbumTop100 : public CDirectoryNode
+ {
+ public:
+ CDirectoryNodeAlbumTop100(const std::string& strName, CDirectoryNode* pParent);
+ protected:
+ NODE_TYPE GetChildType() const override;
+ bool GetContent(CFileItemList& items) const override;
+ std::string GetLocalizedName() const override;
+ };
+ }
+}
+
+
diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumTop100Song.cpp b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumTop100Song.cpp
new file mode 100644
index 0000000..109af60
--- /dev/null
+++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumTop100Song.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DirectoryNodeAlbumTop100Song.h"
+
+#include "music/MusicDatabase.h"
+
+using namespace XFILE::MUSICDATABASEDIRECTORY;
+
+CDirectoryNodeAlbumTop100Song::CDirectoryNodeAlbumTop100Song(const std::string& strName, CDirectoryNode* pParent)
+ : CDirectoryNode(NODE_TYPE_ALBUM_TOP100_SONGS, strName, pParent)
+{
+
+}
+
+bool CDirectoryNodeAlbumTop100Song::GetContent(CFileItemList& items) const
+{
+ CMusicDatabase musicdatabase;
+ if (!musicdatabase.Open())
+ return false;
+
+ std::string strBaseDir=BuildPath();
+ bool bSuccess=musicdatabase.GetTop100AlbumSongs(strBaseDir, items);
+
+ musicdatabase.Close();
+
+ return bSuccess;
+}
diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumTop100Song.h b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumTop100Song.h
new file mode 100644
index 0000000..18351d7
--- /dev/null
+++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeAlbumTop100Song.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DirectoryNode.h"
+
+namespace XFILE
+{
+ namespace MUSICDATABASEDIRECTORY
+ {
+ class CDirectoryNodeAlbumTop100Song : public CDirectoryNode
+ {
+ public:
+ CDirectoryNodeAlbumTop100Song(const std::string& strName, CDirectoryNode* pParent);
+ protected:
+ bool GetContent(CFileItemList& items) const override;
+ };
+ }
+}
+
+
diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeArtist.cpp b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeArtist.cpp
new file mode 100644
index 0000000..b087fa2
--- /dev/null
+++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeArtist.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DirectoryNodeArtist.h"
+
+#include "QueryParams.h"
+#include "ServiceBroker.h"
+#include "guilib/LocalizeStrings.h"
+#include "music/MusicDatabase.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+
+using namespace XFILE::MUSICDATABASEDIRECTORY;
+
+CDirectoryNodeArtist::CDirectoryNodeArtist(const std::string& strName, CDirectoryNode* pParent)
+ : CDirectoryNode(NODE_TYPE_ARTIST, strName, pParent)
+{
+
+}
+
+NODE_TYPE CDirectoryNodeArtist::GetChildType() const
+{
+ return NODE_TYPE_ALBUM;
+}
+
+std::string CDirectoryNodeArtist::GetLocalizedName() const
+{
+ if (GetID() == -1)
+ return g_localizeStrings.Get(15103); // All Artists
+ CMusicDatabase db;
+ if (db.Open())
+ return db.GetArtistById(GetID());
+ return "";
+}
+
+bool CDirectoryNodeArtist::GetContent(CFileItemList& items) const
+{
+ CMusicDatabase musicdatabase;
+ if (!musicdatabase.Open())
+ return false;
+
+ CQueryParams params;
+ CollectQueryParams(params);
+
+ bool bSuccess = musicdatabase.GetArtistsNav(BuildPath(), items, !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MUSICLIBRARY_SHOWCOMPILATIONARTISTS), params.GetGenreId());
+
+ musicdatabase.Close();
+
+ return bSuccess;
+}
diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeArtist.h b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeArtist.h
new file mode 100644
index 0000000..59b8c73
--- /dev/null
+++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeArtist.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DirectoryNode.h"
+
+namespace XFILE
+{
+ namespace MUSICDATABASEDIRECTORY
+ {
+ class CDirectoryNodeArtist : public CDirectoryNode
+ {
+ public:
+ CDirectoryNodeArtist(const std::string& strName, CDirectoryNode* pParent);
+ protected:
+ NODE_TYPE GetChildType() const override;
+ bool GetContent(CFileItemList& items) const override;
+ std::string GetLocalizedName() const override;
+ };
+ }
+}
+
+
diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeDiscs.cpp b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeDiscs.cpp
new file mode 100644
index 0000000..42cf3bb
--- /dev/null
+++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeDiscs.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2005-2019 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DirectoryNodeDiscs.h"
+
+#include "QueryParams.h"
+#include "guilib/LocalizeStrings.h"
+#include "music/MusicDatabase.h"
+
+using namespace XFILE::MUSICDATABASEDIRECTORY;
+
+CDirectoryNodeDiscs::CDirectoryNodeDiscs(const std::string& strName,
+ CDirectoryNode* pParent)
+ : CDirectoryNode(NODE_TYPE_DISC, strName, pParent)
+{
+}
+
+NODE_TYPE CDirectoryNodeDiscs::GetChildType() const
+{
+ return NODE_TYPE_SONG;
+}
+
+std::string CDirectoryNodeDiscs::GetLocalizedName() const
+{
+ CQueryParams params;
+ CollectQueryParams(params);
+ std::string title;
+ CMusicDatabase db;
+ if (db.Open())
+ title = db.GetAlbumDiscTitle(params.GetAlbumId(), params.GetDisc());
+ db.Close();
+ if (title.empty())
+ title = g_localizeStrings.Get(15102); // All Albums
+
+ return title;
+}
+
+bool CDirectoryNodeDiscs::GetContent(CFileItemList& items) const
+{
+ CMusicDatabase musicdatabase;
+ if (!musicdatabase.Open())
+ return false;
+
+ CQueryParams params;
+ CollectQueryParams(params);
+
+ bool bSuccess = musicdatabase.GetDiscsNav(BuildPath(), items, params.GetAlbumId());
+
+ musicdatabase.Close();
+
+ return bSuccess;
+}
diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeDiscs.h b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeDiscs.h
new file mode 100644
index 0000000..5cf4f6a
--- /dev/null
+++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeDiscs.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2005-2019 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DirectoryNode.h"
+
+namespace XFILE
+{
+namespace MUSICDATABASEDIRECTORY
+{
+class CDirectoryNodeDiscs : public CDirectoryNode
+{
+public:
+ CDirectoryNodeDiscs(const std::string& strName, CDirectoryNode* pParent);
+
+protected:
+ NODE_TYPE GetChildType() const override;
+ bool GetContent(CFileItemList& items) const override;
+ std::string GetLocalizedName() const override;
+};
+} // namespace MUSICDATABASEDIRECTORY
+} // namespace XFILE
diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeGrouped.cpp b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeGrouped.cpp
new file mode 100644
index 0000000..65875f7
--- /dev/null
+++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeGrouped.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DirectoryNodeGrouped.h"
+
+#include "music/MusicDatabase.h"
+
+using namespace XFILE::MUSICDATABASEDIRECTORY;
+
+CDirectoryNodeGrouped::CDirectoryNodeGrouped(NODE_TYPE type, const std::string& strName, CDirectoryNode* pParent)
+ : CDirectoryNode(type, strName, pParent)
+{ }
+
+NODE_TYPE CDirectoryNodeGrouped::GetChildType() const
+{
+ if (GetType() == NODE_TYPE_YEAR)
+ return NODE_TYPE_ALBUM;
+
+ return NODE_TYPE_ARTIST;
+}
+
+std::string CDirectoryNodeGrouped::GetLocalizedName() const
+{
+ CMusicDatabase db;
+ if (db.Open())
+ return db.GetItemById(GetContentType(), GetID());
+ return "";
+}
+
+bool CDirectoryNodeGrouped::GetContent(CFileItemList& items) const
+{
+ CMusicDatabase musicdatabase;
+ if (!musicdatabase.Open())
+ return false;
+
+ return musicdatabase.GetItems(BuildPath(), GetContentType(), items);
+}
+
+std::string CDirectoryNodeGrouped::GetContentType() const
+{
+ switch (GetType())
+ {
+ case NODE_TYPE_GENRE:
+ return "genres";
+ case NODE_TYPE_SOURCE:
+ return "sources";
+ case NODE_TYPE_ROLE:
+ return "roles";
+ case NODE_TYPE_YEAR:
+ return "years";
+ default:
+ break;
+ }
+
+ return "";
+}
diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeGrouped.h b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeGrouped.h
new file mode 100644
index 0000000..38e7fd9
--- /dev/null
+++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeGrouped.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DirectoryNode.h"
+
+namespace XFILE
+{
+ namespace MUSICDATABASEDIRECTORY
+ {
+ class CDirectoryNodeGrouped : public CDirectoryNode
+ {
+ public:
+ CDirectoryNodeGrouped(NODE_TYPE type, const std::string& strName, CDirectoryNode* pParent);
+ protected:
+ NODE_TYPE GetChildType() const override;
+ bool GetContent(CFileItemList& items) const override;
+ std::string GetLocalizedName() const override;
+
+ private:
+ std::string GetContentType() const;
+ };
+ }
+}
+
+
diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeOverview.cpp b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeOverview.cpp
new file mode 100644
index 0000000..fefeb6d
--- /dev/null
+++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeOverview.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DirectoryNodeOverview.h"
+
+#include "FileItem.h"
+#include "guilib/LocalizeStrings.h"
+#include "music/MusicDatabase.h"
+#include "utils/StringUtils.h"
+
+namespace XFILE
+{
+ namespace MUSICDATABASEDIRECTORY
+ {
+ Node OverviewChildren[] = {
+ { NODE_TYPE_GENRE, "genres", 135 },
+ { NODE_TYPE_ARTIST, "artists", 133 },
+ { NODE_TYPE_ALBUM, "albums", 132 },
+ { NODE_TYPE_SINGLES, "singles", 1050 },
+ { NODE_TYPE_SONG, "songs", 134 },
+ { NODE_TYPE_YEAR, "years", 652 },
+ { NODE_TYPE_TOP100, "top100", 271 },
+ { NODE_TYPE_ALBUM_RECENTLY_ADDED, "recentlyaddedalbums", 359 },
+ { NODE_TYPE_ALBUM_RECENTLY_PLAYED, "recentlyplayedalbums", 517 },
+ { NODE_TYPE_ALBUM, "compilations", 521 },
+ { NODE_TYPE_ROLE, "roles", 38033 },
+ { NODE_TYPE_SOURCE, "sources", 39031 },
+ { NODE_TYPE_DISC, "discs", 14087 },
+ { NODE_TYPE_YEAR, "originalyears", 38078 },
+ };
+ };
+};
+
+using namespace XFILE::MUSICDATABASEDIRECTORY;
+
+CDirectoryNodeOverview::CDirectoryNodeOverview(const std::string& strName, CDirectoryNode* pParent)
+ : CDirectoryNode(NODE_TYPE_OVERVIEW, strName, pParent)
+{
+
+}
+
+NODE_TYPE CDirectoryNodeOverview::GetChildType() const
+{
+ for (const Node& node : OverviewChildren)
+ if (GetName() == node.id)
+ return node.node;
+ return NODE_TYPE_NONE;
+}
+
+std::string CDirectoryNodeOverview::GetLocalizedName() const
+{
+ for (const Node& node : OverviewChildren)
+ if (GetName() == node.id)
+ return g_localizeStrings.Get(node.label);
+ return "";
+}
+
+bool CDirectoryNodeOverview::GetContent(CFileItemList& items) const
+{
+ CMusicDatabase musicDatabase;
+ musicDatabase.Open();
+
+ bool hasSingles = (musicDatabase.GetSinglesCount() > 0);
+ bool hasCompilations = (musicDatabase.GetCompilationAlbumsCount() > 0);
+
+ for (unsigned int i = 0; i < sizeof(OverviewChildren) / sizeof(Node); ++i)
+ {
+ if (i == 3 && !hasSingles)
+ continue;
+ if (i == 9 && !hasCompilations)
+ continue;
+
+ CFileItemPtr pItem(new CFileItem(g_localizeStrings.Get(OverviewChildren[i].label)));
+ std::string strDir = StringUtils::Format("{}/", OverviewChildren[i].id);
+ pItem->SetPath(BuildPath() + strDir);
+ pItem->m_bIsFolder = true;
+ pItem->SetCanQueue(false);
+ items.Add(pItem);
+ }
+
+ return true;
+}
diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeOverview.h b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeOverview.h
new file mode 100644
index 0000000..057a9e8
--- /dev/null
+++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeOverview.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DirectoryNode.h"
+
+namespace XFILE
+{
+ namespace MUSICDATABASEDIRECTORY
+ {
+ class CDirectoryNodeOverview : public CDirectoryNode
+ {
+ public:
+ CDirectoryNodeOverview(const std::string& strName, CDirectoryNode* pParent);
+ protected:
+ NODE_TYPE GetChildType() const override;
+ bool GetContent(CFileItemList& items) const override;
+ std::string GetLocalizedName() const override;
+ };
+ }
+}
+
+
diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeRoot.cpp b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeRoot.cpp
new file mode 100644
index 0000000..31cc761
--- /dev/null
+++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeRoot.cpp
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DirectoryNodeRoot.h"
+
+using namespace XFILE::MUSICDATABASEDIRECTORY;
+
+CDirectoryNodeRoot::CDirectoryNodeRoot(const std::string& strName, CDirectoryNode* pParent)
+ : CDirectoryNode(NODE_TYPE_ROOT, strName, pParent)
+{
+
+}
+
+NODE_TYPE CDirectoryNodeRoot::GetChildType() const
+{
+ return NODE_TYPE_OVERVIEW;
+}
diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeRoot.h b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeRoot.h
new file mode 100644
index 0000000..c4128a3
--- /dev/null
+++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeRoot.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DirectoryNode.h"
+
+namespace XFILE
+{
+ namespace MUSICDATABASEDIRECTORY
+ {
+ class CDirectoryNodeRoot : public CDirectoryNode
+ {
+ public:
+ CDirectoryNodeRoot(const std::string& strName, CDirectoryNode* pParent);
+ protected:
+ NODE_TYPE GetChildType() const override;
+ };
+ }
+}
+
+
diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeSingles.cpp b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeSingles.cpp
new file mode 100644
index 0000000..8deae27
--- /dev/null
+++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeSingles.cpp
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DirectoryNodeSingles.h"
+
+#include "music/MusicDatabase.h"
+
+using namespace XFILE::MUSICDATABASEDIRECTORY;
+
+CDirectoryNodeSingles::CDirectoryNodeSingles(const std::string& strName, CDirectoryNode* pParent)
+ : CDirectoryNode(NODE_TYPE_SINGLES, strName, pParent)
+{
+
+}
+
+bool CDirectoryNodeSingles::GetContent(CFileItemList& items) const
+{
+ CMusicDatabase musicdatabase;
+ if (!musicdatabase.Open())
+ return false;
+
+ bool bSuccess = musicdatabase.GetSongsFullByWhere(BuildPath(), CDatabase::Filter(), items, SortDescription(), true);
+
+ musicdatabase.Close();
+
+ return bSuccess;
+}
diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeSingles.h b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeSingles.h
new file mode 100644
index 0000000..cc297b7
--- /dev/null
+++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeSingles.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DirectoryNode.h"
+
+namespace XFILE
+{
+ namespace MUSICDATABASEDIRECTORY
+ {
+ class CDirectoryNodeSingles : public CDirectoryNode
+ {
+ public:
+ CDirectoryNodeSingles(const std::string& strName, CDirectoryNode* pParent);
+ protected:
+ bool GetContent(CFileItemList& items) const override;
+ };
+ }
+}
+
+
diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeSong.cpp b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeSong.cpp
new file mode 100644
index 0000000..4c43f90
--- /dev/null
+++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeSong.cpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DirectoryNodeSong.h"
+
+#include "QueryParams.h"
+#include "music/MusicDatabase.h"
+
+using namespace XFILE::MUSICDATABASEDIRECTORY;
+
+CDirectoryNodeSong::CDirectoryNodeSong(const std::string& strName, CDirectoryNode* pParent)
+ : CDirectoryNode(NODE_TYPE_SONG, strName, pParent)
+{
+
+}
+
+bool CDirectoryNodeSong::GetContent(CFileItemList& items) const
+{
+ CMusicDatabase musicdatabase;
+ if (!musicdatabase.Open())
+ return false;
+
+ CQueryParams params;
+ CollectQueryParams(params);
+
+ std::string strBaseDir=BuildPath();
+ bool bSuccess=musicdatabase.GetSongsNav(strBaseDir, items, params.GetGenreId(), params.GetArtistId(), params.GetAlbumId());
+
+ musicdatabase.Close();
+
+ return bSuccess;
+}
diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeSong.h b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeSong.h
new file mode 100644
index 0000000..668014b
--- /dev/null
+++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeSong.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DirectoryNode.h"
+
+namespace XFILE
+{
+ namespace MUSICDATABASEDIRECTORY
+ {
+ class CDirectoryNodeSong : public CDirectoryNode
+ {
+ public:
+ CDirectoryNodeSong(const std::string& strEntryName, CDirectoryNode* pParent);
+ protected:
+ bool GetContent(CFileItemList& items) const override;
+ };
+ }
+}
+
+
diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeSongTop100.cpp b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeSongTop100.cpp
new file mode 100644
index 0000000..a03a8ff
--- /dev/null
+++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeSongTop100.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DirectoryNodeSongTop100.h"
+
+#include "music/MusicDatabase.h"
+
+using namespace XFILE::MUSICDATABASEDIRECTORY;
+
+CDirectoryNodeSongTop100::CDirectoryNodeSongTop100(const std::string& strName, CDirectoryNode* pParent)
+ : CDirectoryNode(NODE_TYPE_SONG_TOP100, strName, pParent)
+{
+
+}
+
+bool CDirectoryNodeSongTop100::GetContent(CFileItemList& items) const
+{
+ CMusicDatabase musicdatabase;
+ if (!musicdatabase.Open())
+ return false;
+
+ std::string strBaseDir=BuildPath();
+ bool bSuccess=musicdatabase.GetTop100(strBaseDir, items);
+
+ musicdatabase.Close();
+
+ return bSuccess;
+}
diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeSongTop100.h b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeSongTop100.h
new file mode 100644
index 0000000..ca34a72
--- /dev/null
+++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeSongTop100.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DirectoryNode.h"
+
+namespace XFILE
+{
+ namespace MUSICDATABASEDIRECTORY
+ {
+ class CDirectoryNodeSongTop100 : public CDirectoryNode
+ {
+ public:
+ CDirectoryNodeSongTop100(const std::string& strName, CDirectoryNode* pParent);
+ protected:
+ bool GetContent(CFileItemList& items) const override;
+ };
+ }
+}
+
+
diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeTop100.cpp b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeTop100.cpp
new file mode 100644
index 0000000..fff9228
--- /dev/null
+++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeTop100.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DirectoryNodeTop100.h"
+
+#include "FileItem.h"
+#include "guilib/LocalizeStrings.h"
+#include "utils/StringUtils.h"
+
+using namespace XFILE::MUSICDATABASEDIRECTORY;
+
+Node Top100Children[] = {
+ { NODE_TYPE_SONG_TOP100, "songs", 10504 },
+ { NODE_TYPE_ALBUM_TOP100, "albums", 10505 },
+ };
+
+CDirectoryNodeTop100::CDirectoryNodeTop100(const std::string& strName, CDirectoryNode* pParent)
+ : CDirectoryNode(NODE_TYPE_TOP100, strName, pParent)
+{
+
+}
+
+NODE_TYPE CDirectoryNodeTop100::GetChildType() const
+{
+ for (const Node& node : Top100Children)
+ if (GetName() == node.id)
+ return node.node;
+
+ return NODE_TYPE_NONE;
+}
+
+std::string CDirectoryNodeTop100::GetLocalizedName() const
+{
+ for (const Node& node : Top100Children)
+ if (GetName() == node.id)
+ return g_localizeStrings.Get(node.label);
+ return "";
+}
+
+bool CDirectoryNodeTop100::GetContent(CFileItemList& items) const
+{
+ for (const Node& node : Top100Children)
+ {
+ CFileItemPtr pItem(new CFileItem(g_localizeStrings.Get(node.label)));
+ std::string strDir = StringUtils::Format("{}/", node.id);
+ pItem->SetPath(BuildPath() + strDir);
+ pItem->m_bIsFolder = true;
+ items.Add(pItem);
+ }
+
+ return true;
+}
diff --git a/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeTop100.h b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeTop100.h
new file mode 100644
index 0000000..bf5068f
--- /dev/null
+++ b/xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeTop100.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DirectoryNode.h"
+
+namespace XFILE
+{
+ namespace MUSICDATABASEDIRECTORY
+ {
+ class CDirectoryNodeTop100 : public CDirectoryNode
+ {
+ public:
+ CDirectoryNodeTop100(const std::string& strName, CDirectoryNode* pParent);
+ protected:
+ NODE_TYPE GetChildType() const override;
+ bool GetContent(CFileItemList& items) const override;
+ std::string GetLocalizedName() const override;
+ };
+ }
+}
+
+
diff --git a/xbmc/filesystem/MusicDatabaseDirectory/QueryParams.cpp b/xbmc/filesystem/MusicDatabaseDirectory/QueryParams.cpp
new file mode 100644
index 0000000..3353631
--- /dev/null
+++ b/xbmc/filesystem/MusicDatabaseDirectory/QueryParams.cpp
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "QueryParams.h"
+
+#include <stdlib.h>
+
+using namespace XFILE::MUSICDATABASEDIRECTORY;
+
+CQueryParams::CQueryParams()
+{
+ m_idArtist=-1;
+ m_idAlbum=-1;
+ m_idGenre=-1;
+ m_idSong=-1;
+ m_year=-1;
+ m_disc = -1;
+}
+
+void CQueryParams::SetQueryParam(NODE_TYPE NodeType, const std::string& strNodeName)
+{
+ int idDb = atoi(strNodeName.c_str());
+
+ switch (NodeType)
+ {
+ case NODE_TYPE_GENRE:
+ m_idGenre=idDb;
+ break;
+ case NODE_TYPE_YEAR:
+ m_year=idDb;
+ break;
+ case NODE_TYPE_ARTIST:
+ m_idArtist=idDb;
+ break;
+ case NODE_TYPE_DISC:
+ m_disc = idDb;
+ break;
+ case NODE_TYPE_ALBUM_RECENTLY_PLAYED:
+ case NODE_TYPE_ALBUM_RECENTLY_ADDED:
+ case NODE_TYPE_ALBUM_TOP100:
+ case NODE_TYPE_ALBUM:
+ m_idAlbum=idDb;
+ break;
+ case NODE_TYPE_ALBUM_RECENTLY_ADDED_SONGS:
+ case NODE_TYPE_ALBUM_RECENTLY_PLAYED_SONGS:
+ case NODE_TYPE_ALBUM_TOP100_SONGS:
+ case NODE_TYPE_SONG:
+ case NODE_TYPE_SONG_TOP100:
+ m_idSong=idDb;
+ default:
+ break;
+ }
+}
diff --git a/xbmc/filesystem/MusicDatabaseDirectory/QueryParams.h b/xbmc/filesystem/MusicDatabaseDirectory/QueryParams.h
new file mode 100644
index 0000000..abfbdea
--- /dev/null
+++ b/xbmc/filesystem/MusicDatabaseDirectory/QueryParams.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DirectoryNode.h"
+
+namespace XFILE
+{
+ namespace MUSICDATABASEDIRECTORY
+ {
+ class CQueryParams
+ {
+ public:
+ CQueryParams();
+ int GetArtistId() { return m_idArtist; }
+ int GetAlbumId() { return m_idAlbum; }
+ int GetGenreId() { return m_idGenre; }
+ int GetSongId() { return m_idSong; }
+ int GetYear() { return m_year; }
+ int GetDisc() { return m_disc; }
+
+ protected:
+ void SetQueryParam(NODE_TYPE NodeType, const std::string& strNodeName);
+
+ friend class CDirectoryNode;
+ private:
+ int m_idArtist;
+ int m_idAlbum;
+ int m_idGenre;
+ int m_idSong;
+ int m_year;
+ int m_disc;
+ };
+ }
+}
+
+
diff --git a/xbmc/filesystem/MusicDatabaseFile.cpp b/xbmc/filesystem/MusicDatabaseFile.cpp
new file mode 100644
index 0000000..0e41ad4
--- /dev/null
+++ b/xbmc/filesystem/MusicDatabaseFile.cpp
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "MusicDatabaseFile.h"
+
+#include "URL.h"
+#include "music/MusicDatabase.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+
+using namespace XFILE;
+
+CMusicDatabaseFile::CMusicDatabaseFile(void) = default;
+
+CMusicDatabaseFile::~CMusicDatabaseFile(void)
+{
+ Close();
+}
+
+std::string CMusicDatabaseFile::TranslateUrl(const CURL& url)
+{
+ CMusicDatabase musicDatabase;
+ if (!musicDatabase.Open())
+ return "";
+
+ std::string strFileName=URIUtils::GetFileName(url.Get());
+ std::string strExtension = URIUtils::GetExtension(strFileName);
+ URIUtils::RemoveExtension(strFileName);
+
+ if (!StringUtils::IsNaturalNumber(strFileName))
+ return "";
+
+ int idSong = atoi(strFileName.c_str());
+
+ CSong song;
+ if (!musicDatabase.GetSong(idSong, song))
+ return "";
+
+ StringUtils::ToLower(strExtension);
+ if (!URIUtils::HasExtension(song.strFileName, strExtension))
+ return "";
+
+ return song.strFileName;
+}
+
+bool CMusicDatabaseFile::Open(const CURL& url)
+{
+ return m_file.Open(TranslateUrl(url));
+}
+
+bool CMusicDatabaseFile::Exists(const CURL& url)
+{
+ return !TranslateUrl(url).empty();
+}
+
+int CMusicDatabaseFile::Stat(const CURL& url, struct __stat64* buffer)
+{
+ return m_file.Stat(TranslateUrl(url), buffer);
+}
+
+ssize_t CMusicDatabaseFile::Read(void* lpBuf, size_t uiBufSize)
+{
+ return m_file.Read(lpBuf, uiBufSize);
+}
+
+int64_t CMusicDatabaseFile::Seek(int64_t iFilePosition, int iWhence /*=SEEK_SET*/)
+{
+ return m_file.Seek(iFilePosition, iWhence);
+}
+
+void CMusicDatabaseFile::Close()
+{
+ m_file.Close();
+}
+
+int64_t CMusicDatabaseFile::GetPosition()
+{
+ return m_file.GetPosition();
+}
+
+int64_t CMusicDatabaseFile::GetLength()
+{
+ return m_file.GetLength();
+}
+
diff --git a/xbmc/filesystem/MusicDatabaseFile.h b/xbmc/filesystem/MusicDatabaseFile.h
new file mode 100644
index 0000000..1e259f0
--- /dev/null
+++ b/xbmc/filesystem/MusicDatabaseFile.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "File.h"
+#include "IFile.h"
+
+namespace XFILE
+{
+class CMusicDatabaseFile : public IFile
+{
+public:
+ CMusicDatabaseFile(void);
+ ~CMusicDatabaseFile(void) override;
+ bool Open(const CURL& url) override;
+ bool Exists(const CURL& url) override;
+ int Stat(const CURL& url, struct __stat64* buffer) override;
+
+ ssize_t Read(void* lpBuf, size_t uiBufSize) override;
+ int64_t Seek(int64_t iFilePosition, int iWhence = SEEK_SET) override;
+ void Close() override;
+ int64_t GetPosition() override;
+ int64_t GetLength() override;
+
+ static std::string TranslateUrl(const CURL& url);
+protected:
+ CFile m_file;
+};
+}
diff --git a/xbmc/filesystem/MusicFileDirectory.cpp b/xbmc/filesystem/MusicFileDirectory.cpp
new file mode 100644
index 0000000..b3c7749
--- /dev/null
+++ b/xbmc/filesystem/MusicFileDirectory.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "MusicFileDirectory.h"
+
+#include "FileItem.h"
+#include "URL.h"
+#include "guilib/LocalizeStrings.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+
+using namespace MUSIC_INFO;
+using namespace XFILE;
+
+CMusicFileDirectory::CMusicFileDirectory(void) = default;
+
+CMusicFileDirectory::~CMusicFileDirectory(void) = default;
+
+bool CMusicFileDirectory::GetDirectory(const CURL& url, CFileItemList &items)
+{
+ std::string strPath=url.Get();
+
+ std::string strFileName;
+ strFileName = URIUtils::GetFileName(strPath);
+ URIUtils::RemoveExtension(strFileName);
+
+ int iStreams = GetTrackCount(strPath);
+
+ URIUtils::AddSlashAtEnd(strPath);
+
+ for (int i=0; i<iStreams; ++i)
+ {
+ std::string strLabel =
+ StringUtils::Format("{} - {} {:02}", strFileName, g_localizeStrings.Get(554), i + 1);
+ CFileItemPtr pItem(new CFileItem(strLabel));
+ strLabel = StringUtils::Format("{}{}-{}.{}", strPath, strFileName, i + 1, m_strExt);
+ pItem->SetPath(strLabel);
+
+ /*
+ * Try fist to load tag about related stream track. If them fails or not
+ * available, take base tag for all streams (in this case the item names
+ * are all the same).
+ */
+ MUSIC_INFO::CMusicInfoTag tag;
+ if (Load(strLabel, tag, nullptr))
+ *pItem->GetMusicInfoTag() = tag;
+ else if (m_tag.Loaded())
+ *pItem->GetMusicInfoTag() = m_tag;
+
+ /*
+ * Check track number not set and take stream entry number about.
+ * NOTE: Audio decoder addons can also give a own track number.
+ */
+ if (pItem->GetMusicInfoTag()->GetTrackNumber() == 0)
+ pItem->GetMusicInfoTag()->SetTrackNumber(i+1);
+ items.Add(pItem);
+ }
+
+ return true;
+}
+
+bool CMusicFileDirectory::Exists(const CURL& url)
+{
+ return true;
+}
+
+bool CMusicFileDirectory::ContainsFiles(const CURL &url)
+{
+ const std::string pathToUrl(url.Get());
+ if (GetTrackCount(pathToUrl) > 1)
+ return true;
+
+ return false;
+}
diff --git a/xbmc/filesystem/MusicFileDirectory.h b/xbmc/filesystem/MusicFileDirectory.h
new file mode 100644
index 0000000..283e40e
--- /dev/null
+++ b/xbmc/filesystem/MusicFileDirectory.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IFileDirectory.h"
+#include "music/tags/MusicInfoTag.h"
+
+namespace XFILE
+{
+ class CMusicFileDirectory : public IFileDirectory
+ {
+ public:
+ CMusicFileDirectory(void);
+ ~CMusicFileDirectory(void) override;
+ bool GetDirectory(const CURL& url, CFileItemList &items) override;
+ bool Exists(const CURL& url) override;
+ bool ContainsFiles(const CURL& url) override;
+ bool AllowAll() const override { return true; }
+ protected:
+ virtual bool Load(const std::string& strFileName,
+ MUSIC_INFO::CMusicInfoTag& tag,
+ EmbeddedArt* art = nullptr) { return false; }
+ virtual int GetTrackCount(const std::string& strPath) = 0;
+ std::string m_strExt;
+ MUSIC_INFO::CMusicInfoTag m_tag;
+ };
+}
diff --git a/xbmc/filesystem/MusicSearchDirectory.cpp b/xbmc/filesystem/MusicSearchDirectory.cpp
new file mode 100644
index 0000000..1d1868a
--- /dev/null
+++ b/xbmc/filesystem/MusicSearchDirectory.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "MusicSearchDirectory.h"
+
+#include "FileItem.h"
+#include "URL.h"
+#include "guilib/LocalizeStrings.h"
+#include "music/MusicDatabase.h"
+#include "utils/log.h"
+
+using namespace XFILE;
+
+CMusicSearchDirectory::CMusicSearchDirectory(void) = default;
+
+CMusicSearchDirectory::~CMusicSearchDirectory(void) = default;
+
+bool CMusicSearchDirectory::GetDirectory(const CURL& url, CFileItemList &items)
+{
+ // break up our path
+ // format is: musicsearch://<url encoded search string>
+ const std::string& search(url.GetHostName());
+
+ if (search.empty())
+ return false;
+
+ // and retrieve the search details
+ items.SetURL(url);
+ auto start = std::chrono::steady_clock::now();
+ CMusicDatabase db;
+ db.Open();
+ db.Search(search, items);
+ db.Close();
+
+ auto end = std::chrono::steady_clock::now();
+ auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
+
+ CLog::Log(LOGDEBUG, "{} ({}) took {} ms", __FUNCTION__, url.GetRedacted(), duration.count());
+
+ items.SetLabel(g_localizeStrings.Get(137)); // Search
+ return true;
+}
+
+bool CMusicSearchDirectory::Exists(const CURL& url)
+{
+ return true;
+}
diff --git a/xbmc/filesystem/MusicSearchDirectory.h b/xbmc/filesystem/MusicSearchDirectory.h
new file mode 100644
index 0000000..67e51f3
--- /dev/null
+++ b/xbmc/filesystem/MusicSearchDirectory.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IDirectory.h"
+
+namespace XFILE
+{
+ class CMusicSearchDirectory : public IDirectory
+ {
+ public:
+ CMusicSearchDirectory(void);
+ ~CMusicSearchDirectory(void) override;
+ bool GetDirectory(const CURL& url, CFileItemList &items) override;
+ bool Exists(const CURL& url) override;
+ bool AllowAll() const override { return true; }
+ };
+}
diff --git a/xbmc/filesystem/NFSDirectory.cpp b/xbmc/filesystem/NFSDirectory.cpp
new file mode 100644
index 0000000..7feba53
--- /dev/null
+++ b/xbmc/filesystem/NFSDirectory.cpp
@@ -0,0 +1,382 @@
+/*
+ * Copyright (C) 2011-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#ifdef TARGET_WINDOWS
+#include <mutex>
+
+#include <sys\stat.h>
+#endif
+
+#include "FileItem.h"
+#include "NFSDirectory.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/XTimeUtils.h"
+#include "utils/log.h"
+
+#ifdef TARGET_WINDOWS
+#include <sys\stat.h>
+#endif
+
+using namespace XFILE;
+#include <limits.h>
+#include <nfsc/libnfs.h>
+#include <nfsc/libnfs-raw-nfs.h>
+
+#if defined(TARGET_WINDOWS)
+#define S_IFLNK 0120000
+#define S_ISBLK(m) (0)
+#define S_ISSOCK(m) (0)
+#define S_ISLNK(m) ((m & S_IFLNK) != 0)
+#define S_ISCHR(m) ((m & _S_IFCHR) != 0)
+#define S_ISDIR(m) ((m & _S_IFDIR) != 0)
+#define S_ISFIFO(m) ((m & _S_IFIFO) != 0)
+#define S_ISREG(m) ((m & _S_IFREG) != 0)
+#endif
+
+CNFSDirectory::CNFSDirectory(void)
+{
+ gNfsConnection.AddActiveConnection();
+}
+
+CNFSDirectory::~CNFSDirectory(void)
+{
+ gNfsConnection.AddIdleConnection();
+}
+
+bool CNFSDirectory::GetDirectoryFromExportList(const std::string& strPath, CFileItemList &items)
+{
+ CURL url(strPath);
+ std::string nonConstStrPath(strPath);
+ std::list<std::string> exportList=gNfsConnection.GetExportList(url);
+
+ for (const std::string& it : exportList)
+ {
+ const std::string& currentExport(it);
+ URIUtils::RemoveSlashAtEnd(nonConstStrPath);
+
+ CFileItemPtr pItem(new CFileItem(currentExport));
+ std::string path(nonConstStrPath + currentExport);
+ URIUtils::AddSlashAtEnd(path);
+ pItem->SetPath(path);
+ pItem->m_dateTime = 0;
+
+ pItem->m_bIsFolder = true;
+ items.Add(pItem);
+ }
+
+ return exportList.empty() ? false : true;
+}
+
+bool CNFSDirectory::GetServerList(CFileItemList &items)
+{
+ struct nfs_server_list *srvrs;
+ struct nfs_server_list *srv;
+ bool ret = false;
+
+ srvrs = nfs_find_local_servers();
+
+ for (srv=srvrs; srv; srv = srv->next)
+ {
+ std::string currentExport(srv->addr);
+
+ CFileItemPtr pItem(new CFileItem(currentExport));
+ std::string path("nfs://" + currentExport);
+ URIUtils::AddSlashAtEnd(path);
+ pItem->m_dateTime=0;
+
+ pItem->SetPath(path);
+ pItem->m_bIsFolder = true;
+ items.Add(pItem);
+ ret = true; //added at least one entry
+ }
+ free_nfs_srvr_list(srvrs);
+
+ return ret;
+}
+
+bool CNFSDirectory::ResolveSymlink( const std::string &dirName, struct nfsdirent *dirent, CURL &resolvedUrl)
+{
+ std::unique_lock<CCriticalSection> lock(gNfsConnection);
+ int ret = 0;
+ bool retVal = true;
+ std::string fullpath = dirName;
+ char resolvedLink[MAX_PATH];
+
+ URIUtils::AddSlashAtEnd(fullpath);
+ fullpath.append(dirent->name);
+
+ resolvedUrl.Reset();
+ resolvedUrl.SetPort(2049);
+ resolvedUrl.SetProtocol("nfs");
+ resolvedUrl.SetHostName(gNfsConnection.GetConnectedIp());
+
+ ret = nfs_readlink(gNfsConnection.GetNfsContext(), fullpath.c_str(), resolvedLink, MAX_PATH);
+
+ if(ret == 0)
+ {
+ nfs_stat_64 tmpBuffer = {};
+ fullpath = dirName;
+ URIUtils::AddSlashAtEnd(fullpath);
+ fullpath.append(resolvedLink);
+
+ //special case - if link target is absolute it could be even another export
+ //intervolume symlinks baby ...
+ if(resolvedLink[0] == '/')
+ {
+ //use the special stat function for using an extra context
+ //because we are inside of a dir traversal
+ //and just can't change the global nfs context here
+ //without destroying something...
+ fullpath = resolvedLink;
+ resolvedUrl.SetFileName(fullpath);
+ ret = gNfsConnection.stat(resolvedUrl, &tmpBuffer);
+ }
+ else
+ {
+ ret = nfs_stat64(gNfsConnection.GetNfsContext(), fullpath.c_str(), &tmpBuffer);
+ resolvedUrl.SetFileName(gNfsConnection.GetConnectedExport() + fullpath);
+ }
+
+ if (ret != 0)
+ {
+ CLog::Log(LOGERROR, "NFS: Failed to stat({}) on link resolve {}", fullpath,
+ nfs_get_error(gNfsConnection.GetNfsContext()));
+ retVal = false;
+ }
+ else
+ {
+ dirent->inode = tmpBuffer.nfs_ino;
+ dirent->mode = tmpBuffer.nfs_mode;
+ dirent->size = tmpBuffer.nfs_size;
+ dirent->atime.tv_sec = tmpBuffer.nfs_atime;
+ dirent->mtime.tv_sec = tmpBuffer.nfs_mtime;
+ dirent->ctime.tv_sec = tmpBuffer.nfs_ctime;
+
+ //map stat mode to nf3type
+ if (S_ISBLK(tmpBuffer.nfs_mode))
+ {
+ dirent->type = NF3BLK;
+ }
+ else if (S_ISCHR(tmpBuffer.nfs_mode))
+ {
+ dirent->type = NF3CHR;
+ }
+ else if (S_ISDIR(tmpBuffer.nfs_mode))
+ {
+ dirent->type = NF3DIR;
+ }
+ else if (S_ISFIFO(tmpBuffer.nfs_mode))
+ {
+ dirent->type = NF3FIFO;
+ }
+ else if (S_ISREG(tmpBuffer.nfs_mode))
+ {
+ dirent->type = NF3REG;
+ }
+ else if (S_ISLNK(tmpBuffer.nfs_mode))
+ {
+ dirent->type = NF3LNK;
+ }
+ else if (S_ISSOCK(tmpBuffer.nfs_mode))
+ {
+ dirent->type = NF3SOCK;
+ }
+ }
+ }
+ else
+ {
+ CLog::Log(LOGERROR, "Failed to readlink({}) {}", fullpath,
+ nfs_get_error(gNfsConnection.GetNfsContext()));
+ retVal = false;
+ }
+ return retVal;
+}
+
+bool CNFSDirectory::GetDirectory(const CURL& url, CFileItemList &items)
+{
+ // We accept nfs://server/path[/file]]]]
+ int ret = 0;
+ KODI::TIME::FileTime fileTime, localTime;
+ std::unique_lock<CCriticalSection> lock(gNfsConnection);
+ std::string strDirName="";
+ std::string myStrPath(url.Get());
+ URIUtils::AddSlashAtEnd(myStrPath); //be sure the dir ends with a slash
+
+ if(!gNfsConnection.Connect(url,strDirName))
+ {
+ //connect has failed - so try to get the exported filesystems if no path is given to the url
+ if(url.GetShareName().empty())
+ {
+ if(url.GetHostName().empty())
+ {
+ return GetServerList(items);
+ }
+ else
+ {
+ return GetDirectoryFromExportList(myStrPath, items);
+ }
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ struct nfsdir *nfsdir = NULL;
+ struct nfsdirent *nfsdirent = NULL;
+
+ ret = nfs_opendir(gNfsConnection.GetNfsContext(), strDirName.c_str(), &nfsdir);
+
+ if(ret != 0)
+ {
+ CLog::Log(LOGERROR, "Failed to open({}) {}", strDirName,
+ nfs_get_error(gNfsConnection.GetNfsContext()));
+ return false;
+ }
+ lock.unlock();
+
+ while((nfsdirent = nfs_readdir(gNfsConnection.GetNfsContext(), nfsdir)) != NULL)
+ {
+ struct nfsdirent tmpDirent = *nfsdirent;
+ std::string strName = tmpDirent.name;
+ std::string path(myStrPath + strName);
+ int64_t iSize = 0;
+ bool bIsDir = false;
+ int64_t lTimeDate = 0;
+
+ //reslove symlinks
+ if(tmpDirent.type == NF3LNK)
+ {
+ CURL linkUrl;
+ //resolve symlink changes tmpDirent and strName
+ if(!ResolveSymlink(strDirName,&tmpDirent,linkUrl))
+ {
+ continue;
+ }
+
+ path = linkUrl.Get();
+ }
+
+ iSize = tmpDirent.size;
+ bIsDir = tmpDirent.type == NF3DIR;
+ lTimeDate = tmpDirent.mtime.tv_sec;
+
+ if (!StringUtils::EqualsNoCase(strName,".") && !StringUtils::EqualsNoCase(strName,"..")
+ && !StringUtils::EqualsNoCase(strName,"lost+found"))
+ {
+ if(lTimeDate == 0) // if modification date is missing, use create date
+ {
+ lTimeDate = tmpDirent.ctime.tv_sec;
+ }
+
+ long long ll = lTimeDate & 0xffffffff;
+ ll *= 10000000ll;
+ ll += 116444736000000000ll;
+ fileTime.lowDateTime = (DWORD)(ll & 0xffffffff);
+ fileTime.highDateTime = (DWORD)(ll >> 32);
+ KODI::TIME::FileTimeToLocalFileTime(&fileTime, &localTime);
+
+ CFileItemPtr pItem(new CFileItem(tmpDirent.name));
+ pItem->m_dateTime=localTime;
+ pItem->m_dwSize = iSize;
+
+ if (bIsDir)
+ {
+ URIUtils::AddSlashAtEnd(path);
+ pItem->m_bIsFolder = true;
+ }
+ else
+ {
+ pItem->m_bIsFolder = false;
+ }
+
+ if (strName[0] == '.')
+ {
+ pItem->SetProperty("file:hidden", true);
+ }
+ pItem->SetPath(path);
+ items.Add(pItem);
+ }
+ }
+
+ lock.lock();
+ nfs_closedir(gNfsConnection.GetNfsContext(), nfsdir);//close the dir
+ lock.unlock();
+ return true;
+}
+
+bool CNFSDirectory::Create(const CURL& url2)
+{
+ int ret = 0;
+ bool success=true;
+
+ std::unique_lock<CCriticalSection> lock(gNfsConnection);
+ std::string folderName(url2.Get());
+ URIUtils::RemoveSlashAtEnd(folderName);//mkdir fails if a slash is at the end!!!
+ CURL url(folderName);
+ folderName = "";
+
+ if(!gNfsConnection.Connect(url,folderName))
+ return false;
+
+ ret = nfs_mkdir(gNfsConnection.GetNfsContext(), folderName.c_str());
+
+ success = (ret == 0 || -EEXIST == ret);
+ if(!success)
+ CLog::Log(LOGERROR, "NFS: Failed to create({}) {}", folderName,
+ nfs_get_error(gNfsConnection.GetNfsContext()));
+ return success;
+}
+
+bool CNFSDirectory::Remove(const CURL& url2)
+{
+ int ret = 0;
+
+ std::unique_lock<CCriticalSection> lock(gNfsConnection);
+ std::string folderName(url2.Get());
+ URIUtils::RemoveSlashAtEnd(folderName);//rmdir fails if a slash is at the end!!!
+ CURL url(folderName);
+ folderName = "";
+
+ if(!gNfsConnection.Connect(url,folderName))
+ return false;
+
+ ret = nfs_rmdir(gNfsConnection.GetNfsContext(), folderName.c_str());
+
+ if(ret != 0 && errno != ENOENT)
+ {
+ CLog::Log(LOGERROR, "{} - Error( {} )", __FUNCTION__,
+ nfs_get_error(gNfsConnection.GetNfsContext()));
+ return false;
+ }
+ return true;
+}
+
+bool CNFSDirectory::Exists(const CURL& url2)
+{
+ int ret = 0;
+
+ std::unique_lock<CCriticalSection> lock(gNfsConnection);
+ std::string folderName(url2.Get());
+ URIUtils::RemoveSlashAtEnd(folderName);//remove slash at end or URIUtils::GetFileName won't return what we want...
+ CURL url(folderName);
+ folderName = "";
+
+ if(!gNfsConnection.Connect(url,folderName))
+ return false;
+
+ nfs_stat_64 info;
+ ret = nfs_stat64(gNfsConnection.GetNfsContext(), folderName.c_str(), &info);
+
+ if (ret != 0)
+ {
+ return false;
+ }
+ return S_ISDIR(info.nfs_mode) ? true : false;
+}
diff --git a/xbmc/filesystem/NFSDirectory.h b/xbmc/filesystem/NFSDirectory.h
new file mode 100644
index 0000000..684ad8c
--- /dev/null
+++ b/xbmc/filesystem/NFSDirectory.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2011-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IDirectory.h"
+#include "NFSFile.h"
+
+struct nfsdirent;
+
+namespace XFILE
+{
+ class CNFSDirectory : public IDirectory
+ {
+ public:
+ CNFSDirectory(void);
+ ~CNFSDirectory(void) override;
+ bool GetDirectory(const CURL& url, CFileItemList &items) override;
+ DIR_CACHE_TYPE GetCacheType(const CURL& url) const override { return DIR_CACHE_ONCE; }
+ bool Create(const CURL& url) override;
+ bool Exists(const CURL& url) override;
+ bool Remove(const CURL& url) override;
+ private:
+ bool GetServerList(CFileItemList &items);
+ bool GetDirectoryFromExportList(const std::string& strPath, CFileItemList &items);
+ bool ResolveSymlink( const std::string &dirName, struct nfsdirent *dirent, CURL &resolvedUrl);
+ };
+}
+
diff --git a/xbmc/filesystem/NFSFile.cpp b/xbmc/filesystem/NFSFile.cpp
new file mode 100644
index 0000000..5e0c148
--- /dev/null
+++ b/xbmc/filesystem/NFSFile.cpp
@@ -0,0 +1,962 @@
+/*
+ * Copyright (C) 2011-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+// FileNFS.cpp: implementation of the CNFSFile class.
+//
+//////////////////////////////////////////////////////////////////////
+
+#include "NFSFile.h"
+
+#include "ServiceBroker.h"
+#include "network/DNSNameCache.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+
+#include <inttypes.h>
+#include <mutex>
+
+#include <nfsc/libnfs-raw-mount.h>
+#include <nfsc/libnfs.h>
+
+#ifdef TARGET_WINDOWS
+#include <fcntl.h>
+#include <sys\stat.h>
+#endif
+
+#if defined(TARGET_WINDOWS)
+#define S_IRGRP 0
+#define S_IROTH 0
+#define S_IWUSR _S_IWRITE
+#define S_IRUSR _S_IREAD
+#endif
+
+using namespace XFILE;
+
+using namespace std::chrono_literals;
+
+namespace
+{
+// Default "lease_time" on most Linux NFSv4 servers are 90s.
+// See: https://linux-nfs.org/wiki/index.php/NFS_lock_recovery_notes
+// Keep alive interval should be always less than lease_time to avoid client session expires
+
+constexpr auto CONTEXT_TIMEOUT = 60s; // 2/3 parts of lease_time
+constexpr auto KEEP_ALIVE_TIMEOUT = 45s; // half of lease_time
+constexpr auto IDLE_TIMEOUT = 30s; // close fast unused contexts when no active connections
+
+constexpr int NFS4ERR_EXPIRED = -11; // client session expired due idle time greater than lease_time
+
+constexpr auto SETTING_NFS_VERSION = "nfs.version";
+} // unnamed namespace
+
+CNfsConnection::CNfsConnection()
+ : m_pNfsContext(NULL),
+ m_exportPath(""),
+ m_hostName(""),
+ m_resolvedHostName(""),
+ m_IdleTimeout(std::chrono::steady_clock::now() + IDLE_TIMEOUT)
+{
+}
+
+CNfsConnection::~CNfsConnection()
+{
+ Deinit();
+}
+
+void CNfsConnection::resolveHost(const CURL& url)
+{
+ // resolve if hostname has changed
+ CDNSNameCache::Lookup(url.GetHostName(), m_resolvedHostName);
+}
+
+std::list<std::string> CNfsConnection::GetExportList(const CURL& url)
+{
+ std::list<std::string> retList;
+
+ struct exportnode *exportlist, *tmp;
+#ifdef HAS_NFS_MOUNT_GETEXPORTS_TIMEOUT
+ exportlist = mount_getexports_timeout(
+ m_resolvedHostName.c_str(),
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_nfsTimeout * 1000);
+#else
+ exportlist = mount_getexports(m_resolvedHostName.c_str());
+#endif
+
+ for (tmp = exportlist; tmp != NULL; tmp = tmp->ex_next)
+ {
+ std::string exportStr = std::string(tmp->ex_dir);
+
+ retList.push_back(exportStr);
+ }
+
+ mount_free_export_list(exportlist);
+ retList.sort();
+ retList.reverse();
+
+ return retList;
+}
+
+void CNfsConnection::clearMembers()
+{
+ // NOTE - DON'T CLEAR m_exportList HERE!
+ // splitUrlIntoExportAndPath checks for m_exportList.empty()
+ // and would query the server in an excessive unwanted fashion
+ // also don't clear m_KeepAliveTimeouts here because we
+ // would loose any "paused" file handles during export change
+ m_exportPath.clear();
+ m_hostName.clear();
+ m_writeChunkSize = 0;
+ m_readChunkSize = 0;
+ m_pNfsContext = NULL;
+}
+
+void CNfsConnection::destroyOpenContexts()
+{
+ std::unique_lock<CCriticalSection> lock(openContextLock);
+ for (auto& it : m_openContextMap)
+ {
+ nfs_destroy_context(it.second.pContext);
+ }
+ m_openContextMap.clear();
+}
+
+void CNfsConnection::destroyContext(const std::string &exportName)
+{
+ std::unique_lock<CCriticalSection> lock(openContextLock);
+ tOpenContextMap::iterator it = m_openContextMap.find(exportName.c_str());
+ if (it != m_openContextMap.end())
+ {
+ nfs_destroy_context(it->second.pContext);
+ m_openContextMap.erase(it);
+ }
+}
+
+struct nfs_context *CNfsConnection::getContextFromMap(const std::string &exportname, bool forceCacheHit/* = false*/)
+{
+ struct nfs_context *pRet = NULL;
+ std::unique_lock<CCriticalSection> lock(openContextLock);
+
+ tOpenContextMap::iterator it = m_openContextMap.find(exportname.c_str());
+ if (it != m_openContextMap.end())
+ {
+ //check if context has timed out already
+ auto now = std::chrono::steady_clock::now();
+ auto duration =
+ std::chrono::duration_cast<std::chrono::milliseconds>(now - it->second.lastAccessedTime);
+ if (duration < CONTEXT_TIMEOUT || forceCacheHit)
+ {
+ //its not timedout yet or caller wants the cached entry regardless of timeout
+ //refresh access time of that
+ //context and return it
+ if (!forceCacheHit) // only log it if this isn't the resetkeepalive on each read ;)
+ CLog::Log(LOGDEBUG, "NFS: Refreshing context for {}, old: {}, new: {}", exportname,
+ it->second.lastAccessedTime.time_since_epoch().count(),
+ now.time_since_epoch().count());
+ it->second.lastAccessedTime = now;
+ pRet = it->second.pContext;
+ }
+ else
+ {
+ //context is timed out
+ //destroy it and return NULL
+ CLog::Log(LOGDEBUG, "NFS: Old context timed out - destroying it");
+ nfs_destroy_context(it->second.pContext);
+ m_openContextMap.erase(it);
+ }
+ }
+ return pRet;
+}
+
+CNfsConnection::ContextStatus CNfsConnection::getContextForExport(const std::string& exportname)
+{
+ CNfsConnection::ContextStatus ret = CNfsConnection::ContextStatus::INVALID;
+
+ clearMembers();
+
+ m_pNfsContext = getContextFromMap(exportname);
+
+ if(!m_pNfsContext)
+ {
+ CLog::Log(LOGDEBUG, "NFS: Context for {} not open - get a new context.", exportname);
+ m_pNfsContext = nfs_init_context();
+
+ if(!m_pNfsContext)
+ {
+ CLog::Log(LOGERROR,"NFS: Error initcontext in getContextForExport.");
+ }
+ else
+ {
+ struct contextTimeout tmp;
+ std::unique_lock<CCriticalSection> lock(openContextLock);
+ setOptions(m_pNfsContext);
+ tmp.pContext = m_pNfsContext;
+ tmp.lastAccessedTime = std::chrono::steady_clock::now();
+ m_openContextMap[exportname] = tmp; //add context to list of all contexts
+ ret = CNfsConnection::ContextStatus::NEW;
+ }
+ }
+ else
+ {
+ ret = CNfsConnection::ContextStatus::CACHED;
+ CLog::Log(LOGDEBUG,"NFS: Using cached context.");
+ }
+ m_lastAccessedTime = std::chrono::steady_clock::now();
+
+ return ret;
+}
+
+bool CNfsConnection::splitUrlIntoExportAndPath(const CURL& url, std::string &exportPath, std::string &relativePath)
+{
+ //refresh exportlist if empty or hostname change
+ if(m_exportList.empty() || !StringUtils::EqualsNoCase(url.GetHostName(), m_hostName))
+ {
+ const auto settingsComponent = CServiceBroker::GetSettingsComponent();
+ if (!settingsComponent)
+ return false;
+
+ const auto settings = settingsComponent->GetSettings();
+ if (!settings)
+ return false;
+
+ const int nfsVersion = settings->GetInt(SETTING_NFS_VERSION);
+
+ if (nfsVersion == 4)
+ m_exportList = {"/"};
+ else
+ m_exportList = GetExportList(url);
+ }
+
+ return splitUrlIntoExportAndPath(url, exportPath, relativePath, m_exportList);
+}
+
+bool CNfsConnection::splitUrlIntoExportAndPath(const CURL& url,std::string &exportPath, std::string &relativePath, std::list<std::string> &exportList)
+{
+ bool ret = false;
+
+ if(!exportList.empty())
+ {
+ relativePath = "";
+ exportPath = "";
+
+ std::string path = url.GetFileName();
+
+ //GetFileName returns path without leading "/"
+ //but we need it because the export paths start with "/"
+ //and path.Find(*it) wouldn't work else
+ if(path[0] != '/')
+ {
+ path = "/" + path;
+ }
+
+ for (const std::string& it : exportList)
+ {
+ //if path starts with the current export path
+ if (URIUtils::PathHasParent(path, it))
+ {
+ /* It's possible that PathHasParent() may not find the correct match first/
+ * As an example, if /path/ & and /path/sub/ are exported, but
+ * the user specifies the path /path/subdir/ (from /path/ export).
+ * If the path is longer than the exportpath, make sure / is next.
+ */
+ if ((path.length() > it.length()) && (path[it.length()] != '/') && it != "/")
+ continue;
+ exportPath = it;
+ //handle special case where root is exported
+ //in that case we don't want to strip off to
+ //much from the path
+ if( exportPath == path )
+ relativePath = "//";
+ else if( exportPath == "/" )
+ relativePath = "//" + path.substr(exportPath.length());
+ else
+ relativePath = "//" + path.substr(exportPath.length()+1);
+ ret = true;
+ break;
+ }
+ }
+ }
+ return ret;
+}
+
+bool CNfsConnection::Connect(const CURL& url, std::string &relativePath)
+{
+ std::unique_lock<CCriticalSection> lock(*this);
+ int nfsRet = 0;
+ std::string exportPath;
+
+ resolveHost(url);
+ bool ret = splitUrlIntoExportAndPath(url, exportPath, relativePath);
+
+ auto now = std::chrono::steady_clock::now();
+ auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now - m_lastAccessedTime);
+
+ if ((ret && (exportPath != m_exportPath || url.GetHostName() != m_hostName)) ||
+ duration > CONTEXT_TIMEOUT)
+ {
+ CNfsConnection::ContextStatus contextRet = getContextForExport(url.GetHostName() + exportPath);
+
+ // we need a new context because sharename or hostname has changed
+ if (contextRet == CNfsConnection::ContextStatus::INVALID)
+ {
+ return false;
+ }
+
+ // new context was created - we need to mount it
+ if (contextRet == CNfsConnection::ContextStatus::NEW)
+ {
+ //we connect to the directory of the path. This will be the "root" path of this connection then.
+ //So all fileoperations are relative to this mountpoint...
+ nfsRet = nfs_mount(m_pNfsContext, m_resolvedHostName.c_str(), exportPath.c_str());
+
+ if(nfsRet != 0)
+ {
+ CLog::Log(LOGERROR, "NFS: Failed to mount nfs share: {} ({})", exportPath,
+ nfs_get_error(m_pNfsContext));
+ destroyContext(url.GetHostName() + exportPath);
+ return false;
+ }
+ CLog::Log(LOGDEBUG, "NFS: Connected to server {} and export {}", url.GetHostName(),
+ exportPath);
+ }
+ m_exportPath = exportPath;
+ m_hostName = url.GetHostName();
+
+ // read chunksize only works after mount
+ m_readChunkSize = nfs_get_readmax(m_pNfsContext);
+ m_writeChunkSize = nfs_get_writemax(m_pNfsContext);
+
+ if (m_readChunkSize == 0)
+ {
+ CLog::Log(LOGDEBUG, "NFS Server did not return max read chunksize - Using 128K default");
+ m_readChunkSize = 128 * 1024; // 128K
+ }
+ if (m_writeChunkSize == 0)
+ {
+ CLog::Log(LOGDEBUG, "NFS Server did not return max write chunksize - Using 128K default");
+ m_writeChunkSize = 128 * 1024; // 128K
+ }
+
+ if (contextRet == CNfsConnection::ContextStatus::NEW)
+ {
+ CLog::Log(LOGDEBUG, "NFS: chunks: r/w {}/{}", (int)m_readChunkSize, (int)m_writeChunkSize);
+ }
+ }
+ return ret;
+}
+
+void CNfsConnection::Deinit()
+{
+ if(m_pNfsContext)
+ {
+ destroyOpenContexts();
+ m_pNfsContext = NULL;
+ }
+ clearMembers();
+ // clear any keep alive timeouts on deinit
+ m_KeepAliveTimeouts.clear();
+}
+
+/* This is called from CApplication::ProcessSlow() and is used to tell if nfs have been idle for too long */
+void CNfsConnection::CheckIfIdle()
+{
+ /* We check if there are open connections. This is done without a lock to not halt the mainthread. It should be thread safe as
+ worst case scenario is that m_OpenConnections could read 0 and then changed to 1 if this happens it will enter the if which will lead to another check, which is locked. */
+ if (m_OpenConnections == 0 && m_pNfsContext != NULL)
+ { /* I've set the the maximum IDLE time to be 1 min and 30 sec. */
+ std::unique_lock<CCriticalSection> lock(*this);
+ if (m_OpenConnections == 0 /* check again - when locked */)
+ {
+ const auto now = std::chrono::steady_clock::now();
+
+ if (m_IdleTimeout < now)
+ {
+ CLog::Log(LOGINFO, "NFS is idle. Closing the remaining connections.");
+ gNfsConnection.Deinit();
+ }
+ }
+ }
+
+ if( m_pNfsContext != NULL )
+ {
+ std::unique_lock<CCriticalSection> lock(keepAliveLock);
+
+ const auto now = std::chrono::steady_clock::now();
+
+ //handle keep alive on opened files
+ for (auto& it : m_KeepAliveTimeouts)
+ {
+ if (it.second.refreshTime < now)
+ {
+ keepAlive(it.second.exportPath, it.first);
+ //reset timeout
+ resetKeepAlive(it.second.exportPath, it.first);
+ }
+ }
+ }
+}
+
+//remove file handle from keep alive list on file close
+void CNfsConnection::removeFromKeepAliveList(struct nfsfh *_pFileHandle)
+{
+ std::unique_lock<CCriticalSection> lock(keepAliveLock);
+ m_KeepAliveTimeouts.erase(_pFileHandle);
+}
+
+//reset timeouts on read
+void CNfsConnection::resetKeepAlive(const std::string& _exportPath, struct nfsfh* _pFileHandle)
+{
+ std::unique_lock<CCriticalSection> lock(keepAliveLock);
+ //refresh last access time of the context aswell
+ struct nfs_context *pContext = getContextFromMap(_exportPath, true);
+
+ // if we keep alive using m_pNfsContext we need to mark
+ // its last access time too here
+ if (m_pNfsContext == pContext)
+ {
+ m_lastAccessedTime = std::chrono::steady_clock::now();
+ }
+
+ //adds new keys - refreshes existing ones
+ m_KeepAliveTimeouts[_pFileHandle].exportPath = _exportPath;
+ m_KeepAliveTimeouts[_pFileHandle].refreshTime = m_lastAccessedTime + KEEP_ALIVE_TIMEOUT;
+}
+
+//keep alive the filehandles nfs connection
+//by blindly doing a read 32bytes - seek back to where
+//we were before
+void CNfsConnection::keepAlive(const std::string& _exportPath, struct nfsfh* _pFileHandle)
+{
+ uint64_t offset = 0;
+ char buffer[32];
+ // this also refreshes the last accessed time for the context
+ // true forces a cachehit regardless the context is timedout
+ // on this call we are sure its not timedout even if the last accessed
+ // time suggests it.
+ struct nfs_context *pContext = getContextFromMap(_exportPath, true);
+
+ if (!pContext)// this should normally never happen - paranoia
+ pContext = m_pNfsContext;
+
+ CLog::LogF(LOGDEBUG, "sending keep alive after {}s.",
+ std::chrono::duration_cast<std::chrono::seconds>(KEEP_ALIVE_TIMEOUT).count());
+
+ std::unique_lock<CCriticalSection> lock(*this);
+
+ nfs_lseek(pContext, _pFileHandle, 0, SEEK_CUR, &offset);
+
+ int bytes = nfs_read(pContext, _pFileHandle, 32, buffer);
+ if (bytes < 0)
+ {
+ CLog::LogF(LOGERROR, "nfs_read - Error ({}, {})", bytes, nfs_get_error(pContext));
+ return;
+ }
+
+ nfs_lseek(pContext, _pFileHandle, offset, SEEK_SET, &offset);
+}
+
+int CNfsConnection::stat(const CURL& url, nfs_stat_64* statbuff)
+{
+ std::unique_lock<CCriticalSection> lock(*this);
+ int nfsRet = 0;
+ std::string exportPath;
+ std::string relativePath;
+ struct nfs_context *pTmpContext = NULL;
+
+ resolveHost(url);
+
+ if(splitUrlIntoExportAndPath(url, exportPath, relativePath))
+ {
+ pTmpContext = nfs_init_context();
+
+ if(pTmpContext)
+ {
+ setOptions(pTmpContext);
+ //we connect to the directory of the path. This will be the "root" path of this connection then.
+ //So all fileoperations are relative to this mountpoint...
+ nfsRet = nfs_mount(pTmpContext, m_resolvedHostName.c_str(), exportPath.c_str());
+
+ if(nfsRet == 0)
+ {
+ nfsRet = nfs_stat64(pTmpContext, relativePath.c_str(), statbuff);
+ }
+ else
+ {
+ CLog::Log(LOGERROR, "NFS: Failed to mount nfs share: {} ({})", exportPath,
+ nfs_get_error(m_pNfsContext));
+ }
+
+ nfs_destroy_context(pTmpContext);
+ CLog::Log(LOGDEBUG, "NFS: Connected to server {} and export {} in tmpContext",
+ url.GetHostName(), exportPath);
+ }
+ }
+ return nfsRet;
+}
+
+/* The following two function is used to keep track on how many Opened files/directories there are.
+needed for unloading the dylib*/
+void CNfsConnection::AddActiveConnection()
+{
+ std::unique_lock<CCriticalSection> lock(*this);
+ m_OpenConnections++;
+}
+
+void CNfsConnection::AddIdleConnection()
+{
+ std::unique_lock<CCriticalSection> lock(*this);
+ m_OpenConnections--;
+ /* If we close a file we reset the idle timer so that we don't have any weird behaviours if a user
+ leaves the movie paused for a long while and then press stop */
+ const auto now = std::chrono::steady_clock::now();
+ m_IdleTimeout = now + IDLE_TIMEOUT;
+}
+
+
+void CNfsConnection::setOptions(struct nfs_context* context)
+{
+ const auto settingsComponent = CServiceBroker::GetSettingsComponent();
+ if (!settingsComponent)
+ return;
+
+ const auto advancedSettings = settingsComponent->GetAdvancedSettings();
+ if (!advancedSettings)
+ return;
+
+#ifdef HAS_NFS_SET_TIMEOUT
+ uint32_t timeout = advancedSettings->m_nfsTimeout;
+ nfs_set_timeout(context, timeout > 0 ? timeout * 1000 : -1);
+#endif
+ int retries = advancedSettings->m_nfsRetries;
+ nfs_set_autoreconnect(context, retries);
+
+ const auto settings = settingsComponent->GetSettings();
+ if (!settings)
+ return;
+
+ const int nfsVersion = settings->GetInt(SETTING_NFS_VERSION);
+
+ int ret = nfs_set_version(context, nfsVersion);
+ if (ret != 0)
+ {
+ CLog::Log(LOGERROR, "NFS: Failed to set nfs version: {} ({})", nfsVersion,
+ nfs_get_error(context));
+ return;
+ }
+
+ CLog::Log(LOGDEBUG, "NFS: version: {}", nfsVersion);
+}
+
+CNfsConnection gNfsConnection;
+
+CNFSFile::CNFSFile()
+: m_pFileHandle(NULL)
+, m_pNfsContext(NULL)
+{
+ gNfsConnection.AddActiveConnection();
+}
+
+CNFSFile::~CNFSFile()
+{
+ Close();
+ gNfsConnection.AddIdleConnection();
+}
+
+int64_t CNFSFile::GetPosition()
+{
+ int ret = 0;
+ uint64_t offset = 0;
+ std::unique_lock<CCriticalSection> lock(gNfsConnection);
+
+ if (gNfsConnection.GetNfsContext() == NULL || m_pFileHandle == NULL) return 0;
+
+ ret = nfs_lseek(gNfsConnection.GetNfsContext(), m_pFileHandle, 0, SEEK_CUR, &offset);
+
+ if (ret < 0)
+ {
+ CLog::Log(LOGERROR, "NFS: Failed to lseek({})", nfs_get_error(gNfsConnection.GetNfsContext()));
+ }
+ return offset;
+}
+
+int64_t CNFSFile::GetLength()
+{
+ if (m_pFileHandle == NULL) return 0;
+ return m_fileSize;
+}
+
+bool CNFSFile::Open(const CURL& url)
+{
+ Close();
+ // we can't open files like nfs://file.f or nfs://server/file.f
+ // if a file matches the if below return false, it can't exist on a nfs share.
+ if (!IsValidFile(url.GetFileName()))
+ {
+ CLog::Log(LOGINFO, "NFS: Bad URL : '{}'", url.GetFileName());
+ return false;
+ }
+
+ std::string filename;
+
+ std::unique_lock<CCriticalSection> lock(gNfsConnection);
+
+ if (!gNfsConnection.Connect(url, filename))
+ return false;
+
+ m_pNfsContext = gNfsConnection.GetNfsContext();
+ m_exportPath = gNfsConnection.GetContextMapId();
+
+ int ret = nfs_open(m_pNfsContext, filename.c_str(), O_RDONLY, &m_pFileHandle);
+
+ if (ret == NFS4ERR_EXPIRED) // client session expired due no activity/keep alive
+ {
+ CLog::Log(LOGERROR,
+ "CNFSFile::Open: Unable to open file - trying again with a new context: error: '{}'",
+ nfs_get_error(m_pNfsContext));
+
+ gNfsConnection.Deinit();
+ m_pNfsContext = gNfsConnection.GetNfsContext();
+ m_exportPath = gNfsConnection.GetContextMapId();
+ ret = nfs_open(m_pNfsContext, filename.c_str(), O_RDONLY, &m_pFileHandle);
+ }
+
+ if (ret != 0)
+ {
+ CLog::Log(LOGERROR, "CNFSFile::Open: Unable to open file: '{}' error: '{}'", url.GetFileName(),
+ nfs_get_error(m_pNfsContext));
+
+ m_pNfsContext = nullptr;
+ m_exportPath.clear();
+ return false;
+ }
+
+ CLog::Log(LOGDEBUG, "CNFSFile::Open - opened {}", url.GetFileName());
+ m_url=url;
+
+ struct __stat64 tmpBuffer;
+
+ if( Stat(&tmpBuffer) )
+ {
+ m_url.Reset();
+ Close();
+ return false;
+ }
+
+ m_fileSize = tmpBuffer.st_size;//cache the size of this file
+ // We've successfully opened the file!
+ return true;
+}
+
+bool CNFSFile::Exists(const CURL& url)
+{
+ return Stat(url,NULL) == 0;
+}
+
+int CNFSFile::Stat(struct __stat64* buffer)
+{
+ return Stat(m_url,buffer);
+}
+
+
+int CNFSFile::Stat(const CURL& url, struct __stat64* buffer)
+{
+ int ret = 0;
+ std::unique_lock<CCriticalSection> lock(gNfsConnection);
+ std::string filename;
+
+ if(!gNfsConnection.Connect(url,filename))
+ return -1;
+
+ nfs_stat_64 tmpBuffer = {};
+
+ ret = nfs_stat64(gNfsConnection.GetNfsContext(), filename.c_str(), &tmpBuffer);
+
+ //if buffer == NULL we where called from Exists - in that case don't spam the log with errors
+ if (ret != 0 && buffer != NULL)
+ {
+ CLog::Log(LOGERROR, "NFS: Failed to stat({}) {}", url.GetFileName(),
+ nfs_get_error(gNfsConnection.GetNfsContext()));
+ ret = -1;
+ }
+ else
+ {
+ if (buffer)
+ {
+ *buffer = {};
+ buffer->st_dev = tmpBuffer.nfs_dev;
+ buffer->st_ino = tmpBuffer.nfs_ino;
+ buffer->st_mode = tmpBuffer.nfs_mode;
+ buffer->st_nlink = tmpBuffer.nfs_nlink;
+ buffer->st_uid = tmpBuffer.nfs_uid;
+ buffer->st_gid = tmpBuffer.nfs_gid;
+ buffer->st_rdev = tmpBuffer.nfs_rdev;
+ buffer->st_size = tmpBuffer.nfs_size;
+ buffer->st_atime = tmpBuffer.nfs_atime;
+ buffer->st_mtime = tmpBuffer.nfs_mtime;
+ buffer->st_ctime = tmpBuffer.nfs_ctime;
+ }
+ }
+ return ret;
+}
+
+ssize_t CNFSFile::Read(void *lpBuf, size_t uiBufSize)
+{
+ if (uiBufSize > SSIZE_MAX)
+ uiBufSize = SSIZE_MAX;
+
+ ssize_t numberOfBytesRead = 0;
+ std::unique_lock<CCriticalSection> lock(gNfsConnection);
+
+ if (m_pFileHandle == NULL || m_pNfsContext == NULL )
+ return -1;
+
+ numberOfBytesRead = nfs_read(m_pNfsContext, m_pFileHandle, uiBufSize, (char *)lpBuf);
+
+ lock.unlock(); //no need to keep the connection lock after that
+
+ gNfsConnection.resetKeepAlive(m_exportPath, m_pFileHandle);//triggers keep alive timer reset for this filehandle
+
+ //something went wrong ...
+ if (numberOfBytesRead < 0)
+ CLog::Log(LOGERROR, "{} - Error( {}, {} )", __FUNCTION__, (int64_t)numberOfBytesRead,
+ nfs_get_error(m_pNfsContext));
+
+ return numberOfBytesRead;
+}
+
+int64_t CNFSFile::Seek(int64_t iFilePosition, int iWhence)
+{
+ int ret = 0;
+ uint64_t offset = 0;
+
+ std::unique_lock<CCriticalSection> lock(gNfsConnection);
+ if (m_pFileHandle == NULL || m_pNfsContext == NULL) return -1;
+
+
+ ret = nfs_lseek(m_pNfsContext, m_pFileHandle, iFilePosition, iWhence, &offset);
+ if (ret < 0)
+ {
+ CLog::Log(LOGERROR, "{} - Error( seekpos: {}, whence: {}, fsize: {}, {})", __FUNCTION__,
+ iFilePosition, iWhence, m_fileSize, nfs_get_error(m_pNfsContext));
+ return -1;
+ }
+ return (int64_t)offset;
+}
+
+int CNFSFile::Truncate(int64_t iSize)
+{
+ int ret = 0;
+
+ std::unique_lock<CCriticalSection> lock(gNfsConnection);
+ if (m_pFileHandle == NULL || m_pNfsContext == NULL) return -1;
+
+
+ ret = nfs_ftruncate(m_pNfsContext, m_pFileHandle, iSize);
+ if (ret < 0)
+ {
+ CLog::Log(LOGERROR, "{} - Error( ftruncate: {}, fsize: {}, {})", __FUNCTION__, iSize,
+ m_fileSize, nfs_get_error(m_pNfsContext));
+ return -1;
+ }
+ return ret;
+}
+
+void CNFSFile::Close()
+{
+ std::unique_lock<CCriticalSection> lock(gNfsConnection);
+
+ if (m_pFileHandle != NULL && m_pNfsContext != NULL)
+ {
+ int ret = 0;
+ CLog::Log(LOGDEBUG, "CNFSFile::Close closing file {}", m_url.GetFileName());
+ // remove it from keep alive list before closing
+ // so keep alive code doesn't process it anymore
+ gNfsConnection.removeFromKeepAliveList(m_pFileHandle);
+ ret = nfs_close(m_pNfsContext, m_pFileHandle);
+
+ if (ret < 0)
+ {
+ CLog::Log(LOGERROR, "Failed to close({}) - {}", m_url.GetFileName(),
+ nfs_get_error(m_pNfsContext));
+ }
+ m_pFileHandle = NULL;
+ m_pNfsContext = NULL;
+ m_fileSize = 0;
+ m_exportPath.clear();
+ }
+}
+
+//this was a bitch!
+//for nfs write to work we have to write chunked
+//otherwise this could crash on big files
+ssize_t CNFSFile::Write(const void* lpBuf, size_t uiBufSize)
+{
+ size_t numberOfBytesWritten = 0;
+ int writtenBytes = 0;
+ size_t leftBytes = uiBufSize;
+ //clamp max write chunksize to 32kb - fixme - this might be superfluous with future libnfs versions
+ size_t chunkSize = gNfsConnection.GetMaxWriteChunkSize() > 32768 ? 32768 : (size_t)gNfsConnection.GetMaxWriteChunkSize();
+
+ std::unique_lock<CCriticalSection> lock(gNfsConnection);
+
+ if (m_pFileHandle == NULL || m_pNfsContext == NULL) return -1;
+
+ //write as long as some bytes are left to be written
+ while( leftBytes )
+ {
+ //the last chunk could be smalle than chunksize
+ if(leftBytes < chunkSize)
+ {
+ chunkSize = leftBytes;//write last chunk with correct size
+ }
+ //write chunk
+ //! @bug libnfs < 2.0.0 isn't const correct
+ writtenBytes = nfs_write(m_pNfsContext,
+ m_pFileHandle,
+ chunkSize,
+ const_cast<char*>((const char *)lpBuf) + numberOfBytesWritten);
+ //decrease left bytes
+ leftBytes-= writtenBytes;
+ //increase overall written bytes
+ numberOfBytesWritten += writtenBytes;
+
+ //danger - something went wrong
+ if (writtenBytes < 0)
+ {
+ CLog::Log(LOGERROR, "Failed to pwrite({}) {}", m_url.GetFileName(),
+ nfs_get_error(m_pNfsContext));
+ if (numberOfBytesWritten == 0)
+ return -1;
+
+ break;
+ }
+ }
+ //return total number of written bytes
+ return numberOfBytesWritten;
+}
+
+bool CNFSFile::Delete(const CURL& url)
+{
+ int ret = 0;
+ std::unique_lock<CCriticalSection> lock(gNfsConnection);
+ std::string filename;
+
+ if(!gNfsConnection.Connect(url, filename))
+ return false;
+
+
+ ret = nfs_unlink(gNfsConnection.GetNfsContext(), filename.c_str());
+
+ if(ret != 0)
+ {
+ CLog::Log(LOGERROR, "{} - Error( {} )", __FUNCTION__,
+ nfs_get_error(gNfsConnection.GetNfsContext()));
+ }
+ return (ret == 0);
+}
+
+bool CNFSFile::Rename(const CURL& url, const CURL& urlnew)
+{
+ int ret = 0;
+ std::unique_lock<CCriticalSection> lock(gNfsConnection);
+ std::string strFile;
+
+ if(!gNfsConnection.Connect(url,strFile))
+ return false;
+
+ std::string strFileNew;
+ std::string strDummy;
+ gNfsConnection.splitUrlIntoExportAndPath(urlnew, strDummy, strFileNew);
+
+ ret = nfs_rename(gNfsConnection.GetNfsContext() , strFile.c_str(), strFileNew.c_str());
+
+ if(ret != 0)
+ {
+ CLog::Log(LOGERROR, "{} - Error( {} )", __FUNCTION__,
+ nfs_get_error(gNfsConnection.GetNfsContext()));
+ }
+ return (ret == 0);
+}
+
+bool CNFSFile::OpenForWrite(const CURL& url, bool bOverWrite)
+{
+ int ret = 0;
+ // we can't open files like nfs://file.f or nfs://server/file.f
+ // if a file matches the if below return false, it can't exist on a nfs share.
+ if (!IsValidFile(url.GetFileName())) return false;
+
+ Close();
+ std::unique_lock<CCriticalSection> lock(gNfsConnection);
+ std::string filename;
+
+ if(!gNfsConnection.Connect(url,filename))
+ return false;
+
+ m_pNfsContext = gNfsConnection.GetNfsContext();
+ m_exportPath = gNfsConnection.GetContextMapId();
+
+ if (bOverWrite)
+ {
+ CLog::Log(LOGWARNING, "FileNFS::OpenForWrite() called with overwriting enabled! - {}",
+ filename);
+ //create file with proper permissions
+ ret = nfs_creat(m_pNfsContext, filename.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH, &m_pFileHandle);
+ //if file was created the file handle isn't valid ... so close it and open later
+ if(ret == 0)
+ {
+ nfs_close(m_pNfsContext,m_pFileHandle);
+ m_pFileHandle = NULL;
+ }
+ }
+
+ ret = nfs_open(m_pNfsContext, filename.c_str(), O_RDWR, &m_pFileHandle);
+
+ if (ret || m_pFileHandle == NULL)
+ {
+ // write error to logfile
+ CLog::Log(LOGERROR, "CNFSFile::Open: Unable to open file : '{}' error : '{}'", filename,
+ nfs_get_error(gNfsConnection.GetNfsContext()));
+ m_pNfsContext = NULL;
+ m_exportPath.clear();
+ return false;
+ }
+ m_url=url;
+
+ struct __stat64 tmpBuffer = {};
+
+ //only stat if file was not created
+ if(!bOverWrite)
+ {
+ if(Stat(&tmpBuffer))
+ {
+ m_url.Reset();
+ Close();
+ return false;
+ }
+ m_fileSize = tmpBuffer.st_size;//cache filesize of this file
+ }
+ else//file was created - filesize is zero
+ {
+ m_fileSize = 0;
+ }
+
+ // We've successfully opened the file!
+ return true;
+}
+
+bool CNFSFile::IsValidFile(const std::string& strFileName)
+{
+ if (strFileName.find('/') == std::string::npos || /* doesn't have sharename */
+ StringUtils::EndsWith(strFileName, "/.") || /* not current folder */
+ StringUtils::EndsWith(strFileName, "/..")) /* not parent folder */
+ return false;
+ return true;
+}
diff --git a/xbmc/filesystem/NFSFile.h b/xbmc/filesystem/NFSFile.h
new file mode 100644
index 0000000..9e9dde9
--- /dev/null
+++ b/xbmc/filesystem/NFSFile.h
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2011-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+// FileNFS.h: interface for the CNFSFile class.
+
+#include "IFile.h"
+#include "URL.h"
+#include "threads/CriticalSection.h"
+
+#include <chrono>
+#include <list>
+#include <map>
+
+struct nfs_stat_64;
+
+class CNfsConnection : public CCriticalSection
+{
+public:
+ struct keepAliveStruct
+ {
+ std::string exportPath;
+ std::chrono::time_point<std::chrono::steady_clock> refreshTime;
+ };
+ typedef std::map<struct nfsfh *, struct keepAliveStruct> tFileKeepAliveMap;
+
+ struct contextTimeout
+ {
+ struct nfs_context *pContext;
+ std::chrono::time_point<std::chrono::steady_clock> lastAccessedTime;
+ };
+
+ typedef std::map<std::string, struct contextTimeout> tOpenContextMap;
+
+ CNfsConnection();
+ ~CNfsConnection();
+ bool Connect(const CURL &url, std::string &relativePath);
+ struct nfs_context *GetNfsContext() {return m_pNfsContext;}
+ uint64_t GetMaxReadChunkSize() {return m_readChunkSize;}
+ uint64_t GetMaxWriteChunkSize() {return m_writeChunkSize;}
+ std::list<std::string> GetExportList(const CURL &url);
+ //this functions splits the url into the exportpath (feed to mount) and the rest of the path
+ //relative to the mounted export
+ bool splitUrlIntoExportAndPath(const CURL& url, std::string &exportPath, std::string &relativePath, std::list<std::string> &exportList);
+ bool splitUrlIntoExportAndPath(const CURL& url, std::string &exportPath, std::string &relativePath);
+
+ //special stat which uses its own context
+ //needed for getting intervolume symlinks to work
+ int stat(const CURL& url, nfs_stat_64* statbuff);
+
+ void AddActiveConnection();
+ void AddIdleConnection();
+ void CheckIfIdle();
+ void Deinit();
+ //adds the filehandle to the keep alive list or resets
+ //the timeout for this filehandle if already in list
+ void resetKeepAlive(const std::string& _exportPath, struct nfsfh* _pFileHandle);
+ //removes file handle from keep alive list
+ void removeFromKeepAliveList(struct nfsfh *_pFileHandle);
+
+ const std::string& GetConnectedIp() const {return m_resolvedHostName;}
+ const std::string& GetConnectedExport() const {return m_exportPath;}
+ const std::string GetContextMapId() const {return m_hostName + m_exportPath;}
+
+private:
+ enum class ContextStatus
+ {
+ INVALID,
+ NEW,
+ CACHED
+ };
+
+ struct nfs_context *m_pNfsContext;//current nfs context
+ std::string m_exportPath;//current connected export path
+ std::string m_hostName;//current connected host
+ std::string m_resolvedHostName;//current connected host - as ip
+ uint64_t m_readChunkSize = 0;//current read chunksize of connected server
+ uint64_t m_writeChunkSize = 0;//current write chunksize of connected server
+ int m_OpenConnections = 0; //number of open connections
+ std::chrono::time_point<std::chrono::steady_clock> m_IdleTimeout;
+ tFileKeepAliveMap m_KeepAliveTimeouts;//mapping filehandles to its idle timeout
+ tOpenContextMap m_openContextMap;//unique map for tracking all open contexts
+ std::chrono::time_point<std::chrono::steady_clock>
+ m_lastAccessedTime; //last access time for m_pNfsContext
+ std::list<std::string> m_exportList;//list of exported paths of current connected servers
+ CCriticalSection keepAliveLock;
+ CCriticalSection openContextLock;
+
+ void clearMembers();
+ struct nfs_context *getContextFromMap(const std::string &exportname, bool forceCacheHit = false);
+
+ // get context for given export and add to open contexts map - sets m_pNfsContext (may return an already mounted cached context)
+ ContextStatus getContextForExport(const std::string& exportname);
+ void destroyOpenContexts();
+ void destroyContext(const std::string &exportName);
+ void resolveHost(const CURL &url);//resolve hostname by dnslookup
+ void keepAlive(const std::string& _exportPath, struct nfsfh* _pFileHandle);
+ static void setOptions(struct nfs_context* context);
+};
+
+extern CNfsConnection gNfsConnection;
+
+namespace XFILE
+{
+ class CNFSFile : public IFile
+ {
+ public:
+ CNFSFile();
+ ~CNFSFile() override;
+ void Close() override;
+ int64_t Seek(int64_t iFilePosition, int iWhence = SEEK_SET) override;
+ ssize_t Read(void* lpBuf, size_t uiBufSize) override;
+ bool Open(const CURL& url) override;
+ bool Exists(const CURL& url) override;
+ int Stat(const CURL& url, struct __stat64* buffer) override;
+ int Stat(struct __stat64* buffer) override;
+ int64_t GetLength() override;
+ int64_t GetPosition() override;
+ ssize_t Write(const void* lpBuf, size_t uiBufSize) override;
+ int Truncate(int64_t iSize) override;
+
+ //implement iocontrol for seek_possible for preventing the stat in File class for
+ //getting this info ...
+ int IoControl(EIoControl request, void* param) override
+ {
+ return request == IOCTRL_SEEK_POSSIBLE ? 1 : -1;
+ }
+ int GetChunkSize() override {return static_cast<int>(gNfsConnection.GetMaxReadChunkSize());}
+
+ bool OpenForWrite(const CURL& url, bool bOverWrite = false) override;
+ bool Delete(const CURL& url) override;
+ bool Rename(const CURL& url, const CURL& urlnew) override;
+ protected:
+ CURL m_url;
+ bool IsValidFile(const std::string& strFileName);
+ int64_t m_fileSize = 0;
+ struct nfsfh *m_pFileHandle;
+ struct nfs_context *m_pNfsContext;//current nfs context
+ std::string m_exportPath;
+ };
+}
+
diff --git a/xbmc/filesystem/NptXbmcFile.cpp b/xbmc/filesystem/NptXbmcFile.cpp
new file mode 100644
index 0000000..bcbdea1
--- /dev/null
+++ b/xbmc/filesystem/NptXbmcFile.cpp
@@ -0,0 +1,534 @@
+/*
+ * Neptune - Files :: XBMC Implementation
+ *
+ * Copyright (c) 2002-2008, Axiomatic Systems, LLC.
+ * All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ * See LICENSES/README.md for more information.
+ */
+
+/*----------------------------------------------------------------------
+| includes
++---------------------------------------------------------------------*/
+#include "File.h"
+#include "FileFactory.h"
+#include "PasswordManager.h"
+#include "URL.h"
+#include "utils/URIUtils.h"
+
+#include <limits>
+
+#include <Neptune/Source/Core/NptDebug.h>
+#include <Neptune/Source/Core/NptFile.h>
+#include <Neptune/Source/Core/NptStrings.h>
+#include <Neptune/Source/Core/NptUtils.h>
+
+#ifdef TARGET_WINDOWS
+#define S_IWUSR _S_IWRITE
+#define S_ISDIR(m) ((m & _S_IFDIR) != 0)
+#define S_ISREG(m) ((m & _S_IFREG) != 0)
+#endif
+
+using namespace XFILE;
+
+typedef NPT_Reference<IFile> NPT_XbmcFileReference;
+
+/*----------------------------------------------------------------------
+| NPT_XbmcFileStream
++---------------------------------------------------------------------*/
+class NPT_XbmcFileStream
+{
+public:
+ // constructors and destructor
+ explicit NPT_XbmcFileStream(const NPT_XbmcFileReference& file) : m_FileReference(file) {}
+
+ // NPT_FileInterface methods
+ NPT_Result Seek(NPT_Position offset);
+ NPT_Result Tell(NPT_Position& offset);
+ NPT_Result Flush();
+
+protected:
+ // constructors and destructors
+ virtual ~NPT_XbmcFileStream() = default;
+
+ // members
+ NPT_XbmcFileReference m_FileReference;
+};
+
+/*----------------------------------------------------------------------
+| NPT_XbmcFileStream::Seek
++---------------------------------------------------------------------*/
+NPT_Result
+NPT_XbmcFileStream::Seek(NPT_Position offset)
+{
+ int64_t result;
+
+ result = m_FileReference->Seek(offset, SEEK_SET) ;
+ if (result >= 0) {
+ return NPT_SUCCESS;
+ } else {
+ return NPT_FAILURE;
+ }
+}
+
+/*----------------------------------------------------------------------
+| NPT_XbmcFileStream::Tell
++---------------------------------------------------------------------*/
+NPT_Result
+NPT_XbmcFileStream::Tell(NPT_Position& offset)
+{
+ int64_t result = m_FileReference->GetPosition();
+ if (result >= 0) {
+ offset = (NPT_Position)result;
+ return NPT_SUCCESS;
+ } else {
+ return NPT_FAILURE;
+ }
+}
+
+/*----------------------------------------------------------------------
+| NPT_XbmcFileStream::Flush
++---------------------------------------------------------------------*/
+NPT_Result
+NPT_XbmcFileStream::Flush()
+{
+ m_FileReference->Flush();
+ return NPT_SUCCESS;
+}
+
+/*----------------------------------------------------------------------
+| NPT_XbmcFileInputStream
++---------------------------------------------------------------------*/
+class NPT_XbmcFileInputStream : public NPT_InputStream,
+ private NPT_XbmcFileStream
+
+{
+public:
+ // constructors and destructor
+ explicit NPT_XbmcFileInputStream(NPT_XbmcFileReference& file) :
+ NPT_XbmcFileStream(file) {}
+
+ // NPT_InputStream methods
+ NPT_Result Read(void* buffer,
+ NPT_Size bytes_to_read,
+ NPT_Size* bytes_read) override;
+ NPT_Result Seek(NPT_Position offset) override {
+ return NPT_XbmcFileStream::Seek(offset);
+ }
+ NPT_Result Tell(NPT_Position& offset) override {
+ return NPT_XbmcFileStream::Tell(offset);
+ }
+ NPT_Result GetSize(NPT_LargeSize& size) override;
+ NPT_Result GetAvailable(NPT_LargeSize& available) override;
+};
+
+/*----------------------------------------------------------------------
+| NPT_XbmcFileInputStream::Read
++---------------------------------------------------------------------*/
+NPT_Result
+NPT_XbmcFileInputStream::Read(void* buffer,
+ NPT_Size bytes_to_read,
+ NPT_Size* bytes_read)
+{
+ unsigned int nb_read;
+
+ // check the parameters
+ if (buffer == NULL) {
+ return NPT_ERROR_INVALID_PARAMETERS;
+ }
+
+ // read from the file
+ nb_read = m_FileReference->Read(buffer, bytes_to_read);
+ if (nb_read > 0) {
+ if (bytes_read) *bytes_read = (NPT_Size)nb_read;
+ return NPT_SUCCESS;
+ } else {
+ if (bytes_read) *bytes_read = 0;
+ return NPT_ERROR_EOS;
+ //} else { // currently no way to indicate failure
+ // if (bytes_read) *bytes_read = 0;
+ // return NPT_ERROR_READ_FAILED;
+ }
+}
+
+/*----------------------------------------------------------------------
+| NPT_XbmcFileInputStream::GetSize
++---------------------------------------------------------------------*/
+NPT_Result
+NPT_XbmcFileInputStream::GetSize(NPT_LargeSize& size)
+{
+ size = m_FileReference->GetLength();
+ return NPT_SUCCESS;
+}
+
+/*----------------------------------------------------------------------
+| NPT_XbmcFileInputStream::GetAvailable
++---------------------------------------------------------------------*/
+NPT_Result
+NPT_XbmcFileInputStream::GetAvailable(NPT_LargeSize& available)
+{
+ int64_t offset = m_FileReference->GetPosition();
+ NPT_LargeSize size = 0;
+
+ if (NPT_SUCCEEDED(GetSize(size)) && offset >= 0 && (NPT_LargeSize)offset <= size) {
+ available = size - offset;
+ return NPT_SUCCESS;
+ } else {
+ available = 0;
+ return NPT_FAILURE;
+ }
+}
+
+/*----------------------------------------------------------------------
+| NPT_XbmcFileOutputStream
++---------------------------------------------------------------------*/
+class NPT_XbmcFileOutputStream : public NPT_OutputStream,
+ private NPT_XbmcFileStream
+{
+public:
+ // constructors and destructor
+ explicit NPT_XbmcFileOutputStream(NPT_XbmcFileReference& file) :
+ NPT_XbmcFileStream(file) {}
+
+ // NPT_OutputStream methods
+ NPT_Result Write(const void* buffer,
+ NPT_Size bytes_to_write,
+ NPT_Size* bytes_written) override;
+ NPT_Result Seek(NPT_Position offset) override {
+ return NPT_XbmcFileStream::Seek(offset);
+ }
+ NPT_Result Tell(NPT_Position& offset) override {
+ return NPT_XbmcFileStream::Tell(offset);
+ }
+ NPT_Result Flush() override {
+ return NPT_XbmcFileStream::Flush();
+ }
+};
+
+/*----------------------------------------------------------------------
+| NPT_XbmcFileOutputStream::Write
++---------------------------------------------------------------------*/
+NPT_Result
+NPT_XbmcFileOutputStream::Write(const void* buffer,
+ NPT_Size bytes_to_write,
+ NPT_Size* bytes_written)
+{
+ int nb_written;
+ nb_written = m_FileReference->Write(buffer, bytes_to_write);
+
+ if (nb_written > 0) {
+ if (bytes_written) *bytes_written = (NPT_Size)nb_written;
+ return NPT_SUCCESS;
+ } else {
+ if (bytes_written) *bytes_written = 0;
+ return NPT_ERROR_WRITE_FAILED;
+ }
+}
+
+/*----------------------------------------------------------------------
+| NPT_XbmcFile
++---------------------------------------------------------------------*/
+class NPT_XbmcFile: public NPT_FileInterface
+{
+public:
+ // constructors and destructor
+ explicit NPT_XbmcFile(NPT_File& delegator);
+ ~NPT_XbmcFile() override;
+
+ // NPT_FileInterface methods
+ NPT_Result Open(OpenMode mode) override;
+ NPT_Result Close() override;
+ NPT_Result GetInputStream(NPT_InputStreamReference& stream) override;
+ NPT_Result GetOutputStream(NPT_OutputStreamReference& stream) override;
+
+private:
+ // members
+ NPT_File& m_Delegator;
+ OpenMode m_Mode;
+ NPT_XbmcFileReference m_FileReference;
+};
+
+/*----------------------------------------------------------------------
+| NPT_XbmcFile::NPT_XbmcFile
++---------------------------------------------------------------------*/
+NPT_XbmcFile::NPT_XbmcFile(NPT_File& delegator) :
+ m_Delegator(delegator),
+ m_Mode(0)
+{
+}
+
+/*----------------------------------------------------------------------
+| NPT_XbmcFile::~NPT_XbmcFile
++---------------------------------------------------------------------*/
+NPT_XbmcFile::~NPT_XbmcFile()
+{
+ Close();
+}
+
+/*----------------------------------------------------------------------
+| NPT_XbmcFile::Open
++---------------------------------------------------------------------*/
+NPT_Result
+NPT_XbmcFile::Open(NPT_File::OpenMode mode)
+{
+ NPT_XbmcFileReference file;
+
+ // check if we're already open
+ if (!m_FileReference.IsNull()) {
+ return NPT_ERROR_FILE_ALREADY_OPEN;
+ }
+
+ // store the mode
+ m_Mode = mode;
+
+ // check for special names
+ const char* name = (const char*)m_Delegator.GetPath();
+ if (NPT_StringsEqual(name, NPT_FILE_STANDARD_INPUT)) {
+ return NPT_ERROR_FILE_NOT_READABLE;
+ } else if (NPT_StringsEqual(name, NPT_FILE_STANDARD_OUTPUT)) {
+ return NPT_ERROR_FILE_NOT_WRITABLE;
+ } else if (NPT_StringsEqual(name, NPT_FILE_STANDARD_ERROR)) {
+ return NPT_ERROR_FILE_NOT_WRITABLE;
+ } else {
+
+ file = CFileFactory::CreateLoader(name);
+ if (file.IsNull()) {
+ return NPT_ERROR_NO_SUCH_FILE;
+ }
+
+ bool result;
+ CURL url(URIUtils::SubstitutePath(name));
+
+ if (CPasswordManager::GetInstance().IsURLSupported(url) && url.GetUserName().empty())
+ CPasswordManager::GetInstance().AuthenticateURL(url);
+
+ // compute mode
+ if (mode & NPT_FILE_OPEN_MODE_WRITE)
+ result = file->OpenForWrite(url, (mode & NPT_FILE_OPEN_MODE_TRUNCATE) ? true : false);
+ else
+ result = file->Open(url);
+
+ if (!result) return NPT_ERROR_NO_SUCH_FILE;
+ }
+
+ // store reference
+ m_FileReference = file;
+
+ return NPT_SUCCESS;
+}
+
+/*----------------------------------------------------------------------
+| NPT_XbmcFile::Close
++---------------------------------------------------------------------*/
+NPT_Result
+NPT_XbmcFile::Close()
+{
+ // release the file reference
+ m_FileReference = NULL;
+
+ // reset the mode
+ m_Mode = 0;
+
+ return NPT_SUCCESS;
+}
+
+/*----------------------------------------------------------------------
+| NPT_XbmcFile::GetInputStream
++---------------------------------------------------------------------*/
+NPT_Result
+NPT_XbmcFile::GetInputStream(NPT_InputStreamReference& stream)
+{
+ // default value
+ stream = NULL;
+
+ // check that the file is open
+ if (m_FileReference.IsNull()) return NPT_ERROR_FILE_NOT_OPEN;
+
+ // check that the mode is compatible
+ if (!(m_Mode & NPT_FILE_OPEN_MODE_READ)) {
+ return NPT_ERROR_FILE_NOT_READABLE;
+ }
+
+ // create a stream
+ stream = new NPT_XbmcFileInputStream(m_FileReference);
+
+ return NPT_SUCCESS;
+}
+
+/*----------------------------------------------------------------------
+| NPT_XbmcFile::GetOutputStream
++---------------------------------------------------------------------*/
+NPT_Result
+NPT_XbmcFile::GetOutputStream(NPT_OutputStreamReference& stream)
+{
+ // default value
+ stream = NULL;
+
+ // check that the file is open
+ if (m_FileReference.IsNull()) return NPT_ERROR_FILE_NOT_OPEN;
+
+ // check that the mode is compatible
+ if (!(m_Mode & NPT_FILE_OPEN_MODE_WRITE)) {
+ return NPT_ERROR_FILE_NOT_WRITABLE;
+ }
+
+ // create a stream
+ stream = new NPT_XbmcFileOutputStream(m_FileReference);
+
+ return NPT_SUCCESS;
+}
+
+static NPT_Result
+MapErrno(int err) {
+ switch (err) {
+ case EACCES: return NPT_ERROR_PERMISSION_DENIED;
+ case EPERM: return NPT_ERROR_PERMISSION_DENIED;
+ case ENOENT: return NPT_ERROR_NO_SUCH_FILE;
+ case ENAMETOOLONG: return NPT_ERROR_INVALID_PARAMETERS;
+ case EBUSY: return NPT_ERROR_FILE_BUSY;
+ case EROFS: return NPT_ERROR_FILE_NOT_WRITABLE;
+ case ENOTDIR: return NPT_ERROR_FILE_NOT_DIRECTORY;
+ case EEXIST: return NPT_ERROR_FILE_ALREADY_EXISTS;
+ case ENOSPC: return NPT_ERROR_FILE_NOT_ENOUGH_SPACE;
+ case ENOTEMPTY: return NPT_ERROR_DIRECTORY_NOT_EMPTY;
+ default: return NPT_ERROR_ERRNO(err);
+ }
+}
+/*----------------------------------------------------------------------
+| NPT_FilePath::Separator
++---------------------------------------------------------------------*/
+const char* const NPT_FilePath::Separator = "/";
+
+/*----------------------------------------------------------------------
+| NPT_File::NPT_File
++---------------------------------------------------------------------*/
+NPT_File::NPT_File(const char* path) : m_Path(path)
+{
+ m_Delegate = new NPT_XbmcFile(*this);
+}
+
+/*----------------------------------------------------------------------
+| NPT_File::operator=
++---------------------------------------------------------------------*/
+NPT_File&
+NPT_File::operator=(const NPT_File& file)
+{
+ if (this != &file) {
+ delete m_Delegate;
+ m_Path = file.m_Path;
+ m_Delegate = new NPT_XbmcFile(*this);
+ }
+ return *this;
+}
+
+/*----------------------------------------------------------------------
+| NPT_File::GetRoots
++---------------------------------------------------------------------*/
+NPT_Result
+NPT_File::GetRoots(NPT_List<NPT_String>& roots)
+{
+ return NPT_FAILURE;
+}
+
+/*----------------------------------------------------------------------
+| NPT_File::CreateDir
++---------------------------------------------------------------------*/
+NPT_Result
+NPT_File::CreateDir(const char* path)
+{
+ return NPT_ERROR_PERMISSION_DENIED;
+}
+
+/*----------------------------------------------------------------------
+| NPT_File::RemoveFile
++---------------------------------------------------------------------*/
+NPT_Result
+NPT_File::RemoveFile(const char* path)
+{
+ return NPT_ERROR_PERMISSION_DENIED;
+}
+
+/*----------------------------------------------------------------------
+| NPT_File::RemoveDir
++---------------------------------------------------------------------*/
+NPT_Result
+NPT_File::RemoveDir(const char* path)
+{
+ return NPT_ERROR_PERMISSION_DENIED;
+}
+
+/*----------------------------------------------------------------------
+| NPT_File::Rename
++---------------------------------------------------------------------*/
+NPT_Result
+NPT_File::Rename(const char* from_path, const char* to_path)
+{
+ return NPT_ERROR_PERMISSION_DENIED;
+}
+
+/*----------------------------------------------------------------------
+| NPT_File::ListDir
++---------------------------------------------------------------------*/
+NPT_Result
+NPT_File::ListDir(const char* path,
+ NPT_List<NPT_String>& entries,
+ NPT_Ordinal start /* = 0 */,
+ NPT_Cardinal max /* = 0 */)
+{
+ return NPT_FAILURE;
+}
+
+/*----------------------------------------------------------------------
+| NPT_File::GetWorkingDir
++---------------------------------------------------------------------*/
+NPT_Result
+NPT_File::GetWorkingDir(NPT_String& path)
+{
+ return NPT_FAILURE;
+}
+
+/*----------------------------------------------------------------------
+| NPT_File::GetInfo
++---------------------------------------------------------------------*/
+NPT_Result
+NPT_File::GetInfo(const char* path, NPT_FileInfo* info)
+{
+ struct __stat64 stat_buffer = {};
+ int result;
+
+ if (!info)
+ return NPT_FAILURE;
+
+ *info = NPT_FileInfo();
+
+ result = CFile::Stat(path, &stat_buffer);
+ if (result != 0)
+ return MapErrno(errno);
+ if (info)
+ {
+ info->m_Size = stat_buffer.st_size;
+ if (S_ISREG(stat_buffer.st_mode))
+ {
+ info->m_Type = NPT_FileInfo::FILE_TYPE_REGULAR;
+ }
+ else if (S_ISDIR(stat_buffer.st_mode))
+ {
+ info->m_Type = NPT_FileInfo::FILE_TYPE_DIRECTORY;
+ }
+ else
+ {
+ info->m_Type = NPT_FileInfo::FILE_TYPE_OTHER;
+ }
+ info->m_AttributesMask &= NPT_FILE_ATTRIBUTE_READ_ONLY;
+ if ((stat_buffer.st_mode & S_IWUSR) == 0)
+ {
+ info->m_Attributes &= NPT_FILE_ATTRIBUTE_READ_ONLY;
+ }
+ info->m_CreationTime.SetSeconds(0);
+ info->m_ModificationTime.SetSeconds(stat_buffer.st_mtime);
+ }
+
+ return NPT_SUCCESS;
+}
+
diff --git a/xbmc/filesystem/OverrideDirectory.cpp b/xbmc/filesystem/OverrideDirectory.cpp
new file mode 100644
index 0000000..0e24247
--- /dev/null
+++ b/xbmc/filesystem/OverrideDirectory.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2014-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "OverrideDirectory.h"
+
+#include "URL.h"
+#include "filesystem/Directory.h"
+
+using namespace XFILE;
+
+
+COverrideDirectory::COverrideDirectory() = default;
+
+
+COverrideDirectory::~COverrideDirectory() = default;
+
+bool COverrideDirectory::Create(const CURL& url)
+{
+ std::string translatedPath = TranslatePath(url);
+
+ return CDirectory::Create(translatedPath.c_str());
+}
+
+bool COverrideDirectory::Remove(const CURL& url)
+{
+ std::string translatedPath = TranslatePath(url);
+
+ return CDirectory::Remove(translatedPath.c_str());
+}
+
+bool COverrideDirectory::Exists(const CURL& url)
+{
+ std::string translatedPath = TranslatePath(url);
+
+ return CDirectory::Exists(translatedPath.c_str());
+}
diff --git a/xbmc/filesystem/OverrideDirectory.h b/xbmc/filesystem/OverrideDirectory.h
new file mode 100644
index 0000000..0c6a7ab
--- /dev/null
+++ b/xbmc/filesystem/OverrideDirectory.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2014-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "filesystem/IDirectory.h"
+
+namespace XFILE
+{
+class COverrideDirectory : public IDirectory
+{
+public:
+ COverrideDirectory();
+ ~COverrideDirectory() override;
+
+ bool Create(const CURL& url) override;
+ bool Exists(const CURL& url) override;
+ bool Remove(const CURL& url) override;
+
+protected:
+ virtual std::string TranslatePath(const CURL &url) = 0;
+};
+}
diff --git a/xbmc/filesystem/OverrideFile.cpp b/xbmc/filesystem/OverrideFile.cpp
new file mode 100644
index 0000000..73fb43e
--- /dev/null
+++ b/xbmc/filesystem/OverrideFile.cpp
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2014-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "OverrideFile.h"
+
+#include "URL.h"
+
+#include <sys/stat.h>
+
+using namespace XFILE;
+
+
+COverrideFile::COverrideFile(bool writable)
+ : m_writable(writable)
+{ }
+
+
+COverrideFile::~COverrideFile()
+{
+ Close();
+}
+
+bool COverrideFile::Open(const CURL& url)
+{
+ std::string strFileName = TranslatePath(url);
+
+ return m_file.Open(strFileName);
+}
+
+bool COverrideFile::OpenForWrite(const CURL& url, bool bOverWrite /* = false */)
+{
+ if (!m_writable)
+ return false;
+
+ std::string strFileName = TranslatePath(url);
+
+ return m_file.OpenForWrite(strFileName, bOverWrite);
+}
+
+bool COverrideFile::Delete(const CURL& url)
+{
+ if (!m_writable)
+ return false;
+
+ std::string strFileName = TranslatePath(url);
+
+ return m_file.Delete(strFileName);
+}
+
+bool COverrideFile::Exists(const CURL& url)
+{
+ std::string strFileName = TranslatePath(url);
+
+ return m_file.Exists(strFileName);
+}
+
+int COverrideFile::Stat(const CURL& url, struct __stat64* buffer)
+{
+ std::string strFileName = TranslatePath(url);
+
+ return m_file.Stat(strFileName, buffer);
+}
+
+bool COverrideFile::Rename(const CURL& url, const CURL& urlnew)
+{
+ if (!m_writable)
+ return false;
+
+ std::string strFileName = TranslatePath(url);
+ std::string strFileName2 = TranslatePath(urlnew);
+
+ return m_file.Rename(strFileName, strFileName2);
+}
+
+int COverrideFile::Stat(struct __stat64* buffer)
+{
+ return m_file.Stat(buffer);
+}
+
+ssize_t COverrideFile::Read(void* lpBuf, size_t uiBufSize)
+{
+ return m_file.Read(lpBuf, uiBufSize);
+}
+
+ssize_t COverrideFile::Write(const void* lpBuf, size_t uiBufSize)
+{
+ if (!m_writable)
+ return -1;
+
+ return m_file.Write(lpBuf, uiBufSize);
+}
+
+int64_t COverrideFile::Seek(int64_t iFilePosition, int iWhence /*=SEEK_SET*/)
+{
+ return m_file.Seek(iFilePosition, iWhence);
+}
+
+void COverrideFile::Close()
+{
+ m_file.Close();
+}
+
+int64_t COverrideFile::GetPosition()
+{
+ return m_file.GetPosition();
+}
+
+int64_t COverrideFile::GetLength()
+{
+ return m_file.GetLength();
+}
diff --git a/xbmc/filesystem/OverrideFile.h b/xbmc/filesystem/OverrideFile.h
new file mode 100644
index 0000000..974b473
--- /dev/null
+++ b/xbmc/filesystem/OverrideFile.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2014-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "filesystem/File.h"
+#include "filesystem/IFile.h"
+
+namespace XFILE
+{
+class COverrideFile : public IFile
+{
+public:
+ explicit COverrideFile(bool writable);
+ ~COverrideFile() override;
+
+ bool Open(const CURL& url) override;
+ bool Exists(const CURL& url) override;
+ int Stat(const CURL& url, struct __stat64* buffer) override;
+ int Stat(struct __stat64* buffer) override;
+ bool OpenForWrite(const CURL& url, bool bOverWrite = false) override;
+ bool Delete(const CURL& url) override;
+ bool Rename(const CURL& url, const CURL& urlnew) override;
+
+ ssize_t Read(void* lpBuf, size_t uiBufSize) override;
+ ssize_t Write(const void* lpBuf, size_t uiBufSize) override;
+ int64_t Seek(int64_t iFilePosition, int iWhence = SEEK_SET) override;
+ void Close() override;
+ int64_t GetPosition() override;
+ int64_t GetLength() override;
+
+protected:
+ virtual std::string TranslatePath(const CURL &url) = 0;
+
+ CFile m_file;
+ bool m_writable;
+};
+}
diff --git a/xbmc/filesystem/PVRDirectory.cpp b/xbmc/filesystem/PVRDirectory.cpp
new file mode 100644
index 0000000..915d3f7
--- /dev/null
+++ b/xbmc/filesystem/PVRDirectory.cpp
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PVRDirectory.h"
+
+#include "pvr/filesystem/PVRGUIDirectory.h"
+
+using namespace XFILE;
+using namespace PVR;
+
+CPVRDirectory::CPVRDirectory() = default;
+
+CPVRDirectory::~CPVRDirectory() = default;
+
+bool CPVRDirectory::Exists(const CURL& url)
+{
+ const CPVRGUIDirectory dir(url);
+ return dir.Exists();
+}
+
+bool CPVRDirectory::GetDirectory(const CURL& url, CFileItemList &items)
+{
+ const CPVRGUIDirectory dir(url);
+ return dir.GetDirectory(items);
+}
+
+bool CPVRDirectory::SupportsWriteFileOperations(const std::string& strPath)
+{
+ const CPVRGUIDirectory dir(strPath);
+ return dir.SupportsWriteFileOperations();
+}
+
+bool CPVRDirectory::HasTVRecordings()
+{
+ return CPVRGUIDirectory::HasTVRecordings();
+}
+
+bool CPVRDirectory::HasDeletedTVRecordings()
+{
+ return CPVRGUIDirectory::HasDeletedTVRecordings();
+}
+
+bool CPVRDirectory::HasRadioRecordings()
+{
+ return CPVRGUIDirectory::HasRadioRecordings();
+}
+
+bool CPVRDirectory::HasDeletedRadioRecordings()
+{
+ return CPVRGUIDirectory::HasDeletedRadioRecordings();
+}
diff --git a/xbmc/filesystem/PVRDirectory.h b/xbmc/filesystem/PVRDirectory.h
new file mode 100644
index 0000000..8745fb1
--- /dev/null
+++ b/xbmc/filesystem/PVRDirectory.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IDirectory.h"
+
+namespace XFILE {
+
+class CPVRDirectory
+ : public IDirectory
+{
+public:
+ CPVRDirectory();
+ ~CPVRDirectory() override;
+
+ bool GetDirectory(const CURL& url, CFileItemList &items) override;
+ bool AllowAll() const override { return true; }
+ DIR_CACHE_TYPE GetCacheType(const CURL& url) const override { return DIR_CACHE_NEVER; }
+ bool Exists(const CURL& url) override;
+
+ static bool SupportsWriteFileOperations(const std::string& strPath);
+
+ static bool HasTVRecordings();
+ static bool HasDeletedTVRecordings();
+ static bool HasRadioRecordings();
+ static bool HasDeletedRadioRecordings();
+};
+
+}
diff --git a/xbmc/filesystem/PipeFile.cpp b/xbmc/filesystem/PipeFile.cpp
new file mode 100644
index 0000000..f732843
--- /dev/null
+++ b/xbmc/filesystem/PipeFile.cpp
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2011-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PipeFile.h"
+
+#include "PipesManager.h"
+#include "URL.h"
+
+#include <mutex>
+
+using namespace XFILE;
+
+CPipeFile::CPipeFile() : m_pipe(NULL)
+{
+}
+
+CPipeFile::~CPipeFile()
+{
+ Close();
+}
+
+int64_t CPipeFile::GetPosition()
+{
+ return m_pos;
+}
+
+int64_t CPipeFile::GetLength()
+{
+ return m_length;
+}
+
+void CPipeFile::SetLength(int64_t len)
+{
+ m_length = len;
+}
+
+bool CPipeFile::Open(const CURL& url)
+{
+ std::string name = url.Get();
+ m_pipe = PipesManager::GetInstance().OpenPipe(name);
+ if (m_pipe)
+ m_pipe->AddListener(this);
+ return (m_pipe != NULL);
+}
+
+bool CPipeFile::Exists(const CURL& url)
+{
+ std::string name = url.Get();
+ return PipesManager::GetInstance().Exists(name);
+}
+
+int CPipeFile::Stat(const CURL& url, struct __stat64* buffer)
+{
+ return -1;
+}
+
+int CPipeFile::Stat(struct __stat64* buffer)
+{
+ if (!buffer)
+ return -1;
+
+ *buffer = {};
+ buffer->st_size = m_length;
+ return 0;
+}
+
+ssize_t CPipeFile::Read(void* lpBuf, size_t uiBufSize)
+{
+ if (!m_pipe)
+ return -1;
+
+ if (uiBufSize > SSIZE_MAX)
+ uiBufSize = SSIZE_MAX;
+
+ return m_pipe->Read((char *)lpBuf,(int)uiBufSize);
+}
+
+ssize_t CPipeFile::Write(const void* lpBuf, size_t uiBufSize)
+{
+ if (!m_pipe)
+ return -1;
+
+ // m_pipe->Write return bool. either all was written or not.
+ return m_pipe->Write((const char *)lpBuf,uiBufSize) ? uiBufSize : -1;
+}
+
+void CPipeFile::SetEof()
+{
+ if (!m_pipe)
+ return ;
+ m_pipe->SetEof();
+}
+
+bool CPipeFile::IsEof()
+{
+ if (!m_pipe)
+ return true;
+ return m_pipe->IsEof();
+}
+
+bool CPipeFile::IsEmpty()
+{
+ if (!m_pipe)
+ return true;
+ return m_pipe->IsEmpty();
+}
+
+int64_t CPipeFile::Seek(int64_t iFilePosition, int iWhence)
+{
+ return -1;
+}
+
+void CPipeFile::Close()
+{
+ if (m_pipe)
+ {
+ m_pipe->RemoveListener(this);
+ PipesManager::GetInstance().ClosePipe(m_pipe);
+ }
+ m_pipe = NULL;
+}
+
+bool CPipeFile::IsClosed()
+{
+ return (m_pipe == NULL);
+}
+
+void CPipeFile::Flush()
+{
+ if (m_pipe)
+ m_pipe->Flush();
+}
+
+bool CPipeFile::OpenForWrite(const CURL& url, bool bOverWrite)
+{
+ std::string name = url.Get();
+
+ m_pipe = PipesManager::GetInstance().CreatePipe(name);
+ if (m_pipe)
+ m_pipe->AddListener(this);
+ return (m_pipe != NULL);
+}
+
+bool CPipeFile::Delete(const CURL& url)
+{
+ return false;
+}
+
+bool CPipeFile::Rename(const CURL& url, const CURL& urlnew)
+{
+ return false;
+}
+
+int CPipeFile::IoControl(EIoControl, void* param)
+{
+ return -1;
+}
+
+std::string CPipeFile::GetName() const
+{
+ if (!m_pipe)
+ return "";
+ return m_pipe->GetName();
+}
+
+void CPipeFile::OnPipeOverFlow()
+{
+ std::unique_lock<CCriticalSection> lock(m_lock);
+ for (size_t l=0; l<m_listeners.size(); l++)
+ m_listeners[l]->OnPipeOverFlow();
+}
+
+int64_t CPipeFile::GetAvailableRead()
+{
+ return m_pipe->GetAvailableRead();
+}
+
+void CPipeFile::OnPipeUnderFlow()
+{
+ for (size_t l=0; l<m_listeners.size(); l++)
+ m_listeners[l]->OnPipeUnderFlow();
+}
+
+void CPipeFile::AddListener(IPipeListener *l)
+{
+ std::unique_lock<CCriticalSection> lock(m_lock);
+ for (size_t i=0; i<m_listeners.size(); i++)
+ {
+ if (m_listeners[i] == l)
+ return;
+ }
+ m_listeners.push_back(l);
+}
+
+void CPipeFile::RemoveListener(IPipeListener *l)
+{
+ std::unique_lock<CCriticalSection> lock(m_lock);
+ std::vector<XFILE::IPipeListener *>::iterator i = m_listeners.begin();
+ while(i != m_listeners.end())
+ {
+ if ( (*i) == l)
+ i = m_listeners.erase(i);
+ else
+ ++i;
+ }
+}
+
+void CPipeFile::SetOpenThreshold(int threshold)
+{
+ m_pipe->SetOpenThreshold(threshold);
+}
+
diff --git a/xbmc/filesystem/PipeFile.h b/xbmc/filesystem/PipeFile.h
new file mode 100644
index 0000000..507006b
--- /dev/null
+++ b/xbmc/filesystem/PipeFile.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2002 Frodo
+ * Portions Copyright (c) by the authors of ffmpeg and xvid
+ * Copyright (C) 2002-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+// FilePipe.h: interface for the CPipeFile class.
+//
+//////////////////////////////////////////////////////////////////////
+
+#include "IFile.h"
+#include "PipesManager.h"
+#include "threads/CriticalSection.h"
+
+#include <string>
+#include <vector>
+
+namespace XFILE
+{
+
+class CPipeFile : public IFile, public IPipeListener
+{
+public:
+ CPipeFile();
+ ~CPipeFile() override;
+ int64_t GetPosition() override;
+ int64_t GetLength() override;
+ virtual void SetLength(int64_t len);
+ bool Open(const CURL& url) override;
+ bool Exists(const CURL& url) override;
+ int Stat(const CURL& url, struct __stat64* buffer) override;
+ int Stat(struct __stat64* buffer) override;
+ ssize_t Read(void* lpBuf, size_t uiBufSize) override;
+ ssize_t Write(const void* lpBuf, size_t uiBufSize) override;
+ int64_t Seek(int64_t iFilePosition, int iWhence = SEEK_SET) override;
+ void Close() override;
+ void Flush() override;
+ virtual int64_t GetAvailableRead();
+
+ bool OpenForWrite(const CURL& url, bool bOverWrite = false) override;
+
+ bool Delete(const CURL& url) override;
+ bool Rename(const CURL& url, const CURL& urlnew) override;
+ int IoControl(EIoControl request, void* param) override;
+
+ std::string GetName() const;
+
+ void OnPipeOverFlow() override;
+ void OnPipeUnderFlow() override;
+
+ void AddListener(IPipeListener *l);
+ void RemoveListener(IPipeListener *l);
+
+ void SetEof();
+ bool IsEof();
+ bool IsEmpty();
+ bool IsClosed();
+
+ void SetOpenThreshold(int threshold);
+
+protected:
+ int64_t m_pos = 0;
+ int64_t m_length = -1;
+
+ XFILE::Pipe *m_pipe;
+
+ CCriticalSection m_lock;
+ std::vector<XFILE::IPipeListener *> m_listeners;
+};
+
+}
diff --git a/xbmc/filesystem/PipesManager.cpp b/xbmc/filesystem/PipesManager.cpp
new file mode 100644
index 0000000..58b9099
--- /dev/null
+++ b/xbmc/filesystem/PipesManager.cpp
@@ -0,0 +1,314 @@
+/*
+ * Copyright (C) 2011-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PipesManager.h"
+
+#include "utils/StringUtils.h"
+
+#include <algorithm>
+#include <mutex>
+
+using namespace XFILE;
+using namespace std::chrono_literals;
+
+Pipe::Pipe(const std::string &name, int nMaxSize)
+{
+ m_buffer.Create(nMaxSize);
+ m_nRefCount = 1;
+ m_readEvent.Reset();
+ m_writeEvent.Set();
+ m_strPipeName = name;
+ m_bOpen = true;
+ m_bEof = false;
+ m_nOpenThreshold = PIPE_DEFAULT_MAX_SIZE / 2;
+ m_bReadyForRead = true; // open threshold disabled atm
+}
+
+Pipe::~Pipe() = default;
+
+void Pipe::SetOpenThreshold(int threshold)
+{
+ m_nOpenThreshold = threshold;
+}
+
+const std::string &Pipe::GetName()
+{
+ return m_strPipeName;
+}
+
+void Pipe::AddRef()
+{
+ std::unique_lock<CCriticalSection> lock(m_lock);
+ m_nRefCount++;
+}
+
+void Pipe::DecRef()
+{
+ std::unique_lock<CCriticalSection> lock(m_lock);
+ m_nRefCount--;
+}
+
+int Pipe::RefCount()
+{
+ std::unique_lock<CCriticalSection> lock(m_lock);
+ return m_nRefCount;
+}
+
+void Pipe::SetEof()
+{
+ m_bEof = true;
+}
+
+bool Pipe::IsEof()
+{
+ return m_bEof;
+}
+
+bool Pipe::IsEmpty()
+{
+ return (m_buffer.getMaxReadSize() == 0);
+}
+
+void Pipe::Flush()
+{
+ std::unique_lock<CCriticalSection> lock(m_lock);
+
+ if (!m_bOpen || !m_bReadyForRead || m_bEof)
+ {
+ return;
+ }
+ m_buffer.Clear();
+ CheckStatus();
+}
+
+int Pipe::Read(char *buf, int nMaxSize, int nWaitMillis)
+{
+ std::unique_lock<CCriticalSection> lock(m_lock);
+
+ if (!m_bOpen)
+ {
+ return -1;
+ }
+
+ while (!m_bReadyForRead && !m_bEof)
+ m_readEvent.Wait(100ms);
+
+ int nResult = 0;
+ if (!IsEmpty())
+ {
+ int nToRead = std::min(static_cast<int>(m_buffer.getMaxReadSize()), nMaxSize);
+ m_buffer.ReadData(buf, nToRead);
+ nResult = nToRead;
+ }
+ else if (m_bEof)
+ {
+ nResult = 0;
+ }
+ else
+ {
+ // we're leaving the guard - add ref to make sure we are not getting erased.
+ // at the moment we leave m_listeners unprotected which might be a problem in future
+ // but as long as we only have 1 listener attaching at startup and detaching on close we're fine
+ AddRef();
+ lock.unlock();
+
+ bool bHasData = false;
+ auto nMillisLeft = std::chrono::milliseconds(nWaitMillis);
+ if (nMillisLeft < 0ms)
+ nMillisLeft = 300000ms; // arbitrary. 5 min.
+
+ do
+ {
+ for (size_t l=0; l<m_listeners.size(); l++)
+ m_listeners[l]->OnPipeUnderFlow();
+
+ bHasData = m_readEvent.Wait(std::min(200ms, nMillisLeft));
+ nMillisLeft -= 200ms;
+ } while (!bHasData && nMillisLeft > 0ms && !m_bEof);
+
+ lock.lock();
+ DecRef();
+
+ if (!m_bOpen)
+ return -1;
+
+ if (bHasData)
+ {
+ int nToRead = std::min(static_cast<int>(m_buffer.getMaxReadSize()), nMaxSize);
+ m_buffer.ReadData(buf, nToRead);
+ nResult = nToRead;
+ }
+ }
+
+ CheckStatus();
+
+ return nResult;
+}
+
+bool Pipe::Write(const char *buf, int nSize, int nWaitMillis)
+{
+ std::unique_lock<CCriticalSection> lock(m_lock);
+ if (!m_bOpen)
+ return false;
+ bool bOk = false;
+ int writeSize = m_buffer.getMaxWriteSize();
+ if (writeSize > nSize)
+ {
+ m_buffer.WriteData(buf, nSize);
+ bOk = true;
+ }
+ else
+ {
+ while ( (int)m_buffer.getMaxWriteSize() < nSize && m_bOpen )
+ {
+ lock.unlock();
+ for (size_t l=0; l<m_listeners.size(); l++)
+ m_listeners[l]->OnPipeOverFlow();
+
+ bool bClear = nWaitMillis < 0 ? m_writeEvent.Wait()
+ : m_writeEvent.Wait(std::chrono::milliseconds(nWaitMillis));
+ lock.lock();
+ if (bClear && (int)m_buffer.getMaxWriteSize() >= nSize)
+ {
+ m_buffer.WriteData(buf, nSize);
+ bOk = true;
+ break;
+ }
+
+ // FIXME: is this right? Shouldn't we see if the time limit has been reached?
+ if (nWaitMillis > 0)
+ break;
+ }
+ }
+
+ CheckStatus();
+
+ return bOk && m_bOpen;
+}
+
+void Pipe::CheckStatus()
+{
+ if (m_bEof)
+ {
+ m_writeEvent.Set();
+ m_readEvent.Set();
+ return;
+ }
+
+ if (m_buffer.getMaxWriteSize() == 0)
+ m_writeEvent.Reset();
+ else
+ m_writeEvent.Set();
+
+ if (m_buffer.getMaxReadSize() == 0)
+ m_readEvent.Reset();
+ else
+ {
+ if (!m_bReadyForRead && (int)m_buffer.getMaxReadSize() >= m_nOpenThreshold)
+ m_bReadyForRead = true;
+ m_readEvent.Set();
+ }
+}
+
+void Pipe::Close()
+{
+ std::unique_lock<CCriticalSection> lock(m_lock);
+ m_bOpen = false;
+ m_readEvent.Set();
+ m_writeEvent.Set();
+}
+
+void Pipe::AddListener(IPipeListener *l)
+{
+ std::unique_lock<CCriticalSection> lock(m_lock);
+ for (size_t i=0; i<m_listeners.size(); i++)
+ {
+ if (m_listeners[i] == l)
+ return;
+ }
+ m_listeners.push_back(l);
+}
+
+void Pipe::RemoveListener(IPipeListener *l)
+{
+ std::unique_lock<CCriticalSection> lock(m_lock);
+ std::vector<XFILE::IPipeListener *>::iterator i = m_listeners.begin();
+ while(i != m_listeners.end())
+ {
+ if ( (*i) == l)
+ i = m_listeners.erase(i);
+ else
+ ++i;
+ }
+}
+
+int Pipe::GetAvailableRead()
+{
+ std::unique_lock<CCriticalSection> lock(m_lock);
+ return m_buffer.getMaxReadSize();
+}
+
+PipesManager::~PipesManager() = default;
+
+PipesManager &PipesManager::GetInstance()
+{
+ static PipesManager instance;
+ return instance;
+}
+
+std::string PipesManager::GetUniquePipeName()
+{
+ std::unique_lock<CCriticalSection> lock(m_lock);
+ return StringUtils::Format("pipe://{}/", m_nGenIdHelper++);
+}
+
+XFILE::Pipe *PipesManager::CreatePipe(const std::string &name, int nMaxPipeSize)
+{
+ std::string pName = name;
+ if (pName.empty())
+ pName = GetUniquePipeName();
+
+ std::unique_lock<CCriticalSection> lock(m_lock);
+ if (m_pipes.find(pName) != m_pipes.end())
+ return NULL;
+
+ XFILE::Pipe *p = new XFILE::Pipe(pName, nMaxPipeSize);
+ m_pipes[pName] = p;
+ return p;
+}
+
+XFILE::Pipe *PipesManager::OpenPipe(const std::string &name)
+{
+ std::unique_lock<CCriticalSection> lock(m_lock);
+ if (m_pipes.find(name) == m_pipes.end())
+ return NULL;
+ m_pipes[name]->AddRef();
+ return m_pipes[name];
+}
+
+void PipesManager::ClosePipe(XFILE::Pipe *pipe)
+{
+ std::unique_lock<CCriticalSection> lock(m_lock);
+ if (!pipe)
+ return ;
+
+ pipe->DecRef();
+ if (pipe->RefCount() == 0)
+ {
+ pipe->Close();
+ m_pipes.erase(pipe->GetName());
+ delete pipe;
+ }
+}
+
+bool PipesManager::Exists(const std::string &name)
+{
+ std::unique_lock<CCriticalSection> lock(m_lock);
+ return (m_pipes.find(name) != m_pipes.end());
+}
+
diff --git a/xbmc/filesystem/PipesManager.h b/xbmc/filesystem/PipesManager.h
new file mode 100644
index 0000000..100f7d8
--- /dev/null
+++ b/xbmc/filesystem/PipesManager.h
@@ -0,0 +1,120 @@
+/*
+ * Many concepts and protocol are taken from
+ * the Boxee project. http://www.boxee.tv
+ *
+ * Copyright (C) 2011-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "threads/CriticalSection.h"
+#include "threads/Event.h"
+#include "utils/RingBuffer.h"
+
+#include <map>
+#include <string>
+#include <vector>
+
+#define PIPE_DEFAULT_MAX_SIZE (6 * 1024 * 1024)
+
+namespace XFILE
+{
+
+class IPipeListener
+{
+public:
+ virtual ~IPipeListener() = default;
+ virtual void OnPipeOverFlow() = 0;
+ virtual void OnPipeUnderFlow() = 0;
+};
+
+class Pipe
+ {
+ public:
+ Pipe(const std::string &name, int nMaxSize = PIPE_DEFAULT_MAX_SIZE );
+ virtual ~Pipe();
+ const std::string &GetName();
+
+ void AddRef();
+ void DecRef(); // a pipe does NOT delete itself with ref-count 0.
+ int RefCount();
+
+ bool IsEmpty();
+
+ /**
+ * Read into the buffer from the Pipe the num of bytes asked for
+ * blocking forever (which happens to be 5 minutes in this case).
+ *
+ * In the case where nWaitMillis is provided block for that number
+ * of milliseconds instead.
+ */
+ int Read(char *buf, int nMaxSize, int nWaitMillis = -1);
+
+ /**
+ * Write into the Pipe from the buffer the num of bytes asked for
+ * blocking forever.
+ *
+ * In the case where nWaitMillis is provided block for that number
+ * of milliseconds instead.
+ */
+ bool Write(const char *buf, int nSize, int nWaitMillis = -1);
+
+ void Flush();
+
+ void CheckStatus();
+ void Close();
+
+ void AddListener(IPipeListener *l);
+ void RemoveListener(IPipeListener *l);
+
+ void SetEof();
+ bool IsEof();
+
+ int GetAvailableRead();
+ void SetOpenThreshold(int threshold);
+
+ protected:
+
+ bool m_bOpen;
+ bool m_bReadyForRead;
+
+ bool m_bEof;
+ CRingBuffer m_buffer;
+ std::string m_strPipeName;
+ int m_nRefCount;
+ int m_nOpenThreshold;
+
+ CEvent m_readEvent;
+ CEvent m_writeEvent;
+
+ std::vector<XFILE::IPipeListener *> m_listeners;
+
+ CCriticalSection m_lock;
+ };
+
+
+class PipesManager
+{
+public:
+ virtual ~PipesManager();
+ static PipesManager &GetInstance();
+
+ std::string GetUniquePipeName();
+ XFILE::Pipe *CreatePipe(const std::string &name="", int nMaxPipeSize = PIPE_DEFAULT_MAX_SIZE);
+ XFILE::Pipe *OpenPipe(const std::string &name);
+ void ClosePipe(XFILE::Pipe *pipe);
+ bool Exists(const std::string &name);
+
+protected:
+ int m_nGenIdHelper = 1;
+ std::map<std::string, XFILE::Pipe *> m_pipes;
+
+ CCriticalSection m_lock;
+};
+
+}
+
diff --git a/xbmc/filesystem/PlaylistDirectory.cpp b/xbmc/filesystem/PlaylistDirectory.cpp
new file mode 100644
index 0000000..8be88dc
--- /dev/null
+++ b/xbmc/filesystem/PlaylistDirectory.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PlaylistDirectory.h"
+
+#include "FileItem.h"
+#include "PlayListPlayer.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "playlists/PlayList.h"
+
+using namespace XFILE;
+
+CPlaylistDirectory::CPlaylistDirectory() = default;
+
+CPlaylistDirectory::~CPlaylistDirectory() = default;
+
+bool CPlaylistDirectory::GetDirectory(const CURL& url, CFileItemList &items)
+{
+ PLAYLIST::Id playlistId = PLAYLIST::TYPE_NONE;
+ if (url.IsProtocol("playlistmusic"))
+ playlistId = PLAYLIST::TYPE_MUSIC;
+ else if (url.IsProtocol("playlistvideo"))
+ playlistId = PLAYLIST::TYPE_VIDEO;
+
+ if (playlistId == PLAYLIST::TYPE_NONE)
+ return false;
+
+ const PLAYLIST::CPlayList& playlist = CServiceBroker::GetPlaylistPlayer().GetPlaylist(playlistId);
+ items.Reserve(playlist.size());
+
+ for (int i = 0; i < playlist.size(); ++i)
+ {
+ CFileItemPtr item = playlist[i];
+ item->SetProperty("playlistposition", i);
+ item->SetProperty("playlisttype", playlistId);
+ //item->m_iprogramCount = i; // the programCount is set as items are added!
+ items.Add(item);
+ }
+
+ return true;
+}
diff --git a/xbmc/filesystem/PlaylistDirectory.h b/xbmc/filesystem/PlaylistDirectory.h
new file mode 100644
index 0000000..416c0ea
--- /dev/null
+++ b/xbmc/filesystem/PlaylistDirectory.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IDirectory.h"
+
+namespace XFILE
+{
+ class CPlaylistDirectory : public IDirectory
+ {
+ public:
+ CPlaylistDirectory(void);
+ ~CPlaylistDirectory(void) override;
+ bool GetDirectory(const CURL& url, CFileItemList &items) override;
+ bool AllowAll() const override { return true; }
+ };
+}
diff --git a/xbmc/filesystem/PlaylistFileDirectory.cpp b/xbmc/filesystem/PlaylistFileDirectory.cpp
new file mode 100644
index 0000000..2bdb243
--- /dev/null
+++ b/xbmc/filesystem/PlaylistFileDirectory.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PlaylistFileDirectory.h"
+
+#include "FileItem.h"
+#include "URL.h"
+#include "filesystem/File.h"
+#include "playlists/PlayList.h"
+#include "playlists/PlayListFactory.h"
+
+using namespace PLAYLIST;
+
+namespace XFILE
+{
+ CPlaylistFileDirectory::CPlaylistFileDirectory() = default;
+
+ CPlaylistFileDirectory::~CPlaylistFileDirectory() = default;
+
+ bool CPlaylistFileDirectory::GetDirectory(const CURL& url, CFileItemList& items)
+ {
+ const std::string pathToUrl = url.Get();
+ std::unique_ptr<CPlayList> pPlayList (CPlayListFactory::Create(pathToUrl));
+ if (nullptr != pPlayList)
+ {
+ // load it
+ if (!pPlayList->Load(pathToUrl))
+ return false; //hmmm unable to load playlist?
+
+ CPlayList playlist = *pPlayList;
+ // convert playlist items to songs
+ for (int i = 0; i < playlist.size(); ++i)
+ {
+ CFileItemPtr item = playlist[i];
+ item->m_iprogramCount = i; // hack for playlist order
+ items.Add(item);
+ }
+ }
+ return true;
+ }
+
+ bool CPlaylistFileDirectory::ContainsFiles(const CURL& url)
+ {
+ const std::string pathToUrl = url.Get();
+ std::unique_ptr<CPlayList> pPlayList (CPlayListFactory::Create(pathToUrl));
+ if (nullptr != pPlayList)
+ {
+ // load it
+ if (!pPlayList->Load(pathToUrl))
+ return false; //hmmm unable to load playlist?
+
+ return (pPlayList->size() > 1);
+ }
+ return false;
+ }
+
+ bool CPlaylistFileDirectory::Remove(const CURL& url)
+ {
+ return XFILE::CFile::Delete(url);
+ }
+}
+
diff --git a/xbmc/filesystem/PlaylistFileDirectory.h b/xbmc/filesystem/PlaylistFileDirectory.h
new file mode 100644
index 0000000..9f956ce
--- /dev/null
+++ b/xbmc/filesystem/PlaylistFileDirectory.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IFileDirectory.h"
+
+namespace XFILE
+{
+ class CPlaylistFileDirectory : public IFileDirectory
+ {
+ public:
+ CPlaylistFileDirectory();
+ ~CPlaylistFileDirectory() override;
+ bool GetDirectory(const CURL& url, CFileItemList& items) override;
+ bool ContainsFiles(const CURL& url) override;
+ bool Remove(const CURL& url) override;
+ bool AllowAll() const override { return true; }
+ };
+}
diff --git a/xbmc/filesystem/PluginDirectory.cpp b/xbmc/filesystem/PluginDirectory.cpp
new file mode 100644
index 0000000..8273886
--- /dev/null
+++ b/xbmc/filesystem/PluginDirectory.cpp
@@ -0,0 +1,548 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PluginDirectory.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "addons/AddonInstaller.h"
+#include "addons/AddonManager.h"
+#include "addons/IAddon.h"
+#include "addons/PluginSource.h"
+#include "addons/addoninfo/AddonType.h"
+#include "interfaces/generic/RunningScriptObserver.h"
+#include "messaging/ApplicationMessenger.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+#include "video/VideoInfoTag.h"
+
+#include <mutex>
+
+using namespace XFILE;
+using namespace ADDON;
+using namespace KODI::MESSAGING;
+
+namespace
+{
+const unsigned int maxPluginResolutions = 5;
+
+/*!
+ \brief Get the plugin path from a CFileItem.
+
+ \param item CFileItem where to get the path.
+ \return The plugin path if found otherwise an empty string.
+*/
+std::string GetOriginalPluginPath(const CFileItem& item)
+{
+ std::string currentPath = item.GetPath();
+ if (URIUtils::IsPlugin(currentPath))
+ return currentPath;
+
+ currentPath = item.GetDynPath();
+ if (URIUtils::IsPlugin(currentPath))
+ return currentPath;
+
+ return std::string();
+}
+} // unnamed namespace
+
+CPluginDirectory::CPluginDirectory()
+ : m_listItems(new CFileItemList), m_fileResult(new CFileItem), m_cancelled(false)
+
+{
+}
+
+CPluginDirectory::~CPluginDirectory(void)
+{
+}
+
+bool CPluginDirectory::StartScript(const std::string& strPath, bool resume)
+{
+ CURL url(strPath);
+
+ ADDON::AddonPtr addon;
+ // try the plugin type first, and if not found, try an unknown type
+ if (!CServiceBroker::GetAddonMgr().GetAddon(url.GetHostName(), addon, AddonType::PLUGIN,
+ OnlyEnabled::CHOICE_YES) &&
+ !CServiceBroker::GetAddonMgr().GetAddon(url.GetHostName(), addon, AddonType::UNKNOWN,
+ OnlyEnabled::CHOICE_YES) &&
+ !CAddonInstaller::GetInstance().InstallModal(url.GetHostName(), addon,
+ InstallModalPrompt::CHOICE_YES))
+ {
+ CLog::Log(LOGERROR, "Unable to find plugin {}", url.GetHostName());
+ return false;
+ }
+
+ // clear out our status variables
+ m_fileResult->Reset();
+ m_listItems->Clear();
+ m_listItems->SetPath(strPath);
+ m_listItems->SetLabel(addon->Name());
+ m_cancelled = false;
+ m_success = false;
+ m_totalItems = 0;
+
+ // run the script
+ return RunScript(this, addon, strPath, resume);
+}
+
+bool CPluginDirectory::GetResolvedPluginResult(CFileItem& resultItem)
+{
+ std::string lastResolvedPath;
+ if (resultItem.HasProperty("ForceResolvePlugin") &&
+ resultItem.GetProperty("ForceResolvePlugin").asBoolean())
+ {
+ // ensures that a plugin have the callback to resolve the paths in any case
+ // also when the same items in the playlist are played more times
+ lastResolvedPath = GetOriginalPluginPath(resultItem);
+ }
+ else
+ {
+ lastResolvedPath = resultItem.GetDynPath();
+ }
+
+ if (!lastResolvedPath.empty())
+ {
+ // we try to resolve recursively up to n. (maxPluginResolutions) nested plugin paths
+ // to avoid deadlocks (plugin:// paths can resolve to plugin:// paths)
+ for (unsigned int i = 0; URIUtils::IsPlugin(lastResolvedPath) && i < maxPluginResolutions; ++i)
+ {
+ bool resume = resultItem.GetStartOffset() == STARTOFFSET_RESUME;
+
+ // we modify the item so that it becomes a real URL
+ if (!XFILE::CPluginDirectory::GetPluginResult(lastResolvedPath, resultItem, resume) ||
+ resultItem.GetDynPath() ==
+ resultItem.GetPath()) // GetPluginResult resolved to an empty path
+ {
+ return false;
+ }
+ lastResolvedPath = resultItem.GetDynPath();
+ }
+ // if after the maximum allowed resolution attempts the item is still a plugin just return, it isn't playable
+ if (URIUtils::IsPlugin(resultItem.GetDynPath()))
+ return false;
+ }
+
+ return true;
+}
+
+bool CPluginDirectory::GetPluginResult(const std::string& strPath, CFileItem &resultItem, bool resume)
+{
+ CURL url(strPath);
+ CPluginDirectory newDir;
+
+ bool success = newDir.StartScript(strPath, resume);
+
+ if (success)
+ { // update the play path and metadata, saving the old one as needed
+ if (!resultItem.HasProperty("original_listitem_url"))
+ resultItem.SetProperty("original_listitem_url", resultItem.GetPath());
+ resultItem.SetDynPath(newDir.m_fileResult->GetPath());
+ resultItem.SetMimeType(newDir.m_fileResult->GetMimeType());
+ resultItem.SetContentLookup(newDir.m_fileResult->ContentLookup());
+
+ if (resultItem.HasProperty("OverrideInfotag") &&
+ resultItem.GetProperty("OverrideInfotag").asBoolean())
+ resultItem.UpdateInfo(*newDir.m_fileResult);
+ else
+ resultItem.MergeInfo(*newDir.m_fileResult);
+
+ if (newDir.m_fileResult->HasVideoInfoTag() && newDir.m_fileResult->GetVideoInfoTag()->GetResumePoint().IsSet())
+ resultItem.SetStartOffset(
+ STARTOFFSET_RESUME); // resume point set in the resume item, so force resume
+ }
+
+ return success;
+}
+
+bool CPluginDirectory::AddItem(int handle, const CFileItem *item, int totalItems)
+{
+ std::unique_lock<CCriticalSection> lock(GetScriptsLock());
+ CPluginDirectory* dir = GetScriptFromHandle(handle);
+ if (!dir)
+ return false;
+
+ CFileItemPtr pItem(new CFileItem(*item));
+ dir->m_listItems->Add(pItem);
+ dir->m_totalItems = totalItems;
+
+ return !dir->m_cancelled;
+}
+
+bool CPluginDirectory::AddItems(int handle, const CFileItemList *items, int totalItems)
+{
+ std::unique_lock<CCriticalSection> lock(GetScriptsLock());
+ CPluginDirectory* dir = GetScriptFromHandle(handle);
+ if (!dir)
+ return false;
+
+ CFileItemList pItemList;
+ pItemList.Copy(*items);
+ dir->m_listItems->Append(pItemList);
+ dir->m_totalItems = totalItems;
+
+ return !dir->m_cancelled;
+}
+
+void CPluginDirectory::EndOfDirectory(int handle, bool success, bool replaceListing, bool cacheToDisc)
+{
+ std::unique_lock<CCriticalSection> lock(GetScriptsLock());
+ CPluginDirectory* dir = GetScriptFromHandle(handle);
+ if (!dir)
+ return;
+
+ // set cache to disc
+ dir->m_listItems->SetCacheToDisc(cacheToDisc ? CFileItemList::CACHE_IF_SLOW : CFileItemList::CACHE_NEVER);
+
+ dir->m_success = success;
+ dir->m_listItems->SetReplaceListing(replaceListing);
+
+ if (!dir->m_listItems->HasSortDetails())
+ dir->m_listItems->AddSortMethod(SortByNone, 552, LABEL_MASKS("%L", "%D"));
+
+ // set the event to mark that we're done
+ dir->SetDone();
+}
+
+void CPluginDirectory::AddSortMethod(int handle, SORT_METHOD sortMethod, const std::string &labelMask, const std::string &label2Mask)
+{
+ std::unique_lock<CCriticalSection> lock(GetScriptsLock());
+ CPluginDirectory* dir = GetScriptFromHandle(handle);
+ if (!dir)
+ return;
+
+ //! @todo Add all sort methods and fix which labels go on the right or left
+ switch(sortMethod)
+ {
+ case SORT_METHOD_LABEL:
+ case SORT_METHOD_LABEL_IGNORE_THE:
+ {
+ dir->m_listItems->AddSortMethod(SortByLabel, 551, LABEL_MASKS(labelMask, label2Mask, labelMask, label2Mask), CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone);
+ break;
+ }
+ case SORT_METHOD_TITLE:
+ case SORT_METHOD_TITLE_IGNORE_THE:
+ {
+ dir->m_listItems->AddSortMethod(SortByTitle, 556, LABEL_MASKS(labelMask, label2Mask, labelMask, label2Mask), CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone);
+ break;
+ }
+ case SORT_METHOD_ARTIST:
+ case SORT_METHOD_ARTIST_IGNORE_THE:
+ {
+ dir->m_listItems->AddSortMethod(SortByArtist, 557, LABEL_MASKS(labelMask, "%A", labelMask, "%A"), CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone);
+ break;
+ }
+ case SORT_METHOD_ALBUM:
+ case SORT_METHOD_ALBUM_IGNORE_THE:
+ {
+ dir->m_listItems->AddSortMethod(SortByAlbum, 558, LABEL_MASKS(labelMask, "%B", labelMask, "%B"), CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone);
+ break;
+ }
+ case SORT_METHOD_DATE:
+ {
+ dir->m_listItems->AddSortMethod(SortByDate, 552, LABEL_MASKS(labelMask, "%J", labelMask, "%J"));
+ break;
+ }
+ case SORT_METHOD_BITRATE:
+ {
+ dir->m_listItems->AddSortMethod(SortByBitrate, 623, LABEL_MASKS(labelMask, "%X", labelMask, "%X"));
+ break;
+ }
+ case SORT_METHOD_SIZE:
+ {
+ dir->m_listItems->AddSortMethod(SortBySize, 553, LABEL_MASKS(labelMask, "%I", labelMask, "%I"));
+ break;
+ }
+ case SORT_METHOD_FILE:
+ {
+ dir->m_listItems->AddSortMethod(SortByFile, 561, LABEL_MASKS(labelMask, label2Mask, labelMask, label2Mask));
+ break;
+ }
+ case SORT_METHOD_TRACKNUM:
+ {
+ dir->m_listItems->AddSortMethod(SortByTrackNumber, 554, LABEL_MASKS(labelMask, label2Mask, labelMask, label2Mask));
+ break;
+ }
+ case SORT_METHOD_DURATION:
+ case SORT_METHOD_VIDEO_RUNTIME:
+ {
+ dir->m_listItems->AddSortMethod(SortByTime, 180, LABEL_MASKS(labelMask, "%D", labelMask, "%D"));
+ break;
+ }
+ case SORT_METHOD_VIDEO_RATING:
+ case SORT_METHOD_SONG_RATING:
+ {
+ dir->m_listItems->AddSortMethod(SortByRating, 563, LABEL_MASKS(labelMask, "%R", labelMask, "%R"));
+ break;
+ }
+ case SORT_METHOD_YEAR:
+ {
+ dir->m_listItems->AddSortMethod(SortByYear, 562, LABEL_MASKS(labelMask, "%Y", labelMask, "%Y"));
+ break;
+ }
+ case SORT_METHOD_GENRE:
+ {
+ dir->m_listItems->AddSortMethod(SortByGenre, 515, LABEL_MASKS(labelMask, "%G", labelMask, "%G"));
+ break;
+ }
+ case SORT_METHOD_COUNTRY:
+ {
+ dir->m_listItems->AddSortMethod(SortByCountry, 574, LABEL_MASKS(labelMask, "%G", labelMask, "%G"));
+ break;
+ }
+ case SORT_METHOD_VIDEO_TITLE:
+ {
+ dir->m_listItems->AddSortMethod(SortByTitle, 369, LABEL_MASKS(labelMask, "%M", labelMask, "%M"));
+ break;
+ }
+ case SORT_METHOD_VIDEO_SORT_TITLE:
+ case SORT_METHOD_VIDEO_SORT_TITLE_IGNORE_THE:
+ {
+ dir->m_listItems->AddSortMethod(SortBySortTitle, 556, LABEL_MASKS(labelMask, "%M", labelMask, "%M"), CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone);
+ break;
+ }
+ case SORT_METHOD_VIDEO_ORIGINAL_TITLE:
+ case SORT_METHOD_VIDEO_ORIGINAL_TITLE_IGNORE_THE:
+ {
+ dir->m_listItems->AddSortMethod(
+ SortByOriginalTitle, 20376, LABEL_MASKS(labelMask, "%M", labelMask, "%M"),
+ CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
+ CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING)
+ ? SortAttributeIgnoreArticle
+ : SortAttributeNone);
+ break;
+ }
+ case SORT_METHOD_MPAA_RATING:
+ {
+ dir->m_listItems->AddSortMethod(SortByMPAA, 20074, LABEL_MASKS(labelMask, "%O", labelMask, "%O"));
+ break;
+ }
+ case SORT_METHOD_STUDIO:
+ case SORT_METHOD_STUDIO_IGNORE_THE:
+ {
+ dir->m_listItems->AddSortMethod(SortByStudio, 572, LABEL_MASKS(labelMask, "%U", labelMask, "%U"), CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone);
+ break;
+ }
+ case SORT_METHOD_PROGRAM_COUNT:
+ {
+ dir->m_listItems->AddSortMethod(SortByProgramCount, 567, LABEL_MASKS(labelMask, "%C", labelMask, "%C"));
+ break;
+ }
+ case SORT_METHOD_UNSORTED:
+ {
+ dir->m_listItems->AddSortMethod(SortByNone, 571, LABEL_MASKS(labelMask, label2Mask, labelMask, label2Mask));
+ break;
+ }
+ case SORT_METHOD_NONE:
+ {
+ dir->m_listItems->AddSortMethod(SortByNone, 552, LABEL_MASKS(labelMask, label2Mask, labelMask, label2Mask));
+ break;
+ }
+ case SORT_METHOD_DRIVE_TYPE:
+ {
+ dir->m_listItems->AddSortMethod(SortByDriveType, 564, LABEL_MASKS()); // Preformatted
+ break;
+ }
+ case SORT_METHOD_PLAYLIST_ORDER:
+ {
+ std::string strTrack=CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_MUSICFILES_TRACKFORMAT);
+ dir->m_listItems->AddSortMethod(SortByPlaylistOrder, 559, LABEL_MASKS(strTrack, "%D"));
+ break;
+ }
+ case SORT_METHOD_EPISODE:
+ {
+ dir->m_listItems->AddSortMethod(SortByEpisodeNumber, 20359, LABEL_MASKS(labelMask, "%R", labelMask, "%R"));
+ break;
+ }
+ case SORT_METHOD_PRODUCTIONCODE:
+ {
+ //dir->m_listItems.AddSortMethod(SORT_METHOD_PRODUCTIONCODE,20368,LABEL_MASKS("%E. %T","%P", "%E. %T","%P"));
+ dir->m_listItems->AddSortMethod(SortByProductionCode, 20368, LABEL_MASKS(labelMask, "%P", labelMask, "%P"));
+ break;
+ }
+ case SORT_METHOD_LISTENERS:
+ {
+ dir->m_listItems->AddSortMethod(SortByListeners, 20455, LABEL_MASKS(labelMask, "%W"));
+ break;
+ }
+ case SORT_METHOD_DATEADDED:
+ {
+ dir->m_listItems->AddSortMethod(SortByDateAdded, 570, LABEL_MASKS(labelMask, "%a"));
+ break;
+ }
+ case SORT_METHOD_FULLPATH:
+ {
+ dir->m_listItems->AddSortMethod(SortByPath, 573, LABEL_MASKS(labelMask, label2Mask, labelMask, label2Mask));
+ break;
+ }
+ case SORT_METHOD_LABEL_IGNORE_FOLDERS:
+ {
+ dir->m_listItems->AddSortMethod(SortByLabel, SortAttributeIgnoreFolders, 551, LABEL_MASKS(labelMask, label2Mask, labelMask, label2Mask));
+ break;
+ }
+ case SORT_METHOD_LASTPLAYED:
+ {
+ dir->m_listItems->AddSortMethod(SortByLastPlayed, 568, LABEL_MASKS(labelMask, "%G"));
+ break;
+ }
+ case SORT_METHOD_PLAYCOUNT:
+ {
+ dir->m_listItems->AddSortMethod(SortByPlaycount, 567, LABEL_MASKS(labelMask, "%V", labelMask, "%V"));
+ break;
+ }
+ case SORT_METHOD_CHANNEL:
+ {
+ dir->m_listItems->AddSortMethod(SortByChannel, 19029, LABEL_MASKS(labelMask, label2Mask, labelMask, label2Mask));
+ break;
+ }
+
+ default:
+ break;
+ }
+}
+
+bool CPluginDirectory::GetDirectory(const CURL& url, CFileItemList& items)
+{
+ const std::string pathToUrl(url.Get());
+ bool success = StartScript(pathToUrl, false);
+
+ // append the items to the list
+ items.Assign(*m_listItems, true); // true to keep the current items
+ m_listItems->Clear();
+ return success;
+}
+
+bool CPluginDirectory::RunScriptWithParams(const std::string& strPath, bool resume)
+{
+ CURL url(strPath);
+ if (url.GetHostName().empty()) // called with no script - should never happen
+ return false;
+
+ AddonPtr addon;
+ if (!CServiceBroker::GetAddonMgr().GetAddon(url.GetHostName(), addon, AddonType::PLUGIN,
+ OnlyEnabled::CHOICE_YES) &&
+ !CAddonInstaller::GetInstance().InstallModal(url.GetHostName(), addon,
+ InstallModalPrompt::CHOICE_YES))
+ {
+ CLog::Log(LOGERROR, "Unable to find plugin {}", url.GetHostName());
+ return false;
+ }
+
+ return ExecuteScript(addon, strPath, resume) >= 0;
+}
+
+void CPluginDirectory::SetResolvedUrl(int handle, bool success, const CFileItem *resultItem)
+{
+ std::unique_lock<CCriticalSection> lock(GetScriptsLock());
+ CPluginDirectory* dir = GetScriptFromHandle(handle);
+ if (!dir)
+ return;
+
+ dir->m_success = success;
+ *dir->m_fileResult = *resultItem;
+
+ // set the event to mark that we're done
+ dir->SetDone();
+}
+
+std::string CPluginDirectory::GetSetting(int handle, const std::string &strID)
+{
+ std::unique_lock<CCriticalSection> lock(GetScriptsLock());
+ CPluginDirectory* dir = GetScriptFromHandle(handle);
+ if (dir && dir->GetAddon())
+ return dir->GetAddon()->GetSetting(strID);
+ else
+ return "";
+}
+
+void CPluginDirectory::SetSetting(int handle, const std::string &strID, const std::string &value)
+{
+ std::unique_lock<CCriticalSection> lock(GetScriptsLock());
+ CPluginDirectory* dir = GetScriptFromHandle(handle);
+ if (dir && dir->GetAddon())
+ dir->GetAddon()->UpdateSetting(strID, value);
+}
+
+void CPluginDirectory::SetContent(int handle, const std::string &strContent)
+{
+ std::unique_lock<CCriticalSection> lock(GetScriptsLock());
+ CPluginDirectory* dir = GetScriptFromHandle(handle);
+ if (dir)
+ dir->m_listItems->SetContent(strContent);
+}
+
+void CPluginDirectory::SetProperty(int handle, const std::string &strProperty, const std::string &strValue)
+{
+ std::unique_lock<CCriticalSection> lock(GetScriptsLock());
+ CPluginDirectory* dir = GetScriptFromHandle(handle);
+ if (!dir)
+ return;
+ if (strProperty == "fanart_image")
+ dir->m_listItems->SetArt("fanart", strValue);
+ else
+ dir->m_listItems->SetProperty(strProperty, strValue);
+}
+
+void CPluginDirectory::CancelDirectory()
+{
+ m_cancelled = true;
+}
+
+float CPluginDirectory::GetProgress() const
+{
+ if (m_totalItems > 0)
+ return (m_listItems->Size() * 100.0f) / m_totalItems;
+ return 0.0f;
+}
+
+bool CPluginDirectory::IsMediaLibraryScanningAllowed(const std::string& content, const std::string& strPath)
+{
+ if (content.empty())
+ return false;
+
+ CURL url(strPath);
+ if (url.GetHostName().empty())
+ return false;
+ AddonPtr addon;
+ if (!CServiceBroker::GetAddonMgr().GetAddon(url.GetHostName(), addon, AddonType::PLUGIN,
+ OnlyEnabled::CHOICE_YES))
+ {
+ CLog::Log(LOGERROR, "Unable to find plugin {}", url.GetHostName());
+ return false;
+ }
+ CPluginSource* plugin = dynamic_cast<CPluginSource*>(addon.get());
+ if (!plugin)
+ return false;
+
+ auto& paths = plugin->MediaLibraryScanPaths();
+ if (paths.empty())
+ return false;
+ auto it = paths.find(content);
+ if (it == paths.end())
+ return false;
+ const std::string& path = url.GetFileName();
+ for (const auto& p : it->second)
+ if (p.empty() || p == "/" || URIUtils::PathHasParent(path, p))
+ return true;
+ return false;
+}
+
+bool CPluginDirectory::CheckExists(const std::string& content, const std::string& strPath)
+{
+ if (!IsMediaLibraryScanningAllowed(content, strPath))
+ return false;
+ // call the plugin at specified path with option "kodi_action=check_exists"
+ // url exists if the plugin returns any fileitem with setResolvedUrl
+ CURL url(strPath);
+ url.SetOption("kodi_action", "check_exists");
+ CFileItem item;
+ return CPluginDirectory::GetPluginResult(url.Get(), item, false);
+}
diff --git a/xbmc/filesystem/PluginDirectory.h b/xbmc/filesystem/PluginDirectory.h
new file mode 100644
index 0000000..ebcfd13
--- /dev/null
+++ b/xbmc/filesystem/PluginDirectory.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IDirectory.h"
+#include "SortFileItem.h"
+#include "interfaces/generic/RunningScriptsHandler.h"
+#include "threads/Event.h"
+
+#include <atomic>
+#include <memory>
+#include <string>
+
+class CURL;
+class CFileItem;
+class CFileItemList;
+
+namespace XFILE
+{
+
+class CPluginDirectory : public IDirectory, public CRunningScriptsHandler<CPluginDirectory>
+{
+public:
+ CPluginDirectory();
+ ~CPluginDirectory(void) override;
+ bool GetDirectory(const CURL& url, CFileItemList& items) override;
+ bool AllowAll() const override { return true; }
+ bool Exists(const CURL& url) override { return true; }
+ float GetProgress() const override;
+ void CancelDirectory() override;
+ static bool RunScriptWithParams(const std::string& strPath, bool resume);
+
+ /*! \brief Get a reproducible CFileItem by trying to recursively resolve the plugin paths
+ up to a maximum allowed limit. If no plugin paths exist it will be ignored.
+ \param resultItem the CFileItem with plugin paths to be resolved.
+ \return false if the plugin path cannot be resolved, true otherwise.
+ */
+ static bool GetResolvedPluginResult(CFileItem& resultItem);
+ static bool GetPluginResult(const std::string& strPath, CFileItem &resultItem, bool resume);
+
+ /*! \brief Check whether a plugin supports media library scanning.
+ \param content content type - movies, tvshows, musicvideos.
+ \param strPath full plugin url.
+ \return true if scanning at specified url is allowed, false otherwise.
+ */
+ static bool IsMediaLibraryScanningAllowed(const std::string& content, const std::string& strPath);
+
+ /*! \brief Check whether a plugin url exists by calling the plugin and checking result.
+ Applies only to plugins that support media library scanning.
+ \param content content type - movies, tvshows, musicvideos.
+ \param strPath full plugin url.
+ \return true if the plugin supports scanning and specified url exists, false otherwise.
+ */
+ static bool CheckExists(const std::string& content, const std::string& strPath);
+
+ // callbacks from python
+ static bool AddItem(int handle, const CFileItem *item, int totalItems);
+ static bool AddItems(int handle, const CFileItemList *items, int totalItems);
+ static void EndOfDirectory(int handle, bool success, bool replaceListing, bool cacheToDisc);
+ static void AddSortMethod(int handle, SORT_METHOD sortMethod, const std::string &labelMask, const std::string &label2Mask);
+ static std::string GetSetting(int handle, const std::string &key);
+ static void SetSetting(int handle, const std::string &key, const std::string &value);
+ static void SetContent(int handle, const std::string &strContent);
+ static void SetProperty(int handle, const std::string &strProperty, const std::string &strValue);
+ static void SetResolvedUrl(int handle, bool success, const CFileItem* resultItem);
+ static void SetLabel2(int handle, const std::string& ident);
+
+protected:
+ // implementations of CRunningScriptsHandler / CScriptRunner
+ bool IsSuccessful() const override { return m_success; }
+ bool IsCancelled() const override { return m_cancelled; }
+
+private:
+ bool StartScript(const std::string& strPath, bool resume);
+
+ std::unique_ptr<CFileItemList> m_listItems;
+ std::unique_ptr<CFileItem> m_fileResult;
+
+ std::atomic<bool> m_cancelled;
+ bool m_success = false; // set by script in EndOfDirectory
+ int m_totalItems = 0; // set by script in AddDirectoryItem
+};
+}
diff --git a/xbmc/filesystem/PluginFile.cpp b/xbmc/filesystem/PluginFile.cpp
new file mode 100644
index 0000000..63bf827
--- /dev/null
+++ b/xbmc/filesystem/PluginFile.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PluginFile.h"
+
+#include "URL.h"
+
+using namespace XFILE;
+
+CPluginFile::CPluginFile(void) : COverrideFile(false)
+{
+}
+
+CPluginFile::~CPluginFile(void) = default;
+
+bool CPluginFile::Open(const CURL& url)
+{
+ return false;
+}
+
+bool CPluginFile::Exists(const CURL& url)
+{
+ return true;
+}
+
+int CPluginFile::Stat(const CURL& url, struct __stat64* buffer)
+{
+ return -1;
+}
+
+int CPluginFile::Stat(struct __stat64* buffer)
+{
+ return -1;
+}
+
+bool CPluginFile::OpenForWrite(const CURL& url, bool bOverWrite)
+{
+ return false;
+}
+
+bool CPluginFile::Delete(const CURL& url)
+{
+ return false;
+}
+
+bool CPluginFile::Rename(const CURL& url, const CURL& urlnew)
+{
+ return false;
+}
+
+std::string CPluginFile::TranslatePath(const CURL& url)
+{
+ return url.Get();
+}
diff --git a/xbmc/filesystem/PluginFile.h b/xbmc/filesystem/PluginFile.h
new file mode 100644
index 0000000..2044fd9
--- /dev/null
+++ b/xbmc/filesystem/PluginFile.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "filesystem/OverrideFile.h"
+
+namespace XFILE
+{
+class CPluginFile : public COverrideFile
+{
+public:
+ CPluginFile(void);
+ ~CPluginFile(void) override;
+ bool Open(const CURL& url) override;
+ bool Exists(const CURL& url) override;
+ int Stat(const CURL& url, struct __stat64* buffer) override;
+ int Stat(struct __stat64* buffer) override;
+ bool OpenForWrite(const CURL& url, bool bOverWrite = false) override;
+ bool Delete(const CURL& url) override;
+ bool Rename(const CURL& url, const CURL& urlnew) override;
+
+protected:
+ std::string TranslatePath(const CURL& url) override;
+};
+} // namespace XFILE
diff --git a/xbmc/filesystem/RSSDirectory.cpp b/xbmc/filesystem/RSSDirectory.cpp
new file mode 100644
index 0000000..a176381
--- /dev/null
+++ b/xbmc/filesystem/RSSDirectory.cpp
@@ -0,0 +1,623 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RSSDirectory.h"
+
+#include "CurlFile.h"
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/FileExtensionProvider.h"
+#include "utils/HTMLUtil.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/XBMCTinyXML.h"
+#include "utils/XMLUtils.h"
+#include "utils/log.h"
+#include "video/VideoInfoTag.h"
+
+#include <climits>
+#include <mutex>
+#include <utility>
+
+using namespace XFILE;
+using namespace MUSIC_INFO;
+
+namespace {
+
+ struct SResource
+ {
+ std::string tag;
+ std::string path;
+ std::string mime;
+ std::string lang;
+ int width = 0;
+ int height = 0;
+ int bitrate = 0;
+ int duration = 0;
+ int64_t size = 0;
+ };
+
+ typedef std::vector<SResource> SResources;
+
+}
+
+std::map<std::string,CDateTime> CRSSDirectory::m_cache;
+CCriticalSection CRSSDirectory::m_section;
+
+CRSSDirectory::CRSSDirectory() = default;
+
+CRSSDirectory::~CRSSDirectory() = default;
+
+bool CRSSDirectory::ContainsFiles(const CURL& url)
+{
+ CFileItemList items;
+ if(!GetDirectory(url, items))
+ return false;
+
+ return items.Size() > 0;
+}
+
+static bool IsPathToMedia(const std::string& strPath )
+{
+ return URIUtils::HasExtension(strPath,
+ CServiceBroker::GetFileExtensionProvider().GetVideoExtensions() + '|' +
+ CServiceBroker::GetFileExtensionProvider().GetMusicExtensions() + '|' +
+ CServiceBroker::GetFileExtensionProvider().GetPictureExtensions());
+}
+
+static bool IsPathToThumbnail(const std::string& strPath )
+{
+ // Currently just check if this is an image, maybe we will add some
+ // other checks later
+ return URIUtils::HasExtension(strPath,
+ CServiceBroker::GetFileExtensionProvider().GetPictureExtensions());
+}
+
+static time_t ParseDate(const std::string & strDate)
+{
+ struct tm pubDate = {};
+ //! @todo Handle time zone
+ strptime(strDate.c_str(), "%a, %d %b %Y %H:%M:%S", &pubDate);
+ // Check the difference between the time of last check and time of the item
+ return mktime(&pubDate);
+}
+static void ParseItem(CFileItem* item, SResources& resources, TiXmlElement* root, const std::string& path);
+
+static std::string GetValue(TiXmlElement *element)
+{
+ if (element && !element->NoChildren())
+ return element->FirstChild()->ValueStr();
+ return "";
+}
+
+static void ParseItemMRSS(CFileItem* item, SResources& resources, TiXmlElement* item_child, const std::string& name, const std::string& xmlns, const std::string& path)
+{
+ CVideoInfoTag* vtag = item->GetVideoInfoTag();
+ std::string text = GetValue(item_child);
+
+ if(name == "content")
+ {
+ SResource res;
+ res.tag = "media:content";
+ res.mime = XMLUtils::GetAttribute(item_child, "type");
+ res.path = XMLUtils::GetAttribute(item_child, "url");
+ item_child->Attribute("width", &res.width);
+ item_child->Attribute("height", &res.height);
+ item_child->Attribute("bitrate", &res.bitrate);
+ item_child->Attribute("duration", &res.duration);
+ if(item_child->Attribute("fileSize"))
+ res.size = std::atoll(item_child->Attribute("fileSize"));
+
+ resources.push_back(res);
+ ParseItem(item, resources, item_child, path);
+ }
+ else if(name == "group")
+ {
+ ParseItem(item, resources, item_child, path);
+ }
+ else if(name == "thumbnail")
+ {
+ if(!item_child->NoChildren() && IsPathToThumbnail(item_child->FirstChild()->ValueStr()))
+ item->SetArt("thumb", item_child->FirstChild()->ValueStr());
+ else
+ {
+ const char * url = item_child->Attribute("url");
+ if(url && IsPathToThumbnail(url))
+ item->SetArt("thumb", url);
+ }
+ }
+ else if (name == "title")
+ {
+ if(text.empty())
+ return;
+
+ if(text.length() > item->m_strTitle.length())
+ item->m_strTitle = text;
+ }
+ else if(name == "description")
+ {
+ if(text.empty())
+ return;
+
+ std::string description = text;
+ if(XMLUtils::GetAttribute(item_child, "type") == "html")
+ HTML::CHTMLUtil::RemoveTags(description);
+ item->SetProperty("description", description);
+ }
+ else if(name == "category")
+ {
+ if(text.empty())
+ return;
+
+ std::string scheme = XMLUtils::GetAttribute(item_child, "scheme");
+
+ /* okey this is silly, boxee what did you think?? */
+ if (scheme == "urn:boxee:genre")
+ vtag->m_genre.push_back(text);
+ else if(scheme == "urn:boxee:title-type")
+ {
+ if (text == "tv")
+ item->SetProperty("boxee:istvshow", true);
+ else if(text == "movie")
+ item->SetProperty("boxee:ismovie", true);
+ }
+ else if(scheme == "urn:boxee:episode")
+ vtag->m_iEpisode = atoi(text.c_str());
+ else if(scheme == "urn:boxee:season")
+ vtag->m_iSeason = atoi(text.c_str());
+ else if(scheme == "urn:boxee:show-title")
+ vtag->m_strShowTitle = text.c_str();
+ else if(scheme == "urn:boxee:view-count")
+ vtag->SetPlayCount(atoi(text.c_str()));
+ else if(scheme == "urn:boxee:source")
+ item->SetProperty("boxee:provider_source", text);
+ else
+ vtag->m_genre = StringUtils::Split(text, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
+ }
+ else if(name == "rating")
+ {
+ std::string scheme = XMLUtils::GetAttribute(item_child, "scheme");
+ if(scheme == "urn:user")
+ vtag->SetRating((float)atof(text.c_str()));
+ else
+ vtag->m_strMPAARating = text;
+ }
+ else if(name == "credit")
+ {
+ std::string role = XMLUtils::GetAttribute(item_child, "role");
+ if (role == "director")
+ vtag->m_director.push_back(text);
+ else if(role == "author"
+ || role == "writer")
+ vtag->m_writingCredits.push_back(text);
+ else if(role == "actor")
+ {
+ SActorInfo actor;
+ actor.strName = text;
+ vtag->m_cast.push_back(actor);
+ }
+ }
+ else if(name == "copyright")
+ vtag->m_studio = StringUtils::Split(text, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
+ else if(name == "keywords")
+ item->SetProperty("keywords", text);
+
+}
+
+static void ParseItemItunes(CFileItem* item, SResources& resources, TiXmlElement* item_child, const std::string& name, const std::string& xmlns, const std::string& path)
+{
+ CVideoInfoTag* vtag = item->GetVideoInfoTag();
+ std::string text = GetValue(item_child);
+
+ if(name == "image")
+ {
+ const char * url = item_child->Attribute("href");
+ if(url)
+ item->SetArt("thumb", url);
+ else
+ item->SetArt("thumb", text);
+ }
+ else if(name == "summary")
+ vtag->m_strPlot = text;
+ else if(name == "subtitle")
+ vtag->m_strPlotOutline = text;
+ else if(name == "author")
+ vtag->m_writingCredits.push_back(text);
+ else if(name == "duration")
+ vtag->SetDuration(StringUtils::TimeStringToSeconds(text));
+ else if(name == "keywords")
+ item->SetProperty("keywords", text);
+}
+
+static void ParseItemRSS(CFileItem* item, SResources& resources, TiXmlElement* item_child, const std::string& name, const std::string& xmlns, const std::string& path)
+{
+ std::string text = GetValue(item_child);
+ if (name == "title")
+ {
+ if(text.length() > item->m_strTitle.length())
+ item->m_strTitle = text;
+ }
+ else if (name == "pubDate")
+ {
+ CDateTime pubDate(ParseDate(text));
+ item->m_dateTime = pubDate;
+ }
+ else if (name == "link")
+ {
+ SResource res;
+ res.tag = "rss:link";
+ res.path = text;
+ resources.push_back(res);
+ }
+ else if(name == "enclosure")
+ {
+ const char * len = item_child->Attribute("length");
+
+ SResource res;
+ res.tag = "rss:enclosure";
+ res.path = XMLUtils::GetAttribute(item_child, "url");
+ res.mime = XMLUtils::GetAttribute(item_child, "type");
+ if(len)
+ res.size = std::atoll(len);
+
+ resources.push_back(res);
+ }
+ else if(name == "description")
+ {
+ std::string description = text;
+ HTML::CHTMLUtil::RemoveTags(description);
+ item->SetProperty("description", description);
+ }
+ else if(name == "guid")
+ {
+ if(IsPathToMedia(text))
+ {
+ SResource res;
+ res.tag = "rss:guid";
+ res.path = text;
+ resources.push_back(res);
+ }
+ }
+}
+
+static void ParseItemVoddler(CFileItem* item, SResources& resources, TiXmlElement* element, const std::string& name, const std::string& xmlns, const std::string& path)
+{
+ CVideoInfoTag* vtag = item->GetVideoInfoTag();
+ std::string text = GetValue(element);
+
+ if(name == "trailer")
+ {
+ vtag->m_strTrailer = text;
+
+ SResource res;
+ res.tag = "voddler:trailer";
+ res.mime = XMLUtils::GetAttribute(element, "type");
+ res.path = text;
+ resources.push_back(res);
+ }
+ else if(name == "year")
+ vtag->SetYear(atoi(text.c_str()));
+ else if(name == "rating")
+ vtag->SetRating((float)atof(text.c_str()));
+ else if(name == "tagline")
+ vtag->m_strTagLine = text;
+ else if(name == "posterwall")
+ {
+ const char* url = element->Attribute("url");
+ if(url)
+ item->SetArt("fanart", url);
+ else if(IsPathToThumbnail(text))
+ item->SetArt("fanart", text);
+ }
+}
+
+static void ParseItemBoxee(CFileItem* item, SResources& resources, TiXmlElement* element, const std::string& name, const std::string& xmlns, const std::string& path)
+{
+ CVideoInfoTag* vtag = item->GetVideoInfoTag();
+ std::string text = GetValue(element);
+
+ if (name == "image")
+ item->SetArt("thumb", text);
+ else if(name == "user_agent")
+ item->SetProperty("boxee:user_agent", text);
+ else if(name == "content_type")
+ item->SetMimeType(text);
+ else if(name == "runtime")
+ vtag->SetDuration(StringUtils::TimeStringToSeconds(text));
+ else if(name == "episode")
+ vtag->m_iEpisode = atoi(text.c_str());
+ else if(name == "season")
+ vtag->m_iSeason = atoi(text.c_str());
+ else if(name == "view-count")
+ vtag->SetPlayCount(atoi(text.c_str()));
+ else if(name == "tv-show-title")
+ vtag->m_strShowTitle = text;
+ else if(name == "release-date")
+ item->SetProperty("boxee:releasedate", text);
+}
+
+static void ParseItemZink(CFileItem* item, SResources& resources, TiXmlElement* element, const std::string& name, const std::string& xmlns, const std::string& path)
+{
+ CVideoInfoTag* vtag = item->GetVideoInfoTag();
+ std::string text = GetValue(element);
+ if (name == "episode")
+ vtag->m_iEpisode = atoi(text.c_str());
+ else if(name == "season")
+ vtag->m_iSeason = atoi(text.c_str());
+ else if(name == "views")
+ vtag->SetPlayCount(atoi(text.c_str()));
+ else if(name == "airdate")
+ vtag->m_firstAired.SetFromDateString(text);
+ else if(name == "userrating")
+ vtag->SetRating((float)atof(text.c_str()));
+ else if(name == "duration")
+ vtag->SetDuration(atoi(text.c_str()));
+ else if(name == "durationstr")
+ vtag->SetDuration(StringUtils::TimeStringToSeconds(text));
+}
+
+static void ParseItemSVT(CFileItem* item, SResources& resources, TiXmlElement* element, const std::string& name, const std::string& xmlns, const std::string& path)
+{
+ std::string text = GetValue(element);
+ if (name == "xmllink")
+ {
+ SResource res;
+ res.tag = "svtplay:xmllink";
+ res.path = text;
+ res.mime = "application/rss+xml";
+ resources.push_back(res);
+ }
+ else if (name == "broadcasts")
+ {
+ CURL url(path);
+ if(StringUtils::StartsWith(url.GetFileName(), "v1/"))
+ {
+ SResource res;
+ res.tag = "svtplay:broadcasts";
+ res.path = url.GetWithoutFilename() + "v1/video/list/" + text;
+ res.mime = "application/rss+xml";
+ resources.push_back(res);
+ }
+ }
+}
+
+static void ParseItem(CFileItem* item, SResources& resources, TiXmlElement* root, const std::string& path)
+{
+ for (TiXmlElement* child = root->FirstChildElement(); child; child = child->NextSiblingElement())
+ {
+ std::string name = child->Value();
+ std::string xmlns;
+ size_t pos = name.find(':');
+ if(pos != std::string::npos)
+ {
+ xmlns = name.substr(0, pos);
+ name.erase(0, pos+1);
+ }
+
+ if (xmlns == "media")
+ ParseItemMRSS (item, resources, child, name, xmlns, path);
+ else if (xmlns == "itunes")
+ ParseItemItunes (item, resources, child, name, xmlns, path);
+ else if (xmlns == "voddler")
+ ParseItemVoddler(item, resources, child, name, xmlns, path);
+ else if (xmlns == "boxee")
+ ParseItemBoxee (item, resources, child, name, xmlns, path);
+ else if (xmlns == "zn")
+ ParseItemZink (item, resources, child, name, xmlns, path);
+ else if (xmlns == "svtplay")
+ ParseItemSVT (item, resources, child, name, xmlns, path);
+ else
+ ParseItemRSS (item, resources, child, name, xmlns, path);
+ }
+}
+
+static bool FindMime(const SResources& resources, const std::string& mime)
+{
+ for (const auto& it : resources)
+ {
+ if (StringUtils::StartsWithNoCase(it.mime, mime))
+ return true;
+ }
+ return false;
+}
+
+static void ParseItem(CFileItem* item, TiXmlElement* root, const std::string& path)
+{
+ SResources resources;
+ ParseItem(item, resources, root, path);
+
+ const char* prio[] = { "media:content", "voddler:trailer", "rss:enclosure", "svtplay:broadcasts", "svtplay:xmllink", "rss:link", "rss:guid", NULL };
+
+ std::string mime;
+ if (FindMime(resources, "video/"))
+ mime = "video/";
+ else if(FindMime(resources, "audio/"))
+ mime = "audio/";
+ else if(FindMime(resources, "application/rss"))
+ mime = "application/rss";
+ else if(FindMime(resources, "image/"))
+ mime = "image/";
+
+ int maxrate = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_NETWORK_BANDWIDTH);
+ if(maxrate == 0)
+ maxrate = INT_MAX;
+
+ SResources::iterator best = resources.end();
+ for(const char** type = prio; *type && best == resources.end(); type++)
+ {
+ for (SResources::iterator it = resources.begin(); it != resources.end(); ++it)
+ {
+ if(!StringUtils::StartsWith(it->mime, mime))
+ continue;
+
+ if(it->tag == *type)
+ {
+ if(best == resources.end())
+ {
+ best = it;
+ continue;
+ }
+
+ if(it->bitrate == best->bitrate)
+ {
+ if(it->width*it->height > best->width*best->height)
+ best = it;
+ continue;
+ }
+
+ if(it->bitrate > maxrate)
+ {
+ if(it->bitrate < best->bitrate)
+ best = it;
+ continue;
+ }
+
+ if(it->bitrate > best->bitrate)
+ {
+ best = it;
+ continue;
+ }
+ }
+ }
+ }
+
+ if(best != resources.end())
+ {
+ item->SetMimeType(best->mime);
+ item->SetPath(best->path);
+ item->m_dwSize = best->size;
+
+ if(best->duration)
+ item->SetProperty("duration", StringUtils::SecondsToTimeString(best->duration));
+
+ /* handling of mimetypes fo directories are sub optimal at best */
+ if(best->mime == "application/rss+xml" && StringUtils::StartsWithNoCase(item->GetPath(), "http://"))
+ item->SetPath("rss://" + item->GetPath().substr(7));
+
+ if(best->mime == "application/rss+xml" && StringUtils::StartsWithNoCase(item->GetPath(), "https://"))
+ item->SetPath("rsss://" + item->GetPath().substr(8));
+
+ if(StringUtils::StartsWithNoCase(item->GetPath(), "rss://")
+ || StringUtils::StartsWithNoCase(item->GetPath(), "rsss://"))
+ item->m_bIsFolder = true;
+ else
+ item->m_bIsFolder = false;
+ }
+
+ if(!item->m_strTitle.empty())
+ item->SetLabel(item->m_strTitle);
+
+ if(item->HasVideoInfoTag())
+ {
+ CVideoInfoTag* vtag = item->GetVideoInfoTag();
+
+ if(item->HasProperty("duration") && !vtag->GetDuration())
+ vtag->SetDuration(StringUtils::TimeStringToSeconds(item->GetProperty("duration").asString()));
+
+ if(item->HasProperty("description") && vtag->m_strPlot.empty())
+ vtag->m_strPlot = item->GetProperty("description").asString();
+
+ if(vtag->m_strPlotOutline.empty() && !vtag->m_strPlot.empty())
+ {
+ size_t pos = vtag->m_strPlot.find('\n');
+ if(pos != std::string::npos)
+ vtag->m_strPlotOutline = vtag->m_strPlot.substr(0, pos);
+ else
+ vtag->m_strPlotOutline = vtag->m_strPlot;
+ }
+
+ if(!vtag->GetDuration())
+ item->SetLabel2(StringUtils::SecondsToTimeString(vtag->GetDuration()));
+ }
+}
+
+bool CRSSDirectory::GetDirectory(const CURL& url, CFileItemList &items)
+{
+ const std::string pathToUrl(url.Get());
+ std::string strPath(pathToUrl);
+ URIUtils::RemoveSlashAtEnd(strPath);
+ std::map<std::string,CDateTime>::iterator it;
+ items.SetPath(strPath);
+ std::unique_lock<CCriticalSection> lock(m_section);
+ if ((it=m_cache.find(strPath)) != m_cache.end())
+ {
+ if (it->second > CDateTime::GetCurrentDateTime() &&
+ items.Load())
+ return true;
+ m_cache.erase(it);
+ }
+ lock.unlock();
+
+ CXBMCTinyXML xmlDoc;
+ if (!xmlDoc.LoadFile(strPath))
+ {
+ CLog::Log(LOGERROR, "failed to load xml from <{}>. error: <{}>", strPath, xmlDoc.ErrorId());
+ return false;
+ }
+ if (xmlDoc.Error())
+ {
+ CLog::Log(LOGERROR, "error parsing xml doc from <{}>. error: <{}>", strPath, xmlDoc.ErrorId());
+ return false;
+ }
+
+ TiXmlElement* rssXmlNode = xmlDoc.RootElement();
+
+ if (!rssXmlNode)
+ return false;
+
+ TiXmlHandle docHandle( &xmlDoc );
+ TiXmlElement* channelXmlNode = docHandle.FirstChild( "rss" ).FirstChild( "channel" ).Element();
+ if (channelXmlNode)
+ ParseItem(&items, channelXmlNode, pathToUrl);
+ else
+ return false;
+
+ TiXmlElement* child = NULL;
+ for (child = channelXmlNode->FirstChildElement("item"); child; child = child->NextSiblingElement())
+ {
+ // Create new item,
+ CFileItemPtr item(new CFileItem());
+ ParseItem(item.get(), child, pathToUrl);
+
+ item->SetProperty("isrss", "1");
+ // Use channel image if item doesn't have one
+ if (!item->HasArt("thumb") && items.HasArt("thumb"))
+ item->SetArt("thumb", items.GetArt("thumb"));
+
+ if (!item->GetPath().empty())
+ items.Add(item);
+ }
+
+ items.AddSortMethod(SortByNone , 231, LABEL_MASKS("%L", "%D", "%L", "")); // FileName, Duration | Foldername, empty
+ items.AddSortMethod(SortByLabel , 551, LABEL_MASKS("%L", "%D", "%L", "")); // FileName, Duration | Foldername, empty
+ items.AddSortMethod(SortBySize , 553, LABEL_MASKS("%L", "%I", "%L", "%I")); // FileName, Size | Foldername, Size
+ items.AddSortMethod(SortByDate , 552, LABEL_MASKS("%L", "%J", "%L", "%J")); // FileName, Date | Foldername, Date
+
+ CDateTime time = CDateTime::GetCurrentDateTime();
+ int mins = 60;
+ TiXmlElement* ttl = docHandle.FirstChild("rss").FirstChild("ttl").Element();
+ if (ttl)
+ mins = strtol(ttl->FirstChild()->Value(),NULL,10);
+ time += CDateTimeSpan(0,0,mins,0);
+ items.SetPath(strPath);
+ items.Save();
+ std::unique_lock<CCriticalSection> lock2(m_section);
+ m_cache.insert(make_pair(strPath,time));
+
+ return true;
+}
+
+bool CRSSDirectory::Exists(const CURL& url)
+{
+ CCurlFile rss;
+ return rss.Exists(url);
+}
diff --git a/xbmc/filesystem/RSSDirectory.h b/xbmc/filesystem/RSSDirectory.h
new file mode 100644
index 0000000..b2e3d5f
--- /dev/null
+++ b/xbmc/filesystem/RSSDirectory.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IFileDirectory.h"
+#include "XBDateTime.h"
+#include "threads/CriticalSection.h"
+
+#include <map>
+#include <string>
+
+class CFileItemList;
+
+namespace XFILE
+{
+ class CRSSDirectory : public IFileDirectory
+ {
+ public:
+ CRSSDirectory();
+ ~CRSSDirectory() override;
+ bool GetDirectory(const CURL& url, CFileItemList &items) override;
+ bool Exists(const CURL& url) override;
+ bool AllowAll() const override { return true; }
+ bool ContainsFiles(const CURL& url) override;
+ DIR_CACHE_TYPE GetCacheType(const CURL& url) const override { return DIR_CACHE_ONCE; }
+
+ protected:
+ // key is path, value is cache invalidation date
+ static std::map<std::string,CDateTime> m_cache;
+ static CCriticalSection m_section;
+ };
+}
+
diff --git a/xbmc/filesystem/ResourceDirectory.cpp b/xbmc/filesystem/ResourceDirectory.cpp
new file mode 100644
index 0000000..c3623d0
--- /dev/null
+++ b/xbmc/filesystem/ResourceDirectory.cpp
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2014-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "ResourceDirectory.h"
+
+#include "FileItem.h"
+#include "URL.h"
+#include "filesystem/Directory.h"
+#include "filesystem/ResourceFile.h"
+#include "utils/URIUtils.h"
+
+using namespace XFILE;
+
+CResourceDirectory::CResourceDirectory() = default;
+
+CResourceDirectory::~CResourceDirectory() = default;
+
+bool CResourceDirectory::GetDirectory(const CURL& url, CFileItemList &items)
+{
+ const std::string pathToUrl(url.Get());
+ std::string translatedPath;
+ if (!CResourceFile::TranslatePath(url, translatedPath))
+ return false;
+
+ if (CDirectory::GetDirectory(translatedPath, items, m_strFileMask, m_flags | DIR_FLAG_GET_HIDDEN))
+ { // replace our paths as necessary
+ items.SetURL(url);
+ for (int i = 0; i < items.Size(); i++)
+ {
+ CFileItemPtr item = items[i];
+ if (URIUtils::PathHasParent(item->GetPath(), translatedPath))
+ item->SetPath(URIUtils::AddFileToFolder(pathToUrl, item->GetPath().substr(translatedPath.size())));
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+std::string CResourceDirectory::TranslatePath(const CURL &url)
+{
+ std::string translatedPath;
+ if (!CResourceFile::TranslatePath(url, translatedPath))
+ return "";
+
+ return translatedPath;
+}
diff --git a/xbmc/filesystem/ResourceDirectory.h b/xbmc/filesystem/ResourceDirectory.h
new file mode 100644
index 0000000..f487852
--- /dev/null
+++ b/xbmc/filesystem/ResourceDirectory.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2014-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "filesystem/OverrideDirectory.h"
+
+namespace XFILE
+{
+ class CResourceDirectory : public COverrideDirectory
+ {
+ public:
+ CResourceDirectory();
+ ~CResourceDirectory() override;
+
+ bool GetDirectory(const CURL& url, CFileItemList &items) override;
+
+ protected:
+ std::string TranslatePath(const CURL &url) override;
+ };
+}
diff --git a/xbmc/filesystem/ResourceFile.cpp b/xbmc/filesystem/ResourceFile.cpp
new file mode 100644
index 0000000..81a9982
--- /dev/null
+++ b/xbmc/filesystem/ResourceFile.cpp
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2014-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "ResourceFile.h"
+
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "Util.h"
+#include "addons/AddonManager.h"
+#include "addons/Resource.h"
+
+using namespace ADDON;
+using namespace XFILE;
+
+CResourceFile::CResourceFile()
+ : COverrideFile(false)
+{ }
+
+CResourceFile::~CResourceFile() = default;
+
+bool CResourceFile::TranslatePath(const std::string &path, std::string &translatedPath)
+{
+ return TranslatePath(CURL(path), translatedPath);
+}
+
+bool CResourceFile::TranslatePath(const CURL &url, std::string &translatedPath)
+{
+ translatedPath = url.Get();
+
+ // only handle resource:// paths
+ if (!url.IsProtocol("resource"))
+ return false;
+
+ // the share name represents an identifier that can be mapped to an addon ID
+ const std::string& addonId = url.GetShareName();
+ std::string filePath;
+ if (url.GetFileName().length() > addonId.length())
+ filePath = url.GetFileName().substr(addonId.size() + 1);
+
+ if (addonId.empty())
+ return false;
+
+ AddonPtr addon;
+ if (!CServiceBroker::GetAddonMgr().GetAddon(addonId, addon, OnlyEnabled::CHOICE_YES) ||
+ addon == NULL)
+ return false;
+
+ std::shared_ptr<CResource> resource = std::dynamic_pointer_cast<ADDON::CResource>(addon);
+ if (resource == NULL)
+ return false;
+
+ if (!resource->IsAllowed(filePath))
+ return false;
+
+ translatedPath = CUtil::ValidatePath(resource->GetFullPath(filePath));
+ return true;
+}
+
+std::string CResourceFile::TranslatePath(const CURL &url)
+{
+ std::string translatedPath;
+ if (!TranslatePath(url, translatedPath))
+ return "";
+
+ return translatedPath;
+}
diff --git a/xbmc/filesystem/ResourceFile.h b/xbmc/filesystem/ResourceFile.h
new file mode 100644
index 0000000..da721b5
--- /dev/null
+++ b/xbmc/filesystem/ResourceFile.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2014-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "filesystem/OverrideFile.h"
+
+namespace XFILE
+{
+class CResourceFile : public COverrideFile
+{
+public:
+ CResourceFile();
+ ~CResourceFile() override;
+
+ static bool TranslatePath(const std::string &path, std::string &translatedPath);
+ static bool TranslatePath(const CURL &url, std::string &translatedPath);
+
+protected:
+ std::string TranslatePath(const CURL &url) override;
+};
+}
diff --git a/xbmc/filesystem/ShoutcastFile.cpp b/xbmc/filesystem/ShoutcastFile.cpp
new file mode 100644
index 0000000..7488ee7
--- /dev/null
+++ b/xbmc/filesystem/ShoutcastFile.cpp
@@ -0,0 +1,362 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+
+// FileShoutcast.cpp: implementation of the CShoutcastFile class.
+//
+//////////////////////////////////////////////////////////////////////
+
+#include "ShoutcastFile.h"
+
+#include "FileCache.h"
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "filesystem/CurlFile.h"
+#include "messaging/ApplicationMessenger.h"
+#include "music/tags/MusicInfoTag.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/SettingsComponent.h"
+#include "threads/SingleLock.h"
+#include "utils/CharsetConverter.h"
+#include "utils/HTMLUtil.h"
+#include "utils/JSONVariantParser.h"
+#include "utils/RegExp.h"
+#include "utils/StringUtils.h"
+#include "utils/UrlOptions.h"
+
+#include <climits>
+#include <mutex>
+
+using namespace XFILE;
+using namespace MUSIC_INFO;
+using namespace std::chrono_literals;
+
+CShoutcastFile::CShoutcastFile() :
+ IFile(), CThread("ShoutcastFile")
+{
+ m_discarded = 0;
+ m_currint = 0;
+ m_buffer = NULL;
+ m_cacheReader = NULL;
+ m_metaint = 0;
+}
+
+CShoutcastFile::~CShoutcastFile()
+{
+ Close();
+}
+
+int64_t CShoutcastFile::GetPosition()
+{
+ return m_file.GetPosition()-m_discarded;
+}
+
+int64_t CShoutcastFile::GetLength()
+{
+ return 0;
+}
+
+std::string CShoutcastFile::DecodeToUTF8(const std::string& str)
+{
+ std::string ret = str;
+
+ if (m_fileCharset.empty())
+ g_charsetConverter.unknownToUTF8(ret);
+ else
+ g_charsetConverter.ToUtf8(m_fileCharset, str, ret);
+
+ std::wstring wBuffer, wConverted;
+ g_charsetConverter.utf8ToW(ret, wBuffer, false);
+ HTML::CHTMLUtil::ConvertHTMLToW(wBuffer, wConverted);
+ g_charsetConverter.wToUTF8(wConverted, ret);
+
+ return ret;
+}
+
+bool CShoutcastFile::Open(const CURL& url)
+{
+ CURL url2(url);
+ url2.SetProtocolOptions(url2.GetProtocolOptions()+"&noshout=true&Icy-MetaData=1");
+ if (url.GetProtocol() == "shouts")
+ url2.SetProtocol("https");
+ else if (url.GetProtocol() == "shout")
+ url2.SetProtocol("http");
+
+ std::string icyTitle;
+ std::string icyGenre;
+
+ bool result = m_file.Open(url2);
+ if (result)
+ {
+ m_fileCharset = m_file.GetProperty(XFILE::FILE_PROPERTY_CONTENT_CHARSET);
+
+ icyTitle = m_file.GetHttpHeader().GetValue("icy-name");
+ if (icyTitle.empty())
+ icyTitle = m_file.GetHttpHeader().GetValue("ice-name"); // icecast
+ if (icyTitle == "This is my server name") // Handle badly set up servers
+ icyTitle.clear();
+
+ icyTitle = DecodeToUTF8(icyTitle);
+
+ icyGenre = m_file.GetHttpHeader().GetValue("icy-genre");
+ if (icyGenre.empty())
+ icyGenre = m_file.GetHttpHeader().GetValue("ice-genre"); // icecast
+
+ icyGenre = DecodeToUTF8(icyGenre);
+ }
+ m_metaint = atoi(m_file.GetHttpHeader().GetValue("icy-metaint").c_str());
+ if (!m_metaint)
+ m_metaint = -1;
+
+ m_buffer = new char[16*255];
+
+ if (result)
+ {
+ std::unique_lock<CCriticalSection> lock(m_tagSection);
+
+ m_masterTag.reset(new CMusicInfoTag());
+ m_masterTag->SetStationName(icyTitle);
+ m_masterTag->SetGenre(icyGenre);
+ m_masterTag->SetLoaded(true);
+
+ m_tags.push({1, m_masterTag});
+ m_tagChange.Set();
+ }
+
+ return result;
+}
+
+ssize_t CShoutcastFile::Read(void* lpBuf, size_t uiBufSize)
+{
+ if (uiBufSize > SSIZE_MAX)
+ uiBufSize = SSIZE_MAX;
+
+ if (m_currint >= m_metaint && m_metaint > 0)
+ {
+ unsigned char header;
+ m_file.Read(&header,1);
+ ReadTruncated(m_buffer, header*16);
+ ExtractTagInfo(m_buffer);
+ m_discarded += header*16+1;
+ m_currint = 0;
+ }
+
+ ssize_t toRead;
+ if (m_metaint > 0)
+ toRead = std::min<size_t>(uiBufSize,m_metaint-m_currint);
+ else
+ toRead = std::min<size_t>(uiBufSize,16*255);
+ toRead = m_file.Read(lpBuf,toRead);
+ if (toRead > 0)
+ m_currint += toRead;
+ return toRead;
+}
+
+int64_t CShoutcastFile::Seek(int64_t iFilePosition, int iWhence)
+{
+ return -1;
+}
+
+void CShoutcastFile::Close()
+{
+ StopThread();
+ delete[] m_buffer;
+ m_buffer = NULL;
+ m_file.Close();
+ m_title.clear();
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_tagSection);
+ while (!m_tags.empty())
+ m_tags.pop();
+ m_masterTag.reset();
+ m_tagChange.Set();
+ }
+}
+
+bool CShoutcastFile::ExtractTagInfo(const char* buf)
+{
+ std::string strBuffer = DecodeToUTF8(buf);
+
+ bool result = false;
+
+ CRegExp reTitle(true);
+ reTitle.RegComp("StreamTitle=\'(.*?)\';");
+
+ if (reTitle.RegFind(strBuffer.c_str()) != -1)
+ {
+ const std::string newtitle = reTitle.GetMatch(1);
+
+ result = (m_title != newtitle);
+ if (result) // track has changed
+ {
+ m_title = newtitle;
+
+ std::string title;
+ std::string artistInfo;
+ std::string coverURL;
+
+ CRegExp reURL(true);
+ reURL.RegComp("StreamUrl=\'(.*?)\';");
+ bool haveStreamUrlData =
+ (reURL.RegFind(strBuffer.c_str()) != -1) && !reURL.GetMatch(1).empty();
+
+ if (haveStreamUrlData) // track has changed and extra metadata might be available
+ {
+ const std::string streamUrlData = reURL.GetMatch(1);
+ if (StringUtils::StartsWithNoCase(streamUrlData, "http://") ||
+ StringUtils::StartsWithNoCase(streamUrlData, "https://"))
+ {
+ // Bauer Media Radio listenapi null event to erase current data
+ if (!StringUtils::EndsWithNoCase(streamUrlData, "eventdata/-1"))
+ {
+ const CURL dataURL(streamUrlData);
+ XFILE::CCurlFile http;
+ std::string extData;
+
+ if (http.Get(dataURL.Get(), extData))
+ {
+ const std::string contentType = http.GetHttpHeader().GetMimeType();
+ if (StringUtils::EqualsNoCase(contentType, "application/json"))
+ {
+ CVariant json;
+ if (CJSONVariantParser::Parse(extData, json))
+ {
+ // Check for Bauer Media Radio listenapi meta data.
+ // Example: StreamUrl='https://listenapi.bauerradio.com/api9/eventdata/58431417'
+ artistInfo = json["eventSongArtist"].asString();
+ title = json["eventSongTitle"].asString();
+ coverURL = json["eventImageUrl"].asString();
+ }
+ }
+ }
+ }
+ }
+ else if (StringUtils::StartsWithNoCase(streamUrlData, "&"))
+ {
+ // Check for SAM Cast meta data.
+ // Example: StreamUrl='&artist=RECLAM&title=BOLORDURAN%2017&album=&duration=17894&songtype=S&overlay=no&buycd=&website=&picture='
+
+ CUrlOptions urlOptions(streamUrlData);
+ const CUrlOptions::UrlOptions& options = urlOptions.GetOptions();
+
+ auto it = options.find("artist");
+ if (it != options.end())
+ artistInfo = (*it).second.asString();
+
+ it = options.find("title");
+ if (it != options.end())
+ title = (*it).second.asString();
+
+ it = options.find("picture");
+ if (it != options.end())
+ {
+ coverURL = (*it).second.asString();
+ if (!coverURL.empty())
+ {
+ // Check value being a URL (not just a file name)
+ const CURL url(coverURL);
+ if (url.GetProtocol().empty())
+ coverURL.clear();
+ }
+ }
+ }
+ }
+
+ if (artistInfo.empty() || title.empty())
+ {
+ // Most stations supply StreamTitle in format "artist - songtitle"
+ const std::vector<std::string> tokens = StringUtils::Split(newtitle, " - ");
+ if (tokens.size() == 2)
+ {
+ if (artistInfo.empty())
+ artistInfo = tokens[0];
+
+ if (title.empty())
+ title = tokens[1];
+ }
+ else
+ {
+ if (title.empty())
+ {
+ // Do not display Bauer Media Radio SteamTitle values to mark start/stop of ad breaks.
+ if (!StringUtils::StartsWithNoCase(newtitle, "START ADBREAK ") &&
+ !StringUtils::StartsWithNoCase(newtitle, "STOP ADBREAK "))
+ title = newtitle;
+ }
+ }
+ }
+
+ if (!CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_bShoutcastArt)
+ coverURL.clear();
+
+ std::unique_lock<CCriticalSection> lock(m_tagSection);
+
+ const std::shared_ptr<CMusicInfoTag> tag = std::make_shared<CMusicInfoTag>(*m_masterTag);
+ tag->SetArtist(artistInfo);
+ tag->SetTitle(title);
+ tag->SetStationArt(coverURL);
+
+ m_tags.push({m_file.GetPosition(), tag});
+ m_tagChange.Set();
+ }
+ }
+
+ return result;
+}
+
+void CShoutcastFile::ReadTruncated(char* buf2, int size)
+{
+ char* buf = buf2;
+ while (size > 0)
+ {
+ int read = m_file.Read(buf,size);
+ size -= read;
+ buf += read;
+ }
+}
+
+int CShoutcastFile::IoControl(EIoControl control, void* payload)
+{
+ if (control == IOCTRL_SET_CACHE && m_cacheReader == nullptr)
+ {
+ std::unique_lock<CCriticalSection> lock(m_tagSection);
+ m_cacheReader = static_cast<CFileCache*>(payload);
+ Create();
+ }
+
+ return IFile::IoControl(control, payload);
+}
+
+void CShoutcastFile::Process()
+{
+ while (!m_bStop)
+ {
+ if (m_tagChange.Wait(500ms))
+ {
+ std::unique_lock<CCriticalSection> lock(m_tagSection);
+ while (!m_bStop && !m_tags.empty())
+ {
+ const TagInfo& front = m_tags.front();
+ if (m_cacheReader->GetPosition() < front.first) // tagpos
+ {
+ CSingleExit ex(m_tagSection);
+ CThread::Sleep(20ms);
+ }
+ else
+ {
+ CFileItem* item = new CFileItem(*front.second); // will be deleted by msg receiver
+ m_tags.pop();
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_UPDATE_CURRENT_ITEM, 1, -1,
+ static_cast<void*>(item));
+ }
+ }
+ }
+ }
+}
diff --git a/xbmc/filesystem/ShoutcastFile.h b/xbmc/filesystem/ShoutcastFile.h
new file mode 100644
index 0000000..6b34361
--- /dev/null
+++ b/xbmc/filesystem/ShoutcastFile.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+// FileShoutcast.h: interface for the CShoutcastFile class.
+//
+//////////////////////////////////////////////////////////////////////
+
+#include "CurlFile.h"
+#include "IFile.h"
+#include "threads/Thread.h"
+
+#include <memory>
+#include <queue>
+#include <utility>
+
+namespace MUSIC_INFO
+{
+class CMusicInfoTag;
+}
+
+namespace XFILE
+{
+
+class CFileCache;
+
+class CShoutcastFile : public IFile, public CThread
+{
+public:
+ CShoutcastFile();
+ ~CShoutcastFile() override;
+ int64_t GetPosition() override;
+ int64_t GetLength() override;
+ bool Open(const CURL& url) override;
+ bool Exists(const CURL& url) override { return true; }
+ int Stat(const CURL& url, struct __stat64* buffer) override
+ {
+ errno = ENOENT;
+ return -1;
+ }
+ ssize_t Read(void* lpBuf, size_t uiBufSize) override;
+ int64_t Seek(int64_t iFilePosition, int iWhence = SEEK_SET) override;
+ void Close() override;
+ int IoControl(EIoControl request, void* param) override;
+
+ void Process() override;
+protected:
+ bool ExtractTagInfo(const char* buf);
+ void ReadTruncated(char* buf2, int size);
+
+private:
+ std::string DecodeToUTF8(const std::string& str);
+
+ CCurlFile m_file;
+ std::string m_fileCharset;
+ int m_metaint;
+ int m_discarded; // data used for tags
+ int m_currint;
+ char* m_buffer; // buffer used for tags
+ std::string m_title;
+
+ CFileCache* m_cacheReader;
+ CEvent m_tagChange;
+ CCriticalSection m_tagSection;
+ using TagInfo = std::pair<int64_t, std::shared_ptr<MUSIC_INFO::CMusicInfoTag>>;
+ std::queue<TagInfo> m_tags; // tagpos, tag
+ std::shared_ptr<MUSIC_INFO::CMusicInfoTag> m_masterTag;
+};
+}
+
diff --git a/xbmc/filesystem/SmartPlaylistDirectory.cpp b/xbmc/filesystem/SmartPlaylistDirectory.cpp
new file mode 100644
index 0000000..9e05599
--- /dev/null
+++ b/xbmc/filesystem/SmartPlaylistDirectory.cpp
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "SmartPlaylistDirectory.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "filesystem/Directory.h"
+#include "filesystem/File.h"
+#include "filesystem/FileDirectoryFactory.h"
+#include "music/MusicDatabase.h"
+#include "music/MusicDbUrl.h"
+#include "playlists/PlayListTypes.h"
+#include "playlists/SmartPlayList.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/SortUtils.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "video/VideoDatabase.h"
+#include "video/VideoDbUrl.h"
+
+#include <math.h>
+
+#define PROPERTY_PATH_DB "path.db"
+#define PROPERTY_SORT_ORDER "sort.order"
+#define PROPERTY_SORT_ASCENDING "sort.ascending"
+#define PROPERTY_GROUP_BY "group.by"
+#define PROPERTY_GROUP_MIXED "group.mixed"
+
+namespace XFILE
+{
+ CSmartPlaylistDirectory::CSmartPlaylistDirectory() = default;
+
+ CSmartPlaylistDirectory::~CSmartPlaylistDirectory() = default;
+
+ bool CSmartPlaylistDirectory::GetDirectory(const CURL& url, CFileItemList& items)
+ {
+ // Load in the SmartPlaylist and get the WHERE query
+ CSmartPlaylist playlist;
+ if (!playlist.Load(url))
+ return false;
+ bool result = GetDirectory(playlist, items);
+ if (result)
+ items.SetProperty("library.smartplaylist", true);
+
+ return result;
+ }
+
+ bool CSmartPlaylistDirectory::GetDirectory(const CSmartPlaylist &playlist, CFileItemList& items, const std::string &strBaseDir /* = "" */, bool filter /* = false */)
+ {
+ bool success = false, success2 = false;
+ std::vector<std::string> virtualFolders;
+
+ SortDescription sorting;
+ if (playlist.GetLimit() > 0)
+ sorting.limitEnd = playlist.GetLimit();
+ sorting.sortBy = playlist.GetOrder();
+ sorting.sortOrder = playlist.GetOrderAscending() ? SortOrderAscending : SortOrderDescending;
+ sorting.sortAttributes = playlist.GetOrderAttributes();
+ if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING))
+ sorting.sortAttributes = (SortAttribute)(sorting.sortAttributes | SortAttributeIgnoreArticle);
+ if (playlist.IsMusicType() && CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
+ CSettings::SETTING_MUSICLIBRARY_USEARTISTSORTNAME))
+ sorting.sortAttributes =
+ static_cast<SortAttribute>(sorting.sortAttributes | SortAttributeUseArtistSortName);
+ items.SetSortIgnoreFolders((sorting.sortAttributes & SortAttributeIgnoreFolders) ==
+ SortAttributeIgnoreFolders);
+
+ std::string option = !filter ? "xsp" : "filter";
+ std::string group = playlist.GetGroup();
+ bool isGrouped = !group.empty() && !StringUtils::EqualsNoCase(group, "none") && !playlist.IsGroupMixed();
+ // Hint for playlist files like STRM
+ PLAYLIST::Id playlistTypeHint = PLAYLIST::TYPE_NONE;
+
+ // get all virtual folders and add them to the item list
+ playlist.GetVirtualFolders(virtualFolders);
+ for (const std::string& virtualFolder : virtualFolders)
+ {
+ CFileItemPtr pItem = CFileItemPtr(new CFileItem(virtualFolder, true));
+ IFileDirectory *dir = CFileDirectoryFactory::Create(pItem->GetURL(), pItem.get());
+
+ if (dir != NULL)
+ {
+ pItem->SetSpecialSort(SortSpecialOnTop);
+ items.Add(pItem);
+ delete dir;
+ }
+ }
+
+ if (playlist.GetType() == "movies" ||
+ playlist.GetType() == "tvshows" ||
+ playlist.GetType() == "episodes")
+ {
+ playlistTypeHint = PLAYLIST::TYPE_VIDEO;
+ CVideoDatabase db;
+ if (db.Open())
+ {
+ MediaType mediaType = CMediaTypes::FromString(playlist.GetType());
+
+ std::string baseDir = strBaseDir;
+ if (strBaseDir.empty())
+ {
+ if (mediaType == MediaTypeTvShow || mediaType == MediaTypeEpisode)
+ baseDir = "videodb://tvshows/";
+ else if (mediaType == MediaTypeMovie)
+ baseDir = "videodb://movies/";
+ else
+ return false;
+
+ if (!isGrouped)
+ baseDir += "titles";
+ else
+ baseDir += group;
+ URIUtils::AddSlashAtEnd(baseDir);
+
+ if (mediaType == MediaTypeEpisode)
+ baseDir += "-1/-1/";
+ }
+
+ CVideoDbUrl videoUrl;
+ if (!videoUrl.FromString(baseDir))
+ return false;
+
+ // store the smartplaylist as JSON in the URL as well
+ std::string xsp;
+ if (!playlist.IsEmpty(filter))
+ {
+ if (!playlist.SaveAsJson(xsp, !filter))
+ return false;
+ }
+
+ if (!xsp.empty())
+ videoUrl.AddOption(option, xsp);
+ else
+ videoUrl.RemoveOption(option);
+
+ CDatabase::Filter dbfilter;
+ success = db.GetItems(videoUrl.ToString(), items, dbfilter, sorting);
+ db.Close();
+
+ // if we retrieve a list of episodes and we didn't receive
+ // a pre-defined base path, we need to fix it
+ if (strBaseDir.empty() && mediaType == MediaTypeEpisode && !isGrouped)
+ videoUrl.AppendPath("-1/-1/");
+ items.SetProperty(PROPERTY_PATH_DB, videoUrl.ToString());
+ }
+ }
+ else if (playlist.IsMusicType() || playlist.GetType().empty())
+ {
+ playlistTypeHint = PLAYLIST::TYPE_MUSIC;
+ CMusicDatabase db;
+ if (db.Open())
+ {
+ CSmartPlaylist plist(playlist);
+ if (playlist.GetType() == "mixed" || playlist.GetType().empty())
+ plist.SetType("songs");
+
+ MediaType mediaType = CMediaTypes::FromString(plist.GetType());
+
+ std::string baseDir = strBaseDir;
+ if (strBaseDir.empty())
+ {
+ baseDir = "musicdb://";
+ if (!isGrouped)
+ {
+ if (mediaType == MediaTypeArtist)
+ baseDir += "artists";
+ else if (mediaType == MediaTypeAlbum)
+ baseDir += "albums";
+ else if (mediaType == MediaTypeSong)
+ baseDir += "songs";
+ else
+ return false;
+ }
+ else
+ baseDir += group;
+
+ URIUtils::AddSlashAtEnd(baseDir);
+ }
+
+ CMusicDbUrl musicUrl;
+ if (!musicUrl.FromString(baseDir))
+ return false;
+
+ // store the smartplaylist as JSON in the URL as well
+ std::string xsp;
+ if (!plist.IsEmpty(filter))
+ {
+ if (!plist.SaveAsJson(xsp, !filter))
+ return false;
+ }
+
+ if (!xsp.empty())
+ musicUrl.AddOption(option, xsp);
+ else
+ musicUrl.RemoveOption(option);
+
+ CDatabase::Filter dbfilter;
+ success = db.GetItems(musicUrl.ToString(), items, dbfilter, sorting);
+ db.Close();
+
+ items.SetProperty(PROPERTY_PATH_DB, musicUrl.ToString());
+ }
+ }
+
+ if (playlist.GetType() == "musicvideos" || playlist.GetType() == "mixed")
+ {
+ playlistTypeHint = PLAYLIST::TYPE_VIDEO;
+ CVideoDatabase db;
+ if (db.Open())
+ {
+ CSmartPlaylist mvidPlaylist(playlist);
+ if (playlist.GetType() == "mixed")
+ mvidPlaylist.SetType("musicvideos");
+
+ std::string baseDir = strBaseDir;
+ if (baseDir.empty())
+ {
+ baseDir = "videodb://musicvideos/";
+
+ if (!isGrouped)
+ baseDir += "titles";
+ else
+ baseDir += group;
+ URIUtils::AddSlashAtEnd(baseDir);
+ }
+
+ CVideoDbUrl videoUrl;
+ if (!videoUrl.FromString(baseDir))
+ return false;
+
+ // adjust the group in case we're retrieving a grouped playlist
+ // based on artists. This is needed because the video library
+ // is using the actorslink table for artists.
+ if (isGrouped && group == "artists")
+ {
+ group = "actors";
+ mvidPlaylist.SetGroup(group);
+ }
+
+ // store the smartplaylist as JSON in the URL as well
+ std::string xsp;
+ if (!mvidPlaylist.IsEmpty(filter))
+ {
+ if (!mvidPlaylist.SaveAsJson(xsp, !filter))
+ return false;
+ }
+
+ if (!xsp.empty())
+ videoUrl.AddOption(option, xsp);
+ else
+ videoUrl.RemoveOption(option);
+
+ CFileItemList items2;
+ CDatabase::Filter dbfilter;
+ success2 = db.GetItems(videoUrl.ToString(), items2, dbfilter, sorting);
+
+ db.Close();
+ if (items.Size() <= 0)
+ items.SetPath(videoUrl.ToString());
+
+ items.Append(items2);
+ if (items2.Size())
+ {
+ if (items.Size() > items2.Size())
+ items.SetContent("mixed");
+ else
+ items.SetContent("musicvideos");
+ }
+ items.SetProperty(PROPERTY_PATH_DB, videoUrl.ToString());
+ }
+ }
+
+ items.SetLabel(playlist.GetName());
+ if (isGrouped)
+ items.SetContent(group);
+ else
+ items.SetContent(playlist.GetType());
+
+ items.SetProperty(PROPERTY_SORT_ORDER, (int)playlist.GetOrder());
+ items.SetProperty(PROPERTY_SORT_ASCENDING, playlist.GetOrderDirection() == SortOrderAscending);
+ if (!group.empty())
+ {
+ items.SetProperty(PROPERTY_GROUP_BY, group);
+ items.SetProperty(PROPERTY_GROUP_MIXED, playlist.IsGroupMixed());
+ }
+
+ // sort grouped list by label
+ if (items.Size() > 1 && !group.empty())
+ items.Sort(SortByLabel, SortOrderAscending, CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone);
+
+ // go through and set the playlist order
+ for (int i = 0; i < items.Size(); i++)
+ {
+ CFileItemPtr item = items[i];
+ item->m_iprogramCount = i; // hack for playlist order
+ item->SetProperty("playlist_type_hint", playlistTypeHint);
+ }
+
+ if (playlist.GetType() == "mixed")
+ return success || success2;
+ else if (playlist.GetType() == "musicvideos")
+ return success2;
+ else
+ return success;
+ }
+
+ bool CSmartPlaylistDirectory::ContainsFiles(const CURL& url)
+ {
+ // smart playlists always have files??
+ return true;
+ }
+
+ std::string CSmartPlaylistDirectory::GetPlaylistByName(const std::string& name, const std::string& playlistType)
+ {
+ CFileItemList list;
+ bool filesExist = false;
+ if (CSmartPlaylist::IsMusicType(playlistType))
+ filesExist = CDirectory::GetDirectory("special://musicplaylists/", list, ".xsp", DIR_FLAG_DEFAULTS);
+ else // all others are video
+ filesExist = CDirectory::GetDirectory("special://videoplaylists/", list, ".xsp", DIR_FLAG_DEFAULTS);
+ if (filesExist)
+ {
+ for (int i = 0; i < list.Size(); i++)
+ {
+ CFileItemPtr item = list[i];
+ CSmartPlaylist playlist;
+ if (playlist.OpenAndReadName(item->GetURL()))
+ {
+ if (StringUtils::EqualsNoCase(playlist.GetName(), name))
+ return item->GetPath();
+ }
+ }
+ for (int i = 0; i < list.Size(); i++)
+ { // check based on filename
+ CFileItemPtr item = list[i];
+ if (URIUtils::GetFileName(item->GetPath()) == name)
+ { // found :)
+ return item->GetPath();
+ }
+ }
+ }
+ return "";
+ }
+
+ bool CSmartPlaylistDirectory::Remove(const CURL& url)
+ {
+ return XFILE::CFile::Delete(url);
+ }
+}
+
+
+
diff --git a/xbmc/filesystem/SmartPlaylistDirectory.h b/xbmc/filesystem/SmartPlaylistDirectory.h
new file mode 100644
index 0000000..145c262
--- /dev/null
+++ b/xbmc/filesystem/SmartPlaylistDirectory.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IFileDirectory.h"
+
+#include <string>
+
+class CSmartPlaylist;
+
+namespace XFILE
+{
+ class CSmartPlaylistDirectory : public IFileDirectory
+ {
+ public:
+ CSmartPlaylistDirectory();
+ ~CSmartPlaylistDirectory() override;
+ bool GetDirectory(const CURL& url, CFileItemList& items) override;
+ bool AllowAll() const override { return true; }
+ bool ContainsFiles(const CURL& url) override;
+ bool Remove(const CURL& url) override;
+
+ static bool GetDirectory(const CSmartPlaylist &playlist, CFileItemList& items, const std::string &strBaseDir = "", bool filter = false);
+
+ static std::string GetPlaylistByName(const std::string& name, const std::string& playlistType);
+ };
+}
diff --git a/xbmc/filesystem/SourcesDirectory.cpp b/xbmc/filesystem/SourcesDirectory.cpp
new file mode 100644
index 0000000..b83ee2e
--- /dev/null
+++ b/xbmc/filesystem/SourcesDirectory.cpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2005-2020 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "SourcesDirectory.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "Util.h"
+#include "guilib/TextureManager.h"
+#include "media/MediaLockState.h"
+#include "profiles/ProfileManager.h"
+#include "settings/MediaSourceSettings.h"
+#include "storage/MediaManager.h"
+#include "utils/FileUtils.h"
+#include "utils/URIUtils.h"
+
+using namespace XFILE;
+
+CSourcesDirectory::CSourcesDirectory(void) = default;
+
+CSourcesDirectory::~CSourcesDirectory(void) = default;
+
+bool CSourcesDirectory::GetDirectory(const CURL& url, CFileItemList &items)
+{
+ // break up our path
+ // format is: sources://<type>/
+ std::string type(url.GetFileName());
+ URIUtils::RemoveSlashAtEnd(type);
+
+ VECSOURCES sources;
+ VECSOURCES *sourcesFromType = CMediaSourceSettings::GetInstance().GetSources(type);
+ if (!sourcesFromType)
+ return false;
+
+ sources = *sourcesFromType;
+ CServiceBroker::GetMediaManager().GetRemovableDrives(sources);
+
+ return GetDirectory(sources, items);
+}
+
+bool CSourcesDirectory::GetDirectory(const VECSOURCES &sources, CFileItemList &items)
+{
+ for (unsigned int i = 0; i < sources.size(); ++i)
+ {
+ const CMediaSource& share = sources[i];
+ CFileItemPtr pItem(new CFileItem(share));
+ if (URIUtils::IsProtocol(pItem->GetPath(), "musicsearch"))
+ pItem->SetCanQueue(false);
+
+ std::string strIcon;
+ // We have the real DVD-ROM, set icon on disktype
+ if (share.m_iDriveType == CMediaSource::SOURCE_TYPE_DVD && share.m_strThumbnailImage.empty())
+ {
+ CUtil::GetDVDDriveIcon( pItem->GetPath(), strIcon );
+ // CDetectDVDMedia::SetNewDVDShareUrl() caches disc thumb as special://temp/dvdicon.tbn
+ std::string strThumb = "special://temp/dvdicon.tbn";
+ if (CFileUtils::Exists(strThumb))
+ pItem->SetArt("thumb", strThumb);
+ }
+ else if (URIUtils::IsProtocol(pItem->GetPath(), "addons"))
+ strIcon = "DefaultHardDisk.png";
+ else if ( pItem->IsPath("special://musicplaylists/")
+ || pItem->IsPath("special://videoplaylists/"))
+ strIcon = "DefaultPlaylist.png";
+ else if ( pItem->IsVideoDb()
+ || pItem->IsMusicDb()
+ || pItem->IsPlugin()
+ || pItem->IsPath("musicsearch://"))
+ strIcon = "DefaultFolder.png";
+ else if (pItem->IsRemote())
+ strIcon = "DefaultNetwork.png";
+ else if (pItem->IsISO9660())
+ strIcon = "DefaultDVDRom.png";
+ else if (pItem->IsDVD())
+ strIcon = "DefaultDVDFull.png";
+ else if (pItem->IsBluray())
+ strIcon = "DefaultBluray.png";
+ else if (pItem->IsCDDA())
+ strIcon = "DefaultCDDA.png";
+ else if (pItem->IsRemovable() && CServiceBroker::GetGUI()->GetTextureManager().HasTexture("DefaultRemovableDisk.png"))
+ strIcon = "DefaultRemovableDisk.png";
+ else
+ strIcon = "DefaultHardDisk.png";
+
+ pItem->SetArt("icon", strIcon);
+ if (share.m_iHasLock == LOCK_STATE_LOCKED &&
+ m_profileManager->GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE)
+ pItem->SetOverlayImage(CGUIListItem::ICON_OVERLAY_LOCKED);
+ else
+ pItem->SetOverlayImage(CGUIListItem::ICON_OVERLAY_NONE);
+
+ items.Add(pItem);
+ }
+ return true;
+}
+
+bool CSourcesDirectory::Exists(const CURL& url)
+{
+ return true;
+}
diff --git a/xbmc/filesystem/SourcesDirectory.h b/xbmc/filesystem/SourcesDirectory.h
new file mode 100644
index 0000000..84df8f0
--- /dev/null
+++ b/xbmc/filesystem/SourcesDirectory.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IDirectory.h"
+
+#include <vector>
+
+class CMediaSource;
+typedef std::vector<CMediaSource> VECSOURCES;
+
+namespace XFILE
+{
+ class CSourcesDirectory : public IDirectory
+ {
+ public:
+ CSourcesDirectory(void);
+ ~CSourcesDirectory(void) override;
+ bool GetDirectory(const CURL& url, CFileItemList &items) override;
+ bool GetDirectory(const VECSOURCES &sources, CFileItemList &items);
+ bool Exists(const CURL& url) override;
+ bool AllowAll() const override { return true; }
+ };
+}
diff --git a/xbmc/filesystem/SpecialProtocol.cpp b/xbmc/filesystem/SpecialProtocol.cpp
new file mode 100644
index 0000000..1828773
--- /dev/null
+++ b/xbmc/filesystem/SpecialProtocol.cpp
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "SpecialProtocol.h"
+
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "Util.h"
+#include "profiles/ProfileManager.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+
+#include <cassert>
+
+#include "PlatformDefs.h"
+#ifdef TARGET_POSIX
+#include <dirent.h>
+#include "utils/StringUtils.h"
+#endif
+
+const CProfileManager *CSpecialProtocol::m_profileManager = nullptr;
+
+void CSpecialProtocol::RegisterProfileManager(const CProfileManager &profileManager)
+{
+ m_profileManager = &profileManager;
+}
+
+void CSpecialProtocol::UnregisterProfileManager()
+{
+ m_profileManager = nullptr;
+}
+
+void CSpecialProtocol::SetProfilePath(const std::string &dir)
+{
+ SetPath("profile", dir);
+ CLog::Log(LOGINFO, "special://profile/ is mapped to: {}", GetPath("profile"));
+}
+
+void CSpecialProtocol::SetXBMCPath(const std::string &dir)
+{
+ SetPath("xbmc", dir);
+}
+
+void CSpecialProtocol::SetXBMCBinPath(const std::string &dir)
+{
+ SetPath("xbmcbin", dir);
+}
+
+void CSpecialProtocol::SetXBMCBinAddonPath(const std::string &dir)
+{
+ SetPath("xbmcbinaddons", dir);
+}
+
+void CSpecialProtocol::SetXBMCAltBinAddonPath(const std::string &dir)
+{
+ SetPath("xbmcaltbinaddons", dir);
+}
+
+void CSpecialProtocol::SetXBMCFrameworksPath(const std::string &dir)
+{
+ SetPath("frameworks", dir);
+}
+
+void CSpecialProtocol::SetHomePath(const std::string &dir)
+{
+ SetPath("home", dir);
+}
+
+void CSpecialProtocol::SetUserHomePath(const std::string &dir)
+{
+ SetPath("userhome", dir);
+}
+
+void CSpecialProtocol::SetEnvHomePath(const std::string &dir)
+{
+ SetPath("envhome", dir);
+}
+
+void CSpecialProtocol::SetMasterProfilePath(const std::string &dir)
+{
+ SetPath("masterprofile", dir);
+}
+
+void CSpecialProtocol::SetTempPath(const std::string &dir)
+{
+ SetPath("temp", dir);
+}
+
+void CSpecialProtocol::SetLogPath(const std::string &dir)
+{
+ SetPath("logpath", dir);
+}
+
+bool CSpecialProtocol::ComparePath(const std::string &path1, const std::string &path2)
+{
+ return TranslatePath(path1) == TranslatePath(path2);
+}
+
+std::string CSpecialProtocol::TranslatePath(const std::string &path)
+{
+ CURL url(path);
+ // check for special-protocol, if not, return
+ if (!url.IsProtocol("special"))
+ {
+ return path;
+ }
+ return TranslatePath(url);
+}
+
+std::string CSpecialProtocol::TranslatePath(const CURL &url)
+{
+ // check for special-protocol, if not, return
+ if (!url.IsProtocol("special"))
+ {
+#if defined(TARGET_POSIX) && defined(_DEBUG)
+ std::string path(url.Get());
+ if (path.length() >= 2 && path[1] == ':')
+ {
+ CLog::Log(LOGWARNING, "Trying to access old style dir: {}", path);
+ // printf("Trying to access old style dir: %s\n", path.c_str());
+ }
+#endif
+
+ return url.Get();
+ }
+
+ const std::string& FullFileName = url.GetFileName();
+
+ std::string translatedPath;
+ std::string FileName;
+ std::string RootDir;
+
+ // Split up into the special://root and the rest of the filename
+ size_t pos = FullFileName.find('/');
+ if (pos != std::string::npos && pos > 1)
+ {
+ RootDir = FullFileName.substr(0, pos);
+
+ if (pos < FullFileName.size())
+ FileName = FullFileName.substr(pos + 1);
+ }
+ else
+ RootDir = FullFileName;
+
+ if (RootDir == "subtitles")
+ translatedPath = URIUtils::AddFileToFolder(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_SUBTITLES_CUSTOMPATH), FileName);
+ else if (RootDir == "userdata" && m_profileManager)
+ translatedPath = URIUtils::AddFileToFolder(m_profileManager->GetUserDataFolder(), FileName);
+ else if (RootDir == "database" && m_profileManager)
+ translatedPath = URIUtils::AddFileToFolder(m_profileManager->GetDatabaseFolder(), FileName);
+ else if (RootDir == "thumbnails" && m_profileManager)
+ translatedPath = URIUtils::AddFileToFolder(m_profileManager->GetThumbnailsFolder(), FileName);
+ else if (RootDir == "recordings" || RootDir == "cdrips")
+ translatedPath = URIUtils::AddFileToFolder(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_AUDIOCDS_RECORDINGPATH), FileName);
+ else if (RootDir == "screenshots")
+ translatedPath = URIUtils::AddFileToFolder(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_DEBUG_SCREENSHOTPATH), FileName);
+ else if (RootDir == "musicartistsinfo")
+ translatedPath = URIUtils::AddFileToFolder(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_MUSICLIBRARY_ARTISTSFOLDER), FileName);
+ else if (RootDir == "musicplaylists")
+ translatedPath = URIUtils::AddFileToFolder(CUtil::MusicPlaylistsLocation(), FileName);
+ else if (RootDir == "videoplaylists")
+ translatedPath = URIUtils::AddFileToFolder(CUtil::VideoPlaylistsLocation(), FileName);
+ else if (RootDir == "skin")
+ {
+ auto winSystem = CServiceBroker::GetWinSystem();
+ // windowing may not have been initialized yet
+ if (winSystem)
+ translatedPath = URIUtils::AddFileToFolder(winSystem->GetGfxContext().GetMediaDir(), FileName);
+ }
+ // from here on, we have our "real" special paths
+ else if (RootDir == "xbmc" ||
+ RootDir == "xbmcbin" ||
+ RootDir == "xbmcbinaddons" ||
+ RootDir == "xbmcaltbinaddons" ||
+ RootDir == "home" ||
+ RootDir == "envhome" ||
+ RootDir == "userhome" ||
+ RootDir == "temp" ||
+ RootDir == "profile" ||
+ RootDir == "masterprofile" ||
+ RootDir == "frameworks" ||
+ RootDir == "logpath")
+ {
+ std::string basePath = GetPath(RootDir);
+ if (!basePath.empty())
+ translatedPath = URIUtils::AddFileToFolder(basePath, FileName);
+ else
+ translatedPath.clear();
+ }
+
+ // check if we need to recurse in
+ if (URIUtils::IsSpecial(translatedPath))
+ { // we need to recurse in, as there may be multiple translations required
+ return TranslatePath(translatedPath);
+ }
+
+ // Validate the final path, just in case
+ return CUtil::ValidatePath(translatedPath);
+}
+
+std::string CSpecialProtocol::TranslatePathConvertCase(const std::string& path)
+{
+ std::string translatedPath = TranslatePath(path);
+
+#ifdef TARGET_POSIX
+ if (translatedPath.find("://") != std::string::npos)
+ return translatedPath;
+
+ // If the file exists with the requested name, simply return it
+ struct stat stat_buf;
+ if (stat(translatedPath.c_str(), &stat_buf) == 0)
+ return translatedPath;
+
+ std::string result;
+ std::vector<std::string> tokens;
+ StringUtils::Tokenize(translatedPath, tokens, "/");
+ std::string file;
+ DIR* dir;
+ struct dirent* de;
+
+ for (unsigned int i = 0; i < tokens.size(); i++)
+ {
+ file = result + "/";
+ file += tokens[i];
+ if (stat(file.c_str(), &stat_buf) == 0)
+ {
+ result += "/" + tokens[i];
+ }
+ else
+ {
+ dir = opendir(result.c_str());
+ if (dir)
+ {
+ while ((de = readdir(dir)) != NULL)
+ {
+ // check if there's a file with same name but different case
+ if (StringUtils::CompareNoCase(de->d_name, tokens[i]) == 0)
+ {
+ result += "/";
+ result += de->d_name;
+ break;
+ }
+ }
+
+ // if we did not find any file that somewhat matches, just
+ // fallback but we know it's not gonna be a good ending
+ if (de == NULL)
+ result += "/" + tokens[i];
+
+ closedir(dir);
+ }
+ else
+ { // this is just fallback, we won't succeed anyway...
+ result += "/" + tokens[i];
+ }
+ }
+ }
+
+ return result;
+#else
+ return translatedPath;
+#endif
+}
+
+void CSpecialProtocol::LogPaths()
+{
+ CLog::Log(LOGINFO, "special://xbmc/ is mapped to: {}", GetPath("xbmc"));
+ CLog::Log(LOGINFO, "special://xbmcbin/ is mapped to: {}", GetPath("xbmcbin"));
+ CLog::Log(LOGINFO, "special://xbmcbinaddons/ is mapped to: {}", GetPath("xbmcbinaddons"));
+ CLog::Log(LOGINFO, "special://masterprofile/ is mapped to: {}", GetPath("masterprofile"));
+#if defined(TARGET_POSIX)
+ CLog::Log(LOGINFO, "special://envhome/ is mapped to: {}", GetPath("envhome"));
+#endif
+ CLog::Log(LOGINFO, "special://home/ is mapped to: {}", GetPath("home"));
+ CLog::Log(LOGINFO, "special://temp/ is mapped to: {}", GetPath("temp"));
+ CLog::Log(LOGINFO, "special://logpath/ is mapped to: {}", GetPath("logpath"));
+ //CLog::Log(LOGINFO, "special://userhome/ is mapped to: {}", GetPath("userhome"));
+ if (!CUtil::GetFrameworksPath().empty())
+ CLog::Log(LOGINFO, "special://frameworks/ is mapped to: {}", GetPath("frameworks"));
+}
+
+// private routines, to ensure we only set/get an appropriate path
+void CSpecialProtocol::SetPath(const std::string &key, const std::string &path)
+{
+ m_pathMap[key] = path;
+}
+
+std::string CSpecialProtocol::GetPath(const std::string &key)
+{
+ std::map<std::string, std::string>::iterator it = m_pathMap.find(key);
+ if (it != m_pathMap.end())
+ return it->second;
+ assert(false);
+ return "";
+}
diff --git a/xbmc/filesystem/SpecialProtocol.h b/xbmc/filesystem/SpecialProtocol.h
new file mode 100644
index 0000000..2d0cec8
--- /dev/null
+++ b/xbmc/filesystem/SpecialProtocol.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <map>
+#include <string>
+
+class CProfileManager;
+
+// static class for path translation from our special:// URLs.
+
+/* paths are as follows:
+
+ special://xbmc/ - the main XBMC folder (i.e. where the app resides).
+ special://home/ - a writeable version of the main XBMC folder
+ Linux: ~/.kodi/
+ OS X: ~/Library/Application Support/Kodi/
+ Win32: ~/Application Data/XBMC/
+ special://envhome/ - on posix systems this will be equal to the $HOME
+ special://userhome/ - a writable version of the user home directory
+ Linux, OS X: ~/.kodi
+ Win32: home directory of user
+ special://masterprofile/ - the master users userdata folder - usually special://home/userdata
+ Linux: ~/.kodi/userdata/
+ OS X: ~/Library/Application Support/Kodi/UserData/
+ Win32: ~/Application Data/XBMC/UserData/
+ special://profile/ - the current users userdata folder - usually special://masterprofile/profiles/<current_profile>
+ Linux: ~/.kodi/userdata/profiles/<current_profile>
+ OS X: ~/Library/Application Support/Kodi/UserData/profiles/<current_profile>
+ Win32: ~/Application Data/XBMC/UserData/profiles/<current_profile>
+
+ special://temp/ - the temporary directory.
+ Linux: ~/.kodi/temp
+ OS X: ~/
+ Win32: ~/Application Data/XBMC/cache
+*/
+class CURL;
+class CSpecialProtocol
+{
+public:
+ static void RegisterProfileManager(const CProfileManager &profileManager);
+ static void UnregisterProfileManager();
+
+ static void SetProfilePath(const std::string &path);
+ static void SetXBMCPath(const std::string &path);
+ static void SetXBMCBinPath(const std::string &path);
+ static void SetXBMCBinAddonPath(const std::string &path);
+ static void SetXBMCAltBinAddonPath(const std::string &path);
+ static void SetXBMCFrameworksPath(const std::string &path);
+ static void SetHomePath(const std::string &path);
+ static void SetUserHomePath(const std::string &path);
+ static void SetEnvHomePath(const std::string &path);
+ static void SetMasterProfilePath(const std::string &path);
+ static void SetTempPath(const std::string &path);
+ static void SetLogPath(const std::string &dir);
+
+ static bool ComparePath(const std::string &path1, const std::string &path2);
+ static void LogPaths();
+
+ static std::string TranslatePath(const std::string &path);
+ static std::string TranslatePath(const CURL &url);
+ static std::string TranslatePathConvertCase(const std::string& path);
+
+private:
+ static const CProfileManager *m_profileManager;
+
+ static void SetPath(const std::string &key, const std::string &path);
+ static std::string GetPath(const std::string &key);
+
+ static std::map<std::string, std::string> m_pathMap;
+};
+
+#ifdef TARGET_WINDOWS
+#define PATH_SEPARATOR_CHAR '\\'
+#define PATH_SEPARATOR_STRING "\\"
+#else
+#define PATH_SEPARATOR_CHAR '/'
+#define PATH_SEPARATOR_STRING "/"
+#endif
diff --git a/xbmc/filesystem/SpecialProtocolDirectory.cpp b/xbmc/filesystem/SpecialProtocolDirectory.cpp
new file mode 100644
index 0000000..e4a970e
--- /dev/null
+++ b/xbmc/filesystem/SpecialProtocolDirectory.cpp
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "SpecialProtocolDirectory.h"
+
+#include "Directory.h"
+#include "FileItem.h"
+#include "SpecialProtocol.h"
+#include "URL.h"
+#include "utils/URIUtils.h"
+
+using namespace XFILE;
+
+CSpecialProtocolDirectory::CSpecialProtocolDirectory(void) = default;
+
+CSpecialProtocolDirectory::~CSpecialProtocolDirectory(void) = default;
+
+bool CSpecialProtocolDirectory::GetDirectory(const CURL& url, CFileItemList &items)
+{
+ const std::string pathToUrl(url.Get());
+ std::string translatedPath = CSpecialProtocol::TranslatePath(url);
+ if (CDirectory::GetDirectory(translatedPath, items, m_strFileMask, m_flags | DIR_FLAG_GET_HIDDEN))
+ { // replace our paths as necessary
+ items.SetURL(url);
+ for (int i = 0; i < items.Size(); i++)
+ {
+ CFileItemPtr item = items[i];
+ if (URIUtils::PathHasParent(item->GetPath(), translatedPath))
+ item->SetPath(URIUtils::AddFileToFolder(pathToUrl, item->GetPath().substr(translatedPath.size())));
+ }
+ return true;
+ }
+ return false;
+}
+
+std::string CSpecialProtocolDirectory::TranslatePath(const CURL &url)
+{
+ return CSpecialProtocol::TranslatePath(url);
+}
diff --git a/xbmc/filesystem/SpecialProtocolDirectory.h b/xbmc/filesystem/SpecialProtocolDirectory.h
new file mode 100644
index 0000000..97b3613
--- /dev/null
+++ b/xbmc/filesystem/SpecialProtocolDirectory.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "filesystem/OverrideDirectory.h"
+
+namespace XFILE
+{
+ class CSpecialProtocolDirectory : public COverrideDirectory
+ {
+ public:
+ CSpecialProtocolDirectory(void);
+ ~CSpecialProtocolDirectory(void) override;
+ bool GetDirectory(const CURL& url, CFileItemList &items) override;
+
+ protected:
+ std::string TranslatePath(const CURL &url) override;
+ };
+}
diff --git a/xbmc/filesystem/SpecialProtocolFile.cpp b/xbmc/filesystem/SpecialProtocolFile.cpp
new file mode 100644
index 0000000..9ef367d
--- /dev/null
+++ b/xbmc/filesystem/SpecialProtocolFile.cpp
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "SpecialProtocolFile.h"
+
+#include "URL.h"
+#include "filesystem/SpecialProtocol.h"
+
+using namespace XFILE;
+
+CSpecialProtocolFile::CSpecialProtocolFile(void)
+ : COverrideFile(true)
+{ }
+
+CSpecialProtocolFile::~CSpecialProtocolFile(void) = default;
+
+std::string CSpecialProtocolFile::TranslatePath(const CURL& url)
+{
+ return CSpecialProtocol::TranslatePath(url);
+}
diff --git a/xbmc/filesystem/SpecialProtocolFile.h b/xbmc/filesystem/SpecialProtocolFile.h
new file mode 100644
index 0000000..58fd649
--- /dev/null
+++ b/xbmc/filesystem/SpecialProtocolFile.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "filesystem/OverrideFile.h"
+
+namespace XFILE
+{
+class CSpecialProtocolFile : public COverrideFile
+{
+public:
+ CSpecialProtocolFile(void);
+ ~CSpecialProtocolFile(void) override;
+
+protected:
+ std::string TranslatePath(const CURL& url) override;
+};
+}
diff --git a/xbmc/filesystem/StackDirectory.cpp b/xbmc/filesystem/StackDirectory.cpp
new file mode 100644
index 0000000..a752a94
--- /dev/null
+++ b/xbmc/filesystem/StackDirectory.cpp
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "StackDirectory.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+
+#include <stdlib.h>
+
+namespace XFILE
+{
+ CStackDirectory::CStackDirectory() = default;
+
+ CStackDirectory::~CStackDirectory() = default;
+
+ bool CStackDirectory::GetDirectory(const CURL& url, CFileItemList& items)
+ {
+ items.Clear();
+ std::vector<std::string> files;
+ const std::string pathToUrl(url.Get());
+ if (!GetPaths(pathToUrl, files))
+ return false; // error in path
+
+ for (const std::string& i : files)
+ {
+ CFileItemPtr item(new CFileItem(i));
+ item->SetPath(i);
+ item->m_bIsFolder = false;
+ items.Add(item);
+ }
+ return true;
+ }
+
+ std::string CStackDirectory::GetStackedTitlePath(const std::string &strPath)
+ {
+ // Load up our REs
+ VECCREGEXP RegExps;
+ CRegExp tempRE(true, CRegExp::autoUtf8);
+ const std::vector<std::string>& strRegExps = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoStackRegExps;
+ std::vector<std::string>::const_iterator itRegExp = strRegExps.begin();
+ while (itRegExp != strRegExps.end())
+ {
+ (void)tempRE.RegComp(*itRegExp);
+ if (tempRE.GetCaptureTotal() == 4)
+ RegExps.push_back(tempRE);
+ else
+ CLog::Log(LOGERROR, "Invalid video stack RE ({}). Must have exactly 4 captures.",
+ itRegExp->c_str());
+ ++itRegExp;
+ }
+ return GetStackedTitlePath(strPath, RegExps);
+ }
+
+ std::string CStackDirectory::GetStackedTitlePath(const std::string &strPath, VECCREGEXP& RegExps)
+ {
+ CStackDirectory stack;
+ CFileItemList files;
+ std::string strStackTitlePath,
+ strCommonDir = URIUtils::GetParentPath(strPath);
+
+ const CURL pathToUrl(strPath);
+ stack.GetDirectory(pathToUrl, files);
+
+ if (files.Size() > 1)
+ {
+ std::string strStackTitle;
+
+ std::string File1 = URIUtils::GetFileName(files[0]->GetPath());
+ std::string File2 = URIUtils::GetFileName(files[1]->GetPath());
+ // Check if source path uses URL encoding
+ if (URIUtils::HasEncodedFilename(CURL(strCommonDir)))
+ {
+ File1 = CURL::Decode(File1);
+ File2 = CURL::Decode(File2);
+ }
+
+ std::vector<CRegExp>::iterator itRegExp = RegExps.begin();
+ int offset = 0;
+
+ while (itRegExp != RegExps.end())
+ {
+ if (itRegExp->RegFind(File1, offset) != -1)
+ {
+ std::string Title1 = itRegExp->GetMatch(1),
+ Volume1 = itRegExp->GetMatch(2),
+ Ignore1 = itRegExp->GetMatch(3),
+ Extension1 = itRegExp->GetMatch(4);
+ if (offset)
+ Title1 = File1.substr(0, itRegExp->GetSubStart(2));
+ if (itRegExp->RegFind(File2, offset) != -1)
+ {
+ std::string Title2 = itRegExp->GetMatch(1),
+ Volume2 = itRegExp->GetMatch(2),
+ Ignore2 = itRegExp->GetMatch(3),
+ Extension2 = itRegExp->GetMatch(4);
+ if (offset)
+ Title2 = File2.substr(0, itRegExp->GetSubStart(2));
+ if (StringUtils::EqualsNoCase(Title1, Title2))
+ {
+ if (!StringUtils::EqualsNoCase(Volume1, Volume2))
+ {
+ if (StringUtils::EqualsNoCase(Ignore1, Ignore2) &&
+ StringUtils::EqualsNoCase(Extension1, Extension2))
+ {
+ // got it
+ strStackTitle = Title1 + Ignore1 + Extension1;
+ // Check if source path uses URL encoding
+ if (URIUtils::HasEncodedFilename(CURL(strCommonDir)))
+ strStackTitle = CURL::Encode(strStackTitle);
+
+ itRegExp = RegExps.end();
+ break;
+ }
+ else // Invalid stack
+ break;
+ }
+ else // Early match, retry with offset
+ {
+ offset = itRegExp->GetSubStart(3);
+ continue;
+ }
+ }
+ }
+ }
+ offset = 0;
+ ++itRegExp;
+ }
+ if (!strCommonDir.empty() && !strStackTitle.empty())
+ strStackTitlePath = strCommonDir + strStackTitle;
+ }
+
+ return strStackTitlePath;
+ }
+
+ std::string CStackDirectory::GetFirstStackedFile(const std::string &strPath)
+ {
+ // the stacked files are always in volume order, so just get up to the first filename
+ // occurrence of " , "
+ std::string file, folder;
+ size_t pos = strPath.find(" , ");
+ if (pos != std::string::npos)
+ URIUtils::Split(strPath.substr(0, pos), folder, file);
+ else
+ URIUtils::Split(strPath, folder, file); // single filed stacks - should really not happen
+
+ // remove "stack://" from the folder
+ folder = folder.substr(8);
+ StringUtils::Replace(file, ",,", ",");
+
+ return URIUtils::AddFileToFolder(folder, file);
+ }
+
+ bool CStackDirectory::GetPaths(const std::string& strPath, std::vector<std::string>& vecPaths)
+ {
+ // format is:
+ // stack://file1 , file2 , file3 , file4
+ // filenames with commas are double escaped (ie replaced with ,,), thus the " , " separator used.
+ std::string path = strPath;
+ // remove stack:// from the beginning
+ path = path.substr(8);
+
+ vecPaths = StringUtils::Split(path, " , ");
+ if (vecPaths.empty())
+ return false;
+
+ // because " , " is used as a separator any "," in the real paths are double escaped
+ for (std::string& itPath : vecPaths)
+ StringUtils::Replace(itPath, ",,", ",");
+
+ return true;
+ }
+
+ std::string CStackDirectory::ConstructStackPath(const CFileItemList &items, const std::vector<int> &stack)
+ {
+ // no checks on the range of stack here.
+ // we replace all instances of comma's with double comma's, then separate
+ // the files using " , ".
+ std::string stackedPath = "stack://";
+ std::string folder, file;
+ URIUtils::Split(items[stack[0]]->GetPath(), folder, file);
+ stackedPath += folder;
+ // double escape any occurrence of commas
+ StringUtils::Replace(file, ",", ",,");
+ stackedPath += file;
+ for (unsigned int i = 1; i < stack.size(); ++i)
+ {
+ stackedPath += " , ";
+ file = items[stack[i]]->GetPath();
+
+ // double escape any occurrence of commas
+ StringUtils::Replace(file, ",", ",,");
+ stackedPath += file;
+ }
+ return stackedPath;
+ }
+
+ bool CStackDirectory::ConstructStackPath(const std::vector<std::string> &paths, std::string& stackedPath)
+ {
+ if (paths.size() < 2)
+ return false;
+ stackedPath = "stack://";
+ std::string folder, file;
+ URIUtils::Split(paths[0], folder, file);
+ stackedPath += folder;
+ // double escape any occurrence of commas
+ StringUtils::Replace(file, ",", ",,");
+ stackedPath += file;
+ for (unsigned int i = 1; i < paths.size(); ++i)
+ {
+ stackedPath += " , ";
+ file = paths[i];
+
+ // double escape any occurrence of commas
+ StringUtils::Replace(file, ",", ",,");
+ stackedPath += file;
+ }
+ return true;
+ }
+}
+
diff --git a/xbmc/filesystem/StackDirectory.h b/xbmc/filesystem/StackDirectory.h
new file mode 100644
index 0000000..5f61fe3
--- /dev/null
+++ b/xbmc/filesystem/StackDirectory.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IDirectory.h"
+#include "utils/RegExp.h"
+
+#include <string>
+#include <vector>
+
+namespace XFILE
+{
+ class CStackDirectory : public IDirectory
+ {
+ public:
+ CStackDirectory();
+ ~CStackDirectory() override;
+ bool GetDirectory(const CURL& url, CFileItemList& items) override;
+ bool AllowAll() const override { return true; }
+ static std::string GetStackedTitlePath(const std::string &strPath);
+ static std::string GetStackedTitlePath(const std::string &strPath, VECCREGEXP& RegExps);
+ static std::string GetFirstStackedFile(const std::string &strPath);
+ static bool GetPaths(const std::string& strPath, std::vector<std::string>& vecPaths);
+ static std::string ConstructStackPath(const CFileItemList& items, const std::vector<int> &stack);
+ static bool ConstructStackPath(const std::vector<std::string> &paths, std::string &stackedPath);
+ };
+}
diff --git a/xbmc/filesystem/UDFBlockInput.cpp b/xbmc/filesystem/UDFBlockInput.cpp
new file mode 100644
index 0000000..b4eb265
--- /dev/null
+++ b/xbmc/filesystem/UDFBlockInput.cpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "UDFBlockInput.h"
+
+#include "filesystem/File.h"
+
+#include <mutex>
+
+#include <udfread/udfread.h>
+
+int CUDFBlockInput::Close(udfread_block_input* bi)
+{
+ auto m_bi = reinterpret_cast<UDF_BI*>(bi);
+
+ m_bi->fp->Close();
+
+ return 0;
+}
+
+uint32_t CUDFBlockInput::Size(udfread_block_input* bi)
+{
+ auto m_bi = reinterpret_cast<UDF_BI*>(bi);
+
+ return static_cast<uint32_t>(m_bi->fp->GetLength() / UDF_BLOCK_SIZE);
+}
+
+int CUDFBlockInput::Read(
+ udfread_block_input* bi, uint32_t lba, void* buf, uint32_t blocks, int flags)
+{
+ auto m_bi = reinterpret_cast<UDF_BI*>(bi);
+ std::unique_lock<CCriticalSection> lock(m_bi->lock);
+
+ int64_t pos = static_cast<int64_t>(lba) * UDF_BLOCK_SIZE;
+
+ if (m_bi->fp->Seek(pos, SEEK_SET) != pos)
+ return -1;
+
+ ssize_t size = blocks * UDF_BLOCK_SIZE;
+ ssize_t read = m_bi->fp->Read(buf, size);
+ if (read > 0)
+ return static_cast<int>(read / UDF_BLOCK_SIZE);
+
+ return static_cast<int>(read);
+}
+
+udfread_block_input* CUDFBlockInput::GetBlockInput(const std::string& file)
+{
+ auto fp = std::make_shared<XFILE::CFile>();
+
+ if (fp->Open(file))
+ {
+ m_bi = std::make_unique<UDF_BI>();
+ if (m_bi)
+ {
+ m_bi->fp = fp;
+ m_bi->bi.close = CUDFBlockInput::Close;
+ m_bi->bi.read = CUDFBlockInput::Read;
+ m_bi->bi.size = CUDFBlockInput::Size;
+
+ return &m_bi->bi;
+ }
+
+ fp->Close();
+ }
+
+ return nullptr;
+}
diff --git a/xbmc/filesystem/UDFBlockInput.h b/xbmc/filesystem/UDFBlockInput.h
new file mode 100644
index 0000000..e7cfc88
--- /dev/null
+++ b/xbmc/filesystem/UDFBlockInput.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "threads/CriticalSection.h"
+
+#include <memory>
+
+#include <udfread/blockinput.h>
+
+namespace XFILE
+{
+class CFile;
+}
+
+class CUDFBlockInput
+{
+public:
+ CUDFBlockInput() = default;
+ ~CUDFBlockInput() = default;
+
+ udfread_block_input* GetBlockInput(const std::string& file);
+
+private:
+ static int Close(udfread_block_input* bi);
+ static uint32_t Size(udfread_block_input* bi);
+ static int Read(udfread_block_input* bi, uint32_t lba, void* buf, uint32_t nblocks, int flags);
+
+ struct UDF_BI
+ {
+ struct udfread_block_input bi;
+ std::shared_ptr<XFILE::CFile> fp{nullptr};
+ CCriticalSection lock;
+ };
+
+ std::unique_ptr<UDF_BI> m_bi{nullptr};
+};
diff --git a/xbmc/filesystem/UDFDirectory.cpp b/xbmc/filesystem/UDFDirectory.cpp
new file mode 100644
index 0000000..236b281
--- /dev/null
+++ b/xbmc/filesystem/UDFDirectory.cpp
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2010 Team Boxee
+ * http://www.boxee.tv
+ *
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "UDFDirectory.h"
+
+#include "FileItem.h"
+#include "URL.h"
+#include "Util.h"
+#include "filesystem/UDFBlockInput.h"
+#include "utils/URIUtils.h"
+
+#include <udfread/udfread.h>
+
+using namespace XFILE;
+
+bool CUDFDirectory::GetDirectory(const CURL& url, CFileItemList& items)
+{
+ CURL url2(url);
+ if (!url2.IsProtocol("udf"))
+ {
+ url2.Reset();
+ url2.SetProtocol("udf");
+ url2.SetHostName(url.Get());
+ }
+
+ std::string strRoot(url2.Get());
+ std::string strSub(url2.GetFileName());
+
+ URIUtils::AddSlashAtEnd(strRoot);
+ URIUtils::AddSlashAtEnd(strSub);
+
+ auto udf = udfread_init();
+
+ if (!udf)
+ return false;
+
+ CUDFBlockInput udfbi;
+
+ auto bi = udfbi.GetBlockInput(url2.GetHostName());
+
+ if (udfread_open_input(udf, bi) < 0)
+ {
+ udfread_close(udf);
+ return false;
+ }
+
+ auto path = udfread_opendir(udf, strSub.c_str());
+ if (!path)
+ {
+ udfread_close(udf);
+ return false;
+ }
+
+ struct udfread_dirent dirent;
+
+ while (udfread_readdir(path, &dirent))
+ {
+ if (dirent.d_type == UDF_DT_DIR)
+ {
+ std::string filename = dirent.d_name;
+ if (filename != "." && filename != "..")
+ {
+ CFileItemPtr pItem(new CFileItem(filename));
+ std::string strDir(strRoot + filename);
+ URIUtils::AddSlashAtEnd(strDir);
+ pItem->SetPath(strDir);
+ pItem->m_bIsFolder = true;
+
+ items.Add(pItem);
+ }
+ }
+ else
+ {
+ std::string filename = dirent.d_name;
+ std::string filenameWithPath{strSub + filename};
+ auto file = udfread_file_open(udf, filenameWithPath.c_str());
+ if (!file)
+ continue;
+
+ CFileItemPtr pItem(new CFileItem(filename));
+ pItem->SetPath(strRoot + filename);
+ pItem->m_bIsFolder = false;
+ pItem->m_dwSize = udfread_file_size(file);
+ items.Add(pItem);
+
+ udfread_file_close(file);
+ }
+ }
+
+ udfread_closedir(path);
+ udfread_close(udf);
+
+ return true;
+}
+
+bool CUDFDirectory::Exists(const CURL& url)
+{
+ CFileItemList items;
+ return GetDirectory(url, items);
+}
diff --git a/xbmc/filesystem/UDFDirectory.h b/xbmc/filesystem/UDFDirectory.h
new file mode 100644
index 0000000..ea4487a
--- /dev/null
+++ b/xbmc/filesystem/UDFDirectory.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2010 Team Boxee
+ * http://www.boxee.tv
+ *
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IFileDirectory.h"
+
+namespace XFILE
+{
+
+class CUDFDirectory : public IFileDirectory
+{
+public:
+ CUDFDirectory() = default;
+ ~CUDFDirectory() = default;
+ bool GetDirectory(const CURL& url, CFileItemList& items) override;
+ bool Exists(const CURL& url) override;
+ bool ContainsFiles(const CURL& url) override { return true; }
+};
+}
+
diff --git a/xbmc/filesystem/UDFFile.cpp b/xbmc/filesystem/UDFFile.cpp
new file mode 100644
index 0000000..f4c5d5b
--- /dev/null
+++ b/xbmc/filesystem/UDFFile.cpp
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "UDFFile.h"
+
+#include "URL.h"
+
+#include <udfread/udfread.h>
+
+using namespace XFILE;
+
+CUDFFile::CUDFFile() : m_bi{std::make_unique<CUDFBlockInput>()}
+{
+}
+
+bool CUDFFile::Open(const CURL& url)
+{
+ if (m_udf && m_file)
+ return true;
+
+ m_udf = udfread_init();
+
+ if (!m_udf)
+ return false;
+
+ auto bi = m_bi->GetBlockInput(url.GetHostName());
+
+ if (!bi)
+ {
+ udfread_close(m_udf);
+ return false;
+ }
+
+ if (udfread_open_input(m_udf, bi) < 0)
+ {
+ bi->close(bi);
+ udfread_close(m_udf);
+ return false;
+ }
+
+ m_file = udfread_file_open(m_udf, url.GetFileName().c_str());
+ if (!m_file)
+ {
+ Close();
+ return false;
+ }
+
+ return true;
+}
+
+void CUDFFile::Close()
+{
+ if (m_file)
+ {
+ udfread_file_close(m_file);
+ m_file = nullptr;
+ }
+
+ if (m_udf)
+ {
+ udfread_close(m_udf);
+ m_udf = nullptr;
+ }
+}
+
+int CUDFFile::Stat(const CURL& url, struct __stat64* buffer)
+{
+ if (!m_udf || !m_file || !buffer)
+ return -1;
+
+ *buffer = {};
+ buffer->st_size = GetLength();
+
+ return 0;
+}
+
+ssize_t CUDFFile::Read(void* buffer, size_t size)
+{
+ return udfread_file_read(m_file, buffer, size);
+}
+
+int64_t CUDFFile::Seek(int64_t filePosition, int whence)
+{
+ return udfread_file_seek(m_file, filePosition, whence);
+}
+
+int64_t CUDFFile::GetLength()
+{
+ return udfread_file_size(m_file);
+}
+
+int64_t CUDFFile::GetPosition()
+{
+ return udfread_file_tell(m_file);
+}
+
+bool CUDFFile::Exists(const CURL& url)
+{
+ return Open(url);
+}
diff --git a/xbmc/filesystem/UDFFile.h b/xbmc/filesystem/UDFFile.h
new file mode 100644
index 0000000..1617a1f
--- /dev/null
+++ b/xbmc/filesystem/UDFFile.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IFile.h"
+#include "filesystem/UDFBlockInput.h"
+
+#include <memory>
+
+class udfread;
+typedef struct udfread_file UDFFILE;
+
+namespace XFILE
+{
+
+class CUDFFile : public IFile
+{
+public:
+ CUDFFile();
+ ~CUDFFile() override = default;
+
+ bool Open(const CURL& url) override;
+ void Close() override;
+
+ int Stat(const CURL& url, struct __stat64* buffer) override;
+
+ ssize_t Read(void* buffer, size_t size) override;
+ int64_t Seek(int64_t filePosition, int whence) override;
+
+ int64_t GetLength() override;
+ int64_t GetPosition() override;
+
+ bool Exists(const CURL& url) override;
+
+private:
+ std::unique_ptr<CUDFBlockInput> m_bi{nullptr};
+
+ udfread* m_udf{nullptr};
+ UDFFILE* m_file{nullptr};
+};
+
+} // namespace XFILE
diff --git a/xbmc/filesystem/UPnPDirectory.cpp b/xbmc/filesystem/UPnPDirectory.cpp
new file mode 100644
index 0000000..b93b6bf
--- /dev/null
+++ b/xbmc/filesystem/UPnPDirectory.cpp
@@ -0,0 +1,358 @@
+/*
+ * UPnP Support for XBMC
+ * Copyright (c) 2006 c0diq (Sylvain Rebaud)
+ * Portions Copyright (c) by the authors of libPlatinum
+ * http://www.plutinosoft.com/blog/category/platinum/
+ *
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+#include "UPnPDirectory.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "network/upnp/UPnP.h"
+#include "network/upnp/UPnPInternal.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+
+#include <Platinum/Source/Devices/MediaServer/PltSyncMediaBrowser.h>
+#include <Platinum/Source/Platinum/Platinum.h>
+
+using namespace MUSIC_INFO;
+using namespace XFILE;
+using namespace UPNP;
+
+namespace XFILE
+{
+
+static std::string GetContentMapping(NPT_String& objectClass)
+{
+ struct SClassMapping
+ {
+ const char* ObjectClass;
+ const char* Content;
+ };
+ static const SClassMapping mapping[] = {
+ { "object.item.videoItem.videoBroadcast" , "episodes" }
+ , { "object.item.videoItem.musicVideoClip" , "musicvideos" }
+ , { "object.item.videoItem" , "movies" }
+ , { "object.item.audioItem.musicTrack" , "songs" }
+ , { "object.item.audioItem" , "songs" }
+ , { "object.item.imageItem.photo" , "photos" }
+ , { "object.item.imageItem" , "photos" }
+ , { "object.container.album.videoAlbum.videoBroadcastShow" , "tvshows" }
+ , { "object.container.album.videoAlbum.videoBroadcastSeason", "seasons" }
+ , { "object.container.album.musicAlbum" , "albums" }
+ , { "object.container.album.photoAlbum" , "photos" }
+ , { "object.container.album" , "albums" }
+ , { "object.container.person" , "artists" }
+ , { NULL , NULL }
+ };
+ for(const SClassMapping* map = mapping; map->ObjectClass; map++)
+ {
+ if(objectClass.StartsWith(map->ObjectClass, true))
+ {
+ return map->Content;
+ break;
+ }
+ }
+ return "unknown";
+}
+
+static bool FindDeviceWait(CUPnP* upnp, const char* uuid, PLT_DeviceDataReference& device)
+{
+ bool client_started = upnp->IsClientStarted();
+ upnp->StartClient();
+
+ // look for device in our list
+ // (and wait for it to respond for 5 secs if we're just starting upnp client)
+ NPT_TimeStamp watchdog;
+ NPT_System::GetCurrentTimeStamp(watchdog);
+ watchdog += 5.0;
+
+ for (;;) {
+ if (NPT_SUCCEEDED(upnp->m_MediaBrowser->FindServer(uuid, device)) && !device.IsNull())
+ break;
+
+ // fail right away if device not found and upnp client was already running
+ if (client_started)
+ return false;
+
+ // otherwise check if we've waited long enough without success
+ NPT_TimeStamp now;
+ NPT_System::GetCurrentTimeStamp(now);
+ if (now > watchdog)
+ return false;
+
+ // sleep a bit and try again
+ NPT_System::Sleep(NPT_TimeInterval((double)1));
+ }
+
+ return !device.IsNull();
+}
+
+/*----------------------------------------------------------------------
+| CUPnPDirectory::GetFriendlyName
++---------------------------------------------------------------------*/
+std::string CUPnPDirectory::GetFriendlyName(const CURL& url)
+{
+ NPT_String path = url.Get().c_str();
+ if (!path.EndsWith("/")) path += "/";
+
+ if (path.Left(7).Compare("upnp://", true) != 0) {
+ return {};
+ } else if (path.Compare("upnp://", true) == 0) {
+ return "UPnP Media Servers (Auto-Discover)";
+ }
+
+ // look for nextslash
+ int next_slash = path.Find('/', 7);
+ if (next_slash == -1)
+ return {};
+
+ NPT_String uuid = path.SubString(7, next_slash-7);
+ NPT_String object_id = path.SubString(next_slash+1, path.GetLength()-next_slash-2);
+
+ // look for device
+ PLT_DeviceDataReference device;
+ if(!FindDeviceWait(CUPnP::GetInstance(), uuid, device))
+ return {};
+
+ return device->GetFriendlyName().GetChars();
+}
+
+/*----------------------------------------------------------------------
+| CUPnPDirectory::GetDirectory
++---------------------------------------------------------------------*/
+bool CUPnPDirectory::GetResource(const CURL& path, CFileItem &item)
+{
+ if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_SERVICES_UPNP))
+ return false;
+
+ if(!path.IsProtocol("upnp"))
+ return false;
+
+ CUPnP* upnp = CUPnP::GetInstance();
+ if(!upnp)
+ return false;
+
+ const std::string& uuid = path.GetHostName();
+ std::string object = path.GetFileName();
+ StringUtils::TrimRight(object, "/");
+ object = CURL::Decode(object);
+
+ PLT_DeviceDataReference device;
+ if(!FindDeviceWait(upnp, uuid.c_str(), device)) {
+ CLog::Log(LOGERROR, "CUPnPDirectory::GetResource - unable to find uuid {}", uuid);
+ return false;
+ }
+
+ PLT_MediaObjectListReference list;
+ if (NPT_FAILED(upnp->m_MediaBrowser->BrowseSync(device, object.c_str(), list, true))) {
+ CLog::Log(LOGERROR, "CUPnPDirectory::GetResource - unable to find object {}", object);
+ return false;
+ }
+
+ if (list.IsNull() || !list->GetItemCount()) {
+ CLog::Log(LOGERROR, "CUPnPDirectory::GetResource - no items returned for object {}", object);
+ return false;
+ }
+
+ PLT_MediaObjectList::Iterator entry = list->GetFirstItem();
+ if (entry == 0)
+ return false;
+
+ return UPNP::GetResource(*entry, item);
+}
+
+
+/*----------------------------------------------------------------------
+| CUPnPDirectory::GetDirectory
++---------------------------------------------------------------------*/
+bool
+CUPnPDirectory::GetDirectory(const CURL& url, CFileItemList &items)
+{
+ if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_SERVICES_UPNP))
+ return false;
+
+ CUPnP* upnp = CUPnP::GetInstance();
+
+ /* upnp should never be cached, it has internal cache */
+ items.SetCacheToDisc(CFileItemList::CACHE_NEVER);
+
+ // We accept upnp://devuuid/[item_id/]
+ NPT_String path = url.Get().c_str();
+ if (!path.StartsWith("upnp://", true)) {
+ return false;
+ }
+
+ if (path.Compare("upnp://", true) == 0) {
+ upnp->StartClient();
+
+ // root -> get list of devices
+ const NPT_Lock<PLT_DeviceDataReferenceList>& devices = upnp->m_MediaBrowser->GetMediaServers();
+ NPT_List<PLT_DeviceDataReference>::Iterator device = devices.GetFirstItem();
+ while (device) {
+ NPT_String name = (*device)->GetFriendlyName();
+ NPT_String uuid = (*device)->GetUUID();
+
+ CFileItemPtr pItem(new CFileItem((const char*)name));
+ pItem->SetPath(std::string((const char*) "upnp://" + uuid + "/"));
+ pItem->m_bIsFolder = true;
+ pItem->SetArt("thumb", (const char*)(*device)->GetIconUrl("image/png"));
+
+ items.Add(pItem);
+
+ ++device;
+ }
+ } else {
+ if (!path.EndsWith("/")) path += "/";
+
+ // look for nextslash
+ int next_slash = path.Find('/', 7);
+
+ NPT_String uuid = (next_slash==-1)?path.SubString(7):path.SubString(7, next_slash-7);
+ NPT_String object_id = (next_slash == -1) ? NPT_String("") : path.SubString(next_slash + 1);
+ object_id.TrimRight("/");
+ if (object_id.GetLength()) {
+ object_id = CURL::Decode((char*)object_id).c_str();
+ }
+
+ // try to find the device with wait on startup
+ PLT_DeviceDataReference device;
+ if (!FindDeviceWait(upnp, uuid, device))
+ goto failure;
+
+ // issue a browse request with object_id
+ // if object_id is empty use "0" for root
+ object_id = object_id.IsEmpty() ? NPT_String("0") : object_id;
+
+ // remember a count of object classes
+ std::map<NPT_String, int> classes;
+
+ // just a guess as to what types of files we want
+ bool video = true;
+ bool audio = true;
+ bool image = true;
+ StringUtils::TrimLeft(m_strFileMask, "/");
+ if (!m_strFileMask.empty()) {
+ video = m_strFileMask.find(".wmv") != std::string::npos;
+ audio = m_strFileMask.find(".wma") != std::string::npos;
+ image = m_strFileMask.find(".jpg") != std::string::npos;
+ }
+
+ // special case for Windows Media Connect and WMP11 when looking for root
+ // We can target which root subfolder we want based on directory mask
+ if (object_id == "0" && ((device->GetFriendlyName().Find("Windows Media Connect", 0, true) >= 0) ||
+ (device->m_ModelName == "Windows Media Player Sharing"))) {
+
+ // look for a specific type to differentiate which folder we want
+ if (audio && !video && !image) {
+ // music
+ object_id = "1";
+ } else if (!audio && video && !image) {
+ // video
+ object_id = "2";
+ } else if (!audio && !video && image) {
+ // pictures
+ object_id = "3";
+ }
+ }
+
+#ifdef DISABLE_SPECIALCASE
+ // same thing but special case for XBMC
+ if (object_id == "0" && ((device->m_ModelName.Find("XBMC", 0, true) >= 0) ||
+ (device->m_ModelName.Find("Xbox Media Center", 0, true) >= 0))) {
+ // look for a specific type to differentiate which folder we want
+ if (audio && !video && !image) {
+ // music
+ object_id = "virtualpath://upnpmusic";
+ } else if (!audio && video && !image) {
+ // video
+ object_id = "virtualpath://upnpvideo";
+ } else if (!audio && !video && image) {
+ // pictures
+ object_id = "virtualpath://upnppictures";
+ }
+ }
+#endif
+
+ // if error, return now, the device could have gone away
+ // this will make us go back to the sources list
+ PLT_MediaObjectListReference list;
+ NPT_Result res = upnp->m_MediaBrowser->BrowseSync(device, object_id, list);
+ if (NPT_FAILED(res)) goto failure;
+
+ // empty list is ok
+ if (list.IsNull()) goto cleanup;
+
+ PLT_MediaObjectList::Iterator entry = list->GetFirstItem();
+ while (entry) {
+ // disregard items with wrong class/type
+ if( (!video && (*entry)->m_ObjectClass.type.CompareN("object.item.videoitem", 21,true) == 0)
+ || (!audio && (*entry)->m_ObjectClass.type.CompareN("object.item.audioitem", 21,true) == 0)
+ || (!image && (*entry)->m_ObjectClass.type.CompareN("object.item.imageitem", 21,true) == 0) )
+ {
+ ++entry;
+ continue;
+ }
+
+ // keep count of classes
+ classes[(*entry)->m_ObjectClass.type]++;
+ CFileItemPtr pItem = BuildObject(*entry, UPnPClient);
+ if(!pItem) {
+ ++entry;
+ continue;
+ }
+
+ std::string id;
+ if ((*entry)->m_ReferenceID.IsEmpty())
+ id = (const char*) (*entry)->m_ObjectID;
+ else
+ id = (const char*) (*entry)->m_ReferenceID;
+
+ id = CURL::Encode(id);
+ URIUtils::AddSlashAtEnd(id);
+ pItem->SetPath(std::string((const char*) "upnp://" + uuid + "/" + id.c_str()));
+
+ items.Add(pItem);
+
+ ++entry;
+ }
+
+ NPT_String max_string = "";
+ int max_count = 0;
+ for (auto& it : classes)
+ {
+ if (it.second > max_count)
+ {
+ max_string = it.first;
+ max_count = it.second;
+ }
+ }
+ std::string content = GetContentMapping(max_string);
+ items.SetContent(content);
+ if (content == "unknown")
+ {
+ items.AddSortMethod(SortByNone, 571, LABEL_MASKS("%L", "%I", "%L", ""));
+ items.AddSortMethod(SortByLabel, SortAttributeIgnoreFolders, 551, LABEL_MASKS("%L", "%I", "%L", ""));
+ items.AddSortMethod(SortBySize, 553, LABEL_MASKS("%L", "%I", "%L", "%I"));
+ items.AddSortMethod(SortByDate, 552, LABEL_MASKS("%L", "%J", "%L", "%J"));
+ }
+ }
+
+cleanup:
+ return true;
+
+failure:
+ return false;
+}
+}
diff --git a/xbmc/filesystem/UPnPDirectory.h b/xbmc/filesystem/UPnPDirectory.h
new file mode 100644
index 0000000..e49407e
--- /dev/null
+++ b/xbmc/filesystem/UPnPDirectory.h
@@ -0,0 +1,37 @@
+/*
+ * UPnP Support for XBMC
+ * Copyright (c) 2006 c0diq (Sylvain Rebaud)
+ * Portions Copyright (c) by the authors of libPlatinum
+ * http://www.plutinosoft.com/blog/category/platinum/
+ *
+ * Copyright (C) 2010-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IDirectory.h"
+
+class CFileItem;
+class CURL;
+
+namespace XFILE
+{
+class CUPnPDirectory : public IDirectory
+{
+public:
+ CUPnPDirectory(void) = default;
+ ~CUPnPDirectory(void) override = default;
+
+ // IDirectory methods
+ bool GetDirectory(const CURL& url, CFileItemList &items) override;
+ bool AllowAll() const override { return true; }
+
+ // class methods
+ static std::string GetFriendlyName(const CURL& url);
+ static bool GetResource(const CURL &path, CFileItem& item);
+};
+}
diff --git a/xbmc/filesystem/UPnPFile.cpp b/xbmc/filesystem/UPnPFile.cpp
new file mode 100644
index 0000000..5605170
--- /dev/null
+++ b/xbmc/filesystem/UPnPFile.cpp
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2011-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "UPnPFile.h"
+
+#include "FileFactory.h"
+#include "FileItem.h"
+#include "UPnPDirectory.h"
+#include "URL.h"
+
+using namespace XFILE;
+
+CUPnPFile::CUPnPFile() = default;
+
+CUPnPFile::~CUPnPFile() = default;
+
+bool CUPnPFile::Open(const CURL& url)
+{
+ CFileItem item_new;
+ if (CUPnPDirectory::GetResource(url, item_new))
+ {
+ //CLog::Log(LOGDEBUG,"FileUPnP - file redirect to {}.", item_new.GetPath());
+ IFile *pNewImp = CFileFactory::CreateLoader(item_new.GetPath());
+ CURL *pNewUrl = new CURL(item_new.GetPath());
+ if (pNewImp)
+ {
+ throw new CRedirectException(pNewImp, pNewUrl);
+ }
+ delete pNewUrl;
+ }
+ return false;
+}
+
+int CUPnPFile::Stat(const CURL& url, struct __stat64* buffer)
+{
+ CFileItem item_new;
+ if (CUPnPDirectory::GetResource(url, item_new))
+ {
+ //CLog::Log(LOGDEBUG,"FileUPnP - file redirect to {}.", item_new.GetPath());
+ IFile *pNewImp = CFileFactory::CreateLoader(item_new.GetPath());
+ CURL *pNewUrl = new CURL(item_new.GetPath());
+ if (pNewImp)
+ {
+ throw new CRedirectException(pNewImp, pNewUrl);
+ }
+ delete pNewUrl;
+ }
+ return -1;
+}
+
+bool CUPnPFile::Exists(const CURL& url)
+{
+ CFileItem item_new;
+ if (CUPnPDirectory::GetResource(url, item_new))
+ {
+ //CLog::Log(LOGDEBUG,"FileUPnP - file redirect to {}.", item_new.GetPath());
+ IFile *pNewImp = CFileFactory::CreateLoader(item_new.GetPath());
+ CURL *pNewUrl = new CURL(item_new.GetPath());
+ if (pNewImp)
+ {
+ throw new CRedirectException(pNewImp, pNewUrl);
+ }
+ delete pNewUrl;
+ }
+ return false;
+}
diff --git a/xbmc/filesystem/UPnPFile.h b/xbmc/filesystem/UPnPFile.h
new file mode 100644
index 0000000..b56dcb1
--- /dev/null
+++ b/xbmc/filesystem/UPnPFile.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2011-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IFile.h"
+
+namespace XFILE
+{
+ class CUPnPFile : public IFile
+ {
+ public:
+ CUPnPFile();
+ ~CUPnPFile() override;
+ bool Open(const CURL& url) override;
+ bool Exists(const CURL& url) override;
+ int Stat(const CURL& url, struct __stat64* buffer) override;
+
+ ssize_t Read(void* lpBuf, size_t uiBufSize) override {return -1;}
+ int64_t Seek(int64_t iFilePosition, int iWhence = SEEK_SET) override {return -1;}
+ void Close() override{}
+ int64_t GetPosition() override {return -1;}
+ int64_t GetLength() override {return -1;}
+ };
+}
diff --git a/xbmc/filesystem/VideoDatabaseDirectory.cpp b/xbmc/filesystem/VideoDatabaseDirectory.cpp
new file mode 100644
index 0000000..6a2cb6a
--- /dev/null
+++ b/xbmc/filesystem/VideoDatabaseDirectory.cpp
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 2016-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "VideoDatabaseDirectory.h"
+
+#include "File.h"
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "VideoDatabaseDirectory/QueryParams.h"
+#include "guilib/LocalizeStrings.h"
+#include "guilib/TextureManager.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/Crc32.h"
+#include "utils/LegacyPathTranslation.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "video/VideoDatabase.h"
+
+using namespace XFILE;
+using namespace VIDEODATABASEDIRECTORY;
+
+CVideoDatabaseDirectory::CVideoDatabaseDirectory(void) = default;
+
+CVideoDatabaseDirectory::~CVideoDatabaseDirectory(void) = default;
+
+namespace
+{
+std::string GetChildContentType(const std::unique_ptr<CDirectoryNode>& node)
+{
+ switch (node->GetChildType())
+ {
+ case NODE_TYPE_EPISODES:
+ case NODE_TYPE_RECENTLY_ADDED_EPISODES:
+ return "episodes";
+ case NODE_TYPE_SEASONS:
+ return "seasons";
+ case NODE_TYPE_TITLE_MOVIES:
+ case NODE_TYPE_RECENTLY_ADDED_MOVIES:
+ return "movies";
+ case NODE_TYPE_TITLE_TVSHOWS:
+ case NODE_TYPE_INPROGRESS_TVSHOWS:
+ return "tvshows";
+ case NODE_TYPE_TITLE_MUSICVIDEOS:
+ case NODE_TYPE_RECENTLY_ADDED_MUSICVIDEOS:
+ return "musicvideos";
+ case NODE_TYPE_GENRE:
+ return "genres";
+ case NODE_TYPE_COUNTRY:
+ return "countries";
+ case NODE_TYPE_ACTOR:
+ {
+ CQueryParams params;
+ node->CollectQueryParams(params);
+ if (static_cast<VideoDbContentType>(params.GetContentType()) ==
+ VideoDbContentType::MUSICVIDEOS)
+ return "artists";
+
+ return "actors";
+ }
+ case NODE_TYPE_DIRECTOR:
+ return "directors";
+ case NODE_TYPE_STUDIO:
+ return "studios";
+ case NODE_TYPE_YEAR:
+ return "years";
+ case NODE_TYPE_MUSICVIDEOS_ALBUM:
+ return "albums";
+ case NODE_TYPE_SETS:
+ return "sets";
+ case NODE_TYPE_TAGS:
+ return "tags";
+ default:
+ break;
+ }
+ return {};
+}
+
+} // unnamed namespace
+
+bool CVideoDatabaseDirectory::GetDirectory(const CURL& url, CFileItemList &items)
+{
+ std::string path = CLegacyPathTranslation::TranslateVideoDbPath(url);
+ items.SetPath(path);
+ items.m_dwSize = -1; // No size
+ std::unique_ptr<CDirectoryNode> pNode(CDirectoryNode::ParseURL(path));
+
+ if (!pNode)
+ return false;
+
+ bool bResult = pNode->GetChilds(items);
+ for (int i=0;i<items.Size();++i)
+ {
+ CFileItemPtr item = items[i];
+ if (item->m_bIsFolder && !item->HasArt("icon") && !item->HasArt("thumb"))
+ {
+ std::string strImage = GetIcon(item->GetPath());
+ if (!strImage.empty() && CServiceBroker::GetGUI()->GetTextureManager().HasTexture(strImage))
+ item->SetArt("icon", strImage);
+ }
+ if (item->GetVideoInfoTag())
+ {
+ item->SetDynPath(item->GetVideoInfoTag()->GetPath());
+ }
+ }
+ if (items.HasProperty("customtitle"))
+ items.SetLabel(items.GetProperty("customtitle").asString());
+ else
+ items.SetLabel(pNode->GetLocalizedName());
+
+ items.SetContent(GetChildContentType(pNode));
+
+ return bResult;
+}
+
+NODE_TYPE CVideoDatabaseDirectory::GetDirectoryChildType(const std::string& strPath)
+{
+ std::string path = CLegacyPathTranslation::TranslateVideoDbPath(strPath);
+ std::unique_ptr<CDirectoryNode> pNode(CDirectoryNode::ParseURL(path));
+
+ if (!pNode)
+ return NODE_TYPE_NONE;
+
+ return pNode->GetChildType();
+}
+
+NODE_TYPE CVideoDatabaseDirectory::GetDirectoryType(const std::string& strPath)
+{
+ std::string path = CLegacyPathTranslation::TranslateVideoDbPath(strPath);
+ std::unique_ptr<CDirectoryNode> pNode(CDirectoryNode::ParseURL(path));
+
+ if (!pNode)
+ return NODE_TYPE_NONE;
+
+ return pNode->GetType();
+}
+
+NODE_TYPE CVideoDatabaseDirectory::GetDirectoryParentType(const std::string& strPath)
+{
+ std::string path = CLegacyPathTranslation::TranslateVideoDbPath(strPath);
+ std::unique_ptr<CDirectoryNode> pNode(CDirectoryNode::ParseURL(path));
+
+ if (!pNode)
+ return NODE_TYPE_NONE;
+
+ CDirectoryNode* pParentNode=pNode->GetParent();
+
+ if (!pParentNode)
+ return NODE_TYPE_NONE;
+
+ return pParentNode->GetChildType();
+}
+
+bool CVideoDatabaseDirectory::GetQueryParams(const std::string& strPath, CQueryParams& params)
+{
+ std::string path = CLegacyPathTranslation::TranslateVideoDbPath(strPath);
+ std::unique_ptr<CDirectoryNode> pNode(CDirectoryNode::ParseURL(path));
+
+ if (!pNode)
+ return false;
+
+ CDirectoryNode::GetDatabaseInfo(strPath,params);
+ return true;
+}
+
+void CVideoDatabaseDirectory::ClearDirectoryCache(const std::string& strDirectory)
+{
+ std::string path = CLegacyPathTranslation::TranslateVideoDbPath(strDirectory);
+ URIUtils::RemoveSlashAtEnd(path);
+
+ uint32_t crc = Crc32::ComputeFromLowerCase(path);
+
+ std::string strFileName = StringUtils::Format("special://temp/archive_cache/{:08x}.fi", crc);
+ CFile::Delete(strFileName);
+}
+
+bool CVideoDatabaseDirectory::IsAllItem(const std::string& strDirectory)
+{
+ if (StringUtils::EndsWith(strDirectory, "/-1/"))
+ return true;
+ return false;
+}
+
+bool CVideoDatabaseDirectory::GetLabel(const std::string& strDirectory, std::string& strLabel)
+{
+ strLabel = "";
+
+ std::string path = CLegacyPathTranslation::TranslateVideoDbPath(strDirectory);
+ std::unique_ptr<CDirectoryNode> pNode(CDirectoryNode::ParseURL(path));
+ if (!pNode || path.empty())
+ return false;
+
+ // first see if there's any filter criteria
+ CQueryParams params;
+ CDirectoryNode::GetDatabaseInfo(path, params);
+
+ CVideoDatabase videodatabase;
+ if (!videodatabase.Open())
+ return false;
+
+ // get genre
+ if (params.GetGenreId() != -1)
+ strLabel += videodatabase.GetGenreById(params.GetGenreId());
+
+ // get country
+ if (params.GetCountryId() != -1)
+ strLabel += videodatabase.GetCountryById(params.GetCountryId());
+
+ // get set
+ if (params.GetSetId() != -1)
+ strLabel += videodatabase.GetSetById(params.GetSetId());
+
+ // get tag
+ if (params.GetTagId() != -1)
+ strLabel += videodatabase.GetTagById(params.GetTagId());
+
+ // get year
+ if (params.GetYear() != -1)
+ {
+ std::string strTemp = std::to_string(params.GetYear());
+ if (!strLabel.empty())
+ strLabel += " / ";
+ strLabel += strTemp;
+ }
+
+ if (strLabel.empty())
+ {
+ switch (pNode->GetChildType())
+ {
+ case NODE_TYPE_TITLE_MOVIES:
+ case NODE_TYPE_TITLE_TVSHOWS:
+ case NODE_TYPE_TITLE_MUSICVIDEOS:
+ strLabel = g_localizeStrings.Get(369); break;
+ case NODE_TYPE_ACTOR: // Actor
+ strLabel = g_localizeStrings.Get(344); break;
+ case NODE_TYPE_GENRE: // Genres
+ strLabel = g_localizeStrings.Get(135); break;
+ case NODE_TYPE_COUNTRY: // Countries
+ strLabel = g_localizeStrings.Get(20451); break;
+ case NODE_TYPE_YEAR: // Year
+ strLabel = g_localizeStrings.Get(562); break;
+ case NODE_TYPE_DIRECTOR: // Director
+ strLabel = g_localizeStrings.Get(20348); break;
+ case NODE_TYPE_SETS: // Sets
+ strLabel = g_localizeStrings.Get(20434); break;
+ case NODE_TYPE_TAGS: // Tags
+ strLabel = g_localizeStrings.Get(20459); break;
+ case NODE_TYPE_MOVIES_OVERVIEW: // Movies
+ strLabel = g_localizeStrings.Get(342); break;
+ case NODE_TYPE_TVSHOWS_OVERVIEW: // TV Shows
+ strLabel = g_localizeStrings.Get(20343); break;
+ case NODE_TYPE_RECENTLY_ADDED_MOVIES: // Recently Added Movies
+ strLabel = g_localizeStrings.Get(20386); break;
+ case NODE_TYPE_RECENTLY_ADDED_EPISODES: // Recently Added Episodes
+ strLabel = g_localizeStrings.Get(20387); break;
+ case NODE_TYPE_STUDIO: // Studios
+ strLabel = g_localizeStrings.Get(20388); break;
+ case NODE_TYPE_MUSICVIDEOS_OVERVIEW: // Music Videos
+ strLabel = g_localizeStrings.Get(20389); break;
+ case NODE_TYPE_RECENTLY_ADDED_MUSICVIDEOS: // Recently Added Music Videos
+ strLabel = g_localizeStrings.Get(20390); break;
+ case NODE_TYPE_SEASONS: // Seasons
+ strLabel = g_localizeStrings.Get(33054); break;
+ case NODE_TYPE_EPISODES: // Episodes
+ strLabel = g_localizeStrings.Get(20360); break;
+ case NODE_TYPE_INPROGRESS_TVSHOWS: // InProgress TvShows
+ strLabel = g_localizeStrings.Get(626); break;
+ default:
+ return false;
+ }
+ }
+
+ return true;
+}
+
+std::string CVideoDatabaseDirectory::GetIcon(const std::string &strDirectory)
+{
+ std::string path = CLegacyPathTranslation::TranslateVideoDbPath(strDirectory);
+ switch (GetDirectoryChildType(path))
+ {
+ case NODE_TYPE_TITLE_MOVIES:
+ if (URIUtils::PathEquals(path, "videodb://movies/titles/"))
+ {
+ if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MYVIDEOS_FLATTEN))
+ return "DefaultMovies.png";
+ return "DefaultMovieTitle.png";
+ }
+ return "";
+ case NODE_TYPE_TITLE_TVSHOWS:
+ if (URIUtils::PathEquals(path, "videodb://tvshows/titles/"))
+ {
+ if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MYVIDEOS_FLATTEN))
+ return "DefaultTVShows.png";
+ return "DefaultTVShowTitle.png";
+ }
+ return "";
+ case NODE_TYPE_TITLE_MUSICVIDEOS:
+ if (URIUtils::PathEquals(path, "videodb://musicvideos/titles/"))
+ {
+ if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MYVIDEOS_FLATTEN))
+ return "DefaultMusicVideos.png";
+ return "DefaultMusicVideoTitle.png";
+ }
+ return "";
+ case NODE_TYPE_ACTOR: // Actor
+ return "DefaultActor.png";
+ case NODE_TYPE_GENRE: // Genres
+ return "DefaultGenre.png";
+ case NODE_TYPE_COUNTRY: // Countries
+ return "DefaultCountry.png";
+ case NODE_TYPE_SETS: // Sets
+ return "DefaultSets.png";
+ case NODE_TYPE_TAGS: // Tags
+ return "DefaultTags.png";
+ case NODE_TYPE_YEAR: // Year
+ return "DefaultYear.png";
+ case NODE_TYPE_DIRECTOR: // Director
+ return "DefaultDirector.png";
+ case NODE_TYPE_MOVIES_OVERVIEW: // Movies
+ return "DefaultMovies.png";
+ case NODE_TYPE_TVSHOWS_OVERVIEW: // TV Shows
+ return "DefaultTVShows.png";
+ case NODE_TYPE_RECENTLY_ADDED_MOVIES: // Recently Added Movies
+ return "DefaultRecentlyAddedMovies.png";
+ case NODE_TYPE_RECENTLY_ADDED_EPISODES: // Recently Added Episodes
+ return "DefaultRecentlyAddedEpisodes.png";
+ case NODE_TYPE_RECENTLY_ADDED_MUSICVIDEOS: // Recently Added Episodes
+ return "DefaultRecentlyAddedMusicVideos.png";
+ case NODE_TYPE_INPROGRESS_TVSHOWS: // InProgress TvShows
+ return "DefaultInProgressShows.png";
+ case NODE_TYPE_STUDIO: // Studios
+ return "DefaultStudios.png";
+ case NODE_TYPE_MUSICVIDEOS_OVERVIEW: // Music Videos
+ return "DefaultMusicVideos.png";
+ case NODE_TYPE_MUSICVIDEOS_ALBUM: // Music Videos - Albums
+ return "DefaultMusicAlbums.png";
+ default:
+ break;
+ }
+
+ return "";
+}
+
+bool CVideoDatabaseDirectory::ContainsMovies(const std::string &path)
+{
+ VIDEODATABASEDIRECTORY::NODE_TYPE type = GetDirectoryChildType(path);
+ if (type == VIDEODATABASEDIRECTORY::NODE_TYPE_TITLE_MOVIES || type == VIDEODATABASEDIRECTORY::NODE_TYPE_EPISODES || type == VIDEODATABASEDIRECTORY::NODE_TYPE_TITLE_MUSICVIDEOS) return true;
+ return false;
+}
+
+bool CVideoDatabaseDirectory::Exists(const CURL& url)
+{
+ std::string path = CLegacyPathTranslation::TranslateVideoDbPath(url);
+ std::unique_ptr<CDirectoryNode> pNode(CDirectoryNode::ParseURL(path));
+
+ if (!pNode)
+ return false;
+
+ if (pNode->GetChildType() == VIDEODATABASEDIRECTORY::NODE_TYPE_NONE)
+ return false;
+
+ return true;
+}
+
+bool CVideoDatabaseDirectory::CanCache(const std::string& strPath)
+{
+ std::string path = CLegacyPathTranslation::TranslateVideoDbPath(strPath);
+ std::unique_ptr<CDirectoryNode> pNode(CDirectoryNode::ParseURL(path));
+ if (!pNode)
+ return false;
+ return pNode->CanCache();
+}
diff --git a/xbmc/filesystem/VideoDatabaseDirectory.h b/xbmc/filesystem/VideoDatabaseDirectory.h
new file mode 100644
index 0000000..9603b7d
--- /dev/null
+++ b/xbmc/filesystem/VideoDatabaseDirectory.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IDirectory.h"
+#include "VideoDatabaseDirectory/DirectoryNode.h"
+#include "VideoDatabaseDirectory/QueryParams.h"
+
+namespace XFILE
+{
+ class CVideoDatabaseDirectory : public IDirectory
+ {
+ public:
+ CVideoDatabaseDirectory(void);
+ ~CVideoDatabaseDirectory(void) override;
+ bool GetDirectory(const CURL& url, CFileItemList &items) override;
+ bool Exists(const CURL& url) override;
+ bool AllowAll() const override { return true; }
+ static VIDEODATABASEDIRECTORY::NODE_TYPE GetDirectoryChildType(const std::string& strPath);
+ static VIDEODATABASEDIRECTORY::NODE_TYPE GetDirectoryType(const std::string& strPath);
+ static VIDEODATABASEDIRECTORY::NODE_TYPE GetDirectoryParentType(const std::string& strPath);
+ static bool GetQueryParams(const std::string& strPath, VIDEODATABASEDIRECTORY::CQueryParams& params);
+ void ClearDirectoryCache(const std::string& strDirectory);
+ static bool IsAllItem(const std::string& strDirectory);
+ static bool GetLabel(const std::string& strDirectory, std::string& strLabel);
+ static std::string GetIcon(const std::string& strDirectory);
+ bool ContainsMovies(const std::string &path);
+ static bool CanCache(const std::string &path);
+ };
+}
diff --git a/xbmc/filesystem/VideoDatabaseDirectory/CMakeLists.txt b/xbmc/filesystem/VideoDatabaseDirectory/CMakeLists.txt
new file mode 100644
index 0000000..149fa5c
--- /dev/null
+++ b/xbmc/filesystem/VideoDatabaseDirectory/CMakeLists.txt
@@ -0,0 +1,37 @@
+set(SOURCES DirectoryNode.cpp
+ DirectoryNodeEpisodes.cpp
+ DirectoryNodeGrouped.cpp
+ DirectoryNodeInProgressTvShows.cpp
+ DirectoryNodeMoviesOverview.cpp
+ DirectoryNodeMusicVideosOverview.cpp
+ DirectoryNodeOverview.cpp
+ DirectoryNodeRecentlyAddedEpisodes.cpp
+ DirectoryNodeRecentlyAddedMovies.cpp
+ DirectoryNodeRecentlyAddedMusicVideos.cpp
+ DirectoryNodeRoot.cpp
+ DirectoryNodeSeasons.cpp
+ DirectoryNodeTitleMovies.cpp
+ DirectoryNodeTitleMusicVideos.cpp
+ DirectoryNodeTitleTvShows.cpp
+ DirectoryNodeTvShowsOverview.cpp
+ QueryParams.cpp)
+
+set(HEADERS DirectoryNode.h
+ DirectoryNodeEpisodes.h
+ DirectoryNodeGrouped.h
+ DirectoryNodeInProgressTvShows.h
+ DirectoryNodeMoviesOverview.h
+ DirectoryNodeMusicVideosOverview.h
+ DirectoryNodeOverview.h
+ DirectoryNodeRecentlyAddedEpisodes.h
+ DirectoryNodeRecentlyAddedMovies.h
+ DirectoryNodeRecentlyAddedMusicVideos.h
+ DirectoryNodeRoot.h
+ DirectoryNodeSeasons.h
+ DirectoryNodeTitleMovies.h
+ DirectoryNodeTitleMusicVideos.h
+ DirectoryNodeTitleTvShows.h
+ DirectoryNodeTvShowsOverview.h
+ QueryParams.h)
+
+core_add_library(videodatabasedirectory)
diff --git a/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNode.cpp b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNode.cpp
new file mode 100644
index 0000000..7f83cff
--- /dev/null
+++ b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNode.cpp
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2016-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DirectoryNode.h"
+
+#include "DirectoryNodeEpisodes.h"
+#include "DirectoryNodeGrouped.h"
+#include "DirectoryNodeInProgressTvShows.h"
+#include "DirectoryNodeMoviesOverview.h"
+#include "DirectoryNodeMusicVideosOverview.h"
+#include "DirectoryNodeOverview.h"
+#include "DirectoryNodeRecentlyAddedEpisodes.h"
+#include "DirectoryNodeRecentlyAddedMovies.h"
+#include "DirectoryNodeRecentlyAddedMusicVideos.h"
+#include "DirectoryNodeRoot.h"
+#include "DirectoryNodeSeasons.h"
+#include "DirectoryNodeTitleMovies.h"
+#include "DirectoryNodeTitleMusicVideos.h"
+#include "DirectoryNodeTitleTvShows.h"
+#include "DirectoryNodeTvShowsOverview.h"
+#include "FileItem.h"
+#include "QueryParams.h"
+#include "URL.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+
+using namespace XFILE::VIDEODATABASEDIRECTORY;
+
+// Constructor is protected use ParseURL()
+CDirectoryNode::CDirectoryNode(NODE_TYPE Type, const std::string& strName, CDirectoryNode* pParent)
+{
+ m_Type = Type;
+ m_strName = strName;
+ m_pParent = pParent;
+}
+
+CDirectoryNode::~CDirectoryNode()
+{
+ delete m_pParent, m_pParent = nullptr;
+}
+
+// Parses a given path and returns the current node of the path
+CDirectoryNode* CDirectoryNode::ParseURL(const std::string& strPath)
+{
+ CURL url(strPath);
+
+ std::string strDirectory = url.GetFileName();
+ URIUtils::RemoveSlashAtEnd(strDirectory);
+
+ std::vector<std::string> Path = StringUtils::Tokenize(strDirectory, '/');
+ // we always have a root node, it is special and has a path of ""
+ Path.insert(Path.begin(), "");
+
+ CDirectoryNode *pNode = nullptr;
+ CDirectoryNode *pParent = nullptr;
+ NODE_TYPE NodeType = NODE_TYPE_ROOT;
+ // loop down the dir path, creating a node with a parent.
+ // if we hit a child type of NODE_TYPE_NONE, then we are done.
+ for (size_t i = 0; i < Path.size() && NodeType != NODE_TYPE_NONE; ++i)
+ {
+ pNode = CDirectoryNode::CreateNode(NodeType, Path[i], pParent);
+ NodeType = pNode ? pNode->GetChildType() : NODE_TYPE_NONE;
+ pParent = pNode;
+ }
+
+ // Add all the additional URL options to the last node
+ if (pNode)
+ pNode->AddOptions(url.GetOptions());
+
+ return pNode;
+}
+
+// returns the database ids of the path,
+void CDirectoryNode::GetDatabaseInfo(const std::string& strPath, CQueryParams& params)
+{
+ std::unique_ptr<CDirectoryNode> pNode(CDirectoryNode::ParseURL(strPath));
+
+ if (!pNode)
+ return;
+
+ pNode->CollectQueryParams(params);
+}
+
+// Create a node object
+CDirectoryNode* CDirectoryNode::CreateNode(NODE_TYPE Type, const std::string& strName, CDirectoryNode* pParent)
+{
+ switch (Type)
+ {
+ case NODE_TYPE_ROOT:
+ return new CDirectoryNodeRoot(strName, pParent);
+ case NODE_TYPE_OVERVIEW:
+ return new CDirectoryNodeOverview(strName, pParent);
+ case NODE_TYPE_GENRE:
+ case NODE_TYPE_COUNTRY:
+ case NODE_TYPE_SETS:
+ case NODE_TYPE_TAGS:
+ case NODE_TYPE_YEAR:
+ case NODE_TYPE_ACTOR:
+ case NODE_TYPE_DIRECTOR:
+ case NODE_TYPE_STUDIO:
+ case NODE_TYPE_MUSICVIDEOS_ALBUM:
+ return new CDirectoryNodeGrouped(Type, strName, pParent);
+ case NODE_TYPE_TITLE_MOVIES:
+ return new CDirectoryNodeTitleMovies(strName, pParent);
+ case NODE_TYPE_TITLE_TVSHOWS:
+ return new CDirectoryNodeTitleTvShows(strName, pParent);
+ case NODE_TYPE_MOVIES_OVERVIEW:
+ return new CDirectoryNodeMoviesOverview(strName, pParent);
+ case NODE_TYPE_TVSHOWS_OVERVIEW:
+ return new CDirectoryNodeTvShowsOverview(strName, pParent);
+ case NODE_TYPE_SEASONS:
+ return new CDirectoryNodeSeasons(strName, pParent);
+ case NODE_TYPE_EPISODES:
+ return new CDirectoryNodeEpisodes(strName, pParent);
+ case NODE_TYPE_RECENTLY_ADDED_MOVIES:
+ return new CDirectoryNodeRecentlyAddedMovies(strName,pParent);
+ case NODE_TYPE_RECENTLY_ADDED_EPISODES:
+ return new CDirectoryNodeRecentlyAddedEpisodes(strName,pParent);
+ case NODE_TYPE_MUSICVIDEOS_OVERVIEW:
+ return new CDirectoryNodeMusicVideosOverview(strName,pParent);
+ case NODE_TYPE_RECENTLY_ADDED_MUSICVIDEOS:
+ return new CDirectoryNodeRecentlyAddedMusicVideos(strName,pParent);
+ case NODE_TYPE_INPROGRESS_TVSHOWS:
+ return new CDirectoryNodeInProgressTvShows(strName,pParent);
+ case NODE_TYPE_TITLE_MUSICVIDEOS:
+ return new CDirectoryNodeTitleMusicVideos(strName,pParent);
+ default:
+ break;
+ }
+
+ return nullptr;
+}
+
+// Current node name
+const std::string& CDirectoryNode::GetName() const
+{
+ return m_strName;
+}
+
+int CDirectoryNode::GetID() const
+{
+ return atoi(m_strName.c_str());
+}
+
+std::string CDirectoryNode::GetLocalizedName() const
+{
+ return "";
+}
+
+// Current node type
+NODE_TYPE CDirectoryNode::GetType() const
+{
+ return m_Type;
+}
+
+// Return the parent directory node or NULL, if there is no
+CDirectoryNode* CDirectoryNode::GetParent() const
+{
+ return m_pParent;
+}
+
+void CDirectoryNode::RemoveParent()
+{
+ m_pParent = nullptr;
+}
+
+// should be overloaded by a derived class
+// to get the content of a node. Will be called
+// by GetChilds() of a parent node
+bool CDirectoryNode::GetContent(CFileItemList& items) const
+{
+ return false;
+}
+
+// Creates a videodb url
+std::string CDirectoryNode::BuildPath() const
+{
+ std::vector<std::string> array;
+
+ if (!m_strName.empty())
+ array.insert(array.begin(), m_strName);
+
+ CDirectoryNode* pParent=m_pParent;
+ while (pParent != nullptr)
+ {
+ const std::string& strNodeName=pParent->GetName();
+ if (!strNodeName.empty())
+ array.insert(array.begin(), strNodeName);
+
+ pParent = pParent->GetParent();
+ }
+
+ std::string strPath="videodb://";
+ for (int i = 0; i < static_cast<int>(array.size()); ++i)
+ strPath += array[i]+"/";
+
+ std::string options = m_options.GetOptionsString();
+ if (!options.empty())
+ strPath += "?" + options;
+
+ return strPath;
+}
+
+void CDirectoryNode::AddOptions(const std::string &options)
+{
+ if (options.empty())
+ return;
+
+ m_options.AddOptions(options);
+}
+
+// Collects Query params from this and all parent nodes. If a NODE_TYPE can
+// be used as a database parameter, it will be added to the
+// params object.
+void CDirectoryNode::CollectQueryParams(CQueryParams& params) const
+{
+ params.SetQueryParam(m_Type, m_strName);
+
+ CDirectoryNode* pParent=m_pParent;
+ while (pParent != nullptr)
+ {
+ params.SetQueryParam(pParent->GetType(), pParent->GetName());
+ pParent = pParent->GetParent();
+ }
+}
+
+// Should be overloaded by a derived class.
+// Returns the NODE_TYPE of the child nodes.
+NODE_TYPE CDirectoryNode::GetChildType() const
+{
+ return NODE_TYPE_NONE;
+}
+
+// Get the child fileitems of this node
+bool CDirectoryNode::GetChilds(CFileItemList& items)
+{
+ if (CanCache() && items.Load())
+ return true;
+
+ std::unique_ptr<CDirectoryNode> pNode(CDirectoryNode::CreateNode(GetChildType(), "", this));
+
+ bool bSuccess=false;
+ if (pNode)
+ {
+ pNode->m_options = m_options;
+ bSuccess = pNode->GetContent(items);
+ if (bSuccess)
+ {
+ if (CanCache())
+ items.SetCacheToDisc(CFileItemList::CACHE_ALWAYS);
+ }
+ else
+ items.Clear();
+
+ pNode->RemoveParent();
+ }
+
+ return bSuccess;
+}
+
+bool CDirectoryNode::CanCache() const
+{
+ // no caching is required - the list is cached in CGUIMediaWindow::GetDirectory
+ // if it was slow to fetch anyway.
+ return false;
+}
diff --git a/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNode.h b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNode.h
new file mode 100644
index 0000000..7ab27c5
--- /dev/null
+++ b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNode.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2016-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "utils/UrlOptions.h"
+
+#include <string>
+
+class CFileItemList;
+
+namespace XFILE
+{
+ namespace VIDEODATABASEDIRECTORY
+ {
+ class CQueryParams;
+
+ typedef enum _NODE_TYPE
+ {
+ NODE_TYPE_NONE=0,
+ NODE_TYPE_MOVIES_OVERVIEW,
+ NODE_TYPE_TVSHOWS_OVERVIEW,
+ NODE_TYPE_GENRE,
+ NODE_TYPE_ACTOR,
+ NODE_TYPE_ROOT,
+ NODE_TYPE_OVERVIEW,
+ NODE_TYPE_TITLE_MOVIES,
+ NODE_TYPE_YEAR,
+ NODE_TYPE_DIRECTOR,
+ NODE_TYPE_TITLE_TVSHOWS,
+ NODE_TYPE_SEASONS,
+ NODE_TYPE_EPISODES,
+ NODE_TYPE_RECENTLY_ADDED_MOVIES,
+ NODE_TYPE_RECENTLY_ADDED_EPISODES,
+ NODE_TYPE_STUDIO,
+ NODE_TYPE_MUSICVIDEOS_OVERVIEW,
+ NODE_TYPE_RECENTLY_ADDED_MUSICVIDEOS,
+ NODE_TYPE_TITLE_MUSICVIDEOS,
+ NODE_TYPE_MUSICVIDEOS_ALBUM,
+ NODE_TYPE_SETS,
+ NODE_TYPE_COUNTRY,
+ NODE_TYPE_TAGS,
+ NODE_TYPE_INPROGRESS_TVSHOWS
+ } NODE_TYPE;
+
+ typedef struct {
+ NODE_TYPE node;
+ std::string id;
+ int label;
+ } Node;
+
+ class CDirectoryNode
+ {
+ public:
+ static CDirectoryNode* ParseURL(const std::string& strPath);
+ static void GetDatabaseInfo(const std::string& strPath, CQueryParams& params);
+ virtual ~CDirectoryNode();
+
+ NODE_TYPE GetType() const;
+
+ bool GetChilds(CFileItemList& items);
+ virtual NODE_TYPE GetChildType() const;
+ virtual std::string GetLocalizedName() const;
+ void CollectQueryParams(CQueryParams& params) const;
+
+ CDirectoryNode* GetParent() const;
+
+ std::string BuildPath() const;
+
+ virtual bool CanCache() const;
+ protected:
+ CDirectoryNode(NODE_TYPE Type, const std::string& strName, CDirectoryNode* pParent);
+ static CDirectoryNode* CreateNode(NODE_TYPE Type, const std::string& strName, CDirectoryNode* pParent);
+
+ void AddOptions(const std::string& options);
+
+ const std::string& GetName() const;
+ int GetID() const;
+ void RemoveParent();
+
+ virtual bool GetContent(CFileItemList& items) const;
+
+
+ private:
+ NODE_TYPE m_Type;
+ std::string m_strName;
+ CDirectoryNode* m_pParent;
+ CUrlOptions m_options;
+ };
+ }
+}
+
+
+
diff --git a/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeEpisodes.cpp b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeEpisodes.cpp
new file mode 100644
index 0000000..9f629ba
--- /dev/null
+++ b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeEpisodes.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DirectoryNodeEpisodes.h"
+
+#include "FileItem.h"
+#include "QueryParams.h"
+#include "video/VideoDatabase.h"
+
+using namespace XFILE::VIDEODATABASEDIRECTORY;
+
+CDirectoryNodeEpisodes::CDirectoryNodeEpisodes(const std::string& strName, CDirectoryNode* pParent)
+ : CDirectoryNode(NODE_TYPE_EPISODES, strName, pParent)
+{
+
+}
+
+bool CDirectoryNodeEpisodes::GetContent(CFileItemList& items) const
+{
+ CVideoDatabase videodatabase;
+ if (!videodatabase.Open())
+ return false;
+
+ CQueryParams params;
+ CollectQueryParams(params);
+
+ int season = (int)params.GetSeason();
+ if (season == -2)
+ season = -1;
+
+ int details = items.HasProperty("set_videodb_details")
+ ? items.GetProperty("set_videodb_details").asInteger32()
+ : VideoDbDetailsNone;
+
+ bool bSuccess = videodatabase.GetEpisodesNav(
+ BuildPath(), items, params.GetGenreId(), params.GetYear(), params.GetActorId(),
+ params.GetDirectorId(), params.GetTvShowId(), season, SortDescription(), details);
+
+ videodatabase.Close();
+
+ return bSuccess;
+}
+
+NODE_TYPE CDirectoryNodeEpisodes::GetChildType() const
+{
+ return NODE_TYPE_EPISODES;
+}
diff --git a/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeEpisodes.h b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeEpisodes.h
new file mode 100644
index 0000000..e5a640a
--- /dev/null
+++ b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeEpisodes.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DirectoryNode.h"
+
+namespace XFILE
+{
+ namespace VIDEODATABASEDIRECTORY
+ {
+ class CDirectoryNodeEpisodes : public CDirectoryNode
+ {
+ public:
+ CDirectoryNodeEpisodes(const std::string& strEntryName, CDirectoryNode* pParent);
+ protected:
+ bool GetContent(CFileItemList& items) const override;
+ NODE_TYPE GetChildType() const override;
+ };
+ }
+}
diff --git a/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeGrouped.cpp b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeGrouped.cpp
new file mode 100644
index 0000000..73ec115
--- /dev/null
+++ b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeGrouped.cpp
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2016-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DirectoryNodeGrouped.h"
+
+#include "QueryParams.h"
+#include "video/VideoDatabase.h"
+#include "video/VideoDbUrl.h"
+
+using namespace XFILE::VIDEODATABASEDIRECTORY;
+
+CDirectoryNodeGrouped::CDirectoryNodeGrouped(NODE_TYPE type, const std::string& strName, CDirectoryNode* pParent)
+ : CDirectoryNode(type, strName, pParent)
+{ }
+
+NODE_TYPE CDirectoryNodeGrouped::GetChildType() const
+{
+ CQueryParams params;
+ CollectQueryParams(params);
+
+ VideoDbContentType type = static_cast<VideoDbContentType>(params.GetContentType());
+ if (type == VideoDbContentType::MOVIES)
+ return NODE_TYPE_TITLE_MOVIES;
+ if (type == VideoDbContentType::MUSICVIDEOS)
+ {
+ if (GetType() == NODE_TYPE_ACTOR)
+ return NODE_TYPE_MUSICVIDEOS_ALBUM;
+ else
+ return NODE_TYPE_TITLE_MUSICVIDEOS;
+ }
+
+ return NODE_TYPE_TITLE_TVSHOWS;
+}
+
+std::string CDirectoryNodeGrouped::GetLocalizedName() const
+{
+ CVideoDatabase db;
+ if (db.Open())
+ return db.GetItemById(GetContentType(), GetID());
+
+ return "";
+}
+
+bool CDirectoryNodeGrouped::GetContent(CFileItemList& items) const
+{
+ CVideoDatabase videodatabase;
+ if (!videodatabase.Open())
+ return false;
+
+ CQueryParams params;
+ CollectQueryParams(params);
+
+ std::string itemType = GetContentType(params);
+ if (itemType.empty())
+ return false;
+
+ // make sure to translate all IDs in the path into URL parameters
+ CVideoDbUrl videoUrl;
+ if (!videoUrl.FromString(BuildPath()))
+ return false;
+
+ return videodatabase.GetItems(videoUrl.ToString(),
+ static_cast<VideoDbContentType>(params.GetContentType()), itemType,
+ items);
+}
+
+std::string CDirectoryNodeGrouped::GetContentType() const
+{
+ CQueryParams params;
+ CollectQueryParams(params);
+
+ return GetContentType(params);
+}
+
+std::string CDirectoryNodeGrouped::GetContentType(const CQueryParams &params) const
+{
+ switch (GetType())
+ {
+ case NODE_TYPE_GENRE:
+ return "genres";
+ case NODE_TYPE_COUNTRY:
+ return "countries";
+ case NODE_TYPE_SETS:
+ return "sets";
+ case NODE_TYPE_TAGS:
+ return "tags";
+ case NODE_TYPE_YEAR:
+ return "years";
+ case NODE_TYPE_ACTOR:
+ if (static_cast<VideoDbContentType>(params.GetContentType()) ==
+ VideoDbContentType::MUSICVIDEOS)
+ return "artists";
+ else
+ return "actors";
+ case NODE_TYPE_DIRECTOR:
+ return "directors";
+ case NODE_TYPE_STUDIO:
+ return "studios";
+ case NODE_TYPE_MUSICVIDEOS_ALBUM:
+ return "albums";
+
+ case NODE_TYPE_EPISODES:
+ case NODE_TYPE_MOVIES_OVERVIEW:
+ case NODE_TYPE_MUSICVIDEOS_OVERVIEW:
+ case NODE_TYPE_NONE:
+ case NODE_TYPE_OVERVIEW:
+ case NODE_TYPE_RECENTLY_ADDED_EPISODES:
+ case NODE_TYPE_RECENTLY_ADDED_MOVIES:
+ case NODE_TYPE_RECENTLY_ADDED_MUSICVIDEOS:
+ case NODE_TYPE_INPROGRESS_TVSHOWS:
+ case NODE_TYPE_ROOT:
+ case NODE_TYPE_SEASONS:
+ case NODE_TYPE_TITLE_MOVIES:
+ case NODE_TYPE_TITLE_MUSICVIDEOS:
+ case NODE_TYPE_TITLE_TVSHOWS:
+ case NODE_TYPE_TVSHOWS_OVERVIEW:
+ default:
+ break;
+ }
+
+ return "";
+}
diff --git a/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeGrouped.h b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeGrouped.h
new file mode 100644
index 0000000..63a5929
--- /dev/null
+++ b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeGrouped.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DirectoryNode.h"
+
+namespace XFILE
+{
+ namespace VIDEODATABASEDIRECTORY
+ {
+ class CDirectoryNodeGrouped : public CDirectoryNode
+ {
+ public:
+ CDirectoryNodeGrouped(NODE_TYPE type, const std::string& strName, CDirectoryNode* pParent);
+ protected:
+ NODE_TYPE GetChildType() const override;
+ bool GetContent(CFileItemList& items) const override;
+ std::string GetLocalizedName() const override;
+
+ private:
+ std::string GetContentType() const;
+ std::string GetContentType(const CQueryParams &params) const;
+ };
+ }
+}
+
+
diff --git a/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeInProgressTvShows.cpp b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeInProgressTvShows.cpp
new file mode 100644
index 0000000..044f8b3
--- /dev/null
+++ b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeInProgressTvShows.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2016-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DirectoryNodeInProgressTvShows.h"
+
+#include "FileItem.h"
+#include "video/VideoDatabase.h"
+
+using namespace XFILE::VIDEODATABASEDIRECTORY;
+
+CDirectoryNodeInProgressTvShows::CDirectoryNodeInProgressTvShows(const std::string& strName, CDirectoryNode* pParent)
+ : CDirectoryNode(NODE_TYPE_INPROGRESS_TVSHOWS, strName, pParent)
+{
+
+}
+
+NODE_TYPE CDirectoryNodeInProgressTvShows::GetChildType() const
+{
+ return NODE_TYPE_SEASONS;
+}
+
+std::string CDirectoryNodeInProgressTvShows::GetLocalizedName() const
+{
+ CVideoDatabase db;
+ if (db.Open())
+ return db.GetTvShowTitleById(GetID());
+ return "";
+}
+
+bool CDirectoryNodeInProgressTvShows::GetContent(CFileItemList& items) const
+{
+ CVideoDatabase videodatabase;
+ if (!videodatabase.Open())
+ return false;
+
+ int details = items.HasProperty("set_videodb_details")
+ ? items.GetProperty("set_videodb_details").asInteger32()
+ : VideoDbDetailsNone;
+ bool bSuccess = videodatabase.GetInProgressTvShowsNav(BuildPath(), items, 0, details);
+
+ videodatabase.Close();
+
+ return bSuccess;
+}
diff --git a/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeInProgressTvShows.h b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeInProgressTvShows.h
new file mode 100644
index 0000000..987a166
--- /dev/null
+++ b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeInProgressTvShows.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2016-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DirectoryNode.h"
+
+namespace XFILE
+{
+ namespace VIDEODATABASEDIRECTORY
+ {
+ class CDirectoryNodeInProgressTvShows : public CDirectoryNode
+ {
+ public:
+ CDirectoryNodeInProgressTvShows(const std::string& strEntryName, CDirectoryNode* pParent);
+ protected:
+ NODE_TYPE GetChildType() const override;
+ bool GetContent(CFileItemList& items) const override;
+ std::string GetLocalizedName() const override;
+ };
+ }
+}
+
+
diff --git a/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeMoviesOverview.cpp b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeMoviesOverview.cpp
new file mode 100644
index 0000000..20ef46e
--- /dev/null
+++ b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeMoviesOverview.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DirectoryNodeMoviesOverview.h"
+
+#include "FileItem.h"
+#include "guilib/LocalizeStrings.h"
+#include "utils/StringUtils.h"
+#include "video/VideoDatabase.h"
+#include "video/VideoDbUrl.h"
+
+using namespace XFILE::VIDEODATABASEDIRECTORY;
+
+Node MovieChildren[] = {
+ { NODE_TYPE_GENRE, "genres", 135 },
+ { NODE_TYPE_TITLE_MOVIES, "titles", 10024 },
+ { NODE_TYPE_YEAR, "years", 652 },
+ { NODE_TYPE_ACTOR, "actors", 344 },
+ { NODE_TYPE_DIRECTOR, "directors", 20348 },
+ { NODE_TYPE_STUDIO, "studios", 20388 },
+ { NODE_TYPE_SETS, "sets", 20434 },
+ { NODE_TYPE_COUNTRY, "countries", 20451 },
+ { NODE_TYPE_TAGS, "tags", 20459 }
+ };
+
+CDirectoryNodeMoviesOverview::CDirectoryNodeMoviesOverview(const std::string& strName, CDirectoryNode* pParent)
+ : CDirectoryNode(NODE_TYPE_MOVIES_OVERVIEW, strName, pParent)
+{
+
+}
+
+NODE_TYPE CDirectoryNodeMoviesOverview::GetChildType() const
+{
+ for (const Node& node : MovieChildren)
+ if (GetName() == node.id)
+ return node.node;
+
+ return NODE_TYPE_NONE;
+}
+
+std::string CDirectoryNodeMoviesOverview::GetLocalizedName() const
+{
+ for (const Node& node : MovieChildren)
+ if (GetName() == node.id)
+ return g_localizeStrings.Get(node.label);
+ return "";
+}
+
+bool CDirectoryNodeMoviesOverview::GetContent(CFileItemList& items) const
+{
+ CVideoDbUrl videoUrl;
+ if (!videoUrl.FromString(BuildPath()))
+ return false;
+
+ for (unsigned int i = 0; i < sizeof(MovieChildren) / sizeof(Node); ++i)
+ {
+ if (i == 6)
+ {
+ CVideoDatabase db;
+ if (db.Open() && !db.HasSets())
+ continue;
+ }
+
+ CVideoDbUrl itemUrl = videoUrl;
+ std::string strDir = StringUtils::Format("{}/", MovieChildren[i].id);
+ itemUrl.AppendPath(strDir);
+
+ CFileItemPtr pItem(new CFileItem(itemUrl.ToString(), true));
+ pItem->SetLabel(g_localizeStrings.Get(MovieChildren[i].label));
+ pItem->SetCanQueue(false);
+ items.Add(pItem);
+ }
+
+ return true;
+}
diff --git a/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeMoviesOverview.h b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeMoviesOverview.h
new file mode 100644
index 0000000..0f32e28
--- /dev/null
+++ b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeMoviesOverview.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DirectoryNode.h"
+
+namespace XFILE
+{
+ namespace VIDEODATABASEDIRECTORY
+ {
+ class CDirectoryNodeMoviesOverview : public CDirectoryNode
+ {
+ public:
+ CDirectoryNodeMoviesOverview(const std::string& strName, CDirectoryNode* pParent);
+ protected:
+ NODE_TYPE GetChildType() const override;
+ bool GetContent(CFileItemList& items) const override;
+ std::string GetLocalizedName() const override;
+ };
+ }
+}
+
+
diff --git a/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeMusicVideosOverview.cpp b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeMusicVideosOverview.cpp
new file mode 100644
index 0000000..b71d8f1
--- /dev/null
+++ b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeMusicVideosOverview.cpp
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DirectoryNodeMusicVideosOverview.h"
+
+#include "FileItem.h"
+#include "guilib/LocalizeStrings.h"
+#include "utils/StringUtils.h"
+#include "video/VideoDbUrl.h"
+
+using namespace XFILE::VIDEODATABASEDIRECTORY;
+
+Node MusicVideoChildren[] = {
+ { NODE_TYPE_GENRE, "genres", 135 },
+ { NODE_TYPE_TITLE_MUSICVIDEOS, "titles", 10024 },
+ { NODE_TYPE_YEAR, "years", 652 },
+ { NODE_TYPE_ACTOR, "artists", 133 },
+ { NODE_TYPE_MUSICVIDEOS_ALBUM, "albums", 132 },
+ { NODE_TYPE_DIRECTOR, "directors", 20348 },
+ { NODE_TYPE_STUDIO, "studios", 20388 },
+ { NODE_TYPE_TAGS, "tags", 20459 }
+ };
+
+CDirectoryNodeMusicVideosOverview::CDirectoryNodeMusicVideosOverview(const std::string& strName, CDirectoryNode* pParent)
+ : CDirectoryNode(NODE_TYPE_MUSICVIDEOS_OVERVIEW, strName, pParent)
+{
+
+}
+
+NODE_TYPE CDirectoryNodeMusicVideosOverview::GetChildType() const
+{
+ for (const Node& node : MusicVideoChildren)
+ if (GetName() == node.id)
+ return node.node;
+
+ return NODE_TYPE_NONE;
+}
+
+std::string CDirectoryNodeMusicVideosOverview::GetLocalizedName() const
+{
+ for (const Node& node : MusicVideoChildren)
+ if (GetName() == node.id)
+ return g_localizeStrings.Get(node.label);
+ return "";
+}
+
+bool CDirectoryNodeMusicVideosOverview::GetContent(CFileItemList& items) const
+{
+ CVideoDbUrl videoUrl;
+ if (!videoUrl.FromString(BuildPath()))
+ return false;
+
+ for (const Node& node : MusicVideoChildren)
+ {
+ CFileItemPtr pItem(new CFileItem(g_localizeStrings.Get(node.label)));
+
+ CVideoDbUrl itemUrl = videoUrl;
+ std::string strDir = StringUtils::Format("{}/", node.id);
+ itemUrl.AppendPath(strDir);
+ pItem->SetPath(itemUrl.ToString());
+
+ pItem->m_bIsFolder = true;
+ pItem->SetCanQueue(false);
+ pItem->SetSpecialSort(SortSpecialOnTop);
+ items.Add(pItem);
+ }
+
+ return true;
+}
+
diff --git a/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeMusicVideosOverview.h b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeMusicVideosOverview.h
new file mode 100644
index 0000000..4a79592
--- /dev/null
+++ b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeMusicVideosOverview.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DirectoryNode.h"
+
+namespace XFILE
+{
+ namespace VIDEODATABASEDIRECTORY
+ {
+ class CDirectoryNodeMusicVideosOverview : public CDirectoryNode
+ {
+ public:
+ CDirectoryNodeMusicVideosOverview(const std::string& strName, CDirectoryNode* pParent);
+ protected:
+ NODE_TYPE GetChildType() const override;
+ bool GetContent(CFileItemList& items) const override;
+ std::string GetLocalizedName() const override;
+ };
+ }
+}
+
+
diff --git a/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeOverview.cpp b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeOverview.cpp
new file mode 100644
index 0000000..b52ae24
--- /dev/null
+++ b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeOverview.cpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2016-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DirectoryNodeOverview.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "guilib/LocalizeStrings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "video/VideoDatabase.h"
+
+#include <utility>
+
+using namespace XFILE::VIDEODATABASEDIRECTORY;
+
+Node OverviewChildren[] = {
+ { NODE_TYPE_MOVIES_OVERVIEW, "movies", 342 },
+ { NODE_TYPE_TVSHOWS_OVERVIEW, "tvshows", 20343 },
+ { NODE_TYPE_MUSICVIDEOS_OVERVIEW, "musicvideos", 20389 },
+ { NODE_TYPE_RECENTLY_ADDED_MOVIES, "recentlyaddedmovies", 20386 },
+ { NODE_TYPE_RECENTLY_ADDED_EPISODES, "recentlyaddedepisodes", 20387 },
+ { NODE_TYPE_RECENTLY_ADDED_MUSICVIDEOS, "recentlyaddedmusicvideos", 20390 },
+ { NODE_TYPE_INPROGRESS_TVSHOWS, "inprogresstvshows", 626 },
+ };
+
+CDirectoryNodeOverview::CDirectoryNodeOverview(const std::string& strName, CDirectoryNode* pParent)
+ : CDirectoryNode(NODE_TYPE_OVERVIEW, strName, pParent)
+{
+
+}
+
+NODE_TYPE CDirectoryNodeOverview::GetChildType() const
+{
+ for (const Node& node : OverviewChildren)
+ if (GetName() == node.id)
+ return node.node;
+
+ return NODE_TYPE_NONE;
+}
+
+std::string CDirectoryNodeOverview::GetLocalizedName() const
+{
+ for (const Node& node : OverviewChildren)
+ if (GetName() == node.id)
+ return g_localizeStrings.Get(node.label);
+ return "";
+}
+
+bool CDirectoryNodeOverview::GetContent(CFileItemList& items) const
+{
+ CVideoDatabase database;
+ database.Open();
+ bool hasMovies = database.HasContent(VideoDbContentType::MOVIES);
+ bool hasTvShows = database.HasContent(VideoDbContentType::TVSHOWS);
+ bool hasMusicVideos = database.HasContent(VideoDbContentType::MUSICVIDEOS);
+ std::vector<std::pair<const char*, int> > vec;
+ if (hasMovies)
+ {
+ if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MYVIDEOS_FLATTEN))
+ vec.emplace_back("movies/titles", 342);
+ else
+ vec.emplace_back("movies", 342); // Movies
+ }
+ if (hasTvShows)
+ {
+ if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MYVIDEOS_FLATTEN))
+ vec.emplace_back("tvshows/titles", 20343);
+ else
+ vec.emplace_back("tvshows", 20343); // TV Shows
+ }
+ if (hasMusicVideos)
+ {
+ if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MYVIDEOS_FLATTEN))
+ vec.emplace_back("musicvideos/titles", 20389);
+ else
+ vec.emplace_back("musicvideos", 20389); // Music Videos
+ }
+ {
+ if (hasMovies)
+ vec.emplace_back("recentlyaddedmovies", 20386); // Recently Added Movies
+ if (hasTvShows)
+ {
+ vec.emplace_back("recentlyaddedepisodes", 20387); // Recently Added Episodes
+ vec.emplace_back("inprogresstvshows", 626); // InProgress TvShows
+ }
+ if (hasMusicVideos)
+ vec.emplace_back("recentlyaddedmusicvideos", 20390); // Recently Added Music Videos
+ }
+ std::string path = BuildPath();
+ for (unsigned int i = 0; i < vec.size(); ++i)
+ {
+ CFileItemPtr pItem(new CFileItem(path + vec[i].first + "/", true));
+ pItem->SetLabel(g_localizeStrings.Get(vec[i].second));
+ pItem->SetLabelPreformatted(true);
+ pItem->SetCanQueue(false);
+ items.Add(pItem);
+ }
+
+ return true;
+}
diff --git a/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeOverview.h b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeOverview.h
new file mode 100644
index 0000000..5ad498b
--- /dev/null
+++ b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeOverview.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DirectoryNode.h"
+
+namespace XFILE
+{
+ namespace VIDEODATABASEDIRECTORY
+ {
+ class CDirectoryNodeOverview : public CDirectoryNode
+ {
+ public:
+ CDirectoryNodeOverview(const std::string& strName, CDirectoryNode* pParent);
+ protected:
+ NODE_TYPE GetChildType() const override;
+ bool GetContent(CFileItemList& items) const override;
+ std::string GetLocalizedName() const override;
+ };
+ }
+}
+
+
diff --git a/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeRecentlyAddedEpisodes.cpp b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeRecentlyAddedEpisodes.cpp
new file mode 100644
index 0000000..fe50dad
--- /dev/null
+++ b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeRecentlyAddedEpisodes.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DirectoryNodeRecentlyAddedEpisodes.h"
+
+#include "FileItem.h"
+#include "video/VideoDatabase.h"
+
+using namespace XFILE::VIDEODATABASEDIRECTORY;
+
+CDirectoryNodeRecentlyAddedEpisodes::CDirectoryNodeRecentlyAddedEpisodes(const std::string& strName, CDirectoryNode* pParent)
+ : CDirectoryNode(NODE_TYPE_RECENTLY_ADDED_EPISODES, strName, pParent)
+{
+
+}
+
+bool CDirectoryNodeRecentlyAddedEpisodes::GetContent(CFileItemList& items) const
+{
+ CVideoDatabase videodatabase;
+ if (!videodatabase.Open())
+ return false;
+
+ int details = items.HasProperty("set_videodb_details")
+ ? items.GetProperty("set_videodb_details").asInteger32()
+ : VideoDbDetailsNone;
+ bool bSuccess = videodatabase.GetRecentlyAddedEpisodesNav(BuildPath(), items, 0, details);
+
+ videodatabase.Close();
+
+ return bSuccess;
+}
diff --git a/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeRecentlyAddedEpisodes.h b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeRecentlyAddedEpisodes.h
new file mode 100644
index 0000000..2cb1e6c
--- /dev/null
+++ b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeRecentlyAddedEpisodes.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DirectoryNode.h"
+
+namespace XFILE
+{
+ namespace VIDEODATABASEDIRECTORY
+ {
+ class CDirectoryNodeRecentlyAddedEpisodes : public CDirectoryNode
+ {
+ public:
+ CDirectoryNodeRecentlyAddedEpisodes(const std::string& strEntryName, CDirectoryNode* pParent);
+ protected:
+ bool GetContent(CFileItemList& items) const override;
+ };
+ }
+}
+
+
diff --git a/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeRecentlyAddedMovies.cpp b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeRecentlyAddedMovies.cpp
new file mode 100644
index 0000000..202d6f7
--- /dev/null
+++ b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeRecentlyAddedMovies.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DirectoryNodeRecentlyAddedMovies.h"
+
+#include "FileItem.h"
+#include "video/VideoDatabase.h"
+
+using namespace XFILE::VIDEODATABASEDIRECTORY;
+
+CDirectoryNodeRecentlyAddedMovies::CDirectoryNodeRecentlyAddedMovies(const std::string& strName, CDirectoryNode* pParent)
+ : CDirectoryNode(NODE_TYPE_RECENTLY_ADDED_MOVIES, strName, pParent)
+{
+
+}
+
+bool CDirectoryNodeRecentlyAddedMovies::GetContent(CFileItemList& items) const
+{
+ CVideoDatabase videodatabase;
+ if (!videodatabase.Open())
+ return false;
+
+ int details = items.HasProperty("set_videodb_details")
+ ? items.GetProperty("set_videodb_details").asInteger32()
+ : VideoDbDetailsNone;
+ bool bSuccess = videodatabase.GetRecentlyAddedMoviesNav(BuildPath(), items, 0, details);
+
+ videodatabase.Close();
+
+ return bSuccess;
+}
diff --git a/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeRecentlyAddedMovies.h b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeRecentlyAddedMovies.h
new file mode 100644
index 0000000..318e0fc
--- /dev/null
+++ b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeRecentlyAddedMovies.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DirectoryNode.h"
+
+namespace XFILE
+{
+ namespace VIDEODATABASEDIRECTORY
+ {
+ class CDirectoryNodeRecentlyAddedMovies : public CDirectoryNode
+ {
+ public:
+ CDirectoryNodeRecentlyAddedMovies(const std::string& strEntryName, CDirectoryNode* pParent);
+ protected:
+ bool GetContent(CFileItemList& items) const override;
+ };
+ }
+}
+
+
diff --git a/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeRecentlyAddedMusicVideos.cpp b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeRecentlyAddedMusicVideos.cpp
new file mode 100644
index 0000000..d8ba5ef
--- /dev/null
+++ b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeRecentlyAddedMusicVideos.cpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DirectoryNodeRecentlyAddedMusicVideos.h"
+
+#include "FileItem.h"
+#include "video/VideoDatabase.h"
+
+using namespace XFILE::VIDEODATABASEDIRECTORY;
+
+CDirectoryNodeRecentlyAddedMusicVideos::CDirectoryNodeRecentlyAddedMusicVideos(const std::string& strName, CDirectoryNode* pParent)
+ : CDirectoryNode(NODE_TYPE_RECENTLY_ADDED_MUSICVIDEOS, strName, pParent)
+{
+
+}
+
+bool CDirectoryNodeRecentlyAddedMusicVideos::GetContent(CFileItemList& items) const
+{
+ CVideoDatabase videodatabase;
+ if (!videodatabase.Open())
+ return false;
+
+ int details = items.HasProperty("set_videodb_details")
+ ? items.GetProperty("set_videodb_details").asInteger32()
+ : VideoDbDetailsNone;
+ bool bSuccess = videodatabase.GetRecentlyAddedMusicVideosNav(BuildPath(), items, 0, details);
+
+ videodatabase.Close();
+
+ return bSuccess;
+}
+
diff --git a/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeRecentlyAddedMusicVideos.h b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeRecentlyAddedMusicVideos.h
new file mode 100644
index 0000000..bbc9711
--- /dev/null
+++ b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeRecentlyAddedMusicVideos.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DirectoryNode.h"
+
+namespace XFILE
+{
+ namespace VIDEODATABASEDIRECTORY
+ {
+ class CDirectoryNodeRecentlyAddedMusicVideos : public CDirectoryNode
+ {
+ public:
+ CDirectoryNodeRecentlyAddedMusicVideos(const std::string& strEntryName, CDirectoryNode* pParent);
+ protected:
+ bool GetContent(CFileItemList& items) const override;
+ };
+ }
+}
+
+
diff --git a/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeRoot.cpp b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeRoot.cpp
new file mode 100644
index 0000000..6e1b69c
--- /dev/null
+++ b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeRoot.cpp
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DirectoryNodeRoot.h"
+
+using namespace XFILE::VIDEODATABASEDIRECTORY;
+
+CDirectoryNodeRoot::CDirectoryNodeRoot(const std::string& strName, CDirectoryNode* pParent)
+ : CDirectoryNode(NODE_TYPE_ROOT, strName, pParent)
+{
+
+}
+
+NODE_TYPE CDirectoryNodeRoot::GetChildType() const
+{
+ return NODE_TYPE_OVERVIEW;
+}
diff --git a/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeRoot.h b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeRoot.h
new file mode 100644
index 0000000..99f8e6e
--- /dev/null
+++ b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeRoot.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DirectoryNode.h"
+
+namespace XFILE
+{
+ namespace VIDEODATABASEDIRECTORY
+ {
+ class CDirectoryNodeRoot : public CDirectoryNode
+ {
+ public:
+ CDirectoryNodeRoot(const std::string& strName, CDirectoryNode* pParent);
+ protected:
+ NODE_TYPE GetChildType() const override;
+ };
+ }
+}
+
+
diff --git a/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeSeasons.cpp b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeSeasons.cpp
new file mode 100644
index 0000000..25b8b09
--- /dev/null
+++ b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeSeasons.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DirectoryNodeSeasons.h"
+
+#include "FileItem.h"
+#include "QueryParams.h"
+#include "guilib/LocalizeStrings.h"
+#include "utils/StringUtils.h"
+#include "video/VideoDatabase.h"
+
+using namespace XFILE::VIDEODATABASEDIRECTORY;
+
+CDirectoryNodeSeasons::CDirectoryNodeSeasons(const std::string& strName, CDirectoryNode* pParent)
+ : CDirectoryNode(NODE_TYPE_SEASONS, strName, pParent)
+{
+
+}
+
+NODE_TYPE CDirectoryNodeSeasons::GetChildType() const
+{
+ return NODE_TYPE_EPISODES;
+}
+
+std::string CDirectoryNodeSeasons::GetLocalizedName() const
+{
+ switch (GetID())
+ {
+ case 0:
+ return g_localizeStrings.Get(20381); // Specials
+ case -1:
+ return g_localizeStrings.Get(20366); // All Seasons
+ case -2:
+ {
+ CDirectoryNode* pParent = GetParent();
+ if (pParent)
+ return pParent->GetLocalizedName();
+ return "";
+ }
+ default:
+ return GetSeasonTitle();
+ }
+}
+
+std::string CDirectoryNodeSeasons::GetSeasonTitle() const
+{
+ std::string season;
+ CVideoDatabase db;
+ if (db.Open())
+ {
+ CQueryParams params;
+ CollectQueryParams(params);
+
+ season = db.GetTvShowNamedSeasonById(params.GetTvShowId(), params.GetSeason());
+ }
+ if (season.empty())
+ season = StringUtils::Format(g_localizeStrings.Get(20358), GetID()); // Season <n>
+
+ return season;
+}
+
+bool CDirectoryNodeSeasons::GetContent(CFileItemList& items) const
+{
+ CVideoDatabase videodatabase;
+ if (!videodatabase.Open())
+ return false;
+
+ CQueryParams params;
+ CollectQueryParams(params);
+
+ bool bSuccess=videodatabase.GetSeasonsNav(BuildPath(), items, params.GetActorId(), params.GetDirectorId(), params.GetGenreId(), params.GetYear(), params.GetTvShowId());
+
+ videodatabase.Close();
+
+ return bSuccess;
+}
diff --git a/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeSeasons.h b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeSeasons.h
new file mode 100644
index 0000000..d5dd3a1
--- /dev/null
+++ b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeSeasons.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DirectoryNode.h"
+
+namespace XFILE
+{
+ namespace VIDEODATABASEDIRECTORY
+ {
+ class CDirectoryNodeSeasons : public CDirectoryNode
+ {
+ public:
+ CDirectoryNodeSeasons(const std::string& strName, CDirectoryNode* pParent);
+ protected:
+ NODE_TYPE GetChildType() const override;
+ bool GetContent(CFileItemList& items) const override;
+ std::string GetLocalizedName() const override;
+
+ private:
+ /*!
+ * \brief Get the title of choosen season.
+ * \return The season title.
+ */
+ std::string GetSeasonTitle() const;
+ };
+ }
+}
+
+
diff --git a/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTitleMovies.cpp b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTitleMovies.cpp
new file mode 100644
index 0000000..45a83ce
--- /dev/null
+++ b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTitleMovies.cpp
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DirectoryNodeTitleMovies.h"
+
+#include "FileItem.h"
+#include "QueryParams.h"
+#include "video/VideoDatabase.h"
+
+using namespace XFILE::VIDEODATABASEDIRECTORY;
+
+CDirectoryNodeTitleMovies::CDirectoryNodeTitleMovies(const std::string& strName, CDirectoryNode* pParent)
+ : CDirectoryNode(NODE_TYPE_TITLE_MOVIES, strName, pParent)
+{
+
+}
+
+bool CDirectoryNodeTitleMovies::GetContent(CFileItemList& items) const
+{
+ CVideoDatabase videodatabase;
+ if (!videodatabase.Open())
+ return false;
+
+ CQueryParams params;
+ CollectQueryParams(params);
+
+ int details = items.HasProperty("set_videodb_details")
+ ? items.GetProperty("set_videodb_details").asInteger32()
+ : VideoDbDetailsNone;
+
+ bool bSuccess = videodatabase.GetMoviesNav(
+ BuildPath(), items, params.GetGenreId(), params.GetYear(), params.GetActorId(),
+ params.GetDirectorId(), params.GetStudioId(), params.GetCountryId(), params.GetSetId(),
+ params.GetTagId(), SortDescription(), details);
+
+ videodatabase.Close();
+
+ return bSuccess;
+}
diff --git a/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTitleMovies.h b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTitleMovies.h
new file mode 100644
index 0000000..7166ac8
--- /dev/null
+++ b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTitleMovies.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DirectoryNode.h"
+
+namespace XFILE
+{
+ namespace VIDEODATABASEDIRECTORY
+ {
+ class CDirectoryNodeTitleMovies : public CDirectoryNode
+ {
+ public:
+ CDirectoryNodeTitleMovies(const std::string& strEntryName, CDirectoryNode* pParent);
+ protected:
+ bool GetContent(CFileItemList& items) const override;
+ };
+ }
+}
diff --git a/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTitleMusicVideos.cpp b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTitleMusicVideos.cpp
new file mode 100644
index 0000000..95fe300
--- /dev/null
+++ b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTitleMusicVideos.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DirectoryNodeTitleMusicVideos.h"
+
+#include "FileItem.h"
+#include "QueryParams.h"
+#include "video/VideoDatabase.h"
+
+using namespace XFILE::VIDEODATABASEDIRECTORY;
+
+CDirectoryNodeTitleMusicVideos::CDirectoryNodeTitleMusicVideos(const std::string& strName, CDirectoryNode* pParent)
+ : CDirectoryNode(NODE_TYPE_TITLE_MUSICVIDEOS, strName, pParent)
+{
+
+}
+
+bool CDirectoryNodeTitleMusicVideos::GetContent(CFileItemList& items) const
+{
+ CVideoDatabase videodatabase;
+ if (!videodatabase.Open())
+ return false;
+
+ CQueryParams params;
+ CollectQueryParams(params);
+
+ int details = items.HasProperty("set_videodb_details")
+ ? items.GetProperty("set_videodb_details").asInteger32()
+ : VideoDbDetailsNone;
+ bool bSuccess = videodatabase.GetMusicVideosNav(
+ BuildPath(), items, params.GetGenreId(), params.GetYear(), params.GetActorId(),
+ params.GetDirectorId(), params.GetStudioId(), params.GetAlbumId(), params.GetTagId(),
+ SortDescription(), details);
+
+ videodatabase.Close();
+
+ return bSuccess;
+}
diff --git a/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTitleMusicVideos.h b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTitleMusicVideos.h
new file mode 100644
index 0000000..88c54fe
--- /dev/null
+++ b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTitleMusicVideos.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DirectoryNode.h"
+
+namespace XFILE
+{
+ namespace VIDEODATABASEDIRECTORY
+ {
+ class CDirectoryNodeTitleMusicVideos : public CDirectoryNode
+ {
+ public:
+ CDirectoryNodeTitleMusicVideos(const std::string& strEntryName, CDirectoryNode* pParent);
+ protected:
+ bool GetContent(CFileItemList& item) const override;
+ };
+ }
+}
diff --git a/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTitleTvShows.cpp b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTitleTvShows.cpp
new file mode 100644
index 0000000..b1a247c
--- /dev/null
+++ b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTitleTvShows.cpp
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DirectoryNodeTitleTvShows.h"
+
+#include "FileItem.h"
+#include "QueryParams.h"
+#include "video/VideoDatabase.h"
+
+using namespace XFILE::VIDEODATABASEDIRECTORY;
+
+CDirectoryNodeTitleTvShows::CDirectoryNodeTitleTvShows(const std::string& strName, CDirectoryNode* pParent)
+ : CDirectoryNode(NODE_TYPE_TITLE_TVSHOWS, strName, pParent)
+{
+
+}
+
+NODE_TYPE CDirectoryNodeTitleTvShows::GetChildType() const
+{
+ return NODE_TYPE_SEASONS;
+}
+
+std::string CDirectoryNodeTitleTvShows::GetLocalizedName() const
+{
+ CVideoDatabase db;
+ if (db.Open())
+ return db.GetTvShowTitleById(GetID());
+ return "";
+}
+
+bool CDirectoryNodeTitleTvShows::GetContent(CFileItemList& items) const
+{
+ CVideoDatabase videodatabase;
+ if (!videodatabase.Open())
+ return false;
+
+ CQueryParams params;
+ CollectQueryParams(params);
+
+ int details = items.HasProperty("set_videodb_details")
+ ? items.GetProperty("set_videodb_details").asInteger32()
+ : VideoDbDetailsNone;
+
+ bool bSuccess = videodatabase.GetTvShowsNav(
+ BuildPath(), items, params.GetGenreId(), params.GetYear(), params.GetActorId(),
+ params.GetDirectorId(), params.GetStudioId(), params.GetTagId(), SortDescription(), details);
+
+ videodatabase.Close();
+
+ return bSuccess;
+}
diff --git a/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTitleTvShows.h b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTitleTvShows.h
new file mode 100644
index 0000000..697d572
--- /dev/null
+++ b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTitleTvShows.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DirectoryNode.h"
+
+namespace XFILE
+{
+ namespace VIDEODATABASEDIRECTORY
+ {
+ class CDirectoryNodeTitleTvShows : public CDirectoryNode
+ {
+ public:
+ CDirectoryNodeTitleTvShows(const std::string& strName, CDirectoryNode* pParent);
+ protected:
+ NODE_TYPE GetChildType() const override;
+ bool GetContent(CFileItemList& items) const override;
+ std::string GetLocalizedName() const override;
+ };
+ }
+}
+
+
diff --git a/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTvShowsOverview.cpp b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTvShowsOverview.cpp
new file mode 100644
index 0000000..07e5ff4
--- /dev/null
+++ b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTvShowsOverview.cpp
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DirectoryNodeTvShowsOverview.h"
+
+#include "FileItem.h"
+#include "guilib/LocalizeStrings.h"
+#include "utils/StringUtils.h"
+#include "video/VideoDbUrl.h"
+
+using namespace XFILE::VIDEODATABASEDIRECTORY;
+
+Node TvShowChildren[] = {
+ { NODE_TYPE_GENRE, "genres", 135 },
+ { NODE_TYPE_TITLE_TVSHOWS, "titles", 10024 },
+ { NODE_TYPE_YEAR, "years", 652 },
+ { NODE_TYPE_ACTOR, "actors", 344 },
+ { NODE_TYPE_STUDIO, "studios", 20388 },
+ { NODE_TYPE_TAGS, "tags", 20459 }
+ };
+
+CDirectoryNodeTvShowsOverview::CDirectoryNodeTvShowsOverview(const std::string& strName, CDirectoryNode* pParent)
+ : CDirectoryNode(NODE_TYPE_TVSHOWS_OVERVIEW, strName, pParent)
+{
+
+}
+
+NODE_TYPE CDirectoryNodeTvShowsOverview::GetChildType() const
+{
+ if (GetName()=="0")
+ return NODE_TYPE_EPISODES;
+
+ for (const Node& node : TvShowChildren)
+ if (GetName() == node.id)
+ return node.node;
+
+ return NODE_TYPE_NONE;
+}
+
+std::string CDirectoryNodeTvShowsOverview::GetLocalizedName() const
+{
+ for (const Node& node : TvShowChildren)
+ if (GetName() == node.id)
+ return g_localizeStrings.Get(node.label);
+ return "";
+}
+
+bool CDirectoryNodeTvShowsOverview::GetContent(CFileItemList& items) const
+{
+ CVideoDbUrl videoUrl;
+ if (!videoUrl.FromString(BuildPath()))
+ return false;
+
+ for (const Node& node : TvShowChildren)
+ {
+ CFileItemPtr pItem(new CFileItem(g_localizeStrings.Get(node.label)));
+
+ CVideoDbUrl itemUrl = videoUrl;
+ std::string strDir = StringUtils::Format("{}/", node.id);
+ itemUrl.AppendPath(strDir);
+ pItem->SetPath(itemUrl.ToString());
+
+ pItem->m_bIsFolder = true;
+ pItem->SetCanQueue(false);
+ items.Add(pItem);
+ }
+
+ return true;
+}
diff --git a/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTvShowsOverview.h b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTvShowsOverview.h
new file mode 100644
index 0000000..4394e1f
--- /dev/null
+++ b/xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTvShowsOverview.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DirectoryNode.h"
+
+namespace XFILE
+{
+ namespace VIDEODATABASEDIRECTORY
+ {
+ class CDirectoryNodeTvShowsOverview : public CDirectoryNode
+ {
+ public:
+ CDirectoryNodeTvShowsOverview(const std::string& strName, CDirectoryNode* pParent);
+ protected:
+ NODE_TYPE GetChildType() const override;
+ bool GetContent(CFileItemList& items) const override;
+ std::string GetLocalizedName() const override;
+ };
+ }
+}
+
+
diff --git a/xbmc/filesystem/VideoDatabaseDirectory/QueryParams.cpp b/xbmc/filesystem/VideoDatabaseDirectory/QueryParams.cpp
new file mode 100644
index 0000000..d4c78ff
--- /dev/null
+++ b/xbmc/filesystem/VideoDatabaseDirectory/QueryParams.cpp
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2016-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "QueryParams.h"
+
+#include "video/VideoDatabase.h"
+
+using namespace XFILE::VIDEODATABASEDIRECTORY;
+
+CQueryParams::CQueryParams()
+{
+ m_idMovie = -1;
+ m_idGenre = -1;
+ m_idCountry = -1;
+ m_idYear = -1;
+ m_idActor = -1;
+ m_idDirector = -1;
+ m_idContent = static_cast<long>(VideoDbContentType::UNKNOWN);
+ m_idShow = -1;
+ m_idSeason = -1;
+ m_idEpisode = -1;
+ m_idStudio = -1;
+ m_idMVideo = -1;
+ m_idAlbum = -1;
+ m_idSet = -1;
+ m_idTag = -1;
+}
+
+void CQueryParams::SetQueryParam(NODE_TYPE NodeType, const std::string& strNodeName)
+{
+ long idDb=atol(strNodeName.c_str());
+
+ switch (NodeType)
+ {
+ case NODE_TYPE_OVERVIEW:
+ if (strNodeName == "tvshows")
+ m_idContent = static_cast<long>(VideoDbContentType::TVSHOWS);
+ else if (strNodeName == "musicvideos")
+ m_idContent = static_cast<long>(VideoDbContentType::MUSICVIDEOS);
+ else
+ m_idContent = static_cast<long>(VideoDbContentType::MOVIES);
+ break;
+ case NODE_TYPE_GENRE:
+ m_idGenre = idDb;
+ break;
+ case NODE_TYPE_COUNTRY:
+ m_idCountry = idDb;
+ break;
+ case NODE_TYPE_YEAR:
+ m_idYear = idDb;
+ break;
+ case NODE_TYPE_ACTOR:
+ m_idActor = idDb;
+ break;
+ case NODE_TYPE_DIRECTOR:
+ m_idDirector = idDb;
+ break;
+ case NODE_TYPE_TITLE_MOVIES:
+ case NODE_TYPE_RECENTLY_ADDED_MOVIES:
+ m_idMovie = idDb;
+ break;
+ case NODE_TYPE_TITLE_TVSHOWS:
+ case NODE_TYPE_INPROGRESS_TVSHOWS:
+ m_idShow = idDb;
+ break;
+ case NODE_TYPE_SEASONS:
+ m_idSeason = idDb;
+ break;
+ case NODE_TYPE_EPISODES:
+ case NODE_TYPE_RECENTLY_ADDED_EPISODES:
+ m_idEpisode = idDb;
+ break;
+ case NODE_TYPE_STUDIO:
+ m_idStudio = idDb;
+ break;
+ case NODE_TYPE_TITLE_MUSICVIDEOS:
+ case NODE_TYPE_RECENTLY_ADDED_MUSICVIDEOS:
+ m_idMVideo = idDb;
+ break;
+ case NODE_TYPE_MUSICVIDEOS_ALBUM:
+ m_idAlbum = idDb;
+ break;
+ case NODE_TYPE_SETS:
+ m_idSet = idDb;
+ break;
+ case NODE_TYPE_TAGS:
+ m_idTag = idDb;
+ break;
+ default:
+ break;
+ }
+}
diff --git a/xbmc/filesystem/VideoDatabaseDirectory/QueryParams.h b/xbmc/filesystem/VideoDatabaseDirectory/QueryParams.h
new file mode 100644
index 0000000..f9d7e45
--- /dev/null
+++ b/xbmc/filesystem/VideoDatabaseDirectory/QueryParams.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DirectoryNode.h"
+
+namespace XFILE
+{
+ namespace VIDEODATABASEDIRECTORY
+ {
+ class CQueryParams
+ {
+ public:
+ CQueryParams();
+ long GetContentType() const { return m_idContent; }
+ long GetMovieId() const { return m_idMovie; }
+ long GetYear() const { return m_idYear; }
+ long GetGenreId() const { return m_idGenre; }
+ long GetCountryId() const { return m_idCountry; }
+ long GetActorId() const { return m_idActor; }
+ long GetAlbumId() const { return m_idAlbum; }
+ long GetDirectorId() const { return m_idDirector; }
+ long GetTvShowId() const { return m_idShow; }
+ long GetSeason() const { return m_idSeason; }
+ long GetEpisodeId() const { return m_idEpisode; }
+ long GetStudioId() const { return m_idStudio; }
+ long GetMVideoId() const { return m_idMVideo; }
+ long GetSetId() const { return m_idSet; }
+ long GetTagId() const { return m_idTag; }
+
+ protected:
+ void SetQueryParam(NODE_TYPE NodeType, const std::string& strNodeName);
+
+ friend class CDirectoryNode;
+ private:
+ long m_idContent;
+ long m_idMovie;
+ long m_idGenre;
+ long m_idCountry;
+ long m_idYear;
+ long m_idActor;
+ long m_idDirector;
+ long m_idShow;
+ long m_idSeason;
+ long m_idEpisode;
+ long m_idStudio;
+ long m_idMVideo;
+ long m_idAlbum;
+ long m_idSet;
+ long m_idTag;
+ };
+ }
+}
diff --git a/xbmc/filesystem/VideoDatabaseFile.cpp b/xbmc/filesystem/VideoDatabaseFile.cpp
new file mode 100644
index 0000000..036eeba
--- /dev/null
+++ b/xbmc/filesystem/VideoDatabaseFile.cpp
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "VideoDatabaseFile.h"
+
+#include "URL.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "video/VideoDatabase.h"
+#include "video/VideoInfoTag.h"
+
+using namespace XFILE;
+
+CVideoDatabaseFile::CVideoDatabaseFile(void)
+ : COverrideFile(true)
+{ }
+
+CVideoDatabaseFile::~CVideoDatabaseFile(void) = default;
+
+CVideoInfoTag CVideoDatabaseFile::GetVideoTag(const CURL& url)
+{
+ CVideoInfoTag tag;
+
+ std::string strFileName = URIUtils::GetFileName(url.Get());
+ if (strFileName.empty())
+ return tag;
+
+ URIUtils::RemoveExtension(strFileName);
+ if (!StringUtils::IsNaturalNumber(strFileName))
+ return tag;
+ long idDb = atol(strFileName.c_str());
+
+ VideoDbContentType type = GetType(url);
+ if (type == VideoDbContentType::UNKNOWN)
+ return tag;
+
+ CVideoDatabase videoDatabase;
+ if (!videoDatabase.Open())
+ return tag;
+
+ tag = videoDatabase.GetDetailsByTypeAndId(type, idDb);
+
+ return tag;
+}
+
+VideoDbContentType CVideoDatabaseFile::GetType(const CURL& url)
+{
+ std::string strPath = URIUtils::GetDirectory(url.Get());
+ if (strPath.empty())
+ return VideoDbContentType::UNKNOWN;
+
+ std::vector<std::string> pathElem = StringUtils::Split(strPath, "/");
+ if (pathElem.size() == 0)
+ return VideoDbContentType::UNKNOWN;
+
+ std::string itemType = pathElem.at(2);
+ VideoDbContentType type;
+ if (itemType == "movies" || itemType == "recentlyaddedmovies")
+ type = VideoDbContentType::MOVIES;
+ else if (itemType == "episodes" || itemType == "recentlyaddedepisodes" || itemType == "inprogresstvshows" || itemType == "tvshows")
+ type = VideoDbContentType::EPISODES;
+ else if (itemType == "musicvideos" || itemType == "recentlyaddedmusicvideos")
+ type = VideoDbContentType::MUSICVIDEOS;
+ else
+ type = VideoDbContentType::UNKNOWN;
+
+ return type;
+}
+
+
+std::string CVideoDatabaseFile::TranslatePath(const CURL& url)
+{
+ std::string strFileName = URIUtils::GetFileName(url.Get());
+ if (strFileName.empty())
+ return "";
+
+ URIUtils::RemoveExtension(strFileName);
+ if (!StringUtils::IsNaturalNumber(strFileName))
+ return "";
+ long idDb = atol(strFileName.c_str());
+
+ VideoDbContentType type = GetType(url);
+ if (type == VideoDbContentType::UNKNOWN)
+ return "";
+
+ CVideoDatabase videoDatabase;
+ if (!videoDatabase.Open())
+ return "";
+
+ std::string realFilename;
+ videoDatabase.GetFilePathById(idDb, realFilename, type);
+
+ return realFilename;
+}
diff --git a/xbmc/filesystem/VideoDatabaseFile.h b/xbmc/filesystem/VideoDatabaseFile.h
new file mode 100644
index 0000000..d5488f9
--- /dev/null
+++ b/xbmc/filesystem/VideoDatabaseFile.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "filesystem/OverrideFile.h"
+
+enum class VideoDbContentType;
+class CVideoInfoTag;
+class CURL;
+
+namespace XFILE
+{
+class CVideoDatabaseFile : public COverrideFile
+{
+public:
+ CVideoDatabaseFile(void);
+ ~CVideoDatabaseFile(void) override;
+
+ static CVideoInfoTag GetVideoTag(const CURL& url);
+
+protected:
+ std::string TranslatePath(const CURL& url) override;
+ static VideoDbContentType GetType(const CURL& url);
+};
+}
diff --git a/xbmc/filesystem/VirtualDirectory.cpp b/xbmc/filesystem/VirtualDirectory.cpp
new file mode 100644
index 0000000..7134eba
--- /dev/null
+++ b/xbmc/filesystem/VirtualDirectory.cpp
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "VirtualDirectory.h"
+
+#include "Directory.h"
+#include "DirectoryFactory.h"
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "SourcesDirectory.h"
+#include "URL.h"
+#include "Util.h"
+#include "storage/MediaManager.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+
+using namespace XFILE;
+
+namespace XFILE
+{
+
+CVirtualDirectory::CVirtualDirectory(void)
+{
+ m_flags = DIR_FLAG_ALLOW_PROMPT;
+ m_allowNonLocalSources = true;
+}
+
+CVirtualDirectory::~CVirtualDirectory(void) = default;
+
+/*!
+ \brief Add shares to the virtual directory
+ \param VECSOURCES Shares to add
+ \sa CMediaSource, VECSOURCES
+ */
+void CVirtualDirectory::SetSources(const VECSOURCES& vecSources)
+{
+ m_vecSources = vecSources;
+}
+
+/*!
+ \brief Retrieve the shares or the content of a directory.
+ \param strPath Specifies the path of the directory to retrieve or pass an empty string to get the shares.
+ \param items Content of the directory.
+ \return Returns \e true, if directory access is successful.
+ \note If \e strPath is an empty string, the share \e items have thumbnails and icons set, else the thumbnails
+ and icons have to be set manually.
+ */
+
+bool CVirtualDirectory::GetDirectory(const CURL& url, CFileItemList &items)
+{
+ return GetDirectory(url, items, true, false);
+}
+
+bool CVirtualDirectory::GetDirectory(const CURL& url, CFileItemList &items, bool bUseFileDirectories, bool keepImpl)
+{
+ std::string strPath = url.Get();
+ int flags = m_flags;
+ if (!bUseFileDirectories)
+ flags |= DIR_FLAG_NO_FILE_DIRS;
+ if (!strPath.empty() && strPath != "files://")
+ {
+ CURL realURL = URIUtils::SubstitutePath(url);
+ if (!m_pDir)
+ m_pDir.reset(CDirectoryFactory::Create(realURL));
+ bool ret = CDirectory::GetDirectory(strPath, m_pDir, items, m_strFileMask, flags);
+ if (!keepImpl)
+ m_pDir.reset();
+ return ret;
+ }
+
+ // if strPath is blank, clear the list (to avoid parent items showing up)
+ if (strPath.empty())
+ items.Clear();
+
+ // return the root listing
+ items.SetPath(strPath);
+
+ // grab our shares
+ VECSOURCES shares;
+ GetSources(shares);
+ CSourcesDirectory dir;
+ return dir.GetDirectory(shares, items);
+}
+
+void CVirtualDirectory::CancelDirectory()
+{
+ if (m_pDir)
+ m_pDir->CancelDirectory();
+}
+
+/*!
+ \brief Is the share \e strPath in the virtual directory.
+ \param strPath Share to test
+ \return Returns \e true, if share is in the virtual directory.
+ \note The parameter \e strPath can not be a share with directory. Eg. "iso9660://dir" will return \e false.
+ It must be "iso9660://".
+ */
+bool CVirtualDirectory::IsSource(const std::string& strPath, VECSOURCES *sources, std::string *name) const
+{
+ std::string strPathCpy = strPath;
+ StringUtils::TrimRight(strPathCpy, "/\\");
+
+ // just to make sure there's no mixed slashing in share/default defines
+ // ie. f:/video and f:\video was not be recognised as the same directory,
+ // resulting in navigation to a lower directory then the share.
+ if(URIUtils::IsDOSPath(strPathCpy))
+ StringUtils::Replace(strPathCpy, '/', '\\');
+
+ VECSOURCES shares;
+ if (sources)
+ shares = *sources;
+ else
+ GetSources(shares);
+ for (int i = 0; i < (int)shares.size(); ++i)
+ {
+ const CMediaSource& share = shares.at(i);
+ std::string strShare = share.strPath;
+ StringUtils::TrimRight(strShare, "/\\");
+ if(URIUtils::IsDOSPath(strShare))
+ StringUtils::Replace(strShare, '/', '\\');
+ if (strShare == strPathCpy)
+ {
+ if (name)
+ *name = share.strName;
+ return true;
+ }
+ }
+ return false;
+}
+
+/*!
+ \brief Is the share \e path in the virtual directory.
+ \param path Share to test
+ \return Returns \e true, if share is in the virtual directory.
+ \note The parameter \e path CAN be a share with directory. Eg. "iso9660://dir" will
+ return the same as "iso9660://".
+ */
+bool CVirtualDirectory::IsInSource(const std::string &path) const
+{
+ bool isSourceName;
+ VECSOURCES shares;
+ GetSources(shares);
+ int iShare = CUtil::GetMatchingSource(path, shares, isSourceName);
+ if (URIUtils::IsOnDVD(path))
+ { // check to see if our share path is still available and of the same type, as it changes during autodetect
+ // and GetMatchingSource() is too naive at it's matching
+ for (unsigned int i = 0; i < shares.size(); i++)
+ {
+ CMediaSource &share = shares[i];
+ if (URIUtils::IsOnDVD(share.strPath) &&
+ URIUtils::PathHasParent(path, share.strPath))
+ return true;
+ }
+ return false;
+ }
+ //! @todo May need to handle other special cases that GetMatchingSource() fails on
+ return (iShare > -1);
+}
+
+void CVirtualDirectory::GetSources(VECSOURCES &shares) const
+{
+ shares = m_vecSources;
+ // add our plug n play shares
+
+ if (m_allowNonLocalSources)
+ CServiceBroker::GetMediaManager().GetRemovableDrives(shares);
+
+#ifdef HAS_DVD_DRIVE
+ // and update our dvd share
+ for (unsigned int i = 0; i < shares.size(); ++i)
+ {
+ CMediaSource& share = shares[i];
+ if (share.m_iDriveType == CMediaSource::SOURCE_TYPE_DVD)
+ {
+ if (CServiceBroker::GetMediaManager().IsAudio(share.strPath))
+ {
+ share.strStatus = "Audio-CD";
+ share.strPath = "cdda://local/";
+ share.strDiskUniqueId = "";
+ }
+ else
+ {
+ share.strStatus = CServiceBroker::GetMediaManager().GetDiskLabel(share.strPath);
+ share.strDiskUniqueId = CServiceBroker::GetMediaManager().GetDiskUniqueId(share.strPath);
+ if (!share.strPath.length()) // unmounted CD
+ {
+ if (CServiceBroker::GetMediaManager().GetDiscPath() == "iso9660://")
+ share.strPath = "iso9660://";
+ else
+ // share is unmounted and not iso9660, discard it
+ shares.erase(shares.begin() + i--);
+ }
+ }
+ }
+ }
+#endif
+}
+}
+
diff --git a/xbmc/filesystem/VirtualDirectory.h b/xbmc/filesystem/VirtualDirectory.h
new file mode 100644
index 0000000..5a46353
--- /dev/null
+++ b/xbmc/filesystem/VirtualDirectory.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IDirectory.h"
+#include "MediaSource.h"
+
+#include <memory>
+#include <string>
+
+namespace XFILE
+{
+
+ /*!
+ \ingroup windows
+ \brief Get access to shares and it's directories.
+ */
+ class CVirtualDirectory : public IDirectory
+ {
+ public:
+ CVirtualDirectory(void);
+ ~CVirtualDirectory(void) override;
+ bool GetDirectory(const CURL& url, CFileItemList &items) override;
+ void CancelDirectory() override;
+ bool GetDirectory(const CURL& url, CFileItemList &items, bool bUseFileDirectories, bool keepImpl);
+ void SetSources(const VECSOURCES& vecSources);
+ inline unsigned int GetNumberOfSources()
+ {
+ return static_cast<uint32_t>(m_vecSources.size());
+ }
+
+ bool IsSource(const std::string& strPath, VECSOURCES *sources = NULL, std::string *name = NULL) const;
+ bool IsInSource(const std::string& strPath) const;
+
+ inline const CMediaSource& operator [](const int index) const
+ {
+ return m_vecSources[index];
+ }
+
+ inline CMediaSource& operator[](const int index)
+ {
+ return m_vecSources[index];
+ }
+
+ void GetSources(VECSOURCES &sources) const;
+
+ void AllowNonLocalSources(bool allow) { m_allowNonLocalSources = allow; }
+
+ std::shared_ptr<IDirectory> GetDirImpl() { return m_pDir; }
+ void ReleaseDirImpl() { m_pDir.reset(); }
+
+ protected:
+ void CacheThumbs(CFileItemList &items);
+
+ VECSOURCES m_vecSources;
+ bool m_allowNonLocalSources;
+ std::shared_ptr<IDirectory> m_pDir;
+ };
+}
diff --git a/xbmc/filesystem/XbtDirectory.cpp b/xbmc/filesystem/XbtDirectory.cpp
new file mode 100644
index 0000000..5c7d395
--- /dev/null
+++ b/xbmc/filesystem/XbtDirectory.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2015-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "XbtDirectory.h"
+
+#include "FileItem.h"
+#include "URL.h"
+#include "filesystem/Directorization.h"
+#include "filesystem/XbtManager.h"
+#include "guilib/XBTF.h"
+
+#include <stdio.h>
+
+namespace XFILE
+{
+
+static CFileItemPtr XBTFFileToFileItem(const CXBTFFile& entry, const std::string& label, const std::string& path, bool isFolder)
+{
+ CFileItemPtr item(new CFileItem(label));
+ if (!isFolder)
+ item->m_dwSize = static_cast<int64_t>(entry.GetUnpackedSize());
+
+ return item;
+}
+
+CXbtDirectory::CXbtDirectory() = default;
+
+CXbtDirectory::~CXbtDirectory() = default;
+
+bool CXbtDirectory::GetDirectory(const CURL& urlOrig, CFileItemList& items)
+{
+ CURL urlXbt(urlOrig);
+
+ // if this isn't a proper xbt:// path, assume it's the path to a xbt file
+ if (!urlOrig.IsProtocol("xbt"))
+ urlXbt = URIUtils::CreateArchivePath("xbt", urlOrig);
+
+ CURL url(urlXbt);
+ url.SetOptions(""); // delete options to have a clean path to add stuff too
+ url.SetFileName(""); // delete filename too as our names later will contain it
+
+ std::vector<CXBTFFile> files;
+ if (!CXbtManager::GetInstance().GetFiles(url, files))
+ return false;
+
+ // prepare the files for directorization
+ DirectorizeEntries<CXBTFFile> entries;
+ entries.reserve(files.size());
+ for (const auto& file : files)
+ entries.push_back(DirectorizeEntry<CXBTFFile>(file.GetPath(), file));
+
+ Directorize(urlXbt, entries, XBTFFileToFileItem, items);
+
+ return true;
+}
+
+bool CXbtDirectory::ContainsFiles(const CURL& url)
+{
+ return CXbtManager::GetInstance().HasFiles(url);
+}
+
+}
diff --git a/xbmc/filesystem/XbtDirectory.h b/xbmc/filesystem/XbtDirectory.h
new file mode 100644
index 0000000..feb4540
--- /dev/null
+++ b/xbmc/filesystem/XbtDirectory.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2015-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IFileDirectory.h"
+
+#include <map>
+#include <string>
+
+class CXBTFFile;
+
+namespace XFILE
+{
+class CXbtDirectory : public IFileDirectory
+{
+public:
+ CXbtDirectory();
+ ~CXbtDirectory() override;
+
+ // specialization of IDirectory
+ DIR_CACHE_TYPE GetCacheType(const CURL& url) const override { return DIR_CACHE_ALWAYS; }
+ bool GetDirectory(const CURL& url, CFileItemList& items) override;
+
+ // specialization of IFileDirectory
+ bool ContainsFiles(const CURL& url) override;
+};
+}
diff --git a/xbmc/filesystem/XbtFile.cpp b/xbmc/filesystem/XbtFile.cpp
new file mode 100644
index 0000000..92586b7
--- /dev/null
+++ b/xbmc/filesystem/XbtFile.cpp
@@ -0,0 +1,368 @@
+/*
+ * Copyright (C) 2015-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "XbtFile.h"
+
+#include "URL.h"
+#include "filesystem/File.h"
+#include "filesystem/XbtManager.h"
+#include "guilib/TextureBundleXBT.h"
+#include "guilib/XBTFReader.h"
+#include "utils/StringUtils.h"
+
+#include <algorithm>
+#include <climits>
+#include <cstring>
+#include <memory>
+#include <string>
+#include <utility>
+
+namespace XFILE
+{
+
+CXbtFile::CXbtFile()
+ : m_url(),
+ m_xbtfReader(nullptr),
+ m_xbtfFile(),
+ m_frameStartPositions(),
+ m_unpackedFrames()
+{ }
+
+CXbtFile::~CXbtFile()
+{
+ Close();
+}
+
+bool CXbtFile::Open(const CURL& url)
+{
+ if (m_open)
+ return false;
+
+ CURL xbtUrl(url);
+ xbtUrl.SetOptions("");
+
+ if (!GetReaderAndFile(url, m_xbtfReader, m_xbtfFile))
+ return false;
+
+ m_url = url;
+ m_open = true;
+
+ uint64_t frameStartPosition = 0;
+ const auto& frames = m_xbtfFile.GetFrames();
+ for (const auto& frame : frames)
+ {
+ m_frameStartPositions.push_back(frameStartPosition);
+
+ frameStartPosition += frame.GetUnpackedSize();
+ }
+
+ m_frameIndex = 0;
+ m_positionWithinFrame = 0;
+ m_positionTotal = 0;
+
+ m_unpackedFrames.resize(frames.size());
+
+ return true;
+}
+
+void CXbtFile::Close()
+{
+ m_unpackedFrames.clear();
+ m_frameIndex = 0;
+ m_positionWithinFrame = 0;
+ m_positionTotal = 0;
+ m_frameStartPositions.clear();
+ m_open = false;
+}
+
+bool CXbtFile::Exists(const CURL& url)
+{
+ CXBTFFile dummy;
+ return GetFile(url, dummy);
+}
+
+int64_t CXbtFile::GetPosition()
+{
+ if (!m_open)
+ return -1;
+
+ return m_positionTotal;
+}
+
+int64_t CXbtFile::GetLength()
+{
+ if (!m_open)
+ return -1;
+
+ return static_cast<int>(m_xbtfFile.GetUnpackedSize());
+}
+
+int CXbtFile::Stat(struct __stat64 *buffer)
+{
+ if (!m_open)
+ return -1;
+
+ return Stat(m_url, buffer);
+}
+
+int CXbtFile::Stat(const CURL& url, struct __stat64* buffer)
+{
+ if (!buffer)
+ return -1;
+
+ *buffer = {};
+
+ // check if the file exists
+ CXBTFReaderPtr reader;
+ CXBTFFile file;
+ if (!GetReaderAndFile(url, reader, file))
+ {
+ // check if the URL points to the XBT file itself
+ if (!url.GetFileName().empty() || !CFile::Exists(url.GetHostName()))
+ return -1;
+
+ // stat the XBT file itself
+ if (XFILE::CFile::Stat(url.GetHostName(), buffer) != 0)
+ return -1;
+
+ buffer->st_mode = _S_IFDIR;
+ return 0;
+ }
+
+ // stat the XBT file itself
+ if (XFILE::CFile::Stat(url.GetHostName(), buffer) != 0)
+ return -1;
+
+ buffer->st_size = file.GetUnpackedSize();
+
+ return 0;
+}
+
+ssize_t CXbtFile::Read(void* lpBuf, size_t uiBufSize)
+{
+ if (lpBuf == nullptr || !m_open)
+ return -1;
+
+ // nothing to read
+ if (m_xbtfFile.GetFrames().empty() || m_positionTotal >= GetLength())
+ return 0;
+
+ // we can't read more than is left
+ if (static_cast<int64_t>(uiBufSize) > GetLength() - m_positionTotal)
+ uiBufSize = static_cast<ssize_t>(GetLength() - m_positionTotal);
+
+ // we can't read more than we can signal with the return value
+ if (uiBufSize > SSIZE_MAX)
+ uiBufSize = SSIZE_MAX;
+
+ const auto& frames = m_xbtfFile.GetFrames();
+
+ size_t remaining = uiBufSize;
+ while (remaining > 0)
+ {
+ const CXBTFFrame& frame = frames[m_frameIndex];
+
+ // check if we have already unpacked the current frame
+ if (m_unpackedFrames[m_frameIndex].empty())
+ {
+ // unpack the data from the current frame
+ std::vector<uint8_t> unpackedFrame =
+ CTextureBundleXBT::UnpackFrame(*m_xbtfReader.get(), frame);
+ if (unpackedFrame.empty())
+ {
+ Close();
+ return -1;
+ }
+
+ m_unpackedFrames[m_frameIndex] = std::move(unpackedFrame);
+ }
+
+ // determine how many bytes we need to copy from the current frame
+ uint64_t remainingBytesInFrame = frame.GetUnpackedSize() - m_positionWithinFrame;
+ size_t bytesToCopy = remaining;
+ if (remainingBytesInFrame <= SIZE_MAX)
+ bytesToCopy = std::min(remaining, static_cast<size_t>(remainingBytesInFrame));
+
+ // copy the data
+ memcpy(lpBuf, m_unpackedFrames[m_frameIndex].data() + m_positionWithinFrame, bytesToCopy);
+ m_positionWithinFrame += bytesToCopy;
+ m_positionTotal += bytesToCopy;
+ remaining -= bytesToCopy;
+
+ // check if we need to go to the next frame and there is a next frame
+ if (m_positionWithinFrame >= frame.GetUnpackedSize() && m_frameIndex < frames.size() - 1)
+ {
+ m_positionWithinFrame = 0;
+ m_frameIndex += 1;
+ }
+ }
+
+ return uiBufSize;
+}
+
+int64_t CXbtFile::Seek(int64_t iFilePosition, int iWhence)
+{
+ if (!m_open)
+ return -1;
+
+ int64_t newPosition = m_positionTotal;
+ switch (iWhence)
+ {
+ case SEEK_SET:
+ newPosition = iFilePosition;
+ break;
+
+ case SEEK_CUR:
+ newPosition += iFilePosition;
+ break;
+
+ case SEEK_END:
+ newPosition = GetLength() + iFilePosition;
+ break;
+
+ // unsupported seek mode
+ default:
+ return -1;
+ }
+
+ // can't seek before the beginning or after the end of the file
+ if (newPosition < 0 || newPosition >= GetLength())
+ return -1;
+
+ // seeking backwards doesn't require additional work
+ if (newPosition <= m_positionTotal)
+ {
+ m_positionTotal = newPosition;
+ return m_positionTotal;
+ }
+
+ // when seeking forward we need to unpack all frames we seek past
+ const auto& frames = m_xbtfFile.GetFrames();
+ while (m_positionTotal < newPosition)
+ {
+ const CXBTFFrame& frame = frames[m_frameIndex];
+
+ // check if we have already unpacked the current frame
+ if (m_unpackedFrames[m_frameIndex].empty())
+ {
+ // unpack the data from the current frame
+ std::vector<uint8_t> unpackedFrame =
+ CTextureBundleXBT::UnpackFrame(*m_xbtfReader.get(), frame);
+ if (unpackedFrame.empty())
+ {
+ Close();
+ return -1;
+ }
+
+ m_unpackedFrames[m_frameIndex] = std::move(unpackedFrame);
+ }
+
+ int64_t remainingBytesToSeek = newPosition - m_positionTotal;
+ // check if the new position is within the current frame
+ uint64_t remainingBytesInFrame = frame.GetUnpackedSize() - m_positionWithinFrame;
+ if (static_cast<uint64_t>(remainingBytesToSeek) < remainingBytesInFrame)
+ {
+ m_positionWithinFrame += remainingBytesToSeek;
+ break;
+ }
+
+ // otherwise move to the end of the frame
+ m_positionTotal += remainingBytesInFrame;
+ m_positionWithinFrame += remainingBytesInFrame;
+
+ // and go to the next frame if there is a next frame
+ if (m_frameIndex < frames.size() - 1)
+ {
+ m_positionWithinFrame = 0;
+ m_frameIndex += 1;
+ }
+ }
+
+ m_positionTotal = newPosition;
+ return m_positionTotal;
+}
+
+uint32_t CXbtFile::GetImageWidth() const
+{
+ CXBTFFrame frame;
+ if (!GetFirstFrame(frame))
+ return false;
+
+ return frame.GetWidth();
+}
+
+uint32_t CXbtFile::GetImageHeight() const
+{
+ CXBTFFrame frame;
+ if (!GetFirstFrame(frame))
+ return false;
+
+ return frame.GetHeight();
+}
+
+uint32_t CXbtFile::GetImageFormat() const
+{
+ CXBTFFrame frame;
+ if (!GetFirstFrame(frame))
+ return false;
+
+ return frame.GetFormat();
+}
+
+bool CXbtFile::HasImageAlpha() const
+{
+ CXBTFFrame frame;
+ if (!GetFirstFrame(frame))
+ return false;
+
+ return frame.HasAlpha();
+}
+
+bool CXbtFile::GetFirstFrame(CXBTFFrame& frame) const
+{
+ if (!m_open)
+ return false;
+
+ const auto& frames = m_xbtfFile.GetFrames();
+ if (frames.empty())
+ return false;
+
+ frame = frames.at(0);
+ return true;
+}
+
+bool CXbtFile::GetReader(const CURL& url, CXBTFReaderPtr& reader)
+{
+ CURL xbtUrl(url);
+ xbtUrl.SetOptions("");
+
+ return CXbtManager::GetInstance().GetReader(xbtUrl, reader);
+}
+
+bool CXbtFile::GetReaderAndFile(const CURL& url, CXBTFReaderPtr& reader, CXBTFFile& file)
+{
+ if (!GetReader(url, reader))
+ return false;
+
+ CURL xbtUrl(url);
+ xbtUrl.SetOptions("");
+
+ // CXBTFReader stores all filenames in lower case
+ std::string fileName = xbtUrl.GetFileName();
+ StringUtils::ToLower(fileName);
+
+ return reader->Get(fileName, file);
+}
+
+bool CXbtFile::GetFile(const CURL& url, CXBTFFile& file)
+{
+ CXBTFReaderPtr reader;
+ return GetReaderAndFile(url, reader, file);
+}
+
+}
diff --git a/xbmc/filesystem/XbtFile.h b/xbmc/filesystem/XbtFile.h
new file mode 100644
index 0000000..fad28a5
--- /dev/null
+++ b/xbmc/filesystem/XbtFile.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2015-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IFile.h"
+#include "URL.h"
+#include "guilib/XBTF.h"
+#include "guilib/XBTFReader.h"
+
+#include <cstdint>
+#include <cstdio>
+#include <vector>
+
+#include "PlatformDefs.h"
+
+namespace XFILE
+{
+class CXbtFile : public IFile
+{
+public:
+ CXbtFile();
+ ~CXbtFile() override;
+
+ bool Open(const CURL& url) override;
+ void Close() override;
+ bool Exists(const CURL& url) override;
+
+ int64_t GetPosition() override;
+ int64_t GetLength() override;
+
+ int Stat(struct __stat64* buffer) override;
+ int Stat(const CURL& url, struct __stat64* buffer) override;
+
+ ssize_t Read(void* lpBuf, size_t uiBufSize) override;
+ int64_t Seek(int64_t iFilePosition, int iWhence = SEEK_SET) override;
+
+ uint32_t GetImageWidth() const;
+ uint32_t GetImageHeight() const;
+ uint32_t GetImageFormat() const;
+ bool HasImageAlpha() const;
+
+private:
+ bool GetFirstFrame(CXBTFFrame& frame) const;
+
+ static bool GetReader(const CURL& url, CXBTFReaderPtr& reader);
+ static bool GetReaderAndFile(const CURL& url, CXBTFReaderPtr& reader, CXBTFFile& file);
+ static bool GetFile(const CURL& url, CXBTFFile& file);
+
+ CURL m_url;
+ bool m_open = false;
+ CXBTFReaderPtr m_xbtfReader;
+ CXBTFFile m_xbtfFile;
+
+ std::vector<uint64_t> m_frameStartPositions;
+ size_t m_frameIndex = 0;
+ uint64_t m_positionWithinFrame = 0;
+ int64_t m_positionTotal = 0;
+
+ std::vector<std::vector<uint8_t>> m_unpackedFrames;
+};
+}
diff --git a/xbmc/filesystem/XbtManager.cpp b/xbmc/filesystem/XbtManager.cpp
new file mode 100644
index 0000000..bb091f1
--- /dev/null
+++ b/xbmc/filesystem/XbtManager.cpp
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2015-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "XbtManager.h"
+
+#include "URL.h"
+#include "guilib/XBTF.h"
+#include "guilib/XBTFReader.h"
+
+#include <utility>
+
+namespace XFILE
+{
+
+CXbtManager::CXbtManager() = default;
+
+CXbtManager::~CXbtManager() = default;
+
+CXbtManager& CXbtManager::GetInstance()
+{
+ static CXbtManager xbtManager;
+ return xbtManager;
+}
+
+bool CXbtManager::HasFiles(const CURL& path) const
+{
+ return ProcessFile(path) != m_readers.end();
+}
+
+bool CXbtManager::GetFiles(const CURL& path, std::vector<CXBTFFile>& files) const
+{
+ const auto& reader = ProcessFile(path);
+ if (reader == m_readers.end())
+ return false;
+
+ files = reader->second.reader->GetFiles();
+ return true;
+}
+
+bool CXbtManager::GetReader(const CURL& path, CXBTFReaderPtr& reader) const
+{
+ const auto& it = ProcessFile(path);
+ if (it == m_readers.end())
+ return false;
+
+ reader = it->second.reader;
+ return true;
+}
+
+void CXbtManager::Release(const CURL& path)
+{
+ const auto& it = GetReader(path);
+ if (it == m_readers.end())
+ return;
+
+ RemoveReader(it);
+}
+
+CXbtManager::XBTFReaders::iterator CXbtManager::GetReader(const CURL& path) const
+{
+ return GetReader(NormalizePath(path));
+}
+
+CXbtManager::XBTFReaders::iterator CXbtManager::GetReader(const std::string& path) const
+{
+ if (path.empty())
+ return m_readers.end();
+
+ return m_readers.find(path);
+}
+
+void CXbtManager::RemoveReader(XBTFReaders::iterator readerIterator) const
+{
+ if (readerIterator == m_readers.end())
+ return;
+
+ // close the reader
+ readerIterator->second.reader->Close();
+
+ // and remove it from the map
+ m_readers.erase(readerIterator);
+}
+
+CXbtManager::XBTFReaders::const_iterator CXbtManager::ProcessFile(const CURL& path) const
+{
+ std::string filePath = NormalizePath(path);
+
+ // check if the file is known
+ auto it = GetReader(filePath);
+ if (it != m_readers.end())
+ {
+ // check if the XBT file has been modified
+ if (it->second.reader->GetLastModificationTimestamp() <= it->second.lastModification)
+ return it;
+
+ // the XBT file has been modified so close and remove it from the cache
+ // it will be re-opened by the following logic
+ RemoveReader(it);
+ }
+
+ // try to read the file
+ CXBTFReaderPtr reader(new CXBTFReader());
+ if (!reader->Open(filePath))
+ return m_readers.end();
+
+ XBTFReader xbtfReader = {
+ reader,
+ reader->GetLastModificationTimestamp()
+ };
+ std::pair<XBTFReaders::iterator, bool> result = m_readers.insert(std::make_pair(filePath, xbtfReader));
+ return result.first;
+}
+
+std::string CXbtManager::NormalizePath(const CURL& path)
+{
+ if (path.IsProtocol("xbt"))
+ return path.GetHostName();
+
+ return path.Get();
+}
+
+}
diff --git a/xbmc/filesystem/XbtManager.h b/xbmc/filesystem/XbtManager.h
new file mode 100644
index 0000000..1b46eba
--- /dev/null
+++ b/xbmc/filesystem/XbtManager.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2015-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "guilib/XBTFReader.h"
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+class CURL;
+class CXBTFFile;
+
+namespace XFILE
+{
+class CXbtManager
+{
+public:
+ ~CXbtManager();
+
+ static CXbtManager& GetInstance();
+
+ bool HasFiles(const CURL& path) const;
+ bool GetFiles(const CURL& path, std::vector<CXBTFFile>& files) const;
+
+ bool GetReader(const CURL& path, CXBTFReaderPtr& reader) const;
+
+ void Release(const CURL& path);
+
+private:
+ CXbtManager();
+ CXbtManager(const CXbtManager&) = delete;
+ CXbtManager& operator=(const CXbtManager&) = delete;
+
+ struct XBTFReader
+ {
+ CXBTFReaderPtr reader;
+ time_t lastModification;
+ };
+ using XBTFReaders = std::map<std::string, XBTFReader>;
+
+ XBTFReaders::iterator GetReader(const CURL& path) const;
+ XBTFReaders::iterator GetReader(const std::string& path) const;
+ void RemoveReader(XBTFReaders::iterator readerIterator) const;
+ XBTFReaders::const_iterator ProcessFile(const CURL& path) const;
+
+ static std::string NormalizePath(const CURL& path);
+
+ mutable XBTFReaders m_readers;
+};
+}
diff --git a/xbmc/filesystem/ZeroconfDirectory.cpp b/xbmc/filesystem/ZeroconfDirectory.cpp
new file mode 100644
index 0000000..f34b6b2
--- /dev/null
+++ b/xbmc/filesystem/ZeroconfDirectory.cpp
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "ZeroconfDirectory.h"
+
+#include "Directory.h"
+#include "FileItem.h"
+#include "URL.h"
+#include "network/ZeroconfBrowser.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+
+#include <cassert>
+#include <stdexcept>
+
+using namespace XFILE;
+
+CZeroconfDirectory::CZeroconfDirectory()
+{
+ CZeroconfBrowser::GetInstance()->Start();
+}
+
+CZeroconfDirectory::~CZeroconfDirectory() = default;
+
+namespace
+{
+ std::string GetHumanReadableProtocol(std::string const& fcr_service_type)
+ {
+ if(fcr_service_type == "_smb._tcp.")
+ return "SAMBA";
+ else if(fcr_service_type == "_ftp._tcp.")
+ return "FTP";
+ else if(fcr_service_type == "_webdav._tcp.")
+ return "WebDAV";
+ else if(fcr_service_type == "_nfs._tcp.")
+ return "NFS";
+ else if(fcr_service_type == "_sftp-ssh._tcp.")
+ return "SFTP";
+ //fallback, just return the received type
+ return fcr_service_type;
+ }
+ bool GetXBMCProtocol(std::string const& fcr_service_type, std::string& fr_protocol)
+ {
+ if(fcr_service_type == "_smb._tcp.")
+ fr_protocol = "smb";
+ else if(fcr_service_type == "_ftp._tcp.")
+ fr_protocol = "ftp";
+ else if(fcr_service_type == "_webdav._tcp.")
+ fr_protocol = "dav";
+ else if(fcr_service_type == "_nfs._tcp.")
+ fr_protocol = "nfs";
+ else if(fcr_service_type == "_sftp-ssh._tcp.")
+ fr_protocol = "sftp";
+ else
+ return false;
+ return true;
+ }
+}
+
+bool GetDirectoryFromTxtRecords(const CZeroconfBrowser::ZeroconfService& zeroconf_service, CURL& url, CFileItemList &items)
+{
+ bool ret = false;
+
+ //get the txt-records from this service
+ CZeroconfBrowser::ZeroconfService::tTxtRecordMap txtRecords=zeroconf_service.GetTxtRecords();
+
+ //if we have some records
+ if(!txtRecords.empty())
+ {
+ std::string path;
+ std::string username;
+ std::string password;
+
+ //search for a path key entry
+ CZeroconfBrowser::ZeroconfService::tTxtRecordMap::iterator it = txtRecords.find(TXT_RECORD_PATH_KEY);
+
+ //if we found the key - be sure there is a value there
+ if( it != txtRecords.end() && !it->second.empty() )
+ {
+ //from now on we treat the value as a path - everything else would mean
+ //a misconfigured zeroconf server.
+ path=it->second;
+ }
+
+ //search for a username key entry
+ it = txtRecords.find(TXT_RECORD_USERNAME_KEY);
+
+ //if we found the key - be sure there is a value there
+ if( it != txtRecords.end() && !it->second.empty() )
+ {
+ username=it->second;
+ url.SetUserName(username);
+ }
+
+ //search for a password key entry
+ it = txtRecords.find(TXT_RECORD_PASSWORD_KEY);
+
+ //if we found the key - be sure there is a value there
+ if( it != txtRecords.end() && !it->second.empty() )
+ {
+ password=it->second;
+ url.SetPassword(password);
+ }
+
+ //if we got a path - add a item - else at least we maybe have set username and password to theurl
+ if( !path.empty())
+ {
+ CFileItemPtr item(new CFileItem("", true));
+ std::string urlStr(url.Get());
+ //if path has a leading slash (sure it should have one)
+ if( path.at(0) == '/' )
+ {
+ URIUtils::RemoveSlashAtEnd(urlStr);//we don't need the slash at and of url then
+ }
+ else//path doesn't start with slash -
+ {//this is some kind of misconfiguration - we fix it by adding a slash to the url
+ URIUtils::AddSlashAtEnd(urlStr);
+ }
+
+ //add slash at end of path since it has to be a folder
+ URIUtils::AddSlashAtEnd(path);
+ //this is the full path including remote stuff (e.x. nfs://ip/path
+ item->SetPath(urlStr + path);
+ //remove the slash at the end of the path or GetFileName will not give the last dir
+ URIUtils::RemoveSlashAtEnd(path);
+ //set the label to the last directory in path
+ if( !URIUtils::GetFileName(path).empty() )
+ item->SetLabel(URIUtils::GetFileName(path));
+ else
+ item->SetLabel("/");
+
+ item->SetLabelPreformatted(true);
+ //just set the default folder icon
+ item->FillInDefaultIcon();
+ item->m_bIsShareOrDrive=true;
+ items.Add(item);
+ ret = true;
+ }
+ }
+ return ret;
+}
+
+bool CZeroconfDirectory::GetDirectory(const CURL& url, CFileItemList &items)
+{
+ assert(url.IsProtocol("zeroconf"));
+ std::string strPath = url.Get();
+ std::string path = strPath.substr(11, strPath.length());
+ URIUtils::RemoveSlashAtEnd(path);
+ if(path.empty())
+ {
+ std::vector<CZeroconfBrowser::ZeroconfService> found_services = CZeroconfBrowser::GetInstance()->GetFoundServices();
+ for (auto& it : found_services)
+ {
+ //only use discovered services we can connect to through directory
+ std::string tmp;
+ if (GetXBMCProtocol(it.GetType(), tmp))
+ {
+ CFileItemPtr item(new CFileItem("", true));
+ CURL url;
+ url.SetProtocol("zeroconf");
+ std::string service_path(CURL::Encode(CZeroconfBrowser::ZeroconfService::toPath(it)));
+ url.SetFileName(service_path);
+ item->SetPath(url.Get());
+
+ //now do the formatting
+ std::string protocol = GetHumanReadableProtocol(it.GetType());
+ item->SetLabel(it.GetName() + " (" + protocol + ")");
+ item->SetLabelPreformatted(true);
+ //just set the default folder icon
+ item->FillInDefaultIcon();
+ items.Add(item);
+ }
+ }
+ return true;
+ }
+ else
+ {
+ //decode the path first
+ std::string decoded(CURL::Decode(path));
+ try
+ {
+ CZeroconfBrowser::ZeroconfService zeroconf_service = CZeroconfBrowser::ZeroconfService::fromPath(decoded);
+
+ if(!CZeroconfBrowser::GetInstance()->ResolveService(zeroconf_service))
+ {
+ CLog::Log(LOGINFO,
+ "CZeroconfDirectory::GetDirectory service ( {} ) could not be resolved in time",
+ zeroconf_service.GetName());
+ return false;
+ }
+ else
+ {
+ assert(!zeroconf_service.GetIP().empty());
+ CURL service;
+ service.SetPort(zeroconf_service.GetPort());
+ service.SetHostName(zeroconf_service.GetIP());
+ //do protocol conversion (_smb._tcp -> smb)
+ //! @todo try automatic conversion -> remove leading '_' and '._tcp'?
+ std::string protocol;
+ if(!GetXBMCProtocol(zeroconf_service.GetType(), protocol))
+ {
+ CLog::Log(LOGERROR,
+ "CZeroconfDirectory::GetDirectory Unknown service type ({}), skipping; ",
+ zeroconf_service.GetType());
+ return false;
+ }
+
+ service.SetProtocol(protocol);
+
+ //first try to show the txt-record defined path if any
+ if(GetDirectoryFromTxtRecords(zeroconf_service, service, items))
+ {
+ return true;
+ }
+ else//no txt record path - so let the CDirectory handler show the folders
+ {
+ return CDirectory::GetDirectory(service.Get(), items, "", DIR_FLAG_ALLOW_PROMPT);
+ }
+ }
+ } catch (std::runtime_error& e) {
+ CLog::Log(LOGERROR,
+ "CZeroconfDirectory::GetDirectory failed getting directory: '{}'. Error: '{}'",
+ decoded, e.what());
+ return false;
+ }
+ }
+}
diff --git a/xbmc/filesystem/ZeroconfDirectory.h b/xbmc/filesystem/ZeroconfDirectory.h
new file mode 100644
index 0000000..fddc2ee
--- /dev/null
+++ b/xbmc/filesystem/ZeroconfDirectory.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IDirectory.h"
+//txt-records as of http://www.dns-sd.org/ServiceTypes.html
+#define TXT_RECORD_PATH_KEY "path"
+#define TXT_RECORD_USERNAME_KEY "u"
+#define TXT_RECORD_PASSWORD_KEY "p"
+
+namespace XFILE
+{
+ class CZeroconfDirectory : public IDirectory
+ {
+ public:
+ CZeroconfDirectory(void);
+ ~CZeroconfDirectory(void) override;
+ bool GetDirectory(const CURL& url, CFileItemList &items) override;
+ DIR_CACHE_TYPE GetCacheType(const CURL& url) const override { return DIR_CACHE_NEVER; }
+ };
+}
+
diff --git a/xbmc/filesystem/ZipDirectory.cpp b/xbmc/filesystem/ZipDirectory.cpp
new file mode 100644
index 0000000..bfe43fb
--- /dev/null
+++ b/xbmc/filesystem/ZipDirectory.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "ZipDirectory.h"
+
+#include "FileItem.h"
+#include "URL.h"
+#include "ZipManager.h"
+#include "filesystem/Directorization.h"
+#include "utils/CharsetConverter.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+
+#include <vector>
+
+namespace XFILE
+{
+
+ static CFileItemPtr ZipEntryToFileItem(const SZipEntry& entry, const std::string& label, const std::string& path, bool isFolder)
+ {
+ CFileItemPtr item(new CFileItem(label));
+ if (!isFolder)
+ {
+ item->m_dwSize = entry.usize;
+ item->m_idepth = entry.method;
+ }
+
+ return item;
+ }
+
+ CZipDirectory::CZipDirectory() = default;
+
+ CZipDirectory::~CZipDirectory() = default;
+
+ bool CZipDirectory::GetDirectory(const CURL& urlOrig, CFileItemList& items)
+ {
+ CURL urlZip(urlOrig);
+
+ /* if this isn't a proper archive path, assume it's the path to a archive file */
+ if (!urlOrig.IsProtocol("zip"))
+ urlZip = URIUtils::CreateArchivePath("zip", urlOrig);
+
+ std::vector<SZipEntry> zipEntries;
+ if (!g_ZipManager.GetZipList(urlZip, zipEntries))
+ return false;
+
+ // prepare the ZIP entries for directorization
+ DirectorizeEntries<SZipEntry> entries;
+ entries.reserve(zipEntries.size());
+ for (const auto& zipEntry : zipEntries)
+ entries.push_back(DirectorizeEntry<SZipEntry>(zipEntry.name, zipEntry));
+
+ // directorize the ZIP entries into files and directories
+ Directorize(urlZip, entries, ZipEntryToFileItem, items);
+
+ return true;
+ }
+
+ bool CZipDirectory::ContainsFiles(const CURL& url)
+ {
+ std::vector<SZipEntry> items;
+ g_ZipManager.GetZipList(url, items);
+ if (items.size())
+ {
+ if (items.size() > 1)
+ return true;
+
+ return false;
+ }
+
+ return false;
+ }
+}
+
diff --git a/xbmc/filesystem/ZipDirectory.h b/xbmc/filesystem/ZipDirectory.h
new file mode 100644
index 0000000..bd09c34
--- /dev/null
+++ b/xbmc/filesystem/ZipDirectory.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IFileDirectory.h"
+
+namespace XFILE
+{
+ class CZipDirectory : public IFileDirectory
+ {
+ public:
+ CZipDirectory();
+ ~CZipDirectory() override;
+ bool GetDirectory(const CURL& url, CFileItemList& items) override;
+ bool ContainsFiles(const CURL& url) override;
+ DIR_CACHE_TYPE GetCacheType(const CURL& url) const override { return DIR_CACHE_ALWAYS; }
+ };
+}
diff --git a/xbmc/filesystem/ZipFile.cpp b/xbmc/filesystem/ZipFile.cpp
new file mode 100644
index 0000000..eb3d953
--- /dev/null
+++ b/xbmc/filesystem/ZipFile.cpp
@@ -0,0 +1,531 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "ZipFile.h"
+
+#include "URL.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+
+#include <sys/stat.h>
+
+#define ZIP_CACHE_LIMIT 4*1024*1024
+
+using namespace XFILE;
+
+CZipFile::CZipFile()
+{
+ m_szStringBuffer = NULL;
+ m_szStartOfStringBuffer = NULL;
+ m_iDataInStringBuffer = 0;
+ m_bCached = false;
+ m_iRead = -1;
+}
+
+CZipFile::~CZipFile()
+{
+ delete[] m_szStringBuffer;
+ Close();
+}
+
+bool CZipFile::Open(const CURL&url)
+{
+ const std::string& strOpts = url.GetOptions();
+ CURL url2(url);
+ url2.SetOptions("");
+ if (!g_ZipManager.GetZipEntry(url2,mZipItem))
+ return false;
+
+ if ((mZipItem.flags & 64) == 64)
+ {
+ CLog::Log(LOGERROR,"FileZip: encrypted file, not supported!");
+ return false;
+ }
+
+ if ((mZipItem.method != 8) && (mZipItem.method != 0))
+ {
+ CLog::Log(LOGERROR,"FileZip: unsupported compression method!");
+ return false;
+ }
+
+ if (mZipItem.method != 0 && mZipItem.usize > ZIP_CACHE_LIMIT && strOpts != "?cache=no")
+ {
+ if (!CFile::Exists("special://temp/" + URIUtils::GetFileName(url2)))
+ {
+ url2.SetOptions("?cache=no");
+ const CURL pathToUrl("special://temp/" + URIUtils::GetFileName(url2));
+ if (!CFile::Copy(url2, pathToUrl))
+ return false;
+ }
+ m_bCached = true;
+ return mFile.Open("special://temp/" + URIUtils::GetFileName(url2));
+ }
+
+ if (!mFile.Open(url.GetHostName())) // this is the zip-file, always open binary
+ {
+ CLog::Log(LOGERROR, "FileZip: unable to open zip file {}!", url.GetHostName());
+ return false;
+ }
+ mFile.Seek(mZipItem.offset,SEEK_SET);
+ return InitDecompress();
+}
+
+bool CZipFile::InitDecompress()
+{
+ m_iRead = 1;
+ m_iFilePos = 0;
+ m_iZipFilePos = 0;
+ m_iAvailBuffer = 0;
+ m_bFlush = false;
+ m_ZStream.zalloc = Z_NULL;
+ m_ZStream.zfree = Z_NULL;
+ m_ZStream.opaque = Z_NULL;
+ if( mZipItem.method != 0 )
+ {
+ if (inflateInit2(&m_ZStream,-MAX_WBITS) != Z_OK)
+ {
+ CLog::Log(LOGERROR,"FileZip: error initializing zlib!");
+ return false;
+ }
+ }
+ m_ZStream.next_in = (Bytef*)m_szBuffer;
+ m_ZStream.avail_in = 0;
+ m_ZStream.total_out = 0;
+
+ return true;
+}
+
+int64_t CZipFile::GetLength()
+{
+ return mZipItem.usize;
+}
+
+int64_t CZipFile::GetPosition()
+{
+ if (m_bCached)
+ return mFile.GetPosition();
+
+ return m_iFilePos;
+}
+
+int64_t CZipFile::Seek(int64_t iFilePosition, int iWhence)
+{
+ if (m_bCached)
+ return mFile.Seek(iFilePosition,iWhence);
+ if (mZipItem.method == 0) // this is easy
+ {
+ int64_t iResult;
+ switch (iWhence)
+ {
+ case SEEK_SET:
+ if (iFilePosition > mZipItem.usize)
+ return -1;
+ m_iFilePos = iFilePosition;
+ m_iZipFilePos = m_iFilePos;
+ iResult = mFile.Seek(iFilePosition+mZipItem.offset,SEEK_SET)-mZipItem.offset;
+ return iResult;
+ break;
+
+ case SEEK_CUR:
+ if (m_iFilePos+iFilePosition > mZipItem.usize)
+ return -1;
+ m_iFilePos += iFilePosition;
+ m_iZipFilePos = m_iFilePos;
+ iResult = mFile.Seek(iFilePosition,SEEK_CUR)-mZipItem.offset;
+ return iResult;
+ break;
+
+ case SEEK_END:
+ if (iFilePosition > mZipItem.usize)
+ return -1;
+ m_iFilePos = mZipItem.usize+iFilePosition;
+ m_iZipFilePos = m_iFilePos;
+ iResult = mFile.Seek(mZipItem.offset+mZipItem.usize+iFilePosition,SEEK_SET)-mZipItem.offset;
+ return iResult;
+ break;
+ default:
+ return -1;
+
+ }
+ }
+ // here goes the stupid part..
+ if (mZipItem.method == 8)
+ {
+ static const int blockSize = 128 * 1024;
+ std::vector<char> buf(blockSize);
+ switch (iWhence)
+ {
+ case SEEK_SET:
+ if (iFilePosition == m_iFilePos)
+ return m_iFilePos; // mp3reader does this lots-of-times
+ if (iFilePosition > mZipItem.usize || iFilePosition < 0)
+ return -1;
+ // read until position in 128k blocks.. only way to do it due to format.
+ // can't start in the middle of data since then we'd have no clue where
+ // we are in uncompressed data..
+ if (iFilePosition < m_iFilePos)
+ {
+ m_iFilePos = 0;
+ m_iZipFilePos = 0;
+ inflateEnd(&m_ZStream);
+ inflateInit2(&m_ZStream,-MAX_WBITS); // simply restart zlib
+ mFile.Seek(mZipItem.offset,SEEK_SET);
+ m_ZStream.next_in = (Bytef*)m_szBuffer;
+ m_ZStream.avail_in = 0;
+ m_ZStream.total_out = 0;
+ while (m_iFilePos < iFilePosition)
+ {
+ ssize_t iToRead = (iFilePosition - m_iFilePos) > blockSize ? blockSize : iFilePosition - m_iFilePos;
+ if (Read(buf.data(), iToRead) != iToRead)
+ return -1;
+ }
+ return m_iFilePos;
+ }
+ else // seek forward
+ return Seek(iFilePosition-m_iFilePos,SEEK_CUR);
+ break;
+
+ case SEEK_CUR:
+ if (iFilePosition < 0)
+ return Seek(m_iFilePos+iFilePosition,SEEK_SET); // can't rewind stream
+ // read until requested position, drop data
+ if (m_iFilePos+iFilePosition > mZipItem.usize)
+ return -1;
+ iFilePosition += m_iFilePos;
+ while (m_iFilePos < iFilePosition)
+ {
+ ssize_t iToRead = (iFilePosition - m_iFilePos)>blockSize ? blockSize : iFilePosition - m_iFilePos;
+ if (Read(buf.data(), iToRead) != iToRead)
+ return -1;
+ }
+ return m_iFilePos;
+ break;
+
+ case SEEK_END:
+ // now this is a nasty bastard, possibly takes lotsoftime
+ // uncompress, minding m_ZStream.total_out
+
+ while(static_cast<ssize_t>(m_ZStream.total_out) < mZipItem.usize+iFilePosition)
+ {
+ ssize_t iToRead = (mZipItem.usize + iFilePosition - m_ZStream.total_out > blockSize) ? blockSize : mZipItem.usize + iFilePosition - m_ZStream.total_out;
+ if (Read(buf.data(), iToRead) != iToRead)
+ return -1;
+ }
+ return m_iFilePos;
+ break;
+ default:
+ return -1;
+ }
+ }
+ return -1;
+}
+
+bool CZipFile::Exists(const CURL& url)
+{
+ SZipEntry item;
+ if (g_ZipManager.GetZipEntry(url,item))
+ return true;
+ return false;
+}
+
+int CZipFile::Stat(struct __stat64 *buffer)
+{
+ int ret;
+ struct tm tm = {};
+
+ ret = mFile.Stat(buffer);
+ tm.tm_sec = (mZipItem.mod_time & 0x1F) << 1;
+ tm.tm_min = (mZipItem.mod_time & 0x7E0) >> 5;
+ tm.tm_hour = (mZipItem.mod_time & 0xF800) >> 11;
+ tm.tm_mday = (mZipItem.mod_date & 0x1F);
+ tm.tm_mon = (mZipItem.mod_date & 0x1E0) >> 5;
+ tm.tm_year = (mZipItem.mod_date & 0xFE00) >> 9;
+ buffer->st_atime = buffer->st_ctime = buffer->st_mtime = mktime(&tm);
+
+ buffer->st_size = mZipItem.usize;
+ buffer->st_dev = (buffer->st_dev << 16) ^ (buffer->st_ino << 16);
+ buffer->st_ino ^= mZipItem.crc32;
+ return ret;
+}
+
+int CZipFile::Stat(const CURL& url, struct __stat64* buffer)
+{
+ if (!buffer)
+ return -1;
+
+ if (!g_ZipManager.GetZipEntry(url, mZipItem))
+ {
+ if (url.GetFileName().empty() && CFile::Exists(url.GetHostName()))
+ { // when accessing the zip "root" recognize it as a directory
+ *buffer = {};
+ buffer->st_mode = _S_IFDIR;
+ return 0;
+ }
+ else
+ return -1;
+ }
+
+ *buffer = {};
+ buffer->st_gid = 0;
+ buffer->st_atime = buffer->st_ctime = mZipItem.mod_time;
+ buffer->st_size = mZipItem.usize;
+ return 0;
+}
+
+ssize_t CZipFile::Read(void* lpBuf, size_t uiBufSize)
+{
+ if (uiBufSize > SSIZE_MAX)
+ uiBufSize = SSIZE_MAX;
+
+ if (m_bCached)
+ return mFile.Read(lpBuf,uiBufSize);
+
+ // flush what might be left in the string buffer
+ if (m_iDataInStringBuffer > 0)
+ {
+ size_t iMax = uiBufSize>m_iDataInStringBuffer?m_iDataInStringBuffer:uiBufSize;
+ memcpy(lpBuf,m_szStartOfStringBuffer,iMax);
+ uiBufSize -= iMax;
+ m_iDataInStringBuffer -= iMax;
+ }
+ if (mZipItem.method == 8) // deflated
+ {
+ uLong iDecompressed = 0;
+ uLong prevOut = m_ZStream.total_out;
+ while ((iDecompressed < uiBufSize) && ((m_iZipFilePos < mZipItem.csize) || (m_bFlush)))
+ {
+ m_ZStream.next_out = (Bytef*)(lpBuf)+iDecompressed;
+ m_ZStream.avail_out = static_cast<uInt>(uiBufSize-iDecompressed);
+ if (m_bFlush) // need to flush buffer !
+ {
+ int iMessage = inflate(&m_ZStream,Z_SYNC_FLUSH);
+ m_bFlush = ((iMessage == Z_OK) && (m_ZStream.avail_out == 0))?true:false;
+ if (!m_ZStream.avail_out) // flush filled buffer, get out of here
+ {
+ iDecompressed = m_ZStream.total_out-prevOut;
+ break;
+ }
+ }
+
+ if (!m_ZStream.avail_in)
+ {
+ if (!FillBuffer()) // eof!
+ {
+ iDecompressed = m_ZStream.total_out-prevOut;
+ break;
+ }
+ }
+
+ int iMessage = inflate(&m_ZStream,Z_SYNC_FLUSH);
+ if (iMessage < 0)
+ {
+ Close();
+ return -1; // READ ERROR
+ }
+
+ m_bFlush = ((iMessage == Z_OK) && (m_ZStream.avail_out == 0))?true:false; // more info in input buffer
+
+ iDecompressed = m_ZStream.total_out-prevOut;
+ }
+ m_iFilePos += iDecompressed;
+ return static_cast<unsigned int>(iDecompressed);
+ }
+ else if (mZipItem.method == 0) // uncompressed. just read from file, but mind our boundaries.
+ {
+ if (uiBufSize+m_iFilePos > mZipItem.csize)
+ uiBufSize = mZipItem.csize-m_iFilePos;
+
+ if (uiBufSize == 0)
+ return 0; // we are past eof, this shouldn't happen but test anyway
+
+ ssize_t iResult = mFile.Read(lpBuf,uiBufSize);
+ if (iResult < 0)
+ return -1;
+ m_iZipFilePos += iResult;
+ m_iFilePos += iResult;
+ return iResult;
+ }
+ else
+ return -1; // shouldn't happen. compression method checked in open
+}
+
+void CZipFile::Close()
+{
+ if (mZipItem.method == 8 && !m_bCached && m_iRead != -1)
+ inflateEnd(&m_ZStream);
+
+ mFile.Close();
+}
+
+bool CZipFile::FillBuffer()
+{
+ ssize_t sToRead = 65535;
+ if (m_iZipFilePos+65535 > mZipItem.csize)
+ sToRead = mZipItem.csize-m_iZipFilePos;
+
+ if (sToRead <= 0)
+ return false; // eof!
+
+ if (mFile.Read(m_szBuffer,sToRead) != sToRead)
+ return false;
+ m_ZStream.avail_in = static_cast<unsigned int>(sToRead);
+ m_ZStream.next_in = reinterpret_cast<Byte*>(m_szBuffer);
+ m_iZipFilePos += sToRead;
+ return true;
+}
+
+void CZipFile::DestroyBuffer(void* lpBuffer, int iBufSize)
+{
+ if (!m_bFlush)
+ return;
+ int iMessage = Z_OK;
+ while ((iMessage == Z_OK) && (m_ZStream.avail_out == 0))
+ {
+ m_ZStream.next_out = (Bytef*)lpBuffer;
+ m_ZStream.avail_out = iBufSize;
+ iMessage = inflate(&m_ZStream,Z_SYNC_FLUSH);
+ }
+ m_bFlush = false;
+}
+
+int CZipFile::UnpackFromMemory(std::string& strDest, const std::string& strInput, bool isGZ)
+{
+ unsigned int iPos=0;
+ int iResult=0;
+ while( iPos+LHDR_SIZE < strInput.size() || isGZ)
+ {
+ if (!isGZ)
+ {
+ CZipManager::readHeader(strInput.data()+iPos,mZipItem);
+ if (mZipItem.header == ZIP_DATA_RECORD_HEADER)
+ {
+ // this header concerns a file we already processed, so we can just skip it
+ iPos += DREC_SIZE;
+ continue;
+ }
+ if (mZipItem.header != ZIP_LOCAL_HEADER)
+ return iResult;
+ if( (mZipItem.flags & 8) == 8 )
+ {
+ // if an extended local header (=data record header) is present,
+ // the following fields are 0 in the local header and we need to read
+ // them from the extended local header
+
+ // search for the extended local header
+ unsigned int i = iPos + LHDR_SIZE + mZipItem.flength + mZipItem.elength;
+ while (1)
+ {
+ if (i + DREC_SIZE > strInput.size())
+ {
+ CLog::Log(LOGERROR, "FileZip: extended local header expected, but not present!");
+ return iResult;
+ }
+ if ((strInput[i] == 0x50) && (strInput[i + 1] == 0x4b) &&
+ (strInput[i + 2] == 0x07) && (strInput[i + 3] == 0x08))
+ break; // header found
+ i++;
+ }
+ // ZIP is little endian:
+ mZipItem.crc32 = static_cast<uint8_t>(strInput[i + 4]) |
+ static_cast<uint8_t>(strInput[i + 5]) << 8 |
+ static_cast<uint8_t>(strInput[i + 6]) << 16 |
+ static_cast<uint8_t>(strInput[i + 7]) << 24;
+ mZipItem.csize = static_cast<uint8_t>(strInput[i + 8]) |
+ static_cast<uint8_t>(strInput[i + 9]) << 8 |
+ static_cast<uint8_t>(strInput[i + 10]) << 16 |
+ static_cast<uint8_t>(strInput[i + 11]) << 24;
+ mZipItem.usize = static_cast<uint8_t>(strInput[i + 12]) |
+ static_cast<uint8_t>(strInput[i + 13]) << 8 |
+ static_cast<uint8_t>(strInput[i + 14]) << 16 |
+ static_cast<uint8_t>(strInput[i + 15]) << 24;
+ }
+ }
+ if (!InitDecompress())
+ return iResult;
+ // we have a file - fill the buffer
+ char* temp;
+ ssize_t toRead=0;
+ if (isGZ)
+ {
+ m_ZStream.avail_in = static_cast<unsigned int>(strInput.size());
+ m_ZStream.next_in = const_cast<Bytef*>((const Bytef*)strInput.data());
+ temp = new char[8192];
+ toRead = 8191;
+ }
+ else
+ {
+ m_ZStream.avail_in = mZipItem.csize;
+ m_ZStream.next_in = const_cast<Bytef*>((const Bytef*)strInput.data())+iPos+LHDR_SIZE+mZipItem.flength+mZipItem.elength;
+ // init m_zipitem
+ strDest.reserve(mZipItem.usize);
+ temp = new char[mZipItem.usize+1];
+ toRead = mZipItem.usize;
+ }
+ int iCurrResult;
+ while((iCurrResult = static_cast<int>(Read(temp, toRead))) > 0)
+ {
+ strDest.append(temp,temp+iCurrResult);
+ iResult += iCurrResult;
+ }
+ Close();
+ delete[] temp;
+ iPos += LHDR_SIZE+mZipItem.flength+mZipItem.elength+mZipItem.csize;
+ if (isGZ)
+ break;
+ }
+
+ return iResult;
+}
+
+bool CZipFile::DecompressGzip(const std::string& in, std::string& out)
+{
+ const int windowBits = MAX_WBITS + 16;
+
+ z_stream strm;
+ strm.zalloc = Z_NULL;
+ strm.zfree = Z_NULL;
+ strm.opaque = Z_NULL;
+
+ int err = inflateInit2(&strm, windowBits);
+ if (err != Z_OK)
+ {
+ CLog::Log(LOGERROR, "FileZip: zlib error {}", err);
+ return false;
+ }
+
+ const int bufferSize = 16384;
+ unsigned char buffer[bufferSize];
+
+ strm.avail_in = static_cast<unsigned int>(in.size());
+ strm.next_in = reinterpret_cast<unsigned char*>(const_cast<char*>(in.c_str()));
+
+ do
+ {
+ strm.avail_out = bufferSize;
+ strm.next_out = buffer;
+ int err = inflate(&strm, Z_NO_FLUSH);
+ switch (err)
+ {
+ case Z_NEED_DICT:
+ err = Z_DATA_ERROR;
+ [[fallthrough]];
+ case Z_DATA_ERROR:
+ case Z_MEM_ERROR:
+ case Z_STREAM_ERROR:
+ CLog::Log(LOGERROR, "FileZip: failed to decompress. zlib error {}", err);
+ inflateEnd(&strm);
+ return false;
+ }
+ int read = bufferSize - strm.avail_out;
+ out.append((char*)buffer, read);
+ }
+ while (strm.avail_out == 0);
+
+ inflateEnd(&strm);
+ return true;
+}
diff --git a/xbmc/filesystem/ZipFile.h b/xbmc/filesystem/ZipFile.h
new file mode 100644
index 0000000..1b09e5c
--- /dev/null
+++ b/xbmc/filesystem/ZipFile.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "File.h"
+#include "IFile.h"
+#include "ZipManager.h"
+
+#include <zlib.h>
+
+namespace XFILE
+{
+ class CZipFile : public IFile
+ {
+ public:
+ CZipFile();
+ ~CZipFile() override;
+
+ int64_t GetPosition() override;
+ int64_t GetLength() override;
+ bool Open(const CURL& url) override;
+ bool Exists(const CURL& url) override;
+ int Stat(struct __stat64* buffer) override;
+ int Stat(const CURL& url, struct __stat64* buffer) override;
+ ssize_t Read(void* lpBuf, size_t uiBufSize) override;
+ //virtual bool ReadString(char *szLine, int iLineLength);
+ int64_t Seek(int64_t iFilePosition, int iWhence = SEEK_SET) override;
+ void Close() override;
+
+ //NOTE: gzip doesn't work. use DecompressGzip() instead
+ int UnpackFromMemory(std::string& strDest, const std::string& strInput, bool isGZ=false);
+
+ /*! Decompress gzip encoded buffer in-memory */
+ static bool DecompressGzip(const std::string& in, std::string& out);
+
+ private:
+ bool InitDecompress();
+ bool FillBuffer();
+ void DestroyBuffer(void* lpBuffer, int iBufSize);
+ CFile mFile;
+ SZipEntry mZipItem;
+ int64_t m_iFilePos = 0; // position in _uncompressed_ data read
+ int64_t m_iZipFilePos = 0; // position in _compressed_ data
+ int m_iAvailBuffer = 0;
+ z_stream m_ZStream;
+ char m_szBuffer[65535]; // 64k buffer for compressed data
+ char* m_szStringBuffer;
+ char* m_szStartOfStringBuffer; // never allocated!
+ size_t m_iDataInStringBuffer;
+ int m_iRead;
+ bool m_bFlush = false;
+ bool m_bCached;
+ };
+}
+
diff --git a/xbmc/filesystem/ZipManager.cpp b/xbmc/filesystem/ZipManager.cpp
new file mode 100644
index 0000000..710cfbc
--- /dev/null
+++ b/xbmc/filesystem/ZipManager.cpp
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "ZipManager.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "File.h"
+#include "URL.h"
+#if defined(TARGET_POSIX)
+#include "PlatformDefs.h"
+#endif
+#include "utils/CharsetConverter.h"
+#include "utils/EndianSwap.h"
+#include "utils/log.h"
+#include "utils/RegExp.h"
+#include "utils/URIUtils.h"
+
+using namespace XFILE;
+
+static const size_t ZC_FLAG_EFS = 1 << 11; // general purpose bit 11 - zip holds utf-8 filenames
+
+CZipManager::CZipManager() = default;
+
+CZipManager::~CZipManager() = default;
+
+bool CZipManager::GetZipList(const CURL& url, std::vector<SZipEntry>& items)
+{
+ struct __stat64 m_StatData = {};
+
+ std::string strFile = url.GetHostName();
+
+ if (CFile::Stat(strFile,&m_StatData))
+ {
+ CLog::Log(LOGDEBUG, "CZipManager::GetZipList: failed to stat file {}", url.GetRedacted());
+ return false;
+ }
+
+ std::map<std::string, std::vector<SZipEntry> >::iterator it = mZipMap.find(strFile);
+ if (it != mZipMap.end()) // already listed, just return it if not changed, else release and reread
+ {
+ std::map<std::string,int64_t>::iterator it2=mZipDate.find(strFile);
+
+ if (m_StatData.st_mtime == it2->second)
+ {
+ items = it->second;
+ return true;
+ }
+ mZipMap.erase(it);
+ mZipDate.erase(it2);
+ }
+
+ CFile mFile;
+ if (!mFile.Open(strFile))
+ {
+ CLog::Log(LOGDEBUG, "ZipManager: unable to open file {}!", strFile);
+ return false;
+ }
+
+ unsigned int hdr;
+ if (mFile.Read(&hdr, 4)!=4 || (Endian_SwapLE32(hdr) != ZIP_LOCAL_HEADER &&
+ Endian_SwapLE32(hdr) != ZIP_DATA_RECORD_HEADER &&
+ Endian_SwapLE32(hdr) != ZIP_SPLIT_ARCHIVE_HEADER))
+ {
+ CLog::Log(LOGDEBUG,"ZipManager: not a zip file!");
+ mFile.Close();
+ return false;
+ }
+
+ if (Endian_SwapLE32(hdr) == ZIP_SPLIT_ARCHIVE_HEADER)
+ CLog::LogF(LOGWARNING, "ZIP split archive header found. Trying to process as a single archive..");
+
+ // push date for update detection
+ mZipDate.insert(make_pair(strFile,m_StatData.st_mtime));
+
+
+ // Look for end of central directory record
+ // Zipfile comment may be up to 65535 bytes
+ // End of central directory record is 22 bytes (ECDREC_SIZE)
+ // -> need to check the last 65557 bytes
+ int64_t fileSize = mFile.GetLength();
+ // Don't need to look in the last 18 bytes (ECDREC_SIZE-4)
+ // But as we need to do overlapping between blocks (3 bytes),
+ // we start the search at ECDREC_SIZE-1 from the end of file
+ if (fileSize < ECDREC_SIZE - 1)
+ {
+ CLog::Log(LOGERROR, "ZipManager: Invalid zip file length: {}", fileSize);
+ return false;
+ }
+ int searchSize = (int) std::min(static_cast<int64_t>(65557), fileSize-ECDREC_SIZE+1);
+ int blockSize = (int) std::min(1024, searchSize);
+ int nbBlock = searchSize / blockSize;
+ int extraBlockSize = searchSize % blockSize;
+ // Signature is on 4 bytes
+ // It could be between 2 blocks, so we need to read 3 extra bytes
+ std::vector<char> buffer(blockSize + 3);
+ bool found = false;
+
+ // Loop through blocks starting at the end of the file (minus ECDREC_SIZE-1)
+ for (int nb=1; !found && (nb <= nbBlock); nb++)
+ {
+ mFile.Seek(fileSize-ECDREC_SIZE+1-(blockSize*nb),SEEK_SET);
+ if (mFile.Read(buffer.data(), blockSize + 3) != blockSize + 3)
+ return false;
+ for (int i=blockSize-1; !found && (i >= 0); i--)
+ {
+ if (Endian_SwapLE32(ReadUnaligned<uint32_t>(buffer.data() + i)) == ZIP_END_CENTRAL_HEADER)
+ {
+ // Set current position to start of end of central directory
+ mFile.Seek(fileSize-ECDREC_SIZE+1-(blockSize*nb)+i,SEEK_SET);
+ found = true;
+ }
+ }
+ }
+
+ // If not found, look in the last block left...
+ if ( !found && (extraBlockSize > 0) )
+ {
+ mFile.Seek(fileSize-ECDREC_SIZE+1-searchSize,SEEK_SET);
+ if (mFile.Read(buffer.data(), extraBlockSize + 3) != extraBlockSize + 3)
+ return false;
+ for (int i=extraBlockSize-1; !found && (i >= 0); i--)
+ {
+ if (Endian_SwapLE32(ReadUnaligned<uint32_t>(buffer.data() + i)) == ZIP_END_CENTRAL_HEADER)
+ {
+ // Set current position to start of end of central directory
+ mFile.Seek(fileSize-ECDREC_SIZE+1-searchSize+i,SEEK_SET);
+ found = true;
+ }
+ }
+ }
+
+ buffer.clear();
+
+ if ( !found )
+ {
+ CLog::Log(LOGDEBUG, "ZipManager: broken file {}!", strFile);
+ mFile.Close();
+ return false;
+ }
+
+ unsigned int cdirOffset, cdirSize;
+ // Get size of the central directory
+ mFile.Seek(12,SEEK_CUR);
+ if (mFile.Read(&cdirSize, 4) != 4)
+ return false;
+ cdirSize = Endian_SwapLE32(cdirSize);
+ // Get Offset of start of central directory with respect to the starting disk number
+ if (mFile.Read(&cdirOffset, 4) != 4)
+ return false;
+ cdirOffset = Endian_SwapLE32(cdirOffset);
+
+ // Go to the start of central directory
+ mFile.Seek(cdirOffset,SEEK_SET);
+
+ CRegExp pathTraversal;
+ pathTraversal.RegComp(PATH_TRAVERSAL);
+
+ char temp[CHDR_SIZE];
+ while (mFile.GetPosition() < cdirOffset + cdirSize)
+ {
+ SZipEntry ze;
+ if (mFile.Read(temp, CHDR_SIZE) != CHDR_SIZE)
+ return false;
+ readCHeader(temp, ze);
+ if (ze.header != ZIP_CENTRAL_HEADER)
+ {
+ CLog::Log(LOGDEBUG, "ZipManager: broken file {}!", strFile);
+ mFile.Close();
+ return false;
+ }
+
+ // Get the filename just after the central file header
+ std::vector<char> bufName(ze.flength);
+ if (mFile.Read(bufName.data(), ze.flength) != ze.flength)
+ return false;
+ std::string strName(bufName.data(), bufName.size());
+ bufName.clear();
+ if ((ze.flags & ZC_FLAG_EFS) == 0)
+ {
+ std::string tmp(strName);
+ g_charsetConverter.ToUtf8("CP437", tmp, strName);
+ }
+ memset(ze.name, 0, 255);
+ strncpy(ze.name, strName.c_str(), strName.size() > 254 ? 254 : strName.size());
+
+ // Jump after central file header extra field and file comment
+ mFile.Seek(ze.eclength + ze.clength,SEEK_CUR);
+
+ if (pathTraversal.RegFind(strName) < 0)
+ items.push_back(ze);
+ }
+
+ /* go through list and figure out file header lengths */
+ for (auto& ze : items)
+ {
+ // Go to the local file header to get the extra field length
+ // !! local header extra field length != central file header extra field length !!
+ mFile.Seek(ze.lhdrOffset+28,SEEK_SET);
+ if (mFile.Read(&(ze.elength), 2) != 2)
+ return false;
+ ze.elength = Endian_SwapLE16(ze.elength);
+
+ // Compressed data offset = local header offset + size of local header + filename length + local file header extra field length
+ ze.offset = ze.lhdrOffset + LHDR_SIZE + ze.flength + ze.elength;
+
+ }
+
+ mZipMap.insert(make_pair(strFile,items));
+ mFile.Close();
+ return true;
+}
+
+bool CZipManager::GetZipEntry(const CURL& url, SZipEntry& item)
+{
+ const std::string& strFile = url.GetHostName();
+
+ std::map<std::string, std::vector<SZipEntry> >::iterator it = mZipMap.find(strFile);
+ std::vector<SZipEntry> items;
+ if (it == mZipMap.end()) // we need to list the zip
+ {
+ GetZipList(url,items);
+ }
+ else
+ {
+ items = it->second;
+ }
+
+ const std::string& strFileName = url.GetFileName();
+ for (const auto& it2 : items)
+ {
+ if (std::string(it2.name) == strFileName)
+ {
+ item = it2;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool CZipManager::ExtractArchive(const std::string& strArchive, const std::string& strPath)
+{
+ const CURL pathToUrl(strArchive);
+ return ExtractArchive(pathToUrl, strPath);
+}
+
+bool CZipManager::ExtractArchive(const CURL& archive, const std::string& strPath)
+{
+ std::vector<SZipEntry> entry;
+ CURL url = URIUtils::CreateArchivePath("zip", archive);
+ GetZipList(url, entry);
+ for (const auto& it : entry)
+ {
+ if (it.name[strlen(it.name) - 1] == '/') // skip dirs
+ continue;
+ std::string strFilePath(it.name);
+
+ CURL zipPath = URIUtils::CreateArchivePath("zip", archive, strFilePath);
+ const CURL pathToUrl(strPath + strFilePath);
+ if (!CFile::Copy(zipPath, pathToUrl))
+ return false;
+ }
+ return true;
+}
+
+// Read local file header
+void CZipManager::readHeader(const char* buffer, SZipEntry& info)
+{
+ info.header = Endian_SwapLE32(ReadUnaligned<uint32_t>(buffer));
+ info.version = Endian_SwapLE16(ReadUnaligned<uint16_t>(buffer + 4));
+ info.flags = Endian_SwapLE16(ReadUnaligned<uint16_t>(buffer + 6));
+ info.method = Endian_SwapLE16(ReadUnaligned<uint16_t>(buffer + 8));
+ info.mod_time = Endian_SwapLE16(ReadUnaligned<uint16_t>(buffer + 10));
+ info.mod_date = Endian_SwapLE16(ReadUnaligned<uint16_t>(buffer + 12));
+ info.crc32 = Endian_SwapLE32(ReadUnaligned<uint32_t>(buffer + 14));
+ info.csize = Endian_SwapLE32(ReadUnaligned<uint32_t>(buffer + 18));
+ info.usize = Endian_SwapLE32(ReadUnaligned<uint32_t>(buffer + 22));
+ info.flength = Endian_SwapLE16(ReadUnaligned<uint16_t>(buffer + 26));
+ info.elength = Endian_SwapLE16(ReadUnaligned<uint16_t>(buffer + 28));
+}
+
+// Read central file header (from central directory)
+void CZipManager::readCHeader(const char* buffer, SZipEntry& info)
+{
+ info.header = Endian_SwapLE32(ReadUnaligned<uint32_t>(buffer));
+ // Skip version made by
+ info.version = Endian_SwapLE16(ReadUnaligned<uint16_t>(buffer + 6));
+ info.flags = Endian_SwapLE16(ReadUnaligned<uint16_t>(buffer + 8));
+ info.method = Endian_SwapLE16(ReadUnaligned<uint16_t>(buffer + 10));
+ info.mod_time = Endian_SwapLE16(ReadUnaligned<uint16_t>(buffer + 12));
+ info.mod_date = Endian_SwapLE16(ReadUnaligned<uint16_t>(buffer + 14));
+ info.crc32 = Endian_SwapLE32(ReadUnaligned<uint32_t>(buffer + 16));
+ info.csize = Endian_SwapLE32(ReadUnaligned<uint32_t>(buffer + 20));
+ info.usize = Endian_SwapLE32(ReadUnaligned<uint32_t>(buffer + 24));
+ info.flength = Endian_SwapLE16(ReadUnaligned<uint16_t>(buffer + 28));
+ info.eclength = Endian_SwapLE16(ReadUnaligned<uint16_t>(buffer + 30));
+ info.clength = Endian_SwapLE16(ReadUnaligned<uint16_t>(buffer + 32));
+ // Skip disk number start, internal/external file attributes
+ info.lhdrOffset = Endian_SwapLE32(ReadUnaligned<uint32_t>(buffer + 42));
+}
+
+void CZipManager::release(const std::string& strPath)
+{
+ CURL url(strPath);
+ std::map<std::string, std::vector<SZipEntry> >::iterator it= mZipMap.find(url.GetHostName());
+ if (it != mZipMap.end())
+ {
+ std::map<std::string,int64_t>::iterator it2=mZipDate.find(url.GetHostName());
+ mZipMap.erase(it);
+ mZipDate.erase(it2);
+ }
+}
+
+
diff --git a/xbmc/filesystem/ZipManager.h b/xbmc/filesystem/ZipManager.h
new file mode 100644
index 0000000..3fba27f
--- /dev/null
+++ b/xbmc/filesystem/ZipManager.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+// See http://www.pkware.com/documents/casestudies/APPNOTE.TXT
+#define ZIP_LOCAL_HEADER 0x04034b50
+#define ZIP_DATA_RECORD_HEADER 0x08074b50
+#define ZIP_CENTRAL_HEADER 0x02014b50
+#define ZIP_END_CENTRAL_HEADER 0x06054b50
+#define ZIP_SPLIT_ARCHIVE_HEADER 0x30304b50
+#define LHDR_SIZE 30
+#define DREC_SIZE 16
+#define CHDR_SIZE 46
+#define ECDREC_SIZE 22
+
+#include <cstring>
+#include <map>
+#include <string>
+#include <vector>
+
+class CURL;
+
+static const std::string PATH_TRAVERSAL(R"_((^|\/|\\)\.{2}($|\/|\\))_");
+
+struct SZipEntry {
+ unsigned int header = 0;
+ unsigned short version = 0;
+ unsigned short flags = 0;
+ unsigned short method = 0;
+ unsigned short mod_time = 0;
+ unsigned short mod_date = 0;
+ unsigned int crc32 = 0;
+ unsigned int csize = 0; // compressed size
+ unsigned int usize = 0; // uncompressed size
+ unsigned short flength = 0; // filename length
+ unsigned short elength = 0; // extra field length (local file header)
+ unsigned short eclength = 0; // extra field length (central file header)
+ unsigned short clength = 0; // file comment length (central file header)
+ unsigned int lhdrOffset = 0; // Relative offset of local header
+ int64_t offset = 0; // offset in file to compressed data
+ char name[255];
+
+ SZipEntry()
+ {
+ name[0] = '\0';
+ }
+};
+
+class CZipManager
+{
+public:
+ CZipManager();
+ ~CZipManager();
+
+ bool GetZipList(const CURL& url, std::vector<SZipEntry>& items);
+ bool GetZipEntry(const CURL& url, SZipEntry& item);
+ bool ExtractArchive(const std::string& strArchive, const std::string& strPath);
+ bool ExtractArchive(const CURL& archive, const std::string& strPath);
+ void release(const std::string& strPath); // release resources used by list zip
+ static void readHeader(const char* buffer, SZipEntry& info);
+ static void readCHeader(const char* buffer, SZipEntry& info);
+private:
+ std::map<std::string,std::vector<SZipEntry> > mZipMap;
+ std::map<std::string,int64_t> mZipDate;
+
+ template<typename T>
+ static T ReadUnaligned(const void* mem)
+ {
+ T var;
+ std::memcpy(&var, mem, sizeof(T));
+ return var;
+ }
+};
+
+extern CZipManager g_ZipManager;
+
diff --git a/xbmc/filesystem/test/CMakeLists.txt b/xbmc/filesystem/test/CMakeLists.txt
new file mode 100644
index 0000000..9572459
--- /dev/null
+++ b/xbmc/filesystem/test/CMakeLists.txt
@@ -0,0 +1,15 @@
+set(SOURCES TestDirectory.cpp
+ TestFile.cpp
+ TestFileFactory.cpp
+ TestZipFile.cpp
+ TestZipManager.cpp)
+
+if(MICROHTTPD_FOUND)
+ list(APPEND SOURCES TestHTTPDirectory.cpp)
+endif()
+
+if(NFS_FOUND)
+ list(APPEND SOURCES TestNfsFile.cpp)
+endif()
+
+core_add_test_library(filesystem_test)
diff --git a/xbmc/filesystem/test/TestDirectory.cpp b/xbmc/filesystem/test/TestDirectory.cpp
new file mode 100644
index 0000000..e99408c
--- /dev/null
+++ b/xbmc/filesystem/test/TestDirectory.cpp
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "FileItem.h"
+#include "filesystem/Directory.h"
+#include "filesystem/IDirectory.h"
+#include "filesystem/SpecialProtocol.h"
+#include "test/TestUtils.h"
+#include "utils/URIUtils.h"
+
+#include <gtest/gtest.h>
+
+TEST(TestDirectory, General)
+{
+ std::string tmppath1, tmppath2, tmppath3;
+ CFileItemList items;
+ CFileItemPtr itemptr;
+ tmppath1 = CSpecialProtocol::TranslatePath("special://temp/");
+ tmppath1 = URIUtils::AddFileToFolder(tmppath1, "TestDirectory");
+ tmppath2 = tmppath1;
+ tmppath2 = URIUtils::AddFileToFolder(tmppath2, "subdir");
+ EXPECT_TRUE(XFILE::CDirectory::Create(tmppath1));
+ EXPECT_TRUE(XFILE::CDirectory::Exists(tmppath1));
+ EXPECT_FALSE(XFILE::CDirectory::Exists(tmppath2));
+ EXPECT_TRUE(XFILE::CDirectory::Create(tmppath2));
+ EXPECT_TRUE(XFILE::CDirectory::Exists(tmppath2));
+ EXPECT_TRUE(XFILE::CDirectory::GetDirectory(tmppath1, items, "", XFILE::DIR_FLAG_DEFAULTS));
+ XFILE::CDirectory::FilterFileDirectories(items, "");
+ tmppath3 = tmppath2;
+ URIUtils::AddSlashAtEnd(tmppath3);
+ itemptr = items[0];
+ EXPECT_STREQ(tmppath3.c_str(), itemptr->GetPath().c_str());
+ EXPECT_TRUE(XFILE::CDirectory::Remove(tmppath2));
+ EXPECT_FALSE(XFILE::CDirectory::Exists(tmppath2));
+ EXPECT_TRUE(XFILE::CDirectory::Exists(tmppath1));
+ EXPECT_TRUE(XFILE::CDirectory::Remove(tmppath1));
+ EXPECT_FALSE(XFILE::CDirectory::Exists(tmppath1));
+}
+
+TEST(TestDirectory, CreateRecursive)
+{
+ auto path1 = URIUtils::AddFileToFolder(
+ CSpecialProtocol::TranslatePath("special://temp/"),
+ "level1");
+ auto path2 = URIUtils::AddFileToFolder(path1,
+ "level2",
+ "level3");
+
+ EXPECT_TRUE(XFILE::CDirectory::Create(path2));
+ EXPECT_TRUE(XFILE::CDirectory::RemoveRecursive(path1));
+}
diff --git a/xbmc/filesystem/test/TestFile.cpp b/xbmc/filesystem/test/TestFile.cpp
new file mode 100644
index 0000000..abefe46
--- /dev/null
+++ b/xbmc/filesystem/test/TestFile.cpp
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "filesystem/File.h"
+#include "test/TestUtils.h"
+
+#include <errno.h>
+#include <string>
+
+#include <gtest/gtest.h>
+
+TEST(TestFile, Read)
+{
+ const std::string newLine = CXBMCTestUtils::Instance().getNewLineCharacters();
+ const size_t size = 1616;
+ const size_t lines = 25;
+ size_t addPerLine = newLine.length() - 1;
+ size_t realSize = size + lines * addPerLine;
+
+ const std::string firstBuf = "About" + newLine + "-----" + newLine + "XBMC is ";
+ const std::string secondBuf = "an award-winning fre";
+ const std::string thirdBuf = "ent hub for digital ";
+ const std::string fourthBuf = "rs, XBMC is a non-pr";
+ const std::string fifthBuf = "multimedia jukebox." + newLine;
+
+ XFILE::CFile file;
+ char buf[23] = {};
+
+ size_t currentPos;
+ ASSERT_TRUE(file.Open(
+ XBMC_REF_FILE_PATH("/xbmc/filesystem/test/reffile.txt")));
+ EXPECT_EQ(0, file.GetPosition());
+ EXPECT_EQ(realSize, file.GetLength());
+ EXPECT_EQ(firstBuf.length(), static_cast<size_t>(file.Read(buf, firstBuf.length())));
+ file.Flush();
+ currentPos = firstBuf.length();
+ EXPECT_EQ(currentPos, file.GetPosition());
+ EXPECT_EQ(0, memcmp(firstBuf.c_str(), buf, firstBuf.length()));
+ EXPECT_EQ(secondBuf.length(), static_cast<size_t>(file.Read(buf, secondBuf.length())));
+ currentPos += secondBuf.length();
+ EXPECT_EQ(currentPos, file.GetPosition());
+ EXPECT_EQ(0, memcmp(secondBuf.c_str(), buf, secondBuf.length()));
+ currentPos = 100 + addPerLine * 3;
+ EXPECT_EQ(currentPos, file.Seek(currentPos));
+ EXPECT_EQ(currentPos, file.GetPosition());
+ EXPECT_EQ(thirdBuf.length(), static_cast<size_t>(file.Read(buf, thirdBuf.length())));
+ file.Flush();
+ currentPos += thirdBuf.length();
+ EXPECT_EQ(currentPos, file.GetPosition());
+ EXPECT_EQ(0, memcmp(thirdBuf.c_str(), buf, thirdBuf.length()));
+ currentPos += 100 + addPerLine * 1;
+ EXPECT_EQ(currentPos, file.Seek(100 + addPerLine * 1, SEEK_CUR));
+ EXPECT_EQ(currentPos, file.GetPosition());
+ EXPECT_EQ(fourthBuf.length(), static_cast<size_t>(file.Read(buf, fourthBuf.length())));
+ file.Flush();
+ currentPos += fourthBuf.length();
+ EXPECT_EQ(currentPos, file.GetPosition());
+ EXPECT_EQ(0, memcmp(fourthBuf.c_str(), buf, fourthBuf.length()));
+ currentPos = realSize - fifthBuf.length();
+ EXPECT_EQ(currentPos, file.Seek(-(int64_t)fifthBuf.length(), SEEK_END));
+ EXPECT_EQ(currentPos, file.GetPosition());
+ EXPECT_EQ(fifthBuf.length(), static_cast<size_t>(file.Read(buf, fifthBuf.length())));
+ file.Flush();
+ currentPos += fifthBuf.length();
+ EXPECT_EQ(currentPos, file.GetPosition());
+ EXPECT_EQ(0, memcmp(fifthBuf.c_str(), buf, fifthBuf.length()));
+ currentPos += 100;
+ EXPECT_EQ(currentPos, file.Seek(100, SEEK_CUR));
+ EXPECT_EQ(currentPos, file.GetPosition());
+ currentPos = 0;
+ EXPECT_EQ(currentPos, file.Seek(currentPos, SEEK_SET));
+ EXPECT_EQ(firstBuf.length(), static_cast<size_t>(file.Read(buf, firstBuf.length())));
+ file.Flush();
+ currentPos += firstBuf.length();
+ EXPECT_EQ(currentPos, file.GetPosition());
+ EXPECT_EQ(0, memcmp(firstBuf.c_str(), buf, firstBuf.length()));
+ EXPECT_EQ(0, file.Seek(0, SEEK_SET));
+ EXPECT_EQ(-1, file.Seek(-100, SEEK_SET));
+ file.Close();
+}
+
+TEST(TestFile, Write)
+{
+ XFILE::CFile *file;
+ const char str[] = "TestFile.Write test string\n";
+ char buf[30] = {};
+
+ ASSERT_NE(nullptr, file = XBMC_CREATETEMPFILE(""));
+ file->Close();
+ ASSERT_TRUE(file->OpenForWrite(XBMC_TEMPFILEPATH(file), true));
+ EXPECT_EQ((int)sizeof(str), file->Write(str, sizeof(str)));
+ file->Flush();
+ EXPECT_EQ((int64_t)sizeof(str), file->GetPosition());
+ file->Close();
+ ASSERT_TRUE(file->Open(XBMC_TEMPFILEPATH(file)));
+ EXPECT_EQ(0, file->GetPosition());
+ EXPECT_EQ((int64_t)sizeof(str), file->Seek(0, SEEK_END));
+ EXPECT_EQ(0, file->Seek(0, SEEK_SET));
+ EXPECT_EQ((int64_t)sizeof(str), file->GetLength());
+ EXPECT_EQ(sizeof(str), static_cast<size_t>(file->Read(buf, sizeof(buf))));
+ file->Flush();
+ EXPECT_EQ((int64_t)sizeof(str), file->GetPosition());
+ EXPECT_EQ(0, memcmp(str, buf, sizeof(str)));
+ file->Close();
+ EXPECT_TRUE(XBMC_DELETETEMPFILE(file));
+}
+
+TEST(TestFile, Exists)
+{
+ XFILE::CFile *file;
+
+ ASSERT_NE(nullptr, file = XBMC_CREATETEMPFILE(""));
+ file->Close();
+ EXPECT_TRUE(XFILE::CFile::Exists(XBMC_TEMPFILEPATH(file)));
+ EXPECT_FALSE(XFILE::CFile::Exists(""));
+ EXPECT_TRUE(XBMC_DELETETEMPFILE(file));
+}
+
+TEST(TestFile, Stat)
+{
+ XFILE::CFile *file;
+ struct __stat64 buffer;
+
+ ASSERT_NE(nullptr, file = XBMC_CREATETEMPFILE(""));
+ EXPECT_EQ(0, file->Stat(&buffer));
+ file->Close();
+ EXPECT_NE(0U, buffer.st_mode | _S_IFREG);
+ EXPECT_EQ(-1, XFILE::CFile::Stat("", &buffer));
+ EXPECT_EQ(ENOENT, errno);
+ EXPECT_TRUE(XBMC_DELETETEMPFILE(file));
+}
+
+TEST(TestFile, Delete)
+{
+ XFILE::CFile *file;
+ std::string path;
+
+ ASSERT_NE(nullptr, file = XBMC_CREATETEMPFILE(""));
+ file->Close();
+ path = XBMC_TEMPFILEPATH(file);
+ EXPECT_TRUE(XFILE::CFile::Exists(path));
+ EXPECT_TRUE(XFILE::CFile::Delete(path));
+ EXPECT_FALSE(XFILE::CFile::Exists(path));
+ EXPECT_FALSE(XBMC_DELETETEMPFILE(file));
+}
+
+TEST(TestFile, Rename)
+{
+ XFILE::CFile *file1, *file2;
+ std::string path1, path2;
+
+ ASSERT_NE(nullptr, file1 = XBMC_CREATETEMPFILE(""));
+ file1->Close();
+ path1 = XBMC_TEMPFILEPATH(file1);
+ ASSERT_NE(nullptr, file2 = XBMC_CREATETEMPFILE(""));
+ file2->Close();
+ path2 = XBMC_TEMPFILEPATH(file2);
+ EXPECT_TRUE(XFILE::CFile::Delete(path1));
+ EXPECT_FALSE(XFILE::CFile::Exists(path1));
+ EXPECT_TRUE(XFILE::CFile::Exists(path2));
+ EXPECT_TRUE(XFILE::CFile::Rename(path2, path1));
+ EXPECT_TRUE(XFILE::CFile::Exists(path1));
+ EXPECT_FALSE(XFILE::CFile::Exists(path2));
+ EXPECT_TRUE(XFILE::CFile::Delete(path1));
+ EXPECT_FALSE(XBMC_DELETETEMPFILE(file1));
+ EXPECT_FALSE(XBMC_DELETETEMPFILE(file2));
+}
+
+TEST(TestFile, Copy)
+{
+ XFILE::CFile *file1, *file2;
+ std::string path1, path2;
+
+ ASSERT_NE(nullptr, file1 = XBMC_CREATETEMPFILE(""));
+ file1->Close();
+ path1 = XBMC_TEMPFILEPATH(file1);
+ ASSERT_NE(nullptr, file2 = XBMC_CREATETEMPFILE(""));
+ file2->Close();
+ path2 = XBMC_TEMPFILEPATH(file2);
+ EXPECT_TRUE(XFILE::CFile::Delete(path1));
+ EXPECT_FALSE(XFILE::CFile::Exists(path1));
+ EXPECT_TRUE(XFILE::CFile::Exists(path2));
+ EXPECT_TRUE(XFILE::CFile::Copy(path2, path1));
+ EXPECT_TRUE(XFILE::CFile::Exists(path1));
+ EXPECT_TRUE(XFILE::CFile::Exists(path2));
+ EXPECT_TRUE(XFILE::CFile::Delete(path1));
+ EXPECT_TRUE(XFILE::CFile::Delete(path2));
+ EXPECT_FALSE(XBMC_DELETETEMPFILE(file1));
+ EXPECT_FALSE(XBMC_DELETETEMPFILE(file2));
+}
+
+TEST(TestFile, SetHidden)
+{
+ XFILE::CFile *file;
+
+ ASSERT_NE(nullptr, file = XBMC_CREATETEMPFILE(""));
+ file->Close();
+ EXPECT_TRUE(XFILE::CFile::Exists(XBMC_TEMPFILEPATH(file)));
+ bool result = XFILE::CFile::SetHidden(XBMC_TEMPFILEPATH(file), true);
+#ifdef TARGET_WINDOWS
+ EXPECT_TRUE(result);
+#else
+ EXPECT_FALSE(result);
+#endif
+ EXPECT_TRUE(XFILE::CFile::Exists(XBMC_TEMPFILEPATH(file)));
+ EXPECT_TRUE(XBMC_DELETETEMPFILE(file));
+}
diff --git a/xbmc/filesystem/test/TestFileFactory.cpp b/xbmc/filesystem/test/TestFileFactory.cpp
new file mode 100644
index 0000000..6129592
--- /dev/null
+++ b/xbmc/filesystem/test/TestFileFactory.cpp
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "ServiceBroker.h"
+#include "filesystem/File.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "test/TestUtils.h"
+#include "utils/StringUtils.h"
+
+#include <gtest/gtest.h>
+
+class TestFileFactory : public testing::Test
+{
+protected:
+ TestFileFactory()
+ {
+ std::vector<std::string> advancedsettings =
+ CXBMCTestUtils::Instance().getAdvancedSettingsFiles();
+ std::vector<std::string> guisettings =
+ CXBMCTestUtils::Instance().getGUISettingsFiles();
+
+ const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+ for (const auto& it : guisettings)
+ settings->Load(it);
+
+ const std::shared_ptr<CAdvancedSettings> advancedSettings = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings();
+ for (const auto& it : advancedsettings)
+ advancedSettings->ParseSettingsFile(it);
+
+ settings->SetLoaded();
+ }
+
+ ~TestFileFactory() override
+ {
+ CServiceBroker::GetSettingsComponent()->GetSettings()->Unload();
+ }
+};
+
+/* The tests for XFILE::CFileFactory are tested indirectly through
+ * XFILE::CFile. Since most parts of the VFS require some form of
+ * network connection, the settings and VFS URLs must be given as
+ * arguments in the main testsuite program.
+ */
+TEST_F(TestFileFactory, Read)
+{
+ XFILE::CFile file;
+ std::string str;
+ ssize_t size, i;
+ unsigned char buf[16];
+ int64_t count = 0;
+
+ std::vector<std::string> urls =
+ CXBMCTestUtils::Instance().getTestFileFactoryReadUrls();
+
+ for (const auto& url : urls)
+ {
+ std::cout << "Testing URL: " << url << std::endl;
+ ASSERT_TRUE(file.Open(url));
+ std::cout << "file.GetLength(): " <<
+ testing::PrintToString(file.GetLength()) << std::endl;
+ std::cout << "file.Seek(file.GetLength() / 2, SEEK_CUR) return value: " <<
+ testing::PrintToString(file.Seek(file.GetLength() / 2, SEEK_CUR)) << std::endl;
+ std::cout << "file.Seek(0, SEEK_END) return value: " <<
+ testing::PrintToString(file.Seek(0, SEEK_END)) << std::endl;
+ std::cout << "file.Seek(0, SEEK_SET) return value: " <<
+ testing::PrintToString(file.Seek(0, SEEK_SET)) << std::endl;
+ std::cout << "File contents:" << std::endl;
+ while ((size = file.Read(buf, sizeof(buf))) > 0)
+ {
+ str = StringUtils::Format(" {:08X}", count);
+ std::cout << str << " ";
+ count += size;
+ for (i = 0; i < size; i++)
+ {
+ str = StringUtils::Format("{:02X} ", buf[i]);
+ std::cout << str;
+ }
+ while (i++ < static_cast<ssize_t> (sizeof(buf)))
+ std::cout << " ";
+ std::cout << " [";
+ for (i = 0; i < size; i++)
+ {
+ if (buf[i] >= ' ' && buf[i] <= '~')
+ std::cout << buf[i];
+ else
+ std::cout << ".";
+ }
+ std::cout << "]" << std::endl;
+ }
+ file.Close();
+ }
+}
+
+TEST_F(TestFileFactory, Write)
+{
+ XFILE::CFile file, inputfile;
+ std::string str;
+ size_t size, i;
+ unsigned char buf[16];
+ int64_t count = 0;
+
+ str = CXBMCTestUtils::Instance().getTestFileFactoryWriteInputFile();
+ ASSERT_TRUE(inputfile.Open(str));
+
+ std::vector<std::string> urls =
+ CXBMCTestUtils::Instance().getTestFileFactoryWriteUrls();
+
+ for (const auto& url : urls)
+ {
+ std::cout << "Testing URL: " << url << std::endl;
+ std::cout << "Writing...";
+ ASSERT_TRUE(file.OpenForWrite(url, true));
+ while ((size = inputfile.Read(buf, sizeof(buf))) > 0)
+ {
+ EXPECT_GE(file.Write(buf, size), 0);
+ }
+ file.Close();
+ std::cout << "done." << std::endl;
+ std::cout << "Reading..." << std::endl;
+ ASSERT_TRUE(file.Open(url));
+ EXPECT_EQ(inputfile.GetLength(), file.GetLength());
+ std::cout << "file.Seek(file.GetLength() / 2, SEEK_CUR) return value: " <<
+ testing::PrintToString(file.Seek(file.GetLength() / 2, SEEK_CUR)) << std::endl;
+ std::cout << "file.Seek(0, SEEK_END) return value: " <<
+ testing::PrintToString(file.Seek(0, SEEK_END)) << std::endl;
+ std::cout << "file.Seek(0, SEEK_SET) return value: " <<
+ testing::PrintToString(file.Seek(0, SEEK_SET)) << std::endl;
+ std::cout << "File contents:\n";
+ while ((size = file.Read(buf, sizeof(buf))) > 0)
+ {
+ str = StringUtils::Format(" {:08X}", count);
+ std::cout << str << " ";
+ count += size;
+ for (i = 0; i < size; i++)
+ {
+ str = StringUtils::Format("{:02X} ", buf[i]);
+ std::cout << str;
+ }
+ while (i++ < sizeof(buf))
+ std::cout << " ";
+ std::cout << " [";
+ for (i = 0; i < size; i++)
+ {
+ if (buf[i] >= ' ' && buf[i] <= '~')
+ std::cout << buf[i];
+ else
+ std::cout << ".";
+ }
+ std::cout << "]" << std::endl;
+ }
+ file.Close();
+ }
+ inputfile.Close();
+}
diff --git a/xbmc/filesystem/test/TestHTTPDirectory.cpp b/xbmc/filesystem/test/TestHTTPDirectory.cpp
new file mode 100644
index 0000000..7736307
--- /dev/null
+++ b/xbmc/filesystem/test/TestHTTPDirectory.cpp
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2015-2020 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "FileItem.h"
+#include "URL.h"
+#include "filesystem/CurlFile.h"
+#include "filesystem/HTTPDirectory.h"
+#include "network/WebServer.h"
+#include "network/httprequesthandler/HTTPVfsHandler.h"
+#include "settings/MediaSourceSettings.h"
+#include "test/TestUtils.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/XTimeUtils.h"
+
+#include <random>
+#include <stdlib.h>
+
+#include <gtest/gtest.h>
+
+using namespace XFILE;
+
+#define WEBSERVER_HOST "localhost"
+
+#define SOURCE_PATH "xbmc/filesystem/test/data/httpdirectory/"
+
+#define TEST_FILE_APACHE_DEFAULT "apache-default.html"
+#define TEST_FILE_APACHE_FANCY "apache-fancy.html"
+#define TEST_FILE_APACHE_HTML "apache-html.html"
+#define TEST_FILE_BASIC "basic.html"
+#define TEST_FILE_BASIC_MULTILINE "basic-multiline.html"
+#define TEST_FILE_LIGHTTP_DEFAULT "lighttp-default.html"
+#define TEST_FILE_NGINX_DEFAULT "nginx-default.html"
+#define TEST_FILE_NGINX_FANCYINDEX "nginx-fancyindex.html"
+
+#define SAMPLE_ITEM_COUNT 6
+
+#define SAMPLE_ITEM_1_LABEL "folder1"
+#define SAMPLE_ITEM_2_LABEL "folder2"
+#define SAMPLE_ITEM_3_LABEL "sample3: the sampling.mpg"
+#define SAMPLE_ITEM_4_LABEL "sample & samplability 4.mpg"
+#define SAMPLE_ITEM_5_LABEL "sample5.mpg"
+#define SAMPLE_ITEM_6_LABEL "sample6.mpg"
+
+#define SAMPLE_ITEM_1_SIZE 0
+#define SAMPLE_ITEM_2_SIZE 0
+#define SAMPLE_ITEM_3_SIZE 123
+#define SAMPLE_ITEM_4_SIZE 125952 // 123K
+#define SAMPLE_ITEM_5_SIZE 128974848 // 123M
+#define SAMPLE_ITEM_6_SIZE 132070244352 // 123G
+
+// HTTPDirectory ignores the seconds component of parsed date/times
+#define SAMPLE_ITEM_1_DATETIME "2019-01-01 01:01:00"
+#define SAMPLE_ITEM_2_DATETIME "2019-02-02 02:02:00"
+#define SAMPLE_ITEM_3_DATETIME "2019-03-03 03:03:00"
+#define SAMPLE_ITEM_4_DATETIME "2019-04-04 04:04:00"
+#define SAMPLE_ITEM_5_DATETIME "2019-05-05 05:05:00"
+#define SAMPLE_ITEM_6_DATETIME "2019-06-06 06:06:00"
+
+class TestHTTPDirectory : public testing::Test
+{
+protected:
+ TestHTTPDirectory() : m_sourcePath(XBMC_REF_FILE_PATH(SOURCE_PATH))
+ {
+ std::random_device rd;
+ std::mt19937 mt(rd());
+ std::uniform_int_distribution<uint16_t> dist(49152, 65535);
+ m_webServerPort = dist(mt);
+
+ m_baseUrl = StringUtils::Format("http://" WEBSERVER_HOST ":{}", m_webServerPort);
+ }
+
+ ~TestHTTPDirectory() override = default;
+
+protected:
+ void SetUp() override
+ {
+ SetupMediaSources();
+
+ m_webServer.Start(m_webServerPort, "", "");
+ m_webServer.RegisterRequestHandler(&m_vfsHandler);
+ }
+
+ void TearDown() override
+ {
+ if (m_webServer.IsStarted())
+ m_webServer.Stop();
+
+ m_webServer.UnregisterRequestHandler(&m_vfsHandler);
+
+ TearDownMediaSources();
+ }
+
+ void SetupMediaSources()
+ {
+ CMediaSource source;
+ source.strName = "WebServer Share";
+ source.strPath = m_sourcePath;
+ source.vecPaths.push_back(m_sourcePath);
+ source.m_allowSharing = true;
+ source.m_iDriveType = CMediaSource::SOURCE_TYPE_LOCAL;
+ source.m_iLockMode = LOCK_MODE_EVERYONE;
+ source.m_ignore = true;
+
+ CMediaSourceSettings::GetInstance().AddShare("videos", source);
+ }
+
+ void TearDownMediaSources() { CMediaSourceSettings::GetInstance().Clear(); }
+
+ std::string GetUrl(const std::string& path)
+ {
+ if (path.empty())
+ return m_baseUrl;
+
+ return URIUtils::AddFileToFolder(m_baseUrl, path);
+ }
+
+ std::string GetUrlOfTestFile(const std::string& testFile)
+ {
+ if (testFile.empty())
+ return "";
+
+ std::string path = URIUtils::AddFileToFolder(m_sourcePath, testFile);
+ path = CURL::Encode(path);
+ path = URIUtils::AddFileToFolder("vfs", path);
+
+ return GetUrl(path);
+ }
+
+ void CheckFileItemTypes(CFileItemList const& items)
+ {
+ ASSERT_EQ(items.GetObjectCount(), SAMPLE_ITEM_COUNT);
+
+ // folders
+ ASSERT_TRUE(items[0]->m_bIsFolder);
+ ASSERT_TRUE(items[1]->m_bIsFolder);
+
+ // files
+ ASSERT_FALSE(items[2]->m_bIsFolder);
+ ASSERT_FALSE(items[3]->m_bIsFolder);
+ ASSERT_FALSE(items[4]->m_bIsFolder);
+ ASSERT_FALSE(items[5]->m_bIsFolder);
+ }
+
+ void CheckFileItemLabels(CFileItemList const& items)
+ {
+ ASSERT_EQ(items.GetObjectCount(), SAMPLE_ITEM_COUNT);
+
+ ASSERT_STREQ(items[0]->GetLabel().c_str(), SAMPLE_ITEM_1_LABEL);
+ ASSERT_STREQ(items[1]->GetLabel().c_str(), SAMPLE_ITEM_2_LABEL);
+ ASSERT_STREQ(items[2]->GetLabel().c_str(), SAMPLE_ITEM_3_LABEL);
+ ASSERT_STREQ(items[3]->GetLabel().c_str(), SAMPLE_ITEM_4_LABEL);
+ ASSERT_STREQ(items[4]->GetLabel().c_str(), SAMPLE_ITEM_5_LABEL);
+ ASSERT_STREQ(items[5]->GetLabel().c_str(), SAMPLE_ITEM_6_LABEL);
+ }
+
+ void CheckFileItemDateTimes(CFileItemList const& items)
+ {
+ ASSERT_EQ(items.GetObjectCount(), SAMPLE_ITEM_COUNT);
+
+ ASSERT_STREQ(items[0]->m_dateTime.GetAsDBDateTime().c_str(), SAMPLE_ITEM_1_DATETIME);
+ ASSERT_STREQ(items[1]->m_dateTime.GetAsDBDateTime().c_str(), SAMPLE_ITEM_2_DATETIME);
+ ASSERT_STREQ(items[2]->m_dateTime.GetAsDBDateTime().c_str(), SAMPLE_ITEM_3_DATETIME);
+ ASSERT_STREQ(items[3]->m_dateTime.GetAsDBDateTime().c_str(), SAMPLE_ITEM_4_DATETIME);
+ ASSERT_STREQ(items[4]->m_dateTime.GetAsDBDateTime().c_str(), SAMPLE_ITEM_5_DATETIME);
+ ASSERT_STREQ(items[5]->m_dateTime.GetAsDBDateTime().c_str(), SAMPLE_ITEM_6_DATETIME);
+ }
+
+ void CheckFileItemSizes(CFileItemList const& items)
+ {
+ ASSERT_EQ(items.GetObjectCount(), SAMPLE_ITEM_COUNT);
+
+ // folders
+ ASSERT_EQ(items[0]->m_dwSize, SAMPLE_ITEM_1_SIZE);
+ ASSERT_EQ(items[1]->m_dwSize, SAMPLE_ITEM_2_SIZE);
+
+ // files - due to K/M/G conversions provided by some formats, allow for
+ // non-zero values that are less than or equal to the expected file size
+ ASSERT_NE(items[2]->m_dwSize, 0);
+ ASSERT_LE(items[2]->m_dwSize, SAMPLE_ITEM_3_SIZE);
+ ASSERT_NE(items[3]->m_dwSize, 0);
+ ASSERT_LE(items[3]->m_dwSize, SAMPLE_ITEM_4_SIZE);
+ ASSERT_NE(items[4]->m_dwSize, 0);
+ ASSERT_LE(items[4]->m_dwSize, SAMPLE_ITEM_5_SIZE);
+ ASSERT_NE(items[5]->m_dwSize, 0);
+ ASSERT_LE(items[5]->m_dwSize, SAMPLE_ITEM_6_SIZE);
+ }
+
+ void CheckFileItems(CFileItemList const& items)
+ {
+ CheckFileItemTypes(items);
+ CheckFileItemLabels(items);
+ }
+
+ void CheckFileItemsAndMetadata(CFileItemList const& items)
+ {
+ CheckFileItems(items);
+ CheckFileItemDateTimes(items);
+ CheckFileItemSizes(items);
+ }
+
+ CWebServer m_webServer;
+ uint16_t m_webServerPort;
+ std::string m_baseUrl;
+ std::string const m_sourcePath;
+ CHTTPVfsHandler m_vfsHandler;
+ CHTTPDirectory m_httpDirectory;
+};
+
+TEST_F(TestHTTPDirectory, IsStarted)
+{
+ ASSERT_TRUE(m_webServer.IsStarted());
+}
+
+TEST_F(TestHTTPDirectory, ApacheDefaultIndex)
+{
+ CFileItemList items;
+
+ ASSERT_TRUE(m_httpDirectory.Exists(CURL(GetUrlOfTestFile(TEST_FILE_APACHE_DEFAULT))));
+ ASSERT_TRUE(
+ m_httpDirectory.GetDirectory(CURL(GetUrlOfTestFile(TEST_FILE_APACHE_DEFAULT)), items));
+
+ CheckFileItems(items);
+}
+
+TEST_F(TestHTTPDirectory, ApacheFancyIndex)
+{
+ CFileItemList items;
+
+ ASSERT_TRUE(m_httpDirectory.Exists(CURL(GetUrlOfTestFile(TEST_FILE_APACHE_FANCY))));
+ ASSERT_TRUE(m_httpDirectory.GetDirectory(CURL(GetUrlOfTestFile(TEST_FILE_APACHE_FANCY)), items));
+
+ CheckFileItemsAndMetadata(items);
+}
+
+TEST_F(TestHTTPDirectory, ApacheHtmlIndex)
+{
+ CFileItemList items;
+
+ ASSERT_TRUE(m_httpDirectory.Exists(CURL(GetUrlOfTestFile(TEST_FILE_APACHE_HTML))));
+ ASSERT_TRUE(m_httpDirectory.GetDirectory(CURL(GetUrlOfTestFile(TEST_FILE_APACHE_HTML)), items));
+
+ CheckFileItemsAndMetadata(items);
+}
+
+TEST_F(TestHTTPDirectory, BasicIndex)
+{
+ CFileItemList items;
+
+ ASSERT_TRUE(m_httpDirectory.Exists(CURL(GetUrlOfTestFile(TEST_FILE_BASIC))));
+ ASSERT_TRUE(m_httpDirectory.GetDirectory(CURL(GetUrlOfTestFile(TEST_FILE_BASIC)), items));
+
+ CheckFileItems(items);
+}
+
+TEST_F(TestHTTPDirectory, BasicMultilineIndex)
+{
+ CFileItemList items;
+
+ ASSERT_TRUE(m_httpDirectory.Exists(CURL(GetUrlOfTestFile(TEST_FILE_BASIC_MULTILINE))));
+ ASSERT_TRUE(
+ m_httpDirectory.GetDirectory(CURL(GetUrlOfTestFile(TEST_FILE_BASIC_MULTILINE)), items));
+
+ CheckFileItems(items);
+}
+
+TEST_F(TestHTTPDirectory, LighttpDefaultIndex)
+{
+ CFileItemList items;
+
+ ASSERT_TRUE(m_httpDirectory.Exists(CURL(GetUrlOfTestFile(TEST_FILE_LIGHTTP_DEFAULT))));
+ ASSERT_TRUE(
+ m_httpDirectory.GetDirectory(CURL(GetUrlOfTestFile(TEST_FILE_LIGHTTP_DEFAULT)), items));
+
+ CheckFileItemsAndMetadata(items);
+}
+
+TEST_F(TestHTTPDirectory, NginxDefaultIndex)
+{
+ CFileItemList items;
+
+ ASSERT_TRUE(m_httpDirectory.Exists(CURL(GetUrlOfTestFile(TEST_FILE_NGINX_DEFAULT))));
+ ASSERT_TRUE(m_httpDirectory.GetDirectory(CURL(GetUrlOfTestFile(TEST_FILE_NGINX_DEFAULT)), items));
+
+ CheckFileItemsAndMetadata(items);
+}
+
+TEST_F(TestHTTPDirectory, NginxFancyIndex)
+{
+ CFileItemList items;
+
+ ASSERT_TRUE(m_httpDirectory.Exists(CURL(GetUrlOfTestFile(TEST_FILE_NGINX_FANCYINDEX))));
+ ASSERT_TRUE(m_httpDirectory.GetDirectory(CURL(GetUrlOfTestFile(TEST_FILE_NGINX_FANCYINDEX)), items));
+
+ CheckFileItemsAndMetadata(items);
+}
diff --git a/xbmc/filesystem/test/TestNfsFile.cpp b/xbmc/filesystem/test/TestNfsFile.cpp
new file mode 100644
index 0000000..69b1203
--- /dev/null
+++ b/xbmc/filesystem/test/TestNfsFile.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "URL.h"
+#include "filesystem/NFSFile.h"
+#include "test/TestUtils.h"
+
+#include <errno.h>
+#include <string>
+
+#include <gtest/gtest.h>
+
+using ::testing::Test;
+using ::testing::WithParamInterface;
+using ::testing::ValuesIn;
+
+struct SplitPath
+{
+ std::string url;
+ std::string exportPath;
+ std::string relativePath;
+ bool expectedResultExport;
+ bool expectedResultPath;
+} g_TestData[] = {
+ {"nfs://192.168.0.1:2049/srv/test/tvmedia/foo.txt", "/srv/test", "//tvmedia/foo.txt", true, true},
+ {"nfs://192.168.0.1/srv/test/tv/media/foo.txt", "/srv/test/tv", "//media/foo.txt", true, true},
+ {"nfs://192.168.0.1:2049/srv/test/tvmedia", "/srv/test", "//tvmedia", true, true},
+ {"nfs://192.168.0.1:2049/srv/test/tvmedia/", "/srv/test", "//tvmedia/", true, true},
+ {"nfs://192.168.0.1:2049/srv/test/tv/media", "/srv/test/tv", "//media", true, true},
+ {"nfs://192.168.0.1:2049/srv/test/tv/media/", "/srv/test/tv", "//media/", true, true},
+ {"nfs://192.168.0.1:2049/srv/test/tv", "/srv/test/tv", "//", true, true},
+ {"nfs://192.168.0.1:2049/srv/test/", "/srv/test", "//", true, true},
+ {"nfs://192.168.0.1:2049/", "/", "//", true, true},
+ {"nfs://192.168.0.1:2049/notexported/foo.txt", "/", "//notexported/foo.txt", true, true},
+
+ {"nfs://192.168.0.1:2049/notexported/foo.txt", "/notexported", "//foo.txt", false, false},
+ };
+
+class TestNfs : public Test,
+ public WithParamInterface<SplitPath>
+{
+};
+
+class ExportList
+{
+ public:
+ std::list<std::string> data;
+
+ ExportList()
+ {
+ data.emplace_back("/srv/test");
+ data.emplace_back("/srv/test/tv");
+ data.emplace_back("/");
+ data.sort();
+ data.reverse();
+ }
+};
+
+static ExportList exportList;
+
+TEST_P(TestNfs, splitUrlIntoExportAndPath)
+{
+ CURL url(GetParam().url);
+ std::string exportPath;
+ std::string relativePath;
+ gNfsConnection.splitUrlIntoExportAndPath(url, exportPath, relativePath, exportList.data);
+
+ if (GetParam().expectedResultExport)
+ EXPECT_STREQ(GetParam().exportPath.c_str(), exportPath.c_str());
+ else
+ EXPECT_STRNE(GetParam().exportPath.c_str(), exportPath.c_str());
+
+ if (GetParam().expectedResultPath)
+ EXPECT_STREQ(GetParam().relativePath.c_str(), relativePath.c_str());
+ else
+ EXPECT_STRNE(GetParam().relativePath.c_str(), relativePath.c_str());
+}
+
+INSTANTIATE_TEST_SUITE_P(NfsFile, TestNfs, ValuesIn(g_TestData));
diff --git a/xbmc/filesystem/test/TestZipFile.cpp b/xbmc/filesystem/test/TestZipFile.cpp
new file mode 100644
index 0000000..3ff518b
--- /dev/null
+++ b/xbmc/filesystem/test/TestZipFile.cpp
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "filesystem/Directory.h"
+#include "filesystem/File.h"
+#include "filesystem/ZipFile.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "test/TestUtils.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+
+#include <errno.h>
+
+#include <gtest/gtest.h>
+
+class TestZipFile : public testing::Test
+{
+protected:
+ TestZipFile() = default;
+
+ ~TestZipFile() override
+ {
+ CServiceBroker::GetSettingsComponent()->GetSettings()->Unload();
+ }
+};
+
+TEST_F(TestZipFile, Read)
+{
+ XFILE::CFile file;
+ char buf[20] = {};
+ std::string reffile, strpathinzip;
+ CFileItemList itemlist;
+
+ reffile = XBMC_REF_FILE_PATH("xbmc/filesystem/test/reffile.txt.zip");
+ CURL zipUrl = URIUtils::CreateArchivePath("zip", CURL(reffile), "");
+ ASSERT_TRUE(XFILE::CDirectory::GetDirectory(zipUrl, itemlist, "",
+ XFILE::DIR_FLAG_NO_FILE_DIRS));
+ EXPECT_GT(itemlist.Size(), 0);
+ EXPECT_FALSE(itemlist[0]->GetPath().empty());
+ strpathinzip = itemlist[0]->GetPath();
+ ASSERT_TRUE(file.Open(strpathinzip));
+ EXPECT_EQ(0, file.GetPosition());
+ EXPECT_EQ(1616, file.GetLength());
+ EXPECT_EQ(sizeof(buf), static_cast<size_t>(file.Read(buf, sizeof(buf))));
+ file.Flush();
+ EXPECT_EQ(20, file.GetPosition());
+ EXPECT_TRUE(!memcmp("About\n-----\nXBMC is ", buf, sizeof(buf) - 1));
+ EXPECT_TRUE(file.ReadString(buf, sizeof(buf)));
+ EXPECT_EQ(39, file.GetPosition());
+ EXPECT_STREQ("an award-winning fr", buf);
+ EXPECT_EQ(100, file.Seek(100));
+ EXPECT_EQ(100, file.GetPosition());
+ EXPECT_EQ(sizeof(buf), static_cast<size_t>(file.Read(buf, sizeof(buf))));
+ file.Flush();
+ EXPECT_EQ(120, file.GetPosition());
+ EXPECT_TRUE(!memcmp("ent hub for digital ", buf, sizeof(buf) - 1));
+ EXPECT_EQ(220, file.Seek(100, SEEK_CUR));
+ EXPECT_EQ(220, file.GetPosition());
+ EXPECT_EQ(sizeof(buf), static_cast<size_t>(file.Read(buf, sizeof(buf))));
+ file.Flush();
+ EXPECT_EQ(240, file.GetPosition());
+ EXPECT_TRUE(!memcmp("rs, XBMC is a non-pr", buf, sizeof(buf) - 1));
+ EXPECT_EQ(1596, file.Seek(-(int64_t)sizeof(buf), SEEK_END));
+ EXPECT_EQ(1596, file.GetPosition());
+ EXPECT_EQ(sizeof(buf), static_cast<size_t>(file.Read(buf, sizeof(buf))));
+ file.Flush();
+ EXPECT_EQ(1616, file.GetPosition());
+ EXPECT_TRUE(!memcmp("multimedia jukebox.\n", buf, sizeof(buf) - 1));
+ EXPECT_EQ(-1, file.Seek(100, SEEK_CUR));
+ EXPECT_EQ(1616, file.GetPosition());
+ EXPECT_EQ(0, file.Seek(0, SEEK_SET));
+ EXPECT_EQ(sizeof(buf), static_cast<size_t>(file.Read(buf, sizeof(buf))));
+ file.Flush();
+ EXPECT_EQ(20, file.GetPosition());
+ EXPECT_TRUE(!memcmp("About\n-----\nXBMC is ", buf, sizeof(buf) - 1));
+ EXPECT_EQ(0, file.Seek(0, SEEK_SET));
+ EXPECT_EQ(-1, file.Seek(-100, SEEK_SET));
+ file.Close();
+}
+
+TEST_F(TestZipFile, Exists)
+{
+ std::string reffile, strpathinzip;
+ CFileItemList itemlist;
+
+ reffile = XBMC_REF_FILE_PATH("xbmc/filesystem/test/reffile.txt.zip");
+ CURL zipUrl = URIUtils::CreateArchivePath("zip", CURL(reffile), "");
+ ASSERT_TRUE(XFILE::CDirectory::GetDirectory(zipUrl, itemlist, "",
+ XFILE::DIR_FLAG_NO_FILE_DIRS));
+ strpathinzip = itemlist[0]->GetPath();
+
+ EXPECT_TRUE(XFILE::CFile::Exists(strpathinzip));
+}
+
+TEST_F(TestZipFile, Stat)
+{
+ struct __stat64 buffer;
+ std::string reffile, strpathinzip;
+ CFileItemList itemlist;
+
+ reffile = XBMC_REF_FILE_PATH("xbmc/filesystem/test/reffile.txt.zip");
+ CURL zipUrl = URIUtils::CreateArchivePath("zip", CURL(reffile), "");
+ ASSERT_TRUE(XFILE::CDirectory::GetDirectory(zipUrl, itemlist, "",
+ XFILE::DIR_FLAG_NO_FILE_DIRS));
+ strpathinzip = itemlist[0]->GetPath();
+
+ EXPECT_EQ(0, XFILE::CFile::Stat(strpathinzip, &buffer));
+ EXPECT_TRUE(buffer.st_mode | _S_IFREG);
+}
+
+/* Test case to test for graceful handling of corrupted input.
+ * NOTE: The test case is considered a "success" as long as the corrupted
+ * file was successfully generated and the test case runs without a segfault.
+ */
+TEST_F(TestZipFile, CorruptedFile)
+{
+ XFILE::CFile *file;
+ char buf[16] = {};
+ std::string reffilepath, strpathinzip, str;
+ CFileItemList itemlist;
+ ssize_t size, i;
+ int64_t count = 0;
+
+ reffilepath = XBMC_REF_FILE_PATH("xbmc/filesystem/test/reffile.txt.zip");
+ ASSERT_TRUE((file = XBMC_CREATECORRUPTEDFILE(reffilepath, ".zip")) != NULL);
+ std::cout << "Reference file generated at '" << XBMC_TEMPFILEPATH(file) << "'" << std::endl;
+
+ CURL zipUrl = URIUtils::CreateArchivePath("zip", CURL(reffilepath), "");
+ if (!XFILE::CDirectory::GetDirectory(zipUrl, itemlist, "",
+ XFILE::DIR_FLAG_NO_FILE_DIRS))
+ {
+ XBMC_DELETETEMPFILE(file);
+ SUCCEED();
+ return;
+ }
+ if (itemlist.IsEmpty())
+ {
+ XBMC_DELETETEMPFILE(file);
+ SUCCEED();
+ return;
+ }
+ strpathinzip = itemlist[0]->GetPath();
+
+ if (!file->Open(strpathinzip))
+ {
+ XBMC_DELETETEMPFILE(file);
+ SUCCEED();
+ return;
+ }
+ std::cout << "file->GetLength(): " <<
+ testing::PrintToString(file->GetLength()) << std::endl;
+ std::cout << "file->Seek(file->GetLength() / 2, SEEK_CUR) return value: " <<
+ testing::PrintToString(file->Seek(file->GetLength() / 2, SEEK_CUR)) << std::endl;
+ std::cout << "file->Seek(0, SEEK_END) return value: " <<
+ testing::PrintToString(file->Seek(0, SEEK_END)) << std::endl;
+ std::cout << "file->Seek(0, SEEK_SET) return value: " <<
+ testing::PrintToString(file->Seek(0, SEEK_SET)) << std::endl;
+ std::cout << "File contents:" << std::endl;
+ while ((size = file->Read(buf, sizeof(buf))) > 0)
+ {
+ str = StringUtils::Format(" {:08X}", count);
+ std::cout << str << " ";
+ count += size;
+ for (i = 0; i < size; i++)
+ {
+ str = StringUtils::Format("{:02X} ", buf[i]);
+ std::cout << str;
+ }
+ while (i++ < static_cast<ssize_t> (sizeof(buf)))
+ std::cout << " ";
+ std::cout << " [";
+ for (i = 0; i < size; i++)
+ {
+ if (buf[i] >= ' ' && buf[i] <= '~')
+ std::cout << buf[i];
+ else
+ std::cout << ".";
+ }
+ std::cout << "]" << std::endl;
+ }
+ file->Close();
+ XBMC_DELETETEMPFILE(file);
+}
+
+TEST_F(TestZipFile, ExtendedLocalHeader)
+{
+ XFILE::CFile file;
+ ssize_t readlen;
+ char zipdata[20000]; // size of zip file is 15352 Bytes
+
+ ASSERT_TRUE(file.Open(XBMC_REF_FILE_PATH("xbmc/filesystem/test/extendedlocalheader.zip")));
+ readlen = file.Read(zipdata, sizeof(zipdata));
+ EXPECT_TRUE(readlen);
+
+ XFILE::CZipFile zipfile;
+ std::string strBuffer;
+
+ int iSize = zipfile.UnpackFromMemory(strBuffer, std::string(zipdata, readlen), false);
+ EXPECT_EQ(152774, iSize); // sum of uncompressed size of all files in zip
+ EXPECT_TRUE(strBuffer.substr(0, 6) == "<Data>");
+ file.Close();
+}
diff --git a/xbmc/filesystem/test/TestZipManager.cpp b/xbmc/filesystem/test/TestZipManager.cpp
new file mode 100644
index 0000000..ca669c5
--- /dev/null
+++ b/xbmc/filesystem/test/TestZipManager.cpp
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "filesystem/ZipManager.h"
+#include "utils/RegExp.h"
+
+#include <gtest/gtest.h>
+
+TEST(TestZipManager, PathTraversal)
+{
+ CRegExp pathTraversal;
+ pathTraversal.RegComp(PATH_TRAVERSAL);
+
+ ASSERT_TRUE(pathTraversal.RegFind("..") >= 0);
+ ASSERT_TRUE(pathTraversal.RegFind("../test.txt") >= 0);
+ ASSERT_TRUE(pathTraversal.RegFind("..\\test.txt") >= 0);
+ ASSERT_TRUE(pathTraversal.RegFind("test/../test.txt") >= 0);
+ ASSERT_TRUE(pathTraversal.RegFind("test\\../test.txt") >= 0);
+ ASSERT_TRUE(pathTraversal.RegFind("test\\..\\test.txt") >= 0);
+
+ ASSERT_FALSE(pathTraversal.RegFind("...") >= 0);
+ ASSERT_FALSE(pathTraversal.RegFind("..test.txt") >= 0);
+ ASSERT_FALSE(pathTraversal.RegFind("test.txt..") >= 0);
+ ASSERT_FALSE(pathTraversal.RegFind("test..test.txt") >= 0);
+}
diff --git a/xbmc/filesystem/test/data/httpdirectory/apache-default.html b/xbmc/filesystem/test/data/httpdirectory/apache-default.html
new file mode 100644
index 0000000..e29ff27
--- /dev/null
+++ b/xbmc/filesystem/test/data/httpdirectory/apache-default.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+ <head>
+ <title>Index of /</title>
+ </head>
+ <body>
+<h1>Index of /</h1>
+<ul><li><a href="folder1/"> folder1/</a></li>
+<li><a href="folder2/"> folder2/</a></li>
+<li><a href="./sample3:%20the%20sampling.mpg"> sample3: the sampling.mpg</a></li>
+<li><a href="sample%20&amp;%20samplability%204.mpg"> sample & samplability 4.mpg</a></li>
+<li><a href="sample5.mpg"> sample5.mpg</a></li>
+<li><a href="sample6.mpg"> sample6.mpg</a></li>
+</ul>
+</body></html> \ No newline at end of file
diff --git a/xbmc/filesystem/test/data/httpdirectory/apache-fancy.html b/xbmc/filesystem/test/data/httpdirectory/apache-fancy.html
new file mode 100644
index 0000000..a45d52a
--- /dev/null
+++ b/xbmc/filesystem/test/data/httpdirectory/apache-fancy.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+ <head>
+ <title>Index of /</title>
+ </head>
+ <body>
+<h1>Index of /</h1>
+<pre> <a href="?C=N;O=D;F=1">Name</a> <a href="?C=M;O=A;F=1">Last modified</a> <a href="?C=S;O=A;F=1">Size</a> <a href="?C=D;O=A;F=1">Description</a><hr> <a href="folder1/">folder1/</a> 2019-01-01 01:01 -
+ <a href="folder2/">folder2/</a> 2019-02-02 02:02 -
+ <a href="./sample3:%20the%20sampling.mpg">sample3: the sampling.mpg</a> 2019-03-03 03:03 123
+ <a href="sample%20&amp;%20samplability%204.mpg">sample & samplability 4.mpg</a> 2019-04-04 04:04 123K
+ <a href="sample5.mpg">sample5.mpg</a> 2019-05-05 05:05 123M
+ <a href="sample6.mpg">sample6.mpg</a> 2019-06-06 06:06 123G
+<hr></pre>
+</body></html>
diff --git a/xbmc/filesystem/test/data/httpdirectory/apache-html.html b/xbmc/filesystem/test/data/httpdirectory/apache-html.html
new file mode 100644
index 0000000..8e69ab4
--- /dev/null
+++ b/xbmc/filesystem/test/data/httpdirectory/apache-html.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+ <head>
+ <title>Index of /</title>
+ </head>
+ <body>
+<h1>Index of /</h1>
+ <table>
+ <tr><th valign="top">&nbsp;</th><th><a href="?C=N;O=D;F=2">Name</a></th><th><a href="?C=M;O=A;F=2">Last modified</a></th><th><a href="?C=S;O=A;F=2">Size</a></th><th><a href="?C=D;O=A;F=2">Description</a></th></tr>
+ <tr><th colspan="5"><hr></th></tr>
+<tr><td valign="top">&nbsp;</td><td><a href="folder1/">folder1/</a> </td><td align="right">2019-01-01 01:01 </td><td align="right"> - </td><td>&nbsp;</td></tr>
+<tr><td valign="top">&nbsp;</td><td><a href="folder2/">folder2/</a> </td><td align="right">2019-02-02 02:02 </td><td align="right"> - </td><td>&nbsp;</td></tr>
+<tr><td valign="top">&nbsp;</td><td><a href="./sample3:%20the%20sampling.mpg">sample3: the sampling.mpg</a> </td><td align="right">2019-03-03 03:03 </td><td align="right">123 </td><td>&nbsp;</td></tr>
+<tr><td valign="top">&nbsp;</td><td><a href="sample%20&amp;%20samplability%204.mpg">sample & samplability 4.mpg</a> </td><td align="right">2019-04-04 04:04 </td><td align="right">123K</td><td>&nbsp;</td></tr>
+<tr><td valign="top">&nbsp;</td><td><a href="sample5.mpg">sample5.mpg</a> </td><td align="right">2019-05-05 05:05 </td><td align="right">123M</td><td>&nbsp;</td></tr>
+<tr><td valign="top">&nbsp;</td><td><a href="sample6.mpg">sample6.mpg</a> </td><td align="right">2019-06-06 06:06 </td><td align="right">123G</td><td>&nbsp;</td></tr>
+ <tr><th colspan="5"><hr></th></tr>
+</table>
+</body></html>
diff --git a/xbmc/filesystem/test/data/httpdirectory/basic-multiline.html b/xbmc/filesystem/test/data/httpdirectory/basic-multiline.html
new file mode 100644
index 0000000..707a1f0
--- /dev/null
+++ b/xbmc/filesystem/test/data/httpdirectory/basic-multiline.html
@@ -0,0 +1,16 @@
+<html>
+ <head>
+ <title>Directory Listing</title>
+ </head>
+ <body>
+ <a href="folder1/">folder1/</a>
+ <a href="folder2/">
+ folder2/
+ </a>
+ <a href="./sample3:%20the%20sampling.mpg">sample3: the sampling.mpg</a>
+ <a href="sample%20&%20samplability%204.mpg">sample & samplability 4.mpg</a> <a href="sample5.mpg">sample5.mpg</a>
+ <a href="sample6.mpg">
+ sample6.mpg
+ </a>
+ </body>
+</html> \ No newline at end of file
diff --git a/xbmc/filesystem/test/data/httpdirectory/basic.html b/xbmc/filesystem/test/data/httpdirectory/basic.html
new file mode 100644
index 0000000..ce98a10
--- /dev/null
+++ b/xbmc/filesystem/test/data/httpdirectory/basic.html
@@ -0,0 +1,13 @@
+<html>
+ <head>
+ <title>Directory Listing</title>
+ </head>
+ <body>
+ <a href="folder1/">folder1/</a>
+ <a href="folder2/">folder2/</a>
+ <a href="./sample3:%20the%20sampling.mpg">sample3: the sampling.mpg</a>
+ <a href="sample%20&%20samplability%204.mpg">sample & samplability 4.mpg</a>
+ <a href="sample5.mpg">sample5.mpg</a>
+ <a href="sample6.mpg">sample6.mpg</a>
+ </body>
+</html> \ No newline at end of file
diff --git a/xbmc/filesystem/test/data/httpdirectory/lighttp-default.html b/xbmc/filesystem/test/data/httpdirectory/lighttp-default.html
new file mode 100644
index 0000000..505f477
--- /dev/null
+++ b/xbmc/filesystem/test/data/httpdirectory/lighttp-default.html
@@ -0,0 +1,211 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Index of /</title>
+<style type="text/css">
+a, a:active {text-decoration: none; color: blue;}
+a:visited {color: #48468F;}
+a:hover, a:focus {text-decoration: underline; color: red;}
+body {background-color: #F5F5F5;}
+h2 {margin-bottom: 12px;}
+table {margin-left: 12px;}
+th, td { font: 90% monospace; text-align: left;}
+th { font-weight: bold; padding-right: 14px; padding-bottom: 3px;}
+td {padding-right: 14px;}
+td.s, th.s {text-align: right;}
+div.list { background-color: white; border-top: 1px solid #646464; border-bottom: 1px solid #646464; padding-top: 10px; padding-bottom: 14px;}
+div.foot { font: 90% monospace; color: #787878; padding-top: 4px;}
+</style>
+</head>
+<body>
+<h2>Index of /</h2>
+<div class="list">
+<table summary="Directory Listing" cellpadding="0" cellspacing="0">
+<thead><tr><th class="n">Name</th><th class="m">Last Modified</th><th class="s">Size</th><th class="t">Type</th></tr></thead>
+<tbody>
+<tr class="d"><td class="n"><a href="folder1/">folder1</a>/</td><td class="m">2019-Jan-01 01:01:01</td><td class="s">- &nbsp;</td><td class="t">Directory</td></tr>
+<tr class="d"><td class="n"><a href="folder2/">folder2</a>/</td><td class="m">2019-Feb-02 02:02:02</td><td class="s">- &nbsp;</td><td class="t">Directory</td></tr>
+<tr><td class="n"><a href="sample3%3a%20the%20sampling.mpg">sample3: the sampling.mpg</a></td><td class="m">2019-Mar-03 03:03:03</td><td class="s">0.1K</td><td class="t">video/mpeg</td></tr>
+<tr><td class="n"><a href="sample%20%26%20samplability%204.mpg">sample &#x26; samplability 4.mpg</a></td><td class="m">2019-Apr-04 04:04:04</td><td class="s">123.0K</td><td class="t">video/mpeg</td></tr>
+<tr><td class="n"><a href="sample5.mpg">sample5.mpg</a></td><td class="m">2019-May-05 05:05:05</td><td class="s">123.0M</td><td class="t">video/mpeg</td></tr>
+<tr><td class="n"><a href="sample6.mpg">sample6.mpg</a></td><td class="m">2019-Jun-06 06:06:06</td><td class="s">123.0G</td><td class="t">video/mpeg</td></tr>
+</tbody>
+</table>
+</div>
+<div class="foot">lighttpd/1.4.49</div>
+
+<script type="text/javascript">
+// <!--
+
+var click_column;
+var name_column = 0;
+var date_column = 1;
+var size_column = 2;
+var type_column = 3;
+var prev_span = null;
+
+if (typeof(String.prototype.localeCompare) === 'undefined') {
+ String.prototype.localeCompare = function(str, locale, options) {
+ return ((this == str) ? 0 : ((this > str) ? 1 : -1));
+ };
+}
+
+if (typeof(String.prototype.toLocaleUpperCase) === 'undefined') {
+ String.prototype.toLocaleUpperCase = function() {
+ return this.toUpperCase();
+ };
+}
+
+function get_inner_text(el) {
+ if((typeof el == 'string')||(typeof el == 'undefined'))
+ return el;
+ if(el.innerText)
+ return el.innerText;
+ else {
+ var str = "";
+ var cs = el.childNodes;
+ var l = cs.length;
+ for (i=0;i<l;i++) {
+ if (cs[i].nodeType==1) str += get_inner_text(cs[i]);
+ else if (cs[i].nodeType==3) str += cs[i].nodeValue;
+ }
+ }
+ return str;
+}
+
+function isdigit(c) {
+ return (c >= '0' && c <= '9');
+}
+
+function unit_multiplier(unit) {
+ return (unit=='K') ? 1000
+ : (unit=='M') ? 1000000
+ : (unit=='G') ? 1000000000
+ : (unit=='T') ? 1000000000000
+ : (unit=='P') ? 1000000000000000
+ : (unit=='E') ? 1000000000000000000 : 1;
+}
+
+var li_date_regex=/(\d{4})-(\w{3})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/;
+
+var li_mon = ['Jan','Feb','Mar','Apr','May','Jun',
+ 'Jul','Aug','Sep','Oct','Nov','Dec'];
+
+function li_mon_num(mon) {
+ var i; for (i = 0; i < 12 && mon != li_mon[i]; ++i); return i;
+}
+
+function li_date_cmp(s1, s2) {
+ var dp1 = li_date_regex.exec(s1)
+ var dp2 = li_date_regex.exec(s2)
+ for (var i = 1; i < 7; ++i) {
+ var cmp = (2 != i)
+ ? parseInt(dp1[i]) - parseInt(dp2[i])
+ : li_mon_num(dp1[2]) - li_mon_num(dp2[2]);
+ if (0 != cmp) return cmp;
+ }
+ return 0;
+}
+
+function sortfn_then_by_name(a,b,sort_column) {
+ if (sort_column == name_column || sort_column == type_column) {
+ var ad = (a.cells[type_column].innerHTML === 'Directory');
+ var bd = (b.cells[type_column].innerHTML === 'Directory');
+ if (ad != bd) return (ad ? -1 : 1);
+ }
+ var at = get_inner_text(a.cells[sort_column]);
+ var bt = get_inner_text(b.cells[sort_column]);
+ var cmp;
+ if (sort_column == name_column) {
+ if (at == '..') return -1;
+ if (bt == '..') return 1;
+ }
+ if (a.cells[sort_column].className == 'int') {
+ cmp = parseInt(at)-parseInt(bt);
+ } else if (sort_column == date_column) {
+ var ad = isdigit(at.substr(0,1));
+ var bd = isdigit(bt.substr(0,1));
+ if (ad != bd) return (!ad ? -1 : 1);
+ cmp = li_date_cmp(at,bt);
+ } else if (sort_column == size_column) {
+ var ai = parseInt(at, 10) * unit_multiplier(at.substr(-1,1));
+ var bi = parseInt(bt, 10) * unit_multiplier(bt.substr(-1,1));
+ if (at.substr(0,1) == '-') ai = -1;
+ if (bt.substr(0,1) == '-') bi = -1;
+ cmp = ai - bi;
+ } else {
+ cmp = at.toLocaleUpperCase().localeCompare(bt.toLocaleUpperCase());
+ if (0 != cmp) return cmp;
+ cmp = at.localeCompare(bt);
+ }
+ if (0 != cmp || sort_column == name_column) return cmp;
+ return sortfn_then_by_name(a,b,name_column);
+}
+
+function sortfn(a,b) {
+ return sortfn_then_by_name(a,b,click_column);
+}
+
+function resort(lnk) {
+ var span = lnk.childNodes[1];
+ var table = lnk.parentNode.parentNode.parentNode.parentNode;
+ var rows = new Array();
+ for (j=1;j<table.rows.length;j++)
+ rows[j-1] = table.rows[j];
+ click_column = lnk.parentNode.cellIndex;
+ rows.sort(sortfn);
+
+ if (prev_span != null) prev_span.innerHTML = '';
+ if (span.getAttribute('sortdir')=='down') {
+ span.innerHTML = '&uarr;';
+ span.setAttribute('sortdir','up');
+ rows.reverse();
+ } else {
+ span.innerHTML = '&darr;';
+ span.setAttribute('sortdir','down');
+ }
+ for (i=0;i<rows.length;i++)
+ table.tBodies[0].appendChild(rows[i]);
+ prev_span = span;
+}
+
+function init_sort(init_sort_column, ascending) {
+ var tables = document.getElementsByTagName("table");
+ for (var i = 0; i < tables.length; i++) {
+ var table = tables[i];
+ //var c = table.getAttribute("class")
+ //if (-1 != c.split(" ").indexOf("sort")) {
+ var row = table.rows[0].cells;
+ for (var j = 0; j < row.length; j++) {
+ var n = row[j];
+ if (n.childNodes.length == 1 && n.childNodes[0].nodeType == 3) {
+ var link = document.createElement("a");
+ var title = n.childNodes[0].nodeValue.replace(/:$/, "");
+ link.appendChild(document.createTextNode(title));
+ link.setAttribute("href", "#");
+ link.setAttribute("class", "sortheader");
+ link.setAttribute("onclick", "resort(this);return false;");
+ var arrow = document.createElement("span");
+ arrow.setAttribute("class", "sortarrow");
+ arrow.appendChild(document.createTextNode(":"));
+ link.appendChild(arrow)
+ n.replaceChild(link, n.firstChild);
+ }
+ }
+ var lnk = row[init_sort_column].firstChild;
+ if (ascending) {
+ var span = lnk.childNodes[1];
+ span.setAttribute('sortdir','down');
+ }
+ resort(lnk);
+ //}
+ }
+}
+
+init_sort(0, 0);
+
+// -->
+</script>
+
+</body>
+</html>
diff --git a/xbmc/filesystem/test/data/httpdirectory/nginx-default.html b/xbmc/filesystem/test/data/httpdirectory/nginx-default.html
new file mode 100644
index 0000000..77bcd75
--- /dev/null
+++ b/xbmc/filesystem/test/data/httpdirectory/nginx-default.html
@@ -0,0 +1,11 @@
+<html>
+<head><title>Index of /</title></head>
+<body>
+<h1>Index of /</h1><hr><pre><a href="folder1/">folder1/</a> 01-Jan-2019 01:01 -
+<a href="folder2/">folder2/</a> 02-Feb-2019 02:02 -
+<a href="sample3%3A%20the%20sampling.mpg">sample3: the sampling.mpg</a> 03-Mar-2019 03:03 123
+<a href="sample%20%26%20samplability%204.mpg">sample &#x26; samplability 4.mpg</a> 04-Apr-2019 04:04 125952
+<a href="sample5.mpg">sample5.mpg</a> 05-May-2019 05:05 128974848
+<a href="sample6.mpg">sample6.mpg</a> 06-Jun-2019 06:06 132070244352
+</pre><hr></body>
+</html>
diff --git a/xbmc/filesystem/test/data/httpdirectory/nginx-fancyindex.html b/xbmc/filesystem/test/data/httpdirectory/nginx-fancyindex.html
new file mode 100644
index 0000000..d9772df
--- /dev/null
+++ b/xbmc/filesystem/test/data/httpdirectory/nginx-fancyindex.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
+ <meta name="viewport" content="width=device-width"/>
+ <link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo=">
+ <title>Files...</title>
+</head>
+<body>
+<div class="box box-breadcrumbs">
+ <div class="box-header">
+ <div class="box-header-content">
+ <div id="breadcrumbs" class="breadcrumbs">
+ <a href="#"></a>
+ </div>
+ </div>
+ </div>
+ <div class="box-content clearfix">
+ <h1>Index of:
+/</h1>
+<table id="list"><thead><tr><th style="width:55%"><a href="?C=N&amp;O=A">File Name</a>&nbsp;<a href="?C=N&amp;O=D">&nbsp;&darr;&nbsp;</a></th><th style="width:20%"><a href="?C=S&amp;O=A">File Size</a>&nbsp;<a href="?C=S&amp;O=D">&nbsp;&darr;&nbsp;</a></th><th style="width:25%"><a href="?C=M&amp;O=A">Date</a>&nbsp;<a href="?C=M&amp;O=D">&nbsp;&darr;&nbsp;</a></th></tr></thead>
+<tbody><tr><td class="link"><a href="../">Parent directory/</a></td><td class="size">-</td><td class="date">-</td></tr>
+<tr><td class="link"><a href="folder1/" title="folder1">folder1/</a></td><td class="size">-</td><td class="date">2019-Jan-01 01:01</td></tr>
+<tr><td class="link"><a href="folder2/" title="folder2">folder2/</a></td><td class="size">-</td><td class="date">2019-Feb-02 02:02</td></tr>
+<tr><td class="link"><a href="sample3%3A%20the%20sampling.mpg" title="sample3: the sampling.mpg">sample3: the sampling.mpg</a></td><td class="size">123 B</td><td class="date">2019-Mar-03 03:03</td></tr>
+<tr><td class="link"><a href="sample%20%26%20samplability%204.mpg" title="sample &#x26; samplability 4.mpg">sample &#x26; samplability 4.mpg</a></td><td class="size">123.0 KiB</td><td class="date">2019-Apr-04 04:04</td></tr>
+<tr><td class="link"><a href="sample5.mpg" title="sample5.mpg">sample5.mpg</a></td><td class="size">123.0 MiB</td><td class="date">2019-May-05 05:05</td></tr>
+<tr><td class="link"><a href="sample6.mpg" title="sample6.mpg">sample6.mpg</a></td><td class="size">123.0 GiB</td><td class="date">2019-Jun-06 06:06</td></tr>
+</tbody></table>
+</div>
+</div>
+</body>
+</html>
diff --git a/xbmc/filesystem/test/extendedlocalheader.zip b/xbmc/filesystem/test/extendedlocalheader.zip
new file mode 100644
index 0000000..b30d92e
--- /dev/null
+++ b/xbmc/filesystem/test/extendedlocalheader.zip
Binary files differ
diff --git a/xbmc/filesystem/test/refRARnormal.rar b/xbmc/filesystem/test/refRARnormal.rar
new file mode 100644
index 0000000..58fe71d
--- /dev/null
+++ b/xbmc/filesystem/test/refRARnormal.rar
Binary files differ
diff --git a/xbmc/filesystem/test/refRARstored.rar b/xbmc/filesystem/test/refRARstored.rar
new file mode 100644
index 0000000..1500027
--- /dev/null
+++ b/xbmc/filesystem/test/refRARstored.rar
Binary files differ
diff --git a/xbmc/filesystem/test/reffile.txt b/xbmc/filesystem/test/reffile.txt
new file mode 100644
index 0000000..7a5e510
--- /dev/null
+++ b/xbmc/filesystem/test/reffile.txt
@@ -0,0 +1,25 @@
+About
+-----
+XBMC is an award-winning free and open source (GPL) software media player and
+entertainment hub for digital media. XBMC is available for multiple platforms.
+Created in 2003 by a group of like minded programmers, XBMC is a non-profit
+project run and developed by volunteers located around the world. More than 50
+software developers have contributed to XBMC, and 100-plus translators have
+worked to expand its reach, making it available in more than 30 languages.
+
+While XBMC functions very well as a standard media player application for your
+computer, it has been designed to be the perfect companion for your HTPC.
+Supporting an almost endless range of remote controls, and combined with its
+beautiful interface and powerful skinning engine, XBMC feels very natural to
+use from the couch and is the ideal solution for your home theater.
+
+Currently XBMC can be used to play almost all popular audio and video formats
+around. It was designed for network playback, so you can stream your multimedia
+from anywhere in the house or directly from the internet using practically any
+protocol available. Use your media as-is: XBMC can play CDs and DVDs directly
+from the disk or image file, almost all popular archive formats from your hard
+drive, and even files inside ZIP and RAR archives. It will even scan all of
+your media and automatically create a personalized library complete with box
+covers, descriptions, and fanart. There are playlist and slideshow functions, a
+weather forecast feature and many audio visualizations. Once installed, your
+computer will become a fully functional multimedia jukebox.
diff --git a/xbmc/filesystem/test/reffile.txt.rar b/xbmc/filesystem/test/reffile.txt.rar
new file mode 100644
index 0000000..20841b8
--- /dev/null
+++ b/xbmc/filesystem/test/reffile.txt.rar
Binary files differ
diff --git a/xbmc/filesystem/test/reffile.txt.zip b/xbmc/filesystem/test/reffile.txt.zip
new file mode 100644
index 0000000..dd0572f
--- /dev/null
+++ b/xbmc/filesystem/test/reffile.txt.zip
Binary files differ