From a06a707f0782c10458532165afe317e9e17eec00 Mon Sep 17 00:00:00 2001 From: Mitchell Scott Date: Thu, 9 Apr 2026 22:43:20 -0600 Subject: [PATCH] feat: add --contrast and --currentpage flags --- README.md | 7 ++++- api/api.go | 2 +- api/sync15/apictx.go | 8 ++--- archive/blob.go | 8 ++--- archive/file.go | 3 +- archive/zipdoc.go | 24 ++++++++++----- shell/mput.go | 2 +- shell/put.go | 69 ++++++++++++++++++++++++++++++++++++++++++-- 8 files changed, 101 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 8d695a3d..a115d626 100644 --- a/README.md +++ b/README.md @@ -187,6 +187,8 @@ put book.pdf /books - `--force`: Completely replace an existing document (removes all annotations and metadata) - `--content-only`: Replace only the PDF content while preserving annotations and metadata - `--coverpage=<0|1>`: Set coverpage (0 to disable, 1 to set first page as cover) +- `--currentpage=`: Set the current page, 1-indexed (PDF only) +- `--contrast=`: Set the contrast filter (PDF and epub only) Examples: @@ -203,6 +205,9 @@ put --content-only document.pdf # Upload with coverpage set to first page put --coverpage=1 document.pdf +# Upload and open to page 5 with full-page contrast +put --currentpage=5 --contrast=fullpage document.pdf + # Replace PDF content in specific directory put --content-only document.pdf /target-directory @@ -210,7 +215,7 @@ put --content-only document.pdf /target-directory put --force document.pdf /reports ``` -**Note**: `--force` and `--content-only` are mutually exclusive. The `--coverpage` flag can be combined with either. If the target document doesn't exist, all flags will create a new document. +**Note**: `--force` and `--content-only` are mutually exclusive. The `--coverpage`, `--currentpage`, and `--contrast` flags can be combined with either. If the target document doesn't exist, all flags will create a new document. ## Recursively upload directories and files diff --git a/api/api.go b/api/api.go index 3384afdd..0334f06b 100644 --- a/api/api.go +++ b/api/api.go @@ -18,7 +18,7 @@ type ApiCtx interface { Filetree() *filetree.FileTreeCtx FetchDocument(docId, dstPath string) error CreateDir(parentId, name string, notify bool) (*model.Document, error) - UploadDocument(parentId string, sourceDocPath string, notify bool, coverpage *int) (*model.Document, error) + UploadDocument(parentId string, sourceDocPath string, notify bool, coverpage *int, currentPage *int, pageCount *int, contrastFilter *string) (*model.Document, error) ReplaceDocumentFile(docId, sourceDocPath string, notify bool) error MoveEntry(src, dstDir *model.Node, name string) (*model.Node, error) DeleteEntry(node *model.Node, recursive, notify bool) error diff --git a/api/sync15/apictx.go b/api/sync15/apictx.go index 31c81551..a8e53334 100644 --- a/api/sync15/apictx.go +++ b/api/sync15/apictx.go @@ -139,13 +139,13 @@ func (ctx *ApiCtx) CreateDir(parentId, name string, notify bool) (*model.Documen return nil, err } id := uuid.New().String() - objectName, filePath, err := archive.CreateMetadata(id, name, parentId, model.DirectoryType, tmpDir) + objectName, filePath, err := archive.CreateMetadata(id, name, parentId, model.DirectoryType, tmpDir, nil) if err != nil { return nil, err } files.AddMap(objectName, filePath, archive.MetadataExt) - objectName, filePath, err = archive.CreateContent(id, "", tmpDir, nil, nil) + objectName, filePath, err = archive.CreateContent(id, "", tmpDir, nil, nil, nil, nil, nil) if err != nil { return nil, err } @@ -335,7 +335,7 @@ func (ctx *ApiCtx) MoveEntry(src, dstDir *model.Node, name string) (*model.Node, } // UploadDocument uploads a local document given by sourceDocPath under the parentId directory -func (ctx *ApiCtx) UploadDocument(parentId string, sourceDocPath string, notify bool, coverpage *int) (*model.Document, error) { +func (ctx *ApiCtx) UploadDocument(parentId string, sourceDocPath string, notify bool, coverpage *int, currentPage *int, pageCount *int, contrastFilter *string) (*model.Document, error) { //TODO: overwrite file name, ext := util.DocPathToName(sourceDocPath) @@ -356,7 +356,7 @@ func (ctx *ApiCtx) UploadDocument(parentId string, sourceDocPath string, notify defer os.RemoveAll(tmpDir) - docFiles, id, err := archive.Prepare(name, parentId, sourceDocPath, ext, tmpDir, coverpage) + docFiles, id, err := archive.Prepare(name, parentId, sourceDocPath, ext, tmpDir, coverpage, currentPage, pageCount, contrastFilter) if err != nil { return nil, err } diff --git a/archive/blob.go b/archive/blob.go index 21236c06..16052065 100644 --- a/archive/blob.go +++ b/archive/blob.go @@ -47,7 +47,7 @@ func (d *DocumentFiles) AddMap(name, filepath string, filetype RmExt) { } // Prepare prepares a file for uploading (creates needed temp files or unpacks a zip) -func Prepare(name, parentId, sourceDocPath, ext, tmpDir string, coverpage *int) (files *DocumentFiles, id string, err error) { +func Prepare(name, parentId, sourceDocPath, ext, tmpDir string, coverpage *int, currentPage *int, pageCount *int, contrastFilter *string) (files *DocumentFiles, id string, err error) { files = &DocumentFiles{} if ext == util.ZIP || ext == util.RMDOC { var metadataPath string @@ -60,7 +60,7 @@ func Prepare(name, parentId, sourceDocPath, ext, tmpDir string, coverpage *int) } if metadataPath == "" { log.Warning.Println("missing metadata, creating...", name) - objectName, filePath, err1 := CreateMetadata(id, name, parentId, model.DocumentType, tmpDir) + objectName, filePath, err1 := CreateMetadata(id, name, parentId, model.DocumentType, tmpDir, currentPage) if err1 != nil { err = err1 return @@ -84,14 +84,14 @@ func Prepare(name, parentId, sourceDocPath, ext, tmpDir string, coverpage *int) pageIds = []string{pageId} } files.AddMap(objectName, sourceDocPath, RmExt(doctype)) - objectName, filePath, err1 := CreateMetadata(id, name, parentId, model.DocumentType, tmpDir) + objectName, filePath, err1 := CreateMetadata(id, name, parentId, model.DocumentType, tmpDir, currentPage) if err1 != nil { err = err1 return } files.AddMap(objectName, filePath, MetadataExt) - objectName, filePath, err = CreateContent(id, doctype, tmpDir, pageIds, coverpage) + objectName, filePath, err = CreateContent(id, doctype, tmpDir, pageIds, coverpage, currentPage, pageCount, contrastFilter) if err != nil { return } diff --git a/archive/file.go b/archive/file.go index bb766a50..dfb27831 100644 --- a/archive/file.go +++ b/archive/file.go @@ -120,7 +120,8 @@ type Content struct { DocumentTags []Tag `json:"tags"` RedirectionMap []int `json:"redirectionPageMap"` TextScale float64 `json:"textScale"` - CoverPageNumber *int `json:"coverPageNumber,omitempty"` + CoverPageNumber *int `json:"coverPageNumber,omitempty"` + ViewBackgroundFilter *string `json:"viewBackgroundFilter,omitempty"` Transform *Transform `json:"-"` } diff --git a/archive/zipdoc.go b/archive/zipdoc.go index a92dc683..c1da744a 100644 --- a/archive/zipdoc.go +++ b/archive/zipdoc.go @@ -144,7 +144,7 @@ func CreateZipDocument(id, srcPath string) (zipPath string, err error) { return } - c, err := createZipContent(fileType, pages, nil) + c, err := createZipContent(fileType, pages, nil, nil, nil, nil) if err != nil { return } @@ -179,7 +179,7 @@ func CreateZipDirectory(id string) (string, error) { return tmp.Name(), nil } -func createZipContent(ext string, pageIDs []string, coverpage *int) (string, error) { +func createZipContent(ext string, pageIDs []string, coverpage *int, currentPage *int, pageCount *int, contrastFilter *string) (string, error) { c := Content{ DummyDocument: false, ExtraMetadata: ExtraMetadata{ @@ -204,8 +204,15 @@ func createZipContent(ext string, pageIDs []string, coverpage *int) (string, err M32: 0, M33: 1, }, - Pages: pageIDs, - CoverPageNumber: coverpage, + Pages: pageIDs, + CoverPageNumber: coverpage, + ViewBackgroundFilter: contrastFilter, + } + if currentPage != nil { + c.LastOpenedPage = *currentPage + } + if pageCount != nil { + c.PageCount = *pageCount } cstring, err := json.Marshal(c) @@ -218,13 +225,13 @@ func createZipContent(ext string, pageIDs []string, coverpage *int) (string, err return string(cstring), nil } -func CreateContent(id, ext, fpath string, pageIds []string, coverpage *int) (fileName, filePath string, err error) { +func CreateContent(id, ext, fpath string, pageIds []string, coverpage *int, currentPage *int, pageCount *int, contrastFilter *string) (fileName, filePath string, err error) { fileName = id + "." + string(ContentExt) filePath = path.Join(fpath, fileName) content := "{}" if ext != "" { - content, err = createZipContent(ext, pageIds, coverpage) + content, err = createZipContent(ext, pageIds, coverpage, currentPage, pageCount, contrastFilter) if err != nil { return } @@ -240,7 +247,7 @@ func UnixTimestamp() string { return tf } -func CreateMetadata(id, name, parent, colType, fpath string) (fileName string, filePath string, err error) { +func CreateMetadata(id, name, parent, colType, fpath string, currentPage *int) (fileName string, filePath string, err error) { fileName = id + "." + string(MetadataExt) filePath = path.Join(fpath, fileName) meta := MetadataFile{ @@ -251,6 +258,9 @@ func CreateMetadata(id, name, parent, colType, fpath string) (fileName string, f Synced: true, LastModified: UnixTimestamp(), } + if currentPage != nil { + meta.LastOpenedPage = *currentPage + } c, err := json.Marshal(meta) if err != nil { diff --git a/shell/mput.go b/shell/mput.go index f614a605..2b76474a 100644 --- a/shell/mput.go +++ b/shell/mput.go @@ -212,7 +212,7 @@ func putFilesAndDirs(pCtx *ShellCtxt, pC *ishell.Context, localDir string, depth pC.Printf("uploading: [%s]...", name) fullName := path.Join(localDir, name) - doc, err := pCtx.api.UploadDocument(pCtx.node.Id(), fullName, false, nil) + doc, err := pCtx.api.UploadDocument(pCtx.node.Id(), fullName, false, nil, nil, nil, nil) if err != nil { pC.Err(fmt.Errorf("failed to upload file '%s', %v", name, err)) diff --git a/shell/put.go b/shell/put.go index 4bcca2bc..eb467c4e 100644 --- a/shell/put.go +++ b/shell/put.go @@ -3,10 +3,13 @@ package shell import ( "errors" "fmt" + "os" + "strconv" "github.com/abiosoft/ishell" "github.com/juruen/rmapi/util" "github.com/ogier/pflag" + pdf "github.com/unidoc/unipdf/v3/model" ) func putCmd(ctx *ShellCtxt) *ishell.Cmd { @@ -29,6 +32,8 @@ func putCmd(ctx *ShellCtxt) *ishell.Cmd { force := flags.Bool("force", false, "Overwrite existing file (recreates document)") contentOnly := flags.Bool("content-only", false, "Overwrite existing file (recreates document)") coverpage := flags.String("coverpage", "", "Set coverpage (0 to disable, 1 to set first page as cover)") + currentpageStr := flags.String("currentpage", "", "Set current page (1-indexed)") + contrast := flags.String("contrast", "", "Set contrast filter (fullpage, off, adaptive)") if !processFlagSet(flags, longHelp, c.Args, c) { return @@ -61,6 +66,64 @@ func putCmd(ctx *ShellCtxt) *ishell.Cmd { } } + _, srcExt := util.DocPathToName(args[0]) + + var currentPageFlag *int + var pageCountFlag *int + if *currentpageStr != "" { + if srcExt != "pdf" { + c.Err(errors.New("--currentpage is only supported for PDF files")) + return + } + val, err := strconv.Atoi(*currentpageStr) + if err != nil || val < 1 { + c.Err(errors.New("--currentpage must be a positive integer")) + return + } + + f, err := os.Open(args[0]) + if err != nil { + c.Err(fmt.Errorf("failed to open file: %v", err)) + return + } + reader, err := pdf.NewPdfReader(f) + f.Close() + if err != nil { + c.Err(fmt.Errorf("failed to read PDF: %v", err)) + return + } + numPages, err := reader.GetNumPages() + if err != nil { + c.Err(fmt.Errorf("failed to get page count: %v", err)) + return + } + if val > numPages { + c.Err(fmt.Errorf("--currentpage %d exceeds page count (%d)", val, numPages)) + return + } + pageCountFlag = &numPages + + val-- // convert 1-indexed to 0-indexed + currentPageFlag = &val + } + + var contrastFlag *string + if *contrast != "" { + if srcExt != "pdf" && srcExt != "epub" { + c.Err(errors.New("--contrast is only supported for PDF and epub files")) + return + } + switch *contrast { + case "fullpage", "off": + contrastFlag = contrast + case "adaptive": + // leave nil to omit field + default: + c.Err(errors.New("--contrast must be fullpage, off, or adaptive")) + return + } + } + srcName := args[0] // Handle --content-only mode (replace PDF content) @@ -90,7 +153,7 @@ func putCmd(ctx *ShellCtxt) *ishell.Cmd { // Document doesn't exist, create new one c.Printf("uploading: [%s]...", srcName) dstDir := node.Id() - document, err := ctx.api.UploadDocument(dstDir, srcName, true, coverpageFlag) + document, err := ctx.api.UploadDocument(dstDir, srcName, true, coverpageFlag, currentPageFlag, pageCountFlag, contrastFlag) if err != nil { c.Err(fmt.Errorf("failed to upload file [%s]: %v", srcName, err)) return @@ -152,7 +215,7 @@ func putCmd(ctx *ShellCtxt) *ishell.Cmd { // Upload new document dstDir := node.Id() - document, err := ctx.api.UploadDocument(dstDir, srcName, true, coverpageFlag) + document, err := ctx.api.UploadDocument(dstDir, srcName, true, coverpageFlag, currentPageFlag, pageCountFlag, contrastFlag) if err != nil { c.Err(fmt.Errorf("failed to upload replacement file [%s]: %v", srcName, err)) return @@ -166,7 +229,7 @@ func putCmd(ctx *ShellCtxt) *ishell.Cmd { // File doesn't exist, upload new document c.Printf("uploading: [%s]...", srcName) dstDir := node.Id() - document, err := ctx.api.UploadDocument(dstDir, srcName, true, coverpageFlag) + document, err := ctx.api.UploadDocument(dstDir, srcName, true, coverpageFlag, currentPageFlag, pageCountFlag, contrastFlag) if err != nil { c.Err(fmt.Errorf("failed to upload file [%s] %v", srcName, err))