# HG changeset patch # User Peter Sanchez # Date 1626311581 25200 # Wed Jul 14 18:13:01 2021 -0700 # Node ID 074d83a71249f5b89b0d4e85cecf1b1373bbd62d # Parent 94dc3dfbbb598a2ef3bf581fdbc4a0fd05679a30 # Parent b8cd732b747d10403dbf58952abfce3743b52611 Merging in refactor branch diff --git a/cmd/clear.go b/cmd/clear.go --- a/cmd/clear.go +++ b/cmd/clear.go @@ -4,6 +4,7 @@ "fmt" "github.com/spf13/cobra" + "hg.code.netlandish.com/~petersanchez/tago/lib" ) func init() { @@ -38,15 +39,15 @@ // 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 @@ 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 } diff --git a/cmd/db.go b/cmd/db.go --- a/cmd/db.go +++ b/cmd/db.go @@ -3,15 +3,12 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 upgradeSchemaToVersion2(qm *QueryManager) error { // Placeholder until an upgrade is actually needed - // WriteSchemaVersion + // lib.WriteSchemaVersion return nil } */ diff --git a/cmd/delete.go b/cmd/delete.go --- a/cmd/delete.go +++ b/cmd/delete.go @@ -4,6 +4,7 @@ "fmt" "github.com/spf13/cobra" + "hg.code.netlandish.com/~petersanchez/tago/lib" ) func init() { @@ -38,15 +39,15 @@ // 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 @@ 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 } diff --git a/cmd/files.go b/cmd/files.go --- a/cmd/files.go +++ b/cmd/files.go @@ -4,6 +4,7 @@ "fmt" "github.com/spf13/cobra" + "hg.code.netlandish.com/~petersanchez/tago/lib" ) func init() { @@ -23,7 +24,7 @@ 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 @@ 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 @@ 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.") diff --git a/cmd/merge.go b/cmd/merge.go --- a/cmd/merge.go +++ b/cmd/merge.go @@ -4,6 +4,7 @@ "fmt" "github.com/spf13/cobra" + "hg.code.netlandish.com/~petersanchez/tago/lib" ) func init() { @@ -21,20 +22,20 @@ // 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 diff --git a/cmd/root.go b/cmd/root.go --- a/cmd/root.go +++ b/cmd/root.go @@ -4,6 +4,7 @@ "os" "github.com/spf13/cobra" + "hg.code.netlandish.com/~petersanchez/tago/lib" ) var ( @@ -16,6 +17,10 @@ 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() { diff --git a/cmd/search.go b/cmd/search.go --- a/cmd/search.go +++ b/cmd/search.go @@ -5,6 +5,7 @@ "github.com/google/shlex" "github.com/spf13/cobra" + "hg.code.netlandish.com/~petersanchez/tago/lib" ) func init() { @@ -26,9 +27,9 @@ 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 @@ // 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 @@ 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.") diff --git a/cmd/stats.go b/cmd/stats.go --- a/cmd/stats.go +++ b/cmd/stats.go @@ -4,6 +4,7 @@ "fmt" "github.com/spf13/cobra" + "hg.code.netlandish.com/~petersanchez/tago/lib" ) func init() { @@ -21,7 +22,7 @@ // 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 @@ 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 diff --git a/cmd/tag.go b/cmd/tag.go --- a/cmd/tag.go +++ b/cmd/tag.go @@ -5,6 +5,7 @@ "github.com/google/shlex" "github.com/spf13/cobra" + "hg.code.netlandish.com/~petersanchez/tago/lib" ) func init() { @@ -40,7 +41,7 @@ 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 @@ 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 @@ 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 @@ } // 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 diff --git a/cmd/tags.go b/cmd/tags.go --- a/cmd/tags.go +++ b/cmd/tags.go @@ -4,6 +4,7 @@ "fmt" "github.com/spf13/cobra" + "hg.code.netlandish.com/~petersanchez/tago/lib" ) func init() { @@ -21,15 +22,15 @@ // 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 @@ } 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) } diff --git a/lib/db.go b/lib/db.go new file mode 100644 --- /dev/null +++ b/lib/db.go @@ -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 +} diff --git a/lib/flags.go b/lib/flags.go new file mode 100644 --- /dev/null +++ b/lib/flags.go @@ -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{} diff --git a/cmd/helpers.go b/lib/helpers.go rename from cmd/helpers.go rename to lib/helpers.go --- a/cmd/helpers.go +++ b/lib/helpers.go @@ -1,4 +1,4 @@ -package cmd +package lib import ( "fmt" @@ -68,6 +68,26 @@ 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 @@ // 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 } diff --git a/cmd/types.go b/lib/types.go rename from cmd/types.go rename to lib/types.go --- a/cmd/types.go +++ b/lib/types.go @@ -1,4 +1,4 @@ -package cmd +package lib import ( "bytes" @@ -70,7 +70,7 @@ 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 @@ 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 @@ 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 @@ q, f.ID, f.Name, - f.indexDesc, + f.IndexDesc, data, ) if err != nil { @@ -744,9 +744,9 @@ // 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 @@ 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 @@ FROM file AS f` - if conf.search != "" { + if conf.Search != "" { q = ` SELECT file_id, @@ -780,7 +780,7 @@ 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 @@ } } - if len(conf.tags) > 0 { + if len(conf.Tags) > 0 { query += ` INNER JOIN tag AS t @@ -801,7 +801,7 @@ 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 @@ 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 @@ 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...)