Rust入门实战 编写Minecraft启动器#4下载资源

首发于Enaium的个人博客


首先我们需要添加几个依赖。

model = { path = "../model" }
parse = { path = "../parse" }
reqwest = { version = "0.12", features = ["blocking", "json"] }
file-hashing = { version = "0.1" }
sha1 = { version = "0.10" }

reqwest用于发送请求,file-hashing用于计算文件的hashsha1用于计算sha1

之后我们需要添加下载的trait

pub trait Download {fn download(&self, game_dir: &Path) -> Result<(), Box<dyn std::error::Error>>;
}

接着我们需要使用Client::builder()来创建一个Client,因为默认的get方法会用有个超时时间,而我们需要设置超时时间为无限。

pub fn get<T: reqwest::IntoUrl>(url: T) -> reqwest::Result<reqwest::blocking::Response> {reqwest::blocking::Client::builder().timeout(None).build()?.get(url).send()
}

最后我们需要创建一个计算文件hash的函数。

pub fn sha1<P: AsRef<Path>>(path: P) -> Result<String, std::io::Error> {let mut hasher = Sha1::new();file_hashing::get_hash_file(path, &mut hasher)
}

之后需要出创建asset.rslibrary.rsversion.rs文件,分别对应下载资源、下载库、下载游戏版本。

asset.rs

use std::{fs, path::Path};use model::asset::*;
use parse::Parse;use crate::{get, Download};impl Download for AssetIndex {fn download(&self, game_dir: &Path) -> Result<(), Box<dyn std::error::Error>> {println!("Downloading asset index:{}", self.id);let indexes_dir = &game_dir.join("assets").join("indexes");if !indexes_dir.exists() {std::fs::create_dir_all(indexes_dir)?;}let path = &indexes_dir.join(&format!("{}.json", self.id));std::fs::File::create(path)?;let url = &self.url;let text = &get(url)?.text()?;std::fs::write(path, text)?;let index = Index::parse(text)?;let objects_dir = &game_dir.join("assets").join("objects");if !objects_dir.exists() {std::fs::create_dir_all(objects_dir)?;}for (_, value) in index.objects {let hash = &value.hash;let hash_first_two = &hash.chars().take(2).collect::<String>();let first_two_dir = &objects_dir.join(hash_first_two);if !first_two_dir.exists() {std::fs::create_dir_all(first_two_dir)?;}let path = &first_two_dir.join(hash);if path.exists() {if crate::sha1(path)?.eq(hash) {continue;} else {std::fs::remove_file(path)?;}}std::fs::File::create(path)?;let url = format!("https://resources.download.minecraft.net/{}/{}",hash_first_two, hash);println!("Downloading:{}", url);let bytes = get(&url)?.bytes()?;fs::write(path, bytes)?;}Ok(())}
}#[cfg(test)]
mod tests {use super::*;#[test]fn test_asset_index() {let asset_index = model::asset::AssetIndex {id: "17".to_string(),sha1: "fab15439bdef669e389e25e815eee8f1b2aa915e".to_string(),size: 447033,total_size: 799252591,url: "https://piston-meta.mojang.com/v1/packages/fab15439bdef669e389e25e815eee8f1b2aa915e/17.json".to_string(),};let download_path = &std::env::temp_dir().join("rust-minecraft-client-launch");std::fs::create_dir_all(download_path).unwrap_or_else(|err| panic!("{:?}", err));if let Err(err) = asset_index.download(download_path) {panic!("{:?}", err);}}
}

library.rs

