github.com/avahowell/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  }