Scafolding for archive-inspect

This commit is contained in:
Neil Macneale IV
2025-09-15 17:52:22 +00:00
parent 26166b56e9
commit 09b92f952c
3 changed files with 236 additions and 0 deletions
+1
View File
@@ -26,5 +26,6 @@ var Commands = cli.NewHiddenSubCommandHandler("admin", "Commands for directly wo
StorageCmd{},
NewGenToOldGenCmd{},
ConjoinCmd{},
ArchiveInspectCmd{},
createchunk.Commands,
})
@@ -0,0 +1,138 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package admin
import (
"context"
"encoding/json"
"os"
"path/filepath"
"strings"
"github.com/dolthub/dolt/go/cmd/dolt/cli"
"github.com/dolthub/dolt/go/libraries/doltcore/env"
"github.com/dolthub/dolt/go/libraries/utils/argparser"
"github.com/dolthub/dolt/go/store/nbs"
)
type ArchiveInspectCmd struct {
}
func (cmd ArchiveInspectCmd) Name() string {
return "archive-inspect"
}
func (cmd ArchiveInspectCmd) Description() string {
return "Inspect a Dolt archive (.darc) file and display basic information about it."
}
func (cmd ArchiveInspectCmd) RequiresRepo() bool {
return false
}
func (cmd ArchiveInspectCmd) Docs() *cli.CommandDocumentation {
return nil
}
func (cmd ArchiveInspectCmd) ArgParser() *argparser.ArgParser {
ap := argparser.NewArgParserWithMaxArgs(cmd.Name(), 1)
ap.SupportsString("archive-path", "", "archive_path", "Full path to the archive file (.darc) to inspect")
ap.SupportsFlag("mmap", "", "Enable memory-mapped index reading for better performance")
return ap
}
func (cmd ArchiveInspectCmd) Exec(ctx context.Context, commandStr string, args []string, dEnv *env.DoltEnv, cliCtx cli.CliContext) int {
ap := cmd.ArgParser()
usage, _ := cli.HelpAndUsagePrinters(cli.CommandDocsForCommandString(commandStr, cli.CommandDocumentationContent{}, ap))
apr := cli.ParseArgsOrDie(ap, args, usage)
var archivePath string
if archivePathArg, ok := apr.GetValue("archive-path"); ok {
archivePath = archivePathArg
} else if apr.NArg() == 1 {
archivePath = apr.Arg(0)
} else {
usage()
return 1
}
// Check if file exists
if _, err := os.Stat(archivePath); os.IsNotExist(err) {
cli.PrintErrln("Error: Archive file does not exist:", archivePath)
return 1
}
// Check if file has .darc extension
if !strings.HasSuffix(strings.ToLower(archivePath), nbs.ArchiveFileSuffix) {
cli.PrintErrln("Warning: File does not have .darc extension")
}
// Make path absolute
absPath, err := filepath.Abs(archivePath)
if err != nil {
cli.PrintErrln("Error getting absolute path:", err.Error())
return 1
}
// Check for mmap flag
enableMmap := apr.Contains("mmap")
// Create archive inspector
inspector, err := nbs.NewArchiveInspectorFromFileWithMmap(ctx, absPath, enableMmap)
if err != nil {
cli.PrintErrln("Error opening archive file:", err.Error())
return 1
}
defer inspector.Close()
// Display basic archive information
cli.Println("Archive file:", absPath)
cli.Println("Archive loaded successfully!")
cli.Println()
// Basic file information
cli.Printf("File size: %d bytes\n", inspector.FileSize())
cli.Printf("Format version: %d\n", inspector.FormatVersion())
cli.Printf("File signature: %s\n", inspector.FileSignature())
cli.Println()
// Archive structure information
cli.Printf("Chunk count: %d\n", inspector.ChunkCount())
cli.Printf("Byte span count: %d\n", inspector.ByteSpanCount())
cli.Printf("Index size: %d bytes\n", inspector.IndexSize())
cli.Printf("Metadata size: %d bytes\n", inspector.MetadataSize())
// Display metadata if present
if inspector.MetadataSize() > 0 {
cli.Println()
cli.Println("Metadata:")
metadataBytes, err := inspector.GetMetadata(ctx)
if err != nil {
cli.PrintErrln("Error reading metadata:", err.Error())
} else {
// Try to parse as JSON and pretty print
var metadataObj interface{}
if err := json.Unmarshal(metadataBytes, &metadataObj); err == nil {
prettyJSON, _ := json.MarshalIndent(metadataObj, " ", " ")
cli.Printf(" %s\n", string(prettyJSON))
} else {
// If not JSON, just print raw bytes
cli.Printf(" %s\n", string(metadataBytes))
}
}
}
return 0
}
+97
View File
@@ -0,0 +1,97 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package nbs
import (
"context"
"github.com/dolthub/dolt/go/store/hash"
)
// ArchiveInspector provides a way to inspect archive files from outside the nbs package
type ArchiveInspector struct {
reader archiveReader
}
// NewArchiveInspectorFromFile creates an ArchiveInspector from a file path with mmap enabled by default
func NewArchiveInspectorFromFile(ctx context.Context, archivePath string) (*ArchiveInspector, error) {
return NewArchiveInspectorFromFileWithMmap(ctx, archivePath, true)
}
// NewArchiveInspectorFromFileWithMmap creates an ArchiveInspector from a file path with configurable mmap
func NewArchiveInspectorFromFileWithMmap(ctx context.Context, archivePath string, enableMmap bool) (*ArchiveInspector, error) {
fra, err := newFileReaderAt(archivePath, enableMmap)
if err != nil {
return nil, err
}
// Use a dummy hash since we're just inspecting
dummyHash := hash.Hash{}
stats := &Stats{}
archiveReader, err := newArchiveReader(ctx, fra, dummyHash, uint64(fra.sz), stats)
if err != nil {
fra.Close()
return nil, err
}
return &ArchiveInspector{reader: archiveReader}, nil
}
// Close releases resources associated with the archive inspector
func (ai *ArchiveInspector) Close() error {
return ai.reader.close()
}
// ChunkCount returns the number of chunks in the archive
func (ai *ArchiveInspector) ChunkCount() uint32 {
return ai.reader.count()
}
// FormatVersion returns the format version of the archive
func (ai *ArchiveInspector) FormatVersion() uint8 {
return ai.reader.footer.formatVersion
}
// FileSignature returns the file signature of the archive
func (ai *ArchiveInspector) FileSignature() string {
return ai.reader.footer.fileSignature
}
// IndexSize returns the size of the index section in bytes
func (ai *ArchiveInspector) IndexSize() uint64 {
return ai.reader.footer.indexSize
}
// MetadataSize returns the size of the metadata section in bytes
func (ai *ArchiveInspector) MetadataSize() uint32 {
return ai.reader.footer.metadataSize
}
// FileSize returns the total size of the archive file
func (ai *ArchiveInspector) FileSize() uint64 {
return ai.reader.footer.fileSize
}
// ByteSpanCount returns the number of byte spans in the archive
func (ai *ArchiveInspector) ByteSpanCount() uint32 {
return ai.reader.footer.byteSpanCount
}
// GetMetadata retrieves the metadata from the archive as raw bytes
func (ai *ArchiveInspector) GetMetadata(ctx context.Context) ([]byte, error) {
stats := &Stats{}
return ai.reader.getMetadata(ctx, stats)
}