github.com/johnathanhowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/siac/main.go (about) 1 package main 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "io/ioutil" 8 "net" 9 "net/http" 10 "os" 11 "reflect" 12 "strings" 13 14 "github.com/spf13/cobra" 15 16 "github.com/NebulousLabs/Sia/api" 17 "github.com/NebulousLabs/Sia/build" 18 ) 19 20 // flags 21 var ( 22 addr string // override default API address 23 initPassword bool // supply a custom password when creating a wallet 24 hostVerbose bool // display additional host info 25 renterShowHistory bool // Show download history in addition to download queue. 26 renterListVerbose bool // Show additional info about uploaded files. 27 ) 28 29 // exit codes 30 // inspired by sysexits.h 31 const ( 32 exitCodeGeneral = 1 // Not in sysexits.h, but is standard practice. 33 exitCodeUsage = 64 // EX_USAGE in sysexits.h 34 ) 35 36 // apiGet wraps a GET request with a status code check, such that if the GET does 37 // not return 200, the error will be read and returned. The response body is 38 // not closed. 39 func apiGet(call string) (*http.Response, error) { 40 if host, port, _ := net.SplitHostPort(addr); host == "" { 41 addr = net.JoinHostPort("localhost", port) 42 } 43 resp, err := api.HttpGET("http://" + addr + call) 44 if err != nil { 45 return nil, errors.New("no response from daemon") 46 } 47 // check error code 48 if resp.StatusCode == http.StatusNotFound { 49 resp.Body.Close() 50 err = errors.New("API call not recognized: " + call) 51 } else if resp.StatusCode != http.StatusOK { 52 errResp, _ := ioutil.ReadAll(resp.Body) 53 resp.Body.Close() 54 err = errors.New(strings.TrimSpace(string(errResp))) 55 } 56 return resp, err 57 } 58 59 // getAPI makes a GET API call and decodes the response. 60 func getAPI(call string, obj interface{}) error { 61 resp, err := apiGet(call) 62 if err != nil { 63 return err 64 } 65 defer resp.Body.Close() 66 err = json.NewDecoder(resp.Body).Decode(obj) 67 if err != nil { 68 return err 69 } 70 return nil 71 } 72 73 // get makes an API call and discards the response. 74 func get(call string) error { 75 resp, err := apiGet(call) 76 if err != nil { 77 return err 78 } 79 resp.Body.Close() 80 return nil 81 } 82 83 // apiPost wraps a POST request with a status code check, such that if the POST 84 // does not return 200, the error will be read and returned. The response body 85 // is not closed. 86 func apiPost(call, vals string) (*http.Response, error) { 87 if host, port, _ := net.SplitHostPort(addr); host == "" { 88 addr = net.JoinHostPort("localhost", port) 89 } 90 91 resp, err := api.HttpPOST("http://"+addr+call, vals) 92 if err != nil { 93 return nil, errors.New("no response from daemon") 94 } 95 // check error code 96 if resp.StatusCode == http.StatusNotFound { 97 resp.Body.Close() 98 err = errors.New("API call not recognized: " + call) 99 } else if resp.StatusCode != http.StatusOK { 100 errResp, _ := ioutil.ReadAll(resp.Body) 101 resp.Body.Close() 102 err = errors.New(strings.TrimSpace(string(errResp))) 103 } 104 return resp, err 105 } 106 107 // postResp makes a POST API call and decodes the response. 108 func postResp(call, vals string, obj interface{}) error { 109 resp, err := apiPost(call, vals) 110 if err != nil { 111 return err 112 } 113 defer resp.Body.Close() 114 err = json.NewDecoder(resp.Body).Decode(obj) 115 if err != nil { 116 return err 117 } 118 return nil 119 } 120 121 func post(call, vals string) error { 122 resp, err := apiPost(call, vals) 123 if err != nil { 124 return err 125 } 126 resp.Body.Close() 127 return nil 128 } 129 130 // wrap wraps a generic command with a check that the command has been 131 // passed the correct number of arguments. The command must take only strings 132 // as arguments. 133 func wrap(fn interface{}) func(*cobra.Command, []string) { 134 fnVal, fnType := reflect.ValueOf(fn), reflect.TypeOf(fn) 135 if fnType.Kind() != reflect.Func { 136 panic("wrapped function has wrong type signature") 137 } 138 for i := 0; i < fnType.NumIn(); i++ { 139 if fnType.In(i).Kind() != reflect.String { 140 panic("wrapped function has wrong type signature") 141 } 142 } 143 144 return func(cmd *cobra.Command, args []string) { 145 if len(args) != fnType.NumIn() { 146 cmd.Usage() 147 os.Exit(exitCodeUsage) 148 } 149 argVals := make([]reflect.Value, fnType.NumIn()) 150 for i := range args { 151 argVals[i] = reflect.ValueOf(args[i]) 152 } 153 fnVal.Call(argVals) 154 } 155 } 156 157 // die prints its arguments to stderr, then exits the program with the default 158 // error code. 159 func die(args ...interface{}) { 160 fmt.Fprintln(os.Stderr, args...) 161 os.Exit(exitCodeGeneral) 162 } 163 164 // yesNo returns "Yes" if b is true, and "No" if b is false. 165 func yesNo(b bool) string { 166 if b { 167 return "Yes" 168 } 169 return "No" 170 } 171 172 func version() { 173 println("Sia Client v" + build.Version) 174 } 175 176 func main() { 177 root := &cobra.Command{ 178 Use: os.Args[0], 179 Short: "Sia Client v" + build.Version, 180 Long: "Sia Client v" + build.Version, 181 Run: wrap(consensuscmd), 182 } 183 184 // create command tree 185 root.AddCommand(&cobra.Command{ 186 Use: "version", 187 Short: "Print version information", 188 Long: "Print version information.", 189 Run: wrap(version), 190 }) 191 192 root.AddCommand(stopCmd) 193 194 root.AddCommand(hostCmd) 195 hostCmd.AddCommand(hostConfigCmd, hostAnnounceCmd, hostFolderCmd, hostSectorCmd) 196 hostFolderCmd.AddCommand(hostFolderAddCmd, hostFolderRemoveCmd, hostFolderResizeCmd) 197 hostSectorCmd.AddCommand(hostSectorDeleteCmd) 198 hostCmd.Flags().BoolVarP(&hostVerbose, "verbose", "v", false, "Display detailed host info") 199 200 root.AddCommand(hostdbCmd) 201 202 root.AddCommand(minerCmd) 203 minerCmd.AddCommand(minerStartCmd, minerStopCmd) 204 205 root.AddCommand(walletCmd) 206 walletCmd.AddCommand(walletAddressCmd, walletAddressesCmd, walletInitCmd, 207 walletLoadCmd, walletLockCmd, walletSeedsCmd, walletSendCmd, 208 walletBalanceCmd, walletTransactionsCmd, walletUnlockCmd) 209 walletInitCmd.Flags().BoolVarP(&initPassword, "password", "p", false, "Prompt for a custom password") 210 walletLoadCmd.AddCommand(walletLoad033xCmd, walletLoadSeedCmd, walletLoadSiagCmd) 211 walletSendCmd.AddCommand(walletSendSiacoinsCmd, walletSendSiafundsCmd) 212 213 root.AddCommand(renterCmd) 214 renterCmd.AddCommand(renterFilesDeleteCmd, renterFilesDownloadCmd, 215 renterDownloadsCmd, renterAllowanceCmd, renterSetAllowanceCmd, 216 renterContractsCmd, renterFilesListCmd, renterFilesLoadCmd, 217 renterFilesLoadASCIICmd, renterFilesRenameCmd, renterFilesShareCmd, 218 renterFilesShareASCIICmd, renterFilesUploadCmd, renterUploadsCmd) 219 renterCmd.Flags().BoolVarP(&renterListVerbose, "verbose", "v", false, "Show additional file info such as redundancy") 220 renterDownloadsCmd.Flags().BoolVarP(&renterShowHistory, "history", "H", false, "Show download history in addition to the download queue") 221 renterFilesListCmd.Flags().BoolVarP(&renterListVerbose, "verbose", "v", false, "Show additional file info such as redundancy") 222 223 root.AddCommand(gatewayCmd) 224 gatewayCmd.AddCommand(gatewayAddCmd, gatewayRemoveCmd, gatewayAddressCmd, gatewayListCmd) 225 226 root.AddCommand(consensusCmd) 227 228 // parse flags 229 root.PersistentFlags().StringVarP(&addr, "addr", "a", "localhost:9980", "which host/port to communicate with (i.e. the host/port siad is listening on)") 230 231 // run 232 if err := root.Execute(); err != nil { 233 // Since no commands return errors (all commands set Command.Run instead of 234 // Command.RunE), Command.Execute() should only return an error on an 235 // invalid command or flag. Therefore Command.Usage() was called (assuming 236 // Command.SilenceUsage is false) and we should exit with exitCodeUsage. 237 os.Exit(exitCodeUsage) 238 } 239 }