use std::path::Path;use model::{library, version::Libraries};use crate::{Download, LibraryAllowed};impl LibraryAllowed for library::Library {fn allowed(&self) -> bool {let mut allowed = true;if self.rules.is_some() {for rule in self.rules.as_ref().unwrap() {if rule.os.name == "osx" && !cfg!(target_os = "macos") {allowed = false;break;} else if rule.os.name == "linux" && !cfg!(target_os = "linux") {allowed = false;break;} else if rule.os.name == "windows" && !cfg!(target_os = "windows") {allowed = false;break;}}}if self.name.contains("natives") {if self.name.contains("x86") && !cfg!(target_arch = "x86") {allowed = false;} else if self.name.contains("arm64") && !cfg!(target_arch = "aarch64") {allowed = false;} else if !cfg!(target_arch = "x86_64") {allowed = false;}}allowed}
}impl Download for Libraries {fn download(&self, game_dir: &Path) -> Result<(), Box<dyn std::error::Error>> {println!("Downloading libraries");let libraries_dir = &game_dir.join("libraries");if !libraries_dir.exists() {std::fs::create_dir_all(libraries_dir)?;}for library in self {if !library.allowed() {continue;}let library_file = &library.downloads.artifact.path;let library_path = &libraries_dir.join(library_file);if !library_path.parent().unwrap().exists() {std::fs::create_dir_all(library_path.parent().unwrap())?;}if library_path.exists() {if crate::sha1(library_path)? == library.downloads.artifact.sha1 {continue;} else {std::fs::remove_file(library_path)?;}}std::fs::File::create(&library_path)?;let url = &library.downloads.artifact.url;println!("Downloading: {}", url);let bytes = crate::get(url)?.bytes()?;std::fs::write(library_path, bytes)?;}Ok(())}
}#[cfg(test)]
mod tests {use super::*;use model::version::Version;#[test]fn test_download() {let game = reqwest::blocking::get("https://piston-meta.mojang.com/v1/packages/177e49d3233cb6eac42f0495c0a48e719870c2ae/1.21.json").unwrap().json::<Version>().unwrap();let download_path = &std::env::temp_dir().join("rust-minecraft-client-launch");std::fs::create_dir_all(download_path).unwrap_or_else(|err| panic!("{:?}", err));if let Err(err) = game.libraries.download(download_path) {panic!("{:?}", err);}}
}

这里我们需要添加一个trait,用于判断库是否允许下载。

pub trait LibraryAllowed {fn allowed(&self) -> bool;
}

version.rs

use std::path::Path;use model::version_manifest::Version;use crate::{get, sha1, Download};impl Download for Version {fn download(&self, game_dir: &Path) -> Result<(), Box<dyn std::error::Error>> {let game = get(&self.url)?.json::<model::version::Version>()?;let versions_dir = &game_dir.join(game_dir).join("versions").join(&self.id);if !versions_dir.exists() {std::fs::create_dir_all(versions_dir)?;}game.libraries.download(game_dir)?;game.asset_index.download(game_dir)?;let version_config = &game_dir.join("versions").join(&self.id).join(&format!("{}.json", &self.id));if version_config.exists() {std::fs::remove_file(version_config).unwrap();}std::fs::File::create(version_config).unwrap();std::fs::write(version_config, get(&self.url).unwrap().bytes().unwrap()).unwrap();let path = &versions_dir.join(versions_dir).join(&format!("{}.jar", &self.id));if path.exists() {if sha1(path)? == game.downloads.client.sha1 {return Ok(());} else {std::fs::remove_file(path)?;}}std::fs::File::create(path)?;let bytes = crate::get(&game.downloads.client.url)?.bytes()?;std::fs::write(path, bytes)?;Ok(())}
}#[cfg(test)]
mod tests {use super::*;#[test]fn test_download() {let version = Version {id: "1.21".to_string(),type_: "release".to_string(),url: "https://piston-meta.mojang.com/v1/packages/177e49d3233cb6eac42f0495c0a48e719870c2ae/1.21.json".to_string(),time : "2024-06-13T08:32:38+00:00".to_string(),release_time : "2024-06-13T08:24:03+00:00".to_string(),};let download_path = &std::env::temp_dir().join("rust-minecraft-client-launch");std::fs::create_dir_all(download_path).unwrap_or_else(|err| panic!("{:?}", err));if let Err(err) = version.download(download_path) {panic!("{:?}", err);}}
}

