File Attachments

Some API parameters take a file: a user's avatar_file, a site's logo_file and icons, a behavior's attachment_file, a bundle's watermark_attachment_file and a style's file. These parameters accept a byte[] or a com.files.net.Attachment, which also sends a local file or a stream and sets the file's name and media type.

Attachments configure objects. To transfer files to and from your Files.com site, use File and Folder.

Example Request

import com.files.exceptions.*;
import com.files.exceptions.ApiErrorException.*;
import com.files.models.User;
import com.files.net.Attachment;
import java.nio.file.Paths;
import java.util.HashMap;

HashMap<String, Object> parameters = new HashMap<>();
parameters.put("avatar_file", Attachment.fromPath(Paths.get("avatar.png")).withContentType("image/png"));

try {
  User user = User.update(userId, parameters);
} catch (NotAuthenticatedException e) {
  System.out.println("Authentication Error Occurred (" + e.getClass().getName() + "): " + e.getMessage());
} catch (SdkException e) {
  System.out.println("Unknown Error Occurred (" + e.getClass().getName() + "): " + e.getMessage());
}

Local Files

Attachment.fromPath reads the file when the request is sent, without loading it into memory. The filename sent is the file's name, without its directories. Before sending, the SDK reads the file once to record its size and SHA-256 digest. Each attempt, including a retry, is checked against them; a changed file fails the request with an UncheckedIOException instead of sending different content.

Example Request

import com.files.exceptions.*;
import com.files.exceptions.ApiErrorException.*;
import com.files.models.Site;
import com.files.net.Attachment;
import java.nio.file.Paths;
import java.util.HashMap;

HashMap<String, Object> parameters = new HashMap<>();
parameters.put("logo_file", Attachment.fromPath(Paths.get("brand", "logo.png")).withContentType("image/png"));
parameters.put("icon16_file", Attachment.fromPath(Paths.get("brand", "icon16.png")).withContentType("image/png"));

try {
  Site site = Site.update(parameters);
} catch (NotAuthenticatedException e) {
  System.out.println("Authentication Error Occurred (" + e.getClass().getName() + "): " + e.getMessage());
} catch (SdkException e) {
  System.out.println("Unknown Error Occurred (" + e.getClass().getName() + "): " + e.getMessage());
}

Bytes

A byte[] is sent as the file's content, as is Attachment.fromBytes, which also lets you name the file. The SDK sends a copy of the array as it was when you made the call. Unless you set them, the filename is the parameter's name, such as avatar_file, and the media type is application/octet-stream. The API's image checks may need the right name and type.

Models keep their byte[] setters: save() sends the bytes as the file.

Example Request

import com.files.exceptions.*;
import com.files.exceptions.ApiErrorException.*;
import com.files.models.User;
import com.files.net.Attachment;
import java.util.HashMap;

HashMap<String, Object> parameters = new HashMap<>();
parameters.put("avatar_file", Attachment.fromBytes(pngBytes).withFilename("avatar.png").withContentType("image/png"));

try {
  User.update(userId, parameters);
  
  User user = User.find(userId, null);
  user.setAvatarFile(pngBytes);
  user.save();
} catch (NotAuthenticatedException e) {
  System.out.println("Authentication Error Occurred (" + e.getClass().getName() + "): " + e.getMessage());
} catch (SdkException e) {
  System.out.println("Unknown Error Occurred (" + e.getClass().getName() + "): " + e.getMessage());
}

Streams

Attachment.fromInputStream sends a stream from its current position to its end. The SDK reads the stream before it sends the request, so that a retry sends the same bytes. It keeps up to 1 MiB of the stream's content in memory and the rest in a temporary file that only your user can read, and deletes that copy when the request ends. A stream of more than 64 MiB fails with an IllegalArgumentException before anything is sent. withSpoolLimit and withMemoryThreshold change these limits. Each attachment a request sends from a stream can use up to its own limit.

The SDK does not close the stream; close it yourself, for example with try-with-resources. The SDK cannot interrupt a stream that blocks while it is being read.

