Skip to content

Commit

Permalink
feat: added grid cleaning
Browse files Browse the repository at this point in the history
  • Loading branch information
Tormak9970 committed Jun 3, 2023
1 parent 476495d commit ce65fc3
Show file tree
Hide file tree
Showing 12 changed files with 371 additions and 21 deletions.
101 changes: 96 additions & 5 deletions src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ mod appinfo_vdf_parser;
mod shortcuts_vdf_parser;
mod vdf_reader;

use std::{path::PathBuf, collections::HashMap, fs::{self, File, write}, io::Write, time::Duration};
use std::{path::PathBuf, collections::HashMap, fs::{self, File}, io::Write, time::Duration};

use appinfo_vdf_parser::open_appinfo_vdf;
use serde_json::{Map, Value};
Expand All @@ -34,6 +34,17 @@ struct Payload {
cwd: String,
}

#[derive(Clone, serde::Serialize)]
#[allow(non_snake_case)]
struct CleanConflicts {
fileAName: String,
fileAPath: String,
fileBName: String,
fileBPath: String,
appid: String,
gridType: String
}

type GridImageCache = HashMap<String, HashMap<String, String>>;

#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]
Expand Down Expand Up @@ -304,9 +315,7 @@ async fn save_changes(app_handle: AppHandle, steam_active_user_id: String, curre
}
logger::log_to_core_file(app_handle.to_owned(), format!("Removed logo position config for {}.", appid).as_str(), 0);
} else {
// let config_file = fs::File::create(&logo_config_path).expect("Should have been able to create or truncate logo pos config file.");

let write_res = write(&logo_config_path, steam_logo_str);
let write_res = fs::write(&logo_config_path, steam_logo_str);

if write_res.is_ok() {
logger::log_to_core_file(app_handle.to_owned(), format!("Wrote logo pos to config for {}.", appid).as_str(), 0);
Expand Down Expand Up @@ -405,6 +414,87 @@ async fn download_grid(app_handle: AppHandle, grid_url: String, dest_path: Strin
}
}

#[tauri::command]
/// Downloads a file from a url.
async fn clean_grids(app_handle: AppHandle, steam_active_user_id: String, preset: String, all_appids: String, selected_game_ids: String) -> String {
logger::log_to_core_file(app_handle.to_owned(), format!("Starting {} grid cleaning.", preset).as_str(), 0);

let appids_arr: Vec<String> = serde_json::from_str(all_appids.as_str()).expect("Should have been able to deserialize appids array.");

let grids_dir_path: String = steam::get_grids_directory(app_handle.to_owned(), steam_active_user_id);
let grids_dir_contents = fs::read_dir(grids_dir_path).unwrap();

let mut found_apps: HashMap<String, (String, String)> = HashMap::new();
let mut conflicts: Vec<CleanConflicts> = Vec::new();


if preset == String::from("clean") {
for dir_entry in grids_dir_contents {
let entry = dir_entry.expect("Should have been able to get directory entry.");

if entry.file_type().unwrap().is_file() {
let full_file_path: PathBuf = entry.path();
let full_file_path_str: &str = full_file_path.to_str().unwrap();
let filename = entry.file_name();
let filename_str: &str = filename.to_str().unwrap();

let (id, grid_type) = zip_controller::get_id_from_grid_name(filename_str);
let id_type_str: String = format!("{}_{}", id, grid_type);

if appids_arr.contains(&id) {
if found_apps.contains_key(&id_type_str) {
// ? There's a conflict
let (other_filename, other_full_path) = found_apps.get(&id_type_str).expect("Map should have contained the id_type_str.");

conflicts.push(CleanConflicts { fileAPath: other_full_path.to_owned(), fileAName: other_filename.to_owned(), fileBPath: String::from(full_file_path_str), fileBName: String::from(filename_str), appid: id.clone(), gridType: grid_type.clone() });

logger::log_to_core_file(app_handle.to_owned(), format!("Detected conflict between {} and {}.", filename_str, other_filename).as_str(), 0);
} else {
found_apps.insert(id_type_str, (String::from(filename_str), String::from(full_file_path_str)));
}
} else {
let remove_res = fs::remove_file(full_file_path);
if remove_res.is_err() {
let err = remove_res.err().unwrap();
return format!("{{ \"error\": \"{}\"}}", err.to_string());
}

logger::log_to_core_file(app_handle.to_owned(), format!("Deleted {}.", filename_str).as_str(), 0);
}
}
}
} else {
let game_ids_arr: Vec<String> = serde_json::from_str(selected_game_ids.as_str()).expect("Should have been able to deserialize selected appids array.");

for dir_entry in grids_dir_contents {
let entry = dir_entry.expect("Should have been able to get directory entry.");

if entry.file_type().unwrap().is_file() {
let full_file_path: PathBuf = entry.path();
let filename = entry.file_name();
let filename_str: &str = filename.to_str().unwrap();

let (id, _) = zip_controller::get_id_from_grid_name(filename_str);

if game_ids_arr.contains(&id) {
let remove_res = fs::remove_file(full_file_path);
if remove_res.is_err() {
let err = remove_res.err().unwrap();
return format!("{{ \"error\": \"{}\"}}", err.to_string());
}

logger::log_to_core_file(app_handle.to_owned(), format!("Deleted {}.", filename_str).as_str(), 0);
}
}
}
}


logger::log_to_core_file(app_handle.to_owned(), format!("{} grid cleaning complete.", preset).as_str(), 0);

return serde_json::to_string(&conflicts).expect("Should have been able to serialize conflict array.");
}


/// Adds the user's steam directory to Tauri FS and Asset scope.
fn add_steam_to_scope(app_handle: &AppHandle) {
Expand Down Expand Up @@ -462,7 +552,8 @@ fn main() {
read_localconfig_vdf,
save_changes,
write_shortcuts,
download_grid
download_grid,
clean_grids
])
.plugin(tauri_plugin_single_instance::init(|app, argv, cwd| {
println!("{}, {argv:?}, {cwd}", app.package_info().name);
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/src/zip_controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use tauri::AppHandle;
use zip;

/// Gets the id for a grid from its name.
fn get_id_from_grid_name(grid_name: &str) -> (String, String) {
pub fn get_id_from_grid_name(grid_name: &str) -> (String, String) {
let dot_index: usize = grid_name.find(".").expect("File should have had a file extension");
let underscore_index_res = grid_name.find("_");
let name: &str = &grid_name[0..dot_index];
Expand Down
3 changes: 3 additions & 0 deletions src/Stores.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ export const showCleanGridsModal = writable(false);

export const showSettingsModal = writable(false);

export const showCleanConflictDialog = writable(false);
export const cleanConflicts: Writable<CleanConflict[]> = writable([]);

export const dbFilters:Writable<DBFilters> = writable({
"Capsule": {
"styles": {
Expand Down
20 changes: 18 additions & 2 deletions src/components/Footer.svelte
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
<script lang="ts">
import { open } from "@tauri-apps/api/shell";
import { AppController } from "../lib/controllers/AppController";
import { WindowController } from "../lib/controllers/WindowController";
import { canSave, isOnline, showManualGamesModal, showBatchApplyModal, showCleanGridsModal, showSettingsModal } from "../Stores";
import Button from "./interactables/Button.svelte";
import IconButton from "./interactables/IconButton.svelte";
import HorizontalSpacer from "./spacers/HorizontalSpacer.svelte";
import { dialog } from "@tauri-apps/api";
async function onCleanGridsClick(): Promise<void> {
if ($canSave) {
const shouldSaveAndOpen = await dialog.ask("You need to save your changes before cleaning. Would you like to save?", {
title: "Found existing changes",
type: "warning"
});
if (shouldSaveAndOpen) {
await AppController.saveChanges();
$showCleanGridsModal = true;
}
} else {
$showCleanGridsModal = true;
}
}
</script>

<div class="footer">
Expand Down Expand Up @@ -38,7 +54,7 @@
</svg>
</IconButton>
<HorizontalSpacer />
<IconButton label="Clean Grids" onClick={() => { $showCleanGridsModal = true; }} width="auto" tooltipPosition="auto">
<IconButton label="Clean Grids" onClick={onCleanGridsClick} width="auto" tooltipPosition="auto">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512" style="height: 12px; width: 12px;">
<!--! Font Awesome Free 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
<path d="M566.6 54.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-192 192-34.7-34.7c-4.2-4.2-10-6.6-16-6.6c-12.5 0-22.6 10.1-22.6 22.6v29.1L364.3 320h29.1c12.5 0 22.6-10.1 22.6-22.6c0-6-2.4-11.8-6.6-16l-34.7-34.7 192-192zM341.1 353.4L222.6 234.9c-42.7-3.7-85.2 11.7-115.8 42.3l-8 8C76.5 307.5 64 337.7 64 369.2c0 6.8 7.1 11.2 13.2 8.2l51.1-25.5c5-2.5 9.5 4.1 5.4 7.9L7.3 473.4C2.7 477.6 0 483.6 0 489.9C0 502.1 9.9 512 22.1 512l173.3 0c38.8 0 75.9-15.4 103.4-42.8c30.6-30.6 45.9-73.1 42.3-115.8z"/>
Expand Down
2 changes: 1 addition & 1 deletion src/components/toast-modals/GridPreviewModal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import VerticalSpacer from "../spacers/VerticalSpacer.svelte";
import Button from "../interactables/Button.svelte";
import { AppController } from "../../lib/controllers/AppController";
import ModalBody from "./modal-utils/ModalBody.svelte";
import ModalBody from "./modal-utils/ModalBody.svelte";
export let onClose: () => void;
Expand Down
194 changes: 194 additions & 0 deletions src/components/toast-modals/clean-grids/CleanConflictDialog.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
<script lang="ts">
import { onMount } from "svelte";
import { GridTypes, cleanConflicts, showCleanConflictDialog } from "../../../Stores";
import ModalBody from "../modal-utils/ModalBody.svelte";
import { ToastController } from "../../../lib/controllers/ToastController";
import { LogController } from "../../../lib/controllers/LogController";
import { fs, tauri } from "@tauri-apps/api";
import Button from "../../interactables/Button.svelte";
import Lazy from "svelte-lazy";
let conflictNumber: number = 1;
let conflict: CleanConflict;
$: conflictGridType = conflict ? conflict.gridType : "";
$: fileAPath = conflict ? tauri.convertFileSrc(conflict.fileAPath) : "";
$: fileBPath = conflict ? tauri.convertFileSrc(conflict.fileBPath) : "";
$: console.log(conflictGridType);
function getNextConflict(): CleanConflict | null {
conflictNumber++;
return $cleanConflicts.length > 0 ? $cleanConflicts.shift() : null;
}
const widths = {
"capsule": 200,
"widecapsule": 280,
"hero": 586,
"logo": 300,
"icon": 256,
};
const heights = {
"capsule": 300,
"widecapsule": 135,
"hero": 210,
"logo": 201,
"icon": 256,
};
async function deleteGrid(keepChoiceA: boolean): Promise<void> {
await fs.removeFile(keepChoiceA ? conflict.fileBPath : conflict.fileAPath);
LogController.log(`Appid: ${conflict.appid}. Keeping ${keepChoiceA ? conflict.fileAName : conflict.fileBName} and deleting ${keepChoiceA ? conflict.fileBName : conflict.fileAName}.`);
conflict = getNextConflict();
if (!conflict) {
$showCleanConflictDialog = false;
$cleanConflicts = [];
ToastController.showSuccessToast("Finished cleaning!");
LogController.log("Finished cleaning!");
}
}
function keepBoth(): void {
LogController.log(`Appid: ${conflict.appid}. Keeping both ${conflict.fileAName} and ${conflict.fileBName}.`);
conflict = getNextConflict();
if (!conflict) {
$showCleanConflictDialog = false;
$cleanConflicts = [];
ToastController.showSuccessToast("Finished cleaning!");
LogController.log("Finished cleaning!");
}
}
onMount(() => {
conflict = getNextConflict();
conflictGridType = conflict.gridType;
fileAPath = tauri.convertFileSrc(conflict.fileAPath);
fileBPath = tauri.convertFileSrc(conflict.fileBPath);
});
</script>

<!-- svelte-ignore a11y-click-events-have-key-events -->
<ModalBody title={`Clean Conflict Dialog #${conflictNumber}`} canClose={false}>
<div class="content">
<div class="description">
Choose which grid you would like to keep.
</div>
<div class="images {conflictGridType}">
<div class="split">
<div class="img-cont">
<div class="img" class:logo-background={conflictGridType == GridTypes.LOGO} class:icon-background={conflictGridType == GridTypes.ICON} style="max-height: {heights[conflictGridType]}px;">
<Lazy height="{heights[conflictGridType]}px" fadeOption={{delay: 500, duration: 1000}}>
<img src="{fileAPath}" alt="Option 1" style="max-width: {widths[conflictGridType]}px; max-height: {heights[conflictGridType]}px; width: auto; height: auto;" />
</Lazy>
</div>
</div>
<div class="filename">{conflict?.fileAName}</div>
</div>
<div class="split">
<div class="img-cont">
<div class="img" class:logo-background={conflictGridType == GridTypes.LOGO} class:icon-background={conflictGridType == GridTypes.ICON} style="max-height: {heights[conflictGridType]}px;">
<Lazy height="{heights[conflictGridType]}px" fadeOption={{delay: 500, duration: 1000}}>
<img src="{fileBPath}" alt="Option 2" style="max-width: {widths[conflictGridType]}px; max-height: {heights[conflictGridType]}px; width: auto; height: auto;" />
</Lazy>
</div>
</div>
<div class="filename">{conflict?.fileBName}</div>
</div>
</div>
<div class="buttons">
<Button label={`Keep ${conflictGridType == "hero" ? "Top" : "Left"}`} onClick={() => { deleteGrid(true); }} width="30%" />
<Button label={`Keep ${conflictGridType == "hero" ? "Bottom" : "Right"}`} onClick={() => { deleteGrid(false); }} width="30%" />
<Button label="Keep Both" onClick={keepBoth} width="30%" />
</div>
</div>
</ModalBody>

<style>
@import "/theme.css";
/* done */
.content {
width: 600px;
height: calc(100% - 60px);
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
}
/* done */
.description {
width: calc(100% - 14px);
font-size: 14px;
margin-top: 7px;
margin-bottom: 7px;
}
.images {
width: 100%;
display: flex;
flex-direction: row;
justify-content: space-around;
}
.images.hero {
flex-direction: column;
justify-content: flex-start;
}
.split {
display: flex;
flex-direction: column;
align-items: center;
}
.images.hero .split {
display: flex;
flex-direction: column;
align-items: center;
}
.capsule {
display: flex;
flex-direction: row;
height: calc(100% - 38px);
}
.icon {
display: flex;
flex-direction: row;
height: calc(100% - 38px);
max-width: 550px;
}
/* done */
.img-cont { padding: 10px; }
.img-cont > .img {
border-radius: 2px;
overflow: hidden;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
/* done */
.buttons {
margin-top: 14px;
margin-bottom: 7px;
width: 100%;
display: flex;
justify-content: space-around;
justify-self: flex-end;
}
</style>
Loading

0 comments on commit ce65fc3

Please sign in to comment.