diff --git a/common/default.go b/common/default.go new file mode 100644 index 0000000..30a9bdb --- /dev/null +++ b/common/default.go @@ -0,0 +1,41 @@ +package common + +import ( + "fmt" + "github.com/fatih/structs" + "reflect" + "strconv" +) + +func ApplyDefaultValues(struct_ interface{}) (err error) { + o := structs.New(struct_) + + for _, field := range o.Fields() { + defaultValue := field.Tag("default") + if defaultValue == "" { + continue + } + var val interface{} + switch field.Kind() { + case reflect.String: + val = defaultValue + case reflect.Bool: + if defaultValue == "true" { + val = true + } else if defaultValue == "false" { + val = false + } else { + return fmt.Errorf("invalid bool expression: %v, use true/false", defaultValue) + } + case reflect.Int: + val, err = strconv.Atoi(defaultValue) + if err != nil { + return err + } + default: + val = field.Value() + } + field.Set(val) + } + return nil +} diff --git a/common/flags.go b/common/flags.go new file mode 100644 index 0000000..d4784f7 --- /dev/null +++ b/common/flags.go @@ -0,0 +1,171 @@ +package common + +import ( + "io/ioutil" + "log" + "os" + "reflect" + "strings" + + "github.com/codegangsta/cli" + "github.com/fatih/structs" + "github.com/yudai/hcl" + + "github.com/yudai/gotty/pkg/homedir" + "gopkg.in/yaml.v2" +) + +func GenerateFlags(options ...interface{}) (flags []cli.Flag, mappings map[string]string, err error) { + mappings = make(map[string]string) + + for _, struct_ := range options { + o := structs.New(struct_) + for _, field := range o.Fields() { + flagName := field.Tag("flagName") + if flagName == "" { + continue + } + envName := "CROC_" + strings.ToUpper(strings.Join(strings.Split(flagName, "-"), "_")) + mappings[flagName] = field.Name() + + flagShortName := field.Tag("flagSName") + if flagShortName != "" { + flagName += ", " + flagShortName + } + + flagDescription := field.Tag("flagDescribe") + + switch field.Kind() { + case reflect.String: + flags = append(flags, cli.StringFlag{ + Name: flagName, + Value: field.Value().(string), + Usage: flagDescription, + EnvVar: envName, + }) + case reflect.Bool: + flags = append(flags, cli.BoolFlag{ + Name: flagName, + Usage: flagDescription, + EnvVar: envName, + }) + case reflect.Int: + flags = append(flags, cli.IntFlag{ + Name: flagName, + Value: field.Value().(int), + Usage: flagDescription, + EnvVar: envName, + }) + } + } + } + + return +} + +func ApplyFlags( + flags []cli.Flag, + mappingHint map[string]string, + c *cli.Context, + options ...interface{}, +) { + objects := make([]*structs.Struct, len(options)) + for i, struct_ := range options { + objects[i] = structs.New(struct_) + } + + for flagName, fieldName := range mappingHint { + if !c.IsSet(flagName) { + continue + } + var field *structs.Field + var ok bool + for _, o := range objects { + field, ok = o.FieldOk(fieldName) + if ok { + break + } + } + if field == nil { + continue + } + var val interface{} + switch field.Kind() { + case reflect.String: + val = c.String(flagName) + case reflect.Bool: + val = c.Bool(flagName) + case reflect.Int: + val = c.Int(flagName) + } + field.Set(val) + } +} + +func ApplyConfigFile(filePath string, options ...interface{}) error { + filePath = homedir.Expand(filePath) + if _, err := os.Stat(filePath); os.IsNotExist(err) { + return err + } + + fileString := []byte{} + log.Printf("Loading config file at: %s", filePath) + fileString, err := ioutil.ReadFile(filePath) + if err != nil { + return err + } + + for _, object := range options { + if err := hcl.Decode(object, string(fileString)); err != nil { + return err + } + } + + return nil +} + +func ApplyConfigFileYaml(filePath string, options ...interface{}) error { + filePath = homedir.Expand(filePath) + if _, err := os.Stat(filePath); os.IsNotExist(err) { + return err + } + + fileString := []byte{} + log.Printf("Loading config file at: %s", filePath) + fileString, err := ioutil.ReadFile(filePath) + if err != nil { + return err + } + + for _, object := range options { + if err := yaml.Unmarshal(fileString, object); err != nil { + return err + } + + } + + return nil +} + +func SaveConfigFileYaml(filePath string, options ...interface{}) error { + filePath = homedir.Expand(filePath) + if _, err := os.Stat(filePath); os.IsNotExist(err) { + return err + } + + fd, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0644) + if err != nil { + return err + } + defer fd.Close() + + for _, object := range options { + if byteString, err := yaml.Marshal(object); err != nil { + return err + } else { + fd.Write(byteString) + } + } + + return nil +} diff --git a/connect.go b/connect.go index bfe0827..43196d3 100644 --- a/connect.go +++ b/connect.go @@ -53,18 +53,18 @@ const ( tmpTarGzFileName = "to_send.tmp.tar.gz" ) -func NewConnection(flags *Flags) (*Connection, error) { +func NewConnection(config *AppConfig) (*Connection, error) { c := new(Connection) - c.Debug = flags.Debug - c.DontEncrypt = flags.DontEncrypt - c.Wait = flags.Wait - c.Server = flags.Server - c.Code = flags.Code - c.NumberOfConnections = flags.NumberOfConnections - c.rate = flags.Rate - if len(flags.File) > 0 { + c.Debug = config.Debug + c.DontEncrypt = config.DontEncrypt + c.Wait = config.Wait + c.Server = config.Server + c.Code = config.Code + c.NumberOfConnections = config.NumberOfConnections + c.rate = config.Rate + if len(config.File) > 0 { // check wether the file is a dir - info, err := os.Stat(flags.File) + info, err := os.Stat(config.File) if err != nil { return c, err } @@ -73,23 +73,23 @@ func NewConnection(flags *Flags) (*Connection, error) { fmt.Println("compressing directory...") // we "tarify" the file - err = tarinator.Tarinate([]string{flags.File}, path.Base(flags.File)+".tar") + err = tarinator.Tarinate([]string{config.File}, path.Base(config.File)+".tar") if err != nil { return c, err } // now, we change the target file name to match the new archive created - flags.File = path.Base(flags.File) + ".tar" + config.File = path.Base(config.File) + ".tar" // we set the value IsDir to true c.File.IsDir = true } - c.File.Name = path.Base(flags.File) - c.File.Path = path.Dir(flags.File) + c.File.Name = path.Base(config.File) + c.File.Path = path.Dir(config.File) c.IsSender = true } else { c.IsSender = false - c.AskPath = flags.PathSpec - c.Path = flags.Path + c.AskPath = config.PathSpec + c.Path = config.Path } log.SetFormatter(&log.TextFormatter{}) diff --git a/help.go b/help.go new file mode 100644 index 0000000..5937b3f --- /dev/null +++ b/help.go @@ -0,0 +1,19 @@ +package main + +var helpTemplate = `NAME: + {{.Name}} - {{.Usage}} + +USAGE: + {{.Name}} [options] + +VERSION: + {{.Version}}{{if or .Author .Email}} + +AUTHOR:{{if .Author}} + {{.Author}}{{if .Email}} - <{{.Email}}>{{end}}{{else}} + {{.Email}}{{end}}{{end}} + +OPTIONS: + {{range .Flags}}{{.}} + {{end}} +` diff --git a/main.go b/main.go index 70428d1..7038da5 100644 --- a/main.go +++ b/main.go @@ -2,51 +2,80 @@ package main import ( "bufio" - "flag" "fmt" "os" "strings" + "github.com/codegangsta/cli" + "github.com/yudai/gotty/pkg/homedir" + "github.com/smileboywtu/croc/common" ) const BUFFERSIZE = 1024 -var oneGigabytePerSecond = 1000000 // expressed as kbps - -type Flags struct { - HideLogo bool - Relay bool - Debug bool - Wait bool - PathSpec bool - DontEncrypt bool - Server string - File string - Path string - Code string - Rate int - NumberOfConnections int +type AppConfig struct { + HideLogo bool `yaml:"hidelogo" flagName:"hidelogo" flagSName:"hl" flagDescribe:"Hidden logo" default:"false"` + Relay bool `yaml:"relay" flagName:"relay" flagSName:"r" flagDescribe:"Run as relay" default:"false"` + Debug bool `yaml:"debug" flagName:"debug" flagSName:"d" flagDescribe:"Debug mode" default:"false"` + Wait bool `yaml:"wait" flagName:"wait" flagSName:"w" flagDescribe:"Wait for code to be sent" default:"false"` + PathSpec bool `yaml:"ask-save" flagName:"ask-save" flagSName:"q" flagDescribe:"Ask for path to save to" default:"false"` + DontEncrypt bool `yaml:"no-encrypt" flagName:"no-encrypt" flagSName:"g" flagDescribe:"Turn off encryption" default:"false"` + Server string `yaml:"server" flagName:"server" flagSName:"l" flagDescribe:"Address of relay server" default:"cowyo.com"` + File string `yaml:"send" flagName:"send" flagSName:"s" flagDescribe:"File to send" default:""` + Path string `yaml:"save" flagName:"save" flagSName:"p" flagDescribe:"Path to save to" default:""` + Code string `yaml:"code" flagName:"code" flagSName:"c" flagDescribe:"Use your own code phrase" default:""` + Rate int `yaml:"rate" flagName:"rate" flagSName:"R" flagDescribe:"Throttle down to speed in kbps" default:"1000000"` + NumberOfConnections int `yaml:"threads" flagName:"threads" flagSName:"n" flagDescribe:"Number of threads to use" default:"4"` } +var email string +var author string var version string func main() { - flags := new(Flags) - flag.BoolVar(&flags.HideLogo, "hidelogo", false, "run as relay") - flag.BoolVar(&flags.Relay, "relay", false, "run as relay") - flag.BoolVar(&flags.Debug, "debug", false, "debug mode") - flag.BoolVar(&flags.Wait, "wait", false, "wait for code to be sent") - flag.BoolVar(&flags.PathSpec, "ask-save", false, "ask for path to save to") - flag.StringVar(&flags.Server, "server", "cowyo.com", "address of relay server") - flag.StringVar(&flags.File, "send", "", "file to send") - flag.StringVar(&flags.Path, "save", "", "path to save to") - flag.StringVar(&flags.Code, "code", "", "use your own code phrase") - flag.IntVar(&flags.Rate, "rate", oneGigabytePerSecond, "throttle down to speed in kbps") - flag.BoolVar(&flags.DontEncrypt, "no-encrypt", false, "turn off encryption") - flag.IntVar(&flags.NumberOfConnections, "threads", 4, "number of threads to use") - flag.Parse() - if !flags.HideLogo { - fmt.Println(` + app := cli.NewApp() + app.Name = "Croc" + app.Version = version + app.Author = author + app.Email = email + app.Usage = "send file by croc bridge" + app.HideHelp = true + + cli.AppHelpTemplate = helpTemplate + + appOptions := &AppConfig{} + if err := common.ApplyDefaultValues(appOptions); err != nil { + exit(err, 1) + } + + cliFlags, flagMappings, err := common.GenerateFlags(appOptions) + if err != nil { + exit(err, 3) + } + + app.Flags = append( + cliFlags, + cli.StringFlag{ + Name: "config", + Value: "~/.croc", + Usage: "Config file path", + EnvVar: "CROC_CONFIG", + }, + ) + + app.Action = func(c *cli.Context) { + + configFile := c.String("config") + _, err := os.Stat(homedir.Expand(configFile)) + if configFile != "~/.croc" || !os.IsNotExist(err) { + if err := common.ApplyConfigFileYaml(configFile, appOptions); err != nil { + exit(err, 2) + } + } + + common.ApplyFlags(cliFlags, flagMappings, c, appOptions) + if !appOptions.HideLogo { + fmt.Println(` ,_ >' ) croc version ` + fmt.Sprintf("%5s", version) + ` ( ( \ @@ -57,23 +86,26 @@ func main() { ...V^V^V^V^V^V^\............................... `) - } - fmt.Printf("croc version %s\n", version) + } + fmt.Printf("croc version %s\n", version) - if flags.Relay { - r := NewRelay(flags) - r.Run() - } else { - c, err := NewConnection(flags) - if err != nil { - fmt.Printf("Error! Please submit the following error to https://github.com/schollz/croc/issues:\n\n'%s'\n\n", err.Error()) - return - } - err = c.Run() - if err != nil { - fmt.Printf("Error! Please submit the following error to https://github.com/schollz/croc/issues:\n\n'%s'\n\n", err.Error()) + if appOptions.Relay { + r := NewRelay(appOptions) + r.Run() + } else { + c, err := NewConnection(appOptions) + if err != nil { + fmt.Printf("Error! Please submit the following error to https://github.com/schollz/croc/issues:\n\n'%s'\n\n", err.Error()) + return + } + err = c.Run() + if err != nil { + fmt.Printf("Error! Please submit the following error to https://github.com/schollz/croc/issues:\n\n'%s'\n\n", err.Error()) + } } } + + app.Run(os.Args) } func getInput(prompt string) string { @@ -82,3 +114,10 @@ func getInput(prompt string) string { text, _ := reader.ReadString('\n') return strings.TrimSpace(text) } + +func exit(err error, code int) { + if err != nil { + fmt.Println(err) + } + os.Exit(code) +} diff --git a/relay.go b/relay.go index 80e4a64..eae9e1d 100644 --- a/relay.go +++ b/relay.go @@ -42,9 +42,9 @@ type Relay struct { NumberOfConnections int } -func NewRelay(flags *Flags) *Relay { +func NewRelay(config *AppConfig) *Relay { r := &Relay{ - Debug: flags.Debug, + Debug: config.Debug, NumberOfConnections: MAX_NUMBER_THREADS, }