blob: b5fc2b4a319daffed2b119e1c0118feefdf7c6ad [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"regexp"
"v.io/x/lib/cmdline"
)
var cmdMadbName = &cmdline.Command{
Children: []*cmdline.Command{cmdMadbNameSet, cmdMadbNameUnset, cmdMadbNameList, cmdMadbNameClearAll},
Name: "name",
Short: "Manage device nicknames",
Long: `
Manages device nicknames, which are meant to be more human-friendly compared to
the device serials provided by adb tool.
NOTE: Device specifier flags (-d, -e, -n) are ignored in all 'madb name' commands.
`,
}
var cmdMadbNameSet = &cmdline.Command{
Runner: runnerFuncWithFilepath(runMadbNameSet),
Name: "set",
Short: "Set a nickname to be used in place of the device serial.",
Long: `
Sets a human-friendly nickname that can be used when specifying the device in
any madb commands.
The device serial can be obtain using the 'adb devices -l' command.
For example, consider the following example output:
HT4BVWV00023 device usb:3-3.4.2 product:volantisg model:Nexus_9 device:flounder_lte
The first value, 'HT4BVWV00023', is the device serial.
To assign a nickname for this device, run the following command:
madb name set HT4BVWV00023 MyTablet
and it will assign the 'MyTablet' nickname to the device serial 'HT4BVWV00023'.
The alternative device specifiers (e.g., 'usb:3-3.4.2', 'product:volantisg')
can also have nicknames.
When a nickname is set for a device serial, the nickname can be used to specify
the device within madb commands.
There can only be one nickname for a device serial.
When the 'madb name set' command is invoked with a device serial with an already
assigned nickname, the old one will be replaced with the newly provided one.
`,
ArgsName: "<device_serial> <nickname>",
ArgsLong: `
<device_serial> is a device serial (e.g., 'HT4BVWV00023') or an alternative device specifier (e.g., 'usb:3-3.4.2') obtained from 'adb devices -l' command
<nickname> is an alpha-numeric string with no special characters or spaces.
`,
}
func runMadbNameSet(env *cmdline.Env, args []string, filename string) error {
// Check if the arguments are valid.
if len(args) != 2 {
return env.UsageErrorf("There must be exactly two arguments.")
}
serial, nickname := args[0], args[1]
if !isValidDeviceSerial(serial) {
return env.UsageErrorf("Not a valid device serial: %v", serial)
}
if !isValidNickname(nickname) {
return env.UsageErrorf("Not a valid nickname: %v", nickname)
}
nsm, err := readNicknameSerialMap(filename)
if err != nil {
return err
}
// If the nickname is already in use, don't allow it at all.
if _, present := nsm[nickname]; present {
return fmt.Errorf("The provided nickname %q is already in use.", nickname)
}
// If the serial number already has an assigned nickname, delete it first.
// Need to do this check, because the nickname-serial map should be a one-to-one mapping.
if nn, present := reverseMap(nsm)[serial]; present {
delete(nsm, nn)
}
// Add the nickname serial mapping.
nsm[nickname] = serial
return writeNicknameSerialMap(nsm, filename)
}
var cmdMadbNameUnset = &cmdline.Command{
Runner: runnerFuncWithFilepath(runMadbNameUnset),
Name: "unset",
Short: "Unset a nickname set by the 'madb name set' command.",
Long: `
Unsets a nickname assigned by the 'madb name set' command. Either the device
serial or the assigned nickname can be specified to remove the mapping.
`,
ArgsName: "<device_serial | nickname>",
ArgsLong: `
There should be only one argument, which is either the device serial or the nickname.
`,
}
func runMadbNameUnset(env *cmdline.Env, args []string, filename string) error {
// Check if the arguments are valid.
if len(args) != 1 {
return env.UsageErrorf("There must be exactly one argument.")
}
name := args[0]
if !isValidDeviceSerial(name) && !isValidNickname(name) {
return env.UsageErrorf("Not a valid device serial or name: %v", name)
}
nsm, err := readNicknameSerialMap(filename)
if err != nil {
return err
}
found := false
for nickname, serial := range nsm {
if nickname == name || serial == name {
delete(nsm, nickname)
found = true
break
}
}
if !found {
return fmt.Errorf("The provided argument is neither a known nickname nor a device serial.")
}
return writeNicknameSerialMap(nsm, filename)
}
var cmdMadbNameList = &cmdline.Command{
Runner: runnerFuncWithFilepath(runMadbNameList),
Name: "list",
Short: "List all the existing nicknames.",
Long: `
Lists all the currently stored nicknames of device serials.
`,
}
func runMadbNameList(env *cmdline.Env, args []string, filename string) error {
nsm, err := readNicknameSerialMap(filename)
if err != nil {
return err
}
// TODO(youngseokyoon): pretty print this.
fmt.Println("Serial Nickname")
fmt.Println("========================")
for nickname, serial := range nsm {
fmt.Printf("%v\t%v\n", serial, nickname)
}
return nil
}
var cmdMadbNameClearAll = &cmdline.Command{
Runner: runnerFuncWithFilepath(runMadbNameClearAll),
Name: "clear-all",
Short: "Clear all the existing nicknames.",
Long: `
Clears all the currently stored nicknames of device serials.
`,
}
func runMadbNameClearAll(env *cmdline.Env, args []string, filename string) error {
return os.Remove(filename)
}
func getDefaultNameFilePath() (string, error) {
configDir, err := getConfigDir()
if err != nil {
return "", err
}
return filepath.Join(configDir, "nicknames"), nil
}
func isValidDeviceSerial(serial string) bool {
r := regexp.MustCompile(`^([A-Za-z0-9:\-\._]+|@\d+)$`)
return r.MatchString(serial)
}
func isValidNickname(nickname string) bool {
r := regexp.MustCompile(`^\w+$`)
return r.MatchString(nickname)
}
// reverseMap returns a new map which contains reversed key, value pairs in the original map.
// The source map is assumed to be a one-to-one mapping between keys and values.
func reverseMap(source map[string]string) map[string]string {
if source == nil {
return nil
}
reversed := make(map[string]string, len(source))
for k, v := range source {
reversed[v] = k
}
return reversed
}
// readNicknameSerialMap reads the provided file and reconstructs the nickname => serial map.
// The mapping is written one per each line, in the form of "<nickname> <serial>".
func readNicknameSerialMap(filename string) (map[string]string, error) {
result := make(map[string]string)
f, err := os.Open(filename)
if err != nil {
// Nickname file may not exist when there are no nicknames assigned, and it is not an error.
if os.IsNotExist(err) {
return result, nil
}
return nil, err
}
defer f.Close()
decoder := json.NewDecoder(f)
// Decoding might fail when the nickname file is somehow corrupted, or when the schema is updated.
// In such cases, move on after resetting the cache file instead of exiting the app.
if err := decoder.Decode(&result); err != nil {
fmt.Fprintf(os.Stderr, "WARNING: Could not decode the nickname file: %q. Resetting the file.\n", err)
if err := os.Remove(f.Name()); err != nil {
return nil, err
}
return make(map[string]string), nil
}
return result, nil
}
// writeNicknameSerialmap takes a nickname => serial map and writes it into the provided file name.
// The mapping is written one per each line, in the form of "<nickname> <serial>".
func writeNicknameSerialMap(nsm map[string]string, filename string) error {
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()
encoder := json.NewEncoder(f)
return encoder.Encode(nsm)
}
// runnerFuncWithFilepath is an adapter that turns the madb name subcommand functions into cmdline.Runners.
type runnerFuncWithFilepath func(*cmdline.Env, []string, string) error
// Run implements the cmdline.Runner interface by providing the default name file path
// as the third string argument of the underlying run function.
func (f runnerFuncWithFilepath) Run(env *cmdline.Env, args []string) error {
p, err := getDefaultNameFilePath()
if err != nil {
return err
}
return f(env, args, p)
}