Add export command and shared item tree
Introduces a pure, FUSE-independent item tree (buildItemTree) shared by the mount and export commands. The new export subcommand downloads Docspell search results into a local directory, mirroring the mount's friendly folder/year/month hierarchy without the by-id directory or metadata JSON file, for archival or migration. - itemtree.go: pure item tree builder shared by mount and export - mount.go: buildMountTree now converts the pure tree into FUSE nodes, with a shared fileNode per attachment across friendly and by-id paths - export.go: export command with concurrent downloads, --dry-run, --skip-existing, --concurrency, --query, --limit, --auth-header - main.go: wires up the export subcommand and its flags - export_test.go: tests for plan collection, writes, skip-existing, failures, modtime - README.md: documents the export command
This commit is contained in:
1 parent
c4a2e9d085
commit
5e88eaea97
6 files changed
+696
-98
No files matched your search
@@ -60,6 +60,12 @@ func main() {
|
||||
fmt.Fprintln(os.Stderr, "mount failed:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
case commands.exportCommand.Used:
|
||||
cfg, password := loadConfigAndPassword()
|
||||
if err := runExportCommand(os.Args[2:], cfg, password); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "export failed:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
case commands.getPathCommand.Used:
|
||||
cfg, password := loadConfigAndPassword()
|
||||
if err := runGetPathCommand(os.Args[2:], cfg, password); err != nil {
|
||||
@@ -80,6 +86,7 @@ func main() {
|
||||
type cliCommands struct {
|
||||
importCommand *flaggy.Subcommand
|
||||
mountCommand *flaggy.Subcommand
|
||||
exportCommand *flaggy.Subcommand
|
||||
getPathCommand *flaggy.Subcommand
|
||||
getURLCommand *flaggy.Subcommand
|
||||
}
|
||||
@@ -93,28 +100,40 @@ func newCLIParser() (*flaggy.Parser, cliCommands) {
|
||||
commands := cliCommands{
|
||||
importCommand: flaggy.NewSubcommand("import"),
|
||||
mountCommand: flaggy.NewSubcommand("mount"),
|
||||
exportCommand: flaggy.NewSubcommand("export"),
|
||||
getPathCommand: flaggy.NewSubcommand("get-path"),
|
||||
getURLCommand: flaggy.NewSubcommand("get-url"),
|
||||
}
|
||||
commands.importCommand.Description = "Scan configured import folders, archive confirmed files, and optionally upload missing files"
|
||||
commands.mountCommand.Description = "Expose Docspell search results as a read-only FUSE filesystem"
|
||||
commands.exportCommand.Description = "Download Docspell search results into a local directory, mirroring the mount hierarchy"
|
||||
commands.getPathCommand.Description = "Resolve a Docspell item ID or URL to the local mounted path"
|
||||
commands.getURLCommand.Description = "Resolve a local mounted path, item ID, or item URL to a Docspell web URL"
|
||||
|
||||
var uploadMissing bool
|
||||
commands.importCommand.Bool(&uploadMissing, "", "upload-missing", "true, to upload files to docspell that do not yet exist there")
|
||||
var query, mountPoint, itemIDOrURL, pathOrID string
|
||||
var query, mountPoint, exportDest, itemIDOrURL, pathOrID string
|
||||
var exportConcurrency int
|
||||
var exportDryRun, exportSkipExisting bool
|
||||
authHeader := defaultDocspellAuthHeader
|
||||
limit := 1000
|
||||
commands.mountCommand.String(&query, "", "query", "Docspell item query to expose")
|
||||
commands.mountCommand.Int(&limit, "", "limit", "number of items to fetch per API request")
|
||||
commands.mountCommand.String(&authHeader, "", "auth-header", "HTTP header used for the Docspell auth token")
|
||||
commands.mountCommand.AddPositionalValue(&mountPoint, "mountpoint", 1, false, "mount point, overriding mountPath from config")
|
||||
commands.exportCommand.String(&query, "", "query", "Docspell item query to expose")
|
||||
commands.exportCommand.Int(&limit, "", "limit", "number of items to fetch per API request")
|
||||
commands.exportCommand.String(&authHeader, "", "auth-header", "HTTP header used for the Docspell auth token")
|
||||
commands.exportCommand.AddPositionalValue(&exportDest, "destination", 1, false, "destination directory for the export")
|
||||
commands.exportCommand.Int(&exportConcurrency, "", "concurrency", "number of attachments to download in parallel")
|
||||
commands.exportCommand.Bool(&exportDryRun, "", "dry-run", "print the files that would be written without downloading")
|
||||
commands.exportCommand.Bool(&exportSkipExisting, "", "skip-existing", "skip files that already exist in the destination")
|
||||
commands.getPathCommand.AddPositionalValue(&itemIDOrURL, "item-id-or-url", 1, true, "Docspell item ID or web URL")
|
||||
commands.getURLCommand.AddPositionalValue(&pathOrID, "local-path-or-item-id", 1, true, "local mounted path, item ID, or item URL")
|
||||
|
||||
parser.AttachSubcommand(commands.importCommand, 1)
|
||||
parser.AttachSubcommand(commands.mountCommand, 1)
|
||||
parser.AttachSubcommand(commands.exportCommand, 1)
|
||||
parser.AttachSubcommand(commands.getPathCommand, 1)
|
||||
parser.AttachSubcommand(commands.getURLCommand, 1)
|
||||
return parser, commands
|
||||
|
||||
Reference in new issue
Block a user