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
+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>,
}