Skip to content

Commit

Permalink
#95: Add the option to configure additional manifests
Browse files Browse the repository at this point in the history
  • Loading branch information
mtkennerly committed Dec 19, 2023
1 parent 6699659 commit 8e87ad6
Show file tree
Hide file tree
Showing 10 changed files with 274 additions and 75 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
## Unreleased

* Added:
* You can now configure additional manifests,
which Ludusavi will download and use just like the primary one.
This allows the community to create additional save lists for specific purposes
that might not be covered by PCGamingWiki.
* CLI: `wrap` command to do a restore before playing a game and a backup afterwards.
([Contributed by sluedecke](https://github.com/mtkennerly/ludusavi/pull/235))
* When a path or URL fails to open, additional information is now logged.
Expand Down
6 changes: 3 additions & 3 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ pub fn run(sub: Subcommand, no_manifest_update: bool, try_manifest_update: bool)
prepare_backup_target(&backup_dir)?;
}

manifest.incorporate_extensions(&config.roots, &config.custom_games);
manifest.incorporate_extensions(&config);

let games_specified = !games.is_empty();
let subjects = GameSubjects::new(manifest.0.keys().cloned().collect(), games);
Expand Down Expand Up @@ -573,7 +573,7 @@ pub fn run(sub: Subcommand, no_manifest_update: bool, try_manifest_update: bool)

let mut manifest = load_manifest(&config, &mut cache, no_manifest_update, try_manifest_update)?;

manifest.incorporate_extensions(&config.roots, &config.custom_games);
manifest.incorporate_extensions(&config);

let restore_dir = match path {
None => config.restore.path.clone(),
Expand Down Expand Up @@ -605,7 +605,7 @@ pub fn run(sub: Subcommand, no_manifest_update: bool, try_manifest_update: bool)
Subcommand::Manifest { sub: manifest_sub } => match manifest_sub {
ManifestSubcommand::Show { api } => {
let mut manifest = Manifest::load().unwrap_or_default();
manifest.incorporate_extensions(&config.roots, &config.custom_games);
manifest.incorporate_extensions(&config);

if api {
println!("{}", serde_json::to_string(&manifest).unwrap());
Expand Down
60 changes: 49 additions & 11 deletions src/gui/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ impl App {

Command::perform(
async move {
manifest.incorporate_extensions(&config.roots, &config.custom_games);
manifest.incorporate_extensions(&config);
let subjects: Vec<_> = if let Some(games) = &games {
manifest.0.keys().filter(|k| games.contains(k)).cloned().collect()
} else if !previewed_games.is_empty() && all_scanned {
Expand Down Expand Up @@ -1233,26 +1233,37 @@ impl Application for App {
},
)
}
Message::ManifestUpdated(updated) => {
Message::ManifestUpdated(updates) => {
self.updating_manifest = false;
let mut errors = vec![];

let updated = match updated {
Ok(Some(updated)) => updated,
Ok(None) => return self.close_specific_modal(Modal::UpdatingManifest),
Err(e) => {
return self.show_error(e);
for update in updates {
match update {
Ok(Some(update)) => {
self.cache.update_manifest(update);
}
Ok(None) => {}
Err(e) => {
errors.push(e);
}
}
};
}

self.cache.update_manifest(updated);
self.cache.save();

match Manifest::load() {
Ok(x) => {
self.manifest = x;
self.close_specific_modal(Modal::UpdatingManifest)
}
Err(variant) => self.show_modal(Modal::Error { variant }),
Err(e) => {
errors.push(e);
}
}

if errors.is_empty() {
self.close_specific_modal(Modal::UpdatingManifest)
} else {
self.show_modal(Modal::Errors { errors })
}
}
Message::Backup(phase) => self.handle_backup(phase),
Expand Down Expand Up @@ -1316,6 +1327,29 @@ impl Application for App {
self.config.save();
Command::none()
}
Message::EditedSecondaryManifest(action) => {
match action {
EditAction::Add => {
self.text_histories.secondary_manifests.push(Default::default());
self.config.manifest.secondary.push("".to_string());
}
EditAction::Change(index, value) => {
self.text_histories.secondary_manifests[index].push(&value);
self.config.manifest.secondary[index] = value;
}
EditAction::Remove(index) => {
self.text_histories.secondary_manifests.remove(index);
self.config.manifest.secondary.remove(index);
}
EditAction::Move(index, direction) => {
let offset = direction.shift(index);
self.text_histories.secondary_manifests.swap(index, offset);
self.config.manifest.secondary.swap(index, offset);
}
}
self.config.save();
Command::none()
}
Message::SelectedRootStore(index, store) => {
self.config.roots[index].store = store;
self.config.save();
Expand Down Expand Up @@ -1923,6 +1957,10 @@ impl Application for App {
),
UndoSubject::Root(i) => shortcut
.apply_to_strict_path_field(&mut self.config.roots[i].path, &mut self.text_histories.roots[i]),
UndoSubject::SecondaryManifest(i) => shortcut.apply_to_string_field(
&mut self.config.manifest.secondary[i],
&mut self.text_histories.secondary_manifests[i],
),
UndoSubject::RedirectSource(i) => shortcut.apply_to_strict_path_field(
&mut self.config.redirects[i].source,
&mut self.text_histories.redirects[i].source,
Expand Down
5 changes: 4 additions & 1 deletion src/gui/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ pub enum Message {
UpdateTime,
PruneNotifications,
UpdateManifest,
ManifestUpdated(Result<Option<ManifestUpdate>, Error>),
ManifestUpdated(Vec<Result<Option<ManifestUpdate>, Error>>),
Backup(BackupPhase),
Restore(RestorePhase),
ValidateBackups(ValidatePhase),
Expand All @@ -115,6 +115,7 @@ pub enum Message {
FindRoots,
ConfirmAddMissingRoots(Vec<RootsConfig>),
EditedRoot(EditAction),
EditedSecondaryManifest(EditAction),
SelectedRootStore(usize, Store),
SelectedRedirectKind(usize, RedirectKind),
EditedRedirect(EditAction, Option<RedirectEditActionField>),
Expand Down Expand Up @@ -615,6 +616,7 @@ pub enum UndoSubject {
BackupSearchGameName,
RestoreSearchGameName,
Root(usize),
SecondaryManifest(usize),
RedirectSource(usize),
RedirectTarget(usize),
CustomGameName(usize),
Expand All @@ -637,6 +639,7 @@ impl UndoSubject {
| UndoSubject::BackupSearchGameName
| UndoSubject::RestoreSearchGameName
| UndoSubject::Root(_)
| UndoSubject::SecondaryManifest(_)
| UndoSubject::RedirectSource(_)
| UndoSubject::RedirectTarget(_)
| UndoSubject::CustomGameName(_)
Expand Down
94 changes: 93 additions & 1 deletion src/gui/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ use crate::{
common::{BackupPhase, BrowseSubject, Message, ScrollSubject, UndoSubject},
shortcuts::TextHistories,
style,
widget::{checkbox, pick_list, text, Column, Container, Row, Tooltip},
widget::{checkbox, pick_list, text, Column, Container, IcedParentExt, Row, Tooltip},
},
lang::TRANSLATOR,
resource::{
cache::Cache,
config::{Config, RedirectKind},
manifest::Store,
},
Expand Down Expand Up @@ -51,6 +52,97 @@ pub fn root<'a>(config: &Config, histories: &TextHistories, modifiers: &keyboard
Container::new(content)
}

pub fn manifest<'a>(config: &Config, cache: &'a Cache, histories: &TextHistories) -> Container<'a> {
let label_width = Length::Fixed(160.0);
let left_offset = Length::Fixed(70.0);
let right_offset = Length::Fixed(70.0);

let get_checked = |url: &str, cache: &'a Cache| {
let cached = cache.manifests.get(url)?;
let checked = match cached.checked {
Some(x) => chrono::DateTime::<chrono::Local>::from(x)
.format("%Y-%m-%dT%H:%M:%S")
.to_string(),
None => "?".to_string(),
};
Some(Container::new(text(checked)).width(label_width))
};

let get_updated = |url: &str, cache: &'a Cache| {
let cached = cache.manifests.get(url)?;
let updated = match cached.updated {
Some(x) => chrono::DateTime::<chrono::Local>::from(x)
.format("%Y-%m-%dT%H:%M:%S")
.to_string(),
None => "?".to_string(),
};
Some(Container::new(text(updated)).width(label_width))
};

let mut content = Column::new()
.padding(5)
.spacing(5)
.push(
Row::new()
.spacing(20)
.align_items(Alignment::Center)
.push_if(
|| !config.manifest.secondary.is_empty(),
|| Space::with_width(left_offset),
)
.push(text(TRANSLATOR.url_label()).width(Length::Fill))
.push(Container::new(text(TRANSLATOR.checked_label())).width(label_width))
.push(Container::new(text(TRANSLATOR.updated_label())).width(label_width))
.push_if(
|| !config.manifest.secondary.is_empty(),
|| Space::with_width(right_offset),
),
)
.push(
Row::new()
.spacing(20)
.align_items(Alignment::Center)
.push_if(
|| !config.manifest.secondary.is_empty(),
|| Space::with_width(left_offset),
)
.push(iced::widget::TextInput::new("", &config.manifest.url).width(Length::Fill))
.push_some(|| get_checked(&config.manifest.url, cache))
.push_some(|| get_updated(&config.manifest.url, cache))
.push_if(
|| !config.manifest.secondary.is_empty(),
|| Space::with_width(right_offset),
),
);

content = config
.manifest
.secondary
.iter()
.enumerate()
.fold(content, |column, (i, _)| {
column.push(
Row::new()
.spacing(20)
.align_items(Alignment::Center)
.push(button::move_up(Message::EditedSecondaryManifest, i))
.push(button::move_down(
Message::EditedSecondaryManifest,
i,
config.manifest.secondary.len(),
))
.push(histories.input(UndoSubject::SecondaryManifest(i)))
.push_some(|| get_checked(&config.manifest.secondary[i], cache))
.push_some(|| get_updated(&config.manifest.secondary[i], cache))
.push(button::remove(Message::EditedSecondaryManifest, i)),
)
});

content = content.push(button::add(Message::EditedSecondaryManifest));

Container::new(content).style(style::Container::GameListEntry)
}

pub fn redirect<'a>(config: &Config, histories: &TextHistories, modifiers: &keyboard::Modifiers) -> Container<'a> {
let redirects = config.get_redirects();

Expand Down
36 changes: 1 addition & 35 deletions src/gui/screen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -428,41 +428,7 @@ pub fn other<'a>(
.push(text(TRANSLATOR.manifest_label()).width(100))
.push(button::refresh(Message::UpdateManifest, updating_manifest)),
)
.push_some(|| {
let cached = cache.manifests.get(&config.manifest.url)?;
let checked = match cached.checked {
Some(x) => chrono::DateTime::<chrono::Local>::from(x)
.format("%Y-%m-%dT%H:%M:%S")
.to_string(),
None => "?".to_string(),
};
let updated = match cached.updated {
Some(x) => chrono::DateTime::<chrono::Local>::from(x)
.format("%Y-%m-%dT%H:%M:%S")
.to_string(),
None => "?".to_string(),
};
Some(
Container::new(
Column::new()
.padding(5)
.spacing(4)
.push(
Row::new()
.align_items(iced::Alignment::Center)
.push(Container::new(text(TRANSLATOR.checked_label())).width(100))
.push(Container::new(text(checked))),
)
.push(
Row::new()
.align_items(iced::Alignment::Center)
.push(Container::new(text(TRANSLATOR.updated_label())).width(100))
.push(Container::new(text(updated))),
),
)
.style(style::Container::GameListEntry),
)
}),
.push(editor::manifest(config, cache, histories).padding([10, 0, 0, 0])),
)
.push(
Column::new()
Expand Down
15 changes: 14 additions & 1 deletion src/gui/shortcuts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ pub struct TextHistories {
pub backup_search_game_name: TextHistory,
pub restore_search_game_name: TextHistory,
pub roots: Vec<TextHistory>,
pub secondary_manifests: Vec<TextHistory>,
pub redirects: Vec<RedirectHistory>,
pub custom_games: Vec<CustomGameHistory>,
pub backup_filter_ignored_paths: Vec<TextHistory>,
Expand Down Expand Up @@ -225,6 +226,10 @@ impl TextHistories {
histories.roots.push(TextHistory::path(&x.path));
}

for x in &config.manifest.secondary {
histories.secondary_manifests.push(TextHistory::raw(x));
}

for x in &config.redirects {
histories.redirects.push(RedirectHistory {
source: TextHistory::path(&x.source),
Expand Down Expand Up @@ -276,6 +281,9 @@ impl TextHistories {
UndoSubject::BackupSearchGameName => self.backup_search_game_name.current(),
UndoSubject::RestoreSearchGameName => self.restore_search_game_name.current(),
UndoSubject::Root(i) => self.roots.get(i).map(|x| x.current()).unwrap_or_default(),
UndoSubject::SecondaryManifest(i) => {
self.secondary_manifests.get(i).map(|x| x.current()).unwrap_or_default()
}
UndoSubject::RedirectSource(i) => self.redirects.get(i).map(|x| x.source.current()).unwrap_or_default(),
UndoSubject::RedirectTarget(i) => self.redirects.get(i).map(|x| x.target.current()).unwrap_or_default(),
UndoSubject::CustomGameName(i) => self.custom_games.get(i).map(|x| x.name.current()).unwrap_or_default(),
Expand Down Expand Up @@ -324,6 +332,9 @@ impl TextHistories {
value,
}),
UndoSubject::Root(i) => Box::new(move |value| Message::EditedRoot(EditAction::Change(i, value))),
UndoSubject::SecondaryManifest(i) => {
Box::new(move |value| Message::EditedSecondaryManifest(EditAction::Change(i, value)))
}
UndoSubject::RedirectSource(i) => Box::new(move |value| {
Message::EditedRedirect(EditAction::Change(i, value), Some(RedirectEditActionField::Source))
}),
Expand Down Expand Up @@ -366,6 +377,7 @@ impl TextHistories {
UndoSubject::BackupSearchGameName => TRANSLATOR.search_game_name_placeholder(),
UndoSubject::RestoreSearchGameName => TRANSLATOR.search_game_name_placeholder(),
UndoSubject::Root(_) => "".to_string(),
UndoSubject::SecondaryManifest(_) => "".to_string(),
UndoSubject::RedirectSource(_) => TRANSLATOR.redirect_source_placeholder(),
UndoSubject::RedirectTarget(_) => TRANSLATOR.redirect_target_placeholder(),
UndoSubject::CustomGameName(_) => TRANSLATOR.custom_game_name_placeholder(),
Expand Down Expand Up @@ -395,7 +407,8 @@ impl TextHistories {
spacing: 5.0,
side: text_input::Side::Right,
}),
UndoSubject::BackupSearchGameName
UndoSubject::SecondaryManifest(_)
| UndoSubject::BackupSearchGameName
| UndoSubject::RestoreSearchGameName
| UndoSubject::CustomGameName(_)
| UndoSubject::CustomGameRegistry(_, _)
Expand Down
Loading

0 comments on commit 8e87ad6

Please sign in to comment.