diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..79d5ee3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +vendor +download +store +*.exe +.idea \ No newline at end of file diff --git a/glide.lock b/glide.lock new file mode 100644 index 0000000..45f0cf9 --- /dev/null +++ b/glide.lock @@ -0,0 +1,8 @@ +hash: b937a6361b08120db9107ffde62ee4c23850da7d2316e217213a79b642fef342 +updated: 2017-07-15T21:04:42.1322442+08:00 +imports: +- name: github.com/codegangsta/cli + version: 0bdeddeeb0f650497d603c4ad7b20cfe685682f6 +- name: github.com/tucnak/store + version: 84b795be94a4e02b5d92d7ef7490c4ebdb347ddf +testImports: [] diff --git a/glide.yaml b/glide.yaml new file mode 100644 index 0000000..ec9e0a9 --- /dev/null +++ b/glide.yaml @@ -0,0 +1,6 @@ +package: github.com/ystyle/jvms +import: +- package: github.com/codegangsta/cli + version: ^1.19.1 +- package: github.com/tucnak/store + version: ^1.0.0 diff --git a/jdkdlindex.json b/jdkdlindex.json new file mode 100644 index 0000000..3881e13 --- /dev/null +++ b/jdkdlindex.json @@ -0,0 +1,26 @@ +[ + { + "version":"ibm_sdk80", + "url":"http://7xo3cg.com1.z0.glb.clouddn.com/vibm_sdk80_x64.zip" + }, + { + "version":"ibm_sdk71", + "url":"http://7xo3cg.com1.z0.glb.clouddn.com/vibm_sdk71_x64.zip" + }, + { + "version":"1.8.0_31", + "url":"http://7xo3cg.com1.z0.glb.clouddn.com/jdk1.8.0_31_x64.zip" + }, + { + "version":"1.7.0_67", + "url":"http://7xo3cg.com1.z0.glb.clouddn.com/jdk1.7.0_67_x64.zip" + }, + { + "version":"1.6.0_43", + "url":"http://7xo3cg.com1.z0.glb.clouddn.com/jdk1.6.0_43_x64.zip" + }, + { + "version":"1.6.0_43_x86", + "url":"http://7xo3cg.com1.z0.glb.clouddn.com/jdk1.6.0_43_x86.zip" + } +] \ No newline at end of file diff --git a/main.go b/main.go new file mode 100644 index 0000000..b8b715c --- /dev/null +++ b/main.go @@ -0,0 +1,310 @@ +package main + +import ( + "os" + "log" + "github.com/codegangsta/cli" + "github.com/tucnak/store" + "errors" + "fmt" + "github.com/ystyle/jvms/utils/jdk" + "os/exec" + "github.com/ystyle/jvms/utils/file" + "github.com/ystyle/jvms/utils/web" + "encoding/json" +) + +const ( + version = "2.0.0" + default_Originalpath = "http://7xo3cg.com1.z0.glb.clouddn.com/2.0/jdkdlindex.json" +) + +type Config struct { + JavaHome string`json:"java_home"` + CurrentJDKVersion string`json:"current_jdk_version"` + Originalpath string`json:"original_path"` + Proxy string`json:"proxy"` + store string + download string +} + +var config Config + +type JdkVersion struct { + Version string`json:"version"` + Url string`json:"url"` +} + +func main() { + app := cli.NewApp() + app.Name = "jvms" + app.Usage = `JDK Version Manager (JVMS) for Windows` + app.Version = version + + app.CommandNotFound = func(c *cli.Context, command string) { + log.Fatal("Command Not Found") + } + app.Commands = commands() + app.Before = startup + app.After = shutdown + if err := app.Run(os.Args); err != nil { + log.Fatal(err.Error()) + os.Exit(1) + } +} + +func commands() []cli.Command { + return []cli.Command{ + { + Name: "init", + Usage: "Initialize config file", + Description: `before init you should clear JAVA_HOME, PATH Environment variable。`, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "java_home", + Usage: "the JAVA_HOME location", + Value: os.Getenv("ProgramFiles") + "\\jdk", + }, + cli.StringFlag{ + Name: "originalpath", + Usage: "the jdk download index file url.", + Value: default_Originalpath, + }, + }, + Action: func(c *cli.Context) error { + if c.IsSet("java_home") || config.JavaHome == "" { + config.JavaHome = c.String("java_home") + cmd := exec.Command("cmd", "/C", "setx", "JAVA_HOME", config.JavaHome, "/M") + err := cmd.Run() + if err != nil { + return errors.New("Set Environment variable `JAVA_HOME` failure: Please run as admin user") + } + fmt.Println("set `JAVA_HOME` Environment variable to ", config.JavaHome) + } + if c.IsSet("originalpath") || config.Originalpath == "" { + config.Originalpath = c.String("originalpath") + } + path := fmt.Sprintf(`%s/bin;%s;%s`, config.JavaHome, os.Getenv("PATH"), file.GetCurrentPath()) + cmd := exec.Command("cmd", "/C", "setx", "path", path, "/m") + err := cmd.Run() + if err != nil { + return errors.New("Set Environment variable `PATH` failure: Please run as admin user") + } + fmt.Println("add jvms.exe to `path` Environment variable") + return nil + }, + }, + { + Name: "list", + ShortName: "ls", + Usage: "List the JDK installations.", + Action: func(c *cli.Context) error { + fmt.Println("Installed jdk (mark up * is in used):") + v := jdk.GetInstalled(config.store) + for i, version := range v { + str := "" + if config.CurrentJDKVersion == version { + str = fmt.Sprintf("%s * %d) %s", str, i+1, version) + } else { + str = fmt.Sprintf("%s %d) %s", str, i+1, version) + } + fmt.Printf(str + "\n") + } + if len(v) == 0 { + fmt.Println("No installations recognized.") + } + return nil + }, + }, + { + Name: "install", + ShortName: "i", + Usage: "Install remote available jdk", + Action: func(c *cli.Context) error { + v := c.Args().Get(0) + if v == "" { + return errors.New("Invalid version., Type \"jvms rls\" to see what is available for install.") + } + + if jdk.IsVersionInstalled(config.store, v) { + fmt.Println("Version " + version + " is already installed.") + return nil + } + versions, err := getJdkVersions() + if err != nil { + return err + } + + if !file.Exists(config.download){ + os.MkdirAll(config.download,0666) + } + if !file.Exists(config.store){ + os.MkdirAll(config.store,0666) + } + + for _, version := range versions { + if version.Version == v { + dlzipfile := fmt.Sprintf("%s%s.zip", config.download, v) + os.Remove(dlzipfile) + success := web.GetJDK(config.download, v, version.Url) + if success { + fmt.Printf("Installing JDK %s...\n", v) + + // Extract jdk to the temp directory + jdktempfile := fmt.Sprintf("%s%s_temp", config.download, v) + file.Unzip(dlzipfile, jdktempfile) + + // Copy the jdk files to the installation directory + os.Rename(jdktempfile, fmt.Sprintf("%s%s", config.store, v)) + + // Remove the temp directory + // may consider keep the temp files here + os.RemoveAll(jdktempfile) + + fmt.Println("Installation complete. If you want to use this version, type\n\njvms use", v) + } else { + fmt.Println("Could not download JDK " + v + " executable.") + } + return nil + } + } + return errors.New("Invalid version., Type \"jvms rls\" to see what is available for install.") + }, + }, + { + Name: "switch", + ShortName: "s", + Usage: "Switch to use the specified version.", + Action: func(c *cli.Context) error { + v := c.Args().Get(0) + if v == "" { + return errors.New("you should input a version, Type \"jvms list\" to see what is installed.") + } + if !jdk.IsVersionInstalled(config.store, v) { + fmt.Printf("jdk %s is uninstall. ", v) + } + // Create or update the symlink + if file.Exists(config.JavaHome) { + err := os.Remove(config.JavaHome) + if err != nil { + return errors.New("Switch jdk failed, please manually remove "+ config.JavaHome) + } + } + err := os.Symlink(config.store+v, config.JavaHome) + if err != nil { + return errors.New("Switch jdk failed, " + err.Error()) + } + fmt.Println("Switch success.\nNow using JDK " + v) + config.CurrentJDKVersion = v + return nil + }, + }, + { + Name: "remove", + ShortName: "rm", + Usage: "Remove a specific version.", + Action: func(c *cli.Context) error { + v := c.Args().Get(0) + if v == "" { + return errors.New("you should input a version, Type \"jvms list\" to see what is installed.") + } + if jdk.IsVersionInstalled(config.store, v) { + fmt.Printf("Remove JDK %s ...\n", v) + if config.CurrentJDKVersion == v { + os.Remove(config.JavaHome) + } + e := os.RemoveAll(config.store + "\\" + v) + if e != nil { + fmt.Println("Error removing jdk " + v) + fmt.Println("Manually remove " + config.store + "\\" + v + ".") + } else { + fmt.Printf(" done") + } + } else { + fmt.Println("jdk " + v + " is not installed. Type \"jvms list\" to see what is installed.") + } + return nil + }, + }, + { + Name: "rls", + Usage: "Show a list of versions available for download. ", + Action: func(c *cli.Context) error { + if config.Proxy != "" { + web.SetProxy(config.Proxy) + } + versions, err := getJdkVersions() + if err != nil { + return err + } + for i, version := range versions { + fmt.Printf(" %d) %s\n", i+1, version.Version) + } + if len(versions) == 0 { + fmt.Println("No availabled jdk veriosn for download.") + } + + fmt.Printf("\nFor a complete list, visit %s\n", config.Originalpath) + return nil + }, + }, + { + Name: "proxy", + Usage: "Set a proxy to use for downloads.", + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "show", + Usage: "show proxy.", + }, + cli.StringFlag{ + Name: "set", + Usage: "set proxy.", + }, + }, + Action: func(c *cli.Context) error { + if c.Bool("show") { + fmt.Printf("Current proxy: %s\n", config.Proxy) + return nil + } + if c.IsSet("set") { + config.Proxy = c.String("set") + } + return nil + }, + }, + } +} + +func getJdkVersions() ([]JdkVersion, error) { + jsonContent, err := web.GetRemoteTextFile(config.Originalpath) + if err != nil { + return nil, err + } + + var versions []JdkVersion + err = json.Unmarshal([]byte(jsonContent), &versions) + if err != nil { + return nil, err + } + return versions, nil +} + +func startup(c *cli.Context) error { + store.Init("jvms") + if err := store.Load("jvms.json", &config); err != nil { + return errors.New("failed to load the config:" + err.Error()) + } + config.store = file.GetCurrentPath() + "store/" + config.download = file.GetCurrentPath() + "download/" + if config.Originalpath == "" { + config.Originalpath = default_Originalpath + } + return nil +} + +func shutdown(c *cli.Context) error { + if err := store.Save("jvms.json", &config); err != nil { + return errors.New("failed to save the config:" + err.Error()) + } + return nil +} diff --git a/utils/file/file.go b/utils/file/file.go new file mode 100644 index 0000000..9dd71ed --- /dev/null +++ b/utils/file/file.go @@ -0,0 +1,87 @@ +package file + +import ( + "os" + "bufio" + "archive/zip" + "path/filepath" + "strings" + "log" + "io" + "os/exec" + "fmt" +) + +// Function courtesy http://stackoverflow.com/users/1129149/swtdrgn +func Unzip(src, dest string) error { + r, err := zip.OpenReader(src) + if err != nil { + return err + } + defer r.Close() + + for _, f := range r.File { + rc, err := f.Open() + if err != nil { + return err + } + defer rc.Close() + + fpath := filepath.Join(dest, f.Name) + if f.FileInfo().IsDir() { + os.MkdirAll(fpath, f.Mode()) + } else { + var fdir string + if lastIndex := strings.LastIndex(fpath, string(os.PathSeparator)); lastIndex > -1 { + fdir = fpath[:lastIndex] + } + + err = os.MkdirAll(fdir, f.Mode()) + if err != nil { + log.Fatal(err) + return err + } + f, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + if err != nil { + return err + } + defer f.Close() + + _, err = io.Copy(f, rc) + if err != nil { + return err + } + } + } + return nil +} + +func ReadLines(path string) ([]string, error) { + file, err := os.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + + var lines []string + scanner := bufio.NewScanner(file) + for scanner.Scan() { + lines = append(lines, scanner.Text()) + } + return lines, scanner.Err() +} + +func Exists(filename string) bool { + _, err := os.Stat(filename) + return err == nil +} + +func GetCurrentPath() string { + s, err := exec.LookPath(os.Args[0]) + if err != nil { + fmt.Println(err.Error()) + } + i := strings.LastIndex(s, "\\") + path := string(s[0 : i+1]) + return strings.Replace(path, "\\", "/", -1) +} diff --git a/utils/jdk/jdk.go b/utils/jdk/jdk.go new file mode 100644 index 0000000..5c7547d --- /dev/null +++ b/utils/jdk/jdk.go @@ -0,0 +1,23 @@ +package jdk + +import ( + "io/ioutil" + "github.com/ystyle/jvms/utils/file" + "fmt" +) + +func GetInstalled(root string) []string { + list := make([]string, 0) + files, _ := ioutil.ReadDir(root) + for i := len(files) - 1; i >= 0; i-- { + if files[i].IsDir() { + list = append(list, files[i].Name()) + } + } + return list +} + +func IsVersionInstalled(root string, version string) bool { + isInstalled := file.Exists(fmt.Sprintf("%s/%s/bin/javac.exe", root, version)) + return isInstalled +} diff --git a/utils/web/web.go b/utils/web/web.go new file mode 100644 index 0000000..51a5e01 --- /dev/null +++ b/utils/web/web.go @@ -0,0 +1,85 @@ +package web + +import ( + "fmt" + "net/http" + "net/url" + "os" + "io" + "io/ioutil" + "errors" +) + +var client = &http.Client{} + +func SetProxy(p string) { + if p != "" && p != "none" { + proxyUrl, _ := url.Parse(p) + client = &http.Client{Transport: &http.Transport{Proxy: http.ProxyURL(proxyUrl)}} + } else { + client = &http.Client{} + } +} + +func Download(url string, target string) bool { + output, err := os.Create(target) + if err != nil { + fmt.Println("Error while creating", target, "-", err) + } + defer output.Close() + + response, err := client.Get(url) + if err != nil { + fmt.Println("Error while downloading", url, "-", err) + } + defer response.Body.Close() + + _, err = io.Copy(output, response.Body) + if err != nil { + fmt.Println("Error while downloading", url, "-", err) + } + + if response.Status[0:3] != "200" { + fmt.Println("Download failed. Rolling Back.") + err := os.Remove(target) + if err != nil { + fmt.Println("Rollback failed.", err) + } + return false + } + + return true +} + +func GetJDK(download string, v string, url string) bool { + + if url == "" { + //No url should mean this version/arch isn't available + fmt.Printf("JDK %s isn't available right now.", v) + } else { + fileName := fmt.Sprintf("%s%s.zip", download, v) + fmt.Printf("Downloading jdk version %s...\n", v) + if Download(url, fileName) { + fmt.Println("Complete") + return true + } else { + return false + } + } + return false + +} + +func GetRemoteTextFile(url string) (string,error) { + response, httperr := client.Get(url) + if httperr != nil { + return "", errors.New(fmt.Sprintf("\nCould not retrieve %s.\n\n%s\n",url,httperr.Error())) + } else { + defer response.Body.Close() + contents, readerr := ioutil.ReadAll(response.Body) + if readerr != nil { + return "", errors.New(fmt.Sprintf("%s", readerr)) + } + return string(contents),nil + } +}