mirror of
https://github.com/Grasscutters/Cultivation.git
synced 2025-12-14 16:14:48 +01:00
downloading and extracting
This commit is contained in:
@@ -8,6 +8,13 @@ use std::path::PathBuf;
|
|||||||
static API_URL: &str = "https://api.gamebanana.com";
|
static API_URL: &str = "https://api.gamebanana.com";
|
||||||
static SITE_URL: &str = "https://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]
|
#[tauri::command]
|
||||||
pub async fn list_submissions(mode: String) -> String {
|
pub async fn list_submissions(mode: String) -> String {
|
||||||
let res = web::query(
|
let res = web::query(
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ fn main() {
|
|||||||
lang::get_languages,
|
lang::get_languages,
|
||||||
web::valid_url,
|
web::valid_url,
|
||||||
web::web_get,
|
web::web_get,
|
||||||
|
gamebanana::get_download_links,
|
||||||
gamebanana::list_submissions,
|
gamebanana::list_submissions,
|
||||||
gamebanana::list_mods,
|
gamebanana::list_mods,
|
||||||
metadata_patcher::patch_metadata
|
metadata_patcher::patch_metadata
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::path;
|
|||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
#[tauri::command]
|
#[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<bool>) {
|
||||||
// Read file TODO: replace test file
|
// Read file TODO: replace test file
|
||||||
let f = match File::open(&zipfile) {
|
let f = match File::open(&zipfile) {
|
||||||
Ok(f) => f,
|
Ok(f) => f,
|
||||||
@@ -21,7 +21,7 @@ pub fn unzip(window: tauri::Window, zipfile: String, destpath: String) {
|
|||||||
|
|
||||||
window.emit("extract_start", &zipfile).unwrap();
|
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(_) => {
|
Ok(_) => {
|
||||||
println!(
|
println!(
|
||||||
"Extracted zip file to: {}",
|
"Extracted zip file to: {}",
|
||||||
|
|||||||
@@ -3,3 +3,17 @@
|
|||||||
height: 90%;
|
height: 90%;
|
||||||
width: 100%;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import DownloadHandler from '../utils/download'
|
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 { ModHeader } from './components/mods/ModHeader'
|
||||||
import { ModList } from './components/mods/ModList'
|
import { ModList } from './components/mods/ModList'
|
||||||
import TopBar from './components/TopBar'
|
import TopBar from './components/TopBar'
|
||||||
@@ -40,7 +43,12 @@ export class Mods extends React.Component<IProps, IState> {
|
|||||||
category: '',
|
category: '',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
console.log(this.props.downloadHandler.downloads)
|
||||||
|
}, 5000)
|
||||||
|
|
||||||
this.setCategory = this.setCategory.bind(this)
|
this.setCategory = this.setCategory.bind(this)
|
||||||
|
this.addDownload = this.addDownload.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
@@ -49,6 +57,22 @@ export class Mods extends React.Component<IProps, IState> {
|
|||||||
|
|
||||||
async addDownload(mod: ModData) {
|
async addDownload(mod: ModData) {
|
||||||
console.log('Downloading:', mod.name)
|
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) {
|
async setCategory(value: string) {
|
||||||
@@ -65,6 +89,10 @@ export class Mods extends React.Component<IProps, IState> {
|
|||||||
<div className="Mods">
|
<div className="Mods">
|
||||||
<TopBar />
|
<TopBar />
|
||||||
|
|
||||||
|
<div className="TopDownloads">
|
||||||
|
<ProgressBar downloadManager={this.props.downloadHandler} />
|
||||||
|
</div>
|
||||||
|
|
||||||
<ModHeader onChange={this.setCategory} headers={headers} defaultHeader={'ripe'} />
|
<ModHeader onChange={this.setCategory} headers={headers} defaultHeader={'ripe'} />
|
||||||
|
|
||||||
<ModList key={this.state.category} mode={this.state.category} addDownload={this.addDownload} />
|
<ModList key={this.state.category} mode={this.state.category} addDownload={this.addDownload} />
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { app } from '@tauri-apps/api'
|
import { app } from '@tauri-apps/api'
|
||||||
import { appWindow } from '@tauri-apps/api/window'
|
import { appWindow } from '@tauri-apps/api/window'
|
||||||
import closeIcon from '../../resources/icons/close.svg'
|
import { getConfig, setConfigOption } from '../../utils/configuration'
|
||||||
import minIcon from '../../resources/icons/min.svg'
|
|
||||||
|
|
||||||
import Tr from '../../utils/language'
|
import Tr from '../../utils/language'
|
||||||
|
|
||||||
import './TopBar.css'
|
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 {
|
interface IProps {
|
||||||
children?: React.ReactNode[]
|
children?: React.ReactNode[]
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import './ProgressBar.css'
|
|||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
downloadManager: DownloadHandler
|
downloadManager: DownloadHandler
|
||||||
|
withStats?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
@@ -70,11 +71,13 @@ export default class ProgressBar extends React.Component<IProps, IState> {
|
|||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="MainProgressText">
|
{this.props.withStats && (
|
||||||
<Tr text="main.files_downloading" /> {this.state.files} ({this.state.speed})
|
<div className="MainProgressText">
|
||||||
<br />
|
<Tr text="main.files_downloading" /> {this.state.files} ({this.state.speed})
|
||||||
<Tr text="main.files_extracting" /> {this.state.extracting}
|
<br />
|
||||||
</div>
|
<Tr text="main.files_extracting" /> {this.state.extracting}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ export default class Downloads extends React.Component<IProps, IState> {
|
|||||||
async downloadGrasscutterStableRepo() {
|
async downloadGrasscutterStableRepo() {
|
||||||
const folder = await this.getGrasscutterFolder()
|
const folder = await this.getGrasscutterFolder()
|
||||||
this.props.downloadManager.addDownload(STABLE_REPO_DOWNLOAD, folder + '\\grasscutter_repo.zip', () => {
|
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()
|
this.toggleButtons()
|
||||||
@@ -121,7 +121,7 @@ export default class Downloads extends React.Component<IProps, IState> {
|
|||||||
async downloadGrasscutterDevRepo() {
|
async downloadGrasscutterDevRepo() {
|
||||||
const folder = await this.getGrasscutterFolder()
|
const folder = await this.getGrasscutterFolder()
|
||||||
this.props.downloadManager.addDownload(DEV_REPO_DOWNLOAD, folder + '\\grasscutter_repo.zip', () => {
|
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()
|
this.toggleButtons()
|
||||||
@@ -130,7 +130,7 @@ export default class Downloads extends React.Component<IProps, IState> {
|
|||||||
async downloadGrasscutterStable() {
|
async downloadGrasscutterStable() {
|
||||||
const folder = await this.getGrasscutterFolder()
|
const folder = await this.getGrasscutterFolder()
|
||||||
this.props.downloadManager.addDownload(STABLE_DOWNLOAD, folder + '\\grasscutter.zip', () => {
|
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
|
// Also add repo download
|
||||||
@@ -142,7 +142,7 @@ export default class Downloads extends React.Component<IProps, IState> {
|
|||||||
async downloadGrasscutterLatest() {
|
async downloadGrasscutterLatest() {
|
||||||
const folder = await this.getGrasscutterFolder()
|
const folder = await this.getGrasscutterFolder()
|
||||||
this.props.downloadManager.addDownload(DEV_DOWNLOAD, folder + '\\grasscutter.zip', () => {
|
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
|
// Also add repo download
|
||||||
@@ -165,7 +165,7 @@ export default class Downloads extends React.Component<IProps, IState> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
await unzip(folder + '\\resources.zip', folder + '\\', () => {
|
await unzip(folder + '\\resources.zip', folder + '\\', true, () => {
|
||||||
// Rename folder to resources
|
// Rename folder to resources
|
||||||
invoke('rename', {
|
invoke('rename', {
|
||||||
path: folder + '\\Resources',
|
path: folder + '\\Resources',
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export default class Downloads extends React.Component<IProps, IState> {
|
|||||||
async downloadGame() {
|
async downloadGame() {
|
||||||
const folder = this.state.gameDownloadFolder
|
const folder = this.state.gameDownloadFolder
|
||||||
this.props.downloadManager.addDownload(GAME_DOWNLOAD, folder + '\\game.zip', () => {
|
this.props.downloadManager.addDownload(GAME_DOWNLOAD, folder + '\\game.zip', () => {
|
||||||
unzip(folder + '\\game.zip', folder + '\\', () => {
|
unzip(folder + '\\game.zip', folder + '\\', true, () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
gameDownloading: false,
|
gameDownloading: false,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -24,8 +24,6 @@ export class ModList extends React.Component<IProps, IState> {
|
|||||||
constructor(props: IProps) {
|
constructor(props: IProps) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
console.log('Getting')
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
modList: null,
|
modList: null,
|
||||||
installedList: null,
|
installedList: null,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { invoke } from '@tauri-apps/api'
|
|||||||
import { getConfigOption } from './configuration'
|
import { getConfigOption } from './configuration'
|
||||||
|
|
||||||
// Generated with https://transform.tools/json-to-typescript I'm lazy cry about it
|
// Generated with https://transform.tools/json-to-typescript I'm lazy cry about it
|
||||||
export interface GamebananaResponse {
|
interface GamebananaResponse {
|
||||||
_idRow: number
|
_idRow: number
|
||||||
_sModelName: string
|
_sModelName: string
|
||||||
_sSingularTitle: string
|
_sSingularTitle: string
|
||||||
@@ -23,12 +23,12 @@ export interface GamebananaResponse {
|
|||||||
_tsDateUpdated?: number
|
_tsDateUpdated?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PreviewMedia {
|
interface PreviewMedia {
|
||||||
_aImages?: Image[]
|
_aImages?: Image[]
|
||||||
_aMetadata?: Metadata
|
_aMetadata?: Metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Image {
|
interface Image {
|
||||||
_sType: string
|
_sType: string
|
||||||
_sBaseUrl: string
|
_sBaseUrl: string
|
||||||
_sFile: string
|
_sFile: string
|
||||||
@@ -38,14 +38,14 @@ export interface Image {
|
|||||||
_sCaption?: string
|
_sCaption?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Metadata {
|
interface Metadata {
|
||||||
_sState?: string
|
_sState?: string
|
||||||
_sSnippet: string
|
_sSnippet: string
|
||||||
_nPostCount?: number
|
_nPostCount?: number
|
||||||
_nBounty?: number
|
_nBounty?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Submitter {
|
interface Submitter {
|
||||||
_idRow: number
|
_idRow: number
|
||||||
_sName: string
|
_sName: string
|
||||||
_bIsOnline: boolean
|
_bIsOnline: boolean
|
||||||
@@ -57,7 +57,7 @@ export interface Submitter {
|
|||||||
_sHdAvatarUrl?: string
|
_sHdAvatarUrl?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RootCategory {
|
interface RootCategory {
|
||||||
_sName: string
|
_sName: string
|
||||||
_sProfileUrl: string
|
_sProfileUrl: string
|
||||||
_sIconUrl: string
|
_sIconUrl: string
|
||||||
@@ -78,6 +78,35 @@ export interface ModData {
|
|||||||
type: string
|
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) {
|
export async function getMods(mode: string) {
|
||||||
const resp = JSON.parse(
|
const resp = JSON.parse(
|
||||||
await invoke('list_submissions', {
|
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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -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/'
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { invoke } from '@tauri-apps/api'
|
import { invoke } from '@tauri-apps/api'
|
||||||
import { listen } from '@tauri-apps/api/event'
|
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', {
|
invoke('unzip', {
|
||||||
zipfile: file,
|
zipfile: file,
|
||||||
destpath: dest,
|
destpath: dest,
|
||||||
|
topLevelStrip,
|
||||||
})
|
})
|
||||||
|
|
||||||
listen('extract_end', ({ payload }) => {
|
listen('extract_end', ({ payload }) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user