refactor: use extensible design for fetching playlist from single file

This commit is contained in:
2026-07-25 12:06:11 +08:00
parent 4ed491c78a
commit f55f8c731d
9 changed files with 146 additions and 76 deletions
+6 -2
View File
@@ -53,7 +53,9 @@ set (PPIC_CPP_FILES
app/exiv2wrapper.cpp app/exiv2wrapper.cpp
app/playlistmanager.cpp app/playlistmanager.cpp
app/fileopeneventhandler.cpp app/fileopeneventhandler.cpp
app/winplaylistpatch.cpp app/playlistscout.cpp
app/playlistscouts/qtscout.cpp
app/playlistscouts/explorerscout.cpp
) )
set (PPIC_HEADER_FILES set (PPIC_HEADER_FILES
@@ -71,7 +73,9 @@ set (PPIC_HEADER_FILES
app/exiv2wrapper.h app/exiv2wrapper.h
app/playlistmanager.h app/playlistmanager.h
app/fileopeneventhandler.h app/fileopeneventhandler.h
app/winplaylistpatch.h app/playlistscout.h
app/playlistscouts/qtscout.h
app/playlistscouts/explorerscout.h
) )
set (PPIC_UI_FILES set (PPIC_UI_FILES
+7 -43
View File
@@ -3,7 +3,7 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
#include "playlistmanager.h" #include "playlistmanager.h"
#include "winplaylistpatch.h" #include "playlistscout.h"
#include <QCollator> #include <QCollator>
#include <QDir> #include <QDir>
@@ -72,50 +72,14 @@ bool PlaylistManager::loadSingleFilePlaylist(const QUrl& url) {
if (!checkUrl(url)) if (!checkUrl(url))
return false; return false;
QList<QUrl> playlist; // Scout playlist by given file
std::optional<qsizetype> idx = std::nullopt; auto scoutResult = PlaylistScout::scout(url, m_allowedSuffixes);
if (!scoutResult.has_value()) {
#ifdef Q_OS_WIN return false;
// In Windows, Qt approach can not aware Windows Explorer custom sort order.
// So we need to try using Win32 functions for fetching it at first
if (!WinPlaylistPatch::loadSingleFilePlaylist(url, m_allowedSuffixes, playlist, idx)) {
#endif // Q_OS_WIN
// If Windows approach is failed or we are not in Windows,
// use Qt way to process it.
QFileInfo info(url.toLocalFile());
QDir dir(info.path());
QString currentFileName = info.fileName();
QStringList entryList = dir.entryList(
m_allowedSuffixes, QDir::Files | QDir::NoSymLinks, QDir::NoSort);
QCollator collator;
collator.setNumericMode(true);
std::sort(entryList.begin(), entryList.end(), collator);
playlist.clear();
idx = std::nullopt;
for (qsizetype i = 0; i < entryList.count(); i++) {
const QString &fileName = entryList.at(i);
const QString &oneEntry = dir.absoluteFilePath(fileName);
const QUrl &url = QUrl::fromLocalFile(oneEntry);
if (!checkUrl(url)) {
return false;
}
playlist.append(url);
if (fileName == currentFileName) {
idx = i;
}
}
#ifdef Q_OS_WIN
} }
#endif // Q_OS_WIN
setPlaylist(std::move(playlist), idx); // Setup playlist with index
setPlaylist(std::move(scoutResult->playlist), scoutResult->indexHint);
return true; return true;
} }
+25
View File
@@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: 2026 yyc12345 <yyc12321@outlook.com>
//
// SPDX-License-Identifier: MIT
#include "playlistscout.h"
#include "playlistscouts/qtscout.h"
#include "playlistscouts/explorerscout.h"
std::optional<PlaylistScout::ScoutResult> PlaylistScout::scout(
const QUrl &url, const QStringList &allowedSuffixes) {
#ifdef Q_OS_WIN
// In Windows, Qt approach can not aware Windows Explorer custom sort order.
// So we need to try using Win32 functions for fetching it at first.
if (auto rv = PlaylistScout::Explorer::scout(url, allowedSuffixes); rv.has_value()) {
return rv;
}
#endif // Q_OS_WIN
// TODO: We may add macOS Finder, KDE Dolphin and etc supports in future.
// Qt way is the last fallback.
return PlaylistScout::Qt::scout(url, allowedSuffixes);
}
+31
View File
@@ -0,0 +1,31 @@
// SPDX-FileCopyrightText: 2026 yyc12345 <yyc12321@outlook.com>
//
// SPDX-License-Identifier: MIT
#pragma once
#include <QUrl>
#include <QStringList>
#include <QList>
#include <optional>
namespace PlaylistScout {
/// The result of scout
struct ScoutResult {
/// The final fetched playlist (it can be empty).
/// Each item must be a QUrl pointing to a local file.
QList<QUrl> playlist;
/// The index of given file located in playlist.
/// Nullopt if it is not presented in playlist.
std::optional<qsizetype> indexHint;
};
/// @brief Scout available image files in given file's parent dir with given allowed suffixes.
/// @param[in] url The path to file whose parent dir is our target dir for finding image files.
/// @param[in] allowedSuffixes
/// Each items in this list is a wildcard pattern for matching allowed file name, like \c *.png .
/// The fetched item will be preserved if its name matching any of them.
/// If this list is empty, there is no restrictions for finding files.
/// @return Scout result if success, otherwise nullopt.
std::optional<ScoutResult> scout(const QUrl &url, const QStringList &allowedSuffixes);
}
@@ -2,7 +2,7 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
#include "winplaylistpatch.h" #include "explorerscout.h"
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
@@ -242,9 +242,9 @@ static ComPtr<IFolderView2> FindLiveFolderView(const QString& targetFolderPath)
return nullptr; return nullptr;
} }
bool WinPlaylistPatch::loadSingleFilePlaylist( std::optional<PlaylistScout::ScoutResult> PlaylistScout::Explorer::scout(
const QUrl &url, const QStringList &allowedSuffixes, QList<QUrl> &playlist, const QUrl &url, const QStringList &allowedSuffixes) {
std::optional<qsizetype> &indexHint) { PlaylistScout::ScoutResult rv;
QFileInfo info(url.toLocalFile()); QFileInfo info(url.toLocalFile());
QString folderPath = info.path(); QString folderPath = info.path();
@@ -263,14 +263,14 @@ bool WinPlaylistPatch::loadSingleFilePlaylist(
ComGuard comGuard; ComGuard comGuard;
if (!comGuard.IsInitialized()) { if (!comGuard.IsInitialized()) {
ReportError("COM initialization failed"); ReportError("COM initialization failed");
return false; return std::nullopt;
} }
// Find a live Explorer window that is currently displaying the target folder. // Find a live Explorer window that is currently displaying the target folder.
ComPtr<IFolderView2> folderView2 = FindLiveFolderView(folderPath); ComPtr<IFolderView2> folderView2 = FindLiveFolderView(folderPath);
if (!folderView2) { if (!folderView2) {
ReportError("No open Explorer window matches the folder: " + folderPath); ReportError("No open Explorer window matches the folder: " + folderPath);
return false; return std::nullopt;
} }
// Get items already sorted in Explorer's view order, as an IShellItemArray. // Get items already sorted in Explorer's view order, as an IShellItemArray.
@@ -279,7 +279,7 @@ bool WinPlaylistPatch::loadSingleFilePlaylist(
if (FAILED(folderView2->Items(SVGIO_ALLVIEW | SVGIO_FLAG_VIEWORDER, if (FAILED(folderView2->Items(SVGIO_ALLVIEW | SVGIO_FLAG_VIEWORDER,
IID_PPV_ARGS(itemArray.GetAddressOf())))) { IID_PPV_ARGS(itemArray.GetAddressOf())))) {
ReportError("IFolderView2::Items(SVGIO_ALLVIEW | SVGIO_FLAG_VIEWORDER) failed"); ReportError("IFolderView2::Items(SVGIO_ALLVIEW | SVGIO_FLAG_VIEWORDER) failed");
return false; return std::nullopt;
} }
DWORD itemCount = 0; DWORD itemCount = 0;
@@ -320,13 +320,13 @@ bool WinPlaylistPatch::loadSingleFilePlaylist(
continue; continue;
} }
playlist.append(QUrl::fromLocalFile(filePath)); rv.playlist.append(QUrl::fromLocalFile(filePath));
if (fileName == currentFileName) { if (fileName == currentFileName) {
indexHint = playlist.size() - 1; rv.indexHint = rv.playlist.size() - 1;
} }
} }
return true; return rv;
} }
#endif // Q_OS_WIN #endif // Q_OS_WIN
+16
View File
@@ -0,0 +1,16 @@
// SPDX-FileCopyrightText: 2026 yyc12345 <yyc12321@outlook.com>
//
// SPDX-License-Identifier: MIT
#pragma once
#include "../playlistscout.h"
#include <QtSystemDetection>
#ifdef Q_OS_WIN
namespace PlaylistScout::Explorer {
std::optional<ScoutResult> scout(const QUrl &url, const QStringList &allowedSuffixes);
}
#endif // Q_OS_WIN
+40
View File
@@ -0,0 +1,40 @@
// SPDX-FileCopyrightText: 2025 Gary Wang <git@blumia.net>
// SPDX-FileCopyrightText: 2026 yyc12345 <yyc12321@outlook.com>
//
// SPDX-License-Identifier: MIT
#include "qtscout.h"
#include <QFileInfo>
#include <QDir>
#include <QCollator>
std::optional<PlaylistScout::ScoutResult> PlaylistScout::Qt::scout(
const QUrl &url, const QStringList &allowedSuffixes) {
PlaylistScout::ScoutResult rv;
QFileInfo info(url.toLocalFile());
QDir dir(info.path());
QString currentFileName = info.fileName();
QStringList entryList = dir.entryList(
allowedSuffixes, QDir::Files | QDir::NoSymLinks, QDir::NoSort);
QCollator collator;
collator.setNumericMode(true);
std::sort(entryList.begin(), entryList.end(), collator);
for (qsizetype i = 0; i < entryList.count(); i++) {
const QString &fileName = entryList.at(i);
const QString &oneEntry = dir.absoluteFilePath(fileName);
const QUrl &url = QUrl::fromLocalFile(oneEntry);
rv.playlist.append(url);
if (fileName == currentFileName) {
rv.indexHint = i;
}
}
return rv;
}
+11
View File
@@ -0,0 +1,11 @@
// SPDX-FileCopyrightText: 2026 yyc12345 <yyc12321@outlook.com>
//
// SPDX-License-Identifier: MIT
#pragma once
#include "../playlistscout.h"
namespace PlaylistScout::Qt {
std::optional<ScoutResult> scout(const QUrl &url, const QStringList &allowedSuffixes);
}
-21
View File
@@ -1,21 +0,0 @@
// SPDX-FileCopyrightText: 2026 yyc12345 <yyc12321@outlook.com>
//
// SPDX-License-Identifier: MIT
#pragma once
#include <QtSystemDetection>
#ifdef Q_OS_WIN
#include <QUrl>
class WinPlaylistPatch
{
public:
static bool loadSingleFilePlaylist(const QUrl &url,
const QStringList &allowedSuffixes,
QList<QUrl> &playlist,
std::optional<qsizetype> &indexHint);
};
#endif // Q_OS_WIN