# Blob Utility Class

The [`Blob`](../../src/main/java/dev/plexapi/sdk/utils/Blob.java) utility class is a powerful tool for both sending and reading data in the SDK. It implements `HttpRequest.BodyPublisher` for sending data in HTTP requests and provides consumption methods for reading data streams. The class offers a reactive, memory-efficient approach to working with various data sources.

## Key Benefits

- **Dual Purpose**: Supports both sending data (via `HttpRequest.BodyPublisher`) and reading data (via consumption methods)
- **Memory Efficiency**: Streams data instead of loading entire files into memory, preventing out-of-memory errors with large files
- **Reactive Processing**: Built on Java's reactive streams (`Flow.Publisher<ByteBuffer>`) for streaming+NIO operations
- **Versatile Input Sources**: Supports multiple data sources with convenient factory methods
- **HTTP Native**: Implements `HttpRequest.BodyPublisher` for seamless integration with HTTP requests
- **Single-Use Safety**: Prevents accidental reuse of consumed instances with clear error handling

## Factory Methods

The [`Blob`](../../src/main/java/dev/plexapi/sdk/utils/Blob.java) class provides factory methods for various input sources:

| Factory Method | Use Case | Memory | Underlying I/O |
|---|---|---|---|
| `Blob.from(Path)` | Large files, documents | 🟢 Streaming | Non-blocking (NIO) |
| `Blob.from(InputStream)` | Network streams, legacy APIs | 🟢 Streaming | Blocking (offloaded to threads) |
| `Blob.from(String)` | Text data, JSON | 🟡 In-memory | N/A (in-memory) |
| `Blob.from(byte[])` | Binary data, images | 🟡 In-memory | N/A (in-memory) |
| `Blob.from(ByteBuffer)` | NIO buffers | 🟡 In-memory | N/A (in-memory) |
| `Blob.from(List<ByteBuffer>)` | Chunked data | 🟡 In-memory | N/A (in-memory) |
| `Blob.from(Flow.Publisher<List<ByteBuffer>>)` | Reactive streams | 🟢 Streaming | Non-blocking (NIO) |

## Retry Compatibility

When using blobs in HTTP requests that may be retried, it's important to understand which blob types support retries effectively:

| Factory Method | Retry Support | Notes |
|---|---|---|
| `Blob.from(Path)` | ✅ **Full Support** | File can be re-read for each retry attempt |
| `Blob.from(String)` | ✅ **Full Support** | Immutable data can be resent |
| `Blob.from(byte[])` | ✅ **Full Support** | In-memory data can be resent |
| `Blob.from(ByteBuffer)` | ✅ **Full Support** | Uses `duplicate()` to preserve original |
| `Blob.from(List<ByteBuffer>)` | ✅ **Full Support** | Uses `duplicate()` to preserve originals |
| `Blob.from(Flow.Publisher<List<ByteBuffer>>)` | ⚠️ **Depends on Publisher** | Stateful publishers may lose data on re-subscription |
| `Blob.from(InputStream)` | ❌ **Limited Support** | Stream gets consumed on first attempt |

**Important**: InputStream-backed blobs and some Flow.Publisher-backed blobs do not support retries effectively. InputStreams get consumed during the first HTTP request attempt, and stateful publishers may lose data on re-subscription, causing subsequent retry attempts to send empty or incomplete request bodies. For retry-compatible scenarios, prefer file-based (`Blob.from(Path)`) or in-memory alternatives (`Blob.from(byte[])`).

### File-Based Sources

#### `Blob.from(Path path)`
Creates a Blob from a file path. **Recommended for large files** as it uses streaming+NIO.

```java
Blob fileBlob = Blob.from(Paths.get("large-document.pdf"));
```

#### `Blob.from(InputStream inputStream)`
Creates a Blob from an InputStream that streams without buffering the whole payload; reading is blocking (performed on background threads if used asynchronously).

```java
FileInputStream fis = new FileInputStream("data.json");
Blob streamBlob = Blob.from(fis);
```

### In-Memory Sources

#### `Blob.from(String string)`
Creates a Blob from a String using UTF-8 encoding.

