diff --git a/src-tauri/src/gamebanana.rs b/src-tauri/src/gamebanana.rs index f3e224b..9bae0d6 100644 --- a/src-tauri/src/gamebanana.rs +++ b/src-tauri/src/gamebanana.rs @@ -8,6 +8,13 @@ use std::path::PathBuf; static API_URL: &str = "https://api.gamebanana.com"; static SITE_URL: &str = "https://gamebanana.com"; +#[tauri::command] +pub async fn get_download_links(modId: String) -> String { + let res = web::query(format!("{}/apiv9/Mod/{}/DownloadPage", SITE_URL, modId).as_str()).await; + + res +} + #[tauri::command] pub async fn list_submissions(mode: String) -> String { let res = web::query( diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index cf60233..c8db8e1 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -60,6 +60,7 @@ fn main() { lang::get_languages, web::valid_url, web::web_get, + gamebanana::get_download_links, gamebanana::list_submissions, gamebanana::list_mods, metadata_patcher::patch_metadata diff --git a/src-tauri/src/unzip.rs b/src-tauri/src/unzip.rs index 310b7c4..b46444a 100644 --- a/src-tauri/src/unzip.rs +++ b/src-tauri/src/unzip.rs @@ -3,7 +3,7 @@ use std::path; use std::thread; #[tauri::command] -pub fn unzip(window: tauri::Window, zipfile: String, destpath: String) { +pub fn unzip(window: tauri::Window, zipfile: String, destpath: String, top_level: Option) { // Read file TODO: replace test file let f = match File::open(&zipfile) { Ok(f) => f, @@ -21,7 +21,7 @@ pub fn unzip(window: tauri::Window, zipfile: String, destpath: String) { window.emit("extract_start", &zipfile).unwrap(); - match zip_extract::extract(&f, &full_path, true) { + match zip_extract::extract(&f, &full_path, top_level.unwrap_or(false)) { Ok(_) => { println!( "Extracted zip file to: {}", diff --git a/src/ui/Mods.css b/src/ui/Mods.css index e532d60..75c0de6 100644 --- a/src/ui/Mods.css +++ b/src/ui/Mods.css @@ -3,3 +3,17 @@ height: 90%; width: 100%; } + +/* Stuff for the top bar progress bar */ +.TopDownloads { + position: absolute; + top: 10px; + left: 35.5%; + + width: 30%; +} + +.TopDownloads .ProgressBar { + width: 100%; + height: 10px; +} diff --git a/src/ui/Mods.tsx b/src/ui/Mods.tsx index d8b49f6..d34d0b1 100644 --- a/src/ui/Mods.tsx +++ b/src/ui/Mods.tsx @@ -1,6 +1,9 @@ import React from 'react' import DownloadHandler from '../utils/download' -import { ModData } from '../utils/gamebanana' +import { getModDownload, ModData } from '../utils/gamebanana' +import { getModsFolder } from '../utils/mods' +import { unzip } from '../utils/zipUtils' +import ProgressBar from './components/common/MainProgressBar' import { ModHeader } from './components/mods/ModHeader' import { ModList } from './components/mods/ModList' import TopBar from './components/TopBar' @@ -40,7 +43,12 @@ export class Mods extends React.Component { category: '', } + setInterval(() => { + console.log(this.props.downloadHandler.downloads) + }, 5000) + this.setCategory = this.setCategory.bind(this) + this.addDownload = this.addDownload.bind(this) } async componentDidMount() { @@ -49,6 +57,22 @@ export class Mods extends React.Component { async addDownload(mod: ModData) { console.log('Downloading:', mod.name) + + const modFolder = await getModsFolder() + const modPath = `${modFolder}${mod.id}.zip` + const dlLinks = await getModDownload(String(mod.id)) + + if (!modFolder || dlLinks.length === 0) return + + // Not gonna bother allowing sorting for now + const firstLink = dlLinks[0].downloadUrl + + this.props.downloadHandler.addDownload(firstLink, modPath, async () => { + console.log('Unzipping:', mod.name) + unzip(modPath, modFolder, false, () => { + console.log('DONE MOD DOWNLOAD') + }) + }) } async setCategory(value: string) { @@ -65,6 +89,10 @@ export class Mods extends React.Component {
+
+ +
+ diff --git a/src/ui/components/TopBar.tsx b/src/ui/components/TopBar.tsx index b51927f..6b37601 100644 --- a/src/ui/components/TopBar.tsx +++ b/src/ui/components/TopBar.tsx @@ -1,13 +1,12 @@ import React from 'react' import { app } from '@tauri-apps/api' import { appWindow } from '@tauri-apps/api/window' -import closeIcon from '../../resources/icons/close.svg' -import minIcon from '../../resources/icons/min.svg' - +import { getConfig, setConfigOption } from '../../utils/configuration' import Tr from '../../utils/language' import './TopBar.css' -import { getConfig, setConfigOption } from '../../utils/configuration' +import closeIcon from '../../resources/icons/close.svg' +import minIcon from '../../resources/icons/min.svg' interface IProps { children?: React.ReactNode[] diff --git a/src/ui/components/common/MainProgressBar.tsx b/src/ui/components/common/MainProgressBar.tsx index ff6d023..89b5407 100644 --- a/src/ui/components/common/MainProgressBar.tsx +++ b/src/ui/components/common/MainProgressBar.tsx @@ -5,6 +5,7 @@ import './ProgressBar.css' interface IProps { downloadManager: DownloadHandler + withStats?: boolean } interface IState { @@ -70,11 +71,13 @@ export default class ProgressBar extends React.Component { >
-
- {this.state.files} ({this.state.speed}) -
- {this.state.extracting} -
+ {this.props.withStats && ( +
+ {this.state.files} ({this.state.speed}) +
+ {this.state.extracting} +
+ )} ) } diff --git a/src/ui/components/menu/Downloads.tsx b/src/ui/components/menu/Downloads.tsx index a97b769..be14405 100644 --- a/src/ui/components/menu/Downloads.tsx +++ b/src/ui/components/menu/Downloads.tsx @@ -112,7 +112,7 @@ export default class Downloads extends React.Component { async downloadGrasscutterStableRepo() { const folder = await this.getGrasscutterFolder() this.props.downloadManager.addDownload(STABLE_REPO_DOWNLOAD, folder + '\\grasscutter_repo.zip', () => { - unzip(folder + '\\grasscutter_repo.zip', folder + '\\', this.toggleButtons) + unzip(folder + '\\grasscutter_repo.zip', folder + '\\', true, this.toggleButtons) }) this.toggleButtons() @@ -121,7 +121,7 @@ export default class Downloads extends React.Component { async downloadGrasscutterDevRepo() { const folder = await this.getGrasscutterFolder() this.props.downloadManager.addDownload(DEV_REPO_DOWNLOAD, folder + '\\grasscutter_repo.zip', () => { - unzip(folder + '\\grasscutter_repo.zip', folder + '\\', this.toggleButtons) + unzip(folder + '\\grasscutter_repo.zip', folder + '\\', true, this.toggleButtons) }) this.toggleButtons() @@ -130,7 +130,7 @@ export default class Downloads extends React.Component { async downloadGrasscutterStable() { const folder = await this.getGrasscutterFolder() this.props.downloadManager.addDownload(STABLE_DOWNLOAD, folder + '\\grasscutter.zip', () => { - unzip(folder + '\\grasscutter.zip', folder + '\\', this.toggleButtons) + unzip(folder + '\\grasscutter.zip', folder + '\\', true, this.toggleButtons) }) // Also add repo download @@ -142,7 +142,7 @@ export default class Downloads extends React.Component { async downloadGrasscutterLatest() { const folder = await this.getGrasscutterFolder() this.props.downloadManager.addDownload(DEV_DOWNLOAD, folder + '\\grasscutter.zip', () => { - unzip(folder + '\\grasscutter.zip', folder + '\\', this.toggleButtons) + unzip(folder + '\\grasscutter.zip', folder + '\\', true, this.toggleButtons) }) // Also add repo download @@ -165,7 +165,7 @@ export default class Downloads extends React.Component { }) } - await unzip(folder + '\\resources.zip', folder + '\\', () => { + await unzip(folder + '\\resources.zip', folder + '\\', true, () => { // Rename folder to resources invoke('rename', { path: folder + '\\Resources', diff --git a/src/ui/components/menu/Game.tsx b/src/ui/components/menu/Game.tsx index 4a6e77f..4f1ed2f 100644 --- a/src/ui/components/menu/Game.tsx +++ b/src/ui/components/menu/Game.tsx @@ -46,7 +46,7 @@ export default class Downloads extends React.Component { async downloadGame() { const folder = this.state.gameDownloadFolder this.props.downloadManager.addDownload(GAME_DOWNLOAD, folder + '\\game.zip', () => { - unzip(folder + '\\game.zip', folder + '\\', () => { + unzip(folder + '\\game.zip', folder + '\\', true, () => { this.setState({ gameDownloading: false, }) diff --git a/src/ui/components/mods/ModList.tsx b/src/ui/components/mods/ModList.tsx index 7b404c5..09c6289 100644 --- a/src/ui/components/mods/ModList.tsx +++ b/src/ui/components/mods/ModList.tsx @@ -24,8 +24,6 @@ export class ModList extends React.Component { constructor(props: IProps) { super(props) - console.log('Getting') - this.state = { modList: null, installedList: null, diff --git a/src/utils/gamebanana.ts b/src/utils/gamebanana.ts index 4b15c9a..3e23ecd 100644 --- a/src/utils/gamebanana.ts +++ b/src/utils/gamebanana.ts @@ -2,7 +2,7 @@ import { invoke } from '@tauri-apps/api' import { getConfigOption } from './configuration' // Generated with https://transform.tools/json-to-typescript I'm lazy cry about it -export interface GamebananaResponse { +interface GamebananaResponse { _idRow: number _sModelName: string _sSingularTitle: string @@ -23,12 +23,12 @@ export interface GamebananaResponse { _tsDateUpdated?: number } -export interface PreviewMedia { +interface PreviewMedia { _aImages?: Image[] _aMetadata?: Metadata } -export interface Image { +interface Image { _sType: string _sBaseUrl: string _sFile: string @@ -38,14 +38,14 @@ export interface Image { _sCaption?: string } -export interface Metadata { +interface Metadata { _sState?: string _sSnippet: string _nPostCount?: number _nBounty?: number } -export interface Submitter { +interface Submitter { _idRow: number _sName: string _bIsOnline: boolean @@ -57,7 +57,7 @@ export interface Submitter { _sHdAvatarUrl?: string } -export interface RootCategory { +interface RootCategory { _sName: string _sProfileUrl: string _sIconUrl: string @@ -78,6 +78,35 @@ export interface ModData { type: string } +interface GamebananaDownloads { + _bIsTrashed: boolean + _bIsWithheld: boolean + _aFiles: File[] + _sLicense: string +} + +interface File { + _idRow: number + _sFile: string + _nFilesize: number + _sDescription: string + _tsDateAdded: number + _nDownloadCount: number + _sAnalysisState: string + _sDownloadUrl: string + _sMd5Checksum: string + _sClamAvResult: string + _sAnalysisResult: string + _bContainsExe: boolean +} + +interface ModDownload { + filename: string + downloadUrl: string + filesize: number + containsExe: boolean +} + export async function getMods(mode: string) { const resp = JSON.parse( await invoke('list_submissions', { @@ -134,3 +163,26 @@ export async function getInstalledMods() { } }) } + +export async function getModDownload(modId: string) { + const resp = JSON.parse( + await invoke('get_download_links', { + modId, + }) + ) as GamebananaDownloads + + return formatDownloadsData(resp) +} + +export async function formatDownloadsData(obj: GamebananaDownloads) { + if (!obj) return [] + + return obj._aFiles.map((itm) => { + return { + filename: itm._sFile, + downloadUrl: `https://files.gamebanana.com/mods/${itm._sFile}`, + filesize: itm._nFilesize, + containsExe: itm._bContainsExe, + } as ModDownload + }) +} diff --git a/src/utils/mods.ts b/src/utils/mods.ts index 336ce12..56caf00 100644 --- a/src/utils/mods.ts +++ b/src/utils/mods.ts @@ -1 +1,13 @@ -export {} +import { getConfigOption } from './configuration' + +export async function getModsFolder() { + const migotoPath = await getConfigOption('migoto_path') + + if (!migotoPath) return null + + // Remove exe from path + const pathArr = migotoPath.replace(/\\/g, '/').split('/') + pathArr.pop() + + return pathArr.join('/') + '/Mods/' +} diff --git a/src/utils/zipUtils.ts b/src/utils/zipUtils.ts index 44aa39c..fa09373 100644 --- a/src/utils/zipUtils.ts +++ b/src/utils/zipUtils.ts @@ -1,10 +1,11 @@ import { invoke } from '@tauri-apps/api' import { listen } from '@tauri-apps/api/event' -export function unzip(file: string, dest: string, onFinish?: () => void) { +export function unzip(file: string, dest: string, topLevelStrip?: boolean, onFinish?: () => void) { invoke('unzip', { zipfile: file, destpath: dest, + topLevelStrip, }) listen('extract_end', ({ payload }) => {