Example Request

import com.files.exceptions.*;
import com.files.exceptions.ApiErrorException.*;
import com.files.models.Bundle;
import com.files.net.Attachment;
import java.io.InputStream;
import java.util.HashMap;

try {
  try (InputStream watermark = openWatermark()) {
    HashMap<String, Object> parameters = new HashMap<>();
    parameters.put("watermark_attachment_file", Attachment.fromInputStream(watermark)
        .withFilename("watermark.png")
        .withContentType("image/png")
        .withSpoolLimit(128L * 1024 * 1024));
    Bundle.update(bundleId, parameters);
  }
} catch (NotAuthenticatedException e) {
  System.out.println("Authentication Error Occurred (" + e.getClass().getName() + "): " + e.getMessage());
} catch (SdkException e) {
  System.out.println("Unknown Error Occurred (" + e.getClass().getName() + "): " + e.getMessage());
}

How the Request Is Sent

The SDK sends every parameter of a call in one request. It uses multipart/form-data, the format of a browser upload, when form fields carry every other parameter exactly. Otherwise it sends JSON, with each file as the API's file object {"encoded_content": ..., "filename": ..., "type": ...}, whose content is Base64 and about a third larger. JSON is used for:

  • a null, an empty list, an empty object or an empty key in an object;
  • a number or boolean inside an object, such as a watermark's transparency;
  • a list of objects;
  • a filename containing %, / or \, which the API's form parser would change.

The request declares its exact length either way.

Example Request

import com.files.exceptions.*;
import com.files.exceptions.ApiErrorException.*;
import com.files.models.Bundle;
import com.files.net.Attachment;
import java.nio.file.Paths;
import java.util.HashMap;

HashMap<String, Object> watermark = new HashMap<>();
watermark.put("transparency", 25);
watermark.put("max_height_or_width", 200);

HashMap<String, Object> parameters = new HashMap<>();
parameters.put("watermark_value", watermark);
parameters.put("watermark_attachment_file", Attachment.fromPath(Paths.get("watermark.png")).withContentType("image/png"));

try {
  Bundle.update(bundleId, parameters); // Sent as JSON, keeping 25 and 200 numbers
} catch (NotAuthenticatedException e) {
  System.out.println("Authentication Error Occurred (" + e.getClass().getName() + "): " + e.getMessage());
} catch (SdkException e) {
  System.out.println("Unknown Error Occurred (" + e.getClass().getName() + "): " + e.getMessage());
}

Retries and Errors

The SDK retries a request as its client allows (see Clients and Connections), and each attempt sends the same bytes. If the response to an update or create is lost, the API may apply it more than once. Cancelling a call while its attachments are still being read or copied stops it before anything is sent, and deletes the copies made so far.

  • IllegalArgumentException: a parameter has the wrong type, a stream is longer than its spool limit, or a path is not a regular file.
  • UncheckedIOException: a file or stream could not be read or copied, or a file changed between attempts. Its cause is the underlying IOException.
  • API errors, such as an invalid image, raise the usual ApiErrorException types.

At DEBUG, the SDK's request log describes each attachment without its content. Apache HttpClient's org.apache.http.wire log, at DEBUG, includes request bodies and so the content.

Example Request

import com.files.exceptions.*;
import com.files.exceptions.ApiErrorException.*;
import com.files.models.User;
import com.files.net.Attachment;
import java.io.UncheckedIOException;
import java.nio.file.Paths;
import java.util.HashMap;

HashMap<String, Object> parameters = new HashMap<>();
parameters.put("avatar_file", Attachment.fromPath(Paths.get("avatar.png")).withContentType("image/png"));

try {
  User.update(userId, parameters);
} catch (UncheckedIOException e) {
  System.out.println("Could not send the file: " + e.getMessage() + ": " + e.getCause().getMessage());
} catch (SdkException e) {
  System.out.println("Unknown Error Occurred (" + e.getClass().getName() + "): " + e.getMessage());
}