```java
Blob textBlob = Blob.from("Hello, World!");
```

#### `Blob.from(byte[] data)`
Creates a Blob from a byte array.

```java
byte[] imageData = loadImageBytes();
Blob imageBlob = Blob.from(imageData);
```

### Buffer-Based Sources

#### `Blob.from(ByteBuffer buffer)`
Creates a Blob from a single ByteBuffer.

```java
ByteBuffer buffer = ByteBuffer.allocate(1024);
// ... populate buffer
Blob bufferBlob = Blob.from(buffer);
```

#### `Blob.from(List<ByteBuffer> buffers)`
Creates a Blob from multiple ByteBuffers.

```java
List<ByteBuffer> buffers = Arrays.asList(buffer1, buffer2, buffer3);
Blob multiBufferBlob = Blob.from(buffers);
```

### Reactive Sources

#### `Blob.from(Flow.Publisher<List<ByteBuffer>> sourcePublisher)`
Creates a Blob from a reactive publisher that provides data as lists of ByteBuffers.

```java
Flow.Publisher<List<ByteBuffer>> publisher = createReactiveSource();
Blob reactiveBlob = Blob.from(publisher);
```

## Consumption Methods

The [`Blob`](../../src/main/java/dev/plexapi/sdk/utils/Blob.java) class provides four methods for reading/consuming the data. All consumption methods use NIO and are safe to use in asynchronous contexts:

### `asPublisher()`
Returns a `Flow.Publisher<ByteBuffer>` for reactive processing.

```java
Blob blob = Blob.from(Paths.get("data.txt"));
Flow.Publisher<ByteBuffer> publisher = blob.asPublisher();

// Use with your preferred reactive library or custom subscriber
publisher.subscribe(myCustomSubscriber);
```

**Illustrative Project Reactor Integration** *(Note: The SDK uses native Java reactive streams and doesn't depend on Project Reactor)*:

```java
// Convert to Reactor Flux for advanced reactive operations
Flux<ByteBuffer> flux = Flux.from(blob.asPublisher());

// Transform and process the stream
Flux<String> textChunks = flux
    .map(buffer -> StandardCharsets.UTF_8.decode(buffer).toString())
    .filter(text -> !text.trim().isEmpty())
    .doOnNext(chunk -> System.out.println("Processing: " + chunk));
```

### `toByteArray()`
Collects the entire stream into a byte array asynchronously.

```java
Blob blob = Blob.from(Paths.get("small-file.txt"));
CompletableFuture<byte[]> future = blob.toByteArray();

// Basic usage with CompletableFuture
future.thenAccept(data -> {
    System.out.println("Received " + data.length + " bytes");
    // Process the complete data
    processData(data);
}).exceptionally(throwable -> {
    System.err.println("Failed to read data: " + throwable.getMessage());
    return null;
});
```

### `toInputStream()`
Converts the entire stream into an `InputStream` for traditional I/O operations.

```java
Blob blob = Blob.from(Paths.get("data.txt"));
CompletableFuture<InputStream> future = blob.toInputStream();

// Basic usage with CompletableFuture
future.thenAccept(inputStream -> {
    try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
        String line;
        while ((line = reader.readLine()) != null) {
            System.out.println("Read line: " + line);
        }
    } catch (IOException e) {
        System.err.println("Error reading from InputStream: " + e.getMessage());
    }
}).exceptionally(throwable -> {
    System.err.println("Failed to create InputStream: " + throwable.getMessage());
    return null;
});
```

### `toFile(Path destinationPath)`
Writes the stream directly to a file asynchronously.

```java
Blob blob = Blob.from(inputStream);
CompletableFuture<Path> future = blob.toFile(Paths.get("output.dat"));

// Basic file writing with error handling
future.thenAccept(path -> {
    System.out.println("Successfully written to: " + path);
    // Verify file was created
    if (Files.exists(path)) {
        System.out.println("File size: " + Files.size(path) + " bytes");
    }
}).exceptionally(throwable -> {
    System.err.println("Failed to write file: " + throwable.getMessage());
    return null;
});

```
