scout_rs 0.3.2

A Rust client for the Scout API that allows uploading events and images to the Scout database
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
# Scout Rust Client

A Rust client for the Scout API that allows uploading events and images to the Scout database.

## Features

- Upload individual events with images and tags
- Upload entire directories of images with metadata extracted from filenames
- **NEW**: Batch upload multiple events and files efficiently
- Automatic filename parsing for location and timestamp data
- Comprehensive error handling and upload reporting

## Installation

Add this to your `Cargo.toml`:

```toml
[dependencies]
scout_rs = { path = "./scout_rs" }
```

## Usage

### Basic Client Setup

```rust
use scout_rs::client::ScoutClient;

let mut client = ScoutClient::new(
    "http://localhost:3000/api/scout".to_string(),  // For local testing
    "your_api_key_here".to_string()
)?;

// Identify and load device/herd information into state
client.identify().await?;
```

### Upload Individual Event

```rust
use scout_rs::client::{Event, Tag};

let event = Event::new(
    Some("Test message".to_string()),
    Some("https://example.com/media".to_string()),
    None,
    Some("https://example.com/earthranger".to_string()),  // Optional EarthRanger URL
    40.7128,  // latitude
    -74.006,  // longitude
    100.0,    // altitude
    45.0,     // heading
    "image".to_string(),
    1,        // device_id
    timestamp_observation,
    false     // is_public
);

let tags = vec![
    Tag::new(1, 100.0, 200.0, 50.0, 30.0, 0.95, "auto".to_string(), "person".to_string())
];

let result = client.post_event_with_tags(&event, &tags, "path/to/image.jpg").await?;
```

### Batch Upload Multiple Events

For uploading multiple events efficiently, use the batch upload method:

```rust
use scout_rs::client::{Event, Tag};

// Prepare multiple events and files
let events_and_files = vec![
    (event1, tags1, "path/to/image1.jpg".to_string()),
    (event2, tags2, "path/to/image2.jpg".to_string()),
    (event3, tags3, "path/to/image3.jpg".to_string()),
];

// Upload in batches of 10 (max 50 per batch)
let result = client.post_events_batch(&events_and_files, 10).await?;
result.print_summary();
```

### Upload Directory of Images

The directory upload feature automatically parses filenames to extract metadata. Filenames should follow this format:

```
device_id|timestamp|lat_underscore|lon_underscore|altitude|heading.ext
```

Example: `29|1733351509|19_754824|-155_15393|10|0.jpg`

**Fallback Behavior**: If filename parsing fails, the system will use default values instead of skipping the file. This ensures all images are uploaded even if they don't follow the expected naming convention.

**Device ID**: The device ID is automatically retrieved from stored state or fetched from the API using your API key.

#### Standard Directory Upload

The `upload_directory` method now uses batch uploads internally for better performance while maintaining the same simple interface:

```rust
// Create client and identify device/herd
let mut client = ScoutClient::new(scout_url, api_key)?;
client.identify().await?;  // Load device and herd into state

let result = client.upload_directory(
    "/path/to/photo/folder",
    Some("https://example.com/earthranger"),  // earthranger_url (optional)
    false,  // is_public
    Some("Uploaded via Rust client"),  // optional message
    Some(19.754824),  // default_latitude (optional)
    Some(-155.15393), // default_longitude (optional)
    Some(10.0),       // default_altitude (optional)
    Some(0.0),        // default_heading (optional)
    Some(20)          // batch_size (optional, default: 20)
).await?;

// Print upload summary
result.print_summary();
```

**Note**: This method now uses batch uploads internally with a default batch size of 10, providing significant performance improvements over individual file uploads.

#### Batch Directory Upload (Recommended)

For better performance with large directories, use the batch upload version:

```rust
// Create client and identify device/herd
let mut client = ScoutClient::new(scout_url, api_key)?;
client.identify().await?;  // Load device and herd into state

let result = client.upload_directory_batch(
    "/path/to/photo/folder",
    Some("https://example.com/earthranger"),  // earthranger_url (optional)
    false,  // is_public
    Some("Uploaded via Rust client"),  // optional message
    Some(19.754824),  // default_latitude (optional)
    Some(-155.15393), // default_longitude (optional)
    Some(10.0),       // default_altitude (optional)
    Some(0.0),        // default_heading (optional)
    10                // batch_size (1-50)
).await?;

// Print batch upload summary
result.print_summary();
```

### Using the Example Binaries

#### Standard Directory Upload

The `upload_directory` binary accepts command-line arguments for easy use:

