Skip to content
This repository has been archived by the owner on Dec 14, 2024. It is now read-only.

Commit

Permalink
Add upgrade command
Browse files Browse the repository at this point in the history
  • Loading branch information
xalanq committed Apr 29, 2019
1 parent eab0206 commit f1d66df
Show file tree
Hide file tree
Showing 7 changed files with 251 additions and 17 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,6 @@ $ go build -ldflags "-s -w" cf.go
## Usage

```plain
Codeforces Tool (cf). https://github.com/xalanq/cf-tool
You should run "cf config login" and "cf config add" at first.
If you want to compete, the best command is "cf race 1111" where "1111" is the contest id.
Expand All @@ -63,6 +61,7 @@ Usage:
cf stand [<contest-id>]
cf race <contest-id>
cf pull [ac] [<contest-id>] [<problem-id>]
cf upgrade
Examples:
cf config login Config your username and password.
Expand Down Expand Up @@ -94,6 +93,7 @@ Examples:
cf pull 100 a Pull the latest code of problem "a" of contest 100 into "./100/<problem-id>".
cf pull ac 100 a Pull the "Accepted" or "Pretests passed" code of problem "a" of contest 100.
cf pull Pull the latest code of current problem into current path.
cf upgrade Upgrade the "cf" to the latest version from GitHub.
Notes:
<problem-id> "a" or "A", case-insensitive.
Expand Down
4 changes: 2 additions & 2 deletions README_zh_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ $ go build -ldflags "-s -w" cf.go
## 使用方法

```plain
Codeforces Tool (cf). https://github.com/xalanq/cf-tool
首先你得用 "cf config login"、 "cf config add" 命令来配置一下
如果你想用本工具打比赛,那么最好用 "cf race 1111" 命令,其中 "1111" 是比赛的 id
Expand All @@ -61,6 +59,7 @@ Codeforces Tool (cf). https://github.com/xalanq/cf-tool
cf stand [<contest-id>]
cf race <contest-id>
cf pull [ac] [<contest-id>] [<problem-id>]
cf upgrade
例子:
cf config login 配置你的用户名和密码。
Expand Down Expand Up @@ -91,6 +90,7 @@ Codeforces Tool (cf). https://github.com/xalanq/cf-tool
cf pull 100 a 拉取比赛 id 为 100 的题目 a 的最新代码到文件夹 "./100/a" 下。
cf pull ac 100 a 拉取比赛 id 为 100 的题目 a 的 AC 代码。
cf pull 拉取当前题目的最新代码到当前文件夹下。
cf upgrade Upgrade the "cf" to the latest version from GitHub.
注意:
<problem-id> 表示题目的 id,比如 "a" 或者 "A",不区分大小写。
Expand Down
13 changes: 11 additions & 2 deletions cf.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package main