好了,现在我们可以测试下载资源了。

项目地址

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/42860.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Xshell 和宝塔有啥区别

Xshell 和宝塔是两种不同类型的工具&#xff0c;具有以下显著区别&#xff1a; 1. 功能和用途 Xshell&#xff1a;主要是一款用于远程连接服务器的终端模拟软件。它允许用户通过 SSH 协议安全地连接到远程服务器&#xff0c;并在终端中执行命令&#xff0c;进行服务器的管理和…

AI论文作图——如何表示模型参数冻结状态

一、LOGO &#x1f525; win10win11 ❄️ win10win11 二、注意事项&#xff1a; 根据电脑系统&#xff0c;选择对应的版本。 参考&#xff1a; 【AI论文作图】如何表示模型参数冻结状态&#xff1f;

对称加密和非对称加密解析

目录 一、对称加密二、非对称加密三、总结 对称加密和非对称加密是两种主要的加密技术&#xff0c;它们在数据安全领域扮演着重要角色。 一、对称加密 基本原理&#xff1a;对称加密使用同一个密钥进行加密和解密。这意味着如果A用某个密钥加密了信息发送给B&#xff0c;那么B…

Redis数据库笔记

一、 认识NoSQL SQLNoSQL数据结构结构化非结构化(键值类型(Redis)文档类型(MongoDB)列类型(HBase)Graph类型(Neo4j))数据关联关联的无关联查询方式SQL查询非SQL事务特性ACIDBASE存储方式磁盘内存扩展性垂直水平使用场景数据结构固定;相关业务对数据安全性、一致性要…

【C++中resize和reserve的区别】

1. resize的用法 改变当前容器内含有元素的数量&#xff08;size()&#xff09;比如&#xff1a; vector<int> vct;int num vct.size();//之前的元素个数为num vct.resize(len);//现在的元素个数为len如果num < len &#xff0c;那么容器vct新增len - num个元素&am…

8-选择静态或共享库

在本节中&#xff0c;我们将展示如何使用BUILD_SHARED_LIBS变量来控制add_library()的默认行为&#xff0c;并允许控制如何构建没有显式类型的库(STATIC、SHARED、MODULE或OBJECT)。 要做到这一点&#xff0c;我们需要将BUILD_SHARED_LIBS添加到顶级的CMakeLists.txt中。我…

神经网络中的激活函数

目录 一、什么是激活函数&#xff1a;二、如何选择激活函数&#xff1a;1.Sigmoid激活函数&#xff1a;2.线性激活函数&#xff1a;3.ReLU激活函数&#xff1a; 一、什么是激活函数&#xff1a; 激活函数是神经网络中的一种函数&#xff0c;它在神经元中起到了非线性映射的作用…

最新 Kubernetes 集群部署 + flannel 网络插件(保姆级教程,最新 K8S 版本)

资源列表 操作系统配置主机名IP所需插件CentOS 7.92C4Gk8s-master192.168.60.143flannel-cni-plugin、flannel、coredns、etcd、kube-apiserver、kube-controller-manager、kube-proxy、 kube-scheduler 、containerd、pause 、crictlCentOS 7.92C4Gk8s-node01192.168.60.144f…

gitee上传和下载idea项目的流程

环境&#xff1a;idea2022 一、上传项目 1、在gitee中新建一个仓库。 2、打开所要上传的项目的文件夹&#xff0c;点击Git Bash&#xff0c;生成.git文件夹。 3、在idea中打开所要上传的项目&#xff0c;在控制台的Terminal菜单中&#xff0c;输入git add . (注意&#xf…

安防综合管理/视频汇聚平台EasyCVR视频监控存储技术:高效稳定的视频数据保障方案

随着科技的飞速发展&#xff0c;视频监控已成为现代社会不可或缺的一部分。无论是城市治安、交通管理&#xff0c;还是商业安保、家庭监控&#xff0c;视频监控都发挥着至关重要的作用。而在这背后&#xff0c;视频监控存储技术则是确保监控数据得以长期保存、高效检索和可靠利…

