first commit
commit
21054959b0
@ -0,0 +1,408 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
_ "github.com/hanwen/go-fuse/fuse"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
)
|
||||
|
||||
var globalDB *sql.DB
|
||||
var globalOpts Options
|
||||
|
||||
const orderby = "-frequency, -mark, CASE WHEN updated_at IS NULL THEN created_at ELSE updated_at END DESC"
|
||||
|
||||
func cmdShow(db *sql.DB, opts Options) bool {
|
||||
if len(opts.IDs) == 0 && len(opts.Aliases) == 0 {
|
||||
opts.IDs = append(opts.IDs, int64(getLastAttrID(db)))
|
||||
}
|
||||
|
||||
for _, id := range opts.IDs {
|
||||
attr := findAttributeByID(db, id)
|
||||
//fmt.Printf(attr.GetValue())
|
||||
printToLess(attr.GetValue())
|
||||
}
|
||||
|
||||
for _, alias := range opts.Aliases {
|
||||
attr := findAttributeByAlias(db, alias)
|
||||
//fmt.Printf(attr.GetValue())
|
||||
printToLess(attr.GetValue())
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func cmdCat(db *sql.DB, opts Options) bool {
|
||||
if len(opts.IDs) == 0 && len(opts.Aliases) == 0 {
|
||||
opts.IDs = append(opts.IDs, int64(getLastAttrID(db)))
|
||||
}
|
||||
|
||||
for _, id := range opts.IDs {
|
||||
attr := findAttributeByID(db, id)
|
||||
fmt.Printf(attr.GetValue())
|
||||
}
|
||||
|
||||
for _, alias := range opts.Aliases {
|
||||
attr := findAttributeByAlias(db, alias)
|
||||
fmt.Printf(attr.GetValue())
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func cmdMount(db *sql.DB, opts Options) bool {
|
||||
// check if opts.MountPoint directory exists, if it exists do nothing, if it doesn't create it and continue
|
||||
globalDB = db
|
||||
globalOpts = opts
|
||||
globalOpts.Offset = 0
|
||||
globalOpts.Limit = 40
|
||||
Mount(opts.MountPoint)
|
||||
return true
|
||||
}
|
||||
|
||||
func cmdAddFiles(db *sql.DB, files []string) bool {
|
||||
tx, err := db.Begin()
|
||||
|
||||
// stmt, err := tx.Prepare("INSERT INTO attributes (name, pwd, value_text, value_blob) VALUES (?, ?, ?, ?)")
|
||||
stmt, err := tx.Prepare("INSERT INTO attributes (name, value_text, value_blob) VALUES (?, ?, ?)")
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
defer stmt.Close()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
content, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fileAbsPath, err := filepath.Abs(file)
|
||||
// fileRelPath, err := filepath.Rel(pwd, fileAbsPath)
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
//_, err = stmt.Exec("file", pwd, fileRelPath, content)
|
||||
_, err = stmt.Exec("file", fileAbsPath, content)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
tx.Commit()
|
||||
return true
|
||||
}
|
||||
|
||||
func cmdLs(db *sql.DB, w *tabwriter.Writer, opts Options) bool {
|
||||
attrs := listWithFilters(db, opts)
|
||||
for _, attr := range attrs {
|
||||
if opts.ListFilepaths {
|
||||
fmt.Println(attr.Filepath())
|
||||
} else {
|
||||
attr.Print(w, opts.Recursive, opts.Indent, opts.Filters, opts.AfterLinesCount)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func cmdNew(db *sql.DB, opts Options) bool {
|
||||
|
||||
var value_text string
|
||||
|
||||
if opts.FromStdin {
|
||||
lines := make([]string, 0, 0)
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
|
||||
go func() {
|
||||
for _ = range c {
|
||||
// CTRL-c
|
||||
// Do nothing, this will continue executing the rest of the code
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
line, _, err := reader.ReadLine()
|
||||
if err != nil {
|
||||
// EOF
|
||||
break
|
||||
}
|
||||
lines = append(lines, string(line))
|
||||
log.Printf("%s\n", prettyAttr("eton", string(line)))
|
||||
}
|
||||
value_text = strings.Join(lines, "\n")
|
||||
} else if len(opts.Note) > 0 {
|
||||
value_text = opts.Note
|
||||
} else {
|
||||
f, err := ioutil.TempFile("", "eton-edit")
|
||||
check(err)
|
||||
|
||||
openEditor(f.Name())
|
||||
value_text = readFile(f.Name())
|
||||
}
|
||||
|
||||
saveString(db, value_text)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func cmdAdd(db *sql.DB, id int, attrs []string) bool {
|
||||
// TODO
|
||||
return false
|
||||
}
|
||||
|
||||
func cmdAddAttr(db *sql.DB, id int, attrs []string) bool {
|
||||
var stmt *sql.Stmt
|
||||
|
||||
tx, err := db.Begin()
|
||||
|
||||
if id == -1 {
|
||||
stmt, err = tx.Prepare("INSERT INTO attributes (name, value_text) VALUES (?, ?)")
|
||||
} else {
|
||||
stmt, err = tx.Prepare("INSERT INTO attributes (name, value_text, parent_id) VALUES (?, ?, ?)")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
defer stmt.Close()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, attr := range attrs {
|
||||
name := ""
|
||||
value := ""
|
||||
|
||||
nameValuePair := strings.SplitN(attr, ":", 2)
|
||||
|
||||
switch len(nameValuePair) {
|
||||
case 1:
|
||||
value = nameValuePair[0]
|
||||
case 2:
|
||||
name = nameValuePair[0]
|
||||
value = nameValuePair[1]
|
||||
}
|
||||
|
||||
if id == -1 {
|
||||
_, err = stmt.Exec(name, value)
|
||||
} else {
|
||||
_, err = stmt.Exec(name, value, id)
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
tx.Commit()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func cmdUnalias(db *sql.DB, opts Options) bool {
|
||||
attr := findAttributeByAlias(db, opts.Alias)
|
||||
if attr.GetID() == -1 {
|
||||
log.Fatalf("alias \"%s\" not found", opts.Alias)
|
||||
} else {
|
||||
attr.SetAlias(db, "")
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func cmdAlias(db *sql.DB, opts Options) bool {
|
||||
if !(opts.ID > 0 && len(opts.Alias1) > 0 || len(opts.Alias2) > 0) && !(len(opts.Alias1) > 0 && len(opts.Alias2) > 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
var attr Attr
|
||||
|
||||
if opts.ID > 0 {
|
||||
attr = findAttributeByID(db, opts.ID)
|
||||
if len(opts.Alias1) > 0 {
|
||||
attr.SetAlias(db, opts.Alias1)
|
||||
} else if len(opts.Alias2) > 0 {
|
||||
attr.SetAlias(db, opts.Alias2)
|
||||
}
|
||||
} else if len(opts.Alias1) > 0 && len(opts.Alias2) > 0 {
|
||||
attr1 := findAttributeByAlias(db, opts.Alias1)
|
||||
attr2 := findAttributeByAlias(db, opts.Alias2)
|
||||
|
||||
if attr1.GetID() > 0 && attr2.GetID() <= 0 {
|
||||
attr1.SetAlias(db, opts.Alias2)
|
||||
} else if attr1.GetID() <= 0 && attr2.GetID() > 0 {
|
||||
attr2.SetAlias(db, opts.Alias1)
|
||||
} else {
|
||||
log.Println("not changing anything", attr1.GetID(), attr2.GetID())
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func cmdEdit(db *sql.DB, opts Options) bool {
|
||||
var totalUpdated int64
|
||||
|
||||
if len(opts.IDs) == 0 && len(opts.Aliases) == 0 {
|
||||
opts.IDs = append(opts.IDs, int64(getLastAttrID(db)))
|
||||
}
|
||||
|
||||
for _, id := range opts.IDs {
|
||||
attr := findAttributeByID(db, id)
|
||||
totalUpdated += attr.Edit(db)
|
||||
}
|
||||
|
||||
for _, alias := range opts.Aliases {
|
||||
attr := findAttributeByAlias(db, alias)
|
||||
totalUpdated += attr.Edit(db)
|
||||
}
|
||||
|
||||
if totalUpdated > 0 {
|
||||
fmt.Println(totalUpdated, "records updated")
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func cmdRm(db *sql.DB, opts Options) bool {
|
||||
|
||||
var totalUpdated int64
|
||||
|
||||
for _, id := range opts.IDs {
|
||||
attr := findAttributeByID(db, id)
|
||||
totalUpdated += attr.Rm(db)
|
||||
}
|
||||
|
||||
for _, alias := range opts.Aliases {
|
||||
attr := findAttributeByAlias(db, alias)
|
||||
totalUpdated += attr.Rm(db)
|
||||
}
|
||||
|
||||
if totalUpdated > 0 {
|
||||
fmt.Println(totalUpdated, "deleted")
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func cmdUnrm(db *sql.DB, opts Options) bool {
|
||||
var totalUpdated int64
|
||||
|
||||
for _, id := range opts.IDs {
|
||||
attr := findAttributeByID(db, id)
|
||||
totalUpdated += attr.Unrm(db)
|
||||
}
|
||||
|
||||
for _, alias := range opts.Aliases {
|
||||
attr := findAttributeByAlias(db, alias)
|
||||
totalUpdated += attr.Unrm(db)
|
||||
}
|
||||
|
||||
if totalUpdated > 0 {
|
||||
fmt.Println(totalUpdated, "recovered")
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func cmdInit(db *sql.DB) bool {
|
||||
InitializeDatabase(db)
|
||||
return true
|
||||
}
|
||||
|
||||
func cmdMark(db *sql.DB, opts Options) bool {
|
||||
var totalUpdated int64
|
||||
for _, id := range opts.IDs {
|
||||
attr := findAttributeByID(db, id)
|
||||
totalUpdated += attr.SetMark(db, 1)
|
||||
}
|
||||
|
||||
for _, alias := range opts.Aliases {
|
||||
attr := findAttributeByAlias(db, alias)
|
||||
totalUpdated += attr.SetMark(db, 1)
|
||||
}
|
||||
|
||||
fmt.Println(totalUpdated, "marked")
|
||||
return true
|
||||
}
|
||||
|
||||
func cmdUnmark(db *sql.DB, opts Options) bool {
|
||||
var totalUpdated int64
|
||||
for _, id := range opts.IDs {
|
||||
attr := findAttributeByID(db, id)
|
||||
totalUpdated += attr.SetMark(db, 0)
|
||||
}
|
||||
|
||||
for _, alias := range opts.Aliases {
|
||||
attr := findAttributeByAlias(db, alias)
|
||||
totalUpdated += attr.SetMark(db, 0)
|
||||
}
|
||||
|
||||
fmt.Println(totalUpdated, "marked")
|
||||
return true
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
func openEditor(filepath string) {
|
||||
cmd := exec.Command("/usr/bin/env", "vim", filepath)
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Run()
|
||||
}
|
||||
|
||||
func readFile(filepath string) string {
|
||||
data, err := ioutil.ReadFile(filepath)
|
||||
check(err)
|
||||
return string(data)
|
||||
}
|
||||
|
||||
func check(e error) {
|
||||
if e != nil {
|
||||
// log.Fatal(e)
|
||||
panic(e)
|
||||
}
|
||||
}
|
||||
|
||||
func printToLess(text string) {
|
||||
// declare your pager
|
||||
cmd := exec.Command("/usr/bin/env", "less")
|
||||
// create a pipe (blocking)
|
||||
r, stdin := io.Pipe()
|
||||
// Set your i/o's
|
||||
cmd.Stdin = r
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
// Create a blocking chan, Run the pager and unblock once it is finished
|
||||
c := make(chan struct{})
|
||||
go func() {
|
||||
defer close(c)
|
||||
cmd.Run()
|
||||
}()
|
||||
|
||||
// Pass anything to your pipe
|
||||
fmt.Fprintf(stdin, text)
|
||||
|
||||
// Close stdin (result in pager to exit)
|
||||
stdin.Close()
|
||||
|
||||
// Wait for the pager to be finished
|
||||
<-c
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/hanwen/go-fuse/fuse"
|
||||
"github.com/hanwen/go-fuse/fuse/nodefs"
|
||||
"github.com/hanwen/go-fuse/fuse/pathfs"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
//"path/filepath"
|
||||
)
|
||||
|
||||
type HelloFs struct {
|
||||
pathfs.FileSystem
|
||||
}
|
||||
|
||||
func (me *HelloFs) GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse.Status) {
|
||||
log.Printf("GetAttr for %v\n", name)
|
||||
switch name {
|
||||
case "":
|
||||
return &fuse.Attr{
|
||||
Mode: fuse.S_IFDIR | 0755,
|
||||
}, fuse.OK
|
||||
default:
|
||||
attr := findAttributeByAliasOrID(globalDB, name)
|
||||
size := 0
|
||||
if attr.GetID() > 0 {
|
||||
size = len(attr.GetValue())
|
||||
}
|
||||
return &fuse.Attr{
|
||||
Mode: fuse.S_IFREG | 0644,
|
||||
Size: uint64(size),
|
||||
}, fuse.OK
|
||||
}
|
||||
return nil, fuse.ENOENT
|
||||
}
|
||||
|
||||
func (me *HelloFs) OpenDir(name string, context *fuse.Context) (c []fuse.DirEntry, code fuse.Status) {
|
||||
log.Printf("OpenDir for %v\n", name)
|
||||
if name == "" {
|
||||
//c = []fuse.DirEntry{{Name: "file.txt", Mode: fuse.S_IFREG}}
|
||||
attrs := listWithFilters(globalDB, globalOpts)
|
||||
c = make([]fuse.DirEntry, len(attrs), len(attrs))
|
||||
|
||||
for i, attr := range attrs {
|
||||
var d fuse.DirEntry
|
||||
d.Name = attr.GetIdentifier()
|
||||
//log.Println(d.Name)
|
||||
d.Mode = fuse.S_IFREG | 0644
|
||||
c[i] = fuse.DirEntry(d)
|
||||
//c = append(c, )
|
||||
}
|
||||
|
||||
return c, fuse.OK
|
||||
}
|
||||
return nil, fuse.ENOENT
|
||||
}
|
||||
|
||||
func (me *HelloFs) Open(name string, flags uint32, context *fuse.Context) (file nodefs.File, code fuse.Status) {
|
||||
log.Printf("Open for %v\n", name)
|
||||
if flags&fuse.O_ANYWRITE != 0 {
|
||||
return nil, fuse.EPERM
|
||||
}
|
||||
attr := findAttributeByAliasOrID(globalDB, name)
|
||||
if attr.GetID() <= 0 {
|
||||
return nil, fuse.ENOENT
|
||||
}
|
||||
bytes := []byte(attr.GetValue())
|
||||
return nodefs.NewDataFile(bytes), fuse.OK
|
||||
}
|
||||
|
||||
func Mount(mountpoint string) {
|
||||
|
||||
log.Println("NOTE: This is just an experiment")
|
||||
log.Println("mounting on", mountpoint)
|
||||
if _, err := os.Stat(mountpoint); err == nil {
|
||||
log.Println("directory exists:", mountpoint)
|
||||
log.Println("move directory and try again")
|
||||
return
|
||||
} else {
|
||||
os.Mkdir(mountpoint, os.ModeDir)
|
||||
}
|
||||
|
||||
nfs := pathfs.NewPathNodeFs(&HelloFs{FileSystem: pathfs.NewDefaultFileSystem()}, nil)
|
||||
server, _, err := nodefs.MountRoot(mountpoint, nfs.Root(), nil)
|
||||
check(err)
|
||||
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
|
||||
defer func() {
|
||||
server.Unmount()
|
||||
//mountpointAbsolutepath, _ := filepath.Abs(mountpoint)
|
||||
os.Remove(mountpoint)
|
||||
log.Println("Removed mount point", mountpoint)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
for _ = range c {
|
||||
// CTRL-c
|
||||
// Do nothing, this will continue executing the rest of the code
|
||||
server.Unmount()
|
||||
}
|
||||
}()
|
||||
|
||||
server.Serve()
|
||||
}
|
@ -0,0 +1,140 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"database/sql"
|
||||
"github.com/docopt/docopt-go"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"log"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"text/tabwriter"
|
||||
)
|
||||
|
||||
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||
var pwd string
|
||||
|
||||
const dbfilename string = ".etondb"
|
||||
|
||||
const usage string = `Usage:
|
||||
eton new [-|<note>]
|
||||
eton (ls|grep) [<filters>...] [--offset OFFSET] [--limit LIMIT] [--removed-only] [--short] [--all] [--exec COMMAND] [--edit] [--list-files] [--after AFTER]
|
||||
eton edit [<ids>...]
|
||||
eton alias <id1> <id2>
|
||||
eton unalias <alias>
|
||||
eton mark <ids>...
|
||||
eton unmark <ids>...
|
||||
eton cat [<ids>...]
|
||||
eton show [<ids>...]
|
||||
eton (rm|remove) <ids>...
|
||||
eton (unrm|unremove|recover) <ids>...
|
||||
eton addfile (-|<file>...)
|
||||
eton mount [<mountpoint>]
|
||||
|
||||
Options:
|
||||
-A, --after AFTER lines to print after a match [default: 0]
|
||||
-o, --offset OFFSET offset for the items listed [default: 0]
|
||||
-L, --limit LIMIT maximum number of rows returned, pass -Lall to list everything [default: 10]
|
||||
-r, --recursive recursive mode
|
||||
-l, --list-files list items as filenames
|
||||
-s, --short short mode lists rows with aliases only
|
||||
`
|
||||
|
||||
func main() {
|
||||
args, _ := docopt.Parse(usage, nil, true, "version 0.0.0", false, true)
|
||||
|
||||
opts := OptionsFromArgs(args)
|
||||
|
||||
//pwd, _ = os.Getwd()
|
||||
|
||||
dbfile := filepath.Join(homeDirectory(), dbfilename)
|
||||
var db *sql.DB
|
||||
|
||||
dbfileExists := false
|
||||
|
||||
if _, err := os.Stat(dbfile); err == nil {
|
||||
dbfileExists = true
|
||||
}
|
||||
|
||||
//if dbfileExists || args["init"].(bool) {
|
||||
if true {
|
||||
var err error
|
||||
db, err = sql.Open("sqlite3", dbfile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
} else {
|
||||
log.Fatal(`database file not found, use "init" command`)
|
||||
}
|
||||
|
||||
if !dbfileExists {
|
||||
cmdInit(db)
|
||||
}
|
||||
|
||||
w := new(tabwriter.Writer)
|
||||
w.Init(os.Stdout, 0, 0, 2, ' ', 0)
|
||||
|
||||
switch true {
|
||||
// case args["init"].(bool):
|
||||
// if dbfileExists {
|
||||
// log.Fatal("database already exists, command ignored.")
|
||||
// }
|
||||
// cmdInit(db)
|
||||
case args["mount"].(bool):
|
||||
cmdMount(db, opts)
|
||||
case args["new"].(bool):
|
||||
cmdNew(db, opts)
|
||||
case args["addfile"].(bool):
|
||||
if len(args["<file>"].([]string)) > 0 {
|
||||
cmdAddFiles(db, args["<file>"].([]string))
|
||||
} else {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
for {
|
||||
line, _, err := reader.ReadLine()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
sline := string(line)
|
||||
cmdAddFiles(db, []string{sline})
|
||||
}
|
||||
}
|
||||
case args["ls"].(bool) || args["grep"].(bool):
|
||||
cmdLs(db, w, opts)
|
||||
case args["cat"].(bool):
|
||||
cmdCat(db, opts)
|
||||
case args["show"].(bool):
|
||||
cmdShow(db, opts)
|
||||
case args["rm"].(bool) || args["remove"].(bool):
|
||||
cmdRm(db, opts)
|
||||
case args["unrm"].(bool) || args["unremove"].(bool) || args["recover"].(bool):
|
||||
cmdUnrm(db, opts)
|
||||
case args["edit"].(bool):
|
||||
cmdEdit(db, opts)
|
||||
case args["mark"].(bool):
|
||||
cmdMark(db, opts)
|
||||
case args["unmark"].(bool):
|
||||
cmdUnmark(db, opts)
|
||||
case args["alias"].(bool):
|
||||
cmdAlias(db, opts)
|
||||
case args["unalias"].(bool):
|
||||
cmdUnalias(db, opts)
|
||||
case args["addattr"].(bool):
|
||||
id, _ := strconv.Atoi(args["<id>"].(string))
|
||||
cmdAddAttr(db, id, args["<filters>"].([]string))
|
||||
default:
|
||||
log.Println("Never reached")
|
||||
}
|
||||
|
||||
//w.Flush()
|
||||
}
|
||||
|
||||
func randSeq(n int) string {
|
||||
b := make([]rune, n)
|
||||
for i := range b {
|
||||
b[i] = letters[rand.Intn(len(letters))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
@ -0,0 +1,749 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"github.com/andrew-d/go-termutil"
|
||||
"github.com/mgutz/ansi"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Attr holds the data fetched from a row
|
||||
// Only 1 ValueXxx field should have value, the others should be nil
|
||||
type Attr struct {
|
||||
// Meta
|
||||
ID sql.NullInt64
|
||||
ParentID sql.NullInt64
|
||||
Name sql.NullString
|
||||
Alias sql.NullString
|
||||
Path sql.NullString
|
||||
Frequency sql.NullInt64
|
||||
Mark sql.NullInt64
|
||||
|
||||
// Values
|
||||
ValueText sql.NullString
|
||||
ValueBlob []byte
|
||||
ValueInt sql.NullInt64
|
||||
ValueReal sql.NullFloat64
|
||||
ValueTime time.Time
|
||||
|
||||
// Timestamps
|
||||
CreatedAt NullTime
|
||||
UpdatedAt NullTime
|
||||
AccessedAt NullTime
|
||||
DeletedAt NullTime
|
||||
}
|
||||
|
||||
const sqlSelect = "id, value_text, name, parent_id, alias, mark, value_blob, created_at, updated_at"
|
||||
|
||||
// GetID returns the int64 value of attr's ID.
|
||||
func (attr Attr) GetID() int64 {
|
||||
//var err error
|
||||
if value, err := attr.ID.Value(); err == nil && value != nil {
|
||||
return value.(int64)
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (attr Attr) GetCreatedAt() (t time.Time) {
|
||||
//var err error
|
||||
if value, err := attr.CreatedAt.Value(); err == nil && value != nil {
|
||||
t = value.(time.Time)
|
||||
return
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func (attr Attr) GetUpdatedAt() (t time.Time) {
|
||||
//var err error
|
||||
if value, err := attr.UpdatedAt.Value(); err == nil && value != nil {
|
||||
t = value.(time.Time)
|
||||
return
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func (attr Attr) GetAccessedAt() (t time.Time) {
|
||||
//var err error
|
||||
if value, err := attr.AccessedAt.Value(); err == nil && value != nil {
|
||||
t = value.(time.Time)
|
||||
return
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func (attr Attr) GetDeletedAt() (t time.Time) {
|
||||
//var err error
|
||||
if value, err := attr.DeletedAt.Value(); err == nil && value != nil {
|
||||
t = value.(time.Time)
|
||||
return
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// GetIDString returns the string value of attr's ID.
|
||||
func (attr Attr) GetIDString() string {
|
||||
var err error
|
||||
if value, err := attr.ID.Value(); err == nil && value != nil {
|
||||
return strconv.Itoa(int(value.(int64)))
|
||||
}
|
||||
log.Fatal("Attr is not loaded, has no id")
|
||||
check(err)
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetMark returns the int value of attr's mark
|
||||
func (attr Attr) GetMark() int {
|
||||
var err error
|
||||
if value, err := attr.Mark.Value(); err == nil && value != nil {
|
||||
return int(value.(int64))
|
||||
}
|
||||
log.Fatal("Mark is not loaded, has no 'mark'")
|
||||
check(err)
|
||||
return 0
|
||||
}
|
||||
|
||||
// GetIdentifier returns attr's ID, or its Alias if it is not nil.
|
||||
func (attr Attr) GetIdentifier() string {
|
||||
alias := attr.GetAlias()
|
||||
if len(alias) > 0 {
|
||||
return alias
|
||||
} else {
|
||||
return attr.GetIDString()
|
||||
}
|
||||
}
|
||||
|
||||
// GetName is a helper to get attr's Name as string
|
||||
func (attr Attr) GetName() string {
|
||||
var err error
|
||||
|
||||
if value, err := attr.Name.Value(); err == nil && value != nil {
|
||||
return value.(string)
|
||||
}
|
||||
log.Fatal("Attr is not loaded, has no name")
|
||||
check(err)
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetName is a helper to get attr's Name as string
|
||||
func (attr Attr) GetAlias() string {
|
||||
var err error
|
||||
|
||||
if value, err := attr.Alias.Value(); err == nil && value != nil {
|
||||
return value.(string)
|
||||
}
|
||||
check(err)
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetTextValue returns a string representation of attr's value, whatever type it is
|
||||
func (attr Attr) GetTextValue() string {
|
||||
var err error
|
||||
if value, err := attr.ValueText.Value(); err == nil && value != nil {
|
||||
return value.(string)
|
||||
}
|
||||
check(err)
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetValue returns a string representation of attr's value, in order of
|
||||
// preference: first ValueBlob, then ValueText, then ValueInt, then ValueReal
|
||||
func (attr Attr) GetValue() string {
|
||||
var err error
|
||||
|
||||
// if ValueBlov exists
|
||||
if len(attr.ValueBlob) > 0 {
|
||||
return string(attr.ValueBlob)
|
||||
}
|
||||
|
||||
if value, err := attr.ValueText.Value(); err == nil && value != nil {
|
||||
return value.(string)
|
||||
}
|
||||
check(err)
|
||||
|
||||
if value, err := attr.ValueInt.Value(); err == nil && value != nil {
|
||||
return strconv.Itoa(value.(int))
|
||||
}
|
||||
check(err)
|
||||
|
||||
if value, err := attr.ValueReal.Value(); err == nil && value != nil {
|
||||
return strconv.FormatFloat(value.(float64), 'f', 2, 32)
|
||||
}
|
||||
check(err)
|
||||
|
||||
log.Fatal("Attr is not loaded, has no value")
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// Print pretty-prints attr's field values.
|
||||
func (attr Attr) Print(w *tabwriter.Writer, verbose bool, indent int, highlighteds []string, after int) {
|
||||
debug := false
|
||||
|
||||
if debug {
|
||||
if value, err := attr.ParentID.Value(); err == nil && value != nil {
|
||||
fmt.Fprintf(w, "%s:%d\t", "ParentID", value)
|
||||
} else {
|
||||
fmt.Fprintf(w, "%s:%s\t", "ParentID", novalue)
|
||||
}
|
||||
|
||||
if value, err := attr.Name.Value(); err == nil && value != nil {
|
||||
fmt.Fprintf(w, "%s:%s\t", "Name", value)
|
||||
} else {
|
||||
fmt.Fprintf(w, "%s:%s\t", "Name", novalue)
|
||||
}
|
||||
|
||||
if value, err := attr.ValueText.Value(); err == nil && value != nil {
|
||||
fmt.Fprintf(w, "%s:%s\t", "ValueText", value)
|
||||
} else {
|
||||
fmt.Fprintf(w, "%s:%s\t", "ValueText", novalue)
|
||||
}
|
||||
|
||||
if attr.ValueBlob != nil {
|
||||
fmt.Fprintf(w, "%s:%d\t", "ValueBlob-len", len(attr.ValueBlob))
|
||||
} else {
|
||||
fmt.Fprintf(w, "%s:%s\t", "ValueBlob-len", novalue)
|
||||
}
|
||||
} else {
|
||||
// Last modifier:
|
||||
//fmt.Fprintf(w, "%s\t", prettyAttr("at", attr.prettyAt()))
|
||||
|
||||
// Name:
|
||||
//fmt.Fprintf(w, "%s\t", prettyAttr("name", attr.GetName()))
|
||||
|
||||
// Value:
|
||||
//fmt.Printf(strings.Repeat(" ", indent))
|
||||
|
||||
if attr.GetMark() == 0 {
|
||||
fmt.Printf("%s%s %s\n", Color("ID:", "default"), Color(attr.GetIdentifier(), "yellow+b"), attr.Title())
|
||||
} else {
|
||||
fmt.Printf("%s%s %s\n", Color("ID:", "default"), Color(attr.GetIdentifier(), "yellow+b"), Color(attr.Title(), "default+u"))
|
||||
}
|
||||
if len(highlighteds) > 0 {
|
||||
fmt.Println(attr.PrettyMatches(highlighteds, after))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (attr Attr) PrettyMatches(highlighteds []string, after int) string {
|
||||
var valueText string
|
||||
if len(highlighteds) == 0 {
|
||||
valueText = attr.Title()
|
||||
} else {
|
||||
valueText = strings.TrimSpace(attr.GetValue())
|
||||
|
||||
matchinglines := make([]string, 0, 0)
|
||||
|
||||
lastMatchingLine := -1
|
||||
var matchCounter int
|
||||
for linenumber, line := range strings.Split(valueText, "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
isCoveredByLastMatch := lastMatchingLine != -1 && linenumber <= lastMatchingLine+after
|
||||
|
||||
line, matched := highlightLine(line, highlighteds)
|
||||
if matched {
|
||||
lastMatchingLine = linenumber
|
||||
if true { // !isCoveredByLastMatch {
|
||||
matchCounter += 1
|
||||
}
|
||||
}
|
||||
if matched || isCoveredByLastMatch {
|
||||
prefix := fmt.Sprintf("%s L%s:", strings.Repeat(" ", len(attr.GetIdentifier())), strconv.Itoa(linenumber+1))
|
||||
matchinglines = append(matchinglines, Color(prefix, "black")+line)
|
||||
if maximumShownMatches != -1 && matchCounter >= maximumShownMatches {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
valueText = strings.Join(matchinglines, "\n")
|
||||
}
|
||||
return valueText + "\n"
|
||||
}
|
||||
|
||||
func (attr Attr) Title() string {
|
||||
valueText := strings.TrimSpace(attr.GetTextValue())
|
||||
firstLineEndIndex := strings.Index(valueText, "\n")
|
||||
|
||||
if firstLineEndIndex >= 0 {
|
||||
valueText = valueText[0:firstLineEndIndex]
|
||||
} else {
|
||||
if len(valueText) > 80 {
|
||||
valueText = valueText[0:80] + ellipsis
|
||||
}
|
||||
}
|
||||
return valueText
|
||||
}
|
||||
|
||||
func (attr Attr) prettyAt() string {
|
||||
if attr.GetUpdatedAt().IsZero() {
|
||||
return attr.GetCreatedAt().Local().Format(datelayout) // + " "
|
||||
} else {
|
||||
return attr.GetUpdatedAt().Local().Format(datelayout) // + "*"
|
||||
}
|
||||
}
|
||||
|
||||
func (attr Attr) prettyCreatedAt() string {
|
||||
return attr.GetCreatedAt().Local().Format(datelayout)
|
||||
}
|
||||
|
||||
func (attr Attr) prettyUpdatedAt() string {
|
||||
if !attr.GetUpdatedAt().IsZero() {
|
||||
return attr.GetUpdatedAt().Local().Format(datelayout)
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (attr Attr) Filepath() string {
|
||||
f, err := ioutil.TempFile("", "eton-edit")
|
||||
check(err)
|
||||
writeToFile(f.Name(), attr.GetValue())
|
||||
return f.Name()
|
||||
}
|
||||
|
||||
// SetAlias sets attr's Alias to the given alias.
|
||||
// If give alias is empty string, it will unset the alias (set it to NULL in the database).
|
||||
func (attr Attr) SetAlias(db *sql.DB, alias string) {
|
||||
|
||||
unset := len(alias) == 0
|
||||
if !unset {
|
||||
var validAlias = regexp.MustCompile(`[^\s\d]+`)
|
||||
if !validAlias.MatchString(alias) {
|
||||
fmt.Println("Alias must contain a non-numeric character")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
stmt, err := db.Prepare("UPDATE attributes SET alias = ? WHERE id = ?")
|
||||
check(err)
|
||||
|
||||
//var result sql.Result
|
||||
if !unset {
|
||||
_, err = stmt.Exec(alias, attr.GetID())
|
||||
} else {
|
||||
_, err = stmt.Exec(nil, attr.GetID())
|
||||
}
|
||||
//check(err)
|
||||
if err == nil {
|
||||
if unset {
|
||||
fmt.Printf("alias NULL set for ID:%d\n", attr.GetID())
|
||||
} else {
|
||||
fmt.Printf("alias \"%s\" set for ID:%d\n", alias, attr.GetID())
|
||||
}
|
||||
} else {
|
||||
log.Fatalf("error while setting alias \"%s\" for ID:%d -- alias must be unique\n", alias, attr.GetID()) // , err)
|
||||
}
|
||||
//rowsAffected, err := result.RowsAffected()
|
||||
}
|
||||
|
||||
func (attr Attr) SetMark(db *sql.DB, mark int) (rowsAffected int64) {
|
||||
stmt, err := db.Prepare("UPDATE attributes SET mark = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND deleted_at IS NULL")
|
||||
check(err)
|
||||
|
||||
result, err := stmt.Exec(mark, attr.GetID())
|
||||
check(err)
|
||||
rowsAffected, err = result.RowsAffected()
|
||||
check(err)
|
||||
|
||||
return rowsAffected
|
||||
}
|
||||
|
||||
func (attr Attr) Rm(db *sql.DB) (rowsAffected int64) {
|
||||
stmt, err := db.Prepare("UPDATE attributes SET deleted_at = CURRENT_TIMESTAMP WHERE id = ? AND deleted_at IS NULL")
|
||||
check(err)
|
||||
|
||||
result, err := stmt.Exec(attr.GetID())
|
||||
check(err)
|
||||
rowsAffected, err = result.RowsAffected()
|
||||
check(err)
|
||||
|
||||
return rowsAffected
|
||||
}
|
||||
|
||||
func (attr Attr) Unrm(db *sql.DB) (rowsAffected int64) {
|
||||
stmt, err := db.Prepare("UPDATE attributes SET deleted_at = NULL WHERE id = ? AND deleted_at IS NOT NULL")
|
||||
check(err)
|
||||
|
||||
result, err := stmt.Exec(attr.GetID())
|
||||
check(err)
|
||||
rowsAffected, err = result.RowsAffected()
|
||||
check(err)
|
||||
|
||||
return rowsAffected
|
||||
}
|
||||
|
||||
func (attr Attr) IncrementFrequency(db *sql.DB) (rowsAffected int64) {
|
||||
stmt, err := db.Prepare("UPDATE attributes SET frequency = frequency + 1 WHERE id = ? AND deleted_at IS NULL")
|
||||
check(err)
|
||||
|
||||
result, err := stmt.Exec(attr.GetID())
|
||||
check(err)
|
||||
|
||||
rowsAffected, err = result.RowsAffected()
|
||||
check(err)
|
||||
return
|
||||
}
|
||||
|
||||
func (attr Attr) Edit(db *sql.DB) (rowsAffected int64) {
|
||||
// f, err := ioutil.TempFile("", "eton-edit")
|
||||
// check(err)
|
||||
// writeToFile(f.Name(), attr.GetValue())
|
||||
filepath := attr.Filepath()
|
||||
|
||||
openEditor(filepath)
|
||||
value_text := readFile(filepath)
|
||||
if value_text == attr.GetValue() {
|
||||
fmt.Printf("Nothing changed for ID:%d\n", attr.GetID())
|
||||
} else {
|
||||
update_stmt, err := db.Prepare("UPDATE attributes SET value_text = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?")
|
||||
check(err)
|
||||
|
||||
result, err := update_stmt.Exec(value_text, attr.GetID())
|
||||
check(err)
|
||||
rowsAffected, err = result.RowsAffected()
|
||||
check(err)
|
||||
fmt.Printf("Updated ID:%d\n", attr.GetID())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
func writeToFile(filepath string, content string) {
|
||||
err := ioutil.WriteFile(filepath, []byte(content), 0644)
|
||||
check(err)
|
||||
}
|
||||
|
||||
func highlightLine(line string, highlighteds []string) (string, bool) {
|
||||
if len(highlighteds) == 0 {
|
||||
return line, false
|
||||
} else {
|
||||
reFlags := "(?i)"
|
||||
re := regexp.MustCompile(reFlags + "(" + strings.Join(highlighteds, "|") + ")")
|
||||
if indexes := re.FindStringIndex(line); indexes != nil {
|
||||
var indexBegin int
|
||||
var indexEnd int
|
||||
var beforeStr string
|
||||
var afterStr string
|
||||
|
||||
if len(indexes) > 0 {
|
||||
firstIndex := indexes[0]
|
||||
indexBegin = firstIndex - 40
|
||||
if indexBegin < 0 {
|
||||
indexBegin = 0
|
||||
}
|
||||
if indexBegin != 0 {
|
||||
beforeStr = ellipsis
|
||||
}
|
||||
indexEnd = firstIndex + 40
|
||||
} else {
|
||||
indexEnd = 80
|
||||
}
|
||||
|
||||
if indexEnd > indexBegin+80 {
|
||||
indexEnd = indexBegin + 80
|
||||
}
|
||||
if indexEnd > len(line) {
|
||||
indexEnd = len(line)
|
||||
}
|
||||
if indexEnd != len(line) {
|
||||
afterStr = ellipsis
|
||||
}
|
||||
|
||||
line = re.ReplaceAllString(line[indexBegin:indexEnd], Color("$0", "black+b:green"))
|
||||
return beforeStr + line + afterStr, true
|
||||
}
|
||||
return line, false
|
||||
}
|
||||
}
|
||||
|
||||
func prettyAttr(name, value string) string {
|
||||
if len(name) > 0 {
|
||||
name = name + ":"
|
||||
}
|
||||
if termutil.Isatty(os.Stdout.Fd()) {
|
||||
return ansi.Color(name, "black") + ansi.Color(value, "default")
|
||||
} else {
|
||||
return name + value
|
||||
}
|
||||
}
|
||||
|
||||
func prettyAttr2(name, value string) string {
|
||||
if termutil.Isatty(os.Stdout.Fd()) {
|
||||
return ansi.Color(name+":", "black") + ansi.Color(value, "blue")
|
||||
} else {
|
||||
return name + ":" + value
|
||||
}
|
||||
}
|
||||
|
||||
// Color is the same as ansi.Color but only if STDOUT is a TTY
|
||||
func Color(str, color string) string {
|
||||
if termutil.Isatty(os.Stdout.Fd()) {
|
||||
return ansi.Color(str, color)
|
||||
} else {
|
||||
return str
|
||||
}
|
||||
}
|
||||
|
||||
func findAttributeByID(db *sql.DB, ID int64) (attr Attr) {
|
||||
var err error
|
||||
var stmt *sql.Stmt
|
||||
|
||||
defer func() {
|
||||
if err == nil {
|
||||
attr.IncrementFrequency(db)
|
||||
}
|
||||
}()
|
||||
|
||||
stmt, err = db.Prepare("SELECT " + sqlSelect + " FROM attributes WHERE id = ? AND deleted_at IS NULL LIMIT 1")
|
||||
check(err)
|
||||
|
||||
err = stmt.QueryRow(ID).Scan(&attr.ID, &attr.ValueText, &attr.Name, &attr.ParentID, &attr.Alias, &attr.Mark, &attr.ValueBlob, &attr.CreatedAt, &attr.UpdatedAt)
|
||||
if err != nil {
|
||||
log.Fatalln("No record found with id", ID, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func findAttributeByAlias(db *sql.DB, alias string) (attr Attr) {
|
||||
var err error
|
||||
var stmt *sql.Stmt
|
||||
|
||||
defer func() {
|
||||
if err == nil {
|
||||
attr.IncrementFrequency(db)
|
||||
}
|
||||
}()
|
||||
|
||||
// Exact match
|
||||
stmt, err = db.Prepare("SELECT " + sqlSelect + " FROM attributes WHERE alias = ? ORDER BY " + orderby + " LIMIT 1")
|
||||
check(err)
|
||||
err = stmt.QueryRow(alias).Scan(&attr.ID, &attr.ValueText, &attr.Name, &attr.ParentID, &attr.Alias, &attr.Mark, &attr.ValueBlob, &attr.CreatedAt, &attr.UpdatedAt)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
stmt, err = db.Prepare("SELECT " + sqlSelect + " FROM attributes WHERE alias LIKE ? ORDER BY " + orderby + " LIMIT 1")
|
||||
check(err)
|
||||
|
||||
// Prefix match
|
||||
err = stmt.QueryRow(alias+"%").Scan(&attr.ID, &attr.ValueText, &attr.Name, &attr.ParentID, &attr.Alias, &attr.Mark, &attr.ValueBlob, &attr.CreatedAt, &attr.UpdatedAt)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Postfix match
|
||||
err = stmt.QueryRow("%"+alias).Scan(&attr.ID, &attr.ValueText, &attr.Name, &attr.ParentID, &attr.Alias, &attr.Mark, &attr.ValueBlob, &attr.CreatedAt, &attr.UpdatedAt)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
prunes := strings.Split(alias, "")
|
||||
|
||||
// Fuzzy match
|
||||
err = stmt.QueryRow("%"+strings.Join(prunes, "%")+"%").Scan(&attr.ID, &attr.ValueText, &attr.Name, &attr.ValueBlob)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func findAttributeByAliasOrID(db *sql.DB, alias_or_id string) (attr Attr) {
|
||||
attr = findAttributeByAlias(db, alias_or_id)
|
||||
if attr.GetID() <= 0 {
|
||||
|
||||
intID, err := strconv.Atoi(alias_or_id)
|
||||
|
||||
if err != nil {
|
||||
return attr
|
||||
}
|
||||
|
||||
attr = findAttributeByID(db, int64(intID))
|
||||
}
|
||||
|
||||
return attr
|
||||
}
|
||||
|
||||
func listWithFilters(db *sql.DB, opts Options) (attrs []Attr) {
|
||||
var stmt *sql.Stmt
|
||||
var rows *sql.Rows
|
||||
var nolimit = opts.Limit == -1
|
||||
|
||||
var sqlConditions string
|
||||
var sqlLimit string
|
||||
|
||||
queryValues := make([]interface{}, 0, 5)
|
||||
|
||||
if opts.RemovedOnly {
|
||||
sqlConditions = "deleted_at IS NOT NULL"
|
||||
} else {
|
||||
sqlConditions = "deleted_at IS NULL"
|
||||
}
|
||||
|
||||
if opts.RootID == -1 {
|
||||
sqlConditions += " AND parent_id IS NULL"
|
||||
} else {
|
||||
nolimit = true
|
||||
sqlConditions += fmt.Sprintf(" AND parent_id = %d ", opts.RootID)
|
||||
}
|
||||
|
||||
if !opts.Recursive {
|
||||
sqlConditions += " AND parent_id IS NULL"
|
||||
}
|
||||
|
||||
if opts.AliasedOrMarkedOnly {
|
||||
sqlConditions += " AND ((alias IS NOT NULL AND alias != '') OR mark > 0)"
|
||||
}
|
||||
|
||||
if opts.RootID == -1 && len(opts.Filters) > 0 {
|
||||
nolimit = true
|
||||
nameOrVal := make([]string, 0, 0)
|
||||
|
||||
for _, filter := range opts.Filters {
|
||||
likeValue := "%" + filter + "%"
|
||||
// queryValues = append(queryValues, likeValue, likeValue, likeValue)
|
||||
queryValues = append(queryValues, likeValue, likeValue)
|
||||
|
||||
// nameOrVal = append(nameOrVal, "(value_text LIKE ? OR name LIKE ? OR alias LIKE ?)")
|
||||
nameOrVal = append(nameOrVal, "(value_text LIKE ? OR alias LIKE ?)")
|
||||
}
|
||||
|
||||
sqlConditions += " AND ( " + strings.Join(nameOrVal, " AND ") + " )"
|
||||
}
|
||||
|
||||
if nolimit {
|
||||
sqlLimit = ""
|
||||
} else {
|
||||
queryValues = append(queryValues, opts.Offset)
|
||||
queryValues = append(queryValues, opts.Limit)
|
||||
sqlLimit = "LIMIT ?, ?"
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
|
||||
tx, err := db.Begin()
|
||||
check(err)
|
||||
stmt, err = tx.Prepare("SELECT " + sqlSelect + " FROM attributes WHERE " + sqlConditions + " ORDER BY " + orderby + " " + sqlLimit)
|
||||
check(err)
|
||||
defer stmt.Close()
|
||||
rows, err = stmt.Query(queryValues...)
|
||||
check(err)
|
||||
defer rows.Close()
|
||||
|
||||
attrs = make([]Attr, 0, 0)
|
||||
|
||||
for rows.Next() {
|
||||
attr := Attr{}
|
||||
err = rows.Scan(&attr.ID, &attr.ValueText, &attr.Name, &attr.ParentID, &attr.Alias, &attr.Mark, &attr.ValueBlob, &attr.CreatedAt, &attr.UpdatedAt)
|
||||
check(err)
|
||||
attrs = append(attrs, attr)
|
||||
|
||||
var optsNew Options
|
||||
optsNew = opts
|
||||
optsNew.RootID = attr.GetID()
|
||||
optsNew.Indent += 2
|
||||
//cmdLs(db, w, optsNew)
|
||||
}
|
||||
|
||||
tx.Commit()
|
||||
return attrs
|
||||
}
|
||||
|
||||
func getLastAttrID(db *sql.DB) int64 {
|
||||
// Experimental
|
||||
var ID int64
|
||||
|
||||
stmt, err := db.Prepare("SELECT id FROM attributes WHERE deleted_at IS NULL ORDER BY " + orderby + " LIMIT 1")
|
||||
check(err)
|
||||
|
||||
err = stmt.QueryRow().Scan(&ID)
|
||||
check(err)
|
||||
return ID
|
||||
}
|
||||
|
||||
func saveString(db *sql.DB, value_text string) {
|
||||
new_stmt, err := db.Prepare("INSERT INTO attributes (name, value_text) VALUES ('note', ?)")
|
||||
check(err)
|
||||
|
||||
result, err := new_stmt.Exec(value_text)
|
||||
check(err)
|
||||
|
||||
lastInsertId, err := result.LastInsertId()
|
||||
check(err)
|
||||
|
||||
fmt.Printf("New note ID:%d\n", lastInsertId)
|
||||
}
|
||||
|
||||
func InitializeDatabase(db *sql.DB) bool {
|
||||
sqlStmt := `
|
||||
DROP TABLE IF EXISTS attributes;
|
||||
CREATE TABLE attributes (
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
name TEXT,
|
||||
alias TEXT,
|
||||
parent_id INTEGER,
|
||||
frequency INTEGER DEFAULT 0,
|
||||
mark INTEGER DEFAULT 0,
|
||||
-- pwd TEXT,
|
||||
|
||||
value_text TEXT,
|
||||
value_blob BLOB,
|
||||
value_int INTEGER,
|
||||
value_real REAL,
|
||||
value_time DATETIME,
|
||||
|
||||
accessed_at DATETIME,
|
||||
updated_at DATETIME,
|
||||
deleted_at DATETIME,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS index_on_alias ON attributes (alias);
|
||||
CREATE INDEX IF NOT EXISTS index_on_name ON attributes (name);
|
||||
CREATE INDEX IF NOT EXISTS index_on_value_text ON attributes (value_text);
|
||||
CREATE INDEX IF NOT EXISTS index_on_value_blob ON attributes (value_blob);
|
||||
CREATE INDEX IF NOT EXISTS index_on_value_int ON attributes (value_int);
|
||||
CREATE INDEX IF NOT EXISTS index_on_value_real ON attributes (value_real);
|
||||
CREATE INDEX IF NOT EXISTS index_on_accessed_at ON attributes (accessed_at);
|
||||
CREATE INDEX IF NOT EXISTS index_on_deleted_at ON attributes (deleted_at);
|
||||
CREATE INDEX IF NOT EXISTS index_on_frequency ON attributes (frequency);
|
||||
CREATE INDEX IF NOT EXISTS index_on_mark ON attributes (mark);
|
||||
`
|
||||
_, err := db.Exec(sqlStmt)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return false
|
||||
}
|
||||
fmt.Println("repository initiated")
|
||||
return true
|
||||
}
|
||||
|
||||
// NullTime allows timestamps to be NULL
|
||||
type NullTime struct {
|
||||
Time time.Time
|
||||
Valid bool // Valid is true if Time is not NULL
|
||||
}
|
||||
|
||||
// Scan implements the Scanner interface.
|
||||
func (nt *NullTime) Scan(value interface{}) error {
|
||||
nt.Time, nt.Valid = value.(time.Time)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (nt NullTime) Value() (driver.Value, error) {
|
||||
if !nt.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return nt.Time, nil
|
||||
}
|
@ -0,0 +1,129 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const novalue string = "nil"
|
||||
const datelayout string = "06/01/02 03:04pm"
|
||||
const ellipsis = "…"
|
||||
const maximumShownMatches = -1 // -1
|
||||
|
||||
type Options struct {
|
||||
ID int64
|
||||
Alias string
|
||||
IDs []int64
|
||||
Aliases []string
|
||||
Limit int
|
||||
Offset int
|
||||
RootID int64
|
||||
Indent int
|
||||
Filters []string
|
||||
FromStdin bool
|
||||
Recursive bool
|
||||
RemovedOnly bool
|
||||
AliasedOrMarkedOnly bool
|
||||
ListFilepaths bool
|
||||
MountPoint string
|
||||
Note string
|
||||
AfterLinesCount int
|
||||
Alias1 string
|
||||
Alias2 string
|
||||
}
|
||||
|
||||
func OptionsFromArgs(args map[string]interface{}) (opts Options) {
|
||||
// log.Printf("%v\n", args)
|
||||
var err error
|
||||
|
||||
opts.RootID = -1
|
||||
opts.Indent = 0
|
||||
|
||||
opts.Offset, err = strconv.Atoi(args["--offset"].(string))
|
||||
check(err)
|
||||
|
||||
opts.ListFilepaths = args["--list-files"].(bool)
|
||||
|
||||
if args["<note>"] != nil {
|
||||
opts.Note = args["<note>"].(string)
|
||||
}
|
||||
|
||||
opts.AfterLinesCount, err = strconv.Atoi(args["--after"].(string))
|
||||
check(err)
|
||||
|
||||
if args["--limit"].(string) == "all" {
|
||||
opts.Limit = -1
|
||||
} else {
|
||||
opts.Limit, err = strconv.Atoi(args["--limit"].(string))
|
||||
check(err)
|
||||
}
|
||||
|
||||
if args["<id1>"] != nil {
|
||||
intID, err := strconv.Atoi(args["<id1>"].(string))
|
||||
if err == nil {
|
||||
opts.ID = int64(intID)
|
||||
} else {
|
||||
opts.Alias1 = args["<id1>"].(string)
|
||||
}
|
||||
}
|
||||
|
||||
if args["<mountpoint>"] != nil {
|
||||
opts.MountPoint = args["<mountpoint>"].(string)
|
||||
} else {
|
||||
opts.MountPoint = filepath.Join(homeDirectory(), "eton-default-mount-point")
|
||||
}
|
||||
|
||||
if args["<id2>"] != nil {
|
||||
intID, err := strconv.Atoi(args["<id2>"].(string))
|
||||
if err == nil {
|
||||
opts.ID = int64(intID)
|
||||
} else {
|
||||
opts.Alias2 = args["<id2>"].(string)
|
||||
}
|
||||
}
|
||||
|
||||
if args["<id>"] != nil {
|
||||
intID, err := strconv.Atoi(args["<id>"].(string))
|
||||
if err == nil {
|
||||
opts.ID = int64(intID)
|
||||
} else {
|
||||
opts.Alias = args["<id>"].(string)
|
||||
}
|
||||
}
|
||||
|
||||
for _, id := range args["<ids>"].([]string) {
|
||||
intID, err := strconv.Atoi(id)
|
||||
if err == nil {
|
||||
opts.IDs = append(opts.IDs, int64(intID))
|
||||
} else {
|
||||
opts.Aliases = append(opts.Aliases, id)
|
||||
}
|
||||
}
|
||||
|
||||
if args["<alias>"] != nil {
|
||||
opts.Alias = args["<alias>"].(string)
|
||||
}
|
||||
|
||||
opts.Filters = args["<filters>"].([]string)
|
||||
opts.FromStdin = args["-"].(bool)
|
||||
opts.Recursive = false // args["--recursive"].(bool)
|
||||
opts.RemovedOnly = args["--removed-only"].(bool)
|
||||
|
||||
opts.AliasedOrMarkedOnly = args["--short"].(bool)
|
||||
return
|
||||
}
|
||||
|
||||
func (opts Options) GetIDsArrayOfInterface() []interface{} {
|
||||
var interfaceIds = make([]interface{}, len(opts.IDs), len(opts.IDs))
|
||||
for i, id := range opts.IDs {
|
||||
interfaceIds[i] = id
|
||||
}
|
||||
return interfaceIds
|
||||
}
|
||||
|
||||
func homeDirectory() string {
|
||||
usr, err := user.Current()
|
||||
check(err)
|
||||
return usr.HomeDir
|
||||
}
|
Loading…
Reference in New Issue