Skip to contents

Implements Zotero's full upload protocol: create the attachment item, request upload authorization (an identical file already in storage short-circuits as exists), upload to the returned storage URL, register the upload, and verify the attachment's MD5 server-side. Uploads may consume Zotero storage quota and have immediate external effects.

Usage

ztAttach(
  config,
  parentKey,
  filePath,
  library = "user",
  filename = basename(filePath),
  contentType = .zotContentType(filePath),
  fileTransport = config$fileTransport
)

Arguments

config

A zotConfig() object.

parentKey

Key of the regular item receiving the file, or NA for a standalone attachment.

filePath

Local file to upload.

library

"user" or a group id.

filename

Stored filename; defaults to the file's basename.

contentType

MIME type; defaults to Zotero's choice by extension table and text/binary sniff (see Details).

fileTransport

The three protocol steps as an injectable list (authorize, upload, register); defaults to the fileTransport of config, and to the real protocol when the configuration has none. Tests replace it.

Value

A list: status ("uploaded", "exists" or "failed"), the attachment key, and error when failed.

Details

The default file transport performs every external protocol step. Supplying fileTransport replaces only authorization, byte upload, and registration; attachment creation and final API verification still use config. A stored file with the same MD5 returns "exists" before uploading bytes.

The default content type follows Zotero's desktop client: a table of known extensions (pdf, epub, md, bib, txt, html, csv, json, office formats, images and others), and for any other extension a sniff of the first 1024 bytes, which yields text/plain when every byte is a text character and application/octet-stream otherwise. An empty file has no type and is refused with a zotPlanError. A text/* file whose bytes are valid UTF-8 is created with charset = "utf-8". The guarded lane offers the same upload with preview, approval and a resumable ledger through ztAttachPlan() and the attach action of zotExecute().

Examples

Path <- tempfile(fileext = ".pdf")
writeBin(as.raw(1:8), Path)
Performer <- function(config, method, path, query, body, version) {
  list(
    status = 200L, headers = list(),
    body = list(successful = list(`0` = list(key = "ATT1")))
  )
}
Config <- structure(
  list(userID = "42", key = "example", apiBase = "https://example.invalid",
       performer = Performer),
  class = "zotConfig"
)
Transport <- list(
  authorize = function(...) list(exists = 1L),
  upload = function(...) stop("upload should not run"),
  register = function(...) stop("registration should not run")
)
ztAttach(Config, parentKey = NA_character_, filePath = Path,
         fileTransport = Transport)
#> $status
#> [1] "exists"
#> 
#> $key
#> [1] "ATT1"
#> 
unlink(Path)