diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 24c6a3799..336ff67e6 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -1,5 +1,32 @@ # Changelog + +## 0.0.3 (2024-05-13) + +### Added + +- ✅ Fix stump_config tests ([#331](https://github.com/stumpapp/stump/issues/331)) [[a98f86f](https://github.com/stumpapp/stump/commit/a98f86f31fd8ce9c7b55ed26b6f5068dfe33c8e1)] + +### Fixed + +- ✏️ Fix dead links ([#337](https://github.com/stumpapp/stump/issues/337)) [[a46fca4](https://github.com/stumpapp/stump/commit/a46fca42a246f9eef78ef1409581e4b4d56a234f)] +- 🐛 Allow canceling islanded jobs [[dcedea4](https://github.com/stumpapp/stump/commit/dcedea47210488c5fa3ca2de493e5228ecb3d664)] +- 🐛 Fix paged reader oversized height [[de86463](https://github.com/stumpapp/stump/commit/de864637956f1b7d30ab80d648769785361a4f3e)] +- 🐛 Fix `check-for-update` false positive ([#333](https://github.com/stumpapp/stump/issues/333)) [[316cd09](https://github.com/stumpapp/stump/commit/316cd091b8c829c4acf4df00af4e47f86f428db2)] +- 🐛 Fix cast error for file size calculation ([#330](https://github.com/stumpapp/stump/issues/330)) [[5ac5aec](https://github.com/stumpapp/stump/commit/5ac5aec97934bf8dc7760c90ab042c4c9c8d0f38)] +- 🐛 Fix image reader navigation (tablet) ([#329](https://github.com/stumpapp/stump/issues/329)) [[abb00de](https://github.com/stumpapp/stump/commit/abb00de4f43f16ba1cf7d1f2d7858fffcf6e751e)] +- 🐛 Fix inverted swipe handlers in EPUB reader ([#324](https://github.com/stumpapp/stump/issues/324)) [[7be6eda](https://github.com/stumpapp/stump/commit/7be6eda9c649e02b57bfc6a0f8b150124212fff7)] +- 💚 Fix `yarn` timeout issues during install step ([#319](https://github.com/stumpapp/stump/issues/319)) [[a71fbe1](https://github.com/stumpapp/stump/commit/a71fbe158ef2ebb7b4404e3dae8cc041988ef09f)] + +### Miscellaneous + +- Merge pull request [#335](https://github.com/stumpapp/stump/issues/335) from stumpapp/al/misc-fixes [[a3e8c7b](https://github.com/stumpapp/stump/commit/a3e8c7b268f63f9ef9a13ca04f07fe6fc087b961)] +- Merge branch 'develop' into al/misc-fixes [[93c2290](https://github.com/stumpapp/stump/commit/93c2290fb5a9553293fd1d090e6f5434a139d6a1)] +- 🌐 Update translations ([#332](https://github.com/stumpapp/stump/issues/332)) [[bf82e01](https://github.com/stumpapp/stump/commit/bf82e01cc69b7749dd17152905f875c302d45ef9)] +- 🩹 Fix dark styles in light theme ([#326](https://github.com/stumpapp/stump/issues/326)) [[61e6fb4](https://github.com/stumpapp/stump/commit/61e6fb4f1753a4b92f861893301999a181ee0759)] +- 🌐 Update translations ([#318](https://github.com/stumpapp/stump/issues/318)) [[58c168d](https://github.com/stumpapp/stump/commit/58c168dca3c706cf5f835312f968caec391bab4d)] + + ## 0.0.2 (2024-04-21) diff --git a/Cargo.lock b/Cargo.lock index 381aade81..01a3591e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -913,7 +913,7 @@ dependencies = [ [[package]] name = "codegen" -version = "0.0.2" +version = "0.0.3" [[package]] name = "codespan-reporting" @@ -2976,7 +2976,7 @@ dependencies = [ [[package]] name = "integrations" -version = "0.0.1" +version = "0.0.3" dependencies = [ "async-trait", "dotenv", @@ -6396,7 +6396,7 @@ dependencies = [ [[package]] name = "stump_core" -version = "0.0.2" +version = "0.0.3" dependencies = [ "alphanumeric-sort", "async-channel", @@ -6423,6 +6423,7 @@ dependencies = [ "serde-xml-rs", "serde_json", "specta", + "temp-env", "tempfile", "thiserror", "tokio", @@ -6456,7 +6457,7 @@ dependencies = [ [[package]] name = "stump_server" -version = "0.0.2" +version = "0.0.3" dependencies = [ "async-stream", "async-trait", @@ -6835,6 +6836,15 @@ dependencies = [ "windows 0.39.0", ] +[[package]] +name = "temp-env" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96374855068f47402c3121c6eed88d29cb1de8f3ab27090e273e420bdabcf050" +dependencies = [ + "parking_lot 0.12.1", +] + [[package]] name = "tempdir" version = "0.3.7" diff --git a/Cargo.toml b/Cargo.toml index 6fdd90ea9..11b667fed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ members = [ ] [workspace.package] -version = "0.0.2" +version = "0.0.3" rust-version = "1.77.2" [workspace.dependencies] diff --git a/apps/desktop/package.json b/apps/desktop/package.json index b62eaf1a3..8709924d1 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,6 +1,6 @@ { "name": "@stump/desktop", - "version": "0.0.2", + "version": "0.0.3", "description": "", "license": "MIT", "scripts": { diff --git a/apps/expo/package.json b/apps/expo/package.json index 67560c83d..16897e8c1 100644 --- a/apps/expo/package.json +++ b/apps/expo/package.json @@ -51,6 +51,6 @@ "tailwindcss": "3.3.2" }, "name": "@stump/mobile", - "version": "0.0.2", + "version": "0.0.3", "private": true } diff --git a/apps/server/package.json b/apps/server/package.json index 75ba46596..3f596a389 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -1,7 +1,7 @@ { "name": "@stump/server", "private": true, - "version": "0.0.2", + "version": "0.0.3", "scripts": { "lint": "cargo clippy --package stump_server -- -D warnings", "format": "cargo fmt --package stump_server", diff --git a/apps/server/src/routers/api/v1/mod.rs b/apps/server/src/routers/api/v1/mod.rs index 8712b611e..46be6710a 100644 --- a/apps/server/src/routers/api/v1/mod.rs +++ b/apps/server/src/routers/api/v1/mod.rs @@ -137,11 +137,15 @@ async fn check_for_updates() -> APIResult> { if github_response.status().is_success() { let github_json: serde_json::Value = github_response.json().await?; - let latest_semver = github_json["tag_name"].as_str().ok_or_else(|| { + let mut latest_semver = github_json["tag_name"].as_str().ok_or_else(|| { APIError::InternalServerError( "Failed to parse latest release tag name".to_string(), ) })?; + if latest_semver.starts_with('v') && latest_semver.len() > 1 { + latest_semver = &latest_semver[1..]; + } + let has_update_available = latest_semver != current_semver; Ok(Json(UpdateCheck { diff --git a/apps/web/package.json b/apps/web/package.json index f67ec83a5..6415179c2 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@stump/web", - "version": "0.0.2", + "version": "0.0.3", "description": "", "license": "MIT", "scripts": { diff --git a/core/Cargo.toml b/core/Cargo.toml index 53570015e..6e5532327 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -52,6 +52,7 @@ regex = "1.10.4" alphanumeric-sort = "1.5.3" [dev-dependencies] +temp-env = "0.3.6" tempfile = { workspace = true } criterion = { version = "0.5.1", features = ["html_reports", "async_tokio"] } diff --git a/core/prisma/migrations/20240428193209_bigint_media_size/migration.sql b/core/prisma/migrations/20240428193209_bigint_media_size/migration.sql new file mode 100644 index 000000000..1250f371a --- /dev/null +++ b/core/prisma/migrations/20240428193209_bigint_media_size/migration.sql @@ -0,0 +1,28 @@ +/* + Warnings: + + - You are about to alter the column `size` on the `media` table. The data in that column could be lost. The data in that column will be cast from `Int` to `BigInt`. + +*/ +-- RedefineTables +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_media" ( + "id" TEXT NOT NULL PRIMARY KEY, + "name" TEXT NOT NULL, + "size" BIGINT NOT NULL, + "extension" TEXT NOT NULL, + "pages" INTEGER NOT NULL, + "updated_at" DATETIME NOT NULL, + "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "modified_at" DATETIME, + "hash" TEXT, + "path" TEXT NOT NULL, + "status" TEXT NOT NULL DEFAULT 'READY', + "series_id" TEXT, + CONSTRAINT "media_series_id_fkey" FOREIGN KEY ("series_id") REFERENCES "series" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); +INSERT INTO "new_media" ("created_at", "extension", "hash", "id", "modified_at", "name", "pages", "path", "series_id", "size", "status", "updated_at") SELECT "created_at", "extension", "hash", "id", "modified_at", "name", "pages", "path", "series_id", "size", "status", "updated_at" FROM "media"; +DROP TABLE "media"; +ALTER TABLE "new_media" RENAME TO "media"; +PRAGMA foreign_key_check; +PRAGMA foreign_keys=ON; diff --git a/core/prisma/schema.prisma b/core/prisma/schema.prisma index da6570cfe..4aa104e36 100644 --- a/core/prisma/schema.prisma +++ b/core/prisma/schema.prisma @@ -178,7 +178,7 @@ model Media { id String @id @default(uuid()) name String // derived from filename - size Int // in bytes + size BigInt // in bytes extension String pages Int updated_at DateTime @updatedAt diff --git a/core/src/config/stump_config.rs b/core/src/config/stump_config.rs index 26f084319..ce9607f89 100644 --- a/core/src/config/stump_config.rs +++ b/core/src/config/stump_config.rs @@ -525,42 +525,49 @@ mod tests { #[test] fn test_getting_config_from_environment() { - // Set environment variables - env::set_var(PROFILE_KEY, "release"); - env::set_var(PORT_KEY, "1337"); - env::set_var(VERBOSITY_KEY, "3"); - env::set_var(DB_PATH_KEY, "not_a_real_path"); - env::set_var(CLIENT_KEY, "not_a_real_dir"); - env::set_var(CONFIG_DIR_KEY, "also_not_a_real_dir"); - env::set_var(DISABLE_SWAGGER_KEY, "true"); - env::set_var(HASH_COST_KEY, "24"); - env::set_var(SESSION_TTL_KEY, (3600 * 24).to_string()); - env::set_var(SESSION_EXPIRY_INTERVAL_KEY, (60 * 60 * 8).to_string()); - - // Create a new StumpConfig and load values from the environment. - let config = StumpConfig::new("not_a_dir".to_string()) - .with_environment() - .unwrap(); - - // Confirm values are as expected - assert_eq!( - config, - StumpConfig { - profile: "release".to_string(), - port: 1337, - verbosity: 3, - pretty_logs: true, - db_path: Some("not_a_real_path".to_string()), - client_dir: "not_a_real_dir".to_string(), - config_dir: "also_not_a_real_dir".to_string(), - allowed_origins: vec![], - pdfium_path: None, - disable_swagger: true, - password_hash_cost: 24, - session_ttl: 3600 * 24, - expired_session_cleanup_interval: 60 * 60 * 8, - scanner_chunk_size: DEFAULT_SCANNER_CHUNK_SIZE, - } + temp_env::with_vars( + [ + (PROFILE_KEY, Some("release")), + (PORT_KEY, Some("1337")), + (VERBOSITY_KEY, Some("2")), + (DB_PATH_KEY, Some("not_a_real_path")), + (CLIENT_KEY, Some("not_a_real_dir")), + (CONFIG_DIR_KEY, Some("also_not_a_real_dir")), + (DISABLE_SWAGGER_KEY, Some("true")), + (HASH_COST_KEY, Some("24")), + (SESSION_TTL_KEY, Some(&(3600 * 24).to_string())), + ( + SESSION_EXPIRY_INTERVAL_KEY, + Some(&(60 * 60 * 8).to_string()), + ), + ], + || { + // Create a new StumpConfig and load values from the environment. + let config = StumpConfig::new("not_a_dir".to_string()) + .with_environment() + .unwrap(); + + // Confirm values are as expected + assert_eq!( + config, + StumpConfig { + profile: "release".to_string(), + port: 1337, + verbosity: 2, + pretty_logs: true, + db_path: Some("not_a_real_path".to_string()), + client_dir: "not_a_real_dir".to_string(), + config_dir: "also_not_a_real_dir".to_string(), + allowed_origins: vec![], + pdfium_path: None, + disable_swagger: true, + password_hash_cost: 24, + session_ttl: 3600 * 24, + expired_session_cleanup_interval: 60 * 60 * 8, + scanner_chunk_size: DEFAULT_SCANNER_CHUNK_SIZE, + } + ); + }, ); } @@ -670,38 +677,46 @@ mod tests { #[test] fn test_simulate_first_boot() { - env::set_var(PORT_KEY, "1337"); - env::set_var(VERBOSITY_KEY, "2"); - env::set_var(DISABLE_SWAGGER_KEY, "true"); - env::set_var(HASH_COST_KEY, "1"); - - let tempdir = tempfile::tempdir().expect("Failed to create temporary directory"); - // Now we can create a StumpConfig rooted at the temporary directory - let config_dir = tempdir.path().to_string_lossy().to_string(); - let generated = StumpConfig::new(config_dir.clone()) - .with_config_file() - .expect("Failed to generate StumpConfig from Stump.toml") - .with_environment() - .expect("Failed to generate StumpConfig from environment"); - - let expected = StumpConfig { - profile: "debug".to_string(), - port: 1337, - verbosity: 2, - pretty_logs: true, - db_path: None, - client_dir: "./dist".to_string(), - config_dir, - allowed_origins: vec![], - pdfium_path: None, - disable_swagger: true, - password_hash_cost: 1, - session_ttl: DEFAULT_SESSION_TTL, - expired_session_cleanup_interval: DEFAULT_SESSION_EXPIRY_CLEANUP_INTERVAL, - scanner_chunk_size: DEFAULT_SCANNER_CHUNK_SIZE, - }; - - assert_eq!(generated, expected); + temp_env::with_vars( + [ + (PORT_KEY, Some("1337")), + (VERBOSITY_KEY, Some("2")), + (DISABLE_SWAGGER_KEY, Some("true")), + (HASH_COST_KEY, Some("1")), + ], + || { + let tempdir = + tempfile::tempdir().expect("Failed to create temporary directory"); + // Now we can create a StumpConfig rooted at the temporary directory + let config_dir = tempdir.path().to_string_lossy().to_string(); + let generated = StumpConfig::new(config_dir.clone()) + .with_config_file() + .expect("Failed to generate StumpConfig from Stump.toml") + .with_environment() + .expect("Failed to generate StumpConfig from environment"); + + assert_eq!( + generated, + StumpConfig { + profile: "debug".to_string(), + port: 1337, + verbosity: 2, + pretty_logs: true, + db_path: None, + client_dir: "./dist".to_string(), + config_dir, + allowed_origins: vec![], + pdfium_path: None, + disable_swagger: true, + password_hash_cost: 1, + session_ttl: DEFAULT_SESSION_TTL, + expired_session_cleanup_interval: + DEFAULT_SESSION_EXPIRY_CLEANUP_INTERVAL, + scanner_chunk_size: DEFAULT_SCANNER_CHUNK_SIZE, + } + ); + }, + ); } fn get_mock_config_file() -> String { diff --git a/core/src/db/entity/media/entity.rs b/core/src/db/entity/media/entity.rs index 3e5f90a41..7e88377d0 100644 --- a/core/src/db/entity/media/entity.rs +++ b/core/src/db/entity/media/entity.rs @@ -21,7 +21,7 @@ pub struct Media { /// The name of the media. ex: "The Amazing Spider-Man (2018) #69" pub name: String, /// The size of the media in bytes. - pub size: i32, + pub size: i64, /// The file extension of the media. ex: "cbz" pub extension: String, /// The number of pages in the media. ex: "69" diff --git a/core/src/filesystem/media/builder.rs b/core/src/filesystem/media/builder.rs index 799fc04c2..3fc3590ef 100644 --- a/core/src/filesystem/media/builder.rs +++ b/core/src/filesystem/media/builder.rs @@ -65,7 +65,7 @@ impl MediaBuilder { (m.len(), last_modified_at) })?; let size = raw_size.try_into().unwrap_or_else(|_| { - tracing::error!(?raw_size, "Failed to convert file size to i32"); + tracing::error!(?raw_size, ?path, "Failed to convert file size to i64"); 0 }); diff --git a/core/src/job/error.rs b/core/src/job/error.rs index 01b76380f..8a3c09dd0 100644 --- a/core/src/job/error.rs +++ b/core/src/job/error.rs @@ -45,6 +45,8 @@ pub enum JobManagerError { JobMissingId, #[error("Job failed to be persisted: {0}")] JobPersistFailed(String), + #[error("A job was found which was in a deeply invalid state")] + JobLostError, #[error("A query error occurred {0}")] QueryError(#[from] prisma_client_rust::QueryError), #[error("An unknown error occurred {0}")] diff --git a/core/src/job/manager.rs b/core/src/job/manager.rs index 5b5c7f594..57b9489b0 100644 --- a/core/src/job/manager.rs +++ b/core/src/job/manager.rs @@ -178,7 +178,23 @@ impl JobManager { }, ); } else { - return Err(JobManagerError::JobNotFound(job_id)); + let islanded_job = self + .client + .job() + .find_first(vec![ + job::id::equals(job_id.clone()), + job::status::equals(JobStatus::Running.to_string()), + ]) + .exec() + .await? + .ok_or_else(|| JobManagerError::JobNotFound(job_id.clone()))?; + tracing::warn!( + ?islanded_job, + "Job was found in an invalid state, attempting to cancel" + ); + handle_do_cancel(job_id.clone(), &self.client, Duration::from_secs(0)) + .await?; + return Err(JobManagerError::JobLostError); } Ok(()) diff --git a/core/src/job/mod.rs b/core/src/job/mod.rs index 95de626ab..c43259478 100644 --- a/core/src/job/mod.rs +++ b/core/src/job/mod.rs @@ -15,7 +15,7 @@ // - https://github.com/Nukesor/pueue use std::{collections::VecDeque, fmt::Debug, sync::Arc, time::Duration}; -use prisma_client_rust::chrono::{DateTime, Utc}; +use prisma_client_rust::chrono::{self, DateTime, Utc}; use serde::{de, Deserialize, Serialize}; mod controller; @@ -502,23 +502,31 @@ pub trait Executor: Send + Sync { let output_data = serde_json::to_vec(&output.output) .map_err(|error| JobError::StateSaveFailed(error.to_string()))?; + let tx_timeout = chrono::Duration::seconds(60).num_milliseconds() as u64; let persisted_job_with_data = db - .job() - .update( - job::id::equals(job_id.to_string()), - vec![ - job::save_state::set(None), - job::output_data::set(Some(output_data)), - job::status::set(JobStatus::Completed.to_string()), - job::ms_elapsed::set( - elapsed.as_millis().try_into().unwrap_or_else(|e| { - tracing::error!(error = ?e, "Wow! You defied logic and overflowed an i64 during the attempt to convert job duration to milliseconds. It must have been a long 292_471_208 years!"); - i64::MAX - }), - ), - ], - ) - .exec() + ._transaction() + .with_max_wait(tx_timeout) + .with_timeout(tx_timeout) + .run(|client| async move { + client + .job() + .update( + job::id::equals(job_id.to_string()), + vec![ + job::save_state::set(None), + job::output_data::set(Some(output_data)), + job::status::set(JobStatus::Completed.to_string()), + job::ms_elapsed::set( + elapsed.as_millis().try_into().unwrap_or_else(|e| { + tracing::error!(error = ?e, "Wow! You defied logic and overflowed an i64 during the attempt to convert job duration to milliseconds. It must have been a long 292_471_208 years!"); + i64::MAX + }), + ), + ], + ) + .exec() + .await + }) .await .map_err(|error| JobError::StateSaveFailed(error.to_string()))?; tracing::trace!(?persisted_job_with_data, "Persisted completed job to DB"); @@ -546,21 +554,29 @@ pub trait Executor: Send + Sync { )); } + let tx_timeout = chrono::Duration::seconds(60).num_milliseconds() as u64; let _persisted_job = db - .job() - .update( - job::id::equals(job_id.to_string()), - vec![ - job::status::set(status.to_string()), - job::ms_elapsed::set( - elapsed.as_millis().try_into().unwrap_or_else(|e| { - tracing::error!(error = ?e, "Wow! You defied logic and overflowed an i64 during the attempt to convert job duration to milliseconds. It must have been a long 292_471_208 years!"); - i64::MAX - }), - ), - ], - ) - .exec() + ._transaction() + .with_max_wait(tx_timeout) + .with_timeout(tx_timeout) + .run(|client| async move { + client + .job() + .update( + job::id::equals(job_id.to_string()), + vec![ + job::status::set(status.to_string()), + job::ms_elapsed::set( + elapsed.as_millis().try_into().unwrap_or_else(|e| { + tracing::error!(error = ?e, "Wow! You defied logic and overflowed an i64 during the attempt to convert job duration to milliseconds. It must have been a long 292_471_208 years!"); + i64::MAX + }), + ), + ], + ) + .exec() + .await + }) .await .map_err(|error| JobError::StateSaveFailed(error.to_string()))?; @@ -795,6 +811,8 @@ impl Executor for WrappedJob { // Put the inner job back into the WrappedJob self.inner_job = Some(inner_job); + tracing::info!(?job_id, ?job_name, "Job execution complete"); + Ok(ExecutorOutput { output: working_output.into_json(), logs, diff --git a/core/src/opds/author.rs b/core/src/opds/author.rs index 07ee01f55..fa3208d13 100644 --- a/core/src/opds/author.rs +++ b/core/src/opds/author.rs @@ -13,6 +13,15 @@ pub struct StumpAuthor { pub uri: Option, } +impl Default for StumpAuthor { + fn default() -> Self { + Self { + name: "Stump".to_string(), + uri: Some("https://github.com/stumpapp/stump".to_string()), + } + } +} + impl StumpAuthor { /// Creates a new author. pub fn new(name: String, uri: Option) -> StumpAuthor { @@ -56,7 +65,7 @@ mod tests { #[test] fn test_author_with_only_name() { - let author = StumpAuthor::new("Aaron Leopold".to_string(), None); + let author = StumpAuthor::new("Stump".to_string(), None); let mut writer = EventWriter::new(Vec::new()); author.write(&mut writer).unwrap(); @@ -66,7 +75,7 @@ mod tests { r#" - Aaron Leopold + Stump "#, ); @@ -77,7 +86,7 @@ mod tests { #[test] fn test_author_with_name_and_uri() { let author = StumpAuthor::new( - "Aaron Leopold".to_string(), + "Stump".to_string(), Some("https://www.stumpapp.dev/".to_string()), ); @@ -89,7 +98,7 @@ mod tests { r#" - Aaron Leopold + Stump https://www.stumpapp.dev/ "#, diff --git a/core/src/opds/feed.rs b/core/src/opds/feed.rs index c076233b2..36596c1cd 100644 --- a/core/src/opds/feed.rs +++ b/core/src/opds/feed.rs @@ -11,6 +11,7 @@ use tracing::warn; use xml::{writer::XmlEvent, EventWriter}; use super::{ + author::StumpAuthor, entry::OpdsEntry, link::{OpdsLinkRel, OpdsLinkType}, util, @@ -86,6 +87,9 @@ impl OpdsFeed { util::write_xml_element("title", &self.title, &mut writer)?; util::write_xml_element("updated", &updated.to_rfc3339(), &mut writer)?; + let author = StumpAuthor::default(); + author.write(&mut writer)?; + if let Some(links) = &self.links { for link in links { link.write(&mut writer)?; @@ -318,6 +322,10 @@ mod tests { feed_id Feed Title {{{INSERT}}} + + Stump + https://github.com/stumpapp/stump + Modern Online Philately urn:uuid:6409a00b-7bf2-405e-826c-3fdff0fd0734 diff --git a/crates/integrations/Cargo.toml b/crates/integrations/Cargo.toml index 0fd391a2d..492437e7a 100644 --- a/crates/integrations/Cargo.toml +++ b/crates/integrations/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "integrations" -version = "0.0.1" +version = "0.0.3" edition = "2021" [dependencies] diff --git a/crates/integrations/src/notifier/discord_client.rs b/crates/integrations/src/notifier/discord_client.rs index 4bb5013e2..a98b9675a 100644 --- a/crates/integrations/src/notifier/discord_client.rs +++ b/crates/integrations/src/notifier/discord_client.rs @@ -76,6 +76,7 @@ mod tests { DiscordClient::new(webhook_url) } + #[ignore = "No token"] #[tokio::test] async fn test_send_message() { let client = get_debug_client(); diff --git a/crates/integrations/src/notifier/telegram_client.rs b/crates/integrations/src/notifier/telegram_client.rs index 363a6f205..8f67cc65d 100644 --- a/crates/integrations/src/notifier/telegram_client.rs +++ b/crates/integrations/src/notifier/telegram_client.rs @@ -61,6 +61,8 @@ mod tests { let chat_id = std::env::var("DUMMY_TG_CHAT_ID").expect("Failed to load chat ID"); TelegramClient::new(token, chat_id) } + + #[ignore = "No token"] #[tokio::test] async fn test_send_message() { let client = get_debug_client(); diff --git a/docker/Dockerfile b/docker/Dockerfile index 1cdedfea6..e5711900a 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -9,9 +9,12 @@ WORKDIR /app COPY . . -RUN yarn install; \ - yarn web build; \ - mv ./apps/web/dist build +# https://github.com/nodejs/docker-node/issues/1335 +RUN yarn config set network-timeout 300000 && \ + yarn install --frozen-lockfile && \ + yarn web build && \ + mv ./apps/web/dist/ ./build && \ + if [ ! -d "./build" ] || [ ! "$(ls -A ./build)" ]; then exit 1; fi # ------------------------------------------------------------------------------ # Cargo Build Stage @@ -78,7 +81,8 @@ COPY --from=frontend /app/build /app/client COPY docker/entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh; \ - ln -s /opt/pdfium/lib/libpdfium.so /lib/libpdfium.so + ln -s /opt/pdfium/lib/libpdfium.so /lib/libpdfium.so; \ + if [ ! -d "/app/client" ] || [ ! "$(ls -A /app/client)" ]; then exit 1; fi # Default Stump environment variables ENV STUMP_CONFIG_DIR=/config \ diff --git a/docs/pages/guides/basics/books.md b/docs/pages/guides/basics/books.md index 3b3703366..c1e201819 100644 --- a/docs/pages/guides/basics/books.md +++ b/docs/pages/guides/basics/books.md @@ -3,7 +3,7 @@ The backbone of Stump! There are a couple key concepts to go over regarding how Stump represents books: - Books (also internally referred to as `media`) primarily refer to files on disk -- Books are grouped into [`series`](/guides/series), which are then grouped into [`libraries`](/guides/libraries) +- Books are grouped into [`series`](/guides/basics/series), which are then grouped into [`libraries`](/guides/basics/libraries) - [Metadata](#metadata) is an associated set of information _about_ a book, such as its title, author, etc. ## Supported formats diff --git a/package.json b/package.json index 9a544bbb0..12355f74c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@stump/monorepo", - "version": "0.0.2", + "version": "0.0.3", "repository": "https://github.com/stumpapp/stump.git", "author": "Aaron Leopold ", "license": "MIT", diff --git a/packages/api/package.json b/packages/api/package.json index 44f630fe1..fa8aedfeb 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,6 +1,6 @@ { "name": "@stump/api", - "version": "0.0.2", + "version": "0.0.3", "description": "", "main": "src/index.ts", "exports": { diff --git a/packages/browser/package.json b/packages/browser/package.json index e977b8eeb..b09bf6eae 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -1,6 +1,6 @@ { "name": "@stump/browser", - "version": "0.0.2", + "version": "0.0.3", "description": "", "license": "MIT", "private": true, diff --git a/packages/browser/src/components/media/MediaCard.tsx b/packages/browser/src/components/media/MediaCard.tsx index 5b736db13..8263099f7 100644 --- a/packages/browser/src/components/media/MediaCard.tsx +++ b/packages/browser/src/components/media/MediaCard.tsx @@ -75,7 +75,7 @@ export default function MediaCard({ return (
- {formatBytes(media.size)} + {formatBytes(media.size.valueOf())}
) diff --git a/packages/browser/src/components/navigation/sidebar/SideBarButtonLink.tsx b/packages/browser/src/components/navigation/sidebar/SideBarButtonLink.tsx index eb2e7aa88..115c9b75a 100644 --- a/packages/browser/src/components/navigation/sidebar/SideBarButtonLink.tsx +++ b/packages/browser/src/components/navigation/sidebar/SideBarButtonLink.tsx @@ -23,7 +23,7 @@ export default function SideBarButtonLink({ return (
{ + /** + * A callback to navigate backward in the book, wrt the natural reading + * progression direction. + * + * If the reading direction is RTL, then the backward navigation is actually + * forward in the book. + */ + const onBackwardNavigation = useCallback(() => { if (invertControls) { onPaginateForward() } else { @@ -30,7 +35,14 @@ export default function EpubNavigationControls({ children }: Props) { } }, [invertControls, onPaginateBackward, onPaginateForward]) - const onRightNavigate = useCallback(() => { + /** + * A callback to navigate forward in the book, wrt the natural reading + * progression direction. + * + * If the reading direction is RTL, then the forward navigation is actually + * backwards in the book. + */ + const onForwardNavigation = useCallback(() => { if (invertControls) { onPaginateBackward() } else { @@ -38,16 +50,21 @@ export default function EpubNavigationControls({ children }: Props) { } }, [invertControls, onPaginateBackward, onPaginateForward]) + /** + * A swipe handler to navigate forward or backward in the book. + * + * Note that the swip handler function semantics are inverted wrt the reading direction. + */ const swipeHandlers = useSwipeable({ - onSwipedLeft: onLeftNavigate, - onSwipedRight: onRightNavigate, + onSwipedLeft: onForwardNavigation, + onSwipedRight: onBackwardNavigation, preventScrollOnSwipe: true, }) return (