22#include <QAbstractListModel>
29#include <QFutureWatcher>
31#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
32#include <QtConcurrent>
34#include <QtConcurrent/QtConcurrentRun>
37#include "../../lib/Movie.h"
38#include "../../lib/top100.h"
39#include "../../lib/config.h"
40#include "../../lib/posting.h"
41#include "../../lib/omdb.h"
49 TitleRole = Qt::UserRole + 1,
64 : QAbstractListModel(parent) {
71 currentOrder_ = SortOrder::DEFAULT;
76 int rowCount(
const QModelIndex& parent = QModelIndex())
const override {
77 if (parent.isValid())
return 0;
78 return static_cast<int>(movies_.size());
81 QVariant data(
const QModelIndex& index,
int role)
const override {
82 if (!index.isValid() || index.row() < 0 || index.row() >=
static_cast<int>(movies_.size()))
84 const auto& m = movies_[index.row()];
86 case Qt::DisplayRole: {
87 QString prefix = (m.userRank > 0) ? QString(
"#%1 ").arg(m.userRank) : QString();
88 return prefix + QString::fromStdString(m.title) + QString(
" (%1)").arg(m.year);
90 case TitleRole:
return QString::fromStdString(m.title);
91 case YearRole:
return m.year;
92 case RankRole:
return m.userRank;
93 case PosterUrlRole:
return QString::fromStdString(m.posterUrl);
94 case PlotFullRole:
return QString::fromStdString(m.plotFull);
95 case ImdbIdRole:
return QString::fromStdString(m.imdbID);
96 case DirectorRole:
return QString::fromStdString(m.director);
99 for (
const auto& a : m.actors) list << QString::fromStdString(a);
104 for (
const auto& g : m.genres) list << QString::fromStdString(g);
107 case RuntimeMinutesRole:
return m.runtimeMinutes;
112 QHash<int, QByteArray> roleNames()
const override {
113 QHash<int, QByteArray> r;
114 r[Qt::DisplayRole] =
"display";
115 r[TitleRole] =
"title";
116 r[YearRole] =
"year";
117 r[RankRole] =
"rank";
118 r[PosterUrlRole] =
"posterUrl";
119 r[PlotFullRole] =
"plotFull";
120 r[ImdbIdRole] =
"imdbID";
121 r[DirectorRole] =
"director";
122 r[ActorsRole] =
"actors";
123 r[GenresRole] =
"genres";
124 r[RuntimeMinutesRole] =
"runtimeMinutes";
136 auto movies = list.
getMovies(currentOrder_);
137 movies_.assign(movies.begin(), movies.end());
142 qInfo() <<
"Top100ListModel: loaded" << movies_.size() <<
"movies";
147 int sortOrder()
const {
return static_cast<int>(currentOrder_); }
153 case static_cast<int>(SortOrder::DEFAULT): newOrder = SortOrder::DEFAULT;
break;
154 case static_cast<int>(SortOrder::BY_YEAR): newOrder = SortOrder::BY_YEAR;
break;
155 case static_cast<int>(SortOrder::ALPHABETICAL): newOrder = SortOrder::ALPHABETICAL;
break;
156 case static_cast<int>(SortOrder::BY_USER_RANK): newOrder = SortOrder::BY_USER_RANK;
break;
157 case static_cast<int>(SortOrder::BY_USER_SCORE): newOrder = SortOrder::BY_USER_SCORE;
break;
158 default: newOrder = SortOrder::DEFAULT;
break;
160 if (newOrder == currentOrder_)
return;
161 currentOrder_ = newOrder;
174 Q_INVOKABLE QVariantMap
get(
int row)
const {
176 if (row < 0 || row >=
static_cast<int>(movies_.size()))
return m;
177 const auto& mv = movies_[row];
178 m[
"title"] = QString::fromStdString(mv.title);
180 m[
"rank"] = mv.userRank;
181 m[
"posterUrl"] = QString::fromStdString(mv.posterUrl);
182 m[
"plotFull"] = QString::fromStdString(mv.plotFull.empty() ? mv.plotShort : mv.plotFull);
183 m[
"imdbID"] = QString::fromStdString(mv.imdbID);
184 m[
"director"] = QString::fromStdString(mv.director);
187 for (
const auto& a : mv.actors) actors << QString::fromStdString(a);
188 m[
"actors"] = actors;
192 for (
const auto& g : mv.genres) genres << QString::fromStdString(g);
193 m[
"genres"] = genres;
195 m[
"runtimeMinutes"] = mv.runtimeMinutes;
205 if (!maybe)
return false;
212 }
catch (...) {
return false; }
223 if (!removed)
return false;
225 }
catch (...) {
return false; }
236 }
catch (...) {
return false; }
248 for (
const auto& r : results) {
250 m[
"title"] = QString::fromStdString(r.title);
252 m[
"imdbID"] = QString::fromStdString(r.imdbID);
269 if (!maybe)
return m;
270 const Movie& mv = *maybe;
271 m[
"title"] = QString::fromStdString(mv.title);
273 m[
"posterUrl"] = QString::fromStdString(mv.posterUrl);
274 m[
"plotShort"] = QString::fromStdString(mv.plotShort);
275 m[
"plotFull"] = QString::fromStdString(mv.plotFull);
287 if (!maybe)
return false;
290 if (!ok)
return false;
291 }
catch (...) {
return false; }
293 QString imdb = imdbId;
294 QMetaObject::Connection conn;
296 for (
int i = 0; i < rowCount(); ++i) {
306 Q_INVOKABLE
int count()
const {
return rowCount(); }
323 if (row < 0 || row >=
static_cast<int>(movies_.size()))
return false;
327 const Movie& m = movies_[row];
328 return postMovieToBlueSky(cfg, m);
336 if (row < 0 || row >=
static_cast<int>(movies_.size()))
return false;
340 const Movie& m = movies_[row];
341 return postMovieToMastodon(cfg, m);
349 if (row < 0 || row >=
static_cast<int>(movies_.size()))
return;
353 Movie mv = movies_[row];
354 auto future = QtConcurrent::run([cfg, mv]() {
return postMovieToBlueSky(cfg, mv); });
355 auto *watcher =
new QFutureWatcher<bool>(
this);
356 connect(watcher, &QFutureWatcher<bool>::finished,
this, [
this, watcher, row]() {
357 bool ok = watcher->result();
359 watcher->deleteLater();
361 watcher->setFuture(future);
366 if (row < 0 || row >=
static_cast<int>(movies_.size()))
return;
370 Movie mv = movies_[row];
371 auto future = QtConcurrent::run([cfg, mv]() {
return postMovieToMastodon(cfg, mv); });
372 auto *watcher =
new QFutureWatcher<bool>(
this);
373 connect(watcher, &QFutureWatcher<bool>::finished,
this, [
this, watcher, row]() {
374 bool ok = watcher->result();
376 watcher->deleteLater();
378 watcher->setFuture(future);
392 std::vector<Movie> movies_;
393 SortOrder currentOrder_ = SortOrder::DEFAULT;
Definition Top100ListModel.h:43
Roles
Definition Top100ListModel.h:48
Q_INVOKABLE QVariantMap get(int row) const
Definition Top100ListModel.h:174
Q_INVOKABLE bool deleteByImdbId(const QString &imdbId)
Definition Top100ListModel.h:218
void requestSelectRow(int row)
Q_INVOKABLE QVariantList searchOmdb(const QString &query)
Definition Top100ListModel.h:242
Q_INVOKABLE bool addMovieByImdbId(const QString &imdbId)
Definition Top100ListModel.h:200
Q_INVOKABLE bool postToMastodon(int row)
Definition Top100ListModel.h:335
Q_INVOKABLE bool updateFromOmdbByImdbId(const QString &imdbId)
Definition Top100ListModel.h:282
Q_INVOKABLE QVariantMap omdbGetByIdMap(const QString &imdbId) const
Definition Top100ListModel.h:263
Q_INVOKABLE bool deleteByTitle(const QString &title)
Definition Top100ListModel.h:231
Q_INVOKABLE bool recordPairwiseResult(int leftRow, int rightRow, int winner)
Record a pairwise ranking result between two rows in the current model view.
Definition Top100ListModel.cpp:27
Q_INVOKABLE void reload()
Definition Top100ListModel.h:129
Q_INVOKABLE void setSortOrder(int order)
Definition Top100ListModel.h:150
void sortOrderChanged(int sortOrder)
void postingFinished(const QString &service, int row, bool success)
Q_INVOKABLE void postToBlueSkyAsync(int row)
Definition Top100ListModel.h:348
Q_INVOKABLE void postToMastodonAsync(int row)
Definition Top100ListModel.h:365
Q_INVOKABLE int count() const
QML-friendly row count accessor.
Definition Top100ListModel.h:306
Q_INVOKABLE bool postToBlueSky(int row)
Definition Top100ListModel.h:322
Persistent container for up to 100 movies, with ranking.
Definition top100.h:41
std::vector< Movie > getMovies(SortOrder order=SortOrder::DEFAULT) const
Return a copy of movies in the requested sort order.
Definition top100.cpp:44
void removeMovie(const std::string &title)
Remove by title (first match). No-op if not found.
Definition top100.cpp:30
void addMovie(const Movie &movie)
Add a movie; replaces existing title+year duplicates.
Definition top100.cpp:26
bool mergeFromOmdbByImdbId(const Movie &omdbMovie)
Merge updated metadata into an existing movie by IMDb ID. Copies all metadata fields from the provide...
Definition top100.cpp:149
void recomputeRanks()
Recompute 1-based userRank from userScore descending.
Definition top100.cpp:132
bool removeByImdbId(const std::string &imdbID)
Remove by IMDb ID (preferred precise delete).
Definition top100.cpp:36
AppConfig loadConfig()
Load configuration from disk, creating defaults if missing.
Definition config.cpp:81
void saveConfig(const AppConfig &cfg)
Persist configuration to disk.
Definition config.cpp:111
SortOrder
Sort orders for listing movies.
Definition top100.h:25
std::optional< Movie > omdbGetById(const std::string &apiKey, const std::string &imdbID)
Definition omdb.cpp:69
std::vector< OmdbSearchResult > omdbSearch(const std::string &apiKey, const std::string &query)
Definition omdb.cpp:51
Persistent application configuration stored in a single JSON file.
Definition config.h:39
bool mastodonEnabled
Whether Mastodon posting is enabled.
Definition config.h:55
int uiSortOrder
Persisted sort order (matches SortOrder enum values)
Definition config.h:60
std::string omdbApiKey
OMDb API key (empty if not configured)
Definition config.h:42
std::string blueSkyAppPassword
App password (keep private)
Definition config.h:47
std::string dataFile
Absolute path to your movie data JSON.
Definition config.h:40
std::string mastodonAccessToken
User access token (keep private)
Definition config.h:57
bool blueSkyEnabled
Whether BlueSky posting is enabled.
Definition config.h:45
std::string blueSkyIdentifier
Handle or email used to login.
Definition config.h:46
std::string mastodonInstance
Instance base URL (e.g., https://mastodon.social)
Definition config.h:56
bool omdbEnabled
Whether OMDb features are enabled in the UI.
Definition config.h:41
Movie domain model and metadata.
Definition Movie.h:27