diff --git a/.github/workflows/lints-nightly.yml b/.github/workflows/lints-nightly.yml index cea9df35..35eda7b7 100644 --- a/.github/workflows/lints-nightly.yml +++ b/.github/workflows/lints-nightly.yml @@ -13,16 +13,10 @@ jobs: steps: - name: Checkout sources uses: actions/checkout@v3 - - name: Install stable toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: nightly - override: true + - name: Install nightly toolchain + uses: dtolnay/rust-toolchain@nightly - name: Run cargo check - uses: actions-rs/cargo@v1 - with: - command: check + run: cargo check lints_nightly: name: Lints (nightly) @@ -30,20 +24,11 @@ jobs: steps: - name: Checkout sources uses: actions/checkout@v3 - - name: Install stable toolchain - uses: actions-rs/toolchain@v1 + - name: Install nightly toolchain + uses: dtolnay/rust-toolchain@nightly with: - profile: minimal - toolchain: nightly - override: true - components: "rustfmt, clippy" + components: "rustfmt,clippy" - name: Run cargo fmt - uses: actions-rs/cargo@v1 - with: - command: fmt - args: "--all -- --check" + run: cargo fmt --all -- --check - name: Clippy with warnings - uses: actions-rs/cargo@v1 - with: - command: clippy - args: "--all-targets --all-features -- -D warnings" + run: cargo clippy --all-targets --all-features -- -D warnings diff --git a/.github/workflows/lints.yml b/.github/workflows/lints.yml index 94db635e..cff8400f 100644 --- a/.github/workflows/lints.yml +++ b/.github/workflows/lints.yml @@ -19,19 +19,14 @@ jobs: msrv=$(cat crates/plex-api/Cargo.toml | grep rust-version | sed 's/.* = "//; s/"//') echo "msrv=$msrv" >> $GITHUB_OUTPUT - name: Install the toolchain - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@master with: - profile: minimal toolchain: ${{steps.current_msrv.outputs.msrv}} - override: true - uses: Swatinem/rust-cache@v2.2.1 with: shared-key: cache - name: Check - uses: actions-rs/cargo@v1 - with: - command: check - args: --workspace --all-targets + run: cargo check --workspace --all-targets - name: Check the new MSRV if: ${{ failure() }} run: | @@ -45,18 +40,12 @@ jobs: - name: Checkout sources uses: actions/checkout@v3 - name: Install stable toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true + uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2.2.1 with: shared-key: cache - name: Run cargo check - uses: actions-rs/cargo@v1 - with: - command: check + run: cargo check lints: name: Lints @@ -65,22 +54,13 @@ jobs: - name: Checkout sources uses: actions/checkout@v3 - name: Install stable toolchain - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@stable with: - profile: minimal - toolchain: stable - override: true - components: "rustfmt, clippy" + components: "rustfmt,clippy" - uses: Swatinem/rust-cache@v2.2.1 with: shared-key: cache - name: Run cargo fmt - uses: actions-rs/cargo@v1 - with: - command: fmt - args: "--all -- --check" + run: cargo fmt --all -- --check - name: Clippy with warnings - uses: actions-rs/cargo@v1 - with: - command: clippy - args: "--all-targets --all-features -- -D warnings" + run: cargo clippy --all-targets --all-features -- -D warnings diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9f974e68..82221b59 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -49,11 +49,7 @@ jobs: # a new release is created: if: ${{ steps.release.outputs.release_created }} - name: Install stable toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true + uses: dtolnay/rust-toolchain@stable if: ${{ steps.release.outputs.release_created }} - run: cargo publish -p ${{ matrix.crate }} env: diff --git a/.github/workflows/rust-clippy.yml b/.github/workflows/rust-clippy.yml deleted file mode 100644 index 647f3499..00000000 --- a/.github/workflows/rust-clippy.yml +++ /dev/null @@ -1,54 +0,0 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. -# rust-clippy is a tool that runs a bunch of lints to catch common -# mistakes in your Rust code and help improve your Rust code. -# More details at https://github.com/rust-lang/rust-clippy -# and https://rust-lang.github.io/rust-clippy/ - -name: rust-clippy analyze - -on: - push: - branches: [ "main" ] - pull_request: - # The branches below must be a subset of the branches above - branches: [ "main" ] - schedule: - - cron: '44 19 * * 5' - -jobs: - rust-clippy-analyze: - name: Run rust-clippy analyzing - runs-on: ubuntu-latest - permissions: - contents: read - security-events: write - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Install Rust toolchain - uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af #@v1 - with: - profile: minimal - toolchain: stable - components: clippy - override: true - - - name: Install required cargo - run: cargo install clippy-sarif sarif-fmt - - - name: Run rust-clippy - run: - cargo clippy - --all-features - --message-format=json | clippy-sarif | tee rust-clippy-results.sarif | sarif-fmt - continue-on-error: true - - - name: Upload analysis results to GitHub - uses: github/codeql-action/upload-sarif@v2 - with: - sarif_file: rust-clippy-results.sarif - wait-for-processing: true diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f12cf4eb..ef7998bf 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,19 +25,12 @@ jobs: - name: Checkout sources uses: actions/checkout@v3 - name: Install stable toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true + uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2.2.1 with: shared-key: cache - name: Run tests - uses: actions-rs/cargo@v1 - with: - command: run - args: "--package xtask -- test --offline" + run: cargo run --package xtask -- test --offline test_on_real_server_anonymous: if: "!github.event.review" @@ -52,19 +45,12 @@ jobs: - name: Checkout sources uses: actions/checkout@v3 - name: Install stable toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true + uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2.2.1 with: shared-key: cache-anonymous - name: "Test against real server [anonymous]" - uses: actions-rs/cargo@v1 - with: - command: run - args: "--package xtask -- test --online --docker-tag '${{ matrix.plex_server_version }}' --deny-unknown-fields" + run: cargo run --package xtask -- test --online --docker-tag '${{ matrix.plex_server_version }}' --deny-unknown-fields test_on_real_server_authenticated_free: if: "(github.ref == 'refs/heads/main' && !github.event.review) || github.event.review.state == 'approved' || github.repository == github.event.pull_request.head.repo.full_name" @@ -81,19 +67,12 @@ jobs: - name: Checkout sources uses: actions/checkout@v3 - name: Install stable toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true + uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2.2.1 with: shared-key: cache-authenticated - name: "Test against real server [authenticated]" - uses: actions-rs/cargo@v1 - with: - command: run - args: "--package xtask -- test --online --docker-tag '${{ matrix.plex_server_version }}' --token '${{ secrets.PLEX_API_AUTH_TOKEN_FREE }}' --deny-unknown-fields" + run: cargo run --package xtask -- test --online --docker-tag '${{ matrix.plex_server_version }}' --token '${{ secrets.PLEX_API_AUTH_TOKEN_FREE }}' --deny-unknown-fields test_on_real_server_authenticated_plexpass: if: "(github.ref == 'refs/heads/main' && !github.event.review) || github.event.review.state == 'approved' || github.repository == github.event.pull_request.head.repo.full_name" @@ -110,19 +89,12 @@ jobs: - name: Checkout sources uses: actions/checkout@v3 - name: Install stable toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true + uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2.2.1 with: shared-key: cache-authenticated - name: "Test against real server [authenticated]" - uses: actions-rs/cargo@v1 - with: - command: run - args: "--package xtask -- test --online --docker-tag '${{ matrix.plex_server_version }}' --token '${{ secrets.PLEX_API_AUTH_TOKEN_PLEXPASS }}' --deny-unknown-fields" + run: cargo run --package xtask -- test --online --docker-tag '${{ matrix.plex_server_version }}' --token '${{ secrets.PLEX_API_AUTH_TOKEN_PLEXPASS }}' --deny-unknown-fields collect_coverage: if: "!github.event.review" diff --git a/.github/workflows/update_features.yml b/.github/workflows/update_features.yml index bf54dcf4..534d3fdb 100644 --- a/.github/workflows/update_features.yml +++ b/.github/workflows/update_features.yml @@ -12,11 +12,7 @@ jobs: - name: Checkout sources uses: actions/checkout@v3 - name: Install stable toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true + uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2.2.1 with: shared-key: cache @@ -26,10 +22,7 @@ jobs: -H 'Accept: application/json' \ https://plex.tv/api/v2/features\?X-Plex-Product\=Plex%20Web\&X-Plex-Version\=4.77.3\&X-Plex-Client-Identifier\=${{ secrets.X_PLEX_CLIENT_IDENTIFIER_FREE }}\&X-Plex-Platform\=Safari\&X-Plex-Platform-Version\=15.4\&X-Plex-Sync-Version\=2\&X-Plex-Features\=external-media%2Cindirect-media\&X-Plex-Model\=hosted\&X-Plex-Device\=OSX\&X-Plex-Device-Name\=Safari\&X-Plex-Device-Screen-Resolution\=1440x772%2C1440x900\&X-Plex-Token\=${{ secrets.PLEX_API_AUTH_TOKEN_PLEXPASS }}\&X-Plex-Language\=en - name: Build plex-api - uses: actions-rs/cargo@v1 - with: - command: build - args: "--package plex-api" + run: cargo build --package plex-api - name: Check if the enum was updated run: | if git diff --exit-code crates/plex-api/src/media_container/server/feature.rs diff --git a/.github/workflows/update_pms.yml b/.github/workflows/update_pms.yml index 8e5936a8..58363b48 100644 --- a/.github/workflows/update_pms.yml +++ b/.github/workflows/update_pms.yml @@ -12,19 +12,12 @@ jobs: - name: Checkout sources uses: actions/checkout@v3 - name: Install stable toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true + uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2.2.1 with: shared-key: cache - name: Build xtask - uses: actions-rs/cargo@v1 - with: - command: build - args: "--package xtask" + run: cargo build --package xtask - id: tags run: | tags=$(cargo run -q --package xtask -- get-last-plex-tags --skip-tag latest --skip-tag beta --skip-tag plexpass) @@ -43,9 +36,9 @@ jobs: sed -i 's/\(Test on a real server ([a-z ]*)\).* # supported-version-1/\1 (${{ steps.tags.outputs.last_tag }}) # supported-version-1/g' .github/mergify.yml sed -i 's/\(Test on a real server ([a-z ]*)\).* # supported-version-2/\1 (${{ steps.tags.outputs.second_tag }}) # supported-version-2/g' .github/mergify.yml sed -i 's/\(Test on a real server ([a-z ]*)\).* # supported-version-3/\1 (${{ steps.tags.outputs.first_tag }}) # supported-version-3/g' .github/mergify.yml - - name: Update modify-data command + - name: Update xtask/DOCKER_PLEX_IMAGE_TAG_MIN_SUPPORTED run: | - sed -i 's/DEFAULT_TAG: &str = "[-a-z0-9.]*/DEFAULT_TAG: \&str = "${{ steps.tags.outputs.last_tag }}/g' crates/xtask/src/modify_data.rs + sed -i 's/DOCKER_PLEX_IMAGE_TAG_MIN_SUPPORTED: &str = "[-a-z0-9.]*/DOCKER_PLEX_IMAGE_TAG_MIN_SUPPORTED: \&str = "${{ steps.tags.outputs.last_tag }}/g' crates/xtask/src/get_last_plex_tags.rs - name: Update the list in README.md run: | plex_tags="$(echo '${{ steps.tags.outputs.tags }}' | jq -r '.[] | "* \(.)"')" diff --git a/crates/plex-api/Cargo.toml b/crates/plex-api/Cargo.toml index 2cd21bc7..bb71d7ae 100644 --- a/crates/plex-api/Cargo.toml +++ b/crates/plex-api/Cargo.toml @@ -29,6 +29,7 @@ sys-info = "^0.9" monostate = "^0.1.2" serde-aux = "^4.1.2" enum_dispatch = "^0.3.8" +secrecy = { version = "^0.8", features = ["serde"] } [build-dependencies] serde = { version = "^1.0", features = ["derive"] } diff --git a/crates/plex-api/examples/get-token.rs b/crates/plex-api/examples/get-token.rs index 3b39ea2d..ec940ce5 100644 --- a/crates/plex-api/examples/get-token.rs +++ b/crates/plex-api/examples/get-token.rs @@ -11,15 +11,15 @@ async fn main() { let password = prompt_password("Password: ").unwrap(); let mut myplex_result = MyPlexBuilder::default() - .set_username_and_password(&username, &password) + .set_username_and_password(&username, password.clone()) .build() .await; if let Err(plex_api::Error::OtpRequired) = myplex_result { let otp = prompt_password("OTP: ").unwrap(); myplex_result = MyPlexBuilder::default() - .set_username_and_password(&username, &password) - .set_otp(&otp) + .set_username_and_password(&username, password) + .set_otp(otp) .build() .await; } diff --git a/crates/plex-api/examples/link.rs b/crates/plex-api/examples/link.rs index 02c83c8d..24907f38 100644 --- a/crates/plex-api/examples/link.rs +++ b/crates/plex-api/examples/link.rs @@ -7,7 +7,7 @@ async fn main() { let code = prompt_password("Code: ").unwrap(); MyPlexBuilder::default() - .set_token(&token) + .set_token(token) .build() .await .unwrap() diff --git a/crates/plex-api/examples/signout.rs b/crates/plex-api/examples/signout.rs index 630ddeae..2f981bf7 100644 --- a/crates/plex-api/examples/signout.rs +++ b/crates/plex-api/examples/signout.rs @@ -6,7 +6,7 @@ async fn main() { let token = prompt_password("Token: ").unwrap(); MyPlexBuilder::default() - .set_token(&token) + .set_token(token) .build() .await .unwrap() diff --git a/crates/plex-api/src/http_client.rs b/crates/plex-api/src/http_client.rs index 2631964d..f0dd953d 100644 --- a/crates/plex-api/src/http_client.rs +++ b/crates/plex-api/src/http_client.rs @@ -7,6 +7,7 @@ use isahc::{ AsyncBody, AsyncReadResponseExt, HttpClient as IsahcHttpClient, Request as HttpRequest, Response as HttpResponse, }; +use secrecy::{ExposeSecret, SecretString}; use serde::de::DeserializeOwned; use std::time::Duration; use uuid::Uuid; @@ -65,7 +66,7 @@ pub struct HttpClient { /// `X-Plex-Token` header value. /// /// Auth token for Plex. - x_plex_token: String, + x_plex_token: SecretString, /// `X-Plex-Sync-Version` header value. /// @@ -90,8 +91,8 @@ impl HttpClient { let mut request = HttpRequest::builder() .header("X-Plex-Client-Identifier", &self.x_plex_client_identifier); - if !self.x_plex_token.is_empty() { - request = request.header("X-Plex-Token", &self.x_plex_token); + if !self.x_plex_token.expose_secret().is_empty() { + request = request.header("X-Plex-Token", self.x_plex_token.expose_secret()); } request @@ -99,7 +100,7 @@ impl HttpClient { /// Verifies that this client has an authentication token. pub fn is_authenticated(&self) -> bool { - !self.x_plex_token.is_empty() + !self.x_plex_token.expose_secret().is_empty() } /// Begins building a request using the HTTP POST method. @@ -221,22 +222,25 @@ impl HttpClient { http_client: &self.http_client, base_url: self.api_url.clone(), path_and_query: path, - request_builder: self.prepare_request().method("DELETE"), + request_builder: self.prepare_request_min().method("DELETE"), timeout: Some(DEFAULT_TIMEOUT), } } /// Set the client's authentication token. - pub fn set_x_plex_token(self, x_plex_token: String) -> Self { + pub fn set_x_plex_token(self, x_plex_token: T) -> Self + where + T: Into, + { Self { - x_plex_token, + x_plex_token: x_plex_token.into(), ..self } } /// Get a reference to the client's authentication token. pub fn x_plex_token(&self) -> &str { - self.x_plex_token.as_ref() + self.x_plex_token.expose_secret() } } @@ -389,7 +393,7 @@ impl Default for HttpClientBuilder { x_plex_device_name: sys_hostname, x_plex_client_identifier: random_uuid.to_string(), x_plex_sync_version: String::from("2"), - x_plex_token: String::new(), + x_plex_token: SecretString::new("".to_owned()), }; Self { client: Ok(client) } @@ -444,7 +448,7 @@ impl HttpClientBuilder { pub fn set_x_plex_token(self, token: String) -> Self { Self { client: self.client.map(move |mut client| { - client.x_plex_token = token; + client.x_plex_token = token.into(); client }), } diff --git a/crates/plex-api/src/media_container/devices.rs b/crates/plex-api/src/media_container/devices.rs index 4cad50c4..38d8241c 100644 --- a/crates/plex-api/src/media_container/devices.rs +++ b/crates/plex-api/src/media_container/devices.rs @@ -1,3 +1,4 @@ +use secrecy::SecretString; use serde::Deserialize; use serde_plain::derive_fromstr_from_deserialize; use serde_with::{formats::CommaSeparator, serde_as, NoneAsEmptyString, StringWithSeparator}; @@ -54,9 +55,9 @@ pub struct Device { #[serde(rename = "@id")] pub id: Option, #[serde(rename = "@token")] - pub token: Option, + pub token: Option, #[serde(rename = "@accessToken")] - pub access_token: Option, + pub access_token: Option, #[serde(with = "time::serde::timestamp", rename = "@createdAt")] pub created_at: OffsetDateTime, #[serde(with = "time::serde::timestamp", rename = "@lastSeenAt")] @@ -83,14 +84,18 @@ pub struct Device { pub owned: Option, #[serde(rename = "SyncList")] pub sync_list: Option, - #[serde(default, rename = "@authToken")] - pub auth_token: String, + #[serde(default = "create_empty_secret_string", rename = "@authToken")] + pub auth_token: SecretString, #[serde(rename = "@dnsRebindingProtection")] pub dns_rebinding_protection: Option, #[serde(rename = "@natLoopbackSupported")] pub nat_loopback_supported: Option, } +fn create_empty_secret_string() -> SecretString { + SecretString::new("".to_string()) +} + #[derive(Debug, Deserialize, Clone)] #[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))] pub struct SyncList { diff --git a/crates/plex-api/src/media_container/server/mod.rs b/crates/plex-api/src/media_container/server/mod.rs index 140634de..a2f41f1c 100644 --- a/crates/plex-api/src/media_container/server/mod.rs +++ b/crates/plex-api/src/media_container/server/mod.rs @@ -6,6 +6,7 @@ use self::library::ContentDirectory; use serde::Deserialize; use serde_plain::derive_fromstr_from_deserialize; use serde_with::{formats::CommaSeparator, serde_as, StringWithSeparator}; +use time::OffsetDateTime; #[derive(Debug, Deserialize, Clone)] pub struct Action { @@ -117,6 +118,15 @@ pub struct MediaProvider { pub friendly_name: Option, } +#[derive(Debug, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub enum StartState { + StartingPlugins, + #[cfg(not(feature = "tests_deny_unknown_fields"))] + #[serde(other)] + Other, +} + #[serde_as] #[derive(Debug, Deserialize, Clone)] #[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))] @@ -155,7 +165,7 @@ pub struct Server { pub plugin_host: bool, pub push_notifications: bool, pub read_only_libraries: bool, - pub start_state: Option, + pub start_state: Option, #[serde(rename = "streamingBrainABRVersion")] pub streaming_brain_abr_version: u8, pub streaming_brain_version: u8, @@ -171,7 +181,8 @@ pub struct Server { pub transcoder_video_qualities: Vec, #[serde_as(as = "StringWithSeparator::")] pub transcoder_video_resolutions: Vec, - pub updated_at: i64, + #[serde(with = "time::serde::timestamp")] + pub updated_at: OffsetDateTime, pub updater: bool, pub version: String, pub voice_search: bool, diff --git a/crates/plex-api/src/myplex/account.rs b/crates/plex-api/src/myplex/account.rs index 0520f7b0..0f27b676 100644 --- a/crates/plex-api/src/myplex/account.rs +++ b/crates/plex-api/src/myplex/account.rs @@ -1,3 +1,4 @@ +use secrecy::SecretString; use serde::{Deserialize, Deserializer}; use serde_repr::Deserialize_repr; use serde_with::{json::JsonString, serde_as}; @@ -67,8 +68,8 @@ pub struct Subscription { pub struct Service { pub identifier: String, pub endpoint: String, - pub token: Option, - pub secret: Option, + pub token: Option, + pub secret: Option, pub status: String, } @@ -88,7 +89,7 @@ pub struct MyPlexAccount { pub email_only_auth: bool, pub has_password: bool, pub cloud_sync_device: Option, - pub auth_token: String, + pub auth_token: SecretString, pub mailing_list_status: Option, pub mailing_list_active: bool, pub scrobble_types: String, diff --git a/crates/plex-api/src/myplex/mod.rs b/crates/plex-api/src/myplex/mod.rs index 41b76310..465899d1 100644 --- a/crates/plex-api/src/myplex/mod.rs +++ b/crates/plex-api/src/myplex/mod.rs @@ -16,6 +16,7 @@ use crate::url::{MYPLEX_SIGNIN_PATH, MYPLEX_SIGNOUT_PATH, MYPLEX_USER_INFO_PATH} use crate::{Error, Result, Server}; use http::StatusCode; use isahc::{AsyncBody, AsyncReadResponseExt, Response as HttpResponse}; +use secrecy::{ExposeSecret, SecretString}; use std::sync::Arc; #[derive(Debug, Clone)] @@ -169,10 +170,10 @@ impl MyPlex { #[derive(Debug, Clone)] pub struct MyPlexBuilder<'a> { client: Option, - token: Option<&'a str>, + token: Option, username: Option<&'a str>, - password: Option<&'a str>, - otp: Option<&'a str>, + password: Option, + otp: Option, test_token_auth: bool, } @@ -221,9 +222,15 @@ impl MyPlexBuilder<'_> { if let (Some(username), Some(password)) = (self.username, self.password) { if let Some(otp) = self.otp { - return MyPlex::login_with_otp(username, password, otp, client).await; + return MyPlex::login_with_otp( + username, + password.expose_secret(), + otp.expose_secret(), + client, + ) + .await; } else { - return MyPlex::login(username, password, client).await; + return MyPlex::login(username, password.expose_secret(), client).await; } } @@ -232,7 +239,7 @@ impl MyPlexBuilder<'_> { } if let Some(token) = self.token { - client = client.set_x_plex_token(token.to_owned()); + client = client.set_x_plex_token(token); } let mut plex_result = MyPlex::new(client); @@ -248,10 +255,13 @@ impl MyPlexBuilder<'_> { } impl<'a> MyPlexBuilder<'a> { - pub fn set_token(self, token: &'a str) -> Self { + pub fn set_token(self, token: T) -> Self + where + T: Into, + { Self { client: self.client, - token: Some(token), + token: Some(token.into()), username: self.username, password: self.password, otp: self.otp, @@ -259,24 +269,30 @@ impl<'a> MyPlexBuilder<'a> { } } - pub fn set_username_and_password(self, username: &'a str, password: &'a str) -> Self { + pub fn set_username_and_password(self, username: &'a str, password: T) -> Self + where + T: Into, + { Self { client: self.client, token: self.token, username: Some(username), - password: Some(password), + password: Some(password.into()), otp: self.otp, test_token_auth: self.test_token_auth, } } - pub fn set_otp(self, otp: &'a str) -> Self { + pub fn set_otp(self, otp: T) -> Self + where + T: Into, + { Self { client: self.client, token: self.token, username: self.username, password: self.password, - otp: Some(otp), + otp: Some(otp.into()), test_token_auth: self.test_token_auth, } } diff --git a/crates/plex-api/tests/myplex_auth.rs b/crates/plex-api/tests/myplex_auth.rs index 98b3de78..0782066b 100644 --- a/crates/plex-api/tests/myplex_auth.rs +++ b/crates/plex-api/tests/myplex_auth.rs @@ -28,7 +28,7 @@ mod offline { let plex_result = MyPlexBuilder::default() .set_client(client_anonymous) - .set_username_and_password("username", "password") + .set_username_and_password("username", "password".to_string()) .build() .await; m.assert(); @@ -59,7 +59,7 @@ mod offline { let plex_result = MyPlexBuilder::default() .set_client(client_anonymous) - .set_username_and_password("username", "password") + .set_username_and_password("username", "password".to_string()) .build() .await; m.assert(); @@ -90,7 +90,7 @@ mod offline { let plex_result = MyPlexBuilder::default() .set_client(client_anonymous) - .set_username_and_password("username", "password") + .set_username_and_password("username", "password".to_string()) .build() .await; m.assert(); @@ -118,8 +118,8 @@ mod offline { let plex_result = MyPlexBuilder::default() .set_client(client_anonymous) - .set_username_and_password("username", "password") - .set_otp("123456") + .set_username_and_password("username", "password".to_string()) + .set_otp("123456".to_string()) .build() .await; m.assert(); @@ -196,7 +196,7 @@ mod offline { let plex_result = MyPlexBuilder::default() .set_client(client_anonymous) - .set_username_and_password("username", "password") + .set_username_and_password("username", "password".to_string()) .build() .await; assert!( @@ -208,8 +208,8 @@ mod offline { let plex_result = MyPlexBuilder::default() .set_client(client2) - .set_username_and_password("username", "password") - .set_otp("123456") + .set_username_and_password("username", "password".to_string()) + .set_otp("123456".to_string()) .build() .await; assert!( diff --git a/crates/plex-cli/src/flags.rs b/crates/plex-cli/src/flags.rs index 39a337d2..2ce7dd42 100644 --- a/crates/plex-cli/src/flags.rs +++ b/crates/plex-cli/src/flags.rs @@ -15,6 +15,9 @@ xflags::xflags! { /// How long to wait for the success. optional --timeout seconds: u32 + + /// Wait for the full server start, not only the general availability (might take a few minutes) + optional --full } /// Manage server preferences. @@ -69,6 +72,7 @@ pub enum PlexCliCmd { pub struct Wait { pub delay: Option, pub timeout: Option, + pub full: bool, } #[derive(Debug)] diff --git a/crates/plex-cli/src/wait.rs b/crates/plex-cli/src/wait.rs index 2f3f7a81..d602d614 100644 --- a/crates/plex-cli/src/wait.rs +++ b/crates/plex-cli/src/wait.rs @@ -1,25 +1,36 @@ use crate::flags; use plex_api::HttpClientBuilder; +use std::time::{Duration, SystemTime}; impl flags::Wait { - pub(crate) async fn run(&self, server: &str, auth_token: &str) -> anyhow::Result<()> { + pub(crate) async fn run(&self, host: &str, auth_token: &str) -> anyhow::Result<()> { let client = HttpClientBuilder::default() .set_x_plex_token(auth_token.to_string()) .build()?; - let start_time = std::time::SystemTime::now(); - let time_limit = std::time::Duration::from_secs(self.timeout.unwrap_or(120) as u64); - let sleep_duration = std::time::Duration::from_secs(self.delay.unwrap_or(1) as u64); + let start_time = SystemTime::now(); + let wait_full_start = self.full; + let time_limit = Duration::from_secs(self.timeout.unwrap_or({ + if wait_full_start { + 300 + } else { + 120 + } + }) as u64); + let sleep_duration = Duration::from_secs(self.delay.unwrap_or(1) as u64); while start_time.elapsed()? < time_limit { - let server_result = plex_api::Server::new(server, client.clone()).await; + let server_result = plex_api::Server::new(host, client.clone()).await; if let Ok(server) = server_result { - let prefs = server.preferences().await; - if prefs.is_ok() { - println!( - "Ready in {:.2} seconds!", - start_time.elapsed()?.as_secs_f32() - ); - return Ok(()); + // The `start_state` is None when the server has finished loading. + if server.media_container.start_state.is_none() || !wait_full_start { + let prefs = server.preferences().await; + if prefs.is_ok() { + println!( + "Ready in {:.2} seconds!", + start_time.elapsed()?.as_secs_f32() + ); + return Ok(()); + } } } diff --git a/crates/xtask/src/flags.rs b/crates/xtask/src/flags.rs index 16c98302..ac0679c3 100644 --- a/crates/xtask/src/flags.rs +++ b/crates/xtask/src/flags.rs @@ -6,7 +6,7 @@ xflags::xflags! { /// Run the tests. cmd test { /// A tag from https://hub.docker.com/r/plexinc/pms-docker/tags to use for the tests. - /// By default, `latest` is used. + /// By default, the min supported version is used (see README.md for details). /// /// WARNING! When you specify a tag without defining `--plex-data-path` /// the default path will be changed to `plex-data-{tag}`. diff --git a/crates/xtask/src/get_last_plex_tags.rs b/crates/xtask/src/get_last_plex_tags.rs index 4830c6d3..f3d2f2c7 100644 --- a/crates/xtask/src/get_last_plex_tags.rs +++ b/crates/xtask/src/get_last_plex_tags.rs @@ -9,6 +9,7 @@ const DEFAULT_VERSION_JUMP: u8 = 1; pub(crate) const DOCKER_PLEX_IMAGE_NAME: &str = "plexinc/pms-docker"; pub(crate) const DOCKER_PLEX_IMAGE_TAG_LATEST: &str = "latest"; +pub(crate) const DOCKER_PLEX_IMAGE_TAG_MIN_SUPPORTED: &str = "1.27.2.5929-a806c5905"; impl flags::GetLastPlexTags { pub(crate) fn run(self) -> anyhow::Result<()> { diff --git a/crates/xtask/src/modify_data.rs b/crates/xtask/src/modify_data.rs index 2b4d6146..c6bc2cd5 100644 --- a/crates/xtask/src/modify_data.rs +++ b/crates/xtask/src/modify_data.rs @@ -1,10 +1,12 @@ -use crate::{flags, get_last_plex_tags::DOCKER_PLEX_IMAGE_NAME, utils::copy_tree}; +use crate::{ + flags, + get_last_plex_tags::{DOCKER_PLEX_IMAGE_NAME, DOCKER_PLEX_IMAGE_TAG_MIN_SUPPORTED}, + utils::copy_tree, +}; use std::{fs::remove_dir_all, io::Write}; use testcontainers::{clients, core::WaitFor, images::generic::GenericImage, RunnableImage}; use xshell::{cmd, Shell}; -const DEFAULT_TAG: &str = "1.27.2.5929-a806c5905"; - impl flags::ModifyData { pub(crate) fn run(self, sh: &Shell) -> anyhow::Result<()> { // First rebuild the data if desired @@ -40,7 +42,7 @@ impl flags::ModifyData { let image_tag = self .docker_tag .clone() - .unwrap_or_else(|| DEFAULT_TAG.to_owned()); + .unwrap_or_else(|| DOCKER_PLEX_IMAGE_TAG_MIN_SUPPORTED.to_owned()); let docker_image: RunnableImage = GenericImage::new(DOCKER_PLEX_IMAGE_NAME, &image_tag) diff --git a/crates/xtask/src/test.rs b/crates/xtask/src/test.rs index cc226ac0..fc532ad0 100644 --- a/crates/xtask/src/test.rs +++ b/crates/xtask/src/test.rs @@ -1,5 +1,7 @@ -use super::get_last_plex_tags::{DOCKER_PLEX_IMAGE_NAME, DOCKER_PLEX_IMAGE_TAG_LATEST}; -use crate::flags; +use crate::{ + flags, get_last_plex_tags::DOCKER_PLEX_IMAGE_NAME, + get_last_plex_tags::DOCKER_PLEX_IMAGE_TAG_MIN_SUPPORTED, +}; use plex_api::MyPlexBuilder; use std::io::Write; use testcontainers::{clients, core::WaitFor, images::generic::GenericImage, RunnableImage}; @@ -62,7 +64,7 @@ impl flags::Test { let claim_token = tokio::runtime::Runtime::new()?.block_on(async { MyPlexBuilder::default() - .set_token(token) + .set_token(token.to_owned()) .set_test_token_auth(false) .build() .await @@ -94,7 +96,7 @@ impl flags::Test { let image_tag = self .docker_tag .clone() - .unwrap_or_else(|| DOCKER_PLEX_IMAGE_TAG_LATEST.to_owned()); + .unwrap_or_else(|| DOCKER_PLEX_IMAGE_TAG_MIN_SUPPORTED.to_owned()); let docker_image: RunnableImage = GenericImage::new(DOCKER_PLEX_IMAGE_NAME, &image_tag) .with_wait_for(WaitFor::Healthcheck) @@ -137,7 +139,7 @@ impl flags::Test { let docker = clients::Cli::default(); - print!("// Spawning docker container... "); + print!("// Spawning docker container {DOCKER_PLEX_IMAGE_NAME}:{image_tag}... "); let _ = std::io::stdout().flush(); let _plex_node = docker.run(docker_image); @@ -148,7 +150,7 @@ impl flags::Test { let server_url = format!("http://localhost:{}/", _plex_node.get_host_port_ipv4(32400)); cmd!( sh, - "cargo run -p plex-cli -- --server {server_url} --token {auth_token} wait" + "cargo run -q -p plex-cli -- --server {server_url} --token {auth_token} wait --full" ) .run()?;