refactor: extract lib.rs, remove club module, add bounded concurrency

- Extract shared types (Car, BestCarRecord, AnalysisContext) into src/lib.rs
- Replace global static BEST_CARS with injected AnalysisContext for testability
- Add semaphore-bounded concurrency (limit 20) in parse_all_times()
- Replace hardcoded track name match with track_names.json loaded via include_str!
- Replace magic number 115 with const MAX_RQ: u8 = 113
- Improve floating-point tie comparison with TIME_TOLERANCE (5ms) constant
- Remove src/club.rs binary (topdrives.club no longer maintained)
- Remove scraper dependency from Cargo.toml
- Remove tracks.txt (replaced by track_names.json)
- Remove empty/placeholder test bodies
- Improve error handling in get_file() for response body read
- Add load_my_cars_data() combining owned + wishlist extraction in one I/O
- Update CHANGELOG, README, and all docs to reflect changes
This commit is contained in:
Jan-Peter von Hunnius 2026-05-06 20:20:15 +02:00
parent 6a68469239
commit 89fa69e3c8
14 changed files with 874 additions and 2037 deletions

View file

@ -23,7 +23,7 @@ async fn main()
**Purpose**: Command-line interface and operation dispatcher
**Arguments**: Command line arguments
- `args[1]`: Command (`fetch`, `best`, `missing`, `trash`, `details`, `track`)
- `args[1]`: Command (`fetch`, `best`, `missing`, `trash`, `details`, `track`, `full_script`)
**Commands**:
- `fetch`: Download all data files
@ -32,6 +32,7 @@ async fn main()
- `trash`: Show removable cars
- `details`: Export all timing data
- `track`: Track-specific analysis
- `full_script`: Run fetch + all analysis commands
**Example**:
```bash
@ -148,7 +149,7 @@ async fn parse_all_times()
**Purpose**: Coordinates parallel processing of car timing files
**Concurrency**: One async task per car file
**Concurrency**: One async task per car file, bounded by a semaphore (limit 20)
**Synchronization**: Waits for all tasks to complete
##### `update_best_car(track_id: &str, car_rq: u8, surface: &str, new_record: BestCarRecord)`
@ -161,12 +162,12 @@ fn update_best_car(track_id: &str, car_rq: u8, surface: &str, new_record: BestCa
**Purpose**: Implements core best-car selection algorithm
**RQ Range Analysis**:
- Tests car from base RQ up to maximum (115)
- Tests car from base RQ up to `MAX_RQ` (113)
- Compares against existing best times
- Updates records for improved performance
**Logic**:
1. For each RQ level from car_rq to 115:
1. For each RQ level from car_rq to `MAX_RQ` (113):
2. If new time is better → replace record
3. If new time ties → add to list (if not duplicate)
4. If new time is worse → ignore
@ -205,7 +206,7 @@ async fn fetch_times()
**Purpose**: Batch download of car timing data
**Rate Limiting**: 20 concurrent downloads maximum
**Rate Limiting**: 20 concurrent downloads maximum (semaphore)
**Progress**: Shows download progress for each file
##### `fetch_car(rid: String)`
@ -258,6 +259,10 @@ async fn get_file(file_url: &str, local_file_path: &str)
- Error handling with graceful failure
- Progress indication through errors
### Shared Library (`src/lib.rs`)
Common utilities shared across the project.
##### `translate_track_name(track_code: &str) -> &str`
**Converts track codes to human-readable names**
@ -267,7 +272,9 @@ fn translate_track_name(track_code: &str) -> &str
**Purpose**: User-friendly track name display
**Coverage**: 200+ track mappings
**Implementation**: Loads mappings from `track_names.json` at compile time via `include_str!`
**Coverage**: All tracks defined in `track_names.json`
**Fallback**: Returns original code if not found
##### `translate_track_type(track_code: &str) -> &str`
@ -299,89 +306,6 @@ fn split_prefix_postfix<'a>(input: &'a str, delimiter: &'a str) -> Option<(&'a s
split_prefix_postfix("laguna_a00", "_a") // Some(("laguna", "00"))
```
### Club Module (`src/club.rs`)
Alternative data source using web scraping from topdrives.club.
#### Public Functions
##### `main()`
**Entry point for club scraper**
```rust
#[tokio::main]
async fn main()
```
**Purpose**: Web scraping interface for live data
**Arguments**:
- Command line arguments for RQ and track filtering
- `-d` flag for detailed output
##### `get_page(url: &str) -> String`
**Fetches HTML content from URLs**
```rust
async fn get_page(url: &str) -> String
```
**Purpose**: Downloads web pages for scraping
**Security**: Accepts invalid SSL certificates for compatibility
##### `parse_best_car(text: String) -> Car`
**Extracts car data from HTML content**
```rust
fn parse_best_car(text: String) -> Car
```
**Purpose**: Parses HTML tables to extract car information
**CSS Selectors**:
- `.carList__item-manufacturer`: Car brand
- `.carList__item-model`: Car model
- `.carList__item-year`: Model year
- `.carList__item-level`: Class and RQ (complex parsing)
- `.carList__item-upgrade`: Upgrade configuration
##### `parse_tracks(file_name: &str) -> HashMap<u16, String>`
**Loads track definitions from file**
```rust
fn parse_tracks(file_name: &str) -> HashMap<u16, String>
```
**Purpose**: Creates track ID to name mappings
**Format**: Text file with `ID NAME` format per line
##### `get_best_car(track: u16, track_name: String, max_level: u16, detailed: bool) -> String`
**Scrapes best car data for specific track**
```rust
async fn get_best_car(track: u16, track_name: String, max_level: u16, detailed: bool) -> String
```
**Purpose**: Fetches and parses best car from topdrives.club
**API Integration**:
- Constructs topdrives.club API URLs
- Parses HTML response tables
- Formats output based on detail level
##### `parse_argument(args: &Vec<String>, index: usize, min: u16, max: u16) -> u16`
**Validates command line arguments**
```rust
fn parse_argument(args: &Vec<String>, index: usize, min: u16, max: u16) -> u16
```
**Purpose**: Input validation with range checking
**Panics**: On invalid values with descriptive messages
## Data Structures
### Records Module Data Types
@ -436,22 +360,15 @@ type TripletKey = (String, u8, String);
// Track ID RQ Level Surface Condition
```
### Club Module Data Types
#### `Car`
**Scraped car data from website**
### Constants
#### `MAX_RQ`
```rust
struct Car {
class: String, // Class letter (S, A, B, etc.)
rq: u8, // Rarity level
manufacturer: String, // Brand name
model: String, // Model name
year: u16, // Model year
upgrade: String, // Upgrade config
}
const MAX_RQ: u8 = 113;
```
**Purpose**: Maximum RQ level for range analysis. Replaces former magic number 115 with a named constant.
## Global State
### `BEST_CARS` Cache
@ -533,7 +450,7 @@ fetch_times().await;
- **Per Car**: ~1-2KB metadata + timing data
### Concurrency
- **Downloads**: 20 concurrent connections max
- **Downloads**: 20 concurrent connections max (semaphore bounded)
- **Processing**: One task per car file
- **Thread Safety**: Mutex-protected shared state
@ -544,41 +461,16 @@ fetch_times().await;
2. Implement analysis function
3. Update help documentation
### Adding New Data Sources
1. Implement new module (similar to `club.rs`)
2. Add data source selection logic
3. Update configuration system
### Adding New Tracks
1. Update `tracks_repo.json` with new track definitions
2. Add track name mapping to `track_names.json`
3. Track names are compiled in at build time
### Custom Analysis Types
1. Create new output formatting functions
2. Add aggregation algorithms
3. Extend data structures as needed
## Testing
### Unit Tests
```rust
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_parse_car_times() {
// Test implementation
}
}
```
### Integration Tests
- Full workflow testing
- File I/O validation
- Network request mocking
### Benchmarking
- Parse performance measurement
- Memory usage profiling
- Concurrent operation testing
## Security Considerations
### Network Security
@ -643,12 +535,14 @@ cargo flamegraph --bin records -- best
- **Future**: Semantic versioning maintained
### Breaking Changes
- None currently planned
- Advance notice provided for API changes
- Deprecation warnings for removed features
- Club module (`src/club.rs`) has been removed
- `scraper` dependency removed
- `tracks.txt` removed (replaced by `track_names.json`)
- Magic number 115 replaced with `const MAX_RQ: u8 = 113`
- `translate_track_name()` now loads from `track_names.json` instead of hardcoded match
---
**API Version**: 0.2.0
**Last Updated**: 2024-12-XX
**Compatibility**: Rust 1.70.0+
**Last Updated**: 2026-05-06
**Compatibility**: Rust 1.70.0+