243 lines
6.4 KiB
Go
243 lines
6.4 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
|
|
StoreFilePartition(fileMetadata types.FileMetadata) error
|
|
GetTotalSize() (int64, error)
|
|
RemovePartitionAssignment() error
|
|
GetFileCount() (int64, error)
|
|
GetFiles() ([]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,
|
|
partitionId TEXT DEFAULT ''
|
|
)`,
|
|
},
|
|
{
|
|
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 DeleteDB(appName string) error {
|
|
configPath := configdir.LocalConfig(appName)
|
|
dbPath := path.Join(configPath, "db", fmt.Sprintf("%v.db", appName))
|
|
err := os.Remove(dbPath)
|
|
if err != nil {
|
|
return fmt.Errorf("error deleting db | %w", err)
|
|
}
|
|
return 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) StoreFilePartition(fileMetadata types.FileMetadata) error {
|
|
query := `UPDATE files SET partitionId = ? WHERE name = ? AND path = ? AND hash = ?`
|
|
_, err := d.db.Exec(
|
|
query,
|
|
fileMetadata.PartitionId,
|
|
fileMetadata.Name,
|
|
fileMetadata.Path,
|
|
fileMetadata.Hash,
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("error storing file's partiition | %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *store) RemovePartitionAssignment() error {
|
|
query := `UPDATE files SET partitionId = ''`
|
|
_, err := d.db.Exec(query)
|
|
if err != nil {
|
|
return fmt.Errorf("error removing partition assignment | %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *store) GetTotalSize() (int64, error) {
|
|
var size int64
|
|
query := `SELECT SUM(size) FROM files`
|
|
err := d.db.QueryRow(query).Scan(&size)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("error getting size | %w", err)
|
|
}
|
|
return size, nil
|
|
}
|
|
|
|
func (d *store) GetFileCount() (int64, error) {
|
|
var count int64
|
|
query := `SELECT COUNT(*) FROM files`
|
|
err := d.db.QueryRow(query).Scan(&count)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("error getting count | %w", err)
|
|
}
|
|
return count, nil
|
|
}
|
|
|
|
func (d *store) GetFiles() ([]types.FileMetadata, error) {
|
|
var files []types.FileMetadata
|
|
query := `SELECT name, path, size, hash, modifiedDate, backedUp, partitionId FROM files order by path, name`
|
|
rows, err := d.db.Query(query)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error getting files | %w", err)
|
|
}
|
|
defer rows.Close()
|
|
for rows.Next() {
|
|
var file types.FileMetadata
|
|
err := rows.Scan(&file.Name, &file.Path, &file.Size, &file.Hash, &file.ModifiedDate, &file.BackedUp, &file.PartitionId)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error scanning file | %w", err)
|
|
}
|
|
files = append(files, file)
|
|
}
|
|
return files, 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 {
|
|
return "", fmt.Errorf("error creating config directory: %w", 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 {
|
|
return "", fmt.Errorf("error creating db directory: %w", 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
|
|
}
|