Initial parser for butlerdb

This commit is contained in:
Philip Kristoffersen
2021-09-29 21:40:35 +02:00
parent 1f66856e46
commit 750379202e
11 changed files with 236 additions and 10 deletions
Generated
+2
View File
@@ -1092,6 +1092,8 @@ dependencies = [
"failure", "failure",
"fl2rust", "fl2rust",
"fltk", "fltk",
"nom 7.0.0",
"nom_locate",
"reqwest", "reqwest",
"serde 1.0.130", "serde 1.0.130",
"serde_json", "serde_json",
+10 -8
View File
@@ -5,16 +5,18 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
steam_shortcuts_util = "1.1.4" steam_shortcuts_util = "^1.1.4"
steamgriddb_api = "0.2,0" steamgriddb_api = "^0.2,0"
serde = { version = "1.0.130", features = ["derive"] } serde = { version = "^1.0.130", features = ["derive"] }
serde_json = "1.0.68" serde_json = "^1.0.68"
tokio = { version = "1.11.0", features = ["full"] } tokio = { version = "^1.11.0", features = ["full"] }
reqwest = { version = "0.11.4", features = ["default"] } reqwest = { version = "^0.11.4", features = ["default"] }
config = "0.11.0" config = "^0.11.0"
failure = "0.1.8" failure = "^0.1.8"
#https://fltk-rs.github.io/fltk-rs/ #https://fltk-rs.github.io/fltk-rs/
fltk = { version = "^1.2", features = ["fltk-bundled"], optional = true } fltk = { version = "^1.2", features = ["fltk-bundled"], optional = true }
nom = "^7.0.*"
nom_locate = "^3.0.*"
[build-dependencies] [build-dependencies]
fl2rust = { version = "0.4", optional = true } fl2rust = { version = "0.4", optional = true }
+3
View File
@@ -6,5 +6,8 @@ debug= false
[steam] [steam]
[itch]
enabled = false
[steamgrid_db] [steamgrid_db]
enabled = true enabled = true
+73
View File
@@ -0,0 +1,73 @@
use nom::{
bytes::complete::{tag, take_until},
multi::many0,
IResult,
};
pub(crate) struct DbPaths<'a> {
pub(crate) base_path: &'a str,
pub(crate) path: &'a str,
}
pub(crate) fn parse_butler_db<'a>(content: &'a [u8]) -> nom::IResult<&[u8], Vec<DbPaths<'a>>> {
many0(parse_path)(content)
}
fn parse_path<'a>(i: &'a [u8]) -> nom::IResult<&[u8], DbPaths<'a>> {
let prefix = "{\"basePath\":\"";
let suffix = "\",\"totalSize\"";
let (i, _taken) = take_until(prefix)(i)?;
let (i, _taken) = tag(prefix)(i)?;
let (i, base_path) = take_until(suffix)(i)?;
let prefix = ":[{\"path\":\"";
let suffix = "\",\"depth";
let (i, _taken) = take_until(prefix)(i)?;
let (i, _taken) = tag(prefix)(i)?;
let (i, path) = take_until(suffix)(i)?;
IResult::Ok((
i,
DbPaths {
base_path: std::str::from_utf8(base_path).unwrap(),
path: std::str::from_utf8(path).unwrap(),
},
))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_itch_butler_db_test() {
let content = include_bytes!("../testdata/itch/butler.db-wal");
let result = parse_butler_db(content);
assert!(result.is_ok());
let (_r, paths) = result.unwrap();
assert_eq!(paths.len(), 6);
assert_eq!(paths[0].base_path, "/home/philip/.config/itch/apps/islands");
assert_eq!(paths[0].path, "Islands_Linux.x86_64");
assert_eq!(
paths[1].base_path,
"/home/philip/.config/itch/apps/night-in-the-woods"
);
assert_eq!(paths[1].path, "Night in the Woods.x86_64");
assert_eq!(paths[2].base_path, "/home/philip/.config/itch/apps/islands");
assert_eq!(paths[2].path, "Islands_Linux.x86_64");
assert_eq!(
paths[3].base_path,
"/home/philip/.config/itch/apps/overland"
);
assert_eq!(paths[3].path, "Overland.x86_64");
assert_eq!(
paths[4].base_path,
"/home/philip/.config/itch/apps/night-in-the-woods"
);
assert_eq!(paths[4].path, "Night in the Woods.x86_64");
assert_eq!(paths[5].base_path, "/home/philip/.config/itch/apps/islands");
assert_eq!(paths[5].path, "Islands_Linux.x86_64");
}
}
+35
View File
@@ -0,0 +1,35 @@
use std::path::Path;
use serde::{Deserialize, Serialize};
use steam_shortcuts_util::{shortcut::ShortcutOwned, Shortcut};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ItchGame {
pub install_path: String,
pub executable: String,
pub title: String,
}
impl From<ItchGame> for ShortcutOwned {
fn from(game: ItchGame) -> Self {
let exe = Path::new(&game.install_path).join(&game.executable);
let exe = exe.to_str().unwrap().to_string();
let shortcut = Shortcut::new(
0,
game.title.as_str(),
exe.as_str(),
&game.install_path,
exe.as_str(),
"",
"",
);
let mut owned_shortcut = shortcut.to_owned();
owned_shortcut.tags.push("Itch".to_owned());
owned_shortcut.tags.push("Ready TO Play".to_owned());
owned_shortcut.tags.push("Installed".to_owned());
owned_shortcut
}
}
+86
View File
@@ -0,0 +1,86 @@
use super::butler_db_parser::*;
use super::{ItchGame, ItchSettings};
use crate::platform::Platform;
use failure::*;
use std::path::Path;
pub struct ItchPlatform {
settings: ItchSettings,
}
impl ItchPlatform {
pub fn new(settings: ItchSettings) -> ItchPlatform {
ItchPlatform { settings }
}
}
impl Platform<ItchGame, ItchErrors> for ItchPlatform {
fn enabled(&self) -> bool {
self.settings.enabled
}
fn name(&self) -> &str {
"Itch"
}
fn get_shortcuts(&self) -> Result<Vec<ItchGame>, ItchErrors> {
let itch_location = self.settings.location.clone();
let itch_location = itch_location.unwrap_or_else(get_default_location);
let itch_db_location = Path::new(&itch_location).join("db").join("butler.db-wal");
if !itch_db_location.exists() {
return Err(ItchErrors::PathNotFound {
path: itch_db_location.to_str().unwrap().to_string(),
});
}
let shortcut_bytes = std::fs::read(&itch_db_location).unwrap();
let res = match parse_butler_db(&shortcut_bytes) {
Ok((_, shortcuts)) => Ok(shortcuts),
Err(e) => Err(ItchErrors::ParseError {
error: e.to_string(),
path: itch_db_location.to_str().unwrap().to_string(),
}),
}?;
let res = res
.iter()
.map(|paths| ItchGame {
install_path: paths.base_path.to_owned(),
executable: paths.path.to_owned(),
title: paths.path.to_owned(),
})
.collect();
Ok(res)
}
}
#[cfg(target_os = "linux")]
fn get_default_location() -> String {
//If we don't have a home drive we have to just die
let home = std::env::var("HOME").expect("Expected a home variable to be defined");
format!("{}/.config/itch/", home)
}
#[cfg(target_os = "windows")]
fn get_default_location() -> String {
let key = "PROGRAMFILES(X86)";
let program_files = env::var(key).expect("Expected a program files variable to be defined");
format!("{}//Itch//", program_files)
}
#[derive(Debug, Fail)]
pub enum ItchErrors {
#[fail(
display = "Itch path: {} could not be found. Try to specify a different path for the Itch.",
path
)]
PathNotFound { path: String },
#[fail(display = "Could not read Itch db at {} error: {}", path, error)]
ReadDirError { path: String, error: std::io::Error },
#[fail(display = "Could not parse Itch db at {} error: {}", path, error)]
ParseError { path: String, error: String },
}
+8
View File
@@ -0,0 +1,8 @@
mod settings;
mod itch_game;
mod itch_platform;
mod butler_db_parser;
pub use settings::*;
pub use itch_game::*;
pub use itch_platform::*;
+7
View File
@@ -0,0 +1,7 @@
use serde::Deserialize;
#[derive(Debug, Deserialize, Clone)]
pub struct ItchSettings {
pub enabled: bool,
pub location: Option<String>,
}
+10 -1
View File
@@ -1,6 +1,10 @@
use crate::steamgriddb::{download_images, CachedSearch}; use crate::{
itch::ItchPlatform,
steamgriddb::{download_images, CachedSearch},
};
use std::{fs::File, io::Write, path::Path}; use std::{fs::File, io::Write, path::Path};
mod egs; mod egs;
mod itch;
mod legendary; mod legendary;
mod platform; mod platform;
mod settings; mod settings;
@@ -84,6 +88,11 @@ async fn run_sync() -> Result<(), Box<dyn Error>> {
&mut new_user_shortcuts, &mut new_user_shortcuts,
); );
update_platform_shortcuts(
&ItchPlatform::new(settings.itch.clone()),
&mut new_user_shortcuts,
);
let shortcuts = new_user_shortcuts.iter().map(|f| f.borrow()).collect(); let shortcuts = new_user_shortcuts.iter().map(|f| f.borrow()).collect();
save_shortcuts(&shortcuts, Path::new(&shortcut_info.path)); save_shortcuts(&shortcuts, Path::new(&shortcut_info.path));
+2 -1
View File
@@ -1,4 +1,4 @@
use crate::{egs::EpicGamesLauncherSettings, legendary::LegendarySettings, steam::SteamSettings, steamgriddb::SteamGridDbSettings}; use crate::{egs::EpicGamesLauncherSettings, itch::ItchSettings, legendary::LegendarySettings, steam::SteamSettings, steamgriddb::SteamGridDbSettings};
use config::{Config, ConfigError, Environment, File}; use config::{Config, ConfigError, Environment, File};
use serde::Deserialize; use serde::Deserialize;
@@ -9,6 +9,7 @@ pub struct Settings {
pub debug: bool, pub debug: bool,
pub epic_games: EpicGamesLauncherSettings, pub epic_games: EpicGamesLauncherSettings,
pub legendary: LegendarySettings, pub legendary: LegendarySettings,
pub itch: ItchSettings,
pub steamgrid_db: SteamGridDbSettings, pub steamgrid_db: SteamGridDbSettings,
pub steam: SteamSettings pub steam: SteamSettings
} }
BIN
View File
Binary file not shown.