Files
forever-files/db/db.go
2023-06-05 00:47:27 -06:00

153 lines
4.1 KiB
Go

package db
import (
"database/sql"
"fmt"
"os"
"path"
"forever-files/types"
"github.com/kirsle/configdir"
_ "github.com/mattn/go-sqlite3"
)
type DB interface {
Close() error
Migrate() error
StoreFile(fileMetadata types.FileMetadata) error
RemoveFile(fileMetadata types.FileMetadata) error
}
type store struct {
db *sql.DB
}
type Migrations struct {
name string
query string
}
var migrations = []Migrations{
{
name: "001-sourceFiles",
query: `CREATE TABLE IF NOT EXISTS files (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
path TEXT NOT NULL,
size INTEGER NOT NULL,
hash TEXT NOT NULL,
modifiedDate TIMESTAMP NOT NULL,
backedUp BOOLEAN NOT NULL
)`,
},
{
name: "002-fileUniqueConstraint",
query: `CREATE UNIQUE INDEX IF NOT EXISTS file_unique ON files (name, path, hash)`,
},
}
func NewDB(appName string) (DB, error) {
dbPath, err := createDBFileIfNotExist(appName)
if err != nil {
return nil, fmt.Errorf("error creating db file %w", err)
}
dbSQL, err := sql.Open("sqlite3", dbPath)
if err != nil {
return nil, fmt.Errorf("error opening db | %w", err)
}
return &store{
db: dbSQL,
}, nil
}
func (d *store) StoreFile(fileMetadata types.FileMetadata) error {
query := `INSERT INTO files (name, path, size, hash, modifiedDate, backedUp) VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT (name, path, hash) DO UPDATE SET size = ?, modifiedDate = ?, backedUp = ?`
_, err := d.db.Exec(query, fileMetadata.Name, fileMetadata.Path, fileMetadata.Size, fileMetadata.Hash, fileMetadata.ModifiedDate, fileMetadata.BackedUp, fileMetadata.Size, fileMetadata.ModifiedDate, fileMetadata.BackedUp)
if err != nil {
return fmt.Errorf("error storing file metadata | %w", err)
}
return nil
}
func (d *store) RemoveFile(fileMetadata types.FileMetadata) error {
query := `DELETE FROM files WHERE name = ? AND path = ? AND hash = ?`
_, err := d.db.Exec(query, fileMetadata.Name, fileMetadata.Path, fileMetadata.Hash)
if err != nil {
return fmt.Errorf("error removing file metadata | %w", err)
}
return nil
}
func (d *store) Close() error {
return d.db.Close()
}
func (d *store) Migrate() error {
// check if migration table exists
var migrationsCheck string
//goland:noinspection SqlResolve
err := d.db.QueryRow("SELECT name FROM sqlite_master WHERE type='table' AND name='migrations'").Scan(&migrationsCheck)
if err != nil {
if err == sql.ErrNoRows {
_, err := d.db.Exec("CREATE TABLE migrations (name TEXT NOT NULL)")
if err != nil {
return fmt.Errorf("error creating migrations table | %w", err)
}
} else {
return fmt.Errorf("error checking if migrations table exists | %w", err)
}
}
for _, migration := range migrations {
var migrationInHistory string
err = d.db.QueryRow("SELECT name FROM migrations WHERE name = ?", migration.name).Scan(&migrationInHistory)
if err != nil {
if err == sql.ErrNoRows {
_, err := d.db.Exec(migration.query)
if err != nil {
return fmt.Errorf("error running migration: %s | %w", migration.name, err)
}
_, err = d.db.Exec("INSERT INTO migrations (name) VALUES (?)", migration.name)
if err != nil {
return fmt.Errorf("error inserting migration: %s into migrations table | %w", migration.name, err)
}
} else {
return fmt.Errorf("error checking if migration: %s has been run | %w", migration.name, err)
}
}
}
return nil
}
func createDBFileIfNotExist(appName string) (string, error) {
configPath := configdir.LocalConfig(appName)
// set up the config directory
err := configdir.MakePath(configPath)
if err != nil {
panic(err)
}
dbDirectoryPath := path.Join(configPath, "db")
dbPath := path.Join(configPath, "db", fmt.Sprintf("%v.db", appName))
// Set up the database
err = configdir.MakePath(dbDirectoryPath)
if err != nil {
panic(err)
}
// If the file doesn't exist, create it, or append to the file
f, err := os.OpenFile(dbPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return "", fmt.Errorf("error opening file: %v", err)
}
defer func(f *os.File) {
err := f.Close()
if err != nil {
fmt.Println("error closing file")
}
}(f)
return dbPath, nil
}