+
Skip to content
This repository was archived by the owner on Sep 17, 2024. It is now read-only.

feat: ability to use multiple wow folders with a single ajour instance #574

Merged
merged 21 commits into from
Mar 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ and `Removed`.

### Added

- Ability to choose a different directory for each flavor.
This means you can use a single instance of Ajour even if you have Retail and
Classic in two different directories.
- Addon backup directory will be created if missing
- 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
Expand Down
45 changes: 26 additions & 19 deletions crates/core/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,28 @@ pub struct Config {
}

impl Config {
/// Returns a `PathBuf` to the flavor directory.
pub fn get_flavor_directory_for_flavor(&self, flavor: &Flavor, path: &PathBuf) -> PathBuf {
path.join(&flavor.folder_name())
}

/// Returns a `Option<PathBuf>` to the root directory of the Flavor.
pub fn get_root_directory_for_flavor(&self, flavor: &Flavor) -> Option<PathBuf> {
if let Some(flavor_dir) = self.wow.directories.get(flavor) {
Some(flavor_dir.parent().unwrap().to_path_buf())
} else {
None
}
}

/// Returns a `Option<PathBuf>` to the directory containing the addons.
/// This will return `None` if no `wow_directory` is set in the config.
pub fn get_addon_directory_for_flavor(&self, flavor: &Flavor) -> Option<PathBuf> {
match &self.wow.directory {
Some(wow_dir) => {
// The path to the flavor directory
let flavor_dir = wow_dir.join(&flavor.folder_name());
let dir = self.wow.directories.get(flavor);
match dir {
Some(dir) => {
// The path to the addons directory
let mut addon_dir = flavor_dir.join("Interface/AddOns");
let mut addon_dir = dir.join("Interface/AddOns");

// If path doesn't exist, it could have been modified by the user.
// Check for a case-insensitive version and use that instead.
Expand All @@ -84,7 +97,7 @@ impl Config {

// For some reason the case insensitive pattern doesn't work
// unless we add an actual pattern symbol, hence the `?`.
let pattern = format!("{}/?nterface/?ddons", flavor_dir.display());
let pattern = format!("{}/?nterface/?ddons", dir.display());

for entry in glob::glob_with(&pattern, options).unwrap() {
if let Ok(path) = entry {
Expand All @@ -96,7 +109,7 @@ impl Config {
// If flavor dir exists but not addon dir we try to create it.
// This state can happen if you do a fresh install of WoW and
// launch Ajour before you launch WoW.
if flavor_dir.exists() && !addon_dir.exists() {
if dir.exists() && !addon_dir.exists() {
let _ = create_dir_all(&addon_dir);
}

Expand All @@ -108,25 +121,19 @@ impl Config {

/// Returns a `Option<PathBuf>` to the directory which will hold the
/// temporary zip archives.
/// This will return `None` if no `wow_directory` is set in the config.
/// This will return `None` if flavor does not have a directory.
pub fn get_download_directory_for_flavor(&self, flavor: Flavor) -> Option<PathBuf> {
match self.get_addon_directory_for_flavor(&flavor) {
Some(dir) => {
// The path to the directory which hold the temporary zip archives
let dir = dir.parent().expect("Expected Addons folder has a parent.");
Some(dir.to_path_buf())
}
None => None,
}
self.wow.directories.get(&flavor).cloned()
}

/// Returns a `Option<PathBuf>` to the WTF directory.
/// This will return `None` if no `wow_directory` is set in the config.
pub fn get_wtf_directory_for_flavor(&self, flavor: &Flavor) -> Option<PathBuf> {
match &self.wow.directory {
let dir = self.wow.directories.get(flavor);
match dir {
Some(dir) => {
// The path to the WTF directory
let mut addon_dir = dir.join(&flavor.folder_name()).join("WTF");
let mut addon_dir = dir.join("WTF");

// If path doesn't exist, it could have been modified by the user.
// Check for a case-insensitive version and use that instead.
Expand All @@ -138,7 +145,7 @@ impl Config {

// For some reason the case insensitive pattern doesn't work
// unless we add an actual pattern symbol, hence the `?`.
let pattern = format!("{}/?tf", dir.join(&flavor.folder_name()).display());
let pattern = format!("{}/?tf", dir.display());

for entry in glob::glob_with(&pattern, options).unwrap() {
if let Ok(path) = entry {
Expand Down
6 changes: 6 additions & 0 deletions crates/core/src/config/wow.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;

/// Struct for settings related to World of Warcraft.
#[serde(default)]
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)]
pub struct Wow {
#[serde(default)]
#[allow(deprecated)]
pub directory: Option<PathBuf>,

#[serde(default)]
pub directories: HashMap<Flavor, PathBuf>,

#[serde(default)]
pub flavor: Flavor,
}
Expand All @@ -16,6 +21,7 @@ impl Default for Wow {
fn default() -> Self {
Wow {
directory: None,
directories: HashMap::new(),
flavor: Flavor::Retail,
}
}
Expand Down
9 changes: 6 additions & 3 deletions crates/core/src/utility.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::config::SelfUpdateChannel;
use crate::config::{Flavor, SelfUpdateChannel};
use crate::error::DownloadError;
#[cfg(target_os = "macos")]
use crate::error::FilesystemError;
Expand Down Expand Up @@ -199,11 +199,14 @@ fn extract_binary_from_tar(
})
}

/// Logic to help pick the right World of Warcraft folder. We want the root folder.
/// Logic to help pick the right World of Warcraft folder.
pub fn wow_path_resolution(path: Option<PathBuf>) -> Option<PathBuf> {
if let Some(path) = path {
// Known folders in World of Warcraft dir
let known_folders = ["_retail_", "_classic_", "_ptr_"];
let known_folders = Flavor::ALL
.iter()
.map(|f| f.folder_name())
.collect::<Vec<String>>();

// If chosen path has any of the known Wow folders, we have the right one.
for folder in known_folders.iter() {
Expand Down
9 changes: 6 additions & 3 deletions src/command/backup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ pub fn backup(
return Err(format_err!("destination must be a folder, not a file"));
}

let wow_dir = config.wow.directory.as_ref().ok_or_else(|| format_err!("No WoW directory set. Launch Ajour and make sure a WoW directory is set before using the command line."))?;
if config.wow.directories.keys().next().is_none() {
return Err(format_err!("No WoW directories set. Launch Ajour and make sure a WoW directory is set before using the command line."));
}

log::info!(
"Backing up:\n\tbackup folders: {:?}\n\tflavors: {:?}\n\tdestination: {:?}",
Expand All @@ -43,8 +45,9 @@ pub fn backup(
let mut src_folders = vec![];

for flavor in flavors {
let addon_directory = config.get_addon_directory_for_flavor(&flavor).ok_or_else(|| format_err!("No WoW directory set. Launch Ajour and make sure a WoW directory is set before using the command line."))?;
let wtf_directory = config.get_wtf_directory_for_flavor(&flavor).ok_or_else(|| format_err!("No WoW directory set. Launch Ajour and make sure a WoW directory is set before using the command line."))?;
let wow_dir = config.get_root_directory_for_flavor(&flavor).ok_or_else(|| format_err!("No WoW directories set. Launch Ajour and make sure a WoW directory is set before using the command line."))?;
let addon_directory = config.get_addon_directory_for_flavor(&flavor).ok_or_else(|| format_err!("No WoW directories set. Launch Ajour and make sure a WoW directory is set before using the command line."))?;
let wtf_directory = config.get_wtf_directory_for_flavor(&flavor).ok_or_else(|| format_err!("No WoW directories set. Launch Ajour and make sure a WoW directory is set before using the command line."))?;

let addons_folder = backup::BackupFolder::new(&addon_directory, &wow_dir);
let wtf_folder = backup::BackupFolder::new(&wtf_directory, &wow_dir);
Expand Down
9 changes: 8 additions & 1 deletion src/gui/element/menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ pub fn data_container<'a>(
state: &HashMap<Mode, State>,
error: &Option<anyhow::Error>,
config: &Config,
valid_flavors: &[Flavor],
settings_button_state: &'a mut button::State,
about_button_state: &'a mut button::State,
addon_mode_button_state: &'a mut button::State,
Expand All @@ -34,6 +33,14 @@ pub fn data_container<'a>(
weak_auras_is_installed: bool,
) -> Container<'a, Message> {
let flavor = config.wow.flavor;
let mut valid_flavors = config
.wow
.directories
.keys()
.copied()
.collect::<Vec<Flavor>>();

valid_flavors.sort();

// State.
let myaddons_state = state
Expand Down
105 changes: 53 additions & 52 deletions src/gui/element/settings.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
#![allow(clippy::too_many_arguments)]

use {
super::{DEFAULT_FONT_SIZE, DEFAULT_HEADER_FONT_SIZE, DEFAULT_PADDING},
crate::gui::{
style, BackupFolderKind, BackupState, CatalogColumnKey, CatalogColumnSettings, ColumnKey,
ColumnSettings, DirectoryType, GlobalReleaseChannel, Interaction, Language, Message,
ScaleState, SelfUpdateChannelState, ThemeState,
ColumnSettings, GlobalReleaseChannel, Interaction, Language, Message, ScaleState,
SelfUpdateChannelState, ThemeState, WowDirectoryState,
},
crate::localization::localized_string,
ajour_core::{config::Config, theme::ColorPalette},
Expand All @@ -19,7 +20,6 @@ use {
pub fn data_container<'a, 'b>(
color_palette: ColorPalette,
scrollable_state: &'a mut scrollable::State,
directory_button_state: &'a mut button::State,
config: &Config,
theme_state: &'a mut ThemeState,
scale_state: &'a mut ScaleState,
Expand All @@ -33,60 +33,51 @@ pub fn data_container<'a, 'b>(
default_addon_release_channel_picklist_state: &'a mut pick_list::State<GlobalReleaseChannel>,
reset_columns_button_state: &'a mut button::State,
localization_picklist_state: &'a mut pick_list::State<Language>,
wow_directories: &'a mut Vec<WowDirectoryState>,
) -> Container<'a, Message> {
let mut scrollable = Scrollable::new(scrollable_state)
.spacing(1)
.height(Length::FillPortion(1))
.style(style::Scrollable(color_palette));

let wow_directory_column = {
// Title for the World of Warcraft directory selection.
let directory_info_text =
Text::new(localized_string("wow-directory")).size(DEFAULT_FONT_SIZE);
let direction_info_text_container = Container::new(directory_info_text)
.style(style::NormalBackgroundContainer(color_palette));

// Directory button for World of Warcraft directory selection.

let directory_button_title_container =
Container::new(Text::new(localized_string("select-directory")).size(DEFAULT_FONT_SIZE))
.width(Length::FillPortion(1))
.center_x()
.align_x(Align::Center);

let directory_button: Element<Interaction> =
Button::new(directory_button_state, directory_button_title_container)
.style(style::DefaultBoxedButton(color_palette))
.on_press(Interaction::SelectDirectory(DirectoryType::Wow))
.into();

// Directory text, written next to directory button to let the user
// know what has been selected..
let no_directory_str = &localized_string("no-directory")[..];
let path_str = config
.wow
.directory
.as_ref()
.and_then(|p| p.to_str())
.unwrap_or(no_directory_str);
let directory_data_text = Text::new(path_str)
.size(14)
.vertical_alignment(VerticalAlignment::Center);
let directory_data_text_container = Container::new(directory_data_text)
.height(Length::Units(25))
.center_y()
.style(style::NormalBackgroundContainer(color_palette));
let mut wow_dir_column = Column::new();
for wow_dir_state in wow_directories {
let flavor = wow_dir_state.flavor;
let path_str = config
.wow
.directories
.get(&flavor)
.map(|p| p.to_str())
.flatten()
.unwrap_or("-");
let flavor_text = Text::new(flavor.to_string())
.size(14)
.vertical_alignment(VerticalAlignment::Center);
let flavor_text_container = Container::new(flavor_text)
.width(Length::Units(75))
.center_y();
let flavor_button: Element<Interaction> =
Button::new(&mut wow_dir_state.button_state, flavor_text_container)
.style(style::DefaultButton(color_palette))
.on_press(Interaction::SelectWowDirectory(Some(flavor)))
.into();
let path_text = Text::new(path_str)
.size(14)
.vertical_alignment(VerticalAlignment::Center);
let path_text_container = Container::new(path_text)
.height(Length::Units(25))
.center_y()
.style(style::NormalBackgroundContainer(color_palette));
let flavor_row = Row::new()
.push(flavor_button.map(Message::Interaction))
.push(Space::new(Length::Units(DEFAULT_PADDING), Length::Units(0)))
.push(path_text_container);

// Data row for the World of Warcraft directory selection.
let directory_data_row = Row::new()
.push(directory_button.map(Message::Interaction))
.push(Space::new(Length::Units(DEFAULT_PADDING), Length::Units(0)))
.push(directory_data_text_container);
wow_dir_column = wow_dir_column.push(flavor_row);
}

Column::new()
.push(direction_info_text_container)
.push(Space::new(Length::Units(0), Length::Units(5)))
.push(directory_data_row)
wow_dir_column
};

let theme_column = {
Expand Down Expand Up @@ -209,7 +200,7 @@ pub fn data_container<'a, 'b>(
directory_button_title_container,
)
.style(style::DefaultBoxedButton(color_palette))
.on_press(Interaction::SelectDirectory(DirectoryType::Backup))
.on_press(Interaction::SelectBackupDirectory())
.into();

// Directory text, written next to directory button to let the user
Expand Down Expand Up @@ -261,7 +252,7 @@ pub fn data_container<'a, 'b>(
// the wow folder is chosen and at least one of the folders is selected
// for backup
if !backup_state.backing_up
&& config.wow.directory.is_some()
&& config.wow.directories.keys().next().is_some()
&& (config.backup_addons || config.backup_wtf)
{
backup_button = backup_button.on_press(Interaction::Backup);
Expand Down Expand Up @@ -433,6 +424,11 @@ pub fn data_container<'a, 'b>(
let general_settings_title_container = Container::new(general_settings_title)
.style(style::BrightBackgroundContainer(color_palette));

let directories_settings_title =
Text::new(localized_string("wow-directories")).size(DEFAULT_HEADER_FONT_SIZE);
let directories_settings_title_container = Container::new(directories_settings_title)
.style(style::BrightBackgroundContainer(color_palette));

let theme_scale_row = Row::new()
.push(theme_column)
.push(scale_column)
Expand Down Expand Up @@ -512,8 +508,6 @@ pub fn data_container<'a, 'b>(
.push(Space::new(Length::Units(0), Length::Units(5)))
.push(language_container)
.push(Space::new(Length::Units(0), Length::Units(10)))
.push(wow_directory_column)
.push(Space::new(Length::Units(0), Length::Units(10)))
.push(theme_scale_row)
.push(Space::new(Length::Units(0), Length::Units(10)))
.push(alternate_row_color_column)
Expand All @@ -523,6 +517,13 @@ pub fn data_container<'a, 'b>(
.push(config_column)
.push(Space::new(Length::Units(0), Length::Units(30)));

// Directories
scrollable = scrollable
.push(directories_settings_title_container)
.push(Space::new(Length::Units(0), Length::Units(5)))
.push(wow_directory_column)
.push(Space::new(Length::Units(0), Length::Units(20)));

// Backup
scrollable = scrollable
.push(backup_title_row)
Expand Down
4 changes: 2 additions & 2 deletions src/gui/element/status.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use {
super::{DEFAULT_FONT_SIZE, DEFAULT_PADDING},
crate::gui::{style, DirectoryType, Interaction, Message, State},
crate::gui::{style, Interaction, Message, State},
crate::localization::localized_string,
ajour_core::theme::ColorPalette,
iced::{
Expand Down Expand Up @@ -44,7 +44,7 @@ pub fn data_container<'a>(
let onboarding_button: Element<Interaction> =
Button::new(btn_state, onboarding_button_title_container)
.style(style::DefaultButton(color_palette))
.on_press(Interaction::SelectDirectory(DirectoryType::Wow))
.on_press(Interaction::SelectWowDirectory(None))
.into();

colum = colum
Expand Down
Loading
点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载