From cc3237634d20f3681de59a83d3aa182592dd7ec9 Mon Sep 17 00:00:00 2001 From: Dawid Jenczewski Date: Tue, 10 Dec 2024 16:25:52 +0100 Subject: [PATCH] docs: Create docs for Inbox stream API (#4) --- .../inboxEntryStream/InboxEntryStream.java | 212 ++++++++++++++++-- .../inboxFileStream/InboxFileStream.java | 26 ++- .../InboxFileStreamReader.java | 11 +- .../InboxFileStreamWriter.java | 32 ++- 4 files changed, 244 insertions(+), 37 deletions(-) diff --git a/privmx-endpoint-extra/src/main/java/com/simplito/java/privmx_endpoint_extra/inboxEntryStream/InboxEntryStream.java b/privmx-endpoint-extra/src/main/java/com/simplito/java/privmx_endpoint_extra/inboxEntryStream/InboxEntryStream.java index 509b289..d7b3b03 100644 --- a/privmx-endpoint-extra/src/main/java/com/simplito/java/privmx_endpoint_extra/inboxEntryStream/InboxEntryStream.java +++ b/privmx-endpoint-extra/src/main/java/com/simplito/java/privmx_endpoint_extra/inboxEntryStream/InboxEntryStream.java @@ -32,6 +32,15 @@ import java.util.concurrent.Future; import java.util.stream.Collectors; +/** + * Provides a streamlined process for creating and sending inbox entries + * with optional file attachments. + *

This class simplifies the complexities of interacting with the Inbox API for sending entries, + * especially when dealing with multiple files. It manages the lifecycle of the entry creation + * process, including file uploads and final entry submission. + * + * @category inbox + */ public class InboxEntryStream { private final InboxApi inboxApi; private final Map inboxFiles; @@ -52,34 +61,88 @@ private InboxEntryStream(InboxApi api, Map inbo } } + /** + * Creates {@link InboxEntryStream} instance ready for streaming. + *

This method initializes an {@link InboxEntryStream} and prepares it for sending + * an entry with the provided data. It creates an Inbox handle and sets the + * initial state of the stream to {@link State#FILES_SENT} + * + * @param inboxApi reference to Inbox API + * @param inboxId ID of the Inbox + * @param entryStreamListener the listener for stream state changes. + * @param data entry data to send + * @return instance of {@link InboxEntryStream} prepared for streaming. + */ public static InboxEntryStream prepareEntry( InboxApi inboxApi, String inboxId, EntryStreamListener entryStreamListener, byte[] data - ) { + ) throws PrivmxException, NativeException, IllegalStateException { return prepareEntry(inboxApi, inboxId, entryStreamListener, data, null); } + /** + * Creates {@link InboxEntryStream} instance ready for streaming. + *

This method initializes an {@link InboxEntryStream} and prepares it for sending an entry with + * associated files and empty data. It creates Inbox and file handles, setting the initial state of the stream + * to {@link State#PREPARED}, indicating readiness for file transfer. + * + * @param inboxApi reference to Inbox API + * @param inboxId ID of the Inbox + * @param entryStreamListener the listener for stream state changes. + * @param filesConfig information about each entry's file to send. + * @return instance of {@link InboxEntryStream} prepared for streaming. + */ public static InboxEntryStream prepareEntry( InboxApi inboxApi, String inboxId, EntryStreamListener entryStreamListener, List filesConfig - ) { + ) throws PrivmxException, NativeException, IllegalStateException { return prepareEntry(inboxApi, inboxId, entryStreamListener, "".getBytes(StandardCharsets.UTF_8), filesConfig); } + /** + * Creates an {@link InboxEntryStream} instance ready for streaming, with optional files and encryption. + *

This method initializes an {@link InboxEntryStream} and prepares it for sending an entry with + * the provided data and optional associated files. It creates Inbox and file handles (if + * {@code filesConfig} is provided), setting the initial state of the stream to + * {@link State#PREPARED}, indicating readiness for data and file transfer. + * + * @param inboxApi reference to Inbox API + * @param inboxId ID of the Inbox + * @param entryStreamListener the listener for stream state changes + * @param data entry data to send + * @param filesConfig information about each entry's file to send. + * @return instance of {@link InboxEntryStream} prepared for streaming. + */ public static InboxEntryStream prepareEntry( InboxApi inboxApi, String inboxId, EntryStreamListener entryStreamListener, byte[] data, List filesConfig - ) { + ) throws PrivmxException, NativeException, IllegalStateException { return prepareEntry(inboxApi, inboxId, entryStreamListener, data, filesConfig, null); } + /** + * Creates an {@link InboxEntryStream} instance ready for streaming, with optional files and encryption. + *

This method initializes an {@link InboxEntryStream} and prepares it for sending an entry with the provided data, + * optional associated files, and optional encryption using the sender's private key. It creates an Inbox handle + * and initializes file handles for any associated files. The initial state of the stream is determined based + * on the presence of files: if no files are provided, the state is set to {@link State#FILES_SENT}; otherwise, + * it's set to {@link State#PREPARED}, indicating readiness for file transfer. + * + * @param inboxApi reference to Inbox API + * @param inboxId ID of the Inbox + * @param entryStreamListener the listener for stream state changes + * @param data entry data to send + * @param filesConfig information about each entry's file to send + * @param userPrivKey sender's private key which can be used later to encrypt data for that sender + * @return instance of {@link InboxEntryStream} prepared for streaming. + */ public static InboxEntryStream prepareEntry( InboxApi inboxApi, String inboxId, @@ -87,7 +150,7 @@ public static InboxEntryStream prepareEntry( byte[] data, List filesConfig, String userPrivKey - ) { + ) throws PrivmxException, NativeException, IllegalStateException { Map files = Optional.ofNullable(filesConfig) .orElse(Collections.emptyList()) .stream() @@ -107,15 +170,20 @@ public static InboxEntryStream prepareEntry( return new InboxEntryStream(inboxApi, files, inboxHandle, entryStreamListener); } + /** + * Initiates the process of sending files using the provided executor. + *

This method submits each file for sending to the {@code fileStreamExecutor} + * and wait for completion. + * + * @param fileStreamExecutor the executor service responsible for executing file sending tasks + * @throws IllegalStateException If the stream is not in the {@link State#PREPARED} state. + */ public synchronized void sendFiles( ExecutorService fileStreamExecutor - ) { + ) throws IllegalStateException { if (streamState != State.PREPARED) { throw new IllegalStateException("Stream should be in state PREPARED. Current state is: " + streamState.name()); } - if (!sendingFiles.isEmpty()) { - throw new IllegalStateException("Uploading files in progress"); - } inboxFiles.forEach((fileInfo, fileHandle) -> { sendingFiles.add(fileStreamExecutor.submit(() -> { try { @@ -150,6 +218,17 @@ public synchronized void sendFiles( } + /** + * Sends files using a single-threaded executor (see: {@link Executors#newSingleThreadExecutor}). + * + * @throws IllegalStateException when this stream is not in state {@link State#PREPARED}. + */ + public void sendFiles() { + try (ExecutorService executor = Executors.newSingleThreadExecutor()) { + sendFiles(executor); + } + } + private void sendFile( FileInfo fileInfo, InboxFileStreamWriter fileHandle @@ -182,7 +261,7 @@ public void onChunkProcessed(Long processedBytes) { } } - public void onError(Throwable t) { + private void onError(Throwable t) { cancel(); stopFileStreams(); closeFileHandles(); @@ -199,15 +278,19 @@ private void updateState(State newState) { } } + /** + * Cancels the stream and sets its state to {@link State#ABORTED}. + *

If the stream is currently sending files, all pending file operations will be canceled. + *

If the stream is in the process of sending the entry, this operation will not have any effect. + */ public void cancel() { if (streamState == State.ERROR) return; if (streamState == State.ABORTED) return; + if (streamState == State.SENT) return; synchronized (this) { stopFileStreams(); closeFileHandles(); - if (streamState != State.SENT) { - updateState(State.ABORTED); - } + updateState(State.ABORTED); } } @@ -234,12 +317,15 @@ private void closeFileHandles() { } } - public void sendFiles() { - try (ExecutorService executor = Executors.newSingleThreadExecutor()) { - sendFiles(executor); - } - } - + /** + * Sends the entry data and closes this stream, transitioning it to the {@link State#SENT} state. + *

This method should only be called after all files associated with the entry have been + * successfully sent, indicated by the stream being in the {@link State#FILES_SENT} state. + * + * @throws PrivmxException when method encounters an exception during call {@link InboxApi#sendEntry} method + * @throws NativeException when method encounters an unknown exception during call {@link InboxApi#sendEntry} method + * @throws IllegalStateException if the stream is not in the {@link State#FILES_SENT} state + */ public synchronized void sendEntry() throws PrivmxException, NativeException, IllegalStateException { if (streamState != State.FILES_SENT) { throw new IllegalStateException("Stream should be in state FILES_SENT. Current state is: " + streamState.name()); @@ -252,6 +338,9 @@ public synchronized void sendEntry() throws PrivmxException, NativeException, Il } } + /** + * Contains available states of {@link InboxEntryStream}. + */ public enum State { /** * The initial state, indicating that {@link InboxEntryStream} is ready to send files. @@ -278,12 +367,42 @@ public enum State { ABORTED } + /** + * Represents information about a file to be sent by {@link InboxEntryStream}. + */ public static class FileInfo { + + /** + * Byte array of any arbitrary metadata that can be read by anyone. + */ public byte[] publicMeta; + + /** + * Byte array of any arbitrary metadata that will be encrypted before sending. + */ public byte[] privateMeta; + + /** + * The total size of the file data. + */ public long fileSize; + + /** + * An optional {@link InputStream} providing the file data. + *

If this value is {@code null}, the stream will call + * {@link EntryStreamListener#onNextChunkRequest} to request chunks of data + * for sending. + */ public InputStream fileStream; + /** + * Creates instance of {@link FileInfo}. + * + * @param publicMeta byte array of any arbitrary metadata that can be read by anyone + * @param privateMeta byte array of any arbitrary metadata that will be encrypted before sending + * @param fileSize the total size of the file data + * @param fileStream reference to {@link InputStream} instance used as source for stream file + */ public FileInfo( byte[] publicMeta, byte[] privateMeta, @@ -297,30 +416,83 @@ public FileInfo( } } + /** + * Interface for listening to state changes and exchanging data with an {@link InboxEntryStream} instance. + *

+ * This interface provides callbacks for various events that occur during the lifecycle of an inbox entry stream, + * such as starting and ending file sending, requesting file chunks, handling errors, and updating the stream state. + *

+ * Implement this interface to monitor and interact with the entry stream. + */ public abstract static class EntryStreamListener { + + /** + * Override this method to handle when file start sending. + * + * @param file information about the file being sent + */ public void onStartFileSending(FileInfo file) { + } + /** + * Override this method to handle when file has been send successfully. + * + * @param file information about the sent file + */ public void onEndFileSending(FileInfo file) { } + /** + * Override this method to handle event when {@link FileInfo#fileStream} is {@code null} + * and the stream requests a chunk of the file to send. + *

This method should return the next chunk of the file. + *

Returning {@code null} will cause a + * {@link NullPointerException} during file sending and stop the {@link InboxEntryStream} instance with + * the state {@link State#ERROR}. + * + * @param file info about file for which chunk is requested + * @return next chunk of file. + */ public byte[] onNextChunkRequest(FileInfo file) { return null; } - public void onFileChunkProcessed(FileInfo file, long chunk) { + /** + * Override this method to handle event when each chunk of a file has been sent successfully. + * + * @param file information about the file for which the chunk was processed + * @param processedBytes accumulated size of sent data + */ + public void onFileChunkProcessed(FileInfo file, long processedBytes) { } + /** + * Override this method to handle event when some error occurs during file sending. + * + * @param file information about the file that caused the error + * @param throwable exception that occurred during file sending + */ public void onErrorDuringSending(FileInfo file, Throwable throwable) { } - public void onError(Throwable t) { + /** + * Override this method to handle event when some error occurs during creating entry. + * + * @param throwable exception that occurred during entry creation + */ + public void onError(Throwable throwable) { } + /** + * Override this method to handle event when stream state has been updated. + * + * @param currentState current state of the stream + */ public void onUpdateState(State currentState) { } diff --git a/privmx-endpoint-extra/src/main/java/com/simplito/java/privmx_endpoint_extra/inboxFileStream/InboxFileStream.java b/privmx-endpoint-extra/src/main/java/com/simplito/java/privmx_endpoint_extra/inboxFileStream/InboxFileStream.java index dd23a7f..10e8c72 100644 --- a/privmx-endpoint-extra/src/main/java/com/simplito/java/privmx_endpoint_extra/inboxFileStream/InboxFileStream.java +++ b/privmx-endpoint-extra/src/main/java/com/simplito/java/privmx_endpoint_extra/inboxFileStream/InboxFileStream.java @@ -18,31 +18,35 @@ import com.simplito.java.privmx_endpoint_extra.storeFileStream.StoreFileStream.ProgressListener; /** - * Base class for Store file streams. Implements progress listeners. + * Base class for Inbox file streams. * - * @category store + * @category inbox */ public abstract class InboxFileStream { + /** * Constant value with optimal size of reading/sending data. */ public static final long OPTIMAL_SEND_SIZE = 128 * 1024L; + /** * Reference to file handle. */ protected final Long handle; + /** - * Reference to {@link com.simplito.java.privmx_endpoint.modules.inbox.InboxApi}. + * Reference to {@link com.simplito.java.privmx_endpoint.modules.inbox.InboxApi} instance. */ protected final InboxApi inboxApi; + private Long processedBytes = 0L; private StoreFileStream.ProgressListener progressListener; private Boolean closed = false; /** - * Creates instance of {@code StoreFileStream}. + * Creates instance of {@link InboxFileStream}. * - * @param handle handle to Store file + * @param handle handle to Inbox file * @param inboxApi {@link InboxApi} instance that calls read/write methods on files */ protected InboxFileStream( @@ -53,6 +57,11 @@ protected InboxFileStream( this.inboxApi = inboxApi; } + /** + * Gets current file handle. + * + * @return file handle. + */ public Long getFileHandle() { return handle; } @@ -78,6 +87,11 @@ protected void callChunkProcessed(Long chunkSize) { } } + /** + * Gets size of sent data. + * + * @return size of sent data. + */ public Long getProcessedBytes() { return this.processedBytes; } @@ -97,7 +111,7 @@ public Boolean isClosed() { * @return ID of the closed file * @throws PrivmxException if there is an error while closing file * @throws NativeException if there is an unknown error while closing file - * @throws IllegalStateException when {@code storeApi} is not initialized or there's no connection + * @throws IllegalStateException when {@link #inboxApi} is not initialized or there's no connection */ public synchronized String close() throws PrivmxException, NativeException, IllegalStateException { String result = inboxApi.closeFile(handle); diff --git a/privmx-endpoint-extra/src/main/java/com/simplito/java/privmx_endpoint_extra/inboxFileStream/InboxFileStreamReader.java b/privmx-endpoint-extra/src/main/java/com/simplito/java/privmx_endpoint_extra/inboxFileStream/InboxFileStreamReader.java index b38c131..63fb68a 100644 --- a/privmx-endpoint-extra/src/main/java/com/simplito/java/privmx_endpoint_extra/inboxFileStream/InboxFileStreamReader.java +++ b/privmx-endpoint-extra/src/main/java/com/simplito/java/privmx_endpoint_extra/inboxFileStream/InboxFileStreamReader.java @@ -21,13 +21,12 @@ /** - * Manages handle for file reading. + * Manages handle for file reading from Inbox. * * @category inbox */ public class InboxFileStreamReader extends InboxFileStream { - private InboxFileStreamReader( Long handle, InboxApi api @@ -56,11 +55,11 @@ public static InboxFileStreamReader openFile( } /** - * Opens Inbox file and writes it into {@link OutputStream}. + * Opens Inbox file and writes it into {@link OutputStream} with optimized chunk size {@link InboxFileStream#OPTIMAL_SEND_SIZE}. * * @param api reference to Inbox API * @param fileId ID of the file to open - * @param outputStream stream to write downloaded data with optimized chunk size {@link InboxFileStream#OPTIMAL_SEND_SIZE} + * @param outputStream stream to write downloaded data * @return ID of the read file * @throws IOException if there is an error while writing the stream * @throws IllegalStateException when inboxApi is not initialized or there's no connection @@ -76,11 +75,11 @@ public static String openFile( } /** - * Opens Inbox file and writes it into {@link OutputStream}. + * Opens Inbox file and writes it into {@link OutputStream} with optimized chunk size {@link InboxFileStream#OPTIMAL_SEND_SIZE}. * * @param api reference to Inbox API * @param fileId ID of the file to open - * @param outputStream stream to write downloaded data with optimized chunk size {@link InboxFileStream#OPTIMAL_SEND_SIZE} + * @param outputStream stream to write downloaded data * @param streamController controls the process of reading file * @return ID of the read file * @throws IOException if there is an error while writing stream diff --git a/privmx-endpoint-extra/src/main/java/com/simplito/java/privmx_endpoint_extra/inboxFileStream/InboxFileStreamWriter.java b/privmx-endpoint-extra/src/main/java/com/simplito/java/privmx_endpoint_extra/inboxFileStream/InboxFileStreamWriter.java index 82846d3..3259418 100644 --- a/privmx-endpoint-extra/src/main/java/com/simplito/java/privmx_endpoint_extra/inboxFileStream/InboxFileStreamWriter.java +++ b/privmx-endpoint-extra/src/main/java/com/simplito/java/privmx_endpoint_extra/inboxFileStream/InboxFileStreamWriter.java @@ -59,13 +59,14 @@ public static InboxFileStreamWriter createFile( /** * Writes data to Inbox file. * - * @param data data to write (the recommended size of data chunk is {@link InboxFileStream#OPTIMAL_SEND_SIZE}) + * @param inboxHandle the handle of an Inbox to write to + * @param data data to write (the recommended size of data chunk is {@link InboxFileStream#OPTIMAL_SEND_SIZE}) * @throws PrivmxException if there is an error while writing chunk * @throws NativeException if there is an unknown error while writing chunk * @throws IllegalStateException when inboxApi is not initialized or there's no connection * @throws IOException when {@code this} is closed - * @throws PrivmxException when method encounters an exception - * @throws NativeException when method encounters an unknown exception + * @throws PrivmxException when method encounters an exception during execution of {@link InboxApi#writeToFile} + * @throws NativeException when method encounters an unknown exception during execution of {@link InboxApi#writeToFile} * @throws IllegalStateException when {@link #inboxApi} is closed */ public void write(long inboxHandle, byte[] data) throws PrivmxException, NativeException, IllegalStateException, IOException { @@ -74,10 +75,31 @@ public void write(long inboxHandle, byte[] data) throws PrivmxException, NativeE callChunkProcessed((long) data.length); } - public void writeStream(long inboxHandle, InputStream stream) throws PrivmxException, NativeException, IllegalStateException, IOException { - writeStream(inboxHandle, stream, null); + /** + * Writes data from an {@link InputStream} to an Inbox file. + * + * @param inboxHandle the handle of an Inbox to write to + * @param inputStream the {@link InputStream} to read data from + * @throws PrivmxException when method encounters an exception during execution of {@link InboxApi#writeToFile} + * @throws NativeException when method encounters an unknown exception during execution of {@link InboxApi#writeToFile} + * @throws IllegalStateException when {@link #inboxApi} is closed + * @throws IOException when {@link InputStream#read} thrown exception + */ + public void writeStream(long inboxHandle, InputStream inputStream) throws PrivmxException, NativeException, IllegalStateException, IOException { + writeStream(inboxHandle, inputStream, null); } + /** + * Writes data from an {@link InputStream} to an Inbox file. + * + * @param inboxHandle the handle of an Inbox to write to + * @param inputStream the {@link InputStream} to read data from + * @param streamController an optional controller for monitoring and controlling the write operation. + * @throws PrivmxException when method encounters an exception during execution of {@link InboxApi#writeToFile} + * @throws NativeException when method encounters an unknown exception during execution of {@link InboxApi#writeToFile} + * @throws IllegalStateException when {@link #inboxApi} is closed + * @throws IOException when {@link InputStream#read} thrown exception + */ public void writeStream(long inboxHandle, InputStream inputStream, StoreFileStream.Controller streamController) throws PrivmxException, NativeException, IllegalStateException, IOException { if (streamController != null) { setProgressListener(streamController);