From 4e09e80f5d258839506965e08061a0e47f9e3987 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Fri, 12 Mar 2021 08:54:08 -0800 Subject: [PATCH 1/9] auto check for updates --- crates/core/src/addon.rs | 8 +- crates/core/src/repository/mod.rs | 173 +++++++++++++++++++++++++++++- src/gui/mod.rs | 7 +- src/gui/update.rs | 112 ++++++++++++++++++- 4 files changed, 296 insertions(+), 4 deletions(-) diff --git a/crates/core/src/addon.rs b/crates/core/src/addon.rs index ddaa5224..c7d1988c 100644 --- a/crates/core/src/addon.rs +++ b/crates/core/src/addon.rs @@ -195,6 +195,12 @@ impl Addon { self.repository = Some(repo_package); } + pub fn set_remote_package_from_repo_package(&mut self, repo_package: &RepositoryPackage) { + if let Some(repo) = self.repository.as_mut() { + repo.metadata.remote_packages = repo_package.metadata.remote_packages.clone(); + } + } + pub fn update_addon_folders(&mut self, mut folders: Vec) { if !folders.is_empty() { folders.sort_by(|a, b| a.id.cmp(&b.id)); @@ -237,7 +243,7 @@ impl Addon { } } - fn repository(&self) -> Option<&RepositoryPackage> { + pub fn repository(&self) -> Option<&RepositoryPackage> { self.repository.as_ref() } diff --git a/crates/core/src/repository/mod.rs b/crates/core/src/repository/mod.rs index 0c72bff6..b8f1a781 100644 --- a/crates/core/src/repository/mod.rs +++ b/crates/core/src/repository/mod.rs @@ -1,7 +1,8 @@ use crate::config::Flavor; -use crate::error::RepositoryError; +use crate::error::{DownloadError, RepositoryError}; use chrono::{DateTime, Utc}; +use futures::future::join_all; use isahc::http::uri::Uri; use serde::{Deserialize, Serialize}; @@ -356,3 +357,173 @@ pub struct RepositoryIdentifiers { pub struct Changelog { pub text: Option, } + +pub async fn batch_refresh_repository_packages( + flavor: Flavor, + repos: &[RepositoryPackage], +) -> Result, DownloadError> { + let curse_ids = repos + .iter() + .filter(|r| r.kind == RepositoryKind::Curse) + .map(|r| r.id.parse::().ok()) + .flatten() + .collect::>(); + let tukui_ids = repos + .iter() + .filter(|r| r.kind == RepositoryKind::Tukui) + .map(|r| r.id.clone()) + .collect::>(); + let wowi_ids = repos + .iter() + .filter(|r| r.kind == RepositoryKind::WowI) + .map(|r| r.id.clone()) + .collect::>(); + let townlong_ids = repos + .iter() + .filter(|r| r.kind == RepositoryKind::TownlongYak) + .map(|r| r.id.clone()) + .collect::>(); + let git_urls = repos + .iter() + .filter(|r| matches!(r.kind, RepositoryKind::Git(_))) + .map(|r| r.id.clone()) + .collect::>(); + + // Get all curse repo packages + let curse_repo_packages = if !curse_ids.is_empty() { + let curse_packages = curse::fetch_remote_packages_by_ids(&curse_ids).await?; + + let mut curse_repo_packages = vec![]; + + curse_repo_packages.extend( + curse_packages + .into_iter() + .map(|package| { + ( + package.id.to_string(), + curse::metadata_from_curse_package(flavor, package), + ) + }) + .filter_map(|(id, metadata)| { + RepositoryPackage::from_repo_id(flavor, RepositoryKind::Curse, id) + .map(|r| r.with_metadata(metadata)) + .ok() + }), + ); + + curse_repo_packages + } else { + vec![] + }; + + // Get all tukui repo packages + let tukui_repo_packages = if !tukui_ids.is_empty() { + let fetch_tasks: Vec<_> = tukui_ids + .iter() + .map(|id| tukui::fetch_remote_package(&id, &flavor)) + .collect(); + + join_all(fetch_tasks) + .await + .into_iter() + .filter_map(Result::ok) + .map(|(id, package)| (id, tukui::metadata_from_tukui_package(package))) + .filter_map(|(id, metadata)| { + RepositoryPackage::from_repo_id(flavor, RepositoryKind::Tukui, id) + .ok() + .map(|r| r.with_metadata(metadata)) + }) + .collect::>() + } else { + vec![] + }; + + // Get all wowi repo packages + let wowi_repo_packages = if !wowi_ids.is_empty() { + let wowi_packages = wowi::fetch_remote_packages(&wowi_ids).await?; + + wowi_packages + .into_iter() + .map(|package| { + ( + package.id.to_string(), + wowi::metadata_from_wowi_package(package), + ) + }) + .filter_map(|(id, metadata)| { + RepositoryPackage::from_repo_id(flavor, RepositoryKind::WowI, id) + .ok() + .map(|r| r.with_metadata(metadata)) + }) + .collect::>() + } else { + vec![] + }; + + // Get all townlong repo packages + let townlong_repo_packages = if !townlong_ids.is_empty() { + let townlong_packages = townlongyak::fetch_remote_packages(flavor, &townlong_ids).await?; + + townlong_packages + .into_iter() + .map(|package| { + ( + package.id.to_string(), + townlongyak::metadata_from_townlong_package(flavor, package), + ) + }) + .filter_map(|(id, metadata)| { + RepositoryPackage::from_repo_id(flavor, RepositoryKind::TownlongYak, id) + .ok() + .map(|r| r.with_metadata(metadata)) + }) + .collect::>() + } else { + vec![] + }; + + // Get all git repo packages + let git_repo_packages = if !git_urls.is_empty() { + let fetch_tasks = git_urls + .iter() + .map(|url| { + let url = url + .parse::() + .map_err(|_| RepositoryError::GitInvalidUrl { url: url.clone() })?; + + RepositoryPackage::from_source_url(flavor, url) + }) + .filter_map(|result| match result { + Ok(package) => Some(package), + Err(e) => { + log::error!("{}", e); + None + } + }) + .map(|mut package| async { + if let Err(e) = package.resolve_metadata().await { + log::error!("{}", e); + Err(e) + } else { + Ok(package) + } + }); + + join_all(fetch_tasks) + .await + .into_iter() + .filter_map(Result::ok) + .collect::>() + } else { + vec![] + }; + + Ok([ + &curse_repo_packages[..], + &tukui_repo_packages[..], + &wowi_repo_packages[..], + &townlong_repo_packages[..], + &git_repo_packages[..], + ] + .concat()) +} diff --git a/src/gui/mod.rs b/src/gui/mod.rs index 36afdd60..183eab11 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -15,7 +15,7 @@ use ajour_core::{ config::{ColumnConfig, ColumnConfigV2, Config, Flavor, Language, SelfUpdateChannel}, error::*, fs::PersistentData, - repository::{Changelog, GlobalReleaseChannel, ReleaseChannel}, + repository::{Changelog, GlobalReleaseChannel, ReleaseChannel, RepositoryPackage}, theme::{load_user_themes, Theme}, utility::{self, get_latest_release}, }; @@ -168,6 +168,8 @@ pub enum Message { ParsedAuras((Flavor, Result, ajour_weak_auras::Error>)), AurasUpdated((Flavor, Result, ajour_weak_auras::Error>)), FetchedChangelog((Addon, Result)), + CheckRepositoryUpdates(Instant), + RepositoryPackagesFetched((Flavor, Result, DownloadError>)), } pub struct Ajour { @@ -323,11 +325,14 @@ impl Application for Ajour { iced_futures::time::every(Duration::from_secs(60 * 5)).map(Message::RefreshCatalog); let new_release_subscription = iced_futures::time::every(Duration::from_secs(60 * 60)) .map(Message::CheckLatestRelease); + let check_updates_subscription = iced_futures::time::every(Duration::from_secs(60 * 30)) + .map(Message::CheckRepositoryUpdates); iced::Subscription::batch(vec![ runtime_subscription, catalog_subscription, new_release_subscription, + check_updates_subscription, ]) } diff --git a/src/gui/update.rs b/src/gui/update.rs index 1fdef864..160a38c3 100644 --- a/src/gui/update.rs +++ b/src/gui/update.rs @@ -20,7 +20,9 @@ use { fs::{delete_addons, delete_saved_variables, install_addon, PersistentData}, network::download_addon, parse::{read_addon_directory, update_addon_fingerprint}, - repository::{Changelog, RepositoryKind, RepositoryPackage}, + repository::{ + batch_refresh_repository_packages, Changelog, RepositoryKind, RepositoryPackage, + }, utility::{download_update_to_temp_file, get_latest_release, wow_path_resolution}, }, ajour_weak_auras::{Aura, AuraStatus}, @@ -527,6 +529,104 @@ pub fn handle_message(ajour: &mut Ajour, message: Message) -> Result {} } } + Message::CheckRepositoryUpdates(_) => { + log::debug!("Message::CheckRepositoryUpdates"); + + let mut commands = vec![]; + + for flavor in Flavor::ALL.iter() { + if let Some(addons) = ajour.addons.get(flavor) { + let repos = addons + .iter() + .map(|a| a.repository().cloned()) + .flatten() + .collect::>(); + + commands.push(Command::perform( + perform_batch_refresh_repository_packages(*flavor, repos), + Message::RepositoryPackagesFetched, + )); + } + } + + return Ok(Command::batch(commands)); + } + Message::RepositoryPackagesFetched((flavor, result)) => { + match result.context(format!( + "Failed to fetch repository packages for {}", + flavor + )) { + Ok(packages) => { + log::debug!( + "Message::RepositoryPackagesFetched({}, {} packages)", + flavor, + packages.len() + ); + + let mut has_update = 0; + + let addons = ajour.addons.entry(flavor).or_default(); + let global_release_channel = ajour.config.addons.global_release_channel; + + // For each addon, check if an updated repository package exists. If it does, + // we will apply that updated package to the addon, then check if + // the addon is updatable. + for addon in addons.iter_mut() { + if let Some(package) = packages.iter().find(|p| { + Some(p.id.as_str()) == addon.repository_id() + && Some(p.kind) == addon.repository_kind() + }) { + // Update remote packages from refeshed repository package + // + // We don't want to replace the entire Repo Package of the addon + // because we don't want to modify certain metadata such as File Id, + // since we didn't use fingerprints to get these updated packages. We + // just want to reference the "latest" remote packages from the repo, + // and assign those to the Addon so we can check for new updates + addon.set_remote_package_from_repo_package(package); + + // Check if addon is updatable. + if let Some(package) = + addon.relevant_release_package(global_release_channel) + { + if addon.is_updatable(&package) { + log::debug!( + "{} - Update is available for {}, {} -> {}", + flavor, + addon.title(), + addon.version().unwrap_or_default(), + package.version + ); + + addon.state = AddonState::Updatable; + + has_update += 1; + } else { + addon.state = AddonState::Idle; + } + } + } + } + + if has_update == 0 { + log::debug!("{} - No addon updates available", flavor); + } else { + // Addons have updates, resort by status to put them up top + sort_addons( + addons, + global_release_channel, + SortDirection::Desc, + ColumnKey::Status, + ); + ajour.header_state.previous_sort_direction = Some(SortDirection::Desc); + ajour.header_state.previous_column_key = Some(ColumnKey::Status); + } + } + Err(error) => { + log_error(&error); + } + } + } Message::ParsedAddons((flavor, result)) => { let global_release_channel = ajour.config.addons.global_release_channel; @@ -2228,6 +2328,16 @@ async fn perform_fetch_changelog( (addon, changelog) } +async fn perform_batch_refresh_repository_packages( + flavor: Flavor, + repos: Vec, +) -> (Flavor, Result, DownloadError>) { + ( + flavor, + batch_refresh_repository_packages(flavor, &repos).await, + ) +} + fn sort_addons( addons: &mut [Addon], global_release_channel: GlobalReleaseChannel, From 4403be60b056777cbf6f601f44e071aac3932aff Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Fri, 12 Mar 2021 09:20:48 -0800 Subject: [PATCH 2/9] dont reset status when no update is available --- src/gui/update.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/gui/update.rs b/src/gui/update.rs index 160a38c3..6e8c00ca 100644 --- a/src/gui/update.rs +++ b/src/gui/update.rs @@ -601,8 +601,6 @@ pub fn handle_message(ajour: &mut Ajour, message: Message) -> Result Date: Fri, 12 Mar 2021 09:46:30 -0800 Subject: [PATCH 3/9] refactor batch repo package functions --- crates/core/src/parse.rs | 163 ++---------------- crates/core/src/repository/backend/curse.rs | 66 ++++++- crates/core/src/repository/backend/git.rs | 52 ++++++ crates/core/src/repository/backend/mod.rs | 2 +- .../src/repository/backend/townlongyak.rs | 33 +++- crates/core/src/repository/backend/tukui.rs | 34 +++- crates/core/src/repository/backend/wowi.rs | 33 +++- crates/core/src/repository/mod.rs | 128 +------------- 8 files changed, 232 insertions(+), 279 deletions(-) diff --git a/crates/core/src/parse.rs b/crates/core/src/parse.rs index 2e59133b..cf5cc631 100644 --- a/crates/core/src/parse.rs +++ b/crates/core/src/parse.rs @@ -2,18 +2,17 @@ use crate::{ addon::{Addon, AddonFolder, AddonState}, cache::{self, AddonCache, AddonCacheEntry, ExternalReleaseId, FingerprintCache}, config::Flavor, - error::{CacheError, DownloadError, ParseError, RepositoryError}, + error::{CacheError, DownloadError, ParseError}, fs::PersistentData, murmur2::calculate_hash, repository::{ - curse, townlongyak, tukui, wowi, RepositoryIdentifiers, RepositoryKind, RepositoryPackage, + curse, git, townlongyak, tukui, wowi, RepositoryIdentifiers, RepositoryKind, + RepositoryPackage, }, utility::format_interface_into_game_version, }; use async_std::sync::{Arc, Mutex}; use fancy_regex::Regex; -use futures::future::join_all; -use isahc::http::Uri; use once_cell::sync::Lazy; use rayon::prelude::*; use serde::{Deserialize, Serialize}; @@ -521,61 +520,8 @@ async fn get_all_repo_packages( } // Get all curse repo packages - let curse_repo_packages = if !curse_ids.is_empty() { - let mut curse_packages = curse::fetch_remote_packages_by_ids(&curse_ids).await?; - - let mut curse_repo_packages = vec![]; - - // Get repo packages from fingerprint exact matches - curse_repo_packages.extend( - fingerprint_info - .exact_matches - .iter() - .map(|info| { - ( - info.id.to_string(), - curse::metadata_from_fingerprint_info(flavor, info), - ) - }) - .filter_map(|(id, metadata)| { - RepositoryPackage::from_repo_id(flavor, RepositoryKind::Curse, id) - .map(|r| r.with_metadata(metadata)) - .ok() - }), - ); - - // Remove any packages that match a fingerprint entry and update missing - // metadata fields with that package info - curse_repo_packages.iter_mut().for_each(|r| { - if let Some(idx) = curse_packages.iter().position(|p| p.id.to_string() == r.id) { - let package = curse_packages.remove(idx); - - r.metadata.title = Some(package.name.clone()); - r.metadata.website_url = Some(package.website_url.clone()); - r.metadata.changelog_url = Some(format!("{}/files", package.website_url)); - } - }); - - curse_repo_packages.extend( - curse_packages - .into_iter() - .map(|package| { - ( - package.id.to_string(), - curse::metadata_from_curse_package(flavor, package), - ) - }) - .filter_map(|(id, metadata)| { - RepositoryPackage::from_repo_id(flavor, RepositoryKind::Curse, id) - .map(|r| r.with_metadata(metadata)) - .ok() - }), - ); - - curse_repo_packages - } else { - vec![] - }; + let curse_repo_packages = + curse::batch_fetch_repo_packages(flavor, &curse_ids, Some(fingerprint_info)).await?; log::debug!( "{} - {} curse packages fetched", @@ -584,26 +530,7 @@ async fn get_all_repo_packages( ); // Get all tukui repo packages - let tukui_repo_packages = if !tukui_ids.is_empty() { - let fetch_tasks: Vec<_> = tukui_ids - .iter() - .map(|id| tukui::fetch_remote_package(&id, &flavor)) - .collect(); - - join_all(fetch_tasks) - .await - .into_iter() - .filter_map(Result::ok) - .map(|(id, package)| (id, tukui::metadata_from_tukui_package(package))) - .filter_map(|(id, metadata)| { - RepositoryPackage::from_repo_id(flavor, RepositoryKind::Tukui, id) - .ok() - .map(|r| r.with_metadata(metadata)) - }) - .collect::>() - } else { - vec![] - }; + let tukui_repo_packages = tukui::batch_fetch_repo_packages(flavor, &tukui_ids).await?; log::debug!( "{} - {} tukui packages fetched", @@ -612,26 +539,7 @@ async fn get_all_repo_packages( ); // Get all wowi repo packages - let wowi_repo_packages = if !wowi_ids.is_empty() { - let wowi_packages = wowi::fetch_remote_packages(&wowi_ids).await?; - - wowi_packages - .into_iter() - .map(|package| { - ( - package.id.to_string(), - wowi::metadata_from_wowi_package(package), - ) - }) - .filter_map(|(id, metadata)| { - RepositoryPackage::from_repo_id(flavor, RepositoryKind::WowI, id) - .ok() - .map(|r| r.with_metadata(metadata)) - }) - .collect::>() - } else { - vec![] - }; + let wowi_repo_packages = wowi::batch_fetch_repo_packages(flavor, &wowi_ids).await?; log::debug!( "{} - {} wowi packages fetched", @@ -640,26 +548,8 @@ async fn get_all_repo_packages( ); // Get all townlong repo packages - let townlong_repo_packages = if !townlong_ids.is_empty() { - let townlong_packages = townlongyak::fetch_remote_packages(flavor, &townlong_ids).await?; - - townlong_packages - .into_iter() - .map(|package| { - ( - package.id.to_string(), - townlongyak::metadata_from_townlong_package(flavor, package), - ) - }) - .filter_map(|(id, metadata)| { - RepositoryPackage::from_repo_id(flavor, RepositoryKind::TownlongYak, id) - .ok() - .map(|r| r.with_metadata(metadata)) - }) - .collect::>() - } else { - vec![] - }; + let townlong_repo_packages = + townlongyak::batch_fetch_repo_packages(flavor, &townlong_ids).await?; log::debug!( "{} - {} townlong packages fetched", @@ -668,40 +558,7 @@ async fn get_all_repo_packages( ); // Get all git repo packages - let git_repo_packages = if !git_urls.is_empty() { - let fetch_tasks = git_urls - .iter() - .map(|url| { - let url = url - .parse::() - .map_err(|_| RepositoryError::GitInvalidUrl { url: url.clone() })?; - - RepositoryPackage::from_source_url(flavor, url) - }) - .filter_map(|result| match result { - Ok(package) => Some(package), - Err(e) => { - log::error!("{}", e); - None - } - }) - .map(|mut package| async { - if let Err(e) = package.resolve_metadata().await { - log::error!("{}", e); - Err(e) - } else { - Ok(package) - } - }); - - join_all(fetch_tasks) - .await - .into_iter() - .filter_map(Result::ok) - .collect::>() - } else { - vec![] - }; + let git_repo_packages = git::batch_fetch_repo_packages(flavor, &git_urls).await?; log::debug!( "{} - {} git packages fetched", diff --git a/crates/core/src/repository/backend/curse.rs b/crates/core/src/repository/backend/curse.rs index 64baf34d..242c0269 100644 --- a/crates/core/src/repository/backend/curse.rs +++ b/crates/core/src/repository/backend/curse.rs @@ -2,7 +2,7 @@ use super::*; use crate::config::Flavor; use crate::error::DownloadError; use crate::network::{post_json_async, request_async}; -use crate::repository::{ReleaseChannel, RemotePackage}; +use crate::repository::{ReleaseChannel, RemotePackage, RepositoryKind, RepositoryPackage}; use crate::utility::{regex_html_tags_to_newline, regex_html_tags_to_space, truncate}; use async_trait::async_trait; @@ -172,6 +172,70 @@ pub(crate) fn metadata_from_fingerprint_info( metadata } +pub(crate) async fn batch_fetch_repo_packages( + flavor: Flavor, + curse_ids: &[i32], + fingerprint_info: Option<&FingerprintInfo>, +) -> Result, DownloadError> { + let mut curse_repo_packages = vec![]; + + if curse_ids.is_empty() { + return Ok(curse_repo_packages); + } + + let mut curse_packages = curse::fetch_remote_packages_by_ids(&curse_ids).await?; + + if let Some(fingerprint_info) = fingerprint_info { + // Get repo packages from fingerprint exact matches + curse_repo_packages.extend( + fingerprint_info + .exact_matches + .iter() + .map(|info| { + ( + info.id.to_string(), + curse::metadata_from_fingerprint_info(flavor, info), + ) + }) + .filter_map(|(id, metadata)| { + RepositoryPackage::from_repo_id(flavor, RepositoryKind::Curse, id) + .map(|r| r.with_metadata(metadata)) + .ok() + }), + ); + + // Remove any packages that match a fingerprint entry and update missing + // metadata fields with that package info + curse_repo_packages.iter_mut().for_each(|r| { + if let Some(idx) = curse_packages.iter().position(|p| p.id.to_string() == r.id) { + let package = curse_packages.remove(idx); + + r.metadata.title = Some(package.name.clone()); + r.metadata.website_url = Some(package.website_url.clone()); + r.metadata.changelog_url = Some(format!("{}/files", package.website_url)); + } + }); + } + + curse_repo_packages.extend( + curse_packages + .into_iter() + .map(|package| { + ( + package.id.to_string(), + curse::metadata_from_curse_package(flavor, package), + ) + }) + .filter_map(|(id, metadata)| { + RepositoryPackage::from_repo_id(flavor, RepositoryKind::Curse, id) + .map(|r| r.with_metadata(metadata)) + .ok() + }), + ); + + Ok(curse_repo_packages) +} + pub(crate) async fn fetch_remote_packages_by_fingerprint( fingerprints: &[u32], ) -> Result { diff --git a/crates/core/src/repository/backend/git.rs b/crates/core/src/repository/backend/git.rs index abbbc3b8..506453df 100644 --- a/crates/core/src/repository/backend/git.rs +++ b/crates/core/src/repository/backend/git.rs @@ -1,6 +1,58 @@ +use crate::config::Flavor; +use crate::error::{DownloadError, RepositoryError}; +use crate::repository::RepositoryPackage; + +use futures::future::join_all; +use isahc::http::Uri; + pub use github::Github; pub use gitlab::Gitlab; +pub(crate) async fn batch_fetch_repo_packages( + flavor: Flavor, + git_urls: &[String], +) -> Result, DownloadError> { + let mut git_repo_packages = vec![]; + + if git_urls.is_empty() { + return Ok(git_repo_packages); + } + + let fetch_tasks = git_urls + .iter() + .map(|url| { + let url = url + .parse::() + .map_err(|_| RepositoryError::GitInvalidUrl { url: url.clone() })?; + + RepositoryPackage::from_source_url(flavor, url) + }) + .filter_map(|result| match result { + Ok(package) => Some(package), + Err(e) => { + log::error!("{}", e); + None + } + }) + .map(|mut package| async { + if let Err(e) = package.resolve_metadata().await { + log::error!("{}", e); + Err(e) + } else { + Ok(package) + } + }); + + git_repo_packages.extend( + join_all(fetch_tasks) + .await + .into_iter() + .filter_map(Result::ok), + ); + + Ok(git_repo_packages) +} + mod github { use crate::config::Flavor; use crate::error::RepositoryError; diff --git a/crates/core/src/repository/backend/mod.rs b/crates/core/src/repository/backend/mod.rs index df7c4564..c88a03cc 100644 --- a/crates/core/src/repository/backend/mod.rs +++ b/crates/core/src/repository/backend/mod.rs @@ -5,7 +5,7 @@ use async_trait::async_trait; use dyn_clone::{clone_trait_object, DynClone}; pub mod curse; -mod git; +pub mod git; pub mod townlongyak; pub mod tukui; pub mod wowi; diff --git a/crates/core/src/repository/backend/townlongyak.rs b/crates/core/src/repository/backend/townlongyak.rs index c57f303a..2c1a0bb9 100644 --- a/crates/core/src/repository/backend/townlongyak.rs +++ b/crates/core/src/repository/backend/townlongyak.rs @@ -2,7 +2,7 @@ use super::*; use crate::config::Flavor; use crate::error::{DownloadError, RepositoryError}; use crate::network::post_json_async; -use crate::repository::{ReleaseChannel, RemotePackage}; +use crate::repository::{ReleaseChannel, RemotePackage, RepositoryKind, RepositoryPackage}; use async_trait::async_trait; use chrono::{DateTime, Utc}; @@ -82,6 +82,37 @@ pub(crate) fn metadata_from_townlong_package( metadata } +pub(crate) async fn batch_fetch_repo_packages( + flavor: Flavor, + townlong_ids: &[String], +) -> Result, DownloadError> { + let mut townlong_repo_packages = vec![]; + + if townlong_ids.is_empty() { + return Ok(townlong_repo_packages); + } + + let townlong_packages = townlongyak::fetch_remote_packages(flavor, &townlong_ids).await?; + + townlong_repo_packages.extend( + townlong_packages + .into_iter() + .map(|package| { + ( + package.id.to_string(), + townlongyak::metadata_from_townlong_package(flavor, package), + ) + }) + .filter_map(|(id, metadata)| { + RepositoryPackage::from_repo_id(flavor, RepositoryKind::TownlongYak, id) + .ok() + .map(|r| r.with_metadata(metadata)) + }), + ); + + Ok(townlong_repo_packages) +} + /// Function to fetch a remote addon package which contains /// information about the addon on the repository. pub(crate) async fn fetch_remote_packages( diff --git a/crates/core/src/repository/backend/tukui.rs b/crates/core/src/repository/backend/tukui.rs index e7afb7e8..f1aa839f 100644 --- a/crates/core/src/repository/backend/tukui.rs +++ b/crates/core/src/repository/backend/tukui.rs @@ -2,11 +2,12 @@ use super::*; use crate::config::Flavor; use crate::error::{DownloadError, RepositoryError}; use crate::network::request_async; -use crate::repository::{ReleaseChannel, RemotePackage}; +use crate::repository::{ReleaseChannel, RemotePackage, RepositoryKind, RepositoryPackage}; use crate::utility::{regex_html_tags_to_newline, regex_html_tags_to_space, truncate}; use async_trait::async_trait; use chrono::{NaiveDateTime, TimeZone, Utc}; +use futures::future::join_all; use isahc::AsyncReadResponseExt; use serde::Deserialize; @@ -140,6 +141,37 @@ fn changelog_endpoint(id: &str, flavor: &Flavor) -> String { } } +pub(crate) async fn batch_fetch_repo_packages( + flavor: Flavor, + tukui_ids: &[String], +) -> Result, DownloadError> { + let mut tukui_repo_packages = vec![]; + + if tukui_ids.is_empty() { + return Ok(tukui_repo_packages); + } + + let fetch_tasks: Vec<_> = tukui_ids + .iter() + .map(|id| tukui::fetch_remote_package(&id, &flavor)) + .collect(); + + tukui_repo_packages.extend( + join_all(fetch_tasks) + .await + .into_iter() + .filter_map(Result::ok) + .map(|(id, package)| (id, tukui::metadata_from_tukui_package(package))) + .filter_map(|(id, metadata)| { + RepositoryPackage::from_repo_id(flavor, RepositoryKind::Tukui, id) + .ok() + .map(|r| r.with_metadata(metadata)) + }), + ); + + Ok(tukui_repo_packages) +} + /// Function to fetch a remote addon package which contains /// information about the addon on the repository. pub(crate) async fn fetch_remote_package( diff --git a/crates/core/src/repository/backend/wowi.rs b/crates/core/src/repository/backend/wowi.rs index 3686765c..125f84cf 100644 --- a/crates/core/src/repository/backend/wowi.rs +++ b/crates/core/src/repository/backend/wowi.rs @@ -2,7 +2,7 @@ use super::*; use crate::config::Flavor; use crate::error::{DownloadError, RepositoryError}; use crate::network::request_async; -use crate::repository::{ReleaseChannel, RemotePackage}; +use crate::repository::{ReleaseChannel, RemotePackage, RepositoryKind, RepositoryPackage}; use async_trait::async_trait; use chrono::{TimeZone, Utc}; @@ -87,6 +87,37 @@ pub(crate) fn addon_url(id: &str) -> String { format!("{}{}", ADDON_URL, id) } +pub(crate) async fn batch_fetch_repo_packages( + flavor: Flavor, + wowi_ids: &[String], +) -> Result, DownloadError> { + let mut wowi_repo_packages = vec![]; + + if wowi_ids.is_empty() { + return Ok(wowi_repo_packages); + } + + let wowi_packages = wowi::fetch_remote_packages(&wowi_ids).await?; + + wowi_repo_packages.extend( + wowi_packages + .into_iter() + .map(|package| { + ( + package.id.to_string(), + wowi::metadata_from_wowi_package(package), + ) + }) + .filter_map(|(id, metadata)| { + RepositoryPackage::from_repo_id(flavor, RepositoryKind::WowI, id) + .ok() + .map(|r| r.with_metadata(metadata)) + }), + ); + + Ok(wowi_repo_packages) +} + /// Function to fetch a remote addon package which contains /// information about the addon on the repository. pub(crate) async fn fetch_remote_packages( diff --git a/crates/core/src/repository/mod.rs b/crates/core/src/repository/mod.rs index b8f1a781..c2ea3128 100644 --- a/crates/core/src/repository/mod.rs +++ b/crates/core/src/repository/mod.rs @@ -2,7 +2,6 @@ use crate::config::Flavor; use crate::error::{DownloadError, RepositoryError}; use chrono::{DateTime, Utc}; -use futures::future::join_all; use isahc::http::uri::Uri; use serde::{Deserialize, Serialize}; @@ -12,7 +11,7 @@ use std::collections::HashMap; mod backend; use backend::Backend; -pub use backend::{curse, townlongyak, tukui, wowi}; +pub use backend::{curse, git, townlongyak, tukui, wowi}; use backend::{Curse, Github, Gitlab, TownlongYak, Tukui, WowI}; #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Ord, Eq, Serialize, Deserialize)] @@ -390,133 +389,20 @@ pub async fn batch_refresh_repository_packages( .collect::>(); // Get all curse repo packages - let curse_repo_packages = if !curse_ids.is_empty() { - let curse_packages = curse::fetch_remote_packages_by_ids(&curse_ids).await?; - - let mut curse_repo_packages = vec![]; - - curse_repo_packages.extend( - curse_packages - .into_iter() - .map(|package| { - ( - package.id.to_string(), - curse::metadata_from_curse_package(flavor, package), - ) - }) - .filter_map(|(id, metadata)| { - RepositoryPackage::from_repo_id(flavor, RepositoryKind::Curse, id) - .map(|r| r.with_metadata(metadata)) - .ok() - }), - ); - - curse_repo_packages - } else { - vec![] - }; + let curse_repo_packages = curse::batch_fetch_repo_packages(flavor, &curse_ids, None).await?; // Get all tukui repo packages - let tukui_repo_packages = if !tukui_ids.is_empty() { - let fetch_tasks: Vec<_> = tukui_ids - .iter() - .map(|id| tukui::fetch_remote_package(&id, &flavor)) - .collect(); - - join_all(fetch_tasks) - .await - .into_iter() - .filter_map(Result::ok) - .map(|(id, package)| (id, tukui::metadata_from_tukui_package(package))) - .filter_map(|(id, metadata)| { - RepositoryPackage::from_repo_id(flavor, RepositoryKind::Tukui, id) - .ok() - .map(|r| r.with_metadata(metadata)) - }) - .collect::>() - } else { - vec![] - }; + let tukui_repo_packages = tukui::batch_fetch_repo_packages(flavor, &tukui_ids).await?; // Get all wowi repo packages - let wowi_repo_packages = if !wowi_ids.is_empty() { - let wowi_packages = wowi::fetch_remote_packages(&wowi_ids).await?; - - wowi_packages - .into_iter() - .map(|package| { - ( - package.id.to_string(), - wowi::metadata_from_wowi_package(package), - ) - }) - .filter_map(|(id, metadata)| { - RepositoryPackage::from_repo_id(flavor, RepositoryKind::WowI, id) - .ok() - .map(|r| r.with_metadata(metadata)) - }) - .collect::>() - } else { - vec![] - }; + let wowi_repo_packages = wowi::batch_fetch_repo_packages(flavor, &wowi_ids).await?; // Get all townlong repo packages - let townlong_repo_packages = if !townlong_ids.is_empty() { - let townlong_packages = townlongyak::fetch_remote_packages(flavor, &townlong_ids).await?; - - townlong_packages - .into_iter() - .map(|package| { - ( - package.id.to_string(), - townlongyak::metadata_from_townlong_package(flavor, package), - ) - }) - .filter_map(|(id, metadata)| { - RepositoryPackage::from_repo_id(flavor, RepositoryKind::TownlongYak, id) - .ok() - .map(|r| r.with_metadata(metadata)) - }) - .collect::>() - } else { - vec![] - }; + let townlong_repo_packages = + townlongyak::batch_fetch_repo_packages(flavor, &townlong_ids).await?; // Get all git repo packages - let git_repo_packages = if !git_urls.is_empty() { - let fetch_tasks = git_urls - .iter() - .map(|url| { - let url = url - .parse::() - .map_err(|_| RepositoryError::GitInvalidUrl { url: url.clone() })?; - - RepositoryPackage::from_source_url(flavor, url) - }) - .filter_map(|result| match result { - Ok(package) => Some(package), - Err(e) => { - log::error!("{}", e); - None - } - }) - .map(|mut package| async { - if let Err(e) = package.resolve_metadata().await { - log::error!("{}", e); - Err(e) - } else { - Ok(package) - } - }); - - join_all(fetch_tasks) - .await - .into_iter() - .filter_map(Result::ok) - .collect::>() - } else { - vec![] - }; + let git_repo_packages = git::batch_fetch_repo_packages(flavor, &git_urls).await?; Ok([ &curse_repo_packages[..], From d1e950612dfaedb3cfa9b8ad3ae54ab926739fb1 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Fri, 12 Mar 2021 10:15:11 -0800 Subject: [PATCH 4/9] add auto refresh --- crates/core/src/config/mod.rs | 3 +++ src/gui/element/settings.rs | 19 ++++++++++++++++++- src/gui/mod.rs | 1 + src/gui/update.rs | 24 ++++++++++++++++++++++++ 4 files changed, 46 insertions(+), 1 deletion(-) diff --git a/crates/core/src/config/mod.rs b/crates/core/src/config/mod.rs index c4cf4d27..278bbd7c 100644 --- a/crates/core/src/config/mod.rs +++ b/crates/core/src/config/mod.rs @@ -58,6 +58,9 @@ pub struct Config { #[serde(default)] pub catalog_source: Option, + + #[serde(default)] + pub auto_update: bool, } impl Config { diff --git a/src/gui/element/settings.rs b/src/gui/element/settings.rs index 163d6623..bbe2ca86 100644 --- a/src/gui/element/settings.rs +++ b/src/gui/element/settings.rs @@ -316,6 +316,21 @@ pub fn data_container<'a, 'b>( ) }; + let auto_update_column = { + let auto_update = config.auto_update; + let checkbox = Checkbox::new( + auto_update, + localized_string("auto-update"), + move |is_checked| Message::Interaction(Interaction::ToggleAutoUpdateAddons(is_checked)), + ) + .style(style::DefaultCheckbox(color_palette)) + .text_size(DEFAULT_FONT_SIZE) + .spacing(5); + let checkbox_container = + Container::new(checkbox).style(style::NormalBackgroundContainer(color_palette)); + Column::new().push(checkbox_container) + }; + let hide_addons_column = { let hide_ignored_addons = config.hide_ignored_addons; let checkbox = Checkbox::new( @@ -525,7 +540,9 @@ pub fn data_container<'a, 'b>( .push(Space::new(Length::Units(0), Length::Units(10))) .push(hide_addons_column) .push(Space::new(Length::Units(0), Length::Units(10))) - .push(delete_saved_variables_column); + .push(delete_saved_variables_column) + .push(Space::new(Length::Units(0), Length::Units(10))) + .push(auto_update_column); let columns_title_text = Text::new(localized_string("columns")).size(DEFAULT_HEADER_FONT_SIZE); let columns_title_text_container = diff --git a/src/gui/mod.rs b/src/gui/mod.rs index 183eab11..5c6fb093 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -124,6 +124,7 @@ pub enum Interaction { ResetColumns, ToggleDeleteSavedVariables(bool), AddonsQuery(String), + ToggleAutoUpdateAddons(bool), } #[derive(Debug)] diff --git a/src/gui/update.rs b/src/gui/update.rs index 6e8c00ca..e68777e6 100644 --- a/src/gui/update.rs +++ b/src/gui/update.rs @@ -618,6 +618,16 @@ pub fn handle_message(ajour: &mut Ajour, message: Message) -> Result { @@ -625,6 +635,12 @@ pub fn handle_message(ajour: &mut Ajour, message: Message) -> Result { + log::debug!("Interaction::ToggleAutoUpdateAddons({})", auto_update); + + ajour.config.auto_update = auto_update; + let _ = ajour.config.save(); + } Message::ParsedAddons((flavor, result)) => { let global_release_channel = ajour.config.addons.global_release_channel; @@ -690,6 +706,14 @@ pub fn handle_message(ajour: &mut Ajour, message: Message) -> Result { log_error(&error); From ca5c87c40d3657047823b3a5da3861ab8ace6b85 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Fri, 12 Mar 2021 10:31:24 -0800 Subject: [PATCH 5/9] add locale key --- locale/en_US.json | 1 + 1 file changed, 1 insertion(+) diff --git a/locale/en_US.json b/locale/en_US.json index d20a1df4..97045058 100644 --- a/locale/en_US.json +++ b/locale/en_US.json @@ -9,6 +9,7 @@ "aura": "Aura", "author": "Author", "authors": "Author(s)", + "auto-update": "Automatically apply new updates when available", "backup": "Backup", "backup-description": "Back up your AddOns and WTF folder to the chosen directory", "backup-latest": "Last backup: {time}", From 6acd9dfe5e3a22895415bc5387cbe73fa1b6d178 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Fri, 12 Mar 2021 10:46:20 -0800 Subject: [PATCH 6/9] update CHANGELOG --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 571e4089..f7527052 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,11 @@ and `Removed`. ### Added - Automatically select account in My WeakAuras if there only is one account +- Addon updates are automatically checked for every 30 minutes while the program + is open. If new updates are available, they will be sorted to the top of the screen +- A new "Auto Update" setting can be enabled in the Addons section of the Settings. + When enabled, Ajour will automatically apply new addon updates when available + (new updates are checked for every 30 minutes) ### Changed From dd0f76c399e821dab93a8f15bfa0c4cc2e80c5fb Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Fri, 12 Mar 2021 15:44:47 -0800 Subject: [PATCH 7/9] fix CHANGELOG --- CHANGELOG.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f7527052..ceb7801e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,17 +14,20 @@ and `Removed`. ## [Unreleased] -## [0.7.2] - 2021-03-02 - ### Added -- Automatically select account in My WeakAuras if there only is one account - Addon updates are automatically checked for every 30 minutes while the program is open. If new updates are available, they will be sorted to the top of the screen - A new "Auto Update" setting can be enabled in the Addons section of the Settings. When enabled, Ajour will automatically apply new addon updates when available (new updates are checked for every 30 minutes) +## [0.7.2] - 2021-03-02 + +### Added + +- Automatically select account in My WeakAuras if there only is one account + ### Changed - Better UX when opening Catalog for the first time From 468e74f3bd553b4ea8207a745b7922a0db6febbd Mon Sep 17 00:00:00 2001 From: casperstorm Date: Sat, 13 Mar 2021 07:26:48 +0000 Subject: [PATCH 8/9] chore: updated localizations --- locale/cs.json | 3 +- locale/da.json | 3 +- locale/de.json | 3 +- locale/en.json | 4 +- locale/es.json | 3 +- locale/fr.json | 3 +- locale/hu.json | 3 +- locale/no.json | 3 +- locale/pt.json | 3 +- locale/ru.json | 3 +- locale/sk.json | 3 +- locale/sv.json | 3 +- locale/tr.json | 3 +- locale/uk.json | 3 +- locale/zh-Hans.json | 123 ++++++++++++++++++++++++++++++++++++++++++++ 15 files changed, 151 insertions(+), 15 deletions(-) create mode 100644 locale/zh-Hans.json diff --git a/locale/cs.json b/locale/cs.json index cb102c8a..5fd4f08f 100644 --- a/locale/cs.json +++ b/locale/cs.json @@ -118,5 +118,6 @@ "kofi-http": "https://ko-fi.com/ajour", "select-catalog-source-picklist": "Vyberte zdroj", "select-catalog-source-title": "Vyberte zdroj v menu", - "select-catalog-source-description": "Ajour má několik zdrojů katalogu. Vyberte zdroj, pro procházení doplňky." + "select-catalog-source-description": "Ajour má několik zdrojů katalogu. Vyberte zdroj, pro procházení doplňky.", + "auto-update": "" } \ No newline at end of file diff --git a/locale/da.json b/locale/da.json index 1a533f2e..7f266875 100644 --- a/locale/da.json +++ b/locale/da.json @@ -118,5 +118,6 @@ "kofi-http": "https://ko-fi.com/ajour", "select-catalog-source-picklist": "Vælg Kilde", "select-catalog-source-title": "Vælg en kilde i menuen", - "select-catalog-source-description": "Ajour har mange katalog kilder. Vælg en kilde for at vise addons." + "select-catalog-source-description": "Ajour har mange katalog kilder. Vælg en kilde for at vise addons.", + "auto-update": "" } \ No newline at end of file diff --git a/locale/de.json b/locale/de.json index 1570b247..6c529158 100644 --- a/locale/de.json +++ b/locale/de.json @@ -118,5 +118,6 @@ "kofi-http": "https://ko-fi.com/ajour", "select-catalog-source-picklist": "Quelle auswählen", "select-catalog-source-title": "Wähle eine Quelle aus dem Menü", - "select-catalog-source-description": "Ajour hat mehrere Katalog-Quellen. Wähle eine davon aus, um deren Addons zu durchsuchen." + "select-catalog-source-description": "Ajour hat mehrere Katalog-Quellen. Wähle eine davon aus, um deren Addons zu durchsuchen.", + "auto-update": "" } \ No newline at end of file diff --git a/locale/en.json b/locale/en.json index 97045058..a0f747cf 100644 --- a/locale/en.json +++ b/locale/en.json @@ -9,7 +9,6 @@ "aura": "Aura", "author": "Author", "authors": "Author(s)", - "auto-update": "Automatically apply new updates when available", "backup": "Backup", "backup-description": "Back up your AddOns and WTF folder to the chosen directory", "backup-latest": "Last backup: {time}", @@ -119,5 +118,6 @@ "kofi-http": "https://ko-fi.com/ajour", "select-catalog-source-picklist": "Select Source", "select-catalog-source-title": "Select a source in the menu", - "select-catalog-source-description": "Ajour has multiple catalog sources. Select a source to browse the addons." + "select-catalog-source-description": "Ajour has multiple catalog sources. Select a source to browse the addons.", + "auto-update": "Automatically apply new updates when available" } \ No newline at end of file diff --git a/locale/es.json b/locale/es.json index 72151176..f5a645e8 100644 --- a/locale/es.json +++ b/locale/es.json @@ -118,5 +118,6 @@ "kofi-http": "https://ko-fi.com/ajour", "select-catalog-source-picklist": "Seleccione la fuente", "select-catalog-source-title": "Seleccione una fuente en el menú", - "select-catalog-source-description": "Ajour tiene múltiples fuentes de catálogo. Seleccione una fuente para examinar los complementos." + "select-catalog-source-description": "Ajour tiene múltiples fuentes de catálogo. Seleccione una fuente para examinar los complementos.", + "auto-update": "" } \ No newline at end of file diff --git a/locale/fr.json b/locale/fr.json index 64f41a06..52c43f27 100644 --- a/locale/fr.json +++ b/locale/fr.json @@ -118,5 +118,6 @@ "kofi-http": "https://ko-fi.com/ajour", "select-catalog-source-picklist": "Choisissez la source", "select-catalog-source-title": "Sélectionnez la source dans le menu", - "select-catalog-source-description": "Ajour contient plusieurs sources d'addons. Choisissez une source pour parcourir les addons." + "select-catalog-source-description": "Ajour contient plusieurs sources d'addons. Choisissez une source pour parcourir les addons.", + "auto-update": "" } \ No newline at end of file diff --git a/locale/hu.json b/locale/hu.json index feb03189..8247f85d 100644 --- a/locale/hu.json +++ b/locale/hu.json @@ -118,5 +118,6 @@ "kofi-http": "https://ko-fi.com/ajour", "select-catalog-source-picklist": "Forrás kiválasztása", "select-catalog-source-title": "Forrás kiválasztása a menüben", - "select-catalog-source-description": "Az Ajour több forrásból is tud addonokat letölteni. Válassz egy forrást a böngészéshez." + "select-catalog-source-description": "Az Ajour több forrásból is tud addonokat letölteni. Válassz egy forrást a böngészéshez.", + "auto-update": "" } \ No newline at end of file diff --git a/locale/no.json b/locale/no.json index a13fede9..34a4c028 100644 --- a/locale/no.json +++ b/locale/no.json @@ -118,5 +118,6 @@ "kofi-http": "https://ko-fi.com/ajour", "select-catalog-source-picklist": "Velg kildekatalog", "select-catalog-source-title": "Velg en kildekatalog i menyen", - "select-catalog-source-description": "Ajour har flere kildekataloger. Velg en kildekatalog for å se tilhørende addons." + "select-catalog-source-description": "Ajour har flere kildekataloger. Velg en kildekatalog for å se tilhørende addons.", + "auto-update": "" } \ No newline at end of file diff --git a/locale/pt.json b/locale/pt.json index 7a05daeb..b7545a06 100644 --- a/locale/pt.json +++ b/locale/pt.json @@ -118,5 +118,6 @@ "kofi-http": "https://ko-fi.com/ajour", "select-catalog-source-picklist": "Selecione a fonte", "select-catalog-source-title": "Selecione uma fonte no menu", - "select-catalog-source-description": "Ajour tem múltiplas fontes de catálogo. Selecione uma fonte para navegar pelos addons." + "select-catalog-source-description": "Ajour tem múltiplas fontes de catálogo. Selecione uma fonte para navegar pelos addons.", + "auto-update": "" } \ No newline at end of file diff --git a/locale/ru.json b/locale/ru.json index ce09b9b3..ee28cf72 100644 --- a/locale/ru.json +++ b/locale/ru.json @@ -118,5 +118,6 @@ "kofi-http": "https://ko-fi.com/ajour", "select-catalog-source-picklist": "Выберите Источник", "select-catalog-source-title": "Выберите источник в меню", - "select-catalog-source-description": "Ajour имеет несколько каталожных источников. Выберите источник для просмотра дополнений." + "select-catalog-source-description": "Ajour имеет несколько каталожных источников. Выберите источник для просмотра дополнений.", + "auto-update": "" } \ No newline at end of file diff --git a/locale/sk.json b/locale/sk.json index e560f417..67329d9c 100644 --- a/locale/sk.json +++ b/locale/sk.json @@ -118,5 +118,6 @@ "kofi-http": "https://ko-fi.com/ajour", "select-catalog-source-picklist": "Vyber zdroj", "select-catalog-source-title": "Vyber zdroj v menu", - "select-catalog-source-description": "Ajour má niekoľko zdrojov katalógu. Vyber zdroj pre prehľadávanie addonov." + "select-catalog-source-description": "Ajour má niekoľko zdrojov katalógu. Vyber zdroj pre prehľadávanie addonov.", + "auto-update": "" } \ No newline at end of file diff --git a/locale/sv.json b/locale/sv.json index bebcddb6..54975dd0 100644 --- a/locale/sv.json +++ b/locale/sv.json @@ -118,5 +118,6 @@ "kofi-http": "https://ko-fi.com/ajour", "select-catalog-source-picklist": "Välj källa", "select-catalog-source-title": "Välj en källa i menyn", - "select-catalog-source-description": "Ajour har fler än en källa. Var god välj en för att hitta addons." + "select-catalog-source-description": "Ajour har fler än en källa. Var god välj en för att hitta addons.", + "auto-update": "" } \ No newline at end of file diff --git a/locale/tr.json b/locale/tr.json index 4b79c535..9c39a000 100644 --- a/locale/tr.json +++ b/locale/tr.json @@ -118,5 +118,6 @@ "kofi-http": "https://ko-fi.com/ajour", "select-catalog-source-picklist": "Kaynak Seç", "select-catalog-source-title": "Menüden bir kaynak seçin", - "select-catalog-source-description": "Ajour'un birden fazla katalog kaynağı vardır. Eklentilere göz atmak için bir kaynak seçin." + "select-catalog-source-description": "Ajour'un birden fazla katalog kaynağı vardır. Eklentilere göz atmak için bir kaynak seçin.", + "auto-update": "" } \ No newline at end of file diff --git a/locale/uk.json b/locale/uk.json index 2e46953a..31039a35 100644 --- a/locale/uk.json +++ b/locale/uk.json @@ -118,5 +118,6 @@ "kofi-http": "https://ko-fi.com/ajour", "select-catalog-source-picklist": "Обрати джерело", "select-catalog-source-title": "Оберіть джерело в меню", - "select-catalog-source-description": "Ajour має кілька джерел каталогів. Оберіть джерело для перегляду додатків." + "select-catalog-source-description": "Ajour має кілька джерел каталогів. Оберіть джерело для перегляду додатків.", + "auto-update": "" } \ No newline at end of file diff --git a/locale/zh-Hans.json b/locale/zh-Hans.json new file mode 100644 index 00000000..b4721ff8 --- /dev/null +++ b/locale/zh-Hans.json @@ -0,0 +1,123 @@ +{ + "about": "关于", + "addon": "插件", + "addons": "插件", + "addons-loaded": "{number} {flavor}插件已加载", + "ajour": "Ajour", + "all-categories": "所有类别", + "alternate-row-colors": "交替行颜色", + "aura": "光环", + "author": "作者", + "authors": "作者", + "backup": "备份", + "backup-description": "在选择的目录里备份你的插件和WTF文件夹", + "backup-latest": "最近备份: {time}", + "backup-never": "从未", + "backup-now": "现在备份", + "backup-progress": "正在备份……", + "catalog": "插件下载目录", + "changelog": "更新日志", + "changelog-for": "{addon}的更新日志", + "changelog-press-full-changelog": "请按右侧的“完整更改日志”在浏览器中查看此更改日志", + "channel": "频道", + "columns": "栏", + "completed": "已完成", + "delete": "删除", + "delete-saved-variables": "删除插件时删除SavedVariables", + "description": "描述", + "downloading": "正在下载", + "failed": "已失败", + "full-changelog": "完整更新日志", + "game-version": "游戏版本", + "global-release-channel": "全球发布渠道", + "hashing": "散列", + "hide-addons": "隐藏排除的插件", + "ignore": "排除", + "ignored": "已排除", + "install": "安装", + "install-for-flavor": "安装{flavor}", + "install-from-url": "从网址安装", + "install-from-url-description": "直接从GitHub或GitLab安装插件\n插件必须作为“Release”方式发布", + "install-from-url-example": "例如:https://github.com/author/repository", + "installed": "已安装", + "language": "语言", + "latest-release": "最新版本", + "loading": "正在读取……", + "loading-catalog": "当前正在加载目录", + "local": "本地版本", + "my-addons": "我的插件", + "my-weakauras": "我的WA", + "new-update-available": "新的Ajour版本已经就绪", + "no-addon-description": "该插件没有描述。", + "no-addons-for-flavor": "你没有{flavor}插件。", + "no-changelog": "没有找到更新日志。", + "no-directory": "未设置目录", + "no-known-weakauras": "你没有已知的{flavor}WA", + "num-downloads": "下载数", + "open-data-directory": "打开数据目录", + "parsing-addons": "当前正在解析{flavor}插件", + "parsing-weakauras": "当前正在解析{flavor}WA", + "patreon": "Patreon", + "patreon-http": "https://patreon.com/getajour", + "refresh": "刷新", + "release-channel-no-release": "没有可用的发行版本", + "remote": "最新版本", + "remote-release-channel": "发行频道", + "reset-columns": "重置栏位", + "retry": "重试", + "scale": "缩放", + "search-for-addon": "搜索插件……", + "select-account": "选择账号", + "select-directory": "选择目录", + "settings": "设置", + "setup-ajour-description": "请选择你的魔兽世界目录", + "setup-ajour-title": "欢迎来到Ajour!", + "setup-weakauras-description": "请选择一个账号以管理", + "setup-weakauras-title": "使用Ajour管理你的WA!", + "source": "来源", + "status": "状态", + "summary": "概述", + "theme": "主题", + "ui": "UI", + "unavailable": "不可用", + "unignore": "包含", + "unknown": "未知", + "unpacking": "正在解包", + "update": "更新", + "update-all": "更新全部", + "ajour-update-channel": "Ajour更新频道", + "updating": "正在更新", + "weakaura-updates-queued": "现在更新已可在游戏内应用", + "weakauras-loaded": "{number}WA已加载", + "website": "网址", + "website-http": "https://getajour.com", + "welcome-to-ajour-description": "请选择你的魔兽世界目录", + "woops": "呜呜!", + "wow-directory": "魔兽世界目录", + "wtf": "WTF", + "channel-default": "默认", + "channel-stable": "稳定", + "channel-beta": "Beta", + "channel-alpha": "Alpha", + "weakaura-update-available": "更新已可用", + "settings-general": "一般", + "catalog-results": "结果:{number}", + "error-fetch-changelog": "无法获取更新日志", + "error-parse-addons": "无法解析插件", + "error-download-addon": "无法下载插件", + "error-unpack-addon": "无法解压插件", + "error-backup-folders": "无法备份目录", + "error-update-ajour": "无法更新Ajour", + "error-remove-cache": "无法删除cache", + "error-list-accounts": "无法列表账号", + "error-parse-weakauras": "无法解析WA", + "error-update-weakauras": "无法更新WA", + "categories": "分类", + "error-update-ajour-permission": "当更新Ajour时遇到权限问题", + "kofi": "Ko-fi", + "kofi-http": "https://ko-fi.com/ajour", + "select-catalog-source-picklist": "选择来源", + "select-catalog-source-title": "在菜单里选择一个来源", + "select-catalog-source-description": "Ajour有许多插件目录下载来源。请选一个来源以展示插件。", + "auto-update": "" +} \ No newline at end of file From 17256abba7387a629a910f116297808ec95d3139 Mon Sep 17 00:00:00 2001 From: Casper Rogild Storm Date: Sat, 13 Mar 2021 08:29:04 +0100 Subject: [PATCH 9/9] chore: removed language test --- src/localization.rs | 146 -------------------------------------------- 1 file changed, 146 deletions(-) diff --git a/src/localization.rs b/src/localization.rs index 52d38e1b..ab46d747 100644 --- a/src/localization.rs +++ b/src/localization.rs @@ -69,149 +69,3 @@ pub fn localized_timeago_formatter() -> timeago::Formatter