Skip to content

Commit

Permalink
Feat: custom mount support (#7)
Browse files Browse the repository at this point in the history
* chore: updated artifact to include folder

* feat: added custom mount support

* bugfix: updated polling to work again
  • Loading branch information
CEbbinghaus authored Nov 1, 2023
1 parent 3196c83 commit fabe780
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 56 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
uses: actions/upload-artifact@v3
with:
name: "MicroSDeck"
path: release/MicroSDeck/*
path: release/MicroSDeck

deploy:
if: github.ref == 'refs/heads/master'
Expand Down
14 changes: 7 additions & 7 deletions backend/src/ds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::{
err::Error,
sdcard::get_steam_acf_files,
};
use log::{error, info};
use log::error;
use semver::Version;
use serde::{Deserialize, Serialize};
use slotmap::{DefaultKey, SlotMap};
Expand Down Expand Up @@ -263,16 +263,16 @@ impl StoreData {
*self.hashes.entry(key.to_string()).or_insert(0) = hash;
}

pub fn is_hash_changed(&self, id: &'_ str) -> Option<u64> {
info!("Checking Hashes. Current values: {:?}", self.hashes);

let file_metadata: Vec<_> = get_steam_acf_files()
pub fn is_hash_changed(&self, id: &'_ str, mount: &Option<String>) -> Option<u64> {
let file_metadata: Vec<_> = get_steam_acf_files(mount)
.ok()?
.filter_map(|f| fs::metadata(f.path()).ok())
.collect();

let mut s = DefaultHasher::new();

mount.hash(&mut s);

for metadata in file_metadata {
metadata.len().hash(&mut s);
metadata
Expand Down Expand Up @@ -472,8 +472,8 @@ impl Store {
self.data.read().unwrap().list_cards_with_games()
}

pub fn is_hash_changed(&self, key: &str) -> Option<u64> {
self.data.read().unwrap().is_hash_changed(key)
pub fn is_hash_changed(&self, key: &str, mount: &Option<String>) -> Option<u64> {
self.data.read().unwrap().is_hash_changed(key, mount)
}

pub fn update_hash(&self, key: &str, hash: u64) {
Expand Down
3 changes: 3 additions & 0 deletions backend/src/dto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ fn default_true() -> bool {
pub struct MicroSDCard {
pub uid: String,
pub libid: String,

#[serde(default)]
pub mount: Option<String>,

pub name: String,
#[serde(default)]
Expand Down
38 changes: 27 additions & 11 deletions backend/src/sdcard.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use std::fs::{self, read_to_string, DirEntry};
use std::{
fs::{self, read_to_string, DirEntry},
io,
};

use crate::err::Error;

pub const STEAM_LIB_FILE: &'static str = "/run/media/mmcblk0p1/libraryfolder.vdf";
pub const STEAM_LIB_FOLDER: &'static str = "/run/media/mmcblk0p1/steamapps/";

pub fn is_card_inserted() -> bool {
std::fs::metadata("/sys/block/mmcblk0").is_ok()
}
Expand All @@ -16,13 +16,29 @@ pub fn get_card_cid() -> Option<String> {
.ok()
}

pub fn is_card_steam_formatted() -> bool {
std::fs::metadata("/run/media/mmcblk0p1/libraryfolder.vdf").is_ok()
pub fn has_libraryfolder(mount: &Option<String>) -> bool {
std::fs::metadata(format!(
"/run/media/{}/libraryfolder.vdf",
mount.clone().unwrap_or("mmcblk0p1".into())
))
.is_ok()
}

pub fn read_libraryfolder(mount: &Option<String>) -> io::Result<String> {
std::fs::read_to_string(format!(
"/run/media/{}/libraryfolder.vdf",
mount.clone().unwrap_or("mmcblk0p1".into())
))
}

pub fn get_steam_acf_files() -> Result<impl Iterator<Item = DirEntry>, Error> {
Ok(fs::read_dir(STEAM_LIB_FOLDER)?
.into_iter()
.filter_map(Result::ok)
.filter(|f| f.path().extension().unwrap_or_default().eq("acf")))
pub fn get_steam_acf_files(
mount: &Option<String>,
) -> Result<impl Iterator<Item = DirEntry>, Error> {
Ok(fs::read_dir(format!(
"/run/media/{}/steamapps/",
mount.clone().unwrap_or("mmcblk0p1".into())
))?
.into_iter()
.filter_map(Result::ok)
.filter(|f| f.path().extension().unwrap_or_default().eq("acf")))
}
83 changes: 61 additions & 22 deletions backend/src/watch.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
use crate::{ds::Store, dto::*, err::Error, sdcard::*, steam::*};
use log::{error, info, trace, debug};
use std::{borrow::Borrow, fs, sync::Arc, time::Duration};
use log::{debug, error, info, trace};
use std::borrow::Borrow;
use std::path::{Path, PathBuf};
use std::{fs, sync::Arc, time::Duration};
use tokio::sync::broadcast::Sender;
use tokio::time::interval;

fn read_msd_directory(datastore: &Store) -> Result<(), Error> {
fn read_msd_directory(datastore: &Store, mount: &Option<String>) -> Result<(), Error> {
let cid = get_card_cid().ok_or(Error::from_str("Unable to retrieve CID from MicroSD card"))?;
let res = fs::read_to_string(STEAM_LIB_FILE)?;

let library: LibraryFolder = keyvalues_serde::from_str(res.as_str())?;
let library: LibraryFolder = keyvalues_serde::from_str(&read_libraryfolder(mount)?)?;

trace!("contentid: {}", library.contentid);

let games: Vec<AppState> = get_steam_acf_files()?
let games: Vec<AppState> = get_steam_acf_files(mount)?
.filter_map(|f| fs::read_to_string(f.path()).ok())
.filter_map(|s| keyvalues_serde::from_str(s.as_str()).ok())
.collect();
Expand All @@ -25,6 +26,7 @@ fn read_msd_directory(datastore: &Store) -> Result<(), Error> {
MicroSDCard {
uid: cid.clone(),
libid: library.contentid.clone(),
mount: mount.clone(),
name: library.label,
position: 0,
hidden: false,
Expand Down Expand Up @@ -61,39 +63,37 @@ fn read_msd_directory(datastore: &Store) -> Result<(), Error> {
}

pub async fn start_watch(datastore: Arc<Store>, sender: Sender<CardEvent>) -> Result<(), Error> {
let mut interval = interval(Duration::from_secs(5));
let mut interval = interval(Duration::from_secs(1));

let mut card_inserted = false;

info!("Starting Watcher...");

let mut mount: Option<String> = None;

loop {
interval.tick().await;

debug!("Watch loop");

// No card no worries.
if !is_card_inserted() {
// The card has been removed since the last check
if card_inserted {
debug!("Card was removed");
let _ = sender.send(CardEvent::Removed);
}

card_inserted = false;
mount = None;

continue;
}

// was the card inserted since the last check.
let card_changed = !card_inserted;
card_inserted = true;

// There is no steam directory so it hasn't been formatted.
if !is_card_steam_formatted() {
debug!("card is not steam formatted");
continue;
if !card_inserted {
let _ = sender.send(CardEvent::Inserted);
mount = None;
}

card_inserted = true;

let cid = match get_card_cid() {
Some(v) => v,
None => {
Expand All @@ -102,20 +102,59 @@ pub async fn start_watch(datastore: Arc<Store>, sender: Sender<CardEvent>) -> Re
}
};

if card_changed {
let _ = sender.send(CardEvent::Inserted);
if !has_libraryfolder(&mount) {
debug!(
"could not find library folder under mount {}",
mount.clone().unwrap_or("mmcblk0".into())
);
debug!("trying to automatically determine label");

if mount == None {
if let Some(card) = datastore.get_card(&cid).ok() {
if card.mount != None {
debug!("MicroSD card had preexisting mount saved. Reusing that.");
}
mount = card.mount
}
}

// Whatever we loaded did not work.
if mount != None && !has_libraryfolder(&mount) {
debug!("mount {mount:?} does not resolve library. Removing it");
mount = None;
}

if mount == None {
for entry in Path::new("/dev/disk/by-label")
.read_dir()?
.filter_map(|dir| dir.ok())
{
if entry.path().canonicalize()? == PathBuf::from("/dev/mmcblk0p1") {
let label = entry.file_name();
info!("Found MicroSD Card label {label:?}");
mount = Some(label.to_string_lossy().to_string());
}
}
}

let _ = datastore.update_card(&cid, |card| {
card.mount = mount.clone();
Ok(())
});

continue;
}

// Do we have changes in the steam directory. This should only occur when something has been added/deleted
let hash = match datastore.is_hash_changed(&cid) {
let hash = match datastore.is_hash_changed(&cid, &mount) {
None => continue,
Some(v) => v,
};

info!("Watcher Detected update");

// Something went wrong during parsing. Not great
if let Err(err) = read_msd_directory(datastore.borrow()) {
if let Err(err) = read_msd_directory(datastore.borrow(), &mount) {
error!("Problem reading MicroSD Card: \"{err}\"");
continue;
}
Expand Down
2 changes: 1 addition & 1 deletion lib/src/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ async function wrapFetch({ url, logger }: FetchProps, init?: RequestInit): Promi
return undefined;
}

export async function fetchEventPoll({ url, logger, signal }: FetchProps & { signal: AbortSignal }): Promise<string | boolean | undefined> {
export async function fetchEventPoll({ url, logger, signal }: FetchProps & { signal: AbortSignal }): Promise<string | false | undefined> {
try {
const result = await fetch(`${url}/listen`, {
keepalive: true,
Expand Down
28 changes: 14 additions & 14 deletions lib/src/state/MicoSDeckManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export class MicroSDeckManager {
}

destruct() {
if(this.isDestructed)return;
if (this.isDestructed) return;
this.isDestructed = true;
this.logger?.Log("Deinitializing MicroSDeckManager");
this.abortController.abort("destruct");
Expand All @@ -53,16 +53,16 @@ export class MicroSDeckManager {
async fetch() {
this.enabled = await fetchHealth(this.fetchProps);
this.version = await fetchVersion(this.fetchProps);

await this.fetchCurrent();
await this.fetchCardsAndGames();
this.eventBus.dispatchEvent(new Event("update"));
}

async fetchCurrent(){
async fetchCurrent() {
this.currentCardAndGames = await fetchCurrentCardAndGames(this.fetchProps);
}
async fetchCardsAndGames(){
async fetchCardsAndGames() {
this.cardsAndGames = await fetchCardsAndGames(this.fetchProps) || [];
}

Expand Down Expand Up @@ -95,9 +95,9 @@ export class MicroSDeckManager {
this.pollLock = {};
this.logger?.Debug("Poll");

let result = await fetchEventPoll({...this.fetchProps, signal });
let result = await fetchEventPoll({ ...this.fetchProps, signal });

this.logger?.Debug("Result was: " + (result === undefined ? "undefined" : result), { result });
this.logger?.Debug("Backend detected an update: {result}", { result });

switch (result) {
// Server is down. Lets try again but back off a bit
Expand All @@ -106,15 +106,15 @@ export class MicroSDeckManager {
await sleep(sleepDelay = Math.min(sleepDelay * 1.5, 1000 * 60));
break;

// We got an update. Time to refresh.
case true:
this.logger?.Debug("Card detected an update.");
await this.fetch();

// Request must have timed out
case false:
sleepDelay = 100;
break;

// We got an update. Time to refresh.
default:
this.eventBus.dispatchEvent(new Event(result));
await this.fetch();
}

this.pollLock = undefined;
Expand All @@ -123,13 +123,13 @@ export class MicroSDeckManager {

async updateCard(card: MicroSDCard) {
this.logger?.Debug("Updating card {uid}", card);
await fetchUpdateCard({...this.fetchProps, card});
await fetchUpdateCard({ ...this.fetchProps, card });
await this.fetch()
}

async deleteCard(card: MicroSDCard) {
this.logger?.Debug("Deleting Card {uid}", card);
await fetchDeleteCard({...this.fetchProps, card});
await fetchDeleteCard({ ...this.fetchProps, card });
await this.fetch();
}

Expand All @@ -140,6 +140,6 @@ export class MicroSDeckManager {
}

async fetchCardsForGame(gameId: string) {
return await fetchCardsForGame({...this.fetchProps, gameId})
return await fetchCardsForGame({ ...this.fetchProps, gameId })
}
}

0 comments on commit fabe780

Please sign in to comment.