mirror of https://github.com/mvdan/fdroidcl.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
257 lines
6.0 KiB
257 lines
6.0 KiB
|
11 years ago
|
// Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc>
|
||
|
|
// See LICENSE for licensing information
|
||
|
11 years ago
|
|
||
|
|
package main
|
||
|
|
|
||
|
|
import (
|
||
|
11 years ago
|
"fmt"
|
||
|
11 years ago
|
"regexp"
|
||
|
11 years ago
|
"sort"
|
||
|
11 years ago
|
"strings"
|
||
|
10 years ago
|
"time"
|
||
|
11 years ago
|
|
||
|
8 years ago
|
"mvdan.cc/fdroidcl/adb"
|
||
|
7 years ago
|
"mvdan.cc/fdroidcl/fdroid"
|
||
|
11 years ago
|
)
|
||
|
|
|
||
|
|
var cmdSearch = &Command{
|
||
|
10 years ago
|
UsageLine: "search [<regexp...>]",
|
||
|
11 years ago
|
Short: "Search available apps",
|
||
|
11 years ago
|
}
|
||
|
|
|
||
|
7 years ago
|
var (
|
||
|
|
searchQuiet = cmdSearch.Fset.Bool("q", false, "Print package names only")
|
||
|
|
searchInstalled = cmdSearch.Fset.Bool("i", false, "Filter installed apps")
|
||
|
|
searchUpdates = cmdSearch.Fset.Bool("u", false, "Filter apps with updates")
|
||
|
|
searchDays = cmdSearch.Fset.Int("d", 0, "Select apps last updated in the last <n> days; a negative value drops them instead")
|
||
|
|
searchCategory = cmdSearch.Fset.String("c", "", "Filter apps by category")
|
||
|
|
searchSortBy = cmdSearch.Fset.String("o", "", "Sort order (added, updated)")
|
||
|
|
)
|
||
|
|
|
||
|
11 years ago
|
func init() {
|
||
|
|
cmdSearch.Run = runSearch
|
||
|
|
}
|
||
|
|
|
||
|
9 years ago
|
func runSearch(args []string) error {
|
||
|
7 years ago
|
if *searchInstalled && *searchUpdates {
|
||
|
9 years ago
|
return fmt.Errorf("-i is redundant if -u is specified")
|
||
|
11 years ago
|
}
|
||
|
7 years ago
|
sfunc, err := sortFunc(*searchSortBy)
|
||
|
11 years ago
|
if err != nil {
|
||
|
9 years ago
|
return err
|
||
|
|
}
|
||
|
|
apps, err := loadIndexes()
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
11 years ago
|
}
|
||
|
7 years ago
|
if len(apps) > 0 && *searchCategory != "" {
|
||
|
|
apps = filterAppsCategory(apps, *searchCategory)
|
||
|
10 years ago
|
if apps == nil {
|
||
|
7 years ago
|
return fmt.Errorf("no such category: %s", *searchCategory)
|
||
|
10 years ago
|
}
|
||
|
|
}
|
||
|
10 years ago
|
if len(apps) > 0 && len(args) > 0 {
|
||
|
10 years ago
|
apps = filterAppsSearch(apps, args)
|
||
|
|
}
|
||
|
10 years ago
|
var device *adb.Device
|
||
|
9 years ago
|
var inst map[string]adb.Package
|
||
|
7 years ago
|
if *searchInstalled || *searchUpdates {
|
||
|
9 years ago
|
if device, err = oneDevice(); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if inst, err = device.Installed(); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
10 years ago
|
}
|
||
|
7 years ago
|
if len(apps) > 0 && *searchInstalled {
|
||
|
9 years ago
|
apps = filterAppsInstalled(apps, inst)
|
||
|
11 years ago
|
}
|
||
|
7 years ago
|
if len(apps) > 0 && *searchUpdates {
|
||
|
9 years ago
|
apps = filterAppsUpdates(apps, inst, device)
|
||
|
11 years ago
|
}
|
||
|
7 years ago
|
if len(apps) > 0 && *searchDays != 0 {
|
||
|
|
apps = filterAppsLastUpdated(apps, *searchDays)
|
||
|
10 years ago
|
}
|
||
|
11 years ago
|
if sfunc != nil {
|
||
|
|
apps = sortApps(apps, sfunc)
|
||
|
|
}
|
||
|
7 years ago
|
if *searchQuiet {
|
||
|
11 years ago
|
for _, app := range apps {
|
||
|
8 years ago
|
fmt.Fprintln(stdout, app.PackageName)
|
||
|
11 years ago
|
}
|
||
|
|
} else {
|
||
|
9 years ago
|
printApps(apps, inst, device)
|
||
|
11 years ago
|
}
|
||
|
9 years ago
|
return nil
|
||
|
11 years ago
|
}
|
||
|
|
|
||
|
7 years ago
|
func filterAppsSearch(apps []fdroid.App, terms []string) []fdroid.App {
|
||
|
11 years ago
|
regexes := make([]*regexp.Regexp, len(terms))
|
||
|
|
for i, term := range terms {
|
||
|
|
regexes[i] = regexp.MustCompile(term)
|
||
|
11 years ago
|
}
|
||
|
7 years ago
|
var result []fdroid.App
|
||
|
11 years ago
|
for _, app := range apps {
|
||
|
|
fields := []string{
|
||
|
8 years ago
|
strings.ToLower(app.PackageName),
|
||
|
11 years ago
|
strings.ToLower(app.Name),
|
||
|
|
strings.ToLower(app.Summary),
|
||
|
8 years ago
|
strings.ToLower(app.Description),
|
||
|
11 years ago
|
}
|
||
|
11 years ago
|
if !appMatches(fields, regexes) {
|
||
|
11 years ago
|
continue
|
||
|
|
}
|
||
|
|
result = append(result, app)
|
||
|
|
}
|
||
|
|
return result
|
||
|
|
}
|
||
|
|
|
||
|
11 years ago
|
func appMatches(fields []string, regexes []*regexp.Regexp) bool {
|
||
|
11 years ago
|
fieldLoop:
|
||
|
|
for _, field := range fields {
|
||
|
11 years ago
|
for _, regex := range regexes {
|
||
|
|
if !regex.MatchString(field) {
|
||
|
11 years ago
|
continue fieldLoop
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
return false
|
||
|
|
}
|
||
|
11 years ago
|
|
||
|
7 years ago
|
func printApps(apps []fdroid.App, inst map[string]adb.Package, device *adb.Device) {
|
||
|
11 years ago
|
maxIDLen := 0
|
||
|
|
for _, app := range apps {
|
||
|
8 years ago
|
if len(app.PackageName) > maxIDLen {
|
||
|
|
maxIDLen = len(app.PackageName)
|
||
|
11 years ago
|
}
|
||
|
|
}
|
||
|
|
for _, app := range apps {
|
||
|
11 years ago
|
var pkg *adb.Package
|
||
|
8 years ago
|
p, e := inst[app.PackageName]
|
||
|
11 years ago
|
if e {
|
||
|
|
pkg = &p
|
||
|
|
}
|
||
|
10 years ago
|
printApp(app, maxIDLen, pkg, device)
|
||
|
11 years ago
|
}
|
||
|
|
}
|
||
|
|
|
||
|
7 years ago
|
func descVersion(app fdroid.App, inst *adb.Package, device *adb.Device) string {
|
||
|
8 years ago
|
if inst != nil {
|
||
|
10 years ago
|
suggested := app.SuggestedApk(device)
|
||
|
8 years ago
|
if suggested != nil && inst.VersCode < suggested.VersCode {
|
||
|
|
return fmt.Sprintf("%s (%d) -> %s (%d)", inst.VersName, inst.VersCode,
|
||
|
|
suggested.VersName, suggested.VersCode)
|
||
|
10 years ago
|
}
|
||
|
8 years ago
|
return fmt.Sprintf("%s (%d)", inst.VersName, inst.VersCode)
|
||
|
11 years ago
|
}
|
||
|
8 years ago
|
return fmt.Sprintf("%s (%d)", app.SugVersName, app.SugVersCode)
|
||
|
11 years ago
|
}
|
||
|
|
|
||
|
7 years ago
|
func printApp(app fdroid.App, IDLen int, inst *adb.Package, device *adb.Device) {
|
||
|
8 years ago
|
fmt.Fprintf(stdout, "%s%s %s - %s\n", app.PackageName, strings.Repeat(" ", IDLen-len(app.PackageName)),
|
||
|
10 years ago
|
app.Name, descVersion(app, inst, device))
|
||
|
8 years ago
|
fmt.Fprintf(stdout, " %s\n", app.Summary)
|
||
|
11 years ago
|
}
|
||
|
11 years ago
|
|
||
|
7 years ago
|
func filterAppsInstalled(apps []fdroid.App, inst map[string]adb.Package) []fdroid.App {
|
||
|
|
var result []fdroid.App
|
||
|
11 years ago
|
for _, app := range apps {
|
||
|
8 years ago
|
if _, e := inst[app.PackageName]; !e {
|
||
|
11 years ago
|
continue
|
||
|
|
}
|
||
|
|
result = append(result, app)
|
||
|
|
}
|
||
|
|
return result
|
||
|
|
}
|
||
|
11 years ago
|
|
||
|
7 years ago
|
func filterAppsUpdates(apps []fdroid.App, inst map[string]adb.Package, device *adb.Device) []fdroid.App {
|
||
|
|
var result []fdroid.App
|
||
|
11 years ago
|
for _, app := range apps {
|
||
|
8 years ago
|
p, e := inst[app.PackageName]
|
||
|
11 years ago
|
if !e {
|
||
|
|
continue
|
||
|
|
}
|
||
|
10 years ago
|
suggested := app.SuggestedApk(device)
|
||
|
|
if suggested == nil {
|
||
|
11 years ago
|
continue
|
||
|
|
}
|
||
|
8 years ago
|
if p.VersCode >= suggested.VersCode {
|
||
|
11 years ago
|
continue
|
||
|
|
}
|
||
|
|
result = append(result, app)
|
||
|
|
}
|
||
|
|
return result
|
||
|
|
}
|
||
|
11 years ago
|
|
||
|
7 years ago
|
func filterAppsLastUpdated(apps []fdroid.App, days int) []fdroid.App {
|
||
|
|
var result []fdroid.App
|
||
|
10 years ago
|
newer := true
|
||
|
|
if days < 0 {
|
||
|
|
days = -days
|
||
|
|
newer = false
|
||
|
|
}
|
||
|
10 years ago
|
date := time.Now().Truncate(24*time.Hour).AddDate(0, 0, 1-days)
|
||
|
10 years ago
|
for _, app := range apps {
|
||
|
|
if app.Updated.Before(date) == newer {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
result = append(result, app)
|
||
|
|
}
|
||
|
|
return result
|
||
|
|
}
|
||
|
|
|
||
|
11 years ago
|
func contains(l []string, s string) bool {
|
||
|
|
for _, s1 := range l {
|
||
|
|
if s1 == s {
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
7 years ago
|
func filterAppsCategory(apps []fdroid.App, categ string) []fdroid.App {
|
||
|
|
var result []fdroid.App
|
||
|
11 years ago
|
for _, app := range apps {
|
||
|
8 years ago
|
if !contains(app.Categories, categ) {
|
||
|
11 years ago
|
continue
|
||
|
|
}
|
||
|
|
result = append(result, app)
|
||
|
|
}
|
||
|
|
return result
|
||
|
|
}
|
||
|
|
|
||
|
7 years ago
|
func cmpAdded(a, b *fdroid.App) bool {
|
||
|
11 years ago
|
return a.Added.Before(b.Added.Time)
|
||
|
|
}
|
||
|
|
|
||
|
7 years ago
|
func cmpUpdated(a, b *fdroid.App) bool {
|
||
|
11 years ago
|
return a.Updated.Before(b.Updated.Time)
|
||
|
|
}
|
||
|
|
|
||
|
7 years ago
|
func sortFunc(sortBy string) (func(a, b *fdroid.App) bool, error) {
|
||
|
11 years ago
|
switch sortBy {
|
||
|
|
case "added":
|
||
|
|
return cmpAdded, nil
|
||
|
11 years ago
|
case "updated":
|
||
|
|
return cmpUpdated, nil
|
||
|
11 years ago
|
case "":
|
||
|
|
return nil, nil
|
||
|
|
}
|
||
|
9 years ago
|
return nil, fmt.Errorf("unknown sort order: %s", sortBy)
|
||
|
11 years ago
|
}
|
||
|
|
|
||
|
|
type appList struct {
|
||
|
7 years ago
|
l []fdroid.App
|
||
|
|
f func(a, b *fdroid.App) bool
|
||
|
11 years ago
|
}
|
||
|
|
|
||
|
|
func (al appList) Len() int { return len(al.l) }
|
||
|
|
func (al appList) Swap(i, j int) { al.l[i], al.l[j] = al.l[j], al.l[i] }
|
||
|
|
func (al appList) Less(i, j int) bool { return al.f(&al.l[i], &al.l[j]) }
|
||
|
|
|
||
|
7 years ago
|
func sortApps(apps []fdroid.App, f func(a, b *fdroid.App) bool) []fdroid.App {
|
||
|
11 years ago
|
sort.Sort(appList{l: apps, f: f})
|
||
|
|
return apps
|
||
|
|
}
|