diff --git a/cmd/stbak/cmd/archive.go b/cmd/stbak/cmd/archive.go index 544dc8c..ce04852 100644 --- a/cmd/stbak/cmd/archive.go +++ b/cmd/stbak/cmd/archive.go @@ -14,6 +14,7 @@ import ( "github.com/pojntfx/stfs/pkg/persisters" "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/volatiletech/sqlboiler/v4/boil" ) const ( @@ -31,6 +32,10 @@ var archiveCmd = &cobra.Command{ return err } + if viper.GetBool(verboseFlag) { + boil.DebugMode = true + } + metadataPersister := persisters.NewMetadataPersister(viper.GetString(metadataFlag)) if err := metadataPersister.Open(); err != nil { return err diff --git a/cmd/stbak/cmd/eject.go b/cmd/stbak/cmd/eject.go index 75e28b7..43730dc 100644 --- a/cmd/stbak/cmd/eject.go +++ b/cmd/stbak/cmd/eject.go @@ -6,6 +6,7 @@ import ( "github.com/pojntfx/stfs/pkg/controllers" "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/volatiletech/sqlboiler/v4/boil" ) var ejectCmd = &cobra.Command{ @@ -17,6 +18,10 @@ var ejectCmd = &cobra.Command{ return err } + if viper.GetBool(verboseFlag) { + boil.DebugMode = true + } + f, err := os.OpenFile(viper.GetString(tapeFlag), os.O_RDONLY, os.ModeCharDevice) if err != nil { panic(err) diff --git a/cmd/stbak/cmd/index.go b/cmd/stbak/cmd/index.go index 4456894..da0fec9 100644 --- a/cmd/stbak/cmd/index.go +++ b/cmd/stbak/cmd/index.go @@ -16,6 +16,7 @@ import ( "github.com/pojntfx/stfs/pkg/persisters" "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/volatiletech/sqlboiler/v4/boil" ) var indexCmd = &cobra.Command{ @@ -27,6 +28,10 @@ var indexCmd = &cobra.Command{ return err } + if viper.GetBool(verboseFlag) { + boil.DebugMode = true + } + return index( viper.GetString(tapeFlag), viper.GetString(metadataFlag), diff --git a/cmd/stbak/cmd/list.go b/cmd/stbak/cmd/list.go index 57ce718..798f49a 100644 --- a/cmd/stbak/cmd/list.go +++ b/cmd/stbak/cmd/list.go @@ -11,6 +11,7 @@ import ( "github.com/pojntfx/stfs/pkg/formatting" "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/volatiletech/sqlboiler/v4/boil" ) var listCmd = &cobra.Command{ @@ -22,6 +23,10 @@ var listCmd = &cobra.Command{ return err } + if viper.GetBool(verboseFlag) { + boil.DebugMode = true + } + f, isRegular, err := openTapeReadOnly(viper.GetString(tapeFlag)) if err != nil { return err diff --git a/cmd/stbak/cmd/move.go b/cmd/stbak/cmd/move.go new file mode 100644 index 0000000..4f22dc8 --- /dev/null +++ b/cmd/stbak/cmd/move.go @@ -0,0 +1,105 @@ +package cmd + +import ( + "archive/tar" + "context" + "strings" + + "github.com/pojntfx/stfs/pkg/converters" + models "github.com/pojntfx/stfs/pkg/db/sqlite/models/metadata" + "github.com/pojntfx/stfs/pkg/formatting" + "github.com/pojntfx/stfs/pkg/pax" + "github.com/pojntfx/stfs/pkg/persisters" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/volatiletech/sqlboiler/v4/boil" +) + +var moveCmd = &cobra.Command{ + Use: "move", + Aliases: []string{"m"}, + Short: "Move a file from tape or tar file and index", + RunE: func(cmd *cobra.Command, args []string) error { + if err := viper.BindPFlags(cmd.PersistentFlags()); err != nil { + return err + } + + if viper.GetBool(verboseFlag) { + boil.DebugMode = true + } + + dirty := false + tw, _, cleanup, err := openTapeWriter(viper.GetString(tapeFlag)) + if err != nil { + return err + } + defer cleanup(&dirty) + + metadataPersister := persisters.NewMetadataPersister(viper.GetString(metadataFlag)) + if err := metadataPersister.Open(); err != nil { + return err + } + + headersToMove := []*models.Header{} + dbhdr, err := metadataPersister.GetHeader(context.Background(), viper.GetString(srcFlag)) + if err != nil { + return err + } + headersToMove = append(headersToMove, dbhdr) + + // If the header refers to a directory, get it's children + if dbhdr.Typeflag == tar.TypeDir { + dbhdrs, err := metadataPersister.GetHeaderChildren(context.Background(), viper.GetString(srcFlag)) + if err != nil { + return err + } + + headersToMove = append(headersToMove, dbhdrs...) + } + + // Move the headers in the index + if err := metadataPersister.MoveHeaders(context.Background(), headersToMove, viper.GetString(srcFlag), viper.GetString(dstFlag)); err != nil { + return nil + } + + if err := formatting.PrintCSV(formatting.TARHeaderCSV); err != nil { + return err + } + + // Append deletion headers to the tape/tar file + for _, dbhdr := range headersToMove { + hdr, err := converters.DBHeaderToTarHeader(dbhdr) + if err != nil { + return err + } + + hdr.Size = 0 // Don't try to seek after the record + hdr.Name = strings.TrimSuffix(viper.GetString(dstFlag), "/") + strings.TrimPrefix(hdr.Name, strings.TrimSuffix(viper.GetString(srcFlag), "/")) + hdr.PAXRecords[pax.STFSRecordVersion] = pax.STFSRecordVersion1 + hdr.PAXRecords[pax.STFSRecordAction] = pax.STFSRecordActionUpdate + hdr.PAXRecords[pax.STFSRecordReplacesName] = viper.GetString(srcFlag) + + if err := tw.WriteHeader(hdr); err != nil { + return err + } + + dirty = true + + if err := formatting.PrintCSV(formatting.GetTARHeaderAsCSV(-1, -1, hdr)); err != nil { + return err + } + } + + return nil + }, +} + +func init() { + moveCmd.PersistentFlags().IntP(recordSizeFlag, "e", 20, "Amount of 512-bit blocks per record") + moveCmd.PersistentFlags().StringP(srcFlag, "s", "", "Current path of the file or directory to move") + moveCmd.PersistentFlags().StringP(dstFlag, "d", "", "Path to move the file or directory to") + + viper.AutomaticEnv() + + rootCmd.AddCommand(moveCmd) +} diff --git a/cmd/stbak/cmd/query.go b/cmd/stbak/cmd/query.go index e92065f..700f29c 100644 --- a/cmd/stbak/cmd/query.go +++ b/cmd/stbak/cmd/query.go @@ -8,6 +8,7 @@ import ( "github.com/pojntfx/stfs/pkg/persisters" "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/volatiletech/sqlboiler/v4/boil" ) var queryCmd = &cobra.Command{ @@ -19,6 +20,10 @@ var queryCmd = &cobra.Command{ return err } + if viper.GetBool(verboseFlag) { + boil.DebugMode = true + } + metadataPersister := persisters.NewMetadataPersister(viper.GetString(metadataFlag)) if err := metadataPersister.Open(); err != nil { return err diff --git a/cmd/stbak/cmd/remove.go b/cmd/stbak/cmd/remove.go index 4eb9532..a28951c 100644 --- a/cmd/stbak/cmd/remove.go +++ b/cmd/stbak/cmd/remove.go @@ -15,6 +15,7 @@ import ( "github.com/pojntfx/stfs/pkg/persisters" "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/volatiletech/sqlboiler/v4/boil" ) const ( @@ -30,6 +31,10 @@ var removeCmd = &cobra.Command{ return err } + if viper.GetBool(verboseFlag) { + boil.DebugMode = true + } + dirty := false tw, _, cleanup, err := openTapeWriter(viper.GetString(tapeFlag)) if err != nil { diff --git a/cmd/stbak/cmd/restore.go b/cmd/stbak/cmd/restore.go index 5b4964f..4c8ea86 100644 --- a/cmd/stbak/cmd/restore.go +++ b/cmd/stbak/cmd/restore.go @@ -11,6 +11,7 @@ import ( "github.com/pojntfx/stfs/pkg/formatting" "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/volatiletech/sqlboiler/v4/boil" ) const ( @@ -29,6 +30,10 @@ var restoreCmd = &cobra.Command{ return err } + if viper.GetBool(verboseFlag) { + boil.DebugMode = true + } + f, isRegular, err := openTapeReadOnly(viper.GetString(tapeFlag)) if err != nil { return err diff --git a/cmd/stbak/cmd/root.go b/cmd/stbak/cmd/root.go index f2c1517..1f0acb1 100644 --- a/cmd/stbak/cmd/root.go +++ b/cmd/stbak/cmd/root.go @@ -12,6 +12,7 @@ import ( const ( tapeFlag = "tape" metadataFlag = "metadata" + verboseFlag = "verbose" ) var rootCmd = &cobra.Command{ @@ -37,6 +38,7 @@ func Execute() { rootCmd.PersistentFlags().StringP(tapeFlag, "t", "/dev/nst0", "Tape or tar file to use") rootCmd.PersistentFlags().StringP(metadataFlag, "m", metadataPath, "Metadata database to use") + rootCmd.PersistentFlags().BoolP(verboseFlag, "v", false, "Enable verbose logging") if err := viper.BindPFlags(rootCmd.PersistentFlags()); err != nil { panic(err) diff --git a/cmd/stbak/cmd/tell.go b/cmd/stbak/cmd/tell.go index 94e339c..aba9359 100644 --- a/cmd/stbak/cmd/tell.go +++ b/cmd/stbak/cmd/tell.go @@ -7,6 +7,7 @@ import ( "github.com/pojntfx/stfs/pkg/controllers" "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/volatiletech/sqlboiler/v4/boil" ) var tellCmd = &cobra.Command{ @@ -18,6 +19,10 @@ var tellCmd = &cobra.Command{ return err } + if viper.GetBool(verboseFlag) { + boil.DebugMode = true + } + f, err := os.OpenFile(viper.GetString(tapeFlag), os.O_RDONLY, os.ModeCharDevice) if err != nil { panic(err) diff --git a/pkg/db/sqlite/migrations/metadata/migrations.go b/pkg/db/sqlite/migrations/metadata/migrations.go index ed338d2..75a59f9 100644 --- a/pkg/db/sqlite/migrations/metadata/migrations.go +++ b/pkg/db/sqlite/migrations/metadata/migrations.go @@ -30,7 +30,7 @@ var _db_sqlite_migrations_metadata_1637447083_sql = []byte("\x1f\x8b\x08\x00\x00 func db_sqlite_migrations_metadata_1637447083_sql() ([]byte, error) { return bindata_read( _db_sqlite_migrations_metadata_1637447083_sql, - "../../../db/sqlite/migrations/metadata/1637447083.sql", + "../../db/sqlite/migrations/metadata/1637447083.sql", ) } @@ -56,7 +56,7 @@ func AssetNames() []string { // _bindata is a table, holding each asset generator, mapped to its name. var _bindata = map[string]func() ([]byte, error){ - "../../../db/sqlite/migrations/metadata/1637447083.sql": db_sqlite_migrations_metadata_1637447083_sql, + "../../db/sqlite/migrations/metadata/1637447083.sql": db_sqlite_migrations_metadata_1637447083_sql, } // AssetDir returns the file names below a certain // directory embedded in the file by go-bindata. @@ -100,13 +100,11 @@ type _bintree_t struct { var _bintree = &_bintree_t{nil, map[string]*_bintree_t{ "..": &_bintree_t{nil, map[string]*_bintree_t{ "..": &_bintree_t{nil, map[string]*_bintree_t{ - "..": &_bintree_t{nil, map[string]*_bintree_t{ - "db": &_bintree_t{nil, map[string]*_bintree_t{ - "sqlite": &_bintree_t{nil, map[string]*_bintree_t{ - "migrations": &_bintree_t{nil, map[string]*_bintree_t{ - "metadata": &_bintree_t{nil, map[string]*_bintree_t{ - "1637447083.sql": &_bintree_t{db_sqlite_migrations_metadata_1637447083_sql, map[string]*_bintree_t{ - }}, + "db": &_bintree_t{nil, map[string]*_bintree_t{ + "sqlite": &_bintree_t{nil, map[string]*_bintree_t{ + "migrations": &_bintree_t{nil, map[string]*_bintree_t{ + "metadata": &_bintree_t{nil, map[string]*_bintree_t{ + "1637447083.sql": &_bintree_t{db_sqlite_migrations_metadata_1637447083_sql, map[string]*_bintree_t{ }}, }}, }}, diff --git a/pkg/db/sqlite/models/metadata/boil_main_test.go b/pkg/db/sqlite/models/metadata/boil_main_test.go index 0b5794a..361c0f2 100644 --- a/pkg/db/sqlite/models/metadata/boil_main_test.go +++ b/pkg/db/sqlite/models/metadata/boil_main_test.go @@ -21,7 +21,7 @@ import ( var flagDebugMode = flag.Bool("test.sqldebug", false, "Turns on debug mode for SQL statements") var flagConfigFile = flag.String("test.config", "", "Overrides the default config") -const outputDirDepth = 8 +const outputDirDepth = 5 var ( dbMain tester diff --git a/pkg/persisters/metadata.go b/pkg/persisters/metadata.go index 3ff1c64..21423d0 100644 --- a/pkg/persisters/metadata.go +++ b/pkg/persisters/metadata.go @@ -1,7 +1,7 @@ package persisters -//go:generate sqlboiler sqlite3 -o ../db/sqlite/models/metadata -c ../../../configs/sqlboiler/metadata.yaml -//go:generate go-bindata -pkg metadata -o ../db/sqlite/migrations/metadata/migrations.go ../../../db/sqlite/migrations/metadata +//go:generate sqlboiler sqlite3 -o ../db/sqlite/models/metadata -c ../../configs/sqlboiler/metadata.yaml +//go:generate go-bindata -pkg metadata -o ../db/sqlite/migrations/metadata/migrations.go ../../db/sqlite/migrations/metadata import ( "context" @@ -28,7 +28,7 @@ func NewMetadataPersister(dbPath string) *MetadataPersister { Migrations: migrate.AssetMigrationSource{ Asset: metadata.Asset, AssetDir: metadata.AssetDir, - Dir: "../../../db/sqlite/migrations/metadata", + Dir: "../../db/sqlite/migrations/metadata", }, }, } @@ -75,26 +75,41 @@ func (p *MetadataPersister) UpdateHeaderMetadata(ctx context.Context, dbhdr *mod return nil } -func (p *MetadataPersister) MoveHeader(ctx context.Context, oldName string, newName string) error { - dbhdr, err := models.FindHeader(ctx, p.db, oldName, models.HeaderColumns.Name) - if err == sql.ErrNoRows { - return nil // We may have renamed the header in a later, but indexed record/block, so we can skip this +func (p *MetadataPersister) moveHeader(ctx context.Context, tx boil.ContextExecutor, oldName string, newName string) error { + // We can't do this with `dbhdr.Update` because we are renaming the primary key + if _, err := queries.Raw( + fmt.Sprintf( + ` update %v set %v = ? where %v = ?;`, + models.TableNames.Headers, + models.HeaderColumns.Name, + models.HeaderColumns.Name, + ), + newName, + oldName, + ).ExecContext(ctx, tx); err != nil { + return err } + return nil +} + +func (p *MetadataPersister) MoveHeader(ctx context.Context, oldName string, newName string) error { + return p.moveHeader(ctx, p.db, oldName, newName) +} + +func (p *MetadataPersister) MoveHeaders(ctx context.Context, hdrs models.HeaderSlice, oldName string, newName string) error { + tx, err := p.db.BeginTx(ctx, nil) if err != nil { return err } - // Update the name - dbhdr.Name = newName - - if _, err := dbhdr.Update(ctx, p.db, boil.Infer()); err != nil { - return err + for _, hdr := range hdrs { + if err := p.moveHeader(ctx, tx, hdr.Name, strings.TrimSuffix(newName, "/")+strings.TrimPrefix(hdr.Name, strings.TrimSuffix(oldName, "/"))); err != nil { + return err + } } - // TODO: Update children's names too - - return nil + return tx.Commit() } func (p *MetadataPersister) GetHeaders(ctx context.Context) (models.HeaderSlice, error) {