1+ use anyhow:: Result ;
2+ use reqwest:: Client ;
3+ use std:: env;
4+ use std:: fs;
5+ use std:: io:: Write ;
6+ use std:: process:: Command ;
7+ use async_trait:: async_trait;
8+ use crate :: commands:: { Command as CCommand , CommandContext , CommandResult } ;
9+
10+ #[ allow( dead_code) ]
11+ pub struct UpdateCommand {
12+ context : CommandContext ,
13+ }
14+
15+ impl UpdateCommand {
16+ pub fn new ( context : CommandContext ) -> Self {
17+ Self { context }
18+ }
19+
20+ async fn get_latest_release ( & self ) -> Result < ( String , String ) > {
21+ let api = "https://api.github.com/repos/FishPiOffical/fishpi-rust/releases/latest" ;
22+ let client = Client :: new ( ) ;
23+ let resp = client
24+ . get ( api)
25+ . header ( "User-Agent" , "fishpi-rust-chatroom" )
26+ . send ( )
27+ . await ?
28+ . json :: < serde_json:: Value > ( )
29+ . await ?;
30+
31+ let tag = resp[ "tag_name" ] . as_str ( ) . unwrap_or ( "" ) . to_string ( ) ;
32+ let asset_url = resp[ "assets" ] [ 0 ] [ "browser_download_url" ]
33+ . as_str ( )
34+ . unwrap_or ( "" )
35+ . to_string ( ) ;
36+ Ok ( ( tag, asset_url) )
37+ }
38+
39+ async fn download_and_replace ( & self , url : & str ) -> Result < ( ) > {
40+ let exe_path = env:: current_exe ( ) ?;
41+ let tmp_path = exe_path. with_extension ( "new" ) ;
42+
43+ let resp = reqwest:: get ( url) . await ?;
44+ let bytes = resp. bytes ( ) . await ?;
45+ let mut file = fs:: File :: create ( & tmp_path) ?;
46+ file. write_all ( & bytes) ?;
47+
48+ self . launch_update_script ( exe_path. to_str ( ) . unwrap ( ) , tmp_path. to_str ( ) . unwrap ( ) ) ?;
49+ println ! ( "已启动自动更新,程序即将退出并自动替换为新版本。" ) ;
50+ std:: process:: exit ( 0 ) ;
51+ }
52+
53+ fn launch_update_script ( & self , exe_path : & str , new_exe_path : & str ) -> std:: io:: Result < ( ) > {
54+ #[ cfg( target_os = "windows" ) ]
55+ {
56+ let script = format ! (
57+ r#"
58+ @echo off
59+ ping 127.0.0.1 -n 2 > nul
60+ move /Y "{new}" "{old}"
61+ start "" "{old}"
62+ "# ,
63+ new = new_exe_path,
64+ old = exe_path
65+ ) ;
66+ let script_path = format ! ( "{}.update.bat" , exe_path) ;
67+ let mut file = fs:: File :: create ( & script_path) ?;
68+ file. write_all ( script. as_bytes ( ) ) ?;
69+ Command :: new ( "cmd" )
70+ . args ( & [ "/C" , & script_path] )
71+ . spawn ( ) ?;
72+ }
73+ #[ cfg( any( target_os = "linux" , target_os = "macos" ) ) ]
74+ {
75+ let script = format ! (
76+ r#"
77+ #!/bin/sh
78+ sleep 1
79+ mv "{new}" "{old}"
80+ chmod +x "{old}"
81+ "{old}" &
82+ "# ,
83+ new = new_exe_path,
84+ old = exe_path
85+ ) ;
86+ let script_path = format ! ( "{}.update.sh" , exe_path) ;
87+ let mut file = fs:: File :: create ( & script_path) ?;
88+ file. write_all ( script. as_bytes ( ) ) ?;
89+ Command :: new ( "sh" )
90+ . arg ( & script_path)
91+ . spawn ( ) ?;
92+ }
93+ Ok ( ( ) )
94+ }
95+ }
96+
97+ #[ async_trait]
98+ impl CCommand for UpdateCommand {
99+ async fn execute ( & mut self , _args : & [ & str ] ) -> Result < CommandResult > {
100+ println ! ( "正在检查新版本..." ) ;
101+ let ( latest_tag, asset_url) = self . get_latest_release ( ) . await ?;
102+ let latest_tag_clean = latest_tag. trim ( ) . trim_start_matches ( 'v' ) ;
103+ let current_version = env ! ( "GIT_TAG" ) . trim ( ) . trim_start_matches ( 'v' ) ;
104+ if latest_tag_clean == current_version {
105+ println ! ( "已是最新版本。" ) ;
106+ return Ok ( CommandResult :: Success ) ;
107+ }
108+ println ! (
109+ "检测到新版本: v{},当前版本: v{}" ,
110+ latest_tag_clean, current_version
111+ ) ;
112+ self . download_and_replace ( & asset_url) . await ?;
113+ Ok ( CommandResult :: Success )
114+ }
115+
116+ fn help ( & self ) -> & ' static str {
117+ "检查并自动更新到最新版本"
118+ }
119+ }
0 commit comments