2.7 KiB
2.7 KiB
Спецификация 02: FitFetcher — цикл забора и дедупликация
Источники: Ф-8.1, module_reqs разд. 2 (два рубежа дедупа), 🛡3 (сбой одного файла не останавливает пакет).
1. Сигнатура
public FitFetcher(IConnectionString cs, GarminClient client,
string downloadDir, string athleteName);
public (int Downloaded, int Skipped, int Failed) FetchNew(
int limit = 20, CancellationToken ct = default);
2. Алгоритм
- Атлет:
athleteName→src.athlete(первый по имени); не найден →FetchException(CLI переведёт в код 1; 🛡1 — факты без атлета не пишутся). client.Login()— исключения пробрасываются (без частичного результата).ListActivities(limit)→ фильтрactivityType == "strength_training".- Для каждой активности, по порядку списка:
downloadDir\{activityId}.fitсуществует →Skipped++(рубеж 1 — без сети), далее;- скачать → записать во временный файл →
FitParser.Parse: сбой → файл остаётся как{activityId}.fit? нет — битый download переносится в{activityId}.fit.bad(не мешает кэшу),Failed++, далее (🛡3); WorkoutImporter.Import(athleteId, facts)— дедуп поfit_file_idвнутри (возврат существующего id, записи нет); если это дубль (возврат id существующей строки, файл не новый) → скачанный файл удаляется (рубеж 2, 🛡2),Skipped++;- иначе файл переименовывается в
{activityId}.fit(кэш),Downloaded++.
- Возврат (Downloaded, Skipped, Failed).
3. Ошибки и коды
FetchException(атлет/конфигурация) → CLI код 1;GarminAuthException→ CLI код 2 + stdout-подсказка «используйте папку импорта (--mode import)» (fallback О-1-А);- сбои отдельных файлов → код 2 при
Failed > 0.
4. Инварианты 🛡
- записи в БД — только через
WorkoutImporter(конвейер m06); - изоляция: факты пишутся только
athleteIdатлета-владельца учётки; - повторный запуск не качает дубли и не создаёт дублей в БД.