Skip to content

Commit

Permalink
Always check all Lutris YAML files regardless of database presence
Browse files Browse the repository at this point in the history
  • Loading branch information
mtkennerly committed Jun 20, 2024
1 parent 1abc826 commit 730acc7
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 54 deletions.
7 changes: 7 additions & 0 deletions src/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,13 @@ impl StrictPath {
self.as_std_path_buf()?.read_dir()
}

pub fn file_stem(&self) -> Option<String> {
self.as_std_path_buf()
.ok()?
.file_stem()
.map(|x| x.to_string_lossy().to_string())
}

// TODO: Refactor to use `popped()`?
pub fn parent(&self) -> Option<Self> {
self.as_std_path_buf().ok()?.parent().map(Self::from)
Expand Down
104 changes: 51 additions & 53 deletions src/scan/launchers/lutris.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ mod spec {

/// For `games/foo.yml`, this would be `foo`.
#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct BareName(pub String);
pub struct Id(pub String);

impl std::fmt::Display for BareName {
impl std::fmt::Display for Id {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", &self.0)
}
Expand Down Expand Up @@ -61,17 +61,23 @@ struct Pending {
}

impl Pending {
fn merge(self, other: Option<Self>) -> Self {
let Some(other) = other else {
return self;
};

Self {
name: self.name.or(other.name),
slug: self.slug.or(other.slug),
prefix: self.prefix.or(other.prefix),
platform: self.platform.or(other.platform),
install_dir: self.install_dir.or(other.install_dir),
fn merge(db: Option<Self>, spec: Option<Self>) -> Self {
match (db, spec) {
(None, None) => Self::default(),
(None, Some(spec)) => spec,
(Some(db), None) => db,
(Some(db), Some(spec)) => {
Self {
name: db.name.or(spec.name),
slug: db.slug.or(spec.slug),
// Apparently, if you change the prefix in the Lutris GUI,
// Lutris updates the spec, but not the database,
// so we prefer the spec version.
prefix: spec.prefix.or(db.prefix),
platform: spec.platform.or(db.platform),
install_dir: db.install_dir.or(spec.install_dir),
}
}
}
}

Expand Down Expand Up @@ -107,43 +113,45 @@ impl Pending {
}
}

pub fn scan(root: &RootsConfig, title_finder: &TitleFinder) -> HashMap<String, HashSet<LauncherGame>> {
let mut games = HashMap::<String, HashSet<LauncherGame>>::new();
#[derive(Debug, Default, Clone)]
struct PendingGroup {
db: Option<Pending>,
spec: Option<Pending>,
}

pub fn scan(root: &RootsConfig, title_finder: &TitleFinder) -> HashMap<String, HashSet<LauncherGame>> {
log::trace!("Scanning Lutris root for games: {:?}", &root.path);

let mut groups = HashMap::<spec::Id, PendingGroup>::new();
match scan_db(root) {
Ok(db_games) => {
for (spec, db_pending) in db_games {
let spec_pending = find_spec(&spec, &root.path);
log::trace!(
"Evaluating game, bare name: {spec}, from DB: {db_pending:?} + from spec: {spec_pending:?}"
);

if let Some((title, game)) = db_pending.merge(spec_pending).evaluate(title_finder) {
log::trace!("Evaluated to '{title}': {game:?}");
games.entry(title).or_default().insert(game);
} else {
log::trace!("Unable to determine game");
}
for (spec_id, pending) in db_games {
groups.entry(spec_id).or_default().db = Some(pending);
}
}
Err(e) => {
log::error!("Failed to read database: {e:?}");
}
}
for spec_path in root.path.joined("games/*.y*ml").glob() {
let Some(pending) = read_spec(&spec_path) else {
continue;
};
let Some(id) = spec_path.file_stem() else {
continue;
};
groups.entry(spec::Id(id)).or_default().spec = Some(pending);
}

for spec_path in root.path.joined("games/*.y*ml").glob() {
let Some(spec_pending) = read_spec(&spec_path) else {
continue;
};
log::trace!("Evaluating game from spec only: {spec_pending:?}");
let mut games = HashMap::<String, HashSet<LauncherGame>>::new();
for (id, PendingGroup { db, spec }) in groups {
log::debug!("Evaluating game, bare name: {id}, from DB: {db:?} + from spec: {spec:?}");

if let Some((title, game)) = spec_pending.evaluate(title_finder) {
log::trace!("Evaluated to '{title}': {game:?}");
games.entry(title).or_default().insert(game);
} else {
log::trace!("Unable to determine game");
}
}
if let Some((title, game)) = Pending::merge(db, spec).evaluate(title_finder) {
log::debug!("Evaluated to '{title}': {game:?}");
games.entry(title).or_default().insert(game);
} else {
log::trace!("Unable to determine game");
}
}

Expand All @@ -160,7 +168,7 @@ pub fn scan(root: &RootsConfig, title_finder: &TitleFinder) -> HashMap<String, H
games
}

fn scan_db(root: &RootsConfig) -> Result<HashMap<spec::BareName, Pending>, Error> {
fn scan_db(root: &RootsConfig) -> Result<HashMap<spec::Id, Pending>, Error> {
#[derive(Debug)]
struct Row {
name: Option<String>,
Expand All @@ -176,7 +184,7 @@ fn scan_db(root: &RootsConfig) -> Result<HashMap<spec::BareName, Pending>, Error
return Err(Error::NoDatabase);
}

let mut games = HashMap::<spec::BareName, Pending>::new();
let mut games = HashMap::<spec::Id, Pending>::new();

let Ok(file) = db_file.as_std_path_buf() else {
return Ok(games);
Expand Down Expand Up @@ -205,7 +213,7 @@ fn scan_db(root: &RootsConfig) -> Result<HashMap<spec::BareName, Pending>, Error
log::warn!("Ignoring row with empty `configpath`");
continue;
}
spec::BareName(spec)
spec::Id(spec)
} else {
log::warn!("Ignoring row without `configpath`");
continue;
Expand Down Expand Up @@ -238,16 +246,6 @@ fn scan_db(root: &RootsConfig) -> Result<HashMap<spec::BareName, Pending>, Error
Ok(games)
}

fn find_spec(name: &spec::BareName, root: &StrictPath) -> Option<Pending> {
for candidate in root.joined(&format!("games/{name}.y*ml")).glob() {
if candidate.is_file() {
return read_spec(&candidate);
}
}

None
}

fn read_spec(file: &StrictPath) -> Option<Pending> {
log::debug!("Inspecting Lutris game file: {:?}", file);

Expand Down Expand Up @@ -408,7 +406,7 @@ mod tests {
hash_map! {
"Windows Game 1".to_string(): hash_set![LauncherGame {
install_dir: Some(StrictPath::new("/home/deck/Games/service/windows-game/drive_c/game".to_string())),
prefix: Some(StrictPath::new("/home/deck/Games/service/windows-game-1".to_string())),
prefix: Some(StrictPath::new("/home/deck/Games/service/windows-game-1b".to_string())),
platform: Some(Os::Windows),
}],
"Windows Game 2".to_string(): hash_set![LauncherGame {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
game:
args: ''
exe: /home/deck/Games/service/windows-game/drive_c/game/YookaLaylee64.exe
prefix: /home/deck/Games/service/windows-game
prefix: /home/deck/Games/service/windows-game-1b
working_dir: /home/deck/Games/service/windows-game/drive_c/game
game_slug: windows-game
name: Windows Game
Expand Down

0 comments on commit 730acc7

Please sign in to comment.