Skip to content

Commit 9370dd7

Browse files
authored
feat: support self_update feature (#20)
1 parent f8421a3 commit 9370dd7

File tree

10 files changed

+163
-6
lines changed

10 files changed

+163
-6
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "roxide"
3-
version = "0.2.0"
3+
version = "0.3.0"
44
edition = "2021"
55
categories = ["command-line-utilities"]
66
readme = "README.md"

install.sh

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,19 @@ ensure_command() {
118118
ensure_command "perl"
119119
ensure_command "tar"
120120

121-
BIN_DIR="/usr/local/bin"
121+
if [[ $# -ge 1 ]]; then
122+
BIN_DIR="$1"
123+
else
124+
BIN_DIR="/usr/local/bin"
125+
fi
122126
TMP_DIR="/tmp/roxide-install"
123127
BASE_URL="https://github.com/fioncat/roxide/releases"
124128

129+
SKIP_CONFIRM="false"
130+
if [[ $# -ge 2 ]]; then
131+
SKIP_CONFIRM="$2"
132+
fi
133+
125134
PLATFORM="$(detect_os)"
126135
ARCH="$(detect_arch)"
127136

@@ -140,7 +149,12 @@ if [ -z ${SUPPORT} ]; then
140149
exit 1
141150
fi
142151

143-
confirm "Install roxide to ${BIN_DIR}?"
152+
if [[ ! "${SKIP_CONFIRM}" == "true" ]]; then
153+
confirm "Install roxide to ${BIN_DIR}?"
154+
else
155+
info "About to install roxide to ${BIN_DIR}"
156+
fi
157+
144158

145159
if [ -d ${TMP_DIR} ]; then
146160
rm -r ${TMP_DIR}
@@ -193,6 +207,10 @@ if ! grep -q "$INIT_ROXIDE_SEARCH" "$PROFILE_PATH"; then
193207
fi
194208

195209

210+
if [[ "${SKIP_CONFIRM}" == "true" ]]; then
211+
exit 0
212+
fi
213+
196214
cat << EOF
197215
198216
Congratulations! roxide has been already installed (or updated) to ${CYAN}${BIN_DIR}${RESET}.

src/api/github.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::time::Duration;
2+
13
use anyhow::{bail, Context, Result};
24
use reqwest::blocking::{Client, Request};
35
use reqwest::{Method, Url};
@@ -34,6 +36,11 @@ struct Error {
3436
pub message: String,
3537
}
3638

39+
#[derive(Debug, Deserialize)]
40+
struct Release {
41+
pub tag_name: String,
42+
}
43+
3744
impl Repo {
3845
fn to_api(self) -> ApiRepo {
3946
let Repo {
@@ -177,6 +184,18 @@ impl Github {
177184
})
178185
}
179186

187+
pub fn new_empty() -> Github {
188+
let client = Client::builder()
189+
.timeout(Duration::from_secs_f32(20.0))
190+
.build()
191+
.unwrap();
192+
Github {
193+
token: None,
194+
per_page: 30,
195+
client,
196+
}
197+
}
198+
180199
fn execute_get<T>(&self, path: &str) -> Result<T>
181200
where
182201
T: DeserializeOwned + ?Sized,
@@ -233,4 +252,10 @@ impl Github {
233252
}
234253
builder.build().context("Build Github request")
235254
}
255+
256+
pub fn get_latest_tag(&self, owner: &str, name: &str) -> Result<String> {
257+
let path = format!("repos/{owner}/{name}/releases/latest");
258+
let release = self.execute_get::<Release>(&path)?;
259+
Ok(release.tag_name)
260+
}
236261
}

src/api/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
mod cache;
2-
mod github;
2+
pub mod github;
33
mod gitlab;
44
pub mod types;
55

src/cmd/app.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use anyhow::Result;
1+
use anyhow::{Context, Result};
22
use clap::{Parser, Subcommand};
33

44
use crate::cmd::run::attach::AttachArgs;
@@ -17,7 +17,9 @@ use crate::cmd::run::remove::RemoveArgs;
1717
use crate::cmd::run::reset::ResetArgs;
1818
use crate::cmd::run::squash::SquashArgs;
1919
use crate::cmd::run::tag::TagArgs;
20+
use crate::cmd::run::update::UpdateArgs;
2021
use crate::cmd::Run;
22+
use crate::self_update;
2123

2224
#[derive(Parser)]
2325
#[command(author, version, about)]
@@ -44,10 +46,15 @@ pub enum Commands {
4446
Release(ReleaseArgs),
4547
Open(OpenArgs),
4648
Reset(ResetArgs),
49+
Update(UpdateArgs),
4750
}
4851

4952
impl Run for App {
5053
fn run(&self) -> Result<()> {
54+
match &self.command {
55+
Commands::Update(_) | Commands::Init(_) | Commands::Complete(_) => {}
56+
_ => self_update::auto().context("Check auto self-update")?,
57+
}
5158
match &self.command {
5259
Commands::Init(args) => args.run(),
5360
Commands::Home(args) => args.run(),
@@ -65,6 +72,7 @@ impl Run for App {
6572
Commands::Release(args) => args.run(),
6673
Commands::Open(args) => args.run(),
6774
Commands::Reset(args) => args.run(),
75+
Commands::Update(args) => args.run(),
6876
}
6977
}
7078
}

src/cmd/run/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ pub mod remove;
1414
pub mod reset;
1515
pub mod squash;
1616
pub mod tag;
17+
pub mod update;

src/cmd/run/update.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
use anyhow::Result;
2+
use clap::Args;
3+
4+
use crate::cmd::Run;
5+
use crate::self_update;
6+
7+
/// If there is a new version, update roxide
8+
#[derive(Args)]
9+
pub struct UpdateArgs {}
10+
11+
impl Run for UpdateArgs {
12+
fn run(&self) -> Result<()> {
13+
self_update::trigger()
14+
}
15+
}

src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ mod cmd;
33
mod config;
44
mod errors;
55
mod repo;
6+
mod self_update;
67
mod shell;
78
mod utils;
89

src/self_update.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
use std::io::ErrorKind;
2+
use std::path::PathBuf;
3+
use std::{env, fs};
4+
5+
use anyhow::{bail, Context, Result};
6+
use console::style;
7+
8+
use crate::api::github::Github;
9+
use crate::shell::Shell;
10+
use crate::{config, confirm, info, utils};
11+
12+
pub fn trigger() -> Result<()> {
13+
let exec_path = env::current_exe().context("Get current exec path")?;
14+
let exec_dir = match exec_path.parent() {
15+
Some(dir) => dir,
16+
None => bail!("Invalid exec path {}", exec_path.display()),
17+
};
18+
info!("Checking new version for roxide");
19+
let api = Github::new_empty();
20+
let latest = api.get_latest_tag("fioncat", "roxide")?;
21+
let current = env!("CARGO_PKG_VERSION");
22+
let current = format!("v{current}");
23+
if latest.as_str() == current {
24+
info!("Your roxide is up-to-date");
25+
return Ok(());
26+
}
27+
28+
confirm!(
29+
"Found new version {} for roxide, do you want to update",
30+
style(&latest).magenta()
31+
);
32+
33+
let update_script = include_bytes!("../install.sh");
34+
let path = PathBuf::from(&config::base().metadir).join("update.sh");
35+
utils::write_file(&path, update_script)?;
36+
37+
let script = format!("sh {} {} true", path.display(), exec_dir.display());
38+
let mut shell = Shell::sh(&script);
39+
shell.with_desc("Execute update script");
40+
shell.execute()?.check()?;
41+
42+
info!("Update roxide to {} done", latest);
43+
Ok(())
44+
}
45+
46+
pub fn auto() -> Result<()> {
47+
match env::var_os("ROXIDE_AUTO_UPDATE") {
48+
Some(auto_update_opt) => match auto_update_opt.to_str() {
49+
Some(opt) => match opt.to_lowercase().as_str() {
50+
"yes" | "true" => {}
51+
_ => return Ok(()),
52+
},
53+
_ => return Ok(()),
54+
},
55+
None => return Ok(()),
56+
}
57+
let now = config::now_secs();
58+
let last_update = get_last_update_time()?;
59+
let duration = now.saturating_sub(last_update);
60+
if duration < utils::DAY {
61+
return Ok(());
62+
}
63+
write_last_update_time(now)?;
64+
65+
trigger()
66+
}
67+
68+
fn get_last_update_time() -> Result<u64> {
69+
let path = PathBuf::from(&config::base().metadir).join("last_update");
70+
utils::ensure_dir(&path)?;
71+
72+
match fs::read(&path) {
73+
Ok(data) => {
74+
let time = String::from_utf8(data).context("Decode last_update utf-8")?;
75+
let time = time
76+
.parse::<u64>()
77+
.context("Parse last_update as integer")?;
78+
Ok(time)
79+
}
80+
Err(err) if err.kind() == ErrorKind::NotFound => Ok(0),
81+
Err(err) => Err(err).with_context(|| format!("Read last_update file {}", path.display())),
82+
}
83+
}
84+
85+
fn write_last_update_time(now: u64) -> Result<()> {
86+
let path = PathBuf::from(&config::base().metadir).join("last_update");
87+
let content = format!("{now}");
88+
utils::write_file(&path, content.as_bytes()).context("Write last_update file")
89+
}

0 commit comments

Comments
 (0)