Compare commits
1
Commits
share
...
work/jbb/qcoro
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
701e5cfddc |
No files matched your search
@@ -32,12 +32,15 @@ ecm_setup_version(${PROJECT_VERSION}
|
||||
)
|
||||
|
||||
kde_enable_exceptions()
|
||||
add_compile_options(-fsanitize=address)
|
||||
add_link_options(-fsanitize=address)
|
||||
|
||||
find_package(Qt5 ${QT5_MIN_VERSION} REQUIRED COMPONENTS Core Gui Qml QuickControls2 Svg Sql)
|
||||
find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Kirigami2 I18n CoreAddons Crash)
|
||||
find_package(pybind11 REQUIRED)
|
||||
find_package(Ytdlp REQUIRED RUNTIME)
|
||||
find_package(YTMusicAPI REQUIRED RUNTIME)
|
||||
find_package(QCoro5 REQUIRED COMPONENTS Core Qml Network)
|
||||
|
||||
ki18n_install(po)
|
||||
|
||||
|
||||
@@ -48,6 +48,9 @@ target_link_libraries(audiotube
|
||||
KF5::I18n
|
||||
KF5::CoreAddons
|
||||
KF5::Crash
|
||||
QCoro5::Core
|
||||
QCoro5::Qml
|
||||
QCoro5::Network
|
||||
ytm
|
||||
asyncdatabase
|
||||
)
|
||||
|
||||
+30
-13
@@ -4,26 +4,43 @@
|
||||
|
||||
#include "albummodel.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QCoro/QCoroFuture>
|
||||
#include <QCoro/QCoroTask>
|
||||
|
||||
|
||||
#define AWAIT(coroutine) \
|
||||
({ \
|
||||
bool __abort = false; \
|
||||
connect(this, &QObject::destroyed, [&]() { \
|
||||
__abort = true; \
|
||||
}); \
|
||||
auto val = co_await coroutine; \
|
||||
if (abort) { \
|
||||
co_return; \
|
||||
} \
|
||||
val; \
|
||||
})
|
||||
|
||||
|
||||
|
||||
AlbumModel::AlbumModel(QObject *parent)
|
||||
: AbstractYTMusicModel(parent)
|
||||
{
|
||||
connect(this, &AlbumModel::browseIdChanged, this, [this] {
|
||||
connect(this, &AlbumModel::browseIdChanged, this, [this]() -> QCoro::Task<> {
|
||||
setLoading(true);
|
||||
auto future = YTMusicThread::instance()->fetchAlbum(m_browseId);
|
||||
connectFuture(future, this, [=, this](const album::Album &album) {
|
||||
setLoading(false);
|
||||
|
||||
beginResetModel();
|
||||
m_album = album;
|
||||
endResetModel();
|
||||
std::sort(m_album.thumbnails.begin(), m_album.thumbnails.end());
|
||||
auto album = co_await YTMusicThread::instance()->fetchAlbum(m_browseId).withContext(this);
|
||||
|
||||
Q_EMIT titleChanged();
|
||||
Q_EMIT thumbnailUrlChanged();
|
||||
Q_EMIT playlistIdChanged();
|
||||
});
|
||||
setLoading(false);
|
||||
|
||||
beginResetModel();
|
||||
m_album = album;
|
||||
endResetModel();
|
||||
std::sort(m_album.thumbnails.begin(), m_album.thumbnails.end());
|
||||
|
||||
Q_EMIT titleChanged();
|
||||
Q_EMIT thumbnailUrlChanged();
|
||||
Q_EMIT playlistIdChanged();
|
||||
});
|
||||
|
||||
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::errorOccurred, this, [=, this] {
|
||||
|
||||
+19
-18
@@ -6,37 +6,38 @@
|
||||
|
||||
#include <asyncytmusic.h>
|
||||
|
||||
#include <QCoro/QCoroTask>
|
||||
#include <QCoro/QCoroFuture>
|
||||
|
||||
ArtistModel::ArtistModel(QObject *parent)
|
||||
: AbstractYTMusicModel(parent)
|
||||
, m_view(albums, singles, songs, videos)
|
||||
{
|
||||
connect(this, &ArtistModel::channelIdChanged, this, [this] {
|
||||
connect(this, &ArtistModel::channelIdChanged, this, [this]() -> QCoro::Task<> {
|
||||
if (m_channelId.isEmpty()) {
|
||||
return;
|
||||
co_return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
|
||||
auto future = YTMusicThread::instance()->fetchArtist(m_channelId);
|
||||
connectFuture(future, this, [=, this](const artist::Artist &artist) {
|
||||
setLoading(false);
|
||||
auto artist = co_await YTMusicThread::instance()->fetchArtist(m_channelId);
|
||||
setLoading(false);
|
||||
|
||||
beginResetModel();
|
||||
m_artist = artist;
|
||||
std::sort(m_artist.thumbnails.begin(), m_artist.thumbnails.end());
|
||||
beginResetModel();
|
||||
m_artist = artist;
|
||||
std::sort(m_artist.thumbnails.begin(), m_artist.thumbnails.end());
|
||||
|
||||
albums = m_artist.albums ? m_artist.albums->results : std::vector<artist::Artist::Album>();
|
||||
singles = m_artist.singles ? m_artist.singles->results : std::vector<artist::Artist::Single>();
|
||||
songs = m_artist.songs ? m_artist.songs->results : std::vector<artist::Artist::Song>();
|
||||
videos = m_artist.videos ? m_artist.videos->results : std::vector<artist::Artist::Video>();
|
||||
albums = m_artist.albums ? m_artist.albums->results : std::vector<artist::Artist::Album>();
|
||||
singles = m_artist.singles ? m_artist.singles->results : std::vector<artist::Artist::Single>();
|
||||
songs = m_artist.songs ? m_artist.songs->results : std::vector<artist::Artist::Song>();
|
||||
videos = m_artist.videos ? m_artist.videos->results : std::vector<artist::Artist::Video>();
|
||||
|
||||
// std::span can't know if the data pointer underneath it was changed, so re-create
|
||||
m_view = MultiIterableView(albums, singles, songs, videos);
|
||||
endResetModel();
|
||||
// std::span can't know if the data pointer underneath it was changed, so re-create
|
||||
m_view = MultiIterableView(albums, singles, songs, videos);
|
||||
endResetModel();
|
||||
|
||||
Q_EMIT titleChanged();
|
||||
Q_EMIT thumbnailUrlChanged();
|
||||
});
|
||||
Q_EMIT titleChanged();
|
||||
Q_EMIT thumbnailUrlChanged();
|
||||
});
|
||||
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::errorOccurred, this, [this] {
|
||||
setLoading(false);
|
||||
|
||||
@@ -60,7 +60,7 @@ AsyncYTMusic::AsyncYTMusic(QObject *parent)
|
||||
//
|
||||
// search
|
||||
//
|
||||
QFuture<std::vector<search::SearchResultItem>> AsyncYTMusic::search(const QString &query)
|
||||
QCoro::Task<std::vector<search::SearchResultItem>> AsyncYTMusic::search(const QString &query)
|
||||
{
|
||||
return invokeAndCatchOnThread([=, this]() {
|
||||
return m_ytm->search(query.toStdString());
|
||||
@@ -70,7 +70,7 @@ QFuture<std::vector<search::SearchResultItem>> AsyncYTMusic::search(const QStrin
|
||||
//
|
||||
// fetchArtist
|
||||
//
|
||||
QFuture<artist::Artist> AsyncYTMusic::fetchArtist(const QString &channelId)
|
||||
QCoro::Task<artist::Artist> AsyncYTMusic::fetchArtist(const QString &channelId)
|
||||
{
|
||||
return invokeAndCatchOnThread([=, this]() {
|
||||
return m_ytm->get_artist(channelId.toStdString());
|
||||
@@ -80,7 +80,7 @@ QFuture<artist::Artist> AsyncYTMusic::fetchArtist(const QString &channelId)
|
||||
//
|
||||
// fetchAlbum
|
||||
//
|
||||
QFuture<album::Album> AsyncYTMusic::fetchAlbum(const QString &browseId)
|
||||
QCoro::Task<album::Album> AsyncYTMusic::fetchAlbum(const QString &browseId)
|
||||
{
|
||||
return invokeAndCatchOnThread([=, this]() {
|
||||
return m_ytm->get_album(browseId.toStdString());
|
||||
@@ -90,7 +90,7 @@ QFuture<album::Album> AsyncYTMusic::fetchAlbum(const QString &browseId)
|
||||
//
|
||||
// fetchSong
|
||||
//
|
||||
QFuture<std::optional<song::Song>> AsyncYTMusic::fetchSong(const QString &videoId)
|
||||
QCoro::Task<std::optional<song::Song>> AsyncYTMusic::fetchSong(const QString &videoId)
|
||||
{
|
||||
return invokeAndCatchOnThread([=, this]() {
|
||||
return m_ytm->get_song(videoId.toStdString());
|
||||
@@ -100,7 +100,7 @@ QFuture<std::optional<song::Song>> AsyncYTMusic::fetchSong(const QString &videoI
|
||||
//
|
||||
// fetchPlaylist
|
||||
//
|
||||
QFuture<playlist::Playlist> AsyncYTMusic::fetchPlaylist(const QString &playlistId) {
|
||||
QCoro::Task<playlist::Playlist> AsyncYTMusic::fetchPlaylist(const QString &playlistId) {
|
||||
return invokeAndCatchOnThread([=, this]() {
|
||||
return m_ytm->get_playlist(playlistId.toStdString());
|
||||
});
|
||||
@@ -109,7 +109,7 @@ QFuture<playlist::Playlist> AsyncYTMusic::fetchPlaylist(const QString &playlistI
|
||||
//
|
||||
// fetchArtistAlbum
|
||||
//
|
||||
QFuture<std::vector<artist::Artist::Album>> AsyncYTMusic::fetchArtistAlbums(const QString &channelId, const QString ¶ms)
|
||||
QCoro::Task<std::vector<artist::Artist::Album>> AsyncYTMusic::fetchArtistAlbums(const QString &channelId, const QString ¶ms)
|
||||
{
|
||||
return invokeAndCatchOnThread([=, this]() {
|
||||
return m_ytm->get_artist_albums(channelId.toStdString(), params.toStdString());
|
||||
@@ -119,7 +119,7 @@ QFuture<std::vector<artist::Artist::Album>> AsyncYTMusic::fetchArtistAlbums(cons
|
||||
//
|
||||
// extractVideoInfo
|
||||
//
|
||||
QFuture<video_info::VideoInfo> AsyncYTMusic::extractVideoInfo(const QString &videoId)
|
||||
QCoro::Task<video_info::VideoInfo> AsyncYTMusic::extractVideoInfo(const QString &videoId)
|
||||
{
|
||||
return invokeAndCatchOnThread([=, this]() {
|
||||
return m_ytm->extract_video_info(videoId.toStdString());
|
||||
@@ -129,7 +129,7 @@ QFuture<video_info::VideoInfo> AsyncYTMusic::extractVideoInfo(const QString &vid
|
||||
//
|
||||
// fetchWatchPlaylist
|
||||
//
|
||||
QFuture<watch::Playlist> AsyncYTMusic::fetchWatchPlaylist(const std::optional<QString> &videoId, const std::optional<QString> &playlistId)
|
||||
QCoro::Task<watch::Playlist> AsyncYTMusic::fetchWatchPlaylist(const std::optional<QString> &videoId, const std::optional<QString> &playlistId)
|
||||
{
|
||||
return invokeAndCatchOnThread([=, this]() {
|
||||
return m_ytm->get_watch_playlist(
|
||||
|
||||
+13
-10
@@ -9,6 +9,9 @@
|
||||
#include <QFuture>
|
||||
#include <QFutureWatcher>
|
||||
|
||||
#include <QCoroFuture>
|
||||
#include <QCoroTask>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <ytmusic.h>
|
||||
@@ -100,21 +103,21 @@ class AsyncYTMusic : public QObject
|
||||
|
||||
public:
|
||||
// public functions need to be thread safe
|
||||
QFuture<std::vector<search::SearchResultItem>> search(const QString &query);
|
||||
QCoro::Task<std::vector<search::SearchResultItem>> search(const QString &query);
|
||||
|
||||
QFuture<artist::Artist> fetchArtist(const QString &channelId);
|
||||
QCoro::Task<artist::Artist> fetchArtist(const QString &channelId);
|
||||
|
||||
QFuture<album::Album> fetchAlbum(const QString &browseId);
|
||||
QCoro::Task<album::Album> fetchAlbum(const QString &browseId);
|
||||
|
||||
QFuture<std::optional<song::Song> > fetchSong(const QString &videoId);
|
||||
QCoro::Task<std::optional<song::Song> > fetchSong(const QString &videoId);
|
||||
|
||||
QFuture<playlist::Playlist> fetchPlaylist(const QString &playlistId);
|
||||
QCoro::Task<playlist::Playlist> fetchPlaylist(const QString &playlistId);
|
||||
|
||||
QFuture<std::vector<artist::Artist::Album>> fetchArtistAlbums(const QString &channelId, const QString ¶ms);
|
||||
QCoro::Task<std::vector<artist::Artist::Album>> fetchArtistAlbums(const QString &channelId, const QString ¶ms);
|
||||
|
||||
QFuture<video_info::VideoInfo> extractVideoInfo(const QString &videoId);
|
||||
QCoro::Task<video_info::VideoInfo> extractVideoInfo(const QString &videoId);
|
||||
|
||||
QFuture<watch::Playlist> fetchWatchPlaylist(const std::optional<QString> &videoId = std::nullopt ,
|
||||
QCoro::Task<watch::Playlist> fetchWatchPlaylist(const std::optional<QString> &videoId = std::nullopt ,
|
||||
const std::optional<QString> &playlistId = std::nullopt);
|
||||
|
||||
Q_SIGNAL void errorOccurred(const QString &error);
|
||||
@@ -125,7 +128,7 @@ protected:
|
||||
private:
|
||||
/// Invokes the given function on the thread of the YTMusic object, and handles exceptions that occur while invoking it.
|
||||
template <typename Func>
|
||||
QFuture<std::invoke_result_t<Func>> invokeAndCatchOnThread(Func fun) {
|
||||
QCoro::Task<std::invoke_result_t<Func>> invokeAndCatchOnThread(Func fun) {
|
||||
using ReturnType = std::invoke_result_t<Func>;
|
||||
auto interface = std::make_shared<QFutureInterface<ReturnType>>();
|
||||
QMetaObject::invokeMethod(this, [=, this]() {
|
||||
@@ -138,7 +141,7 @@ private:
|
||||
Q_EMIT errorOccurred(QString::fromLocal8Bit(err.what()));
|
||||
}
|
||||
});
|
||||
return interface->future();
|
||||
co_return co_await interface->future();
|
||||
}
|
||||
|
||||
// Python interpreter will be initialized from the thread calling the methods
|
||||
|
||||
+59
-44
@@ -12,8 +12,18 @@
|
||||
|
||||
#include "asyncdatabase.h"
|
||||
|
||||
#include <QCoroTask>
|
||||
#include <QCoroFuture>
|
||||
#include <QCoroQmlTask>
|
||||
#include <QCoroNetworkReply>
|
||||
|
||||
namespace ranges = std::ranges;
|
||||
|
||||
template <typename T>
|
||||
QCoro::Task<T> toTask(QFuture<T> &&future) {
|
||||
co_return co_await future;
|
||||
}
|
||||
|
||||
Library::Library(QObject *parent)
|
||||
: QObject{parent}
|
||||
, m_database(ThreadedDatabase::establishConnection([]() -> DatabaseConfiguration {
|
||||
@@ -41,19 +51,23 @@ Library &Library::instance()
|
||||
FavouritesModel *Library::favourites()
|
||||
{
|
||||
auto future = m_database->getResults<Song>("select * from favourites natural join songs");
|
||||
return new FavouritesModel(std::move(future), this);
|
||||
return new FavouritesModel(toTask(std::move(future)), this);
|
||||
}
|
||||
|
||||
void Library::addFavourite(const QString &videoId, const QString &title)
|
||||
QCoro::QmlTask Library::addFavourite(const QString &videoId, const QString &title)
|
||||
{
|
||||
connectFuture(addSong(videoId, title), this, [=, this] {
|
||||
connectFuture(m_database->execute("insert or ignore into favourites (video_id) values (?)", videoId), this, &Library::favouritesChanged);
|
||||
});
|
||||
return [=, this]() -> QCoro::Task<> {
|
||||
co_await addSong(videoId, title);
|
||||
co_await m_database->execute("insert or ignore into favourites (video_id) values (?)", videoId);
|
||||
}();
|
||||
}
|
||||
|
||||
void Library::removeFavourite(const QString &videoId)
|
||||
QCoro::QmlTask Library::removeFavourite(const QString &videoId)
|
||||
{
|
||||
connectFuture(m_database->execute("delete from favourites where video_id = ?", videoId), this, &Library::favouritesChanged);
|
||||
return [=, this]() -> QCoro::Task<> {
|
||||
co_await m_database->execute("delete from favourites where video_id = ?", videoId);
|
||||
Library::favouritesChanged();
|
||||
}();
|
||||
}
|
||||
|
||||
FavouriteWatcher *Library::favouriteWatcher(const QString &videoId)
|
||||
@@ -66,7 +80,7 @@ FavouriteWatcher *Library::favouriteWatcher(const QString &videoId)
|
||||
|
||||
SearchHistoryModel *Library::searches()
|
||||
{
|
||||
return new SearchHistoryModel(m_database->getResults<SingleValue<QString>>("select (search_query) from searches order by search_id desc"), this);
|
||||
return new SearchHistoryModel(toTask(m_database->getResults<SingleValue<QString>>("select (search_query) from searches order by search_id desc")), this);
|
||||
}
|
||||
|
||||
void Library::addSearch(const QString &text)
|
||||
@@ -77,22 +91,22 @@ void Library::addSearch(const QString &text)
|
||||
PlaybackHistoryModel *Library::playbackHistory()
|
||||
{
|
||||
auto future = m_database->getResults<PlayedSong>("select * from played_songs natural join songs");
|
||||
return new PlaybackHistoryModel(std::move(future), this);
|
||||
return new PlaybackHistoryModel(toTask(std::move(future)), this);
|
||||
}
|
||||
|
||||
void Library::addPlaybackHistoryItem(const QString &videoId, const QString &title)
|
||||
QCoro::QmlTask Library::addPlaybackHistoryItem(const QString videoId, const QString title)
|
||||
{
|
||||
connectFuture(addSong(videoId, title), this, [=, this] {
|
||||
connectFuture(m_database->execute("insert or ignore into played_songs (video_id, plays) values (?, ?)", videoId, 0), this, [=, this] {
|
||||
connectFuture(m_database->execute("update played_songs set plays = plays + 1 where video_id = ? ", videoId), this, &Library::playbackHistoryChanged);
|
||||
});
|
||||
});
|
||||
return [=, this]() -> QCoro::Task<> {
|
||||
co_await addSong(videoId, title);
|
||||
co_await m_database->execute("insert or ignore into played_songs (video_id, plays) values (?, ?)", videoId, 0);
|
||||
co_await m_database->execute("update played_songs set plays = plays + 1 where video_id = ? ", videoId);
|
||||
}();
|
||||
}
|
||||
|
||||
PlaybackHistoryModel *Library::mostPlayed()
|
||||
{
|
||||
auto future = m_database->getResults<PlayedSong>("select * from played_songs natural join songs order by plays desc limit 10");
|
||||
return new PlaybackHistoryModel(std::move(future), this);
|
||||
return new PlaybackHistoryModel(toTask(std::move(future)), this);
|
||||
}
|
||||
|
||||
QNetworkAccessManager &Library::nam()
|
||||
@@ -100,14 +114,14 @@ QNetworkAccessManager &Library::nam()
|
||||
return m_networkImageCacher;
|
||||
}
|
||||
|
||||
QFuture<void> Library::addSong(const QString &videoId, const QString &title)
|
||||
QCoro::Task<> Library::addSong(const QString videoId, const QString title)
|
||||
{
|
||||
return m_database->execute("insert or ignore into songs (video_id, title, artist, album) values (?, ?, null, null)", videoId, title);
|
||||
co_await m_database->execute("insert or ignore into songs (video_id, title, artist, album) values (?, ?, null, null)", videoId, title);
|
||||
}
|
||||
|
||||
void ThumbnailSource::setVideoId(const QString &id) {
|
||||
QCoro::Task<> ThumbnailSource::setVideoId(const QString &id) {
|
||||
if (m_videoId == id) {
|
||||
return;
|
||||
co_return;
|
||||
}
|
||||
|
||||
m_videoId = id;
|
||||
@@ -119,25 +133,21 @@ void ThumbnailSource::setVideoId(const QString &id) {
|
||||
|
||||
if (QFile::exists(cacheLocation)) {
|
||||
setCachedPath(QUrl::fromLocalFile(cacheLocation));
|
||||
return;
|
||||
co_return;
|
||||
}
|
||||
|
||||
auto future = YTMusicThread::instance()->extractVideoInfo(id);
|
||||
connectFuture(future, this, [this, id, cacheLocation](video_info::VideoInfo &&info) {
|
||||
auto *reply = Library::instance().nam().get(QNetworkRequest(QUrl(QString::fromStdString(info.thumbnail))));
|
||||
connect(reply, &QNetworkReply::finished, this, [id, reply, this, cacheLocation]() {
|
||||
QFile file(cacheLocation);
|
||||
file.open(QFile::WriteOnly);
|
||||
file.write(reply->readAll());
|
||||
setCachedPath(QUrl::fromLocalFile(cacheLocation));
|
||||
});
|
||||
});
|
||||
auto info = co_await YTMusicThread::instance()->extractVideoInfo(id);
|
||||
auto *reply = co_await Library::instance().nam().get(QNetworkRequest(QUrl(QString::fromStdString(info.thumbnail))));
|
||||
QFile file(cacheLocation);
|
||||
file.open(QFile::WriteOnly);
|
||||
file.write(reply->readAll());
|
||||
setCachedPath(QUrl::fromLocalFile(cacheLocation));
|
||||
}
|
||||
|
||||
PlaybackHistoryModel::PlaybackHistoryModel(QFuture<std::vector<PlayedSong> > &&songs, QObject *parent)
|
||||
PlaybackHistoryModel::PlaybackHistoryModel(QCoro::Task<std::vector<PlayedSong> > &&songs, QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
connectFuture(songs, this, [this](const auto songs) {
|
||||
songs.then([this](const auto songs) {
|
||||
beginResetModel();
|
||||
m_playedSongs = songs;
|
||||
endResetModel();
|
||||
@@ -172,10 +182,10 @@ QVariant PlaybackHistoryModel::data(const QModelIndex &index, int role) const {
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
|
||||
FavouritesModel::FavouritesModel(QFuture<std::vector<Song>> &&songs, QObject *parent)
|
||||
FavouritesModel::FavouritesModel(QCoro::Task<std::vector<Song>> &&songs, QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
connectFuture(songs, this, [this](const auto songs) {
|
||||
songs.then([this](const auto songs) {
|
||||
beginResetModel();
|
||||
m_favouriteSongs = songs;
|
||||
endResetModel();
|
||||
@@ -207,16 +217,21 @@ QVariant FavouritesModel::data(const QModelIndex &index, int role) const {
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
QCoro::Task<T> toTask(QFuture<T> &future) {
|
||||
auto result = co_await future;
|
||||
co_return result;
|
||||
}
|
||||
|
||||
FavouriteWatcher::FavouriteWatcher(Library *library, const QString &videoId)
|
||||
: QObject(library), m_videoId(videoId), m_library(library)
|
||||
{
|
||||
auto update = [this] {
|
||||
connectFuture(m_library->database().getResult<SingleValue<bool>>("select count(*) > 0 from favourites where video_id = ?", m_videoId), this, [this](auto count) {
|
||||
if (count) {
|
||||
m_isFavourite = count->value;
|
||||
Q_EMIT isFavouriteChanged();
|
||||
}
|
||||
});
|
||||
auto update = [this]() -> QCoro::Task<> {
|
||||
auto count = co_await toTask(m_library->database().getResult<SingleValue<bool>>("select count(*) > 0 from favourites where video_id = ?", m_videoId)).withContext(this);
|
||||
if (count) {
|
||||
m_isFavourite = count->value;
|
||||
Q_EMIT isFavouriteChanged();
|
||||
}
|
||||
};
|
||||
update();
|
||||
connect(library, &Library::favouritesChanged, this, update);
|
||||
@@ -226,10 +241,10 @@ bool FavouriteWatcher::isFavourite() const {
|
||||
return m_isFavourite;
|
||||
}
|
||||
|
||||
SearchHistoryModel::SearchHistoryModel(QFuture<std::vector<SingleValue<QString>>> &&historyFuture, QObject *parent)
|
||||
SearchHistoryModel::SearchHistoryModel(QCoro::Task<std::vector<SingleValue<QString>>> &&historyFuture, QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
connectFuture(historyFuture, this, [this](const auto history) {
|
||||
historyFuture.then([this](const auto history) {
|
||||
beginResetModel();
|
||||
m_history = history;
|
||||
endResetModel();
|
||||
|
||||
+10
-8
@@ -9,6 +9,8 @@
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QAbstractListModel>
|
||||
|
||||
#include <QCoroQmlTask>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "asyncytmusic.h"
|
||||
@@ -40,7 +42,7 @@ class FavouritesModel : public QAbstractListModel {
|
||||
};
|
||||
|
||||
public:
|
||||
FavouritesModel(QFuture<std::vector<Song>> &&songs, QObject *parent = nullptr);
|
||||
FavouritesModel(QCoro::Task<std::vector<Song>> &&songs, QObject *parent = nullptr);
|
||||
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
int rowCount(const QModelIndex &parent) const override;
|
||||
@@ -76,7 +78,7 @@ class PlaybackHistoryModel : public QAbstractListModel {
|
||||
};
|
||||
|
||||
public:
|
||||
PlaybackHistoryModel(QFuture<std::vector<PlayedSong>> &&songs, QObject *parent = nullptr);
|
||||
PlaybackHistoryModel(QCoro::Task<std::vector<PlayedSong>> &&songs, QObject *parent = nullptr);
|
||||
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
int rowCount(const QModelIndex &parent) const override;
|
||||
@@ -90,7 +92,7 @@ class SearchHistoryModel : public QAbstractListModel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SearchHistoryModel(QFuture<std::vector<SingleValue<QString>>> &&historyFuture, QObject *parent = nullptr);
|
||||
SearchHistoryModel(QCoro::Task<std::vector<SingleValue<QString>>> &&historyFuture, QObject *parent = nullptr);
|
||||
int rowCount(const QModelIndex &parent) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
|
||||
@@ -114,8 +116,8 @@ public:
|
||||
|
||||
FavouritesModel *favourites();
|
||||
Q_SIGNAL void favouritesChanged();
|
||||
Q_INVOKABLE void addFavourite(const QString &videoId, const QString &title);
|
||||
Q_INVOKABLE void removeFavourite(const QString &videoId);
|
||||
Q_INVOKABLE QCoro::QmlTask addFavourite(const QString &videoId, const QString &title);
|
||||
Q_INVOKABLE QCoro::QmlTask removeFavourite(const QString &videoId);
|
||||
Q_INVOKABLE FavouriteWatcher *favouriteWatcher(const QString &videoId);
|
||||
|
||||
SearchHistoryModel *searches();
|
||||
@@ -124,7 +126,7 @@ public:
|
||||
|
||||
PlaybackHistoryModel *playbackHistory();
|
||||
Q_SIGNAL void playbackHistoryChanged();
|
||||
Q_INVOKABLE void addPlaybackHistoryItem(const QString &videoId, const QString &title);
|
||||
Q_INVOKABLE QCoro::QmlTask addPlaybackHistoryItem(const QString videoId, const QString title);
|
||||
|
||||
PlaybackHistoryModel *mostPlayed();
|
||||
|
||||
@@ -134,7 +136,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
QFuture<void> addSong(const QString &videoId, const QString &title);
|
||||
QCoro::Task<> addSong(const QString videoId, const QString title);
|
||||
|
||||
QNetworkAccessManager m_networkImageCacher;
|
||||
std::unique_ptr<ThreadedDatabase> m_database;
|
||||
@@ -150,7 +152,7 @@ class ThumbnailSource : public QObject {
|
||||
QString videoId() const {
|
||||
return m_videoId;
|
||||
}
|
||||
void setVideoId(const QString &id);
|
||||
QCoro::Task<> setVideoId(const QString &id);
|
||||
Q_SIGNAL void videoIdChanged();
|
||||
|
||||
QUrl cachedPath() const {
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
#include <KCrash>
|
||||
#include <KAboutData>
|
||||
|
||||
#include <QCoroQml>
|
||||
|
||||
#include "searchmodel.h"
|
||||
#include "albummodel.h"
|
||||
#include "videoinfoextractor.h"
|
||||
@@ -38,6 +40,8 @@ Q_DECL_EXPORT int main(int argc, char *argv[])
|
||||
KAboutData::setApplicationData(KAboutData(QStringLiteral("audiotube"), QStringLiteral("AudioTube")));
|
||||
KCrash::initialize();
|
||||
|
||||
QCoro::Qml::registerTypes();
|
||||
|
||||
QQmlApplicationEngine engine;
|
||||
|
||||
qmlRegisterType<SearchModel>(URI, 1, 0, "SearchModel");
|
||||
|
||||
@@ -16,7 +16,7 @@ PlaylistModel::PlaylistModel(QObject *parent)
|
||||
connect(this, &PlaylistModel::playlistIdChanged, this, [=, this] {
|
||||
setLoading(true);
|
||||
auto future = YTMusicThread::instance()->fetchPlaylist(m_playlistId);
|
||||
connectFuture(future, this, [=, this](const playlist::Playlist &playlist) {
|
||||
future.then(this, [=, this](const playlist::Playlist &playlist) {
|
||||
setLoading(false);
|
||||
beginResetModel();
|
||||
m_playlist = playlist;
|
||||
|
||||
+10
-9
@@ -4,25 +4,26 @@
|
||||
|
||||
#include "searchmodel.h"
|
||||
|
||||
#include <QCoroFuture>
|
||||
#include <QCoroTask>
|
||||
|
||||
SearchModel::SearchModel(QObject *parent)
|
||||
: AbstractYTMusicModel(parent)
|
||||
{
|
||||
connect(this, &SearchModel::searchQueryChanged, this, [this] {
|
||||
connect(this, &SearchModel::searchQueryChanged, this, [this]() -> QCoro::Task<> {
|
||||
if (m_searchQuery.isEmpty()) {
|
||||
beginResetModel();
|
||||
m_searchResults.clear();
|
||||
endResetModel();
|
||||
return;
|
||||
co_return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
auto future = YTMusicThread::instance()->search(m_searchQuery);
|
||||
connectFuture(future, this, [=, this](const std::vector<search::SearchResultItem> &results) {
|
||||
beginResetModel();
|
||||
setLoading(false);
|
||||
m_searchResults = results;
|
||||
endResetModel();
|
||||
});
|
||||
auto results = co_await YTMusicThread::instance()->search(m_searchQuery);
|
||||
beginResetModel();
|
||||
setLoading(false);
|
||||
m_searchResults = results;
|
||||
endResetModel();
|
||||
});
|
||||
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::errorOccurred, this, [this] {
|
||||
setLoading(false);
|
||||
|
||||
@@ -41,7 +41,7 @@ UserPlaylistModel::UserPlaylistModel(QObject *parent)
|
||||
|
||||
setLoading(true);
|
||||
auto future = YTMusicThread::instance()->fetchWatchPlaylist(m_initialVideoId);
|
||||
connectFuture(future, this, handleResult);
|
||||
future.then(this, handleResult);
|
||||
});
|
||||
connect(this, &UserPlaylistModel::playlistIdChanged, this, [=, this] {
|
||||
if (m_playlistId.isEmpty()) {
|
||||
@@ -50,7 +50,7 @@ UserPlaylistModel::UserPlaylistModel(QObject *parent)
|
||||
|
||||
setLoading(true);
|
||||
auto future = YTMusicThread::instance()->fetchWatchPlaylist(std::nullopt, m_playlistId);
|
||||
connectFuture(future, this, handleResult);;
|
||||
future.then(this, handleResult);
|
||||
});
|
||||
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::errorOccurred, this, [this] {
|
||||
setLoading(false);
|
||||
|
||||
@@ -27,7 +27,7 @@ VideoInfoExtractor::VideoInfoExtractor(QObject *parent)
|
||||
setLoading(true);
|
||||
|
||||
auto future = YTMusicThread::instance()->extractVideoInfo(QString::fromStdString(m_videoId.toStdString()));
|
||||
connectFuture(future, this, [this](const video_info::VideoInfo &videoInfo) {
|
||||
future.then(this, [this](const video_info::VideoInfo &videoInfo) {
|
||||
m_videoInfo = videoInfo;
|
||||
setLoading(false);
|
||||
Q_EMIT audioUrlChanged();
|
||||
|
||||
Reference in new issue
Block a user