```bash
# Basic usage
cargo run --bin upload_directory -- --directory /path/to/photos --api-key your_api_key

# With default location values for files that can't be parsed
cargo run --bin upload_directory -- \
  --directory /path/to/photos \
  --default-latitude 19.754824 \
  --default-longitude -155.15393 \
  --default-altitude 10.0 \
  --default-heading 0.0

# With custom message and public events
cargo run --bin upload_directory -- \
  --directory /path/to/photos \
  --message "Uploaded via Rust client" \
  --public

# With EarthRanger URL
cargo run --bin upload_directory -- \
  --directory /path/to/photos \
  --earthranger-url "https://your-earthranger-instance.com"

# With custom batch size
cargo run --bin upload_directory -- \
  --directory /path/to/photos \
  --batch-size 25
```

#### Batch Directory Upload (Recommended)

The `upload_batch` binary provides efficient batch uploading:

```bash
# Basic batch upload with default batch size of 10
cargo run --bin upload_batch -- --directory /path/to/photos

# Custom batch size (max 50)
cargo run --bin upload_batch -- \
  --directory /path/to/photos \
  --batch-size 25

# With all options
cargo run --bin upload_batch -- \
  --directory /path/to/photos \
  --batch-size 20 \
  --earthranger-url "https://your-earthranger-instance.com" \
  --message "Batch uploaded via Rust client" \
  --public \
  --default-latitude 19.754824 \
  --default-longitude -155.15393 \
  --default-altitude 10.0 \
  --default-heading 0.0 \
  --log-level debug
```

#### Command-line Options for Directory Upload

