M cmd/clear.go +9 -8
@@ 4,6 4,7 @@ import (
"fmt"
"github.com/spf13/cobra"
+ "hg.code.netlandish.com/~petersanchez/tago/lib"
)
func init() {
@@ 38,15 39,15 @@ func init() {
// ClearFiles remove all tags for given file
func ClearFiles(cmd *cobra.Command, args []string) error {
cmd.SilenceUsage = true // Usage is correct, don't show on errors
- qm, err := NewQueryManager()
+ qm, err := lib.NewQueryManager()
if err != nil {
return err
}
defer qm.DB.Close()
- file := NewFile(qm, args[0])
+ file := lib.NewFile(qm, args[0])
if err := file.Load(qm, false); err != nil {
- if _, ok := err.(NotFoundError); ok {
+ if _, ok := err.(lib.NotFoundError); ok {
fmt.Println(err.Error())
return nil
}
@@ 56,27 57,27 @@ func ClearFiles(cmd *cobra.Command, args
if err = file.Clear(qm); err != nil {
return err
}
- fmt.Printf("Successfully cleared all tags for %s.\n", RelativePath(file.FullPath()))
+ fmt.Printf("Successfully cleared all tags for %s.\n", lib.RelativePath(file.FullPath()))
return nil
}
// ClearTags remove all tag assignments for given tag
func ClearTags(cmd *cobra.Command, args []string) error {
cmd.SilenceUsage = true // Usage is correct, don't show on errors
- qm, err := NewQueryManager()
+ qm, err := lib.NewQueryManager()
if err != nil {
return err
}
defer qm.DB.Close()
- if _, err := ValidateTagName(args[0]); err != nil {
+ if _, err := lib.ValidateTagName(args[0]); err != nil {
fmt.Println(err)
return nil
}
- tag := Tag{Name: args[0]}
+ tag := lib.Tag{Name: args[0]}
if err := tag.Fetch(qm); err != nil {
- if _, ok := err.(NotFoundError); ok {
+ if _, ok := err.(lib.NotFoundError); ok {
fmt.Println(err.Error())
return nil
}
M cmd/db.go +12 -225
@@ 3,15 3,12 @@ package cmd
import (
"fmt"
"os"
- "path/filepath"
- "github.com/jmoiron/sqlx"
_ "github.com/mattn/go-sqlite3" // sqlite3
"github.com/spf13/cobra"
+ "hg.code.netlandish.com/~petersanchez/tago/lib"
)
-const schemaVersion int = 1
-
func init() {
var dbCmd = &cobra.Command{
@@ 42,180 39,15 @@ func init() {
rootCmd.AddCommand(dbCmd)
}
-// WriteSchemaVersion write schema version to db
-func WriteSchemaVersion(db *sqlx.DB, version int) error {
- tx, err := db.Beginx()
- if err != nil {
- return err
- }
- defer tx.Rollback() // Ignored after Commit()
-
- q := "UPDATE schema_version SET version = ?"
- res, err := tx.Exec(q, version)
- if err != nil {
- return fmt.Errorf("Error with version query: %v", err)
- }
- cnt, err := res.RowsAffected()
- if err != nil {
- return err
- }
- if cnt != 1 {
- // Not yet set, write the version
- q = "INSERT INTO schema_version VALUES (?)"
- _, err := tx.Exec(q, version)
- if err != nil {
- return fmt.Errorf("Writing schema version failed: %v", err)
- }
- }
-
- // Commit the transaction
- if err := tx.Commit(); err != nil {
- return err
- }
- return nil
-}
-
-// TxExec Run the query
-func TxExec(tx *sqlx.Tx, query string, args ...interface{}) error {
- _, err := tx.Exec(query, args...)
- if err != nil {
- return err
- }
- return nil
-}
-
-// CreateTables create db tables
-func CreateTables(db *sqlx.DB) error {
- tx, err := db.Beginx()
- if err != nil {
- return err
- }
- defer tx.Rollback() // Ignored after Commit()
-
- // Schema version table
- sql := `
- CREATE TABLE IF NOT EXISTS schema_version (
- version INTEGER NOT NULL PRIMARY KEY
- )`
-
- if err = TxExec(tx, sql); err != nil {
- return err
- }
-
- // Main file table
- sql = `
- CREATE TABLE IF NOT EXISTS file (
- id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
- directory TEXT NOT NULL,
- name TEXT NOT NULL,
- fingerprint TEXT NOT NULL,
- content_type TEXT NOT NULL,
- mod_time DATETIME NOT NULL,
- size INTEGER NOT NULL,
- is_dir BOOLEAN NOT NULL,
- is_index BOOLEAN NOT NULL,
- CONSTRAINT con_file_path UNIQUE (directory, name)
- )`
-
- if err = TxExec(tx, sql); err != nil {
- return err
- }
-
- sql = `
- CREATE VIRTUAL TABLE file_index USING fts5(
- file_id UNINDEXED,
- filename,
- desc,
- contents
- )`
-
- if err = TxExec(tx, sql); err != nil {
- return err
- }
-
- sql = `
- CREATE TABLE IF NOT EXISTS tag (
- id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
- name TEXT NOT NULL,
- CONSTRAINT con_tag_name UNIQUE (name)
- )`
-
- if err = TxExec(tx, sql); err != nil {
- return err
- }
-
- sql = "CREATE INDEX IF NOT EXISTS idx_tag_name ON tag(name)"
- if err = TxExec(tx, sql); err != nil {
- return err
- }
-
- // Tag mapping table
- sql = `
- CREATE TABLE IF NOT EXISTS tag_map (
- file_id INTEGER NOT NULL,
- tag_id INTEGER NOT NULL,
- PRIMARY KEY (file_id, tag_id),
- FOREIGN KEY (file_id) REFERENCES file(id),
- FOREIGN KEY (tag_id) REFERENCES tag(id)
- CONSTRAINT con_file_tag UNIQUE (file_id, tag_id)
- )`
- if err = TxExec(tx, sql); err != nil {
- return err
- }
-
- // Commit the transaction
- if err := tx.Commit(); err != nil {
- return err
- }
- return nil
-}
-
-// CreateDatabase creates the db file
-func CreateDatabase(dbfile string) error {
- fmt.Printf("Creating database %v...\n", dbfile)
-
- dir := filepath.Dir(dbfile)
-
- _, err := os.Stat(dir)
- if err != nil {
- if os.IsNotExist(err) {
- // Create directories
- fmt.Printf("Base directory %v doesn't exist. Creating...\n", dir)
- if err = os.MkdirAll(dir, 0755); err != nil {
- return err
- }
- } else {
- return err
- }
- }
-
- file, err := os.Create(dbfile)
- if err != nil {
- return err
- }
- file.Close()
-
- sqldb, _ := sqlx.Open("sqlite3", dbfile)
- defer sqldb.Close()
-
- if err = CreateTables(sqldb); err != nil {
- return err
- }
- if err = WriteSchemaVersion(sqldb, schemaVersion); err != nil {
- return err
- }
- return nil
-}
-
// DatabaseInit initializes the db file
func DatabaseInit(cmd *cobra.Command, args []string) error {
cmd.SilenceUsage = true // Usage is correct, don't show on errors
force, _ := cmd.Flags().GetBool("force")
- fpath := GetFullFilePath(dbFile)
+ fpath := lib.GetFullFilePath(dbFile)
_, err := os.Stat(fpath)
if err != nil {
if os.IsNotExist(err) {
- if err = CreateDatabase(fpath); err != nil {
+ if err = lib.CreateDatabase(fpath); err != nil {
return err
}
return nil
@@ 226,10 58,10 @@ func DatabaseInit(cmd *cobra.Command, ar
fmt.Printf("Database file %v exists...\n", dbFile)
if force == true {
fmt.Println("Force flag is set, removing existing database file.")
- if err = BackupDatabase(fpath); err != nil {
+ if err = lib.BackupDatabase(fpath); err != nil {
return err
}
- if err = CreateDatabase(fpath); err != nil {
+ if err = lib.CreateDatabase(fpath); err != nil {
return err
}
} else {
@@ 238,55 70,10 @@ func DatabaseInit(cmd *cobra.Command, ar
return nil
}
-// GetDatabase returns an sqlx conn to db
-func GetDatabase() (*sqlx.DB, error) {
- fpath := GetFullFilePath(dbFile)
- _, err := os.Stat(fpath)
- if err != nil {
- return nil, err
- }
- sqldb, err := sqlx.Open("sqlite3", fpath)
- if err != nil {
- return nil, err
- }
- return sqldb, nil
-}
-
-// GetDBTransaction returns a db transaction
-func GetDBTransaction(db *sqlx.DB) (*sqlx.Tx, error) {
- tx, err := db.Beginx()
- if err != nil {
- return nil, err
- }
- return tx, nil
-}
-
-// NewQueryManager returns a new QueryManager instance
-func NewQueryManager() (*QueryManager, error) {
- db, err := GetDatabase()
- if err != nil {
- fmt.Fprintf(
- os.Stderr,
- "Unable to open database file. Perhaps you need to run: tago db init\n",
- )
- return nil, err
- }
- qm := &QueryManager{
- DB: db,
- Commit: true,
- Rollback: true,
- }
- err = qm.SchemaCheck()
- if err != nil {
- return nil, err
- }
- return qm, nil
-}
-
// UpgradeSchema upgrades db schema if needed
func UpgradeSchema(cmd *cobra.Command, args []string) error {
cmd.SilenceUsage = true // Usage is correct, don't show on errors
- qm, err := NewQueryManager()
+ qm, err := lib.NewQueryManager()
if err != nil {
return err
}
@@ 299,11 86,11 @@ func UpgradeSchema(cmd *cobra.Command, a
return fmt.Errorf("Unable to fetch schema version: %v", err)
}
- if currentVersion < schemaVersion {
- if err = BackupDatabase(GetFullFilePath(dbFile)); err != nil {
+ if currentVersion < lib.SchemaVersion {
+ if err = lib.BackupDatabase(lib.GetFullFilePath(dbFile)); err != nil {
return err
}
- for currentVersion < schemaVersion {
+ for currentVersion < lib.SchemaVersion {
currentVersion++
switch currentVersion {
/* When changes are needed, something like:
@@ 315,10 102,10 @@ func UpgradeSchema(cmd *cobra.Command, a
fmt.Println("Schema version: ", currentVersion)
}
// Upgrade schema version after every iteration
- WriteSchemaVersion(qm.DB, currentVersion)
+ lib.WriteSchemaVersion(qm.DB, currentVersion)
}
} else {
- fmt.Println("Your schema is at the current version: ", schemaVersion)
+ fmt.Println("Your schema is at the current version: ", lib.SchemaVersion)
}
return nil
@@ 329,7 116,7 @@ func UpgradeSchema(cmd *cobra.Command, a
/*
func upgradeSchemaToVersion2(qm *QueryManager) error {
// Placeholder until an upgrade is actually needed
- // WriteSchemaVersion
+ // lib.WriteSchemaVersion
return nil
}
*/
M cmd/delete.go +9 -8
@@ 4,6 4,7 @@ import (
"fmt"
"github.com/spf13/cobra"
+ "hg.code.netlandish.com/~petersanchez/tago/lib"
)
func init() {
@@ 38,15 39,15 @@ func init() {
// DeleteFiles deletes files from db
func DeleteFiles(cmd *cobra.Command, args []string) error {
cmd.SilenceUsage = true // Usage is correct, don't show on errors
- qm, err := NewQueryManager()
+ qm, err := lib.NewQueryManager()
if err != nil {
return err
}
defer qm.DB.Close()
- file := NewFile(qm, args[0])
+ file := lib.NewFile(qm, args[0])
if err := file.Load(qm, false); err != nil {
- if _, ok := err.(NotFoundError); ok {
+ if _, ok := err.(lib.NotFoundError); ok {
fmt.Println(err.Error())
return nil
}
@@ 56,27 57,27 @@ func DeleteFiles(cmd *cobra.Command, arg
if err = file.Delete(qm); err != nil {
return err
}
- fmt.Printf("Successfully removed %s from the database.\n", RelativePath(file.FullPath()))
+ fmt.Printf("Successfully removed %s from the database.\n", lib.RelativePath(file.FullPath()))
return nil
}
// DeleteTags deletes tags from db
func DeleteTags(cmd *cobra.Command, args []string) error {
cmd.SilenceUsage = true // Usage is correct, don't show on errors
- qm, err := NewQueryManager()
+ qm, err := lib.NewQueryManager()
if err != nil {
return err
}
defer qm.DB.Close()
- if _, err := ValidateTagName(args[0]); err != nil {
+ if _, err := lib.ValidateTagName(args[0]); err != nil {
fmt.Println(err)
return nil
}
- tag := Tag{Name: args[0]}
+ tag := lib.Tag{Name: args[0]}
if err := tag.Fetch(qm); err != nil {
- if _, ok := err.(NotFoundError); ok {
+ if _, ok := err.(lib.NotFoundError); ok {
fmt.Println(err.Error())
return nil
}
M cmd/files.go +8 -7
@@ 4,6 4,7 @@ import (
"fmt"
"github.com/spf13/cobra"
+ "hg.code.netlandish.com/~petersanchez/tago/lib"
)
func init() {
@@ 23,7 24,7 @@ func init() {
func ListFiles(cmd *cobra.Command, args []string) error {
cmd.SilenceUsage = true // Usage is correct, don't show on errors
ctype, _ := cmd.Flags().GetString("ctype")
- qm, err := NewQueryManager()
+ qm, err := lib.NewQueryManager()
if err != nil {
return err
}
@@ 31,7 32,7 @@ func ListFiles(cmd *cobra.Command, args
vargs := make([]string, 0, len(args))
for _, arg := range args {
- if tag, err := ValidateTagName(arg); err != nil {
+ if tag, err := lib.ValidateTagName(arg); err != nil {
fmt.Println(err)
} else {
vargs = append(vargs, tag)
@@ 42,19 43,19 @@ func ListFiles(cmd *cobra.Command, args
return fmt.Errorf("No valid tags were given")
}
- conf := &SearchFilesConfig{}
- conf.tags = vargs
+ conf := &lib.SearchFilesConfig{}
+ conf.Tags = vargs
if ctype != "" {
- conf.ctype = ctype
+ conf.Ctype = ctype
}
- files, err := FileSearch(qm, conf)
+ files, err := lib.FileSearch(qm, conf)
if err != nil {
return err
}
if len(files) > 0 {
for _, file := range files {
- fmt.Printf("%v\n", RelativePath(file.FullPath()))
+ fmt.Printf("%v\n", lib.RelativePath(file.FullPath()))
}
} else {
fmt.Println("There are no files with the given tags and options.")
M cmd/merge.go +5 -4
@@ 4,6 4,7 @@ import (
"fmt"
"github.com/spf13/cobra"
+ "hg.code.netlandish.com/~petersanchez/tago/lib"
)
func init() {
@@ 21,20 22,20 @@ func init() {
// MergeTags merges all tags from old-tag into new-tag
func MergeTags(cmd *cobra.Command, args []string) error {
cmd.SilenceUsage = true // Usage is correct, don't show on errors
- qm, err := NewQueryManager()
+ qm, err := lib.NewQueryManager()
if err != nil {
return err
}
defer qm.DB.Close()
for _, arg := range args {
- if _, err := ValidateTagName(arg); err != nil {
+ if _, err := lib.ValidateTagName(arg); err != nil {
return fmt.Errorf(err.Error())
}
}
- otag := Tag{Name: args[0]}
- ntag := Tag{Name: args[1]}
+ otag := lib.Tag{Name: args[0]}
+ ntag := lib.Tag{Name: args[1]}
if err := ntag.Merge(qm, &otag); err != nil {
return err
M cmd/root.go +5 -0
@@ 4,6 4,7 @@ import (
"os"
"github.com/spf13/cobra"
+ "hg.code.netlandish.com/~petersanchez/tago/lib"
)
var (
@@ 16,6 17,10 @@ var rootCmd = &cobra.Command{
Version: "0.1.0",
Short: "Tag and index your system files.",
Long: "Tag and index your system files.",
+ PersistentPreRun: func(cmd *cobra.Command, args []string) {
+ lib.TagoRunFlags.DbFile = dbFile
+ lib.TagoRunFlags.ShowFullPath = fullPath
+ },
}
func init() {
M cmd/search.go +8 -7
@@ 5,6 5,7 @@ import (
"github.com/google/shlex"
"github.com/spf13/cobra"
+ "hg.code.netlandish.com/~petersanchez/tago/lib"
)
func init() {
@@ 26,9 27,9 @@ func SearchFiles(cmd *cobra.Command, arg
cmd.SilenceUsage = true // Usage is correct, don't show on errors
ctype, _ := cmd.Flags().GetString("ctype")
ltags, _ := cmd.Flags().GetString("tags")
- conf := &SearchFilesConfig{search: args[0]}
+ conf := &lib.SearchFilesConfig{Search: args[0]}
- qm, err := NewQueryManager()
+ qm, err := lib.NewQueryManager()
if err != nil {
return err
}
@@ 44,7 45,7 @@ func SearchFiles(cmd *cobra.Command, arg
// Get tags
tags := make([]string, 0, len(ptags))
for _, arg := range ptags {
- if tag, err := ValidateTagName(arg); err != nil {
+ if tag, err := lib.ValidateTagName(arg); err != nil {
fmt.Println(err)
} else {
tags = append(tags, tag)
@@ 54,21 55,21 @@ func SearchFiles(cmd *cobra.Command, arg
if len(tags) == 0 {
fmt.Println("No valid tags were given. Skipping tags.")
} else {
- conf.tags = tags
+ conf.Tags = tags
}
}
if ctype != "" {
- conf.ctype = ctype
+ conf.Ctype = ctype
}
- files, err := FileSearch(qm, conf)
+ files, err := lib.FileSearch(qm, conf)
if err != nil {
return err
}
if len(files) > 0 {
for _, file := range files {
- fmt.Printf("%v\n", RelativePath(file.FullPath()))
+ fmt.Printf("%v\n", lib.RelativePath(file.FullPath()))
}
} else {
fmt.Println("There are no files with the given query and options.")
M cmd/stats.go +3 -2
@@ 4,6 4,7 @@ import (
"fmt"
"github.com/spf13/cobra"
+ "hg.code.netlandish.com/~petersanchez/tago/lib"
)
func init() {
@@ 21,7 22,7 @@ func init() {
// ListTags lists all tags in the database
func ListTags(cmd *cobra.Command, args []string) error {
cmd.SilenceUsage = true // Usage is correct, don't show on errors
- qm, err := NewQueryManager()
+ qm, err := lib.NewQueryManager()
if err != nil {
return err
}
@@ 40,7 41,7 @@ func ListTags(cmd *cobra.Command, args [
ORDER BY
file_count DESC, length(name), name`
- var tags []Tag
+ var tags []lib.Tag
err = qm.DB.Select(&tags, q)
if err != nil {
return err
M cmd/tag.go +48 -23
@@ 5,6 5,7 @@ import (
"github.com/google/shlex"
"github.com/spf13/cobra"
+ "hg.code.netlandish.com/~petersanchez/tago/lib"
)
func init() {
@@ 40,7 41,7 @@ func TagFile(cmd *cobra.Command, args []
ltags, _ := cmd.Flags().GetString("tags")
desc, _ := cmd.Flags().GetString("desc")
index, _ := cmd.Flags().GetBool("index")
- qm, err := NewQueryManager()
+ qm, err := lib.NewQueryManager()
if err != nil {
return err
}
@@ 48,19 49,12 @@ func TagFile(cmd *cobra.Command, args []
if ltags == "" {
// One file
- file := NewFile(qm, args[0])
- file.IsIndex = index
- file.indexDesc = desc
- if err := file.Load(qm, true); err != nil {
- return err
- }
-
- tags := make([]Tag, 0, len(args[1:]))
+ tags := make([]lib.Tag, 0, len(args[1:]))
for _, arg := range args[1:] {
- if tag, err := ValidateTagName(arg); err != nil {
+ if tag, err := lib.ValidateTagName(arg); err != nil {
fmt.Println(err)
} else {
- tags = append(tags, Tag{Name: tag})
+ tags = append(tags, lib.Tag{Name: tag})
}
}
@@ 68,10 62,27 @@ func TagFile(cmd *cobra.Command, args []
return fmt.Errorf("No valid tags were given")
}
- if err := file.Tag(qm, tags...); err != nil {
+ files, err := lib.NewFiles(qm, args[0])
+ if err != nil {
return err
}
- fmt.Println(RelativePath(file.FullPath()))
+
+ for _, file := range files {
+ file.IsIndex = index
+ file.IndexDesc = desc
+ if err := file.Load(qm, true); err != nil {
+ if len(files) == 1 {
+ return err
+ }
+ fmt.Println(err)
+ continue
+ }
+
+ if err := file.Tag(qm, tags...); err != nil {
+ return err
+ }
+ fmt.Println(lib.RelativePath(file.FullPath()))
+ }
} else {
// Potentially multiple files
ptags, err := shlex.Split(ltags)
@@ 80,27 91,41 @@ func TagFile(cmd *cobra.Command, args []
}
// Get tags
- tags := make([]Tag, 0, len(ptags))
+ tags := make([]lib.Tag, 0, len(ptags))
for _, arg := range ptags {
- if tag, err := ValidateTagName(arg); err != nil {
+ if tag, err := lib.ValidateTagName(arg); err != nil {
fmt.Println(err)
} else {
- tags = append(tags, Tag{Name: tag})
+ tags = append(tags, lib.Tag{Name: tag})
}
}
+ if len(tags) == 0 {
+ return fmt.Errorf("No valid tags were given")
+ }
+
// Tag each file
for _, arg := range args {
- file := NewFile(qm, arg)
- file.IsIndex = index
- file.indexDesc = desc
- if err := file.Load(qm, true); err != nil {
+ files, err := lib.NewFiles(qm, arg)
+ if err != nil {
return err
}
- if err := file.Tag(qm, tags...); err != nil {
- return err
+
+ for _, file := range files {
+ file.IsIndex = index
+ file.IndexDesc = desc
+ if err := file.Load(qm, true); err != nil {
+ if len(files) == 1 {
+ return err
+ }
+ fmt.Println(err)
+ continue
+ }
+ if err := file.Tag(qm, tags...); err != nil {
+ return err
+ }
+ fmt.Println(lib.RelativePath(file.FullPath()))
}
- fmt.Println(RelativePath(file.FullPath()))
}
}
return nil
M cmd/tags.go +6 -5
@@ 4,6 4,7 @@ import (
"fmt"
"github.com/spf13/cobra"
+ "hg.code.netlandish.com/~petersanchez/tago/lib"
)
func init() {
@@ 21,15 22,15 @@ func init() {
// ShowFileTags shows tags assigned to given file
func ShowFileTags(cmd *cobra.Command, args []string) error {
cmd.SilenceUsage = true // Usage is correct, don't show on errors
- qm, err := NewQueryManager()
+ qm, err := lib.NewQueryManager()
if err != nil {
return err
}
defer qm.DB.Close()
- file := NewFile(qm, args[0])
+ file := lib.NewFile(qm, args[0])
if err := file.Load(qm, false); err != nil {
- if _, ok := err.(NotFoundError); ok {
+ if _, ok := err.(lib.NotFoundError); ok {
fmt.Println(err.Error())
return nil
}
@@ 41,11 42,11 @@ func ShowFileTags(cmd *cobra.Command, ar
}
if len(tags) == 0 {
- fmt.Printf("%v: File is not currently tagged.\n", RelativePath(file.FullPath()))
+ fmt.Printf("%v: File is not currently tagged.\n", lib.RelativePath(file.FullPath()))
return nil
}
- fmt.Println(RelativePath(file.FullPath()) + ":")
+ fmt.Println(lib.RelativePath(file.FullPath()) + ":")
for _, tag := range tags {
fmt.Printf("\t%v\n", tag.Name)
}
A => lib/db.go +223 -0
@@ 0,0 1,223 @@
+package lib
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+
+ "github.com/jmoiron/sqlx"
+ _ "github.com/mattn/go-sqlite3" // sqlite3
+)
+
+// SchemaVersion is the current db schema version
+const SchemaVersion int = 1
+
+// WriteSchemaVersion write schema version to db
+func WriteSchemaVersion(db *sqlx.DB, version int) error {
+ tx, err := db.Beginx()
+ if err != nil {
+ return err
+ }
+ defer tx.Rollback() // Ignored after Commit()
+
+ q := "UPDATE schema_version SET version = ?"
+ res, err := tx.Exec(q, version)
+ if err != nil {
+ return fmt.Errorf("Error with version query: %v", err)
+ }
+ cnt, err := res.RowsAffected()
+ if err != nil {
+ return err
+ }
+ if cnt != 1 {
+ // Not yet set, write the version
+ q = "INSERT INTO schema_version VALUES (?)"
+ _, err := tx.Exec(q, version)
+ if err != nil {
+ return fmt.Errorf("Writing schema version failed: %v", err)
+ }
+ }
+
+ // Commit the transaction
+ if err := tx.Commit(); err != nil {
+ return err
+ }
+ return nil
+}
+
+// TxExec Run the query
+func TxExec(tx *sqlx.Tx, query string, args ...interface{}) error {
+ _, err := tx.Exec(query, args...)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// CreateTables create db tables
+func CreateTables(db *sqlx.DB) error {
+ tx, err := db.Beginx()
+ if err != nil {
+ return err
+ }
+ defer tx.Rollback() // Ignored after Commit()
+
+ // Schema version table
+ sql := `
+ CREATE TABLE IF NOT EXISTS schema_version (
+ version INTEGER NOT NULL PRIMARY KEY
+ )`
+
+ if err = TxExec(tx, sql); err != nil {
+ return err
+ }
+
+ // Main file table
+ sql = `
+ CREATE TABLE IF NOT EXISTS file (
+ id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+ directory TEXT NOT NULL,
+ name TEXT NOT NULL,
+ fingerprint TEXT NOT NULL,
+ content_type TEXT NOT NULL,
+ mod_time DATETIME NOT NULL,
+ size INTEGER NOT NULL,
+ is_dir BOOLEAN NOT NULL,
+ is_index BOOLEAN NOT NULL,
+ CONSTRAINT con_file_path UNIQUE (directory, name)
+ )`
+
+ if err = TxExec(tx, sql); err != nil {
+ return err
+ }
+
+ sql = `
+ CREATE VIRTUAL TABLE file_index USING fts5(
+ file_id UNINDEXED,
+ filename,
+ desc,
+ contents
+ )`
+
+ if err = TxExec(tx, sql); err != nil {
+ return err
+ }
+
+ sql = `
+ CREATE TABLE IF NOT EXISTS tag (
+ id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+ name TEXT NOT NULL,
+ CONSTRAINT con_tag_name UNIQUE (name)
+ )`
+
+ if err = TxExec(tx, sql); err != nil {
+ return err
+ }
+
+ sql = "CREATE INDEX IF NOT EXISTS idx_tag_name ON tag(name)"
+ if err = TxExec(tx, sql); err != nil {
+ return err
+ }
+
+ // Tag mapping table
+ sql = `
+ CREATE TABLE IF NOT EXISTS tag_map (
+ file_id INTEGER NOT NULL,
+ tag_id INTEGER NOT NULL,
+ PRIMARY KEY (file_id, tag_id),
+ FOREIGN KEY (file_id) REFERENCES file(id),
+ FOREIGN KEY (tag_id) REFERENCES tag(id)
+ CONSTRAINT con_file_tag UNIQUE (file_id, tag_id)
+ )`
+ if err = TxExec(tx, sql); err != nil {
+ return err
+ }
+
+ // Commit the transaction
+ if err := tx.Commit(); err != nil {
+ return err
+ }
+ return nil
+}
+
+// CreateDatabase creates the db file
+func CreateDatabase(dbfile string) error {
+ fmt.Printf("Creating database %v...\n", dbfile)
+
+ dir := filepath.Dir(dbfile)
+
+ _, err := os.Stat(dir)
+ if err != nil {
+ if os.IsNotExist(err) {
+ // Create directories
+ fmt.Printf("Base directory %v doesn't exist. Creating...\n", dir)
+ if err = os.MkdirAll(dir, 0755); err != nil {
+ return err
+ }
+ } else {
+ return err
+ }
+ }
+
+ file, err := os.Create(dbfile)
+ if err != nil {
+ return err
+ }
+ file.Close()
+
+ sqldb, _ := sqlx.Open("sqlite3", dbfile)
+ defer sqldb.Close()
+
+ if err = CreateTables(sqldb); err != nil {
+ return err
+ }
+ if err = WriteSchemaVersion(sqldb, SchemaVersion); err != nil {
+ return err
+ }
+ return nil
+}
+
+// GetDatabase returns an sqlx conn to db
+func GetDatabase(dbFile string) (*sqlx.DB, error) {
+ fpath := GetFullFilePath(dbFile)
+ _, err := os.Stat(fpath)
+ if err != nil {
+ return nil, err
+ }
+ sqldb, err := sqlx.Open("sqlite3", fpath)
+ if err != nil {
+ return nil, err
+ }
+ return sqldb, nil
+}
+
+// GetDBTransaction returns a db transaction
+func GetDBTransaction(db *sqlx.DB) (*sqlx.Tx, error) {
+ tx, err := db.Beginx()
+ if err != nil {
+ return nil, err
+ }
+ return tx, nil
+}
+
+// NewQueryManager returns a new QueryManager instance
+func NewQueryManager() (*QueryManager, error) {
+ db, err := GetDatabase(TagoRunFlags.DbFile)
+ if err != nil {
+ fmt.Fprintf(
+ os.Stderr,
+ "Unable to open database file. Perhaps you need to run: tago db init\n",
+ )
+ return nil, err
+ }
+ qm := &QueryManager{
+ DB: db,
+ Commit: true,
+ Rollback: true,
+ }
+ err = qm.SchemaCheck()
+ if err != nil {
+ return nil, err
+ }
+ return qm, nil
+}
A => lib/flags.go +10 -0
@@ 0,0 1,10 @@
+package lib
+
+// CLIFlags are global flags set on the command line
+type CLIFlags struct {
+ DbFile string
+ ShowFullPath bool
+}
+
+// TagoRunFlags are global flags set on the CLI
+var TagoRunFlags = &CLIFlags{}
M cmd/helpers.go => lib/helpers.go +22 -2
@@ 1,4 1,4 @@
-package cmd
+package lib
import (
"fmt"
@@ 68,6 68,26 @@ func NewFile(qm *QueryManager, fname str
return file
}
+// NewFiles returns a slice of *File objects
+func NewFiles(qm *QueryManager, fname string) ([]*File, error) {
+ files := make([]*File, 0)
+ matches, err := filepath.Glob(fname)
+ if err != nil {
+ return nil, err
+ }
+
+ if len(matches) > 0 {
+ for _, match := range matches {
+ file := NewFile(qm, match)
+ files = append(files, file)
+ }
+ } else {
+ file := NewFile(qm, fname)
+ files = append(files, file)
+ }
+ return files, nil
+}
+
// ValidateTagName ensures given tag(s) are valid
func ValidateTagName(tag string) (string, error) {
tag = strings.ToLower(tag) // Always lowercase
@@ 91,7 111,7 @@ func ValidateTagName(tag string) (string
// RelativePath shows relative path from the current directory
func RelativePath(path string) string {
- if fullPath {
+ if TagoRunFlags.ShowFullPath {
// Do not use relative path with fullPath is switched on
return path
}
M cmd/types.go => lib/types.go +18 -18
@@ 1,4 1,4 @@
-package cmd
+package lib
import (
"bytes"
@@ 70,7 70,7 @@ func (q *QueryManager) SchemaCheck() err
return err
}
- if curVer != schemaVersion {
+ if curVer != SchemaVersion {
return fmt.Errorf(
"Schema version mismatch. Perhaps you need to run: tago db upgrade",
)
@@ 114,10 114,10 @@ type File struct {
Size int64 `db:"size"`
IsDir bool `db:"is_dir"`
IsIndex bool `db:"is_index"`
+ IndexDesc string `db:"-"`
// Internal use
- needsUpdate bool `db:"-"`
- indexDesc string `db:"-"`
+ needsUpdate bool `db:"-"`
}
// FullPath return file full path
@@ 338,7 338,7 @@ func (f *File) Write(qm *QueryManager) (
WHERE
file_id = ?
`
- res, err = qm.Exec(q, f.Name, f.indexDesc, data, f.ID)
+ res, err = qm.Exec(q, f.Name, f.IndexDesc, data, f.ID)
if err != nil {
return res, err
}
@@ 355,7 355,7 @@ func (f *File) Write(qm *QueryManager) (
q,
f.ID,
f.Name,
- f.indexDesc,
+ f.IndexDesc,
data,
)
if err != nil {
@@ 744,9 744,9 @@ type ftsMatch struct {
// SearchFilesConfig submits search terms, etc.
type SearchFilesConfig struct {
- search string
- ctype string
- tags []string
+ Search string
+ Ctype string
+ Tags []string
}
// FileSearch will search db files and content indexes
@@ 759,7 759,7 @@ func FileSearch(qm *QueryManager, conf *
ftsMatches []ftsMatch
)
- if conf.search == "" && len(conf.tags) == 0 {
+ if conf.Search == "" && len(conf.Tags) == 0 {
return nil, fmt.Errorf("Must specify either a search query or tag(s)")
}
@@ 769,7 769,7 @@ func FileSearch(qm *QueryManager, conf *
FROM
file AS f`
- if conf.search != "" {
+ if conf.Search != "" {
q = `
SELECT
file_id,
@@ 780,7 780,7 @@ func FileSearch(qm *QueryManager, conf *
file_index
WHERE
file_index MATCH ?`
- err = qm.DB.Select(&ftsMatches, q, conf.search)
+ err = qm.DB.Select(&ftsMatches, q, conf.Search)
if err != nil {
return nil, err
}
@@ 789,7 789,7 @@ func FileSearch(qm *QueryManager, conf *
}
}
- if len(conf.tags) > 0 {
+ if len(conf.Tags) > 0 {
query += `
INNER JOIN
tag AS t
@@ 801,7 801,7 @@ func FileSearch(qm *QueryManager, conf *
tm.file_id = f.id
AND
tm.tag_id = t.id`
- nargs = append(nargs, conf.tags)
+ nargs = append(nargs, conf.Tags)
}
if len(ftsMatches) > 0 {
@@ 815,9 815,9 @@ func FileSearch(qm *QueryManager, conf *
nargs = append(nargs, fids)
}
- if conf.ctype != "" {
+ if conf.Ctype != "" {
// User specified content type filter
- ctypeMeta = GetContentTypeQuery(conf.ctype)
+ ctypeMeta = GetContentTypeQuery(conf.Ctype)
if len(ftsMatches) > 0 {
query += `
AND
@@ 845,11 845,11 @@ func FileSearch(qm *QueryManager, conf *
GROUP BY
f.id`
- if len(conf.tags) > 0 {
+ if len(conf.Tags) > 0 {
query += `
HAVING
COUNT(f.id) = ?`
- nargs = append(nargs, len(conf.tags))
+ nargs = append(nargs, len(conf.Tags))
}
query, nargs, err = sqlx.In(query, nargs...)