Compare commits
2 Commits
a6ef6ba9c0
...
a1c36e4fb2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1c36e4fb2 | ||
|
|
3222c26bb6 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,3 +5,4 @@
|
|||||||
.session
|
.session
|
||||||
api/config.toml
|
api/config.toml
|
||||||
api/items.json
|
api/items.json
|
||||||
|
config.toml
|
||||||
|
|||||||
1437
Cargo.lock
generated
1437
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -9,16 +9,13 @@ license = "MIT"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
api = { version = "0.1.0", path = "api" }
|
api = { version = "0.1.0", path = "api" }
|
||||||
blurhash = "0.2.3"
|
|
||||||
clap = { version = "4.5", features = ["derive"] }
|
clap = { version = "4.5", features = ["derive"] }
|
||||||
clap_complete = "4.5"
|
clap_complete = "4.5"
|
||||||
dotenvy = "0.15.7"
|
dotenvy = "0.15.7"
|
||||||
error-stack = "0.6"
|
error-stack = "0.6"
|
||||||
gpui = { version = "0.2.2", default-features = false, features = ["wayland"] }
|
|
||||||
image = "0.25.9"
|
|
||||||
tap = "1.0.1"
|
|
||||||
thiserror = "2.0"
|
thiserror = "2.0"
|
||||||
tokio = { version = "1.43.1", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1.43.1", features = ["macros", "rt-multi-thread"] }
|
||||||
|
toml = "0.9.8"
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ tap = "1.0.1"
|
|||||||
thiserror = "2.0.17"
|
thiserror = "2.0.17"
|
||||||
tokio = { version = "1.48.0", features = ["fs"] }
|
tokio = { version = "1.48.0", features = ["fs"] }
|
||||||
toml = "0.9.8"
|
toml = "0.9.8"
|
||||||
|
tracing = "0.1.41"
|
||||||
uuid = { version = "1.18.1", features = ["serde"] }
|
uuid = { version = "1.18.1", features = ["serde"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|||||||
@@ -2,15 +2,23 @@ use api::*;
|
|||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
pub async fn main() {
|
pub async fn main() {
|
||||||
let config = std::fs::read_to_string("config.toml").expect("Config.toml");
|
let config = std::fs::read_to_string("config.toml").expect("Config.toml");
|
||||||
let config: JellyfinConfig =
|
let config: JellyfinConfig = toml::from_str(&config).expect("Failed to parse config.toml");
|
||||||
toml::from_str(&config).expect("Failed to parse config.toml");
|
|
||||||
|
|
||||||
let mut jellyfin = JellyfinClient::new(config);
|
let mut jellyfin = JellyfinClient::new(config);
|
||||||
jellyfin.authenticate_with_cached_token(".session").await.expect("Auth");
|
jellyfin
|
||||||
let items = jellyfin.raw_items().await.expect("Items");
|
.authenticate_with_cached_token(".session")
|
||||||
|
.await
|
||||||
|
.expect("Auth");
|
||||||
|
let items = jellyfin.items(None).await.expect("Items");
|
||||||
std::fs::write(
|
std::fs::write(
|
||||||
"items.json",
|
"items.json",
|
||||||
serde_json::to_string_pretty(&items).expect("Serialize items"),
|
serde_json::to_string_pretty(&items).expect("Serialize items"),
|
||||||
);
|
);
|
||||||
|
for item in items {
|
||||||
|
println!("{}: {:?}", item.id, item.name);
|
||||||
|
let items = jellyfin.items(item.id).await.expect("Items");
|
||||||
|
for item in items {
|
||||||
|
println!(" {}: {:?}", item.id, item.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ pub struct ActivityLogEntry {
|
|||||||
pub item_id: Option<String>,
|
pub item_id: Option<String>,
|
||||||
/// Gets or sets the date.
|
/// Gets or sets the date.
|
||||||
#[serde(rename = "Date")]
|
#[serde(rename = "Date")]
|
||||||
pub date: jiff::Zoned,
|
pub date: jiff::Timestamp,
|
||||||
/// Gets or sets the user identifier.
|
/// Gets or sets the user identifier.
|
||||||
#[serde(rename = "UserId")]
|
#[serde(rename = "UserId")]
|
||||||
pub user_id: uuid::Uuid,
|
pub user_id: uuid::Uuid,
|
||||||
@@ -130,7 +130,7 @@ pub struct AlbumInfo {
|
|||||||
#[serde(rename = "ParentIndexNumber")]
|
#[serde(rename = "ParentIndexNumber")]
|
||||||
pub parent_index_number: Option<i32>,
|
pub parent_index_number: Option<i32>,
|
||||||
#[serde(rename = "PremiereDate")]
|
#[serde(rename = "PremiereDate")]
|
||||||
pub premiere_date: Option<jiff::Zoned>,
|
pub premiere_date: Option<jiff::Timestamp>,
|
||||||
#[serde(rename = "IsAutomated")]
|
#[serde(rename = "IsAutomated")]
|
||||||
pub is_automated: bool,
|
pub is_automated: bool,
|
||||||
/// Gets or sets the album artist.
|
/// Gets or sets the album artist.
|
||||||
@@ -138,7 +138,7 @@ pub struct AlbumInfo {
|
|||||||
pub album_artists: Vec<String>,
|
pub album_artists: Vec<String>,
|
||||||
/// Gets or sets the artist provider ids.
|
/// Gets or sets the artist provider ids.
|
||||||
#[serde(rename = "ArtistProviderIds")]
|
#[serde(rename = "ArtistProviderIds")]
|
||||||
pub artist_provider_ids: std::collections::HashMap<String, Option<String>>,
|
pub artist_provider_ids: Option<std::collections::HashMap<String, Option<String>>>,
|
||||||
#[serde(rename = "SongInfos")]
|
#[serde(rename = "SongInfos")]
|
||||||
pub song_infos: Vec<SongInfo>,
|
pub song_infos: Vec<SongInfo>,
|
||||||
}
|
}
|
||||||
@@ -195,7 +195,7 @@ pub struct ArtistInfo {
|
|||||||
#[serde(rename = "ParentIndexNumber")]
|
#[serde(rename = "ParentIndexNumber")]
|
||||||
pub parent_index_number: Option<i32>,
|
pub parent_index_number: Option<i32>,
|
||||||
#[serde(rename = "PremiereDate")]
|
#[serde(rename = "PremiereDate")]
|
||||||
pub premiere_date: Option<jiff::Zoned>,
|
pub premiere_date: Option<jiff::Timestamp>,
|
||||||
#[serde(rename = "IsAutomated")]
|
#[serde(rename = "IsAutomated")]
|
||||||
pub is_automated: bool,
|
pub is_automated: bool,
|
||||||
#[serde(rename = "SongInfos")]
|
#[serde(rename = "SongInfos")]
|
||||||
@@ -252,12 +252,12 @@ pub struct AuthenticationInfo {
|
|||||||
pub is_active: bool,
|
pub is_active: bool,
|
||||||
/// Gets or sets the date created.
|
/// Gets or sets the date created.
|
||||||
#[serde(rename = "DateCreated")]
|
#[serde(rename = "DateCreated")]
|
||||||
pub date_created: jiff::Zoned,
|
pub date_created: jiff::Timestamp,
|
||||||
/// Gets or sets the date revoked.
|
/// Gets or sets the date revoked.
|
||||||
#[serde(rename = "DateRevoked")]
|
#[serde(rename = "DateRevoked")]
|
||||||
pub date_revoked: Option<jiff::Zoned>,
|
pub date_revoked: Option<jiff::Timestamp>,
|
||||||
#[serde(rename = "DateLastActivity")]
|
#[serde(rename = "DateLastActivity")]
|
||||||
pub date_last_activity: jiff::Zoned,
|
pub date_last_activity: jiff::Timestamp,
|
||||||
#[serde(rename = "UserName")]
|
#[serde(rename = "UserName")]
|
||||||
pub user_name: Option<String>,
|
pub user_name: Option<String>,
|
||||||
}
|
}
|
||||||
@@ -316,9 +316,9 @@ pub struct BaseItemDto {
|
|||||||
pub playlist_item_id: Option<String>,
|
pub playlist_item_id: Option<String>,
|
||||||
/// Gets or sets the date created.
|
/// Gets or sets the date created.
|
||||||
#[serde(rename = "DateCreated")]
|
#[serde(rename = "DateCreated")]
|
||||||
pub date_created: Option<jiff::Zoned>,
|
pub date_created: Option<jiff::Timestamp>,
|
||||||
#[serde(rename = "DateLastMediaAdded")]
|
#[serde(rename = "DateLastMediaAdded")]
|
||||||
pub date_last_media_added: Option<jiff::Zoned>,
|
pub date_last_media_added: Option<jiff::Timestamp>,
|
||||||
#[serde(rename = "ExtraType")]
|
#[serde(rename = "ExtraType")]
|
||||||
pub extra_type: Option<ExtraType>,
|
pub extra_type: Option<ExtraType>,
|
||||||
#[serde(rename = "AirsBeforeSeasonNumber")]
|
#[serde(rename = "AirsBeforeSeasonNumber")]
|
||||||
@@ -351,7 +351,7 @@ pub struct BaseItemDto {
|
|||||||
pub video3_d_format: Option<Video3DFormat>,
|
pub video3_d_format: Option<Video3DFormat>,
|
||||||
/// Gets or sets the premiere date.
|
/// Gets or sets the premiere date.
|
||||||
#[serde(rename = "PremiereDate")]
|
#[serde(rename = "PremiereDate")]
|
||||||
pub premiere_date: Option<jiff::Zoned>,
|
pub premiere_date: Option<jiff::Timestamp>,
|
||||||
/// Gets or sets the external urls.
|
/// Gets or sets the external urls.
|
||||||
#[serde(rename = "ExternalUrls")]
|
#[serde(rename = "ExternalUrls")]
|
||||||
pub external_urls: Option<Vec<ExternalUrl>>,
|
pub external_urls: Option<Vec<ExternalUrl>>,
|
||||||
@@ -568,7 +568,7 @@ pub struct BaseItemDto {
|
|||||||
/** Gets or sets the blurhashes for the image tags.
|
/** Gets or sets the blurhashes for the image tags.
|
||||||
Maps image type to dictionary mapping image tag to blurhash value.*/
|
Maps image type to dictionary mapping image tag to blurhash value.*/
|
||||||
#[serde(rename = "ImageBlurHashes")]
|
#[serde(rename = "ImageBlurHashes")]
|
||||||
pub image_blur_hashes: Option<std::collections::HashMap<String, serde_json::Value>>,
|
pub image_blur_hashes: BaseItemDtoImageBlurHashes,
|
||||||
/// Gets or sets the series studio.
|
/// Gets or sets the series studio.
|
||||||
#[serde(rename = "SeriesStudio")]
|
#[serde(rename = "SeriesStudio")]
|
||||||
pub series_studio: Option<String>,
|
pub series_studio: Option<String>,
|
||||||
@@ -592,7 +592,7 @@ Maps image type to dictionary mapping image tag to blurhash value.*/
|
|||||||
pub trickplay: Option<
|
pub trickplay: Option<
|
||||||
std::collections::HashMap<
|
std::collections::HashMap<
|
||||||
String,
|
String,
|
||||||
std::collections::HashMap<String, TrickplayInfo>,
|
Option<std::collections::HashMap<String, TrickplayInfo>>,
|
||||||
>,
|
>,
|
||||||
>,
|
>,
|
||||||
/// Gets or sets the type of the location.
|
/// Gets or sets the type of the location.
|
||||||
@@ -606,7 +606,7 @@ Maps image type to dictionary mapping image tag to blurhash value.*/
|
|||||||
pub media_type: MediaType,
|
pub media_type: MediaType,
|
||||||
/// Gets or sets the end date.
|
/// Gets or sets the end date.
|
||||||
#[serde(rename = "EndDate")]
|
#[serde(rename = "EndDate")]
|
||||||
pub end_date: Option<jiff::Zoned>,
|
pub end_date: Option<jiff::Timestamp>,
|
||||||
/// Gets or sets the locked fields.
|
/// Gets or sets the locked fields.
|
||||||
#[serde(rename = "LockedFields")]
|
#[serde(rename = "LockedFields")]
|
||||||
pub locked_fields: Option<Vec<MetadataField>>,
|
pub locked_fields: Option<Vec<MetadataField>>,
|
||||||
@@ -677,7 +677,7 @@ Maps image type to dictionary mapping image tag to blurhash value.*/
|
|||||||
pub channel_primary_image_tag: Option<String>,
|
pub channel_primary_image_tag: Option<String>,
|
||||||
/// Gets or sets the start date of the recording, in UTC.
|
/// Gets or sets the start date of the recording, in UTC.
|
||||||
#[serde(rename = "StartDate")]
|
#[serde(rename = "StartDate")]
|
||||||
pub start_date: Option<jiff::Zoned>,
|
pub start_date: Option<jiff::Timestamp>,
|
||||||
/// Gets or sets the completion percentage.
|
/// Gets or sets the completion percentage.
|
||||||
#[serde(rename = "CompletionPercentage")]
|
#[serde(rename = "CompletionPercentage")]
|
||||||
pub completion_percentage: Option<f64>,
|
pub completion_percentage: Option<f64>,
|
||||||
@@ -724,6 +724,37 @@ Maps image type to dictionary mapping image tag to blurhash value.*/
|
|||||||
#[serde(rename = "CurrentProgram")]
|
#[serde(rename = "CurrentProgram")]
|
||||||
pub current_program: Option<Box<BaseItemDto>>,
|
pub current_program: Option<Box<BaseItemDto>>,
|
||||||
}
|
}
|
||||||
|
/** Gets or sets the blurhashes for the image tags.
|
||||||
|
Maps image type to dictionary mapping image tag to blurhash value.*/
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct BaseItemDtoImageBlurHashes {
|
||||||
|
#[serde(rename = "Primary")]
|
||||||
|
pub primary: Option<std::collections::HashMap<String, String>>,
|
||||||
|
#[serde(rename = "Art")]
|
||||||
|
pub art: Option<std::collections::HashMap<String, String>>,
|
||||||
|
#[serde(rename = "Backdrop")]
|
||||||
|
pub backdrop: Option<std::collections::HashMap<String, String>>,
|
||||||
|
#[serde(rename = "Banner")]
|
||||||
|
pub banner: Option<std::collections::HashMap<String, String>>,
|
||||||
|
#[serde(rename = "Logo")]
|
||||||
|
pub logo: Option<std::collections::HashMap<String, String>>,
|
||||||
|
#[serde(rename = "Thumb")]
|
||||||
|
pub thumb: Option<std::collections::HashMap<String, String>>,
|
||||||
|
#[serde(rename = "Disc")]
|
||||||
|
pub disc: Option<std::collections::HashMap<String, String>>,
|
||||||
|
#[serde(rename = "Box")]
|
||||||
|
pub _box: Option<std::collections::HashMap<String, String>>,
|
||||||
|
#[serde(rename = "Screenshot")]
|
||||||
|
pub screenshot: Option<std::collections::HashMap<String, String>>,
|
||||||
|
#[serde(rename = "Menu")]
|
||||||
|
pub menu: Option<std::collections::HashMap<String, String>>,
|
||||||
|
#[serde(rename = "Chapter")]
|
||||||
|
pub chapter: Option<std::collections::HashMap<String, String>>,
|
||||||
|
#[serde(rename = "BoxRear")]
|
||||||
|
pub box_rear: Option<std::collections::HashMap<String, String>>,
|
||||||
|
#[serde(rename = "Profile")]
|
||||||
|
pub profile: Option<std::collections::HashMap<String, String>>,
|
||||||
|
}
|
||||||
/// Query result container.
|
/// Query result container.
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct BaseItemDtoQueryResult {
|
pub struct BaseItemDtoQueryResult {
|
||||||
@@ -757,7 +788,37 @@ pub struct BaseItemPerson {
|
|||||||
pub primary_image_tag: Option<String>,
|
pub primary_image_tag: Option<String>,
|
||||||
/// Gets or sets the primary image blurhash.
|
/// Gets or sets the primary image blurhash.
|
||||||
#[serde(rename = "ImageBlurHashes")]
|
#[serde(rename = "ImageBlurHashes")]
|
||||||
pub image_blur_hashes: Option<std::collections::HashMap<String, serde_json::Value>>,
|
pub image_blur_hashes: BaseItemPersonImageBlurHashes,
|
||||||
|
}
|
||||||
|
/// Gets or sets the primary image blurhash.
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct BaseItemPersonImageBlurHashes {
|
||||||
|
#[serde(rename = "Primary")]
|
||||||
|
pub primary: Option<std::collections::HashMap<String, String>>,
|
||||||
|
#[serde(rename = "Art")]
|
||||||
|
pub art: Option<std::collections::HashMap<String, String>>,
|
||||||
|
#[serde(rename = "Backdrop")]
|
||||||
|
pub backdrop: Option<std::collections::HashMap<String, String>>,
|
||||||
|
#[serde(rename = "Banner")]
|
||||||
|
pub banner: Option<std::collections::HashMap<String, String>>,
|
||||||
|
#[serde(rename = "Logo")]
|
||||||
|
pub logo: Option<std::collections::HashMap<String, String>>,
|
||||||
|
#[serde(rename = "Thumb")]
|
||||||
|
pub thumb: Option<std::collections::HashMap<String, String>>,
|
||||||
|
#[serde(rename = "Disc")]
|
||||||
|
pub disc: Option<std::collections::HashMap<String, String>>,
|
||||||
|
#[serde(rename = "Box")]
|
||||||
|
pub _box: Option<std::collections::HashMap<String, String>>,
|
||||||
|
#[serde(rename = "Screenshot")]
|
||||||
|
pub screenshot: Option<std::collections::HashMap<String, String>>,
|
||||||
|
#[serde(rename = "Menu")]
|
||||||
|
pub menu: Option<std::collections::HashMap<String, String>>,
|
||||||
|
#[serde(rename = "Chapter")]
|
||||||
|
pub chapter: Option<std::collections::HashMap<String, String>>,
|
||||||
|
#[serde(rename = "BoxRear")]
|
||||||
|
pub box_rear: Option<std::collections::HashMap<String, String>>,
|
||||||
|
#[serde(rename = "Profile")]
|
||||||
|
pub profile: Option<std::collections::HashMap<String, String>>,
|
||||||
}
|
}
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct BookInfo {
|
pub struct BookInfo {
|
||||||
@@ -787,7 +848,7 @@ pub struct BookInfo {
|
|||||||
#[serde(rename = "ParentIndexNumber")]
|
#[serde(rename = "ParentIndexNumber")]
|
||||||
pub parent_index_number: Option<i32>,
|
pub parent_index_number: Option<i32>,
|
||||||
#[serde(rename = "PremiereDate")]
|
#[serde(rename = "PremiereDate")]
|
||||||
pub premiere_date: Option<jiff::Zoned>,
|
pub premiere_date: Option<jiff::Timestamp>,
|
||||||
#[serde(rename = "IsAutomated")]
|
#[serde(rename = "IsAutomated")]
|
||||||
pub is_automated: bool,
|
pub is_automated: bool,
|
||||||
#[serde(rename = "SeriesName")]
|
#[serde(rename = "SeriesName")]
|
||||||
@@ -834,7 +895,7 @@ pub struct BoxSetInfo {
|
|||||||
#[serde(rename = "ParentIndexNumber")]
|
#[serde(rename = "ParentIndexNumber")]
|
||||||
pub parent_index_number: Option<i32>,
|
pub parent_index_number: Option<i32>,
|
||||||
#[serde(rename = "PremiereDate")]
|
#[serde(rename = "PremiereDate")]
|
||||||
pub premiere_date: Option<jiff::Zoned>,
|
pub premiere_date: Option<jiff::Timestamp>,
|
||||||
#[serde(rename = "IsAutomated")]
|
#[serde(rename = "IsAutomated")]
|
||||||
pub is_automated: bool,
|
pub is_automated: bool,
|
||||||
}
|
}
|
||||||
@@ -869,7 +930,7 @@ pub struct BrandingOptions {
|
|||||||
pub struct BufferRequestDto {
|
pub struct BufferRequestDto {
|
||||||
/// Gets or sets when the request has been made by the client.
|
/// Gets or sets when the request has been made by the client.
|
||||||
#[serde(rename = "When")]
|
#[serde(rename = "When")]
|
||||||
pub when: jiff::Zoned,
|
pub when: jiff::Timestamp,
|
||||||
/// Gets or sets the position ticks.
|
/// Gets or sets the position ticks.
|
||||||
#[serde(rename = "PositionTicks")]
|
#[serde(rename = "PositionTicks")]
|
||||||
pub position_ticks: i64,
|
pub position_ticks: i64,
|
||||||
@@ -958,7 +1019,7 @@ pub struct ChapterInfo {
|
|||||||
#[serde(rename = "ImagePath")]
|
#[serde(rename = "ImagePath")]
|
||||||
pub image_path: Option<String>,
|
pub image_path: Option<String>,
|
||||||
#[serde(rename = "ImageDateModified")]
|
#[serde(rename = "ImageDateModified")]
|
||||||
pub image_date_modified: jiff::Zoned,
|
pub image_date_modified: jiff::Timestamp,
|
||||||
#[serde(rename = "ImageTag")]
|
#[serde(rename = "ImageTag")]
|
||||||
pub image_tag: Option<String>,
|
pub image_tag: Option<String>,
|
||||||
}
|
}
|
||||||
@@ -1183,7 +1244,7 @@ pub struct DeviceInfoDto {
|
|||||||
pub last_user_id: Option<uuid::Uuid>,
|
pub last_user_id: Option<uuid::Uuid>,
|
||||||
/// Gets or sets the date last modified.
|
/// Gets or sets the date last modified.
|
||||||
#[serde(rename = "DateLastActivity")]
|
#[serde(rename = "DateLastActivity")]
|
||||||
pub date_last_activity: Option<jiff::Zoned>,
|
pub date_last_activity: Option<jiff::Timestamp>,
|
||||||
/// Gets or sets the capabilities.
|
/// Gets or sets the capabilities.
|
||||||
#[serde(rename = "Capabilities")]
|
#[serde(rename = "Capabilities")]
|
||||||
pub capabilities: ClientCapabilitiesDto,
|
pub capabilities: ClientCapabilitiesDto,
|
||||||
@@ -1301,7 +1362,7 @@ pub struct DisplayPreferencesDto {
|
|||||||
pub primary_image_width: i32,
|
pub primary_image_width: i32,
|
||||||
/// Gets or sets the custom prefs.
|
/// Gets or sets the custom prefs.
|
||||||
#[serde(rename = "CustomPrefs")]
|
#[serde(rename = "CustomPrefs")]
|
||||||
pub custom_prefs: std::collections::HashMap<String, Option<String>>,
|
pub custom_prefs: Option<std::collections::HashMap<String, Option<String>>>,
|
||||||
/// Gets or sets the scroll direction.
|
/// Gets or sets the scroll direction.
|
||||||
#[serde(rename = "ScrollDirection")]
|
#[serde(rename = "ScrollDirection")]
|
||||||
pub scroll_direction: ScrollDirection,
|
pub scroll_direction: ScrollDirection,
|
||||||
@@ -1527,10 +1588,10 @@ pub struct FontFile {
|
|||||||
pub size: i64,
|
pub size: i64,
|
||||||
/// Gets or sets the date created.
|
/// Gets or sets the date created.
|
||||||
#[serde(rename = "DateCreated")]
|
#[serde(rename = "DateCreated")]
|
||||||
pub date_created: jiff::Zoned,
|
pub date_created: jiff::Timestamp,
|
||||||
/// Gets or sets the date modified.
|
/// Gets or sets the date modified.
|
||||||
#[serde(rename = "DateModified")]
|
#[serde(rename = "DateModified")]
|
||||||
pub date_modified: jiff::Zoned,
|
pub date_modified: jiff::Timestamp,
|
||||||
}
|
}
|
||||||
/// Force keep alive websocket messages.
|
/// Force keep alive websocket messages.
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
@@ -1569,7 +1630,7 @@ pub struct ForgotPasswordResult {
|
|||||||
pub pin_file: Option<String>,
|
pub pin_file: Option<String>,
|
||||||
/// Gets or sets the pin expiration date.
|
/// Gets or sets the pin expiration date.
|
||||||
#[serde(rename = "PinExpirationDate")]
|
#[serde(rename = "PinExpirationDate")]
|
||||||
pub pin_expiration_date: Option<jiff::Zoned>,
|
pub pin_expiration_date: Option<jiff::Timestamp>,
|
||||||
}
|
}
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct GeneralCommand {
|
pub struct GeneralCommand {
|
||||||
@@ -1579,7 +1640,7 @@ pub struct GeneralCommand {
|
|||||||
#[serde(rename = "ControllingUserId")]
|
#[serde(rename = "ControllingUserId")]
|
||||||
pub controlling_user_id: uuid::Uuid,
|
pub controlling_user_id: uuid::Uuid,
|
||||||
#[serde(rename = "Arguments")]
|
#[serde(rename = "Arguments")]
|
||||||
pub arguments: std::collections::HashMap<String, Option<String>>,
|
pub arguments: Option<std::collections::HashMap<String, Option<String>>>,
|
||||||
}
|
}
|
||||||
/// General command websocket message.
|
/// General command websocket message.
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
@@ -1605,7 +1666,7 @@ pub struct GetProgramsDto {
|
|||||||
pub user_id: Option<uuid::Uuid>,
|
pub user_id: Option<uuid::Uuid>,
|
||||||
/// Gets or sets the minimum premiere start date.
|
/// Gets or sets the minimum premiere start date.
|
||||||
#[serde(rename = "MinStartDate")]
|
#[serde(rename = "MinStartDate")]
|
||||||
pub min_start_date: Option<jiff::Zoned>,
|
pub min_start_date: Option<jiff::Timestamp>,
|
||||||
/// Gets or sets filter by programs that have completed airing, or not.
|
/// Gets or sets filter by programs that have completed airing, or not.
|
||||||
#[serde(rename = "HasAired")]
|
#[serde(rename = "HasAired")]
|
||||||
pub has_aired: Option<bool>,
|
pub has_aired: Option<bool>,
|
||||||
@@ -1614,13 +1675,13 @@ pub struct GetProgramsDto {
|
|||||||
pub is_airing: Option<bool>,
|
pub is_airing: Option<bool>,
|
||||||
/// Gets or sets the maximum premiere start date.
|
/// Gets or sets the maximum premiere start date.
|
||||||
#[serde(rename = "MaxStartDate")]
|
#[serde(rename = "MaxStartDate")]
|
||||||
pub max_start_date: Option<jiff::Zoned>,
|
pub max_start_date: Option<jiff::Timestamp>,
|
||||||
/// Gets or sets the minimum premiere end date.
|
/// Gets or sets the minimum premiere end date.
|
||||||
#[serde(rename = "MinEndDate")]
|
#[serde(rename = "MinEndDate")]
|
||||||
pub min_end_date: Option<jiff::Zoned>,
|
pub min_end_date: Option<jiff::Timestamp>,
|
||||||
/// Gets or sets the maximum premiere end date.
|
/// Gets or sets the maximum premiere end date.
|
||||||
#[serde(rename = "MaxEndDate")]
|
#[serde(rename = "MaxEndDate")]
|
||||||
pub max_end_date: Option<jiff::Zoned>,
|
pub max_end_date: Option<jiff::Timestamp>,
|
||||||
/// Gets or sets filter for movies.
|
/// Gets or sets filter for movies.
|
||||||
#[serde(rename = "IsMovie")]
|
#[serde(rename = "IsMovie")]
|
||||||
pub is_movie: Option<bool>,
|
pub is_movie: Option<bool>,
|
||||||
@@ -1696,7 +1757,7 @@ pub struct GroupInfoDto {
|
|||||||
pub participants: Vec<String>,
|
pub participants: Vec<String>,
|
||||||
/// Gets the date when this DTO has been created.
|
/// Gets the date when this DTO has been created.
|
||||||
#[serde(rename = "LastUpdatedAt")]
|
#[serde(rename = "LastUpdatedAt")]
|
||||||
pub last_updated_at: jiff::Zoned,
|
pub last_updated_at: jiff::Timestamp,
|
||||||
}
|
}
|
||||||
/// Class GroupUpdate.
|
/// Class GroupUpdate.
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
@@ -1748,10 +1809,10 @@ pub struct GroupUpdate {
|
|||||||
pub struct GuideInfo {
|
pub struct GuideInfo {
|
||||||
/// Gets or sets the start date.
|
/// Gets or sets the start date.
|
||||||
#[serde(rename = "StartDate")]
|
#[serde(rename = "StartDate")]
|
||||||
pub start_date: jiff::Zoned,
|
pub start_date: jiff::Timestamp,
|
||||||
/// Gets or sets the end date.
|
/// Gets or sets the end date.
|
||||||
#[serde(rename = "EndDate")]
|
#[serde(rename = "EndDate")]
|
||||||
pub end_date: jiff::Zoned,
|
pub end_date: jiff::Timestamp,
|
||||||
}
|
}
|
||||||
/// Class IgnoreWaitRequestDto.
|
/// Class IgnoreWaitRequestDto.
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
@@ -2215,10 +2276,10 @@ pub struct LocalizationOption {
|
|||||||
pub struct LogFile {
|
pub struct LogFile {
|
||||||
/// Gets or sets the date created.
|
/// Gets or sets the date created.
|
||||||
#[serde(rename = "DateCreated")]
|
#[serde(rename = "DateCreated")]
|
||||||
pub date_created: jiff::Zoned,
|
pub date_created: jiff::Timestamp,
|
||||||
/// Gets or sets the date modified.
|
/// Gets or sets the date modified.
|
||||||
#[serde(rename = "DateModified")]
|
#[serde(rename = "DateModified")]
|
||||||
pub date_modified: jiff::Zoned,
|
pub date_modified: jiff::Timestamp,
|
||||||
/// Gets or sets the size.
|
/// Gets or sets the size.
|
||||||
#[serde(rename = "Size")]
|
#[serde(rename = "Size")]
|
||||||
pub size: i64,
|
pub size: i64,
|
||||||
@@ -2755,7 +2816,7 @@ pub struct MovieInfo {
|
|||||||
#[serde(rename = "ParentIndexNumber")]
|
#[serde(rename = "ParentIndexNumber")]
|
||||||
pub parent_index_number: Option<i32>,
|
pub parent_index_number: Option<i32>,
|
||||||
#[serde(rename = "PremiereDate")]
|
#[serde(rename = "PremiereDate")]
|
||||||
pub premiere_date: Option<jiff::Zoned>,
|
pub premiere_date: Option<jiff::Timestamp>,
|
||||||
#[serde(rename = "IsAutomated")]
|
#[serde(rename = "IsAutomated")]
|
||||||
pub is_automated: bool,
|
pub is_automated: bool,
|
||||||
}
|
}
|
||||||
@@ -2800,7 +2861,7 @@ pub struct MusicVideoInfo {
|
|||||||
#[serde(rename = "ParentIndexNumber")]
|
#[serde(rename = "ParentIndexNumber")]
|
||||||
pub parent_index_number: Option<i32>,
|
pub parent_index_number: Option<i32>,
|
||||||
#[serde(rename = "PremiereDate")]
|
#[serde(rename = "PremiereDate")]
|
||||||
pub premiere_date: Option<jiff::Zoned>,
|
pub premiere_date: Option<jiff::Timestamp>,
|
||||||
#[serde(rename = "IsAutomated")]
|
#[serde(rename = "IsAutomated")]
|
||||||
pub is_automated: bool,
|
pub is_automated: bool,
|
||||||
#[serde(rename = "Artists")]
|
#[serde(rename = "Artists")]
|
||||||
@@ -3070,7 +3131,7 @@ pub struct PersonLookupInfo {
|
|||||||
#[serde(rename = "ParentIndexNumber")]
|
#[serde(rename = "ParentIndexNumber")]
|
||||||
pub parent_index_number: Option<i32>,
|
pub parent_index_number: Option<i32>,
|
||||||
#[serde(rename = "PremiereDate")]
|
#[serde(rename = "PremiereDate")]
|
||||||
pub premiere_date: Option<jiff::Zoned>,
|
pub premiere_date: Option<jiff::Timestamp>,
|
||||||
#[serde(rename = "IsAutomated")]
|
#[serde(rename = "IsAutomated")]
|
||||||
pub is_automated: bool,
|
pub is_automated: bool,
|
||||||
}
|
}
|
||||||
@@ -3419,7 +3480,7 @@ pub struct PlayQueueUpdate {
|
|||||||
pub reason: PlayQueueUpdateReason,
|
pub reason: PlayQueueUpdateReason,
|
||||||
/// Gets the UTC time of the last change to the playing queue.
|
/// Gets the UTC time of the last change to the playing queue.
|
||||||
#[serde(rename = "LastUpdate")]
|
#[serde(rename = "LastUpdate")]
|
||||||
pub last_update: jiff::Zoned,
|
pub last_update: jiff::Timestamp,
|
||||||
/// Gets the playlist.
|
/// Gets the playlist.
|
||||||
#[serde(rename = "Playlist")]
|
#[serde(rename = "Playlist")]
|
||||||
pub playlist: Vec<SyncPlayQueueItem>,
|
pub playlist: Vec<SyncPlayQueueItem>,
|
||||||
@@ -3729,14 +3790,14 @@ pub struct QuickConnectResult {
|
|||||||
pub app_version: String,
|
pub app_version: String,
|
||||||
/// Gets or sets the DateTime that this request was created.
|
/// Gets or sets the DateTime that this request was created.
|
||||||
#[serde(rename = "DateAdded")]
|
#[serde(rename = "DateAdded")]
|
||||||
pub date_added: jiff::Zoned,
|
pub date_added: jiff::Timestamp,
|
||||||
}
|
}
|
||||||
/// Class ReadyRequest.
|
/// Class ReadyRequest.
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct ReadyRequestDto {
|
pub struct ReadyRequestDto {
|
||||||
/// Gets or sets when the request has been made by the client.
|
/// Gets or sets when the request has been made by the client.
|
||||||
#[serde(rename = "When")]
|
#[serde(rename = "When")]
|
||||||
pub when: jiff::Zoned,
|
pub when: jiff::Timestamp,
|
||||||
/// Gets or sets the position ticks.
|
/// Gets or sets the position ticks.
|
||||||
#[serde(rename = "PositionTicks")]
|
#[serde(rename = "PositionTicks")]
|
||||||
pub position_ticks: i64,
|
pub position_ticks: i64,
|
||||||
@@ -3849,7 +3910,7 @@ pub struct RemoteSearchResult {
|
|||||||
#[serde(rename = "ParentIndexNumber")]
|
#[serde(rename = "ParentIndexNumber")]
|
||||||
pub parent_index_number: Option<i32>,
|
pub parent_index_number: Option<i32>,
|
||||||
#[serde(rename = "PremiereDate")]
|
#[serde(rename = "PremiereDate")]
|
||||||
pub premiere_date: Option<jiff::Zoned>,
|
pub premiere_date: Option<jiff::Timestamp>,
|
||||||
#[serde(rename = "ImageUrl")]
|
#[serde(rename = "ImageUrl")]
|
||||||
pub image_url: Option<String>,
|
pub image_url: Option<String>,
|
||||||
#[serde(rename = "SearchProviderName")]
|
#[serde(rename = "SearchProviderName")]
|
||||||
@@ -3878,7 +3939,7 @@ pub struct RemoteSubtitleInfo {
|
|||||||
#[serde(rename = "Comment")]
|
#[serde(rename = "Comment")]
|
||||||
pub comment: Option<String>,
|
pub comment: Option<String>,
|
||||||
#[serde(rename = "DateCreated")]
|
#[serde(rename = "DateCreated")]
|
||||||
pub date_created: Option<jiff::Zoned>,
|
pub date_created: Option<jiff::Timestamp>,
|
||||||
#[serde(rename = "CommunityRating")]
|
#[serde(rename = "CommunityRating")]
|
||||||
pub community_rating: Option<f32>,
|
pub community_rating: Option<f32>,
|
||||||
#[serde(rename = "FrameRate")]
|
#[serde(rename = "FrameRate")]
|
||||||
@@ -4051,10 +4112,10 @@ pub struct SearchHint {
|
|||||||
pub media_type: MediaType,
|
pub media_type: MediaType,
|
||||||
/// Gets or sets the start date.
|
/// Gets or sets the start date.
|
||||||
#[serde(rename = "StartDate")]
|
#[serde(rename = "StartDate")]
|
||||||
pub start_date: Option<jiff::Zoned>,
|
pub start_date: Option<jiff::Timestamp>,
|
||||||
/// Gets or sets the end date.
|
/// Gets or sets the end date.
|
||||||
#[serde(rename = "EndDate")]
|
#[serde(rename = "EndDate")]
|
||||||
pub end_date: Option<jiff::Zoned>,
|
pub end_date: Option<jiff::Timestamp>,
|
||||||
/// Gets or sets the series.
|
/// Gets or sets the series.
|
||||||
#[serde(rename = "Series")]
|
#[serde(rename = "Series")]
|
||||||
pub series: Option<String>,
|
pub series: Option<String>,
|
||||||
@@ -4117,7 +4178,7 @@ pub struct SendCommand {
|
|||||||
pub playlist_item_id: uuid::Uuid,
|
pub playlist_item_id: uuid::Uuid,
|
||||||
/// Gets or sets the UTC time when to execute the command.
|
/// Gets or sets the UTC time when to execute the command.
|
||||||
#[serde(rename = "When")]
|
#[serde(rename = "When")]
|
||||||
pub when: jiff::Zoned,
|
pub when: jiff::Timestamp,
|
||||||
/// Gets the position ticks.
|
/// Gets the position ticks.
|
||||||
#[serde(rename = "PositionTicks")]
|
#[serde(rename = "PositionTicks")]
|
||||||
pub position_ticks: Option<i64>,
|
pub position_ticks: Option<i64>,
|
||||||
@@ -4126,7 +4187,7 @@ pub struct SendCommand {
|
|||||||
pub command: SendCommandType,
|
pub command: SendCommandType,
|
||||||
/// Gets the UTC time when this command has been emitted.
|
/// Gets the UTC time when this command has been emitted.
|
||||||
#[serde(rename = "EmittedAt")]
|
#[serde(rename = "EmittedAt")]
|
||||||
pub emitted_at: jiff::Zoned,
|
pub emitted_at: jiff::Timestamp,
|
||||||
}
|
}
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct SeriesInfo {
|
pub struct SeriesInfo {
|
||||||
@@ -4156,7 +4217,7 @@ pub struct SeriesInfo {
|
|||||||
#[serde(rename = "ParentIndexNumber")]
|
#[serde(rename = "ParentIndexNumber")]
|
||||||
pub parent_index_number: Option<i32>,
|
pub parent_index_number: Option<i32>,
|
||||||
#[serde(rename = "PremiereDate")]
|
#[serde(rename = "PremiereDate")]
|
||||||
pub premiere_date: Option<jiff::Zoned>,
|
pub premiere_date: Option<jiff::Timestamp>,
|
||||||
#[serde(rename = "IsAutomated")]
|
#[serde(rename = "IsAutomated")]
|
||||||
pub is_automated: bool,
|
pub is_automated: bool,
|
||||||
}
|
}
|
||||||
@@ -4238,10 +4299,10 @@ pub struct SeriesTimerInfoDto {
|
|||||||
pub overview: Option<String>,
|
pub overview: Option<String>,
|
||||||
/// Gets or sets the start date of the recording, in UTC.
|
/// Gets or sets the start date of the recording, in UTC.
|
||||||
#[serde(rename = "StartDate")]
|
#[serde(rename = "StartDate")]
|
||||||
pub start_date: jiff::Zoned,
|
pub start_date: jiff::Timestamp,
|
||||||
/// Gets or sets the end date of the recording, in UTC.
|
/// Gets or sets the end date of the recording, in UTC.
|
||||||
#[serde(rename = "EndDate")]
|
#[serde(rename = "EndDate")]
|
||||||
pub end_date: jiff::Zoned,
|
pub end_date: jiff::Timestamp,
|
||||||
/// Gets or sets the name of the service.
|
/// Gets or sets the name of the service.
|
||||||
#[serde(rename = "ServiceName")]
|
#[serde(rename = "ServiceName")]
|
||||||
pub service_name: Option<String>,
|
pub service_name: Option<String>,
|
||||||
@@ -4537,13 +4598,13 @@ pub struct SessionInfoDto {
|
|||||||
pub client: Option<String>,
|
pub client: Option<String>,
|
||||||
/// Gets or sets the last activity date.
|
/// Gets or sets the last activity date.
|
||||||
#[serde(rename = "LastActivityDate")]
|
#[serde(rename = "LastActivityDate")]
|
||||||
pub last_activity_date: jiff::Zoned,
|
pub last_activity_date: jiff::Timestamp,
|
||||||
/// Gets or sets the last playback check in.
|
/// Gets or sets the last playback check in.
|
||||||
#[serde(rename = "LastPlaybackCheckIn")]
|
#[serde(rename = "LastPlaybackCheckIn")]
|
||||||
pub last_playback_check_in: jiff::Zoned,
|
pub last_playback_check_in: jiff::Timestamp,
|
||||||
/// Gets or sets the last paused date.
|
/// Gets or sets the last paused date.
|
||||||
#[serde(rename = "LastPausedDate")]
|
#[serde(rename = "LastPausedDate")]
|
||||||
pub last_paused_date: Option<jiff::Zoned>,
|
pub last_paused_date: Option<jiff::Timestamp>,
|
||||||
/// Gets or sets the name of the device.
|
/// Gets or sets the name of the device.
|
||||||
#[serde(rename = "DeviceName")]
|
#[serde(rename = "DeviceName")]
|
||||||
pub device_name: Option<String>,
|
pub device_name: Option<String>,
|
||||||
@@ -4699,7 +4760,7 @@ pub struct SongInfo {
|
|||||||
#[serde(rename = "ParentIndexNumber")]
|
#[serde(rename = "ParentIndexNumber")]
|
||||||
pub parent_index_number: Option<i32>,
|
pub parent_index_number: Option<i32>,
|
||||||
#[serde(rename = "PremiereDate")]
|
#[serde(rename = "PremiereDate")]
|
||||||
pub premiere_date: Option<jiff::Zoned>,
|
pub premiere_date: Option<jiff::Timestamp>,
|
||||||
#[serde(rename = "IsAutomated")]
|
#[serde(rename = "IsAutomated")]
|
||||||
pub is_automated: bool,
|
pub is_automated: bool,
|
||||||
#[serde(rename = "AlbumArtists")]
|
#[serde(rename = "AlbumArtists")]
|
||||||
@@ -4992,10 +5053,10 @@ pub struct TaskInfo {
|
|||||||
pub struct TaskResult {
|
pub struct TaskResult {
|
||||||
/// Gets or sets the start time UTC.
|
/// Gets or sets the start time UTC.
|
||||||
#[serde(rename = "StartTimeUtc")]
|
#[serde(rename = "StartTimeUtc")]
|
||||||
pub start_time_utc: jiff::Zoned,
|
pub start_time_utc: jiff::Timestamp,
|
||||||
/// Gets or sets the end time UTC.
|
/// Gets or sets the end time UTC.
|
||||||
#[serde(rename = "EndTimeUtc")]
|
#[serde(rename = "EndTimeUtc")]
|
||||||
pub end_time_utc: jiff::Zoned,
|
pub end_time_utc: jiff::Timestamp,
|
||||||
/// Gets or sets the status.
|
/// Gets or sets the status.
|
||||||
#[serde(rename = "Status")]
|
#[serde(rename = "Status")]
|
||||||
pub status: TaskCompletionStatus,
|
pub status: TaskCompletionStatus,
|
||||||
@@ -5121,10 +5182,10 @@ pub struct TimerInfoDto {
|
|||||||
pub overview: Option<String>,
|
pub overview: Option<String>,
|
||||||
/// Gets or sets the start date of the recording, in UTC.
|
/// Gets or sets the start date of the recording, in UTC.
|
||||||
#[serde(rename = "StartDate")]
|
#[serde(rename = "StartDate")]
|
||||||
pub start_date: jiff::Zoned,
|
pub start_date: jiff::Timestamp,
|
||||||
/// Gets or sets the end date of the recording, in UTC.
|
/// Gets or sets the end date of the recording, in UTC.
|
||||||
#[serde(rename = "EndDate")]
|
#[serde(rename = "EndDate")]
|
||||||
pub end_date: jiff::Zoned,
|
pub end_date: jiff::Timestamp,
|
||||||
/// Gets or sets the name of the service.
|
/// Gets or sets the name of the service.
|
||||||
#[serde(rename = "ServiceName")]
|
#[serde(rename = "ServiceName")]
|
||||||
pub service_name: Option<String>,
|
pub service_name: Option<String>,
|
||||||
@@ -5208,7 +5269,7 @@ pub struct TrailerInfo {
|
|||||||
#[serde(rename = "ParentIndexNumber")]
|
#[serde(rename = "ParentIndexNumber")]
|
||||||
pub parent_index_number: Option<i32>,
|
pub parent_index_number: Option<i32>,
|
||||||
#[serde(rename = "PremiereDate")]
|
#[serde(rename = "PremiereDate")]
|
||||||
pub premiere_date: Option<jiff::Zoned>,
|
pub premiere_date: Option<jiff::Timestamp>,
|
||||||
#[serde(rename = "IsAutomated")]
|
#[serde(rename = "IsAutomated")]
|
||||||
pub is_automated: bool,
|
pub is_automated: bool,
|
||||||
}
|
}
|
||||||
@@ -5518,7 +5579,7 @@ pub struct UpdateUserItemDataDto {
|
|||||||
pub likes: Option<bool>,
|
pub likes: Option<bool>,
|
||||||
/// Gets or sets the last played date.
|
/// Gets or sets the last played date.
|
||||||
#[serde(rename = "LastPlayedDate")]
|
#[serde(rename = "LastPlayedDate")]
|
||||||
pub last_played_date: Option<jiff::Zoned>,
|
pub last_played_date: Option<jiff::Timestamp>,
|
||||||
/// Gets or sets a value indicating whether this MediaBrowser.Model.Dto.UserItemDataDto is played.
|
/// Gets or sets a value indicating whether this MediaBrowser.Model.Dto.UserItemDataDto is played.
|
||||||
#[serde(rename = "Played")]
|
#[serde(rename = "Played")]
|
||||||
pub played: Option<bool>,
|
pub played: Option<bool>,
|
||||||
@@ -5674,10 +5735,10 @@ This is not used by the server and is for client-side usage only.*/
|
|||||||
pub enable_auto_login: Option<bool>,
|
pub enable_auto_login: Option<bool>,
|
||||||
/// Gets or sets the last login date.
|
/// Gets or sets the last login date.
|
||||||
#[serde(rename = "LastLoginDate")]
|
#[serde(rename = "LastLoginDate")]
|
||||||
pub last_login_date: Option<jiff::Zoned>,
|
pub last_login_date: Option<jiff::Timestamp>,
|
||||||
/// Gets or sets the last activity date.
|
/// Gets or sets the last activity date.
|
||||||
#[serde(rename = "LastActivityDate")]
|
#[serde(rename = "LastActivityDate")]
|
||||||
pub last_activity_date: Option<jiff::Zoned>,
|
pub last_activity_date: Option<jiff::Timestamp>,
|
||||||
/// Gets or sets the configuration.
|
/// Gets or sets the configuration.
|
||||||
#[serde(rename = "Configuration")]
|
#[serde(rename = "Configuration")]
|
||||||
pub configuration: Option<UserConfiguration>,
|
pub configuration: Option<UserConfiguration>,
|
||||||
@@ -5714,7 +5775,7 @@ pub struct UserItemDataDto {
|
|||||||
pub likes: Option<bool>,
|
pub likes: Option<bool>,
|
||||||
/// Gets or sets the last played date.
|
/// Gets or sets the last played date.
|
||||||
#[serde(rename = "LastPlayedDate")]
|
#[serde(rename = "LastPlayedDate")]
|
||||||
pub last_played_date: Option<jiff::Zoned>,
|
pub last_played_date: Option<jiff::Timestamp>,
|
||||||
/// Gets or sets a value indicating whether this MediaBrowser.Model.Dto.UserItemDataDto is played.
|
/// Gets or sets a value indicating whether this MediaBrowser.Model.Dto.UserItemDataDto is played.
|
||||||
#[serde(rename = "Played")]
|
#[serde(rename = "Played")]
|
||||||
pub played: bool,
|
pub played: bool,
|
||||||
@@ -5854,10 +5915,10 @@ pub struct UserUpdatedMessage {
|
|||||||
pub struct UtcTimeResponse {
|
pub struct UtcTimeResponse {
|
||||||
/// Gets the UTC time when request has been received.
|
/// Gets the UTC time when request has been received.
|
||||||
#[serde(rename = "RequestReceptionTime")]
|
#[serde(rename = "RequestReceptionTime")]
|
||||||
pub request_reception_time: jiff::Zoned,
|
pub request_reception_time: jiff::Timestamp,
|
||||||
/// Gets the UTC time when response has been sent.
|
/// Gets the UTC time when response has been sent.
|
||||||
#[serde(rename = "ResponseTransmissionTime")]
|
#[serde(rename = "ResponseTransmissionTime")]
|
||||||
pub response_transmission_time: jiff::Zoned,
|
pub response_transmission_time: jiff::Timestamp,
|
||||||
}
|
}
|
||||||
/// Validate path object.
|
/// Validate path object.
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
pub mod jellyfin;
|
pub mod jellyfin;
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use ::tap::*;
|
use ::tap::*;
|
||||||
use reqwest::Method;
|
use reqwest::Method;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -12,6 +14,8 @@ pub enum JellyfinApiError {
|
|||||||
SerdeError(#[from] serde_json::Error),
|
SerdeError(#[from] serde_json::Error),
|
||||||
#[error("IO error: {0}")]
|
#[error("IO error: {0}")]
|
||||||
IoError(#[from] std::io::Error),
|
IoError(#[from] std::io::Error),
|
||||||
|
#[error("Unknown Jellyfin API error")]
|
||||||
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
type Result<T, E = JellyfinApiError> = std::result::Result<T, E>;
|
type Result<T, E = JellyfinApiError> = std::result::Result<T, E>;
|
||||||
@@ -19,8 +23,8 @@ type Result<T, E = JellyfinApiError> = std::result::Result<T, E>;
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct JellyfinClient {
|
pub struct JellyfinClient {
|
||||||
client: reqwest::Client,
|
client: reqwest::Client,
|
||||||
access_token: Option<String>,
|
access_token: Option<Arc<str>>,
|
||||||
config: JellyfinConfig,
|
pub config: Arc<JellyfinConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JellyfinClient {
|
impl JellyfinClient {
|
||||||
@@ -28,13 +32,13 @@ impl JellyfinClient {
|
|||||||
JellyfinClient {
|
JellyfinClient {
|
||||||
client: reqwest::Client::new(),
|
client: reqwest::Client::new(),
|
||||||
access_token: None,
|
access_token: None,
|
||||||
config,
|
config: Arc::new(config),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn save_token(&self, path: impl AsRef<std::path::Path>) -> std::io::Result<()> {
|
pub async fn save_token(&self, path: impl AsRef<std::path::Path>) -> std::io::Result<()> {
|
||||||
if let Some(token) = &self.access_token {
|
if let Some(token) = &self.access_token {
|
||||||
tokio::fs::write(path, token).await
|
tokio::fs::write(path, &**token).await
|
||||||
} else {
|
} else {
|
||||||
Err(std::io::Error::new(
|
Err(std::io::Error::new(
|
||||||
std::io::ErrorKind::Other,
|
std::io::ErrorKind::Other,
|
||||||
@@ -43,10 +47,17 @@ impl JellyfinClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn load_token(&mut self, path: impl AsRef<std::path::Path>) -> std::io::Result<()> {
|
pub async fn load_token(
|
||||||
|
&mut self,
|
||||||
|
path: impl AsRef<std::path::Path>,
|
||||||
|
) -> std::io::Result<String> {
|
||||||
let token = tokio::fs::read_to_string(path).await?;
|
let token = tokio::fs::read_to_string(path).await?;
|
||||||
self.access_token = Some(token);
|
self.access_token = Some(token.clone().into());
|
||||||
Ok(())
|
Ok(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_token(&mut self, token: impl AsRef<str>) {
|
||||||
|
self.access_token = Some(token.as_ref().into());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn request_builder(
|
pub fn request_builder(
|
||||||
@@ -59,7 +70,7 @@ impl JellyfinClient {
|
|||||||
.header("X-Emby-Authorization", format!("MediaBrowser Client=\"Jello\", Device=\"Jello\", DeviceId=\"{}\", Version=\"1.0.0\"", self.config.device_id))
|
.header("X-Emby-Authorization", format!("MediaBrowser Client=\"Jello\", Device=\"Jello\", DeviceId=\"{}\", Version=\"1.0.0\"", self.config.device_id))
|
||||||
.pipe(|builder| {
|
.pipe(|builder| {
|
||||||
if let Some(token) = &self.access_token {
|
if let Some(token) = &self.access_token {
|
||||||
builder.header("X-MediaBrowser-Token", token)
|
builder.header("X-MediaBrowser-Token", &**token)
|
||||||
} else {
|
} else {
|
||||||
builder
|
builder
|
||||||
}
|
}
|
||||||
@@ -118,20 +129,33 @@ impl JellyfinClient {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
self.access_token = auth_result.access_token.clone();
|
self.access_token = auth_result.access_token.clone().map(Into::into);
|
||||||
Ok(auth_result)
|
Ok(auth_result)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn authenticate_with_cached_token(
|
pub async fn authenticate_with_cached_token(
|
||||||
&mut self,
|
&mut self,
|
||||||
path: impl AsRef<std::path::Path>,
|
path: impl AsRef<std::path::Path>,
|
||||||
) -> Result<()> {
|
) -> Result<String> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
if !self.load_token(path).await.is_ok() {
|
if let Ok(token) = self
|
||||||
self.authenticate().await?;
|
.load_token(path)
|
||||||
|
.await
|
||||||
|
.inspect_err(|err| tracing::warn!("Failed to load cached token: {}", err))
|
||||||
|
{
|
||||||
|
tracing::info!("Authenticating with cached token from {:?}", path);
|
||||||
|
self.access_token = Some(token.clone().into());
|
||||||
|
Ok(token)
|
||||||
|
} else {
|
||||||
|
tracing::info!("No cached token found at {:?}, authenticating...", path);
|
||||||
|
let token = self
|
||||||
|
.authenticate()
|
||||||
|
.await?
|
||||||
|
.access_token
|
||||||
|
.ok_or_else(|| JellyfinApiError::Unknown)?;
|
||||||
self.save_token(path).await?;
|
self.save_token(path).await?;
|
||||||
|
Ok(token)
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn raw_items(&self) -> Result<jellyfin::BaseItemDtoQueryResult> {
|
pub async fn raw_items(&self) -> Result<jellyfin::BaseItemDtoQueryResult> {
|
||||||
@@ -148,7 +172,7 @@ impl JellyfinClient {
|
|||||||
|
|
||||||
pub async fn items(
|
pub async fn items(
|
||||||
&self,
|
&self,
|
||||||
root: impl Into<Option<String>>,
|
root: impl Into<Option<uuid::Uuid>>,
|
||||||
) -> Result<Vec<jellyfin::BaseItemDto>> {
|
) -> Result<Vec<jellyfin::BaseItemDto>> {
|
||||||
let text = &self
|
let text = &self
|
||||||
.request_builder(Method::GET, "Items")
|
.request_builder(Method::GET, "Items")
|
||||||
|
|||||||
3897
api/types.rs
3897
api/types.rs
File diff suppressed because it is too large
Load Diff
@@ -43,7 +43,7 @@
|
|||||||
cargoToml = builtins.fromTOML (builtins.readFile ./Cargo.toml);
|
cargoToml = builtins.fromTOML (builtins.readFile ./Cargo.toml);
|
||||||
name = cargoToml.package.name;
|
name = cargoToml.package.name;
|
||||||
|
|
||||||
toolchain = pkgs.rust-bin.nightly.latest.default;
|
toolchain = pkgs.rust-bin.stable.latest.default;
|
||||||
toolchainWithLLvmTools = toolchain.override {
|
toolchainWithLLvmTools = toolchain.override {
|
||||||
extensions = ["rust-src" "llvm-tools"];
|
extensions = ["rust-src" "llvm-tools"];
|
||||||
};
|
};
|
||||||
|
|||||||
65
src/main.rs
65
src/main.rs
@@ -2,30 +2,51 @@ mod errors;
|
|||||||
use api::{JellyfinClient, JellyfinConfig};
|
use api::{JellyfinClient, JellyfinConfig};
|
||||||
use errors::*;
|
use errors::*;
|
||||||
|
|
||||||
#[tokio::main]
|
fn jellyfin_config_try() -> Result<JellyfinConfig> {
|
||||||
pub async fn main() -> Result<()> {
|
let file = std::fs::read("config.toml").change_context(Error)?;
|
||||||
dotenvy::dotenv()
|
let config: JellyfinConfig = toml::from_slice(&file)
|
||||||
.change_context(Error)
|
.change_context(Error)
|
||||||
.inspect_err(|err| {
|
.attach("Failed to parse Jellyfin Config")?;
|
||||||
eprintln!("Failed to load .env file: {}", err);
|
Ok(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn jellyfin_config() -> JellyfinConfig {
|
||||||
|
jellyfin_config_try().unwrap_or_else(|err| {
|
||||||
|
eprintln!("Error loading Jellyfin configuration: {:?}", err);
|
||||||
|
std::process::exit(1);
|
||||||
})
|
})
|
||||||
.ok();
|
}
|
||||||
let config = JellyfinConfig::new(
|
|
||||||
std::env::var("JELLYFIN_USERNAME").change_context(Error)?,
|
|
||||||
std::env::var("JELLYFIN_PASSWORD").change_context(Error)?,
|
|
||||||
std::env::var("JELLYFIN_SERVER_URL").change_context(Error)?,
|
|
||||||
"jello".to_string(),
|
|
||||||
);
|
|
||||||
let mut jellyfin = api::JellyfinClient::new(config);
|
|
||||||
jellyfin
|
|
||||||
.authenticate_with_cached_token(".session")
|
|
||||||
.await
|
|
||||||
.change_context(Error)?;
|
|
||||||
|
|
||||||
#[cfg(feature = "iced")]
|
|
||||||
ui_iced::ui(jellyfin);
|
|
||||||
#[cfg(feature = "gpui")]
|
|
||||||
ui_gpui::ui(jellyfin);
|
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
ui_iced::ui(jellyfin_config).change_context(Error)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #[tokio::main]
|
||||||
|
// pub async fn main() -> Result<()> {
|
||||||
|
// dotenvy::dotenv()
|
||||||
|
// .change_context(Error)
|
||||||
|
// .inspect_err(|err| {
|
||||||
|
// eprintln!("Failed to load .env file: {}", err);
|
||||||
|
// })
|
||||||
|
// .ok();
|
||||||
|
// let config = JellyfinConfig::new(
|
||||||
|
// std::env::var("JELLYFIN_USERNAME").change_context(Error)?,
|
||||||
|
// std::env::var("JELLYFIN_PASSWORD").change_context(Error)?,
|
||||||
|
// std::env::var("JELLYFIN_SERVER_URL").change_context(Error)?,
|
||||||
|
// "jello".to_string(),
|
||||||
|
// );
|
||||||
|
// let mut jellyfin = api::JellyfinClient::new(config);
|
||||||
|
// jellyfin
|
||||||
|
// .authenticate_with_cached_token(".session")
|
||||||
|
// .await
|
||||||
|
// .change_context(Error)?;
|
||||||
|
//
|
||||||
|
// #[cfg(feature = "iced")]
|
||||||
|
// ui_iced::ui(jellyfin);
|
||||||
|
// #[cfg(feature = "gpui")]
|
||||||
|
// ui_gpui::ui(jellyfin);
|
||||||
|
//
|
||||||
|
// Ok(())
|
||||||
|
// }
|
||||||
|
|||||||
@@ -11,4 +11,4 @@ proc-macro2 = "1.0.103"
|
|||||||
quote = "1.0.41"
|
quote = "1.0.41"
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
serde_json = "1.0.145"
|
serde_json = "1.0.145"
|
||||||
syn = { version = "2.0.108", features = ["full", "parsing"] }
|
syn = { version = "2.0.108", features = ["extra-traits", "full", "parsing"] }
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use indexmap::IndexMap;
|
|||||||
|
|
||||||
const KEYWORDS: &[&str] = &[
|
const KEYWORDS: &[&str] = &[
|
||||||
"type", "match", "enum", "struct", "fn", "mod", "pub", "use", "crate", "self", "super", "as",
|
"type", "match", "enum", "struct", "fn", "mod", "pub", "use", "crate", "self", "super", "as",
|
||||||
"in", "let", "mut", "ref", "static", "trait", "where",
|
"in", "let", "mut", "ref", "static", "trait", "where", "box",
|
||||||
];
|
];
|
||||||
|
|
||||||
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
|
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
|
||||||
@@ -15,6 +15,7 @@ pub struct JellyfinOpenapi {
|
|||||||
pub struct Components {
|
pub struct Components {
|
||||||
schemas: indexmap::IndexMap<String, Schema>,
|
schemas: indexmap::IndexMap<String, Schema>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
|
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
|
||||||
pub struct Schema {
|
pub struct Schema {
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
@@ -46,12 +47,15 @@ pub struct Property {
|
|||||||
nullable: Option<bool>,
|
nullable: Option<bool>,
|
||||||
format: Option<String>,
|
format: Option<String>,
|
||||||
items: Option<Box<Property>>,
|
items: Option<Box<Property>>,
|
||||||
|
properties: Option<indexmap::IndexMap<String, Property>>,
|
||||||
#[serde(rename = "additionalProperties")]
|
#[serde(rename = "additionalProperties")]
|
||||||
additional_properties: Option<Box<Property>>,
|
additional_properties: Option<Box<Property>>,
|
||||||
#[serde(rename = "enum")]
|
#[serde(rename = "enum")]
|
||||||
_enum: Option<Vec<String>>,
|
_enum: Option<Vec<String>>,
|
||||||
#[serde(rename = "allOf")]
|
#[serde(rename = "allOf")]
|
||||||
all_of: Option<Vec<RefName>>,
|
all_of: Option<Vec<RefName>>,
|
||||||
|
#[serde(rename = "oneOf")]
|
||||||
|
one_of: Option<Vec<RefName>>,
|
||||||
description: Option<String>,
|
description: Option<String>,
|
||||||
#[serde(rename = "$ref")]
|
#[serde(rename = "$ref")]
|
||||||
_ref: Option<String>,
|
_ref: Option<String>,
|
||||||
@@ -62,7 +66,7 @@ impl Property {
|
|||||||
let out = match self._type {
|
let out = match self._type {
|
||||||
Some(Types::String) => match self.format.as_deref() {
|
Some(Types::String) => match self.format.as_deref() {
|
||||||
Some("uuid") => "uuid::Uuid".to_string(),
|
Some("uuid") => "uuid::Uuid".to_string(),
|
||||||
Some("date-time") => "jiff::Zoned".to_string(),
|
Some("date-time") => "jiff::Timestamp".to_string(),
|
||||||
_ => "String".to_string(),
|
_ => "String".to_string(),
|
||||||
},
|
},
|
||||||
Some(Types::Integer) => match self.format.as_deref() {
|
Some(Types::Integer) => match self.format.as_deref() {
|
||||||
@@ -89,6 +93,8 @@ impl Property {
|
|||||||
"std::collections::HashMap<String, {}>",
|
"std::collections::HashMap<String, {}>",
|
||||||
properties.to_string()
|
properties.to_string()
|
||||||
)
|
)
|
||||||
|
// } else if let Some(props) = &self.properties {
|
||||||
|
// todo!()
|
||||||
} else {
|
} else {
|
||||||
"std::collections::HashMap<String, serde_json::Value>".to_string()
|
"std::collections::HashMap<String, serde_json::Value>".to_string()
|
||||||
}
|
}
|
||||||
@@ -106,6 +112,8 @@ impl Property {
|
|||||||
};
|
};
|
||||||
if let Some(true) = self.nullable {
|
if let Some(true) = self.nullable {
|
||||||
format!("Option<{}>", out)
|
format!("Option<{}>", out)
|
||||||
|
} else if self.nullable.is_none() && self._type == Some(Types::Object) {
|
||||||
|
format!("Option<{}>", out)
|
||||||
} else {
|
} else {
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
@@ -115,7 +123,7 @@ impl Property {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
|
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
pub enum Types {
|
pub enum Types {
|
||||||
Object,
|
Object,
|
||||||
@@ -144,73 +152,10 @@ fn main() {
|
|||||||
.map(|(k, v)| (k.clone(), v.clone()))
|
.map(|(k, v)| (k.clone(), v.clone()))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let syn_structs: Vec<syn::ItemStruct> = structs
|
let syn_structs: Vec<syn::Item> = structs
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(key, value)| {
|
.map(|(key, value)| generate_struct(key, value))
|
||||||
let fields = value
|
.flatten()
|
||||||
.properties
|
|
||||||
.as_ref()
|
|
||||||
.expect("Possible properties")
|
|
||||||
.iter()
|
|
||||||
.map(|(name, _type)| {
|
|
||||||
let og_name = name.clone();
|
|
||||||
let name = modify_keyword(&name.to_snake_case());
|
|
||||||
let _type_desc = _type.description();
|
|
||||||
let _type_desc = if let Some(desc) = &_type_desc {
|
|
||||||
Some(format!(" {}", desc))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
let _type = _type.to_string();
|
|
||||||
let _type = if _type.contains(key) {
|
|
||||||
_type.replace(&format!("<{}>", key), format!("<Box<{}>>", key).as_str())
|
|
||||||
} else {
|
|
||||||
_type
|
|
||||||
};
|
|
||||||
syn::Field {
|
|
||||||
attrs: if let Some(desc) = _type_desc {
|
|
||||||
syn::parse_quote! {
|
|
||||||
#[doc = #desc]
|
|
||||||
#[serde(rename = #og_name)]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
syn::parse_quote! {
|
|
||||||
#[serde(rename = #og_name)]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mutability: syn::FieldMutability::None,
|
|
||||||
vis: syn::Visibility::Public(syn::Token,
|
|
||||||
)),
|
|
||||||
ident: Some(syn::Ident::new(&name, proc_macro2::Span::call_site())),
|
|
||||||
colon_token: Some(syn::token::Colon(proc_macro2::Span::call_site())),
|
|
||||||
ty: syn::parse_str(&_type).expect("Failed to parse type"),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<syn::Field>>();
|
|
||||||
let key = modify_keyword(key);
|
|
||||||
let desc = value.description.clone();
|
|
||||||
let key = syn::Ident::new(&key.to_pascal_case(), proc_macro2::Span::call_site());
|
|
||||||
let tokens = if let Some(desc) = desc {
|
|
||||||
let desc = format!(" {}", desc);
|
|
||||||
quote::quote! {
|
|
||||||
#[doc = #desc]
|
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
|
||||||
pub struct #key {
|
|
||||||
#(#fields),*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
quote::quote! {
|
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
|
||||||
pub struct #key {
|
|
||||||
#(#fields),*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
syn::parse2(tokens).expect("Failed to parse struct")
|
|
||||||
})
|
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let syn_enums = enums
|
let syn_enums = enums
|
||||||
@@ -262,7 +207,6 @@ fn main() {
|
|||||||
attrs: vec![],
|
attrs: vec![],
|
||||||
items: syn_structs
|
items: syn_structs
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(syn::Item::Struct)
|
|
||||||
.chain(syn_enums.into_iter().map(syn::Item::Enum))
|
.chain(syn_enums.into_iter().map(syn::Item::Enum))
|
||||||
.collect(),
|
.collect(),
|
||||||
};
|
};
|
||||||
@@ -277,3 +221,104 @@ fn modify_keyword(name: &str) -> String {
|
|||||||
name.to_string()
|
name.to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn generate_struct(key: impl AsRef<str>, value: &Schema) -> Vec<syn::Item> {
|
||||||
|
let key = key.as_ref();
|
||||||
|
|
||||||
|
let extra_structs = value
|
||||||
|
.properties
|
||||||
|
.as_ref()
|
||||||
|
.expect("Possible properties")
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(name, property)| {
|
||||||
|
if property._type == Some(Types::Object) && property.properties.is_some() {
|
||||||
|
Some(generate_struct(
|
||||||
|
&format!("{}_{}", key, name),
|
||||||
|
&Schema {
|
||||||
|
_type: Types::Object,
|
||||||
|
properties: property.properties.clone(),
|
||||||
|
one_of: None,
|
||||||
|
_enum: None,
|
||||||
|
description: property.description.clone(),
|
||||||
|
},
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<syn::Item>>();
|
||||||
|
|
||||||
|
let fields = value
|
||||||
|
.properties
|
||||||
|
.as_ref()
|
||||||
|
.expect("Possible properties")
|
||||||
|
.iter()
|
||||||
|
.map(|(name, property)| {
|
||||||
|
let nested_struct =
|
||||||
|
property._type == Some(Types::Object) && property.properties.is_some();
|
||||||
|
let og_name = name.clone();
|
||||||
|
let name = modify_keyword(&name.to_snake_case());
|
||||||
|
let _type_desc = property.description();
|
||||||
|
let _type_desc = if let Some(desc) = &_type_desc {
|
||||||
|
Some(format!(" {}", desc))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let _type = if !nested_struct {
|
||||||
|
property.to_string()
|
||||||
|
} else {
|
||||||
|
format!("{}_{}", key, og_name).to_pascal_case()
|
||||||
|
};
|
||||||
|
let _type = if _type.contains(key) {
|
||||||
|
_type.replace(&format!("<{}>", key), format!("<Box<{}>>", key).as_str())
|
||||||
|
} else {
|
||||||
|
_type
|
||||||
|
};
|
||||||
|
syn::Field {
|
||||||
|
attrs: if let Some(desc) = _type_desc {
|
||||||
|
syn::parse_quote! {
|
||||||
|
#[doc = #desc]
|
||||||
|
#[serde(rename = #og_name)]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
syn::parse_quote! {
|
||||||
|
#[serde(rename = #og_name)]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mutability: syn::FieldMutability::None,
|
||||||
|
vis: syn::Visibility::Public(syn::Token)),
|
||||||
|
ident: Some(syn::Ident::new(&name, proc_macro2::Span::call_site())),
|
||||||
|
colon_token: Some(syn::token::Colon(proc_macro2::Span::call_site())),
|
||||||
|
ty: syn::parse_str(&_type).expect("Failed to parse type"),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<syn::Field>>();
|
||||||
|
|
||||||
|
let key = modify_keyword(key);
|
||||||
|
let desc = value.description.clone();
|
||||||
|
let key = syn::Ident::new(&key.to_pascal_case(), proc_macro2::Span::call_site());
|
||||||
|
let tokens = if let Some(desc) = desc {
|
||||||
|
let desc = format!(" {}", desc);
|
||||||
|
quote::quote! {
|
||||||
|
#[doc = #desc]
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct #key {
|
||||||
|
#(#fields),*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote::quote! {
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct #key {
|
||||||
|
#(#fields),*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut out = syn::parse2::<syn::File>(tokens)
|
||||||
|
.expect("Failed to parse struct")
|
||||||
|
.items;
|
||||||
|
out.extend(extra_structs);
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,3 +6,4 @@ edition = "2024"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
gpui = { version = "0.2.2", default-features = false, features = ["wayland"] }
|
gpui = { version = "0.2.2", default-features = false, features = ["wayland"] }
|
||||||
tap = "1.0.1"
|
tap = "1.0.1"
|
||||||
|
blurhash = "0.2.3"
|
||||||
|
|||||||
@@ -4,6 +4,14 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
api = { version = "0.1.0", path = "../api" }
|
||||||
|
blurhash = "0.2.3"
|
||||||
gpui_util = "0.2.2"
|
gpui_util = "0.2.2"
|
||||||
iced = { version = "0.13.1", features = ["canvas", "image", "tokio"] }
|
iced = { git = "https://github.com/iced-rs/iced", features = [
|
||||||
|
"advanced",
|
||||||
|
"canvas",
|
||||||
|
"image",
|
||||||
|
"tokio",
|
||||||
|
] }
|
||||||
|
tracing = "0.1.41"
|
||||||
uuid = "1.18.1"
|
uuid = "1.18.1"
|
||||||
|
|||||||
119
ui-iced/src/blur_hash.rs
Normal file
119
ui-iced/src/blur_hash.rs
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
use std::sync::{Arc, LazyLock, atomic::AtomicBool};
|
||||||
|
|
||||||
|
use iced::{Element, advanced::Widget, widget::Image};
|
||||||
|
|
||||||
|
use crate::shared_string::SharedString;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct BlurHash {
|
||||||
|
hash: SharedString,
|
||||||
|
handle: Arc<iced::advanced::image::Handle>,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
punch: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlurHash {
|
||||||
|
pub fn recompute(&mut self) {
|
||||||
|
let pixels = blurhash::decode(&self.hash, self.width, self.height, self.punch)
|
||||||
|
.unwrap_or_else(|_| vec![0; (self.width * self.height * 4) as usize]);
|
||||||
|
let handle = iced::advanced::image::Handle::from_rgba(self.width, self.height, pixels);
|
||||||
|
self.handle = Arc::new(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(hash: impl AsRef<str>) -> Self {
|
||||||
|
let hash = SharedString::from(hash.as_ref().to_string());
|
||||||
|
let pixels = blurhash::decode(&hash, 32, 32, 1.0).unwrap_or_else(|_| vec![0; 32 * 32 * 4]);
|
||||||
|
let handle = iced::advanced::image::Handle::from_rgba(32, 32, pixels);
|
||||||
|
let handle = Arc::new(handle);
|
||||||
|
BlurHash {
|
||||||
|
hash,
|
||||||
|
handle,
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
punch: 1.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn width(mut self, height: u32) -> Self {
|
||||||
|
self.width = height;
|
||||||
|
self.recompute();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn height(mut self, height: u32) -> Self {
|
||||||
|
self.height = height;
|
||||||
|
self.recompute();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn punch(mut self, punch: f32) -> Self {
|
||||||
|
self.punch = punch;
|
||||||
|
self.recompute();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> for BlurHash
|
||||||
|
where
|
||||||
|
Renderer: iced::advanced::image::Renderer<Handle = iced::advanced::image::Handle>,
|
||||||
|
{
|
||||||
|
fn size(&self) -> iced::Size<iced::Length> {
|
||||||
|
iced::Size {
|
||||||
|
width: iced::Length::Fixed(self.width as f32),
|
||||||
|
height: iced::Length::Fixed(self.height as f32),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layout(
|
||||||
|
&mut self,
|
||||||
|
_tree: &mut iced::advanced::widget::Tree,
|
||||||
|
renderer: &Renderer,
|
||||||
|
limits: &iced::advanced::layout::Limits,
|
||||||
|
) -> iced::advanced::layout::Node {
|
||||||
|
iced::widget::image::layout(
|
||||||
|
renderer,
|
||||||
|
limits,
|
||||||
|
&self.handle,
|
||||||
|
self.width.into(),
|
||||||
|
self.height.into(),
|
||||||
|
None,
|
||||||
|
iced::ContentFit::default(),
|
||||||
|
iced::Rotation::default(),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
_state: &iced::advanced::widget::Tree,
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
_theme: &Theme,
|
||||||
|
_style: &iced::advanced::renderer::Style,
|
||||||
|
layout: iced::advanced::Layout<'_>,
|
||||||
|
_cursor: iced::advanced::mouse::Cursor,
|
||||||
|
_viewport: &iced::Rectangle,
|
||||||
|
) {
|
||||||
|
iced::widget::image::draw(
|
||||||
|
renderer,
|
||||||
|
layout,
|
||||||
|
&self.handle,
|
||||||
|
None,
|
||||||
|
iced::border::Radius::default(),
|
||||||
|
iced::ContentFit::default(),
|
||||||
|
iced::widget::image::FilterMethod::default(),
|
||||||
|
iced::Rotation::default(),
|
||||||
|
1.0,
|
||||||
|
1.0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message, Theme, Renderer> From<BlurHash> for iced::Element<'a, Message, Theme, Renderer>
|
||||||
|
where
|
||||||
|
Renderer: iced::advanced::image::Renderer<Handle = iced::advanced::image::Handle>,
|
||||||
|
{
|
||||||
|
fn from(blur_hash: BlurHash) -> Element<'a, Message, Theme, Renderer> {
|
||||||
|
iced::Element::new(blur_hash)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,33 +1,301 @@
|
|||||||
use std::{collections::BTreeMap, sync::Arc};
|
mod shared_string;
|
||||||
type SharedString = Arc<str>;
|
use shared_string::SharedString;
|
||||||
|
|
||||||
use iced::{Element, Task};
|
mod blur_hash;
|
||||||
|
use blur_hash::BlurHash;
|
||||||
|
|
||||||
struct State {
|
use iced::{Alignment, Element, Length, Task, widget::*};
|
||||||
loading: Option<Loading>,
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
current: Screen,
|
|
||||||
cache: ItemCache,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ItemCache {
|
|
||||||
pub items: BTreeMap<uuid::Uuid, Item>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct Item {
|
|
||||||
pub id: SharedString,
|
|
||||||
pub name: SharedString,
|
|
||||||
pub item_type: SharedString,
|
|
||||||
pub media_type: SharedString,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum Screen {
|
|
||||||
Home,
|
|
||||||
Settings,
|
|
||||||
Profile,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct Loading {
|
pub struct Loading {
|
||||||
to: Screen,
|
to: Screen,
|
||||||
from: Screen,
|
from: Screen,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone)]
|
||||||
|
pub struct ItemCache {
|
||||||
|
pub items: BTreeMap<uuid::Uuid, Item>,
|
||||||
|
pub tree: BTreeMap<Option<uuid::Uuid>, BTreeSet<uuid::Uuid>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ItemCache {
|
||||||
|
pub fn insert(&mut self, parent: impl Into<Option<uuid::Uuid>>, item: Item) {
|
||||||
|
let parent = parent.into();
|
||||||
|
self.tree.entry(parent).or_default().insert(item.id);
|
||||||
|
self.items.insert(item.id, item);
|
||||||
|
}
|
||||||
|
pub fn extend<I: IntoIterator<Item = Item>>(
|
||||||
|
&mut self,
|
||||||
|
parent: impl Into<Option<uuid::Uuid>>,
|
||||||
|
items: I,
|
||||||
|
) {
|
||||||
|
let parent = parent.into();
|
||||||
|
items.into_iter().for_each(|item| {
|
||||||
|
self.insert(parent, item);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
pub fn items_of(&self, parent: impl Into<Option<uuid::Uuid>>) -> Vec<&Item> {
|
||||||
|
let parent = parent.into();
|
||||||
|
self.tree.get(&None);
|
||||||
|
self.tree
|
||||||
|
.get(&parent)
|
||||||
|
.map(|ids| {
|
||||||
|
ids.iter()
|
||||||
|
.filter_map(|id| self.items.get(id))
|
||||||
|
.collect::<Vec<&Item>>()
|
||||||
|
})
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Thumbnail {
|
||||||
|
pub id: SharedString,
|
||||||
|
pub blur_hash: Option<SharedString>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<api::jellyfin::BaseItemDto> for Item {
|
||||||
|
fn from(dto: api::jellyfin::BaseItemDto) -> Self {
|
||||||
|
Item {
|
||||||
|
id: dto.id,
|
||||||
|
name: dto.name.map(Into::into),
|
||||||
|
parent_id: dto.parent_id,
|
||||||
|
thumbnail: dto
|
||||||
|
.image_tags
|
||||||
|
.and_then(|tags| tags.get("Primary").cloned())
|
||||||
|
.map(|tag| Thumbnail {
|
||||||
|
id: tag.clone().into(),
|
||||||
|
blur_hash: dto
|
||||||
|
.image_blur_hashes
|
||||||
|
.primary
|
||||||
|
.and_then(|hashes| hashes.get(&tag).cloned())
|
||||||
|
.map(|s| s.clone().into()),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Item {
|
||||||
|
pub id: uuid::Uuid,
|
||||||
|
pub parent_id: Option<uuid::Uuid>,
|
||||||
|
pub name: Option<SharedString>,
|
||||||
|
pub thumbnail: Option<Thumbnail>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub enum Screen {
|
||||||
|
#[default]
|
||||||
|
Home,
|
||||||
|
Settings,
|
||||||
|
Profile,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct State {
|
||||||
|
loading: Option<Loading>,
|
||||||
|
current: Option<uuid::Uuid>,
|
||||||
|
cache: ItemCache,
|
||||||
|
jellyfin_client: api::JellyfinClient,
|
||||||
|
messages: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
pub fn new(jellyfin_client: api::JellyfinClient) -> Self {
|
||||||
|
State {
|
||||||
|
loading: None,
|
||||||
|
current: None,
|
||||||
|
cache: ItemCache::default(),
|
||||||
|
jellyfin_client,
|
||||||
|
messages: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Message {
|
||||||
|
OpenSettings,
|
||||||
|
Refresh,
|
||||||
|
OpenItem(Option<uuid::Uuid>),
|
||||||
|
LoadedItem(Option<uuid::Uuid>, Vec<Item>),
|
||||||
|
Error(String),
|
||||||
|
SetToken(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(state: &mut State, message: Message) -> Task<Message> {
|
||||||
|
match message {
|
||||||
|
Message::OpenSettings => {
|
||||||
|
// Foo
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::OpenItem(id) => {
|
||||||
|
let client = state.jellyfin_client.clone();
|
||||||
|
Task::perform(
|
||||||
|
async move {
|
||||||
|
let items: Result<Vec<Item>, api::JellyfinApiError> = client
|
||||||
|
.items(id)
|
||||||
|
.await
|
||||||
|
.map(|items| items.into_iter().map(Item::from).collect());
|
||||||
|
(id, items)
|
||||||
|
},
|
||||||
|
|(msg, items)| match items {
|
||||||
|
Err(e) => Message::Error(format!("Failed to load item: {}", e)),
|
||||||
|
Ok(items) => Message::LoadedItem(msg, items),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Message::LoadedItem(id, items) => {
|
||||||
|
state.cache.extend(id, items);
|
||||||
|
state.current = id;
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::Refresh => {
|
||||||
|
// Handle refresh logic
|
||||||
|
let client = state.jellyfin_client.clone();
|
||||||
|
let current = state.current;
|
||||||
|
Task::perform(
|
||||||
|
async move {
|
||||||
|
let items: Result<Vec<Item>, api::JellyfinApiError> = client
|
||||||
|
.items(current)
|
||||||
|
.await
|
||||||
|
.map(|items| items.into_iter().map(Item::from).collect());
|
||||||
|
(current, items)
|
||||||
|
},
|
||||||
|
|(msg, items)| match items {
|
||||||
|
Err(e) => Message::Error(format!("Failed to refresh items: {}", e)),
|
||||||
|
Ok(items) => Message::LoadedItem(msg, items),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Message::Error(err) => {
|
||||||
|
tracing::error!("Error: {}", err);
|
||||||
|
state.messages.push(err);
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::SetToken(token) => {
|
||||||
|
tracing::info!("Authenticated with token: {}", token);
|
||||||
|
state.jellyfin_client.set_token(token);
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(state: &State) -> Element<'_, Message> {
|
||||||
|
column([header(state), body(state), footer(state)]).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn body(state: &State) -> Element<'_, Message> {
|
||||||
|
container(
|
||||||
|
Grid::with_children(state.cache.items_of(state.current).into_iter().map(card)).spacing(70),
|
||||||
|
)
|
||||||
|
.padding(70)
|
||||||
|
.align_x(Alignment::Center)
|
||||||
|
// .align_y(Alignment::Center)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn header(state: &State) -> Element<'_, Message> {
|
||||||
|
row([
|
||||||
|
container(
|
||||||
|
Text::new(state.jellyfin_client.config.server_url.as_str())
|
||||||
|
.width(Length::Fill)
|
||||||
|
.align_x(Alignment::Start),
|
||||||
|
)
|
||||||
|
.padding(10)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.align_x(Alignment::Start)
|
||||||
|
.align_y(Alignment::Center)
|
||||||
|
.style(container::rounded_box)
|
||||||
|
.into(),
|
||||||
|
container(
|
||||||
|
row([
|
||||||
|
button("Settings").on_press(Message::OpenSettings).into(),
|
||||||
|
button("Refresh").on_press(Message::Refresh).into(),
|
||||||
|
])
|
||||||
|
.spacing(10),
|
||||||
|
)
|
||||||
|
.padding(10)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.align_x(Alignment::End)
|
||||||
|
.align_y(Alignment::Center)
|
||||||
|
.style(container::rounded_box)
|
||||||
|
.into(),
|
||||||
|
])
|
||||||
|
.align_y(Alignment::Center)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(50)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn footer(state: &State) -> Element<'_, Message> {
|
||||||
|
container(
|
||||||
|
column(
|
||||||
|
state
|
||||||
|
.messages
|
||||||
|
.iter()
|
||||||
|
.map(|msg| Text::new(msg).size(12).into())
|
||||||
|
.collect::<Vec<Element<'_, Message>>>(),
|
||||||
|
)
|
||||||
|
.spacing(5),
|
||||||
|
)
|
||||||
|
.padding(10)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Shrink)
|
||||||
|
.style(container::rounded_box)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn card(item: &Item) -> Element<'_, Message> {
|
||||||
|
let name = item
|
||||||
|
.name
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| s.as_ref())
|
||||||
|
.unwrap_or("Unnamed Item");
|
||||||
|
container(
|
||||||
|
column([
|
||||||
|
BlurHash::new(
|
||||||
|
item.thumbnail
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|t| t.blur_hash.as_ref())
|
||||||
|
.map(|s| s.as_ref())
|
||||||
|
.unwrap_or(""),
|
||||||
|
)
|
||||||
|
.width(200)
|
||||||
|
.height(400)
|
||||||
|
.into(),
|
||||||
|
Text::new(name).size(16).into(),
|
||||||
|
])
|
||||||
|
.align_x(Alignment::Center)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill),
|
||||||
|
)
|
||||||
|
.padding(70)
|
||||||
|
.width(Length::FillPortion(5))
|
||||||
|
.height(Length::FillPortion(5))
|
||||||
|
.style(container::rounded_box)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(config: impl Fn() -> api::JellyfinConfig + 'static) -> impl Fn() -> (State, Task<Message>) {
|
||||||
|
move || {
|
||||||
|
let mut jellyfin = api::JellyfinClient::new(config());
|
||||||
|
(
|
||||||
|
State::new(jellyfin.clone()),
|
||||||
|
Task::perform(
|
||||||
|
async move { jellyfin.authenticate_with_cached_token(".session").await },
|
||||||
|
|token| match token {
|
||||||
|
Ok(token) => Message::SetToken(token),
|
||||||
|
Err(e) => Message::Error(format!("Authentication failed: {}", e)),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ui(config: impl Fn() -> api::JellyfinConfig + 'static) -> iced::Result {
|
||||||
|
iced::application(init(config), update, view).run()
|
||||||
|
}
|
||||||
|
|||||||
68
ui-iced/src/shared_string.rs
Normal file
68
ui-iced/src/shared_string.rs
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
use std::{borrow::Cow, sync::Arc};
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct SharedString(ArcCow<'static, str>);
|
||||||
|
|
||||||
|
impl From<String> for SharedString {
|
||||||
|
fn from(s: String) -> Self {
|
||||||
|
SharedString(ArcCow::Owned(Arc::from(s)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> iced::advanced::text::IntoFragment<'a> for SharedString {
|
||||||
|
fn into_fragment(self) -> Cow<'a, str> {
|
||||||
|
match self.0 {
|
||||||
|
ArcCow::Borrowed(b) => Cow::Borrowed(b),
|
||||||
|
ArcCow::Owned(o) => Cow::Owned(o.as_ref().to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> iced::advanced::text::IntoFragment<'a> for &SharedString {
|
||||||
|
fn into_fragment(self) -> Cow<'a, str> {
|
||||||
|
match &self.0 {
|
||||||
|
ArcCow::Borrowed(b) => Cow::Borrowed(b),
|
||||||
|
ArcCow::Owned(o) => Cow::Owned(o.as_ref().to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&'static str> for SharedString {
|
||||||
|
fn from(s: &'static str) -> Self {
|
||||||
|
SharedString(ArcCow::Borrowed(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<str> for SharedString {
|
||||||
|
fn as_ref(&self) -> &str {
|
||||||
|
match &self.0 {
|
||||||
|
ArcCow::Borrowed(b) => b,
|
||||||
|
ArcCow::Owned(o) => o.as_ref(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Deref for SharedString {
|
||||||
|
type Target = str;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
self.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum ArcCow<'a, T: ?Sized> {
|
||||||
|
Borrowed(&'a T),
|
||||||
|
Owned(Arc<T>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> Clone for ArcCow<'a, T>
|
||||||
|
where
|
||||||
|
T: ?Sized,
|
||||||
|
{
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
match self {
|
||||||
|
ArcCow::Borrowed(b) => ArcCow::Borrowed(b),
|
||||||
|
ArcCow::Owned(o) => ArcCow::Owned(Arc::clone(o)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user