mirror of
https://github.com/steredbits/pelagia.git
synced 2026-10-23 07:01:50 +02:00
193 lines
6.2 KiB
C++
193 lines
6.2 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
// Copyright (C) 2026 steredbits and Pelagia contributors
|
|
#include "ui/jellyfin_backend.h"
|
|
|
|
#include "ui/format.h"
|
|
#include "ui/jellyfin_playback.h"
|
|
#include "util/log.h"
|
|
|
|
namespace ui {
|
|
|
|
namespace {
|
|
|
|
constexpr int kResumeLimit = 12;
|
|
constexpr int kLatestLimit = 16;
|
|
|
|
api::ItemsQuery library_query(const api::Library& library) {
|
|
api::ItemsQuery q;
|
|
q.parent_id = library.id;
|
|
q.recursive = true;
|
|
q.include_item_types = item_types_for(library);
|
|
// Individual movies rather than collections (BoxSets are not playable,
|
|
// seen on a real server).
|
|
q.collapse_box_set_items = false;
|
|
return q;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
JellyfinBackend::JellyfinBackend(std::string device_id, api::StreamAuthMode stream_auth)
|
|
: device_id_(std::move(device_id)), stream_auth_(stream_auth) {
|
|
transport_.set_timeouts(5, 15);
|
|
}
|
|
|
|
std::shared_ptr<api::JellyfinClient> JellyfinBackend::client() const {
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
return client_;
|
|
}
|
|
|
|
api::ApiResult JellyfinBackend::not_connected() const {
|
|
api::ApiResult r;
|
|
r.error = api::ApiError::NotAuthenticated;
|
|
r.message = "no open session";
|
|
return r;
|
|
}
|
|
|
|
api::ApiResult JellyfinBackend::login(const std::string& server, const std::string& user,
|
|
const std::string& password, SavedSession* out) {
|
|
// New authenticated client outside the lock, published only on success.
|
|
std::shared_ptr<api::JellyfinClient> fresh = std::make_shared<api::JellyfinClient>(
|
|
transport_, complete_server_address(server), device_id_);
|
|
const api::ApiResult result = fresh->authenticate(user, password);
|
|
if (!result.ok()) {
|
|
return result;
|
|
}
|
|
out->server = fresh->server_url();
|
|
out->token = fresh->session().access_token;
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
client_ = fresh;
|
|
return result;
|
|
}
|
|
|
|
api::ApiResult JellyfinBackend::restore(const SavedSession& session) {
|
|
std::shared_ptr<api::JellyfinClient> fresh = std::make_shared<api::JellyfinClient>(
|
|
transport_, complete_server_address(session.server), device_id_);
|
|
const api::ApiResult result = fresh->restore_session(session.token);
|
|
if (result.ok()) {
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
client_ = fresh;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
api::ApiResult JellyfinBackend::logout() {
|
|
std::shared_ptr<api::JellyfinClient> old;
|
|
{
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
old.swap(client_);
|
|
}
|
|
if (!old) {
|
|
return api::ApiResult{};
|
|
}
|
|
// Copy: other threads may still read the session of the old
|
|
// client (poster downloads in progress).
|
|
api::JellyfinClient copy = *old;
|
|
const api::ApiResult result = copy.logout();
|
|
if (!result.ok()) {
|
|
LOG_WARN("Token revocation failed (%s): local session closed anyway",
|
|
result.message.c_str());
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::string JellyfinBackend::user_name() const {
|
|
std::shared_ptr<api::JellyfinClient> c = client();
|
|
return c ? c->session().user_name : "";
|
|
}
|
|
|
|
api::ApiResult JellyfinBackend::libraries(std::vector<api::Library>* out) {
|
|
std::shared_ptr<api::JellyfinClient> c = client();
|
|
if (!c) {
|
|
return not_connected();
|
|
}
|
|
std::vector<api::Library> all;
|
|
const api::ApiResult result = c->fetch_libraries(&all);
|
|
if (result.ok()) {
|
|
*out = supported_libraries(all);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
api::ApiResult JellyfinBackend::resume(api::ItemList* out) {
|
|
std::shared_ptr<api::JellyfinClient> c = client();
|
|
return c ? c->fetch_resume(kResumeLimit, out) : not_connected();
|
|
}
|
|
|
|
api::ApiResult JellyfinBackend::latest(const api::Library& library, api::ItemList* out) {
|
|
std::shared_ptr<api::JellyfinClient> c = client();
|
|
if (!c) {
|
|
return not_connected();
|
|
}
|
|
api::ItemsQuery q = library_query(library);
|
|
q.sort_by = "DateCreated";
|
|
q.sort_descending = true;
|
|
q.limit = kLatestLimit;
|
|
return c->fetch_items(q, out);
|
|
}
|
|
|
|
api::ApiResult JellyfinBackend::library_items(const api::Library& library,
|
|
api::ItemList* out) {
|
|
std::shared_ptr<api::JellyfinClient> c = client();
|
|
if (!c) {
|
|
return not_connected();
|
|
}
|
|
const api::ApiResult result = c->fetch_items(library_query(library), out);
|
|
if (result.ok()) {
|
|
sort_for_display(&out->items);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
api::ApiResult JellyfinBackend::seasons(const std::string& series_id, api::ItemList* out) {
|
|
std::shared_ptr<api::JellyfinClient> c = client();
|
|
return c ? c->fetch_seasons(series_id, out) : not_connected();
|
|
}
|
|
|
|
api::ApiResult JellyfinBackend::episodes(const std::string& series_id,
|
|
const std::string& season_id, api::ItemList* out) {
|
|
std::shared_ptr<api::JellyfinClient> c = client();
|
|
return c ? c->fetch_episodes(series_id, season_id, out) : not_connected();
|
|
}
|
|
|
|
api::ApiResult JellyfinBackend::item(const std::string& id, api::MediaItem* out) {
|
|
std::shared_ptr<api::JellyfinClient> c = client();
|
|
return c ? c->fetch_item(id, out) : not_connected();
|
|
}
|
|
|
|
api::ApiResult JellyfinBackend::subtitle_text(const std::string& item_id,
|
|
const std::string& media_source_id, int stream_index,
|
|
std::string* srt) {
|
|
std::shared_ptr<api::JellyfinClient> c = client();
|
|
return c ? c->fetch_subtitle(item_id, media_source_id, stream_index, srt) : not_connected();
|
|
}
|
|
|
|
bool JellyfinBackend::fetch(const PosterKey& key, std::string* bytes) {
|
|
std::shared_ptr<api::JellyfinClient> c = client();
|
|
if (!c) {
|
|
return false;
|
|
}
|
|
const api::ApiResult result = c->fetch_image(key.item_id, key.tag, key.width, bytes);
|
|
if (!result.ok()) {
|
|
LOG_DEBUG("Poster %s: %s (HTTP %ld)", key.item_id.c_str(), result.message.c_str(),
|
|
result.http_status);
|
|
}
|
|
return result.ok();
|
|
}
|
|
|
|
api::UserPreferences JellyfinBackend::preferences() const {
|
|
std::shared_ptr<api::JellyfinClient> c = client();
|
|
return c ? c->session().preferences : api::UserPreferences();
|
|
}
|
|
|
|
std::unique_ptr<PlaybackMedia> JellyfinBackend::create_playback(
|
|
const api::MediaItem& item, int64_t start_ms, const api::TrackSelection& tracks) {
|
|
std::shared_ptr<api::JellyfinClient> c = client();
|
|
if (!c) {
|
|
return nullptr;
|
|
}
|
|
return std::unique_ptr<PlaybackMedia>(
|
|
new JellyfinPlaybackMedia(c, item, start_ms, stream_auth_, tracks));
|
|
}
|
|
|
|
} // namespace ui
|