pub struct OfflineRepository {
db_service: Arc<RusqliteService>,
server_id: String,
user_id: String,
}Fields§
§db_service: Arc<RusqliteService>§server_id: String§user_id: StringImplementations§
Source§impl OfflineRepository
impl OfflineRepository
pub fn new( db_service: Arc<RusqliteService>, server_id: String, user_id: String, ) -> Self
Sourcefn cached_item_to_media_item(
item: CachedItem,
user_data: Option<UserData>,
) -> MediaItem
fn cached_item_to_media_item( item: CachedItem, user_data: Option<UserData>, ) -> MediaItem
Helper to convert CachedItem from storage to MediaItem
Sourceasync fn get_user_data(&self, item_id: &str) -> Option<UserData>
async fn get_user_data(&self, item_id: &str) -> Option<UserData>
Get user data for an item (playback position, favorite, etc.)
Source§impl OfflineRepository
impl OfflineRepository
Sourceconst LIBRARY_HOLDS_ITEM: &'static str = "(
(l.collection_type = 'music' AND i.item_type IN ('MusicAlbum', 'MusicArtist', 'Audio'))
OR (l.collection_type = 'movies' AND i.item_type = 'Movie')
OR (l.collection_type = 'tvshows' AND i.item_type IN ('Series', 'Season', 'Episode'))
OR l.collection_type IS NULL
OR l.collection_type NOT IN ('music', 'movies', 'tvshows')
)"
const LIBRARY_HOLDS_ITEM: &'static str = "( (l.collection_type = 'music' AND i.item_type IN ('MusicAlbum', 'MusicArtist', 'Audio')) OR (l.collection_type = 'movies' AND i.item_type = 'Movie') OR (l.collection_type = 'tvshows' AND i.item_type IN ('Series', 'Season', 'Episode')) OR l.collection_type IS NULL OR l.collection_type NOT IN ('music', 'movies', 'tvshows') )"
SQL fragment: the set of item ids that are “on the device” — playable
items with a completed download, plus containers (album/series/season)
that have at least one downloaded child. This is the get_items CTE with
the synced-but-not-downloaded catalog branch deliberately excluded, so it
is authoritative regardless of the process-wide catalog-browse flag.
Whether cached item i belongs to library l, decided by media kind.
The cache leaves library_id/parent_id NULL on every item
([[offline-libraries-never-cached]]), so there is no link to follow: a
library’s collection_type and an item’s item_type are the only things
that can associate them. This is Jellyfin taxonomy and therefore lives in
Rust, never in the frontend.
It is a named constant because it is needed in two places that must agree — which library appears in the Downloaded list, and which items appear inside it. They disagreed: the listing query used this mapping while the browse query only checked that the requested library existed, so opening any library showed every downloaded top-level item on the server.
A library of some other (or unknown) type keeps everything, since there is no mapping to narrow it by and hiding its contents would be worse.
TRACES: UR-055 | DR-082, DR-167
Sourceconst DOWNLOADED_ITEMS_CTE: &'static str = "
WITH downloaded_items AS (
SELECT DISTINCT i.id
FROM items i
INNER JOIN downloads d ON i.id = d.item_id
WHERE d.status = 'completed'
AND i.item_type IN ('Audio', 'Movie', 'Episode')
UNION
SELECT DISTINCT i.id
FROM items i
INNER JOIN items children ON (children.parent_id = i.id OR children.album_id = i.id OR children.season_id = i.id OR children.series_id = i.id)
INNER JOIN downloads d ON children.id = d.item_id
WHERE d.status = 'completed'
AND i.item_type IN ('MusicAlbum', 'Series', 'Season', 'BoxSet', 'Folder', 'CollectionFolder')
)"
const DOWNLOADED_ITEMS_CTE: &'static str = " WITH downloaded_items AS ( SELECT DISTINCT i.id FROM items i INNER JOIN downloads d ON i.id = d.item_id WHERE d.status = 'completed' AND i.item_type IN ('Audio', 'Movie', 'Episode') UNION SELECT DISTINCT i.id FROM items i INNER JOIN items children ON (children.parent_id = i.id OR children.album_id = i.id OR children.season_id = i.id OR children.series_id = i.id) INNER JOIN downloads d ON children.id = d.item_id WHERE d.status = 'completed' AND i.item_type IN ('MusicAlbum', 'Series', 'Season', 'BoxSet', 'Folder', 'CollectionFolder') )"
TRACES: UR-055 | DR-082, DR-083
Sourcepub async fn prune_stale_catalog(
&self,
cutoff: &str,
item_types: &[String],
) -> Result<usize, RepoError>
pub async fn prune_stale_catalog( &self, cutoff: &str, item_types: &[String], ) -> Result<usize, RepoError>
Remove catalog entries the server no longer has (mark-and-sweep).
save_to_cache stamps every row it writes with a fresh synced_at, so
after a complete crawl anything still on the server carries a timestamp
newer than cutoff (taken before the crawl began) and anything deleted
server-side kept its older one. Sweeping by timestamp avoids binding the
crawl’s entire id set, which would blow past SQLite’s variable limit on a
large library.
Three exclusions, each load-bearing:
- Only
item_typesthe crawl actually requested. The crawl asks forCATALOG_ITEM_TYPES; rows of any other type (artists, playlists, theFolderparent stubssave_to_cacheinserts) are never refreshed by it, so sweeping by age alone would delete every one of them. - Anything a completed download depends on — the downloaded item itself, and any container with a downloaded child. The user has those bytes on disk; dropping the row would orphan the file.
- Callers must only invoke this after a crawl in which every library
succeeded.
sync_full_catalogis best-effort per library, anditems.parent_idisON DELETE CASCADE, so sweeping after a partial crawl could cascade an entire series away because one request timed out.
Returns the number of rows removed.
TRACES: UR-065 | DR-110 | UT-113
Sourceasync fn search_people(
&self,
fts_query: &str,
limit: usize,
) -> Result<Vec<MediaItem>, RepoError>
async fn search_people( &self, fts_query: &str, limit: usize, ) -> Result<Vec<MediaItem>, RepoError>
Full-text search over cached people (cast and crew).
People are stored in their own table rather than in items, so search
has to look them up separately and adapt them to MediaItem. Availability
gating deliberately does not apply: a person is metadata, never a
download, so there is nothing to be offline about.
TRACES: UR-065, UR-060 | DR-111 | UT-114
Sourcepub async fn save_to_cache(
&self,
parent_id: &str,
items: &[MediaItem],
) -> Result<usize, RepoError>
pub async fn save_to_cache( &self, parent_id: &str, items: &[MediaItem], ) -> Result<usize, RepoError>
Save browsed items to cache for faster subsequent loading
This persists metadata for items that were browsed (not necessarily downloaded). Items are marked with current timestamp for freshness tracking.
async fn save_to_cache_impl( &self, parent_id: &str, items: &[MediaItem], now: &str, ) -> Result<usize, RepoError>
Sourceasync fn mirror_user_data(
&self,
item: &MediaItem,
now: &str,
) -> Result<(), RepoError>
async fn mirror_user_data( &self, item: &MediaItem, now: &str, ) -> Result<(), RepoError>
Mirror the server’s per-user state for an item into the local
user_data table, so favourites marked — and positions watched — on any
other client are visible here, including offline, where the local table
is the only source.
The WHERE user_data.pending_sync = 0 on the conflict clause is the
conflict rule: a change made while the server was unreachable is still
waiting to be pushed, and must not be clobbered by the stale value the
server is still reporting. For a position that means it is never pulled
backwards by a server that has not yet heard where we got to.
Each field is mirrored only when the server actually reported it —
COALESCE(excluded.x, user_data.x) keeps the stored value for anything
absent, and a row with neither field is skipped outright rather than
written as zeroes, which would fabricate an “unfavourited, unwatched”
record from an endpoint that simply omits UserData.
The position half is what makes cross-device resume work: the resume check reads this table alone, so before it was mirrored an item watched elsewhere resumed from whatever this device last saw, or not at all.
TRACES: UR-025, UR-069 | DR-114, DR-155 | UT-102, UT-152
Sourcepub async fn save_libraries_to_cache(
&self,
libraries: &[Library],
) -> Result<usize, RepoError>
pub async fn save_libraries_to_cache( &self, libraries: &[Library], ) -> Result<usize, RepoError>
Cache the library (view) list from the server into the local database.
Called by HybridRepository after a successful online fetch so the list is
available offline. Without this, the libraries table stays empty and
offline startup shows no libraries at all.
Sourcepub async fn save_genres_to_cache(
&self,
parent_id: Option<&str>,
genres: &[Genre],
) -> Result<usize, RepoError>
pub async fn save_genres_to_cache( &self, parent_id: Option<&str>, genres: &[Genre], ) -> Result<usize, RepoError>
Cache the full server genre catalog for a library, so offline (and the hybrid cache-first race) can return the complete list instead of only the genres derivable from locally-cached albums. Replaces the scope’s rows wholesale so genres removed on the server don’t linger.
Sourcepub async fn get_downloaded_items(
&self,
parent_id: &str,
options: Option<GetItemsOptions>,
) -> Result<SearchResult, RepoError>
pub async fn get_downloaded_items( &self, parent_id: &str, options: Option<GetItemsOptions>, ) -> Result<SearchResult, RepoError>
Downloaded-only browse: items under parent_id that are on the device.
Unlike MediaRepository::get_items, this never includes the
synced-but-not-downloaded catalog and never consults the process-wide
INCLUDE_CATALOG_BROWSE flag — it is the dedicated Downloads surface.
An empty result is authoritative (“nothing downloaded here”), so the
hybrid repo must call this directly rather than racing the server.
TRACES: UR-055 | DR-082, DR-083
Sourcepub async fn get_downloaded_libraries(&self) -> Result<Vec<Library>, RepoError>
pub async fn get_downloaded_libraries(&self) -> Result<Vec<Library>, RepoError>
Libraries that contain at least one downloaded item. Libraries with nothing on the device are omitted, so the Downloaded surface only lists libraries the user actually has offline content in.
TRACES: UR-055 | DR-082
Sourcepub async fn get_download_disk_usage(
&self,
) -> Result<DownloadDiskUsage, RepoError>
pub async fn get_download_disk_usage( &self, ) -> Result<DownloadDiskUsage, RepoError>
On-disk bytes for downloaded content, for the disk-usage display.
Returns one entry per container or leaf that appears in the Downloaded
browse: a leaf’s own file_size, a container’s summed downloaded
descendants — plus the device total and item (leaf) count. This is pure
aggregation over downloads.file_size, not new tracking.
TRACES: UR-056 | DR-085
Sourcepub async fn save_playlist_items_to_cache(
&self,
playlist_id: &str,
entries: &[PlaylistEntry],
) -> Result<(), RepoError>
pub async fn save_playlist_items_to_cache( &self, playlist_id: &str, entries: &[PlaylistEntry], ) -> Result<(), RepoError>
Cache playlist items from server into local database Called by HybridRepository after fetching from online
Trait Implementations§
Source§impl MediaRepository for OfflineRepository
impl MediaRepository for OfflineRepository
Source§fn get_favorites<'life0, 'async_trait>(
&'life0 self,
scope: SearchScope,
options: Option<GetItemsOptions>,
) -> Pin<Box<dyn Future<Output = Result<SearchResult, RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn get_favorites<'life0, 'async_trait>(
&'life0 self,
scope: SearchScope,
options: Option<GetItemsOptions>,
) -> Pin<Box<dyn Future<Output = Result<SearchResult, RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Favourites held locally — the ones mirrored from the server by
save_to_cache plus anything favourited on this device.
Gated by the same available_items rules as browsing, so with “Show all
server media” off this returns favourites that are actually on the
device rather than the whole favourited catalog (DR-080).
TRACES: UR-067 | DR-115 | UT-101
Source§fn get_libraries<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<Vec<Library>, RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn get_libraries<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<Vec<Library>, RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Source§fn get_items<'life0, 'life1, 'async_trait>(
&'life0 self,
parent_id: &'life1 str,
options: Option<GetItemsOptions>,
) -> Pin<Box<dyn Future<Output = Result<SearchResult, RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn get_items<'life0, 'life1, 'async_trait>(
&'life0 self,
parent_id: &'life1 str,
options: Option<GetItemsOptions>,
) -> Pin<Box<dyn Future<Output = Result<SearchResult, RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Source§fn get_item<'life0, 'life1, 'async_trait>(
&'life0 self,
item_id: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<MediaItem, RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn get_item<'life0, 'life1, 'async_trait>(
&'life0 self,
item_id: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<MediaItem, RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Source§fn get_latest_items<'life0, 'life1, 'async_trait>(
&'life0 self,
parent_id: &'life1 str,
limit: Option<usize>,
) -> Pin<Box<dyn Future<Output = Result<Vec<MediaItem>, RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn get_latest_items<'life0, 'life1, 'async_trait>(
&'life0 self,
parent_id: &'life1 str,
limit: Option<usize>,
) -> Pin<Box<dyn Future<Output = Result<Vec<MediaItem>, RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Source§fn get_resume_items<'life0, 'life1, 'async_trait>(
&'life0 self,
parent_id: Option<&'life1 str>,
limit: Option<usize>,
) -> Pin<Box<dyn Future<Output = Result<Vec<MediaItem>, RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn get_resume_items<'life0, 'life1, 'async_trait>(
&'life0 self,
parent_id: Option<&'life1 str>,
limit: Option<usize>,
) -> Pin<Box<dyn Future<Output = Result<Vec<MediaItem>, RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Source§fn get_next_up_episodes<'life0, 'life1, 'async_trait>(
&'life0 self,
_series_id: Option<&'life1 str>,
_limit: Option<usize>,
) -> Pin<Box<dyn Future<Output = Result<Vec<MediaItem>, RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn get_next_up_episodes<'life0, 'life1, 'async_trait>(
&'life0 self,
_series_id: Option<&'life1 str>,
_limit: Option<usize>,
) -> Pin<Box<dyn Future<Output = Result<Vec<MediaItem>, RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Source§fn get_recently_played_audio<'life0, 'async_trait>(
&'life0 self,
limit: Option<usize>,
) -> Pin<Box<dyn Future<Output = Result<Vec<MediaItem>, RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn get_recently_played_audio<'life0, 'async_trait>(
&'life0 self,
limit: Option<usize>,
) -> Pin<Box<dyn Future<Output = Result<Vec<MediaItem>, RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Source§fn get_rediscover_albums<'life0, 'life1, 'async_trait>(
&'life0 self,
_parent_id: Option<&'life1 str>,
_limit: Option<usize>,
) -> Pin<Box<dyn Future<Output = Result<Vec<MediaItem>, RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn get_rediscover_albums<'life0, 'life1, 'async_trait>(
&'life0 self,
_parent_id: Option<&'life1 str>,
_limit: Option<usize>,
) -> Pin<Box<dyn Future<Output = Result<Vec<MediaItem>, RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Source§fn get_resume_movies<'life0, 'async_trait>(
&'life0 self,
limit: Option<usize>,
) -> Pin<Box<dyn Future<Output = Result<Vec<MediaItem>, RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn get_resume_movies<'life0, 'async_trait>(
&'life0 self,
limit: Option<usize>,
) -> Pin<Box<dyn Future<Output = Result<Vec<MediaItem>, RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Source§fn get_genres<'life0, 'life1, 'async_trait>(
&'life0 self,
parent_id: Option<&'life1 str>,
) -> Pin<Box<dyn Future<Output = Result<Vec<Genre>, RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn get_genres<'life0, 'life1, 'async_trait>(
&'life0 self,
parent_id: Option<&'life1 str>,
) -> Pin<Box<dyn Future<Output = Result<Vec<Genre>, RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Source§fn search<'life0, 'life1, 'async_trait>(
&'life0 self,
query: &'life1 str,
options: Option<SearchOptions>,
) -> Pin<Box<dyn Future<Output = Result<SearchResult, RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn search<'life0, 'life1, 'async_trait>(
&'life0 self,
query: &'life1 str,
options: Option<SearchOptions>,
) -> Pin<Box<dyn Future<Output = Result<SearchResult, RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Source§fn get_playback_info<'life0, 'life1, 'async_trait>(
&'life0 self,
_item_id: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<PlaybackInfo, RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn get_playback_info<'life0, 'life1, 'async_trait>(
&'life0 self,
_item_id: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<PlaybackInfo, RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Source§fn get_audio_stream_url<'life0, 'life1, 'async_trait>(
&'life0 self,
_item_id: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<String, RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn get_audio_stream_url<'life0, 'life1, 'async_trait>(
&'life0 self,
_item_id: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<String, RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Source§fn get_audio_only_stream_url_for_video<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
_item_id: &'life1 str,
_media_source_id: Option<&'life2 str>,
_start_time_seconds: Option<f64>,
_audio_stream_index: Option<i32>,
) -> Pin<Box<dyn Future<Output = Result<String, RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn get_audio_only_stream_url_for_video<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
_item_id: &'life1 str,
_media_source_id: Option<&'life2 str>,
_start_time_seconds: Option<f64>,
_audio_stream_index: Option<i32>,
) -> Pin<Box<dyn Future<Output = Result<String, RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Source§fn get_live_tv_channels<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<Vec<MediaItem>, RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn get_live_tv_channels<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<Vec<MediaItem>, RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Source§fn get_channels<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<SearchResult, RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn get_channels<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<SearchResult, RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
get_items(channel_id, ...).Source§fn open_live_stream<'life0, 'life1, 'async_trait>(
&'life0 self,
_item_id: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<LiveStreamInfo, RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn open_live_stream<'life0, 'life1, 'async_trait>(
&'life0 self,
_item_id: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<LiveStreamInfo, RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Source§fn report_playback_start<'life0, 'life1, 'async_trait>(
&'life0 self,
_item_id: &'life1 str,
_position_ticks: i64,
) -> Pin<Box<dyn Future<Output = Result<(), RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn report_playback_start<'life0, 'life1, 'async_trait>(
&'life0 self,
_item_id: &'life1 str,
_position_ticks: i64,
) -> Pin<Box<dyn Future<Output = Result<(), RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Source§fn report_playback_progress<'life0, 'life1, 'async_trait>(
&'life0 self,
_item_id: &'life1 str,
_position_ticks: i64,
) -> Pin<Box<dyn Future<Output = Result<(), RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn report_playback_progress<'life0, 'life1, 'async_trait>(
&'life0 self,
_item_id: &'life1 str,
_position_ticks: i64,
) -> Pin<Box<dyn Future<Output = Result<(), RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Source§fn report_playback_stopped<'life0, 'life1, 'async_trait>(
&'life0 self,
_item_id: &'life1 str,
_position_ticks: i64,
) -> Pin<Box<dyn Future<Output = Result<(), RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn report_playback_stopped<'life0, 'life1, 'async_trait>(
&'life0 self,
_item_id: &'life1 str,
_position_ticks: i64,
) -> Pin<Box<dyn Future<Output = Result<(), RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Source§fn get_image_url(
&self,
item_id: &str,
image_type: ImageType,
options: Option<ImageOptions>,
) -> String
fn get_image_url( &self, item_id: &str, image_type: ImageType, options: Option<ImageOptions>, ) -> String
Source§fn get_subtitle_url(
&self,
_item_id: &str,
_media_source_id: &str,
_stream_index: i32,
_format: &str,
) -> String
fn get_subtitle_url( &self, _item_id: &str, _media_source_id: &str, _stream_index: i32, _format: &str, ) -> String
Source§fn get_video_download_url(
&self,
_item_id: &str,
_quality: &str,
_media_source_id: Option<&str>,
_source_audio_codec: Option<&str>,
) -> String
fn get_video_download_url( &self, _item_id: &str, _quality: &str, _media_source_id: Option<&str>, _source_audio_codec: Option<&str>, ) -> String
resolve_video_download_url rather than calling it directly. Read moreSource§fn mark_favorite<'life0, 'life1, 'async_trait>(
&'life0 self,
_item_id: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<(), RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn mark_favorite<'life0, 'life1, 'async_trait>(
&'life0 self,
_item_id: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<(), RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Source§fn unmark_favorite<'life0, 'life1, 'async_trait>(
&'life0 self,
_item_id: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<(), RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn unmark_favorite<'life0, 'life1, 'async_trait>(
&'life0 self,
_item_id: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<(), RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Source§fn clear_watch_history<'life0, 'life1, 'async_trait>(
&'life0 self,
_item_id: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<(), RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn clear_watch_history<'life0, 'life1, 'async_trait>(
&'life0 self,
_item_id: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<(), RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Source§fn mark_played<'life0, 'life1, 'async_trait>(
&'life0 self,
_item_id: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<(), RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn mark_played<'life0, 'life1, 'async_trait>(
&'life0 self,
_item_id: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<(), RepoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
clear_watch_history. Needed by the
sync-queue drain, which replays mark_played rows queued while the
server was unreachable; reporting a stop at a made-up position was the
previous stand-in and does not set the played flag reliably. Read more