- `-d, --directory`: Directory path containing images to upload (required)
- `--scout-url`: Scout API URL (default: http://localhost:3000/api/scout)
- `--api-key`: API key for authentication (or set SCOUT_DEVICE_API_KEY env var)
- `--earthranger-url`: EarthRanger URL (optional)
- `--public`: Make events public (default: false)
- `-m, --message`: Message to include with events (optional)
- `--default-latitude`: Default latitude for files without metadata (optional)
- `--default-longitude`: Default longitude for files without metadata (optional)
- `--default-altitude`: Default altitude for files without metadata (optional)
- `--default-heading`: Default heading for files without metadata (optional)
- `--batch-size`: Batch size for uploads (1-50, default: 20)
- `--log-level`: Log level (trace, debug, info, warn, error) (default: info)

**Note**: The device ID is automatically retrieved from your API key, so you don't need to specify it manually.

## Testing with Localhost

For development and testing, you can use localhost instead of the production server:

### Environment Setup

```bash
# Set your API key
export SCOUT_API_KEY=your_api_key_here

# Optional: Set custom Scout URL (defaults to localhost:3000)
export SCOUT_URL=http://localhost:3000/api/scout
```

### Running the Frontend Server

Make sure your frontend server is running on localhost:3000:

```bash
cd frontend
npm run dev
```

### Testing Uploads

```bash
# Test directory upload with localhost
cargo run --bin upload_directory -- \
  --directory /path/to/photos \
  --scout-url http://localhost:3000/api/scout

# Test batch upload with localhost
cargo run --bin upload_batch -- \
  --directory /path/to/photos \
  --batch-size 5
```

## Performance Comparison

| Method          | Files | Time  | Network Requests |
| --------------- | ----- | ----- | ---------------- |
| Individual      | 100   | ~100s | 100              |
| Batch (size 10) | 100   | ~20s  | 10               |
| Batch (size 25) | 100   | ~12s  | 4                |

_Times are approximate and depend on network conditions and file sizes._

## Logging

The client uses the `tracing` crate for structured logging. You can control the log level using the `--log-level` option:

```bash
# Verbose debugging
cargo run --bin upload_batch -- --directory /path/to/photos --log-level debug

# Only warnings and errors
cargo run --bin upload_batch -- --directory /path/to/photos --log-level warn

# All logs including trace
cargo run --bin upload_batch -- --directory /path/to/photos --log-level trace
```

#### Environment Variables

You can also set the API key via environment variable:

```bash
export SCOUT_API_KEY=your_api_key_here
cargo run --bin upload_batch -- --directory /path/to/photos
```

## Filename Format

Images should be named with the following format:

- `device_id`: The device identifier (integer)
- `timestamp`: Unix timestamp when the image was taken
- `lat_underscore`: Latitude with underscores instead of decimal points (e.g., `19_754824` for 19.754824)
- `lon_underscore`: Longitude with underscores instead of decimal points (e.g., `-155_15393` for -155.15393)
- `altitude`: Altitude in meters
- `heading`: Heading in degrees
- `ext`: Image file extension (jpg, jpeg, png, webp)

Example: `29|1733351509|19_754824|-155_15393|10|0.jpg`

## Supported Image Formats

- JPEG (.jpg, .jpeg)
- PNG (.png)
- WebP (.webp)

## Error Handling

The client provides comprehensive error handling:

- **Filename Parsing**: Invalid filenames are logged and processed with default values instead of being skipped
- **Failed Uploads**: Network or server errors are tracked and reported
- **File System Errors**: Missing directories or files are caught and reported
- **Graceful Degradation**: The system continues processing other files even if some fail
- **Batch Failures**: If a batch fails, individual files are marked as failed and processing continues

### Fallback Values

When filename parsing fails, the following defaults are used:

- **Timestamp**: Current system time
- **Location**: Default latitude/longitude (if provided) or 0.0/0.0
- **Altitude**: Default altitude (if provided) or 0.0
- **Heading**: Default heading (if provided) or 0.0

### Upload Results

Both upload methods return detailed results:

```rust
// Standard upload result
let result: UploadResult = client.upload_directory(...).await?;
println!("Uploaded {} of {} files", result.successful_uploads, result.total_files);

// Batch upload result
let batch_result: BatchUploadResult = client.upload_directory_batch(...).await?;
println!("Completed {} batches, uploaded {} of {} files",
         batch_result.successful_batches,
         batch_result.successful_uploads,
         batch_result.total_files);
```

## API Endpoints

The client supports both individual and batch upload endpoints:

- **Individual**: `POST /events` - Upload single event with file
- **Batch**: `POST /events/batch` - Upload multiple events with files (max 50 per batch)

The batch endpoint is more efficient for uploading multiple files as it reduces network overhead and server load.

## Testing

### Setting Up Environment Variables

To run the tests, you need to set up your Scout API key as an environment variable. Create a `.env` file in the project root:

```bash
# Create .env file
echo "SCOUT_API_KEY=your_actual_api_key_here" > .env
```

**Important**: The `.env` file is already added to `.gitignore` to prevent accidentally committing your API key.

### Running Tests

```bash
# Run all tests
cargo test

# Run only unit tests (no API calls)
cargo test --lib

# Run integration tests with API calls
cargo test --test scout_client

# Run specific test
cargo test test_session_api_integration

# Run all session-related tests
cargo test session

# Run session creation tests specifically
cargo test test_session_creation_api

# Run tests with verbose output
cargo test -- --nocapture
```

### Test Categories

- **Unit Tests**: Test individual functions without API calls
- **Integration Tests**: Test API interactions (require valid API key)
- **Error Handling Tests**: Test error scenarios with invalid API keys
- **Session Tests**: Test session creation, retrieval, and management functionality

### Test Environment Variables

The tests automatically load environment variables from the `.env` file. If no `SCOUT_API_KEY` is found, integration tests will be skipped with a message.

### Session Testing

The client includes comprehensive tests for session management functionality:

#### Session Creation Tests

- **`test_session_creation_api`**: Tests creating sessions with realistic data and validates the returned session ID
- **`test_session_creation_error_handling`**: Tests error handling with invalid data (negative device IDs, invalid timestamps, etc.)
- **`test_session_api_integration`**: Tests the full session lifecycle including creation, retrieval, and cleanup

#### Session Management Tests

- **`test_sessions_by_herd`**: Tests retrieving sessions by herd ID
- **`test_session_update_methods`**: Tests session update functionality
- **`test_session_with_id`**: Tests session creation with pre-assigned IDs

#### Running Session Tests

```bash
# Run all session-related tests
cargo test session

# Run specific session creation test
cargo test test_session_creation_api

# Run session error handling tests
cargo test test_session_creation_error_handling

# Run with verbose output to see detailed logs
cargo test test_session_creation_api -- --nocapture
```

#### Session Test Features

- **Realistic Data**: Tests use realistic GPS coordinates, altitude, and velocity data
- **Validation**: Tests validate that created sessions have correct data and positive IDs
- **Cleanup**: Tests automatically clean up created sessions to avoid test pollution
- **Error Handling**: Tests verify proper error handling for invalid inputs
- **Environment Variables**: Tests use environment variables for API configuration

### Example .env File

```env
# Scout API Key for testing
SCOUT_API_KEY=your_actual_api_key_here

# Optional: Custom Scout URL for testing
SCOUT_URL=https://www.adventurelabs.earth/api/scout
```

## License

GPL-3.0