pub struct MeowClient { /* private fields */ }Expand description
Main entry point of the rusty-cat SDK.
MeowClient owns runtime state and provides high-level operations:
enqueue, bounded binary GET, pause, resume, cancel, snapshot, and close.
§Usage pattern
- Create
MeowConfig. - Construct
MeowClient::new(config). - Build tasks with upload/download builders.
- Call
Self::try_enqueueorSelf::enqueue_and_wait. - Control task lifecycle with pause/resume/cancel.
- Call
Self::closeduring shutdown.
§Lifecycle contract: you must call Self::close
Transfer and binary schedulers use separate threads, runtimes, HTTP clients,
command queues and callback dispatchers. The clean shutdown protocol is an explicit
close().await command which:
- cancels in-flight transfers,
- flushes
Pausedstatus events to user callbacks for every known group, - drains already submitted callback jobs,
- joins the scheduler thread and lets the runtime drop.
Forgetting to call close leaves the scheduler thread alive until all
command senders are dropped (which does happen when MeowClient is
dropped, but only as a fallback). When that fallback path runs, the
guarantees above do not hold: callers may miss terminal status
events, in-flight HTTP transfers are aborted abruptly, and for long-lived
SDK hosts (servers, mobile runtimes, etc.) the misuse is nearly
impossible to debug from the outside.
To help surface this misuse the internal executor implements a
best-effort Drop that, when close was never called:
- emits a
Warn-level log via the debug log listener (tag"executor_drop"), - performs a non-blocking
try_sendof a finalClosecommand so the worker still has a chance to drain its state, - then drops the command sender, causing the worker loop to exit on its own.
This is a safety net, not a substitute for calling close. Treat
close().await as a mandatory step in your shutdown sequence.
§Sharing across tasks / threads
MeowClient intentionally does not implement Clone.
The client owns a lazily-initialized internal Executor (a single background
worker loop plus its task table, scheduler state and shutdown flag). A
naive field-by-field Clone would copy the OnceLock<Executor> before
it was initialized, letting different clones each spin up their own
executor on first use. The result would be:
- multiple independent task tables (tasks enqueued via one clone are
invisible to
pause/resume/cancel/snapshoton another); - concurrency limits (
MeowConfig::max_upload_concurrency/MeowConfig::max_download_concurrency) silently multiplied by the number of clones; Self::closeonly shutting down one of the worker loops, leaking the rest.
To share a client across tasks or threads, wrap it in std::sync::Arc
and clone the Arc instead:
use std::sync::Arc;
use rusty_cat::api::{MeowClient, MeowConfig};
let client = Arc::new(MeowClient::new(MeowConfig::default()));
let client_for_task = Arc::clone(&client);
tokio::spawn(async move {
let _ = client_for_task; // use the shared client here
});Implementations§
Source§impl MeowClient
impl MeowClient
Sourcepub fn new(config: MeowConfig) -> Self
pub fn new(config: MeowConfig) -> Self
Creates a new client with the provided configuration.
The internal executor is initialized lazily on first task operation.
§Examples
use rusty_cat::api::{MeowClient, MeowConfig};
let config = MeowConfig::default();
let client = MeowClient::new(config);
let _ = client;Sourcepub fn http_client(&self) -> Result<Client, MeowError>
pub fn http_client(&self) -> Result<Client, MeowError>
Returns a reqwest::Client aligned with this client’s configuration.
- If
MeowConfigBuilder::http_clientinjected a custom client, this returns its clone. - Otherwise, this builds a new client from
http_timeoutandtcp_keepalive.
§Errors
Returns MeowError with HttpClientBuildFailed when client creation
fails.
§Examples
use rusty_cat::api::{MeowClient, MeowConfig};
let client = MeowClient::new(MeowConfig::default());
let http = client.http_client()?;
let _ = http;Sourcepub fn register_global_progress_listener<F>(
&self,
listener: F,
) -> Result<GlobalProgressListenerId, MeowError>
pub fn register_global_progress_listener<F>( &self, listener: F, ) -> Result<GlobalProgressListenerId, MeowError>
Registers a global progress listener for all tasks.
§Parameters
listener: Callback receivingFileTransferRecordupdates.
§Returns
Returns a listener ID used by
Self::unregister_global_progress_listener.
§Usage rules
Keep callback execution short and panic-free. A heavy callback can slow down global event delivery.
§Errors
Returns LockPoisoned when listener storage lock is poisoned.
§Examples
use rusty_cat::api::{MeowClient, MeowConfig};
let client = MeowClient::new(MeowConfig::default());
let listener_id = client.register_global_progress_listener(|record| {
println!("task={} progress={:.2}", record.task_id(), record.progress());
})?;
let _ = listener_id;Sourcepub fn unregister_global_progress_listener(
&self,
id: GlobalProgressListenerId,
) -> Result<bool, MeowError>
pub fn unregister_global_progress_listener( &self, id: GlobalProgressListenerId, ) -> Result<bool, MeowError>
Unregisters one previously registered global progress listener.
Returns Ok(false) when the ID does not exist.
§Errors
Returns LockPoisoned when listener storage lock is poisoned.
§Examples
use rusty_cat::api::{MeowClient, MeowConfig};
let client = MeowClient::new(MeowConfig::default());
let id = client.register_global_progress_listener(|_| {})?;
let removed = client.unregister_global_progress_listener(id)?;
assert!(removed);Sourcepub fn clear_global_listener(&self) -> Result<(), MeowError>
pub fn clear_global_listener(&self) -> Result<(), MeowError>
Sourcepub fn set_debug_log_listener(
&self,
listener: Option<DebugLogListener>,
) -> Result<(), DebugLogListenerError>
pub fn set_debug_log_listener( &self, listener: Option<DebugLogListener>, ) -> Result<(), DebugLogListenerError>
Sets or clears the global debug log listener.
- Pass
Some(listener)to set/replace. - Pass
Noneto clear.
This affects all MeowClient instances in the current process.
§Errors
Returns DebugLogListenerError when the internal global listener lock
is poisoned.
§Examples
use std::sync::Arc;
use rusty_cat::api::{Log, MeowClient, MeowConfig};
let client = MeowClient::new(MeowConfig::default());
client.set_debug_log_listener(Some(Arc::new(|log: Log| {
println!("{log}");
})))?;
// Clear listener when no longer needed.
client.set_debug_log_listener(None)?;Source§impl MeowClient
impl MeowClient
Sourcepub async fn try_enqueue<PCB, CCB>(
&self,
task: PounceTask,
progress_cb: PCB,
complete_cb: CCB,
) -> Result<TaskId, MeowError>
pub async fn try_enqueue<PCB, CCB>( &self, task: PounceTask, progress_cb: PCB, complete_cb: CCB, ) -> Result<TaskId, MeowError>
Submits a transfer task to the internal scheduler and returns its
TaskId.
The actual upload/download execution is dispatched to an internal worker system thread. This method only performs lightweight validation and submission, so it does not block the caller thread waiting for full transfer completion.
try_enqueue is also the recovery entrypoint after process restart.
If the application was killed during a previous upload/download,
restart your process and call try_enqueue again to resume that
transfer workflow.
§Back-pressure semantics (why the try_ prefix)
Internally this method uses
tokio::sync::mpsc::Sender::try_send to hand the Enqueue command
to the scheduler worker, not send().await. That means:
- The
awaitpoint in this function is used for task normalization (e.g. resolving upload breakpoints, building an internalInnerTask), not for waiting on command-queue capacity. - If the command queue is momentarily full (bursty enqueue under
MeowConfig::command_queue_capacity), this method returns an immediateCommandSendFailederror instead of suspending the caller until a slot frees up. - Other control APIs (
Self::pause,Self::resume,Self::cancel,Self::snapshot) usesend().awaitand do wait for queue capacity. Only enqueue is fail-fast.
Callers that want to batch-enqueue under burst load should either:
- size
MeowConfig::command_queue_capacityappropriately, or - retry on
CommandSendFailedwith their own back-off, or - rate-limit enqueue calls on the caller side.
The name explicitly carries try_ so this fail-fast behavior is
visible at the call site. If a fully-awaiting variant is introduced
later it should be named enqueue (without the try_ prefix).
§Parameters
task: Built by upload/download task builders.progress_cb: Per-task callback invoked with transfer progress.complete_cb: Callback fired once when task reachescrate::transfer_status::TransferStatus::Complete. The second argument is provider-defined payload returned by upload protocolcomplete_upload; download tasks usually receiveNone.
§Usage rules
taskmust be non-empty (required path/name/url and valid upload size).- Callback should be lightweight and non-blocking.
- Store returned task ID for subsequent task control operations.
try_enqueueis asynchronous task submission, not synchronous transfer.- For restart recovery, re-enqueue the same logical task (same upload/download target and compatible checkpoint context) so the runtime can continue from existing local/remote progress.
§Errors
Returns:
ClientClosedif the client was closed.ParameterEmptyif the task is invalid/empty.CommandSendFailedif the scheduler command queue is full at the moment of submission (see back-pressure semantics above).- Any runtime initialization errors from the executor.
§Examples
use rusty_cat::api::{DownloadPounceBuilder, MeowClient, MeowConfig};
let client = MeowClient::new(MeowConfig::default());
let task = DownloadPounceBuilder::new(
"example.bin",
"./downloads/example.bin",
1024 * 1024,
"https://example.com/example.bin",
)
.build();
let task_id = client
.try_enqueue(
task,
|record| {
println!("status={:?} progress={:.2}", record.status(), record.progress());
},
|task_id, payload| {
println!("task {task_id} completed, payload={payload:?}");
},
)
.await?;
println!("enqueued task: {task_id}");Sourcepub async fn try_enqueue_paused<PCB, CCB>(
&self,
task: PounceTask,
progress_cb: PCB,
complete_cb: CCB,
) -> Result<TaskId, MeowError>
pub async fn try_enqueue_paused<PCB, CCB>( &self, task: PounceTask, progress_cb: PCB, complete_cb: CCB, ) -> Result<TaskId, MeowError>
Imports a transfer task in the paused state without scheduling it.
This is the restart/restore entry point for callers that persist their
own transfer records: rebuild a PounceTask from your database, import
it here, and the task is registered into the scheduler as
TransferStatus::Paused without queueing, so it performs zero
network or file I/O until you explicitly start it.
To start a previously imported task, call Self::resume with the
returned TaskId. A typical “restore N, start a user-selected subset”
flow imports every task with try_enqueue_paused and then calls
Self::resume only for the ids the user chose; the rest stay paused.
§Difference from Self::try_enqueue
try_enqueueschedules immediately (the task becomesPendingand may start transferring as soon as a concurrency slot is free).try_enqueue_pausedregisters the task asPausedand never queues it untilSelf::resumeis called.
Back-pressure is identical: this method uses
tokio::sync::mpsc::Sender::try_send and fails fast with
CommandSendFailed if the command queue is full (see
Self::try_enqueue for the rationale behind the try_ prefix).
§Resume semantics after import
When the imported task is later resumed, the resume point is recomputed by the executor, not taken from any value passed here:
- Download: resumes from the on-disk partial file length, so the
partial file must still exist at the task’s
file_path. - Upload: resumes from the server-reported
next_byteduring the uploadpreparestage.
§Progress reporting while paused
The single Paused FileTransferRecord emitted on import reports
progress 0.0 because no prepare has run yet. Render the imported
task’s real progress from your own persisted record; the SDK corrects it
after the first resume.
§Parameters
Same as Self::try_enqueue: a built task, a per-task progress_cb,
and a complete_cb fired once on terminal Complete.
§Errors
ClientClosedif the client was closed.ParameterEmptyif the task is invalid/empty.CommandSendFailedif the scheduler command queue is full.- Any runtime initialization errors from the executor.
§Examples
use rusty_cat::api::{DownloadPounceBuilder, MeowClient, MeowConfig};
let client = MeowClient::new(MeowConfig::default());
let task = DownloadPounceBuilder::new(
"example.bin",
"./downloads/example.bin",
1024 * 1024,
"https://example.com/example.bin",
)
.build();
// Import without starting it (no HTTP request, no file open).
let task_id = client
.try_enqueue_paused(task, |_record| {}, |_id, _payload| {})
.await?;
// Later, when the user chooses to start this one:
client.resume(task_id).await?;Sourcepub async fn enqueue_and_wait<PCB>(
&self,
task: PounceTask,
progress_cb: PCB,
) -> Result<TaskOutcome, MeowError>
pub async fn enqueue_and_wait<PCB>( &self, task: PounceTask, progress_cb: PCB, ) -> Result<TaskOutcome, MeowError>
Enqueues a task and awaits until it reaches a terminal status.
Wraps Self::try_enqueue with an internal oneshot channel so callers
do not have to write the channel + double-callback + single-send-guard
boilerplate themselves.
§Returns
Ok(TaskOutcome)when the task reachesTransferStatus::Complete.Err(MeowError)carrying the underlying failure forTransferStatus::Failed.Err(MeowError)with codeInnerErrorCode::TaskCanceledforTransferStatus::Canceled.
§Progress
progress_cb receives every FileTransferRecord update, identical to
the per-task progress callback in Self::try_enqueue.
§Cancellation / timeout
Dropping the returned future does not cancel the underlying transfer;
the task continues running in the executor. Use Self::cancel with
the task id (obtainable from progress_cb’s record.task_id()) to
abort an in-flight transfer.
To cap wall-clock waiting time, wrap this future:
let outcome = tokio::time::timeout(
std::time::Duration::from_secs(60),
client.enqueue_and_wait(task, |_| {}),
)
.await??;§Errors
In addition to the terminal-status errors above, propagates any error
from Self::try_enqueue (e.g. ClientClosed, ParameterEmpty,
CommandSendFailed).
§Examples
use rusty_cat::api::{DownloadPounceBuilder, MeowClient, MeowConfig};
let client = MeowClient::new(MeowConfig::default());
let task = DownloadPounceBuilder::new(
"example.bin",
"./downloads/example.bin",
1024 * 1024,
"https://example.com/example.bin",
)
.build();
let outcome = client
.enqueue_and_wait(task, |record| {
println!(
"task={} progress={:.2}",
record.task_id(),
record.progress()
);
})
.await?;
println!("task {} complete, payload={:?}", outcome.task_id, outcome.payload);Sourcepub async fn pause(&self, task_id: TaskId) -> Result<(), MeowError>
pub async fn pause(&self, task_id: TaskId) -> Result<(), MeowError>
Pauses a running or pending PounceTask by ID.
This API sends a control command to the internal scheduler worker thread. It does not execute transfer pause logic on the caller thread.
§Usage rules
Call this with a valid task ID returned by Self::try_enqueue or
observed through Self::enqueue_and_wait’s progress callback.
§Errors
BinaryTask IDs return InvalidTaskState because binary tasks only
support cancellation. Other errors include ClientClosed,
TaskNotFound, or Pounce state-transition errors.
§Examples
use rusty_cat::api::{MeowClient, MeowConfig, TaskId};
let client = MeowClient::new(MeowConfig::default());
client.pause(task_id).await?;Sourcepub async fn resume(&self, task_id: TaskId) -> Result<(), MeowError>
pub async fn resume(&self, task_id: TaskId) -> Result<(), MeowError>
Resumes a previously paused PounceTask.
The same TaskId continues to identify the task after resume.
The resume command is forwarded to the internal scheduler worker
thread, so caller thread is not responsible for running transfer logic.
§Errors
Returns ClientClosed, TaskNotFound, or InvalidTaskState.
§Examples
use rusty_cat::api::{MeowClient, MeowConfig, TaskId};
let client = MeowClient::new(MeowConfig::default());
client.resume(task_id).await?;Sourcepub async fn cancel(&self, task_id: TaskId) -> Result<(), MeowError>
pub async fn cancel(&self, task_id: TaskId) -> Result<(), MeowError>
Cancels a task by ID.
Cancellation is routed to the isolated Binary executor for a live BinaryTask ID; all other IDs retain the existing Pounce scheduler path.
§Usage rules
Cancellation is best-effort; protocol-specific cleanup may run.
§Errors
Returns ClientClosed, TaskNotFound, or runtime cancellation errors.
§Examples
use rusty_cat::api::{MeowClient, MeowConfig, TaskId};
let client = MeowClient::new(MeowConfig::default());
client.cancel(task_id).await?;Sourcepub async fn snapshot(&self) -> Result<TransferSnapshot, MeowError>
pub async fn snapshot(&self) -> Result<TransferSnapshot, MeowError>
Returns a snapshot of queue and active Pounce transfer groups.
Useful for diagnostics and external monitoring dashboards. BinaryTask state is intentionally excluded and never queried here.
§Errors
Returns ClientClosed, runtime command delivery errors, or scheduler
snapshot retrieval errors.
§Examples
use rusty_cat::api::{MeowClient, MeowConfig};
let client = MeowClient::new(MeowConfig::default());
let snap = client.snapshot().await?;
println!("queued={}, active={}", snap.queued_groups, snap.active_groups);Sourcepub async fn close(&self) -> Result<(), MeowError>
pub async fn close(&self) -> Result<(), MeowError>
Closes this client and every initialized Pounce/Binary executor.
close is the terminal lifecycle operation for a MeowClient. After
it succeeds, this client stays permanently closed; submit more work by
constructing a new MeowClient and enqueueing tasks there.
After a successful close:
- New task and control operations on this client are rejected with
ClientClosed. - All known unfinished task groups (queued, paused, or active) receive
a
Pausedprogress notification through their task callback and all registered global listeners. - In-flight transfers are cancelled and the scheduler state is cleared.
- Already submitted callback jobs are drained before returning.
- The internal scheduler thread is joined, which drops its Tokio runtime and releases SDK-owned background execution resources.
Paused is used for shutdown notifications rather than Canceled so
callers can recreate a client later and re-enqueue the same logical
transfer when they want to resume from available breakpoint state.
§Idempotency
Calling close more than once returns ClientClosed.
A client that initialized BinaryExecutor performs mixed teardown on an
SDK-owned coordinator, so dropping the close Future does not strand the
lifecycle and does not require an additional caller-side Tokio runtime.
If BinaryExecutor was never initialized, close does not create any
Binary runtime, thread, HTTP client, or channel.
§Retry behavior
If BinaryExecutor was never initialized, the existing Pounce-only close
failure behavior is preserved: the closed flag is rolled back so callers
can retry. For a mixed client, a partial close never reopens business
APIs; another close call retries only unfinished teardown.
§Errors
Returns ClientClosed when already closed, InvalidTaskState when
synchronously awaited from this client’s transfer callback dispatcher,
or underlying executor close errors when shutdown is not completed.
§Examples
use rusty_cat::api::{MeowClient, MeowConfig};
let client = MeowClient::new(MeowConfig::default());
client.close().await?;Source§impl MeowClient
impl MeowClient
Sourcepub fn try_enqueue_binary_task<CCB>(
&self,
task: BinaryTask,
complete_cb: CCB,
) -> Result<TaskId, MeowError>
pub fn try_enqueue_binary_task<CCB>( &self, task: BinaryTask, complete_cb: CCB, ) -> Result<TaskId, MeowError>
Enqueues one bounded, in-memory HTTP GET on an isolated executor.
Binary tasks support Self::cancel only. They do not support
pause/resume and are deliberately excluded from Self::snapshot.
The callback may run concurrently before this method returns. It owns a
BinaryDownloadOutput; move or clone its Bytes when retaining data.
The callback must return in bounded time and must not synchronously wait
for close() on this same client because close drains callbacks.
At most two binary HTTP requests run concurrently. At most 1024 accepted tasks may be queued, active, or waiting for their callback to return.