「C++系列」C++ 修饰符类型

文章目录 一、C 修饰符类型1. 访问修饰符&#xff08;Access Modifiers&#xff09;2. 存储类修饰符&#xff08;Storage Class Specifiers&#xff09;3. 类型修饰符&#xff08;Type Modifiers&#xff09;4. 函数修饰符 二、C 修饰符类型-案例1. 访问修饰符案例2. 存储类修饰…

精讲:java之多维数组的使用

一、多维数组简介 1.为什么需要二维数组 我们看下面这个例子&#xff1f;“ 某公司2022年全年各个月份的销售额进行登记。按月份存储&#xff0c;可以使用一维数组。如果改写为按季度为单位存储怎么办呢&#xff1f; 或许现在学习了一维数组的你只能申请四个一维数组去存储每…

【福利】代码公开!咸鱼之王自动答题脚本

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 微信或QQ打开咸鱼之王小程序&#xff0c;进入答题界面&#xff0c;运行main.py。期间不要动鼠标。 可自行更改代码来适配自己的需求~ 可以按照示例图片…

Kubernetes(k8s)和Docker Compose本质区别

Kubernetes&#xff08;k8s&#xff09;和Docker Compose是两种不同的容器编排工具&#xff0c;它们有各自的特点和使用场景。 Kubernetes&#xff1a; Kubernetes是一个开源的容器编排平台&#xff0c;用于自动化计算机软件的部署、扩展和管理。它支持跨多个主机集群的容器化…

HarmonyOS Next 原生应用开发-从TS到ArkTS的适配规则(四)

一、不支持以#开头的私有字段 规则&#xff1a;arkts-no-private-identifiers 级别&#xff1a;错误 ArkTS不支持使用#符号开头声明的私有字段。改用private关键字。 TypeScript class C {#foo: number 42 }ArkTS class C {private foo: number 42 }二、类型、命名空间的命…

深入了解线程锁的使用及锁的本质

文章目录 线程锁的本质局部锁的使用 锁的封装及演示线程饥饿问题 线程加锁本质可重入和线程安全死锁问题 根据前面内容的概述, 上述我们已经知道了在linux下关于线程封装和线程互斥,锁的相关的概念, 下面就来介绍一下关于线程锁的一些其他概念. 线程锁的本质 当这个锁是全局的…

Codeforces Round #956 (Div. 2) and ByteRace 2024

A. Array Divisibility 思路: 找出特例,发现输出 1∼&#x1d45b; 符合题意。直接输出1~n即可. 代码: #include<bits/stdc.h> using namespace std; typedef long long ll; #define N 1000005 ll dp[N], w[N], v[N], h[N]; ll dis[1005][1005]; ll a, b, c, n, m, t;…

iOS 开发技巧 - 使用本地 json 文件

前言 使用本地 json 文件的场景&#xff0c;在我们开发功能的阶段&#xff0c;服务端接口字段定义好了后&#xff0c;有些接口响应很慢&#xff0c;请求到响应可能要 几十秒甚至一分钟&#xff0c;我们需要频繁调用接口来调试功能&#xff1b;还有就是调用一些我们需要付费的三…

Ubuntu20.04下修改samba用户密码

Ubuntu20.04下修改samba用户密码 在Ubuntu系统中&#xff0c;修改samba密码通常涉及到两个方面&#xff1a;更改samba用户的密码和重置samba服务的密码数据库。以下是如何进行操作的步骤&#xff1a; 1、更改samba用户密码&#xff1a; 打开终端&#xff0c;使用以下命令更改…

vue打包terser压缩去除控制台打印和断点

情况一&#xff1a; 1、vue-cli搭建 代码压缩工具terser在vue-cli里面是自动支持的&#xff0c;所以直接在vue.config.js里面加入下面配置&#xff1a; const {defineConfig} require(vue/cli-service) module.exportsdefineConfig({transpileDependencies:true,terser:{te…