package main import ( "fmt" "path/filepath" "strings" "github.com/AudriusButkevicius/cli" "github.com/syncthing/syncthing/lib/config" ) func init() { cliCommands = append(cliCommands, cli.Command{ Name: "folders", HideHelp: true, Usage: "Folder command group", Subcommands: []cli.Command{ { Name: "list", Usage: "List available folders", Requires: &cli.Requires{}, Action: foldersList, }, { Name: "add", Usage: "Add a new folder", Requires: &cli.Requires{"folder id", "directory"}, Action: foldersAdd, }, { Name: "remove", Usage: "Remove an existing folder", Requires: &cli.Requires{"folder id"}, Action: foldersRemove, }, { Name: "override", Usage: "Override changes from other nodes for a master folder", Requires: &cli.Requires{"folder id"}, Action: foldersOverride, }, { Name: "get", Usage: "Get a property of a folder", Requires: &cli.Requires{"folder id", "property"}, Action: foldersGet, }, { Name: "set", Usage: "Set a property of a folder", Requires: &cli.Requires{"folder id", "property", "value..."}, Action: foldersSet, }, { Name: "unset", Usage: "Unset a property of a folder", Requires: &cli.Requires{"folder id", "property"}, Action: foldersUnset, }, { Name: "devices", Usage: "Folder devices command group", HideHelp: true, Subcommands: []cli.Command{ { Name: "list", Usage: "List of devices which the folder is shared with", Requires: &cli.Requires{"folder id"}, Action: foldersDevicesList, }, { Name: "add", Usage: "Share a folder with a device", Requires: &cli.Requires{"folder id", "device id"}, Action: foldersDevicesAdd, }, { Name: "remove", Usage: "Unshare a folder with a device", Requires: &cli.Requires{"folder id", "device id"}, Action: foldersDevicesRemove, }, { Name: "clear", Usage: "Unshare a folder with all devices", Requires: &cli.Requires{"folder id"}, Action: foldersDevicesClear, }, }, }, }, }) } func foldersList(c *cli.Context) { cfg := getConfig(c) first := true writer := newTableWriter() for _, folder := range cfg.Folders { if !first { fmt.Fprintln(writer) } fmt.Fprintln(writer, "ID:\t", folder.ID, "\t") fmt.Fprintln(writer, "Path:\t", folder.RawPath, "\t(directory)") fmt.Fprintln(writer, "Folder type:\t", folder.Type, "\t(type)") fmt.Fprintln(writer, "Ignore permissions:\t", folder.IgnorePerms, "\t(permissions)") fmt.Fprintln(writer, "Rescan interval in seconds:\t", folder.RescanIntervalS, "\t(rescan)") if folder.Versioning.Type != "" { fmt.Fprintln(writer, "Versioning:\t", folder.Versioning.Type, "\t(versioning)") for key, value := range folder.Versioning.Params { fmt.Fprintf(writer, "Versioning %s:\t %s \t(versioning-%s)\n", key, value, key) } } first = false } writer.Flush() } func foldersAdd(c *cli.Context) { cfg := getConfig(c) abs, err := filepath.Abs(c.Args()[1]) die(err) folder := config.FolderConfiguration{ ID: c.Args()[0], RawPath: filepath.Clean(abs), } cfg.Folders = append(cfg.Folders, folder) setConfig(c, cfg) } func foldersRemove(c *cli.Context) { cfg := getConfig(c) rid := c.Args()[0] for i, folder := range cfg.Folders { if folder.ID == rid { last := len(cfg.Folders) - 1 cfg.Folders[i] = cfg.Folders[last] cfg.Folders = cfg.Folders[:last] setConfig(c, cfg) return } } die("Folder " + rid + " not found") } func foldersOverride(c *cli.Context) { cfg := getConfig(c) rid := c.Args()[0] for _, folder := range cfg.Folders { if folder.ID == rid && folder.Type == config.FolderTypeReadOnly { response := httpPost(c, "db/override", "") if response.StatusCode != 200 { err := fmt.Sprint("Failed to override changes\nStatus code: ", response.StatusCode) body := string(responseToBArray(response)) if body != "" { err += "\nBody: " + body } die(err) } return } } die("Folder " + rid + " not found or folder not master") } func foldersGet(c *cli.Context) { cfg := getConfig(c) rid := c.Args()[0] arg := strings.ToLower(c.Args()[1]) for _, folder := range cfg.Folders { if folder.ID != rid { continue } if strings.HasPrefix(arg, "versioning-") { arg = arg[11:] value, ok := folder.Versioning.Params[arg] if ok { fmt.Println(value) return } die("Versioning property " + c.Args()[1][11:] + " not found") } switch arg { case "directory": fmt.Println(folder.RawPath) case "type": fmt.Println(folder.Type) case "permissions": fmt.Println(folder.IgnorePerms) case "rescan": fmt.Println(folder.RescanIntervalS) case "versioning": if folder.Versioning.Type != "" { fmt.Println(folder.Versioning.Type) } default: die("Invalid property: " + c.Args()[1] + "\nAvailable properties: directory, type, permissions, versioning, versioning-") } return } die("Folder " + rid + " not found") } func foldersSet(c *cli.Context) { rid := c.Args()[0] arg := strings.ToLower(c.Args()[1]) val := strings.Join(c.Args()[2:], " ") cfg := getConfig(c) for i, folder := range cfg.Folders { if folder.ID != rid { continue } if strings.HasPrefix(arg, "versioning-") { cfg.Folders[i].Versioning.Params[arg[11:]] = val setConfig(c, cfg) return } switch arg { case "directory": cfg.Folders[i].RawPath = val case "type": var t config.FolderType if err := t.UnmarshalText([]byte(val)); err != nil { die("Invalid folder type: " + err.Error()) } cfg.Folders[i].Type = t case "permissions": cfg.Folders[i].IgnorePerms = parseBool(val) case "rescan": cfg.Folders[i].RescanIntervalS = parseInt(val) case "versioning": cfg.Folders[i].Versioning.Type = val default: die("Invalid property: " + c.Args()[1] + "\nAvailable properties: directory, master, permissions, versioning, versioning-") } setConfig(c, cfg) return } die("Folder " + rid + " not found") } func foldersUnset(c *cli.Context) { rid := c.Args()[0] arg := strings.ToLower(c.Args()[1]) cfg := getConfig(c) for i, folder := range cfg.Folders { if folder.ID != rid { continue } if strings.HasPrefix(arg, "versioning-") { arg = arg[11:] if _, ok := folder.Versioning.Params[arg]; ok { delete(cfg.Folders[i].Versioning.Params, arg) setConfig(c, cfg) return } die("Versioning property " + c.Args()[1][11:] + " not found") } switch arg { case "versioning": cfg.Folders[i].Versioning.Type = "" cfg.Folders[i].Versioning.Params = make(map[string]string) default: die("Invalid property: " + c.Args()[1] + "\nAvailable properties: versioning, versioning-") } setConfig(c, cfg) return } die("Folder " + rid + " not found") } func foldersDevicesList(c *cli.Context) { rid := c.Args()[0] cfg := getConfig(c) for _, folder := range cfg.Folders { if folder.ID != rid { continue } for _, device := range folder.Devices { fmt.Println(device.DeviceID) } return } die("Folder " + rid + " not found") } func foldersDevicesAdd(c *cli.Context) { rid := c.Args()[0] nid := parseDeviceID(c.Args()[1]) cfg := getConfig(c) for i, folder := range cfg.Folders { if folder.ID != rid { continue } for _, device := range folder.Devices { if device.DeviceID == nid { die("Device " + c.Args()[1] + " is already part of this folder") } } for _, device := range cfg.Devices { if device.DeviceID == nid { cfg.Folders[i].Devices = append(folder.Devices, config.FolderDeviceConfiguration{ DeviceID: device.DeviceID, }) setConfig(c, cfg) return } } die("Device " + c.Args()[1] + " not found in device list") } die("Folder " + rid + " not found") } func foldersDevicesRemove(c *cli.Context) { rid := c.Args()[0] nid := parseDeviceID(c.Args()[1]) cfg := getConfig(c) for ri, folder := range cfg.Folders { if folder.ID != rid { continue } for ni, device := range folder.Devices { if device.DeviceID == nid { last := len(folder.Devices) - 1 cfg.Folders[ri].Devices[ni] = folder.Devices[last] cfg.Folders[ri].Devices = cfg.Folders[ri].Devices[:last] setConfig(c, cfg) return } } die("Device " + c.Args()[1] + " not found") } die("Folder " + rid + " not found") } func foldersDevicesClear(c *cli.Context) { rid := c.Args()[0] cfg := getConfig(c) for i, folder := range cfg.Folders { if folder.ID != rid { continue } cfg.Folders[i].Devices = []config.FolderDeviceConfiguration{} setConfig(c, cfg) return } die("Folder " + rid + " not found") }