import (
"fmt"
"strings"

"github.com/fatih/color"
ansi "github.com/k0kubun/go-ansi"
"github.com/xalanq/cf-tool/cmd"
Expand All @@ -9,8 +12,10 @@ import (
docopt "github.com/docopt/docopt-go"
)

const version = "v0.5.0"

func main() {
usage := `Codeforces Tool (cf). https://github.com/xalanq/cf-tool
usage := `Codeforces Tool $%version%$ (cf). https://github.com/xalanq/cf-tool
You should run "cf config login" and "cf config add" at first.
Expand All @@ -29,6 +34,7 @@ Usage:
cf stand [<contest-id>]
cf race <contest-id>
cf pull [ac] [<contest-id>] [<problem-id>]
cf upgrade
Examples:
cf config login Config your username and password.
Expand Down Expand Up @@ -60,6 +66,7 @@ Examples:
cf pull 100 a Pull the latest code of problem "a" of contest 100 into "./100/<problem-id>".
cf pull ac 100 a Pull the "Accepted" or "Pretests passed" code of problem "a" of contest 100.
cf pull Pull the latest code of current problem into current path.
cf upgrade Upgrade the "cf" to the latest version from GitHub.
Notes:
<problem-id> "a" or "A", case-insensitive.
Expand Down Expand Up @@ -106,8 +113,10 @@ Script in template:
Options:
-h --help
--version`
usage = strings.Replace(usage, `$%version%$`, version, 1)

args, _ := docopt.Parse(usage, nil, true, "Codeforces Tool (cf) v0.4.0", false)
args, _ := docopt.Parse(usage, nil, true, fmt.Sprintf("Codeforces Tool (cf) %v", version), false)
args[`{version}`] = version
color.Output = ansi.NewAnsiStdout()
config.Init()
err := cmd.Eval(args)
Expand Down
2 changes: 2 additions & 0 deletions cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ func Eval(args map[string]interface{}) error {
return Race(args)
} else if args["pull"].(bool) {
return Pull(args)
} else if args["upgrade"].(bool) {
return Upgrade(args["{version}"].(string))
}
return nil
}
Expand Down
216 changes: 216 additions & 0 deletions cmd/upgrade.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
package cmd

import (
"archive/zip"
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"regexp"
"runtime"
"strconv"
"time"

"github.com/fatih/color"
"github.com/xalanq/cf-tool/util"
)

func less(a, b string) bool {
reg := regexp.MustCompile(`(\d+)\.(\d+)\.(\d+)`)
x := reg.FindSubmatch([]byte(a))
y := reg.FindSubmatch([]byte(b))
num := func(s []byte) int {
n, _ := strconv.Atoi(string(s))
return n
}
for i := 1; i <= 3; i++ {
if num(x[i]) < num(y[i]) {
return true
} else if num(x[i]) > num(y[i]) {
return false
}
}
return false
}

func getLatest() (version, note, ptime, url string, size uint, err error) {
goos := ""
switch runtime.GOOS {
case "darwin":
goos = "osx"
case "linux":
goos = "linux"
case "windows":
goos = "win"
default:
err = fmt.Errorf("Not support %v", runtime.GOOS)
return
}

arch := ""
switch runtime.GOARCH {
case "386":
arch = "32"
case "amd64":
arch = "64"
default:
err = fmt.Errorf("Not support %v", runtime.GOARCH)
return
}

resp, err := http.Get("https://api.github.com/repos/xalanq/cf-tool/releases/latest")
if err != nil {
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return
}
result := make(map[string]interface{})
json.Unmarshal(body, &result)
version = result["tag_name"].(string)
note = result["body"].(string)
tm, _ := time.Parse("2006-01-02T15:04:05Z", result["published_at"].(string))
ptime = tm.In(time.Local).Format("2006-01-02 15:04")
url = fmt.Sprintf("https://github.com/xalanq/cf-tool/releases/download/%v/cf_%v_%v_%v.zip", version, version, goos, arch)
assets, _ := result["assets"].([]interface{})
for _, tmp := range assets {
asset, _ := tmp.(map[string]interface{})
if url == asset["browser_download_url"] {
size = uint(asset["size"].(float64))
break
}
}
return
}

// WriteCounter progress counter
type WriteCounter struct {
Count, Total uint
last uint
}

// Print print progress
func (w *WriteCounter) Print() {
fmt.Printf("\rProgress: %v/%v KB Speed: %v KB/s", w.Count/1024, w.Total/1024, (w.Count-w.last)/1024)
w.last = w.Count
}

func (w *WriteCounter) Write(p []byte) (int, error) {
n := len(p)
w.Count += uint(n)
return n, nil
}

func upgrade(url, exe string, size uint) (err error) {
color.Cyan("Download %v", url)
counter := &WriteCounter{Count: 0, Total: size, last: 0}
counter.Print()

ticker := time.NewTicker(time.Second)
go func() {
for range ticker.C {
counter.Print()
}
}()

resp, err := http.Get(url)
if err != nil {
return
}
defer resp.Body.Close()

data, err := ioutil.ReadAll(io.TeeReader(resp.Body, counter))
if err != nil {
ticker.Stop()
counter.Print()
fmt.Println()
return
}
ticker.Stop()
counter.Print()
fmt.Println()
reader, err := zip.NewReader(bytes.NewReader(data), int64(len(data)))
if err != nil {
return
}

rc, err := reader.File[0].Open()
if err != nil {
return
}
defer rc.Close()

newPath := filepath.Join(os.TempDir(), fmt.Sprintf(".%s.new", filepath.Base(exe)))
oldPath := filepath.Join(os.TempDir(), fmt.Sprintf(".%s.old", filepath.Base(exe)))

file, err := os.OpenFile(newPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755)
if err != nil {
return
}
defer file.Close()

_, err = io.Copy(file, rc)
if err != nil {
return
}
file.Close()

err = os.Rename(exe, oldPath)
if err != nil {
os.Remove(newPath)
return
}

err = os.Rename(newPath, exe)
if err != nil {
os.Rename(oldPath, exe)
os.Remove(newPath)
return
}

os.Remove(oldPath)
return nil
}

// Upgrade itself
func Upgrade(version string) error {
color.Cyan("Checking version")
latest, note, ptime, url, size, err := getLatest()
if err != nil {
return err
}
if !less(version, latest) {
color.Green("Current version %v is the latest", version)
return nil
}

color.Red("Current version is %v", version)
color.Green("The latest version is %v, published at %v", latest, ptime)
fmt.Println(note)

if !util.YesOrNo("Do you want to upgrade (y/n)? ") {
return nil
}

exe, err := os.Executable()
if err != nil {
return err
}

if exe, err = filepath.EvalSymlinks(exe); err != nil {
return err
}

if err = upgrade(url, exe, size); err != nil {
return err
}

color.Green("Successfully updated to version %v", latest)
return nil
}
14 changes: 3 additions & 11 deletions config/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,18 +72,10 @@ func (c *Config) AddTemplate() (err error) {
beforeScript, script, afterScript,
})

color.Cyan("Make it default (y/n)? ")
for {
tmp := util.ScanlineTrim()
if tmp == "y" || tmp == "Y" {
c.Default = len(c.Template) - 1
break
}
if tmp == "n" || tmp == "N" {
break
}
color.Red("Invalid input. Please input again: ")
if util.YesOrNo("Make it default (y/n)? ") {
c.Default = len(c.Template) - 1
}

return c.save()
}

Expand Down
15 changes: 15 additions & 0 deletions util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,18 @@ func ChooseIndex(maxLen int) int {
color.Red("Invalid index! Please try again: ")
}
}

// YesOrNo must choose one
func YesOrNo(note string) bool {
color.Cyan(note)
for {
tmp := ScanlineTrim()
if tmp == "y" || tmp == "Y" {
return true
}
if tmp == "n" || tmp == "N" {
return false
}
color.Red("Invalid input. Please input again: ")
}
}

0 comments on commit f1d66df

Please sign in to comment.