diff --git a/adb/adb.go b/adb/adb.go index 4c4caaa..b7654cc 100644 --- a/adb/adb.go +++ b/adb/adb.go @@ -41,7 +41,7 @@ func Devices() ([]Device, error) { for scanner.Scan() { line := scanner.Text() m := deviceRegex.FindStringSubmatch(line) - if len(m) < 2 { + if m == nil { continue } device := Device{ @@ -58,13 +58,18 @@ func Devices() ([]Device, error) { return devices, nil } -func (d Device) AdbCommand(args ...string) *exec.Cmd { +func (d Device) AdbCmd(args ...string) *exec.Cmd { cmdArgs := append([]string{"-s", d.Id}, args...) return exec.Command("adb", cmdArgs...) } +func (d Device) AdbShell(args ...string) *exec.Cmd { + shellArgs := append([]string{"shell"}, args...) + return d.AdbCmd(shellArgs...) +} + func (d Device) Install(path string) error { - cmd := d.AdbCommand("install", path) + cmd := d.AdbCmd("install", path) if err := cmd.Start(); err != nil { return err } @@ -72,9 +77,33 @@ func (d Device) Install(path string) error { } func (d Device) Uninstall(pkg string) error { - cmd := d.AdbCommand("uninstall", pkg) + cmd := d.AdbCmd("uninstall", pkg) if err := cmd.Start(); err != nil { return err } return nil } + +var packageRegex = regexp.MustCompile(`^package:([^\s]+)`) + +func (d Device) Installed() ([]string, error) { + cmd := d.AdbShell("pm", "list", "packages") + stdout, err := cmd.StdoutPipe() + if err != nil { + return nil, err + } + if err := cmd.Start(); err != nil { + return nil, err + } + var ids []string + scanner := bufio.NewScanner(stdout) + for scanner.Scan() { + line := scanner.Text() + m := packageRegex.FindStringSubmatch(line) + if m == nil { + continue + } + ids = append(ids, m[1]) + } + return ids, nil +} diff --git a/cmd/fdroidcl/main.go b/cmd/fdroidcl/main.go index 908880c..b6616b4 100644 --- a/cmd/fdroidcl/main.go +++ b/cmd/fdroidcl/main.go @@ -45,6 +45,18 @@ func filterAppsSearch(apps *map[string]fdroidcl.App, terms []string) { } } +func filterAppsInstalled(apps *map[string]fdroidcl.App, installed []string) { + instMap := make(map[string]struct{}, len(installed)) + for _, id := range installed { + instMap[id] = struct{}{} + } + for appID := range *apps { + if _, e := instMap[appID]; !e { + delete(*apps, appID) + } + } +} + type appList []fdroidcl.App func (al appList) Len() int { return len(al) } @@ -83,6 +95,28 @@ func mustLoadApps(repoName string) map[string]fdroidcl.App { return apps } +func mustInstalled(device adb.Device) []string { + installed, err := device.Installed() + if err != nil { + log.Fatalf("Could not get installed packages: %v", err) + } + return installed +} + +func oneDevice() adb.Device { + devices, err := adb.Devices() + if err != nil { + log.Fatalf("Could not get devices: %v", err) + } + if len(devices) == 0 { + log.Fatalf("No devices found") + } + if len(devices) > 1 { + log.Fatalf("Too many devices found") + } + return devices[0] +} + func main() { flag.Parse() if flag.NArg() == 0 { @@ -126,11 +160,19 @@ func main() { case "devices": devices, err := adb.Devices() if err != nil { - log.Fatalf("Could not list devices: %v", err) + log.Fatalf("Could not get devices: %v", err) } for _, device := range devices { fmt.Printf("%s - %s (%s)\n", device.Id, device.Model, device.Product) } + case "installed": + apps := mustLoadApps(repoName) + device := oneDevice() + installed := mustInstalled(device) + filterAppsInstalled(&apps, installed) + for _, app := range sortedApps(apps) { + app.WriteShort(os.Stdout) + } default: log.Printf("Unrecognised command '%s'\n\n", cmd) flag.Usage()