package database import ( "context" "database/sql" "fmt" "time" ) type FileRepository struct { db *DB } func NewFileRepository(db *DB) *FileRepository { return &FileRepository{db: db} } func (r *FileRepository) Create(ctx context.Context, tx *sql.Tx, file *File) error { query := ` INSERT INTO files (path, mtime, ctime, size, mode, uid, gid, link_target) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(path) DO UPDATE SET mtime = excluded.mtime, ctime = excluded.ctime, size = excluded.size, mode = excluded.mode, uid = excluded.uid, gid = excluded.gid, link_target = excluded.link_target ` var err error if tx != nil { _, err = tx.ExecContext(ctx, query, file.Path, file.MTime.Unix(), file.CTime.Unix(), file.Size, file.Mode, file.UID, file.GID, file.LinkTarget) } else { _, err = r.db.ExecWithLock(ctx, query, file.Path, file.MTime.Unix(), file.CTime.Unix(), file.Size, file.Mode, file.UID, file.GID, file.LinkTarget) } if err != nil { return fmt.Errorf("inserting file: %w", err) } return nil } func (r *FileRepository) GetByPath(ctx context.Context, path string) (*File, error) { query := ` SELECT path, mtime, ctime, size, mode, uid, gid, link_target FROM files WHERE path = ? ` var file File var mtimeUnix, ctimeUnix int64 var linkTarget sql.NullString err := r.db.conn.QueryRowContext(ctx, query, path).Scan( &file.Path, &mtimeUnix, &ctimeUnix, &file.Size, &file.Mode, &file.UID, &file.GID, &linkTarget, ) if err == sql.ErrNoRows { return nil, nil } if err != nil { return nil, fmt.Errorf("querying file: %w", err) } file.MTime = time.Unix(mtimeUnix, 0) file.CTime = time.Unix(ctimeUnix, 0) if linkTarget.Valid { file.LinkTarget = linkTarget.String } return &file, nil } func (r *FileRepository) ListModifiedSince(ctx context.Context, since time.Time) ([]*File, error) { query := ` SELECT path, mtime, ctime, size, mode, uid, gid, link_target FROM files WHERE mtime >= ? ORDER BY path ` rows, err := r.db.conn.QueryContext(ctx, query, since.Unix()) if err != nil { return nil, fmt.Errorf("querying files: %w", err) } defer CloseRows(rows) var files []*File for rows.Next() { var file File var mtimeUnix, ctimeUnix int64 var linkTarget sql.NullString err := rows.Scan( &file.Path, &mtimeUnix, &ctimeUnix, &file.Size, &file.Mode, &file.UID, &file.GID, &linkTarget, ) if err != nil { return nil, fmt.Errorf("scanning file: %w", err) } file.MTime = time.Unix(mtimeUnix, 0) file.CTime = time.Unix(ctimeUnix, 0) if linkTarget.Valid { file.LinkTarget = linkTarget.String } files = append(files, &file) } return files, rows.Err() } func (r *FileRepository) Delete(ctx context.Context, tx *sql.Tx, path string) error { query := `DELETE FROM files WHERE path = ?` var err error if tx != nil { _, err = tx.ExecContext(ctx, query, path) } else { _, err = r.db.ExecWithLock(ctx, query, path) } if err != nil { return fmt.Errorf("deleting file: %w", err) } return nil } func (r *FileRepository) ListByPrefix(ctx context.Context, prefix string) ([]*File, error) { query := ` SELECT path, mtime, ctime, size, mode, uid, gid, link_target FROM files WHERE path LIKE ? || '%' ORDER BY path ` rows, err := r.db.conn.QueryContext(ctx, query, prefix) if err != nil { return nil, fmt.Errorf("querying files: %w", err) } defer CloseRows(rows) var files []*File for rows.Next() { var file File var mtimeUnix, ctimeUnix int64 var linkTarget sql.NullString err := rows.Scan( &file.Path, &mtimeUnix, &ctimeUnix, &file.Size, &file.Mode, &file.UID, &file.GID, &linkTarget, ) if err != nil { return nil, fmt.Errorf("scanning file: %w", err) } file.MTime = time.Unix(mtimeUnix, 0) file.CTime = time.Unix(ctimeUnix, 0) if linkTarget.Valid { file.LinkTarget = linkTarget.String } files = append(files, &file) } return files, rows.Err() }