diff --git a/internal/cmd/docs_tab_export.go b/internal/cmd/docs_tab_export.go index 726ad8cce..ffccfbe53 100644 --- a/internal/cmd/docs_tab_export.go +++ b/internal/cmd/docs_tab_export.go @@ -66,10 +66,11 @@ func isGoogleAuthHost(host string) bool { } type tabExportParams struct { - DocID string - OutFlag string - Format string - TabQuery string + DocID string + OutFlag string + Format string + TabQuery string + Overwrite bool } // sanitizeFilenameComponent replaces characters unsafe for filenames with @@ -181,10 +182,11 @@ func runDocsTabExport(ctx context.Context, flags *RootFlags, p tabExportParams) } if dryErr := dryRunExit(ctx, flags, "docs.tab-export", map[string]any{ - "docID": p.DocID, - "tab": p.TabQuery, - "format": format, - "out": outPath, + "docID": p.DocID, + "tab": p.TabQuery, + "format": format, + "out": outPath, + "overwrite": p.Overwrite, }); dryErr != nil { return dryErr } @@ -234,7 +236,11 @@ func runDocsTabExport(ctx context.Context, flags *RootFlags, p tabExportParams) return copyErr } - f, outPath, writeErr := createUserOutputFile(outPath) + f, outPath, writeErr := openUserOutputFile(outPath, outputFileOptions{ + Overwrite: p.Overwrite, + FileMode: 0o600, + DirMode: 0o700, + }) if writeErr != nil { return writeErr } diff --git a/internal/cmd/drive_download.go b/internal/cmd/drive_download.go index 46a82a82a..f59214987 100644 --- a/internal/cmd/drive_download.go +++ b/internal/cmd/drive_download.go @@ -17,10 +17,11 @@ import ( ) type DriveDownloadCmd struct { - FileID string `arg:"" name:"fileId" help:"File ID"` - Output OutputPathFlag `embed:""` - Format string `name:"format" help:"Export format for Google Docs files: pdf|csv|xlsx|pptx|txt|png|docx|md (default: inferred)"` - Tab string `name:"tab" help:"(experimental) Export a specific tab by title or ID (Google Docs only; see 'gog docs list-tabs')"` + FileID string `arg:"" name:"fileId" help:"File ID"` + Output OutputPathFlag `embed:""` + Format string `name:"format" help:"Export format for Google Docs files: pdf|csv|xlsx|pptx|txt|png|docx|md (default: inferred)"` + Tab string `name:"tab" help:"(experimental) Export a specific tab by title or ID (Google Docs only; see 'gog docs list-tabs')"` + Overwrite bool `name:"overwrite" help:"Overwrite an existing output file"` } func (c *DriveDownloadCmd) Run(ctx context.Context, flags *RootFlags) error { @@ -36,10 +37,11 @@ func (c *DriveDownloadCmd) Run(ctx context.Context, flags *RootFlags) error { } } return runDocsTabExport(ctx, flags, tabExportParams{ - DocID: fileID, - OutFlag: c.Output.Path, - Format: c.Format, - TabQuery: tab, + DocID: fileID, + OutFlag: c.Output.Path, + Format: c.Format, + TabQuery: tab, + Overwrite: c.Overwrite, }) } @@ -72,6 +74,7 @@ func (c *DriveDownloadCmd) Run(ctx context.Context, flags *RootFlags) error { "out": outPathFlag, "default_downloads_dir": defaultDir, "format": strings.ToLower(strings.TrimSpace(c.Format)), + "overwrite": c.Overwrite, }); dryRunErr != nil { return dryRunErr } @@ -106,7 +109,7 @@ func (c *DriveDownloadCmd) Run(ctx context.Context, flags *RootFlags) error { return err } - downloadedPath, size, err := downloadDriveFile(ctx, svc, meta, destPath, c.Format) + downloadedPath, size, err := downloadDriveFile(ctx, svc, meta, destPath, c.Format, c.Overwrite) if err != nil { return err } @@ -126,7 +129,7 @@ func (c *DriveDownloadCmd) Run(ctx context.Context, flags *RootFlags) error { return nil } -func downloadDriveFile(ctx context.Context, svc *drive.Service, meta *drive.File, destPath string, format string) (string, int64, error) { +func downloadDriveFile(ctx context.Context, svc *drive.Service, meta *drive.File, destPath string, format string, overwrite bool) (string, int64, error) { isGoogleDoc := strings.HasPrefix(meta.MimeType, "application/vnd.google-apps.") normalizedFormat := strings.ToLower(strings.TrimSpace(format)) if normalizedFormat == formatAuto { @@ -182,7 +185,11 @@ func downloadDriveFile(ctx context.Context, svc *drive.Service, meta *drive.File return stdoutPath, n, copyErr } - f, outPath, err := createUserOutputFile(outPath) + f, outPath, err := openUserOutputFile(outPath, outputFileOptions{ + Overwrite: overwrite, + FileMode: 0o600, + DirMode: 0o700, + }) if err != nil { return "", 0, err } diff --git a/internal/cmd/export_via_drive.go b/internal/cmd/export_via_drive.go index e12a26214..c5f856759 100644 --- a/internal/cmd/export_via_drive.go +++ b/internal/cmd/export_via_drive.go @@ -107,7 +107,7 @@ func exportViaDrive(ctx context.Context, flags *RootFlags, opts exportViaDriveOp return usage("can't combine --json with --out -") } - downloadedPath, size, err := downloadDriveFile(ctx, svc, meta, destPath, format) + downloadedPath, size, err := downloadDriveFile(ctx, svc, meta, destPath, format, false) if err != nil { return err } diff --git a/internal/cmd/slides_thumbnail.go b/internal/cmd/slides_thumbnail.go index 6635245f4..9fc101033 100644 --- a/internal/cmd/slides_thumbnail.go +++ b/internal/cmd/slides_thumbnail.go @@ -18,6 +18,7 @@ type SlidesThumbnailCmd struct { Size string `name:"size" help:"Thumbnail size: small|medium|large" default:"large"` Format string `name:"format" help:"Thumbnail format: png|jpeg" default:"png"` Output string `name:"out" aliases:"output" help:"Write the thumbnail image to a local file"` + Overwrite bool `name:"overwrite" help:"Overwrite an existing output file"` } func (c *SlidesThumbnailCmd) Run(ctx context.Context, flags *RootFlags) error { @@ -53,6 +54,7 @@ func (c *SlidesThumbnailCmd) Run(ctx context.Context, flags *RootFlags) error { "size": strings.ToLower(size), "format": strings.ToLower(format), "out": outputPath, + "overwrite": c.Overwrite, }); dryRunErr != nil { return dryRunErr } @@ -90,7 +92,7 @@ func (c *SlidesThumbnailCmd) Run(ctx context.Context, flags *RootFlags) error { } if outputPath != "" { - written, writtenPath, err := downloadSlidesThumbnail(ctx, thumb.ContentUrl, outputPath) + written, writtenPath, err := downloadSlidesThumbnail(ctx, thumb.ContentUrl, outputPath, c.Overwrite) if err != nil { return err } @@ -148,7 +150,7 @@ func normalizeSlidesThumbnailFormat(v string) (string, error) { } } -func downloadSlidesThumbnail(ctx context.Context, url, outputPath string) (int64, string, error) { +func downloadSlidesThumbnail(ctx context.Context, url, outputPath string, overwrite bool) (int64, string, error) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return 0, "", fmt.Errorf("build thumbnail download request: %w", err) @@ -164,7 +166,11 @@ func downloadSlidesThumbnail(ctx context.Context, url, outputPath string) (int64 return 0, "", fmt.Errorf("download thumbnail: unexpected status %s", resp.Status) } - f, expandedPath, err := createUserOutputFile(outputPath) + f, expandedPath, err := openUserOutputFile(outputPath, outputFileOptions{ + Overwrite: overwrite, + FileMode: 0o600, + DirMode: 0o700, + }) if err != nil { return 0, "", fmt.Errorf("create output file: %w", err) }