Skip to content

Commit

Permalink
refactor(services/importer): extract importing exercises into a function
Browse files Browse the repository at this point in the history
  • Loading branch information
IgnisDa committed Dec 3, 2024
1 parent d469370 commit 5bf8681
Showing 1 changed file with 141 additions and 131 deletions.
272 changes: 141 additions & 131 deletions crates/services/importer/src/strong_app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,142 +54,152 @@ pub async fn import(
let mut completed = vec![];
let mut failed = vec![];
if let Some(csv_path) = input.data_export_path {
let file_string = fs::read_to_string(&csv_path)?;
// DEV: Delimiter is `;` on android and `,` on iOS, so we determine it by reading the first line
let data = file_string.clone();
let first_line = data.lines().next().unwrap();
let delimiter = if first_line.contains(';') {
b';'
} else if first_line.contains(',') {
b','
import_exercises(csv_path, ss, &mut failed, &mut completed).await?;
}
Ok(ImportResult {
failed,
completed,
..Default::default()
})
}

async fn import_exercises(
csv_path: String,
ss: &Arc<SupportingService>,
failed: &mut Vec<ImportFailedItem>,
completed: &mut Vec<ImportCompletedItem>,
) -> Result<()> {
let file_string = fs::read_to_string(&csv_path)?;
// DEV: Delimiter is `;` on android and `,` on iOS, so we determine it by reading the first line
let data = file_string.clone();
let first_line = data.lines().next().unwrap();
let delimiter = if first_line.contains(';') {
b';'
} else if first_line.contains(',') {
b','
} else {
return Err("Could not determine delimiter".into());
};
let mut entries_reader = ReaderBuilder::new()
.delimiter(delimiter)
.from_reader(file_string.as_bytes())
.deserialize::<Entry>()
.map(|r| r.unwrap())
.collect_vec();
// DEV: Without this, the last workout does not get appended
entries_reader.push(Entry {
set_order: "0".to_string(),
date: "invalid".to_string(),
..Default::default()
});
let mut unique_exercises: HashMap<String, exercise::Model> = HashMap::new();
let mut exercises = vec![];
let mut sets = vec![];
let mut notes = vec![];
for (entry, next_entry) in entries_reader.into_iter().tuple_windows() {
if entry.set_order == "Note" {
continue;
}
let exercise_lot = if entry.seconds.is_some() && entry.distance.is_some() {
ExerciseLot::DistanceAndDuration
} else if entry.seconds.is_some() {
ExerciseLot::Duration
} else if entry.reps.is_some() && entry.weight.is_some() {
ExerciseLot::RepsAndWeight
} else if entry.reps.is_some() {
ExerciseLot::Reps
} else {
return Err("Could not determine delimiter".into());
failed.push(ImportFailedItem {
lot: None,
identifier: format!(
"Workout #{}, Set #{}",
entry.workout_number, entry.set_order
),
step: ImportFailStep::InputTransformation,
error: Some(format!(
"Could not determine exercise lot: {}",
serde_json::to_string(&entry).unwrap()
)),
});
continue;
};
let mut entries_reader = ReaderBuilder::new()
.delimiter(delimiter)
.from_reader(file_string.as_bytes())
.deserialize::<Entry>()
.map(|r| r.unwrap())
.collect_vec();
// DEV: Without this, the last workout does not get appended
entries_reader.push(Entry {
set_order: "0".to_string(),
date: "invalid".to_string(),
..Default::default()
});
let mut unique_exercises: HashMap<String, exercise::Model> = HashMap::new();
let mut exercises = vec![];
let mut sets = vec![];
let mut notes = vec![];
for (entry, next_entry) in entries_reader.into_iter().tuple_windows() {
if entry.set_order == "Note" {
continue;
}
let exercise_lot = if entry.seconds.is_some() && entry.distance.is_some() {
ExerciseLot::DistanceAndDuration
} else if entry.seconds.is_some() {
ExerciseLot::Duration
} else if entry.reps.is_some() && entry.weight.is_some() {
ExerciseLot::RepsAndWeight
} else if entry.reps.is_some() {
ExerciseLot::Reps
} else {
failed.push(ImportFailedItem {
lot: None,
identifier: format!(
"Workout #{}, Set #{}",
entry.workout_number, entry.set_order
),
step: ImportFailStep::InputTransformation,
error: Some(format!(
"Could not determine exercise lot: {}",
serde_json::to_string(&entry).unwrap()
)),
});
continue;
};
let existing_exercise = Exercise::find()
.filter(exercise::Column::Id.eq(&entry.exercise_name))
.filter(exercise::Column::Lot.eq(exercise_lot))
.one(&ss.db)
.await?;
let exercise_id = if let Some(db_ex) = existing_exercise {
db_ex.id
} else if let Some(mem_ex) = unique_exercises.get(&entry.exercise_name) {
mem_ex.id.clone()
} else {
let id = format!("{} [{}]", entry.exercise_name, nanoid!(5));
unique_exercises.insert(
entry.exercise_name.clone(),
exercise::Model {
id: id.clone(),
lot: exercise_lot,
..Default::default()
},
);
id
};
ryot_log!(debug, "Importing exercise with id = {}", exercise_id);
let weight = entry.weight.map(|d| if d == dec!(0) { dec!(1) } else { d });
sets.push(UserWorkoutSetRecord {
statistic: WorkoutSetStatistic {
weight,
reps: entry.reps,
duration: entry.seconds.and_then(|r| r.checked_div(dec!(60))),
distance: entry.distance.and_then(|d| d.checked_div(dec!(1000))),
let existing_exercise = Exercise::find()
.filter(exercise::Column::Id.eq(&entry.exercise_name))
.filter(exercise::Column::Lot.eq(exercise_lot))
.one(&ss.db)
.await?;
let exercise_id = if let Some(db_ex) = existing_exercise {
db_ex.id
} else if let Some(mem_ex) = unique_exercises.get(&entry.exercise_name) {
mem_ex.id.clone()
} else {
let id = format!("{} [{}]", entry.exercise_name, nanoid!(5));
unique_exercises.insert(
entry.exercise_name.clone(),
exercise::Model {
id: id.clone(),
lot: exercise_lot,
..Default::default()
},
note: None,
rest_time: None,
confirmed_at: None,
lot: SetLot::Normal,
);
id
};
ryot_log!(debug, "Importing exercise with id = {}", exercise_id);
let weight = entry.weight.map(|d| if d == dec!(0) { dec!(1) } else { d });
sets.push(UserWorkoutSetRecord {
statistic: WorkoutSetStatistic {
weight,
reps: entry.reps,
duration: entry.seconds.and_then(|r| r.checked_div(dec!(60))),
distance: entry.distance.and_then(|d| d.checked_div(dec!(1000))),
..Default::default()
},
note: None,
rest_time: None,
confirmed_at: None,
lot: SetLot::Normal,
});
if let Some(n) = entry.notes {
notes.push(n);
}
if next_entry.set_order <= entry.set_order {
exercises.push(UserExerciseInput {
sets,
notes,
exercise_id,
assets: None,
});
if let Some(n) = entry.notes {
notes.push(n);
}
if next_entry.set_order <= entry.set_order {
exercises.push(UserExerciseInput {
sets,
notes,
exercise_id,
assets: None,
});
sets = vec![];
notes = vec![];
}
if next_entry.date != entry.date {
let ndt = NaiveDateTime::parse_from_str(&entry.date, "%Y-%m-%d %H:%M:%S")
.expect("Failed to parse input string");
let ndt = utils::get_date_time_with_offset(ndt, &ss.timezone);
let workout_duration =
Duration::try_seconds(entry.workout_duration.parse().unwrap()).unwrap();
completed.push(ImportCompletedItem::Workout(UserWorkoutInput {
exercises,
assets: None,
start_time: ndt,
supersets: vec![],
template_id: None,
repeated_from: None,
create_workout_id: None,
update_workout_id: None,
name: entry.workout_name,
comment: entry.workout_notes,
end_time: ndt + workout_duration,
update_workout_template_id: None,
}));
exercises = vec![];
}
sets = vec![];
notes = vec![];
}
if next_entry.date != entry.date {
let ndt = NaiveDateTime::parse_from_str(&entry.date, "%Y-%m-%d %H:%M:%S")
.expect("Failed to parse input string");
let ndt = utils::get_date_time_with_offset(ndt, &ss.timezone);
let workout_duration =
Duration::try_seconds(entry.workout_duration.parse().unwrap()).unwrap();
completed.push(ImportCompletedItem::Workout(UserWorkoutInput {
exercises,
assets: None,
start_time: ndt,
supersets: vec![],
template_id: None,
repeated_from: None,
create_workout_id: None,
update_workout_id: None,
name: entry.workout_name,
comment: entry.workout_notes,
end_time: ndt + workout_duration,
update_workout_template_id: None,
}));
exercises = vec![];
}
completed.extend(
unique_exercises
.values()
.cloned()
.map(ImportCompletedItem::Exercise),
);
}
Ok(ImportResult {
failed,
completed,
..Default::default()
})
completed.extend(
unique_exercises
.values()
.cloned()
.map(ImportCompletedItem::Exercise),
);
Ok(())
}

0 comments on commit 5bf8681

Please sign in to comment.