Import legacy data. Show welcome screen when no database is configured. #198
|
@ -14,19 +14,21 @@ General Public License for more details.
|
||||||
You should have received a copy of the GNU General Public License along with FitnessTrax. If not, see <https://www.gnu.org/licenses/>.
|
You should have received a copy of the GNU General Public License along with FitnessTrax. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::{
|
|
||||||
fs::File,
|
|
||||||
io::{BufRead, BufReader, Read},
|
|
||||||
fmt,
|
|
||||||
str::FromStr,
|
|
||||||
};
|
|
||||||
use chrono::SecondsFormat;
|
use chrono::SecondsFormat;
|
||||||
use chrono_tz::Etc::UTC;
|
use chrono_tz::Etc::UTC;
|
||||||
use emseries::{Record, Series, Timestamp, RecordId};
|
use dimensioned::si;
|
||||||
use ft_core::TraxRecord;
|
use emseries::{Record, RecordId, Series, Timestamp};
|
||||||
use serde::{Deserialize, Serialize, Deserializer, Serializer, de::{self, Visitor}};
|
use ft_core::{self, DurationWorkout, DurationWorkoutActivity, SetRepActivity, TraxRecord};
|
||||||
|
use serde::{
|
||||||
|
de::{self, Visitor},
|
||||||
|
Deserialize, Deserializer, Serialize, Serializer,
|
||||||
|
};
|
||||||
|
use std::{
|
||||||
|
fmt,
|
||||||
|
fs::File,
|
||||||
|
io::{BufRead, BufReader, Read},
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
|
||||||
/// This is a wrapper around date time objects, using timezones from the chroon-tz database and
|
/// This is a wrapper around date time objects, using timezones from the chroon-tz database and
|
||||||
/// providing string representation and parsing of the form "<RFC3339> <Timezone Name>", i.e.,
|
/// providing string representation and parsing of the form "<RFC3339> <Timezone Name>", i.e.,
|
||||||
|
@ -116,11 +118,19 @@ impl<'de> Deserialize<'de> for DateTimeTz {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
struct Steps {
|
struct Steps {
|
||||||
date: DateTimeTz,
|
date: DateTimeTz,
|
||||||
steps: u64,
|
steps: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Steps> for ft_core::Steps {
|
||||||
|
fn from(s: Steps) -> Self {
|
||||||
|
Self {
|
||||||
|
date: s.date.0.naive_utc().date(),
|
||||||
|
count: s.steps,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
@ -129,13 +139,34 @@ struct Weight {
|
||||||
weight: f64,
|
weight: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Weight> for ft_core::Weight {
|
||||||
|
fn from(w: Weight) -> Self {
|
||||||
|
Self {
|
||||||
|
date: w.date.0.naive_utc().date(),
|
||||||
|
weight: w.weight * si::KG,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
enum TDActivity {
|
enum TDActivity {
|
||||||
Cycling,
|
Cycling,
|
||||||
Running,
|
|
||||||
Walking,
|
|
||||||
Swimming,
|
|
||||||
Rowing,
|
Rowing,
|
||||||
|
Running,
|
||||||
|
Swimming,
|
||||||
|
Walking,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<TDActivity> for ft_core::TimeDistanceActivity {
|
||||||
|
fn from(activity: TDActivity) -> Self {
|
||||||
|
match activity {
|
||||||
|
TDActivity::Cycling => ft_core::TimeDistanceActivity::Biking,
|
||||||
|
TDActivity::Rowing => ft_core::TimeDistanceActivity::Rowing,
|
||||||
|
TDActivity::Running => ft_core::TimeDistanceActivity::Running,
|
||||||
|
TDActivity::Swimming => ft_core::TimeDistanceActivity::Swimming,
|
||||||
|
TDActivity::Walking => ft_core::TimeDistanceActivity::Walking,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
@ -147,25 +178,63 @@ struct TimeDistance {
|
||||||
duration: Option<f64>,
|
duration: Option<f64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<TimeDistance> for ft_core::TimeDistance {
|
||||||
|
fn from(td: TimeDistance) -> Self {
|
||||||
|
Self {
|
||||||
|
datetime: td.date.0.fixed_offset(),
|
||||||
|
activity: td.activity.into(),
|
||||||
|
comments: td.comments,
|
||||||
|
distance: td.distance.map(|d| d * si::M),
|
||||||
|
duration: td.duration.map(|d| d * si::S),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
enum SRActivity {
|
enum SRActivity {
|
||||||
Pushups,
|
Pushups,
|
||||||
Situps,
|
Situps,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<SRActivity> for SetRepActivity {
|
||||||
|
fn from(activity: SRActivity) -> Self {
|
||||||
|
match activity {
|
||||||
|
SRActivity::Pushups => Self::Pushups,
|
||||||
|
SRActivity::Situps => Self::Situps,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
struct SetRep {
|
struct SetRep {
|
||||||
date: DateTimeTz,
|
date: DateTimeTz,
|
||||||
activity: SRActivity,
|
activity: SRActivity,
|
||||||
sets: Vec<u64>,
|
sets: Vec<u32>,
|
||||||
comments: Option<String>,
|
comments: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<SetRep> for ft_core::SetRep {
|
||||||
|
fn from(sr: SetRep) -> Self {
|
||||||
|
Self {
|
||||||
|
date: sr.date.0.naive_utc().date(),
|
||||||
|
activity: sr.activity.into(),
|
||||||
|
sets: sr.sets,
|
||||||
|
comments: sr.comments,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
enum RDActivity {
|
enum RDActivity {
|
||||||
MartialArts,
|
MartialArts,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<RDActivity> for DurationWorkoutActivity {
|
||||||
|
fn from(_: RDActivity) -> Self {
|
||||||
|
Self::MartialArts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
struct RepDuration {
|
struct RepDuration {
|
||||||
date: DateTimeTz,
|
date: DateTimeTz,
|
||||||
|
@ -173,13 +242,24 @@ struct RepDuration {
|
||||||
sets: Vec<f64>,
|
sets: Vec<f64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<RepDuration> for DurationWorkout {
|
||||||
|
fn from(rd: RepDuration) -> Self {
|
||||||
|
Self {
|
||||||
|
datetime: rd.date.0.fixed_offset(),
|
||||||
|
activity: rd.activity.into(),
|
||||||
|
duration: rd.sets.into_iter().map(|d| d * si::S).next(),
|
||||||
|
comments: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
enum LegacyData {
|
enum LegacyData {
|
||||||
Steps(Steps),
|
|
||||||
Weight(Weight),
|
|
||||||
TimeDistance(TimeDistance),
|
|
||||||
SetRep(SetRep),
|
|
||||||
RepDuration(RepDuration),
|
RepDuration(RepDuration),
|
||||||
|
SetRep(SetRep),
|
||||||
|
Steps(Steps),
|
||||||
|
TimeDistance(TimeDistance),
|
||||||
|
Weight(Weight),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
@ -188,11 +268,39 @@ struct LegacyRecord {
|
||||||
data: LegacyData,
|
data: LegacyData,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<LegacyRecord> for Record<TraxRecord> {
|
||||||
|
fn from(record: LegacyRecord) -> Self {
|
||||||
|
match record.data {
|
||||||
|
LegacyData::RepDuration(rd) => Record {
|
||||||
|
id: record.id,
|
||||||
|
data: TraxRecord::DurationWorkout(rd.into()),
|
||||||
|
},
|
||||||
|
LegacyData::SetRep(sr) => Record {
|
||||||
|
id: record.id,
|
||||||
|
data: TraxRecord::SetRep(sr.into()),
|
||||||
|
},
|
||||||
|
LegacyData::Steps(s) => Record {
|
||||||
|
id: record.id,
|
||||||
|
data: TraxRecord::Steps(s.into()),
|
||||||
|
},
|
||||||
|
LegacyData::TimeDistance(td) => Record {
|
||||||
|
id: record.id,
|
||||||
|
data: TraxRecord::TimeDistance(td.into()),
|
||||||
|
},
|
||||||
|
LegacyData::Weight(weight) => Record {
|
||||||
|
id: record.id,
|
||||||
|
data: TraxRecord::Weight(weight.into()),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut args = std::env::args();
|
let mut args = std::env::args();
|
||||||
let _ = args.next().unwrap();
|
let _ = args.next().unwrap();
|
||||||
let input_filename = args.next().unwrap();
|
let input_filename = args.next().unwrap();
|
||||||
println!("input filename: {}", input_filename);
|
println!("input filename: {}", input_filename);
|
||||||
|
// let output: Series<ft_core::TraxRecord> = Series::open_file("import.fitnesstrax").unwrap();
|
||||||
|
|
||||||
let input_file = File::open(input_filename).unwrap();
|
let input_file = File::open(input_filename).unwrap();
|
||||||
let mut buf_reader = BufReader::new(input_file);
|
let mut buf_reader = BufReader::new(input_file);
|
||||||
|
@ -211,7 +319,8 @@ fn main() {
|
||||||
Ok(0) => std::process::exit(0),
|
Ok(0) => std::process::exit(0),
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
let record = serde_json::from_str::<LegacyRecord>(&line).unwrap();
|
let record = serde_json::from_str::<LegacyRecord>(&line).unwrap();
|
||||||
println!("[{}] {:?}", count, record);
|
let record: Record<TraxRecord> = record.into();
|
||||||
|
println!("{}", serde_json::to_string(&record).unwrap());
|
||||||
count += 1;
|
count += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,5 +2,7 @@ mod legacy;
|
||||||
|
|
||||||
mod types;
|
mod types;
|
||||||
pub use types::{
|
pub use types::{
|
||||||
Steps, TimeDistance, TimeDistanceActivity, TraxRecord, Weight, TIME_DISTANCE_ACTIVITIES,
|
SetRep, SetRepActivity, Steps, TimeDistance, TimeDistanceActivity, TraxRecord, Weight,
|
||||||
|
DurationWorkoutActivity, DurationWorkout,
|
||||||
|
SET_REP_ACTIVITIES, TIME_DISTANCE_ACTIVITIES,
|
||||||
};
|
};
|
||||||
|
|
|
@ -19,17 +19,38 @@ use dimensioned::si;
|
||||||
use emseries::{Recordable, Timestamp};
|
use emseries::{Recordable, Timestamp};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub enum SetRepActivity {
|
||||||
|
Pushups,
|
||||||
|
Situps,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const SET_REP_ACTIVITIES: [SetRepActivity; 2] =
|
||||||
|
[SetRepActivity::Pushups, SetRepActivity::Situps];
|
||||||
|
|
||||||
/// SetRep represents workouts like pushups or situps, which involve doing a "set" of a number of
|
/// SetRep represents workouts like pushups or situps, which involve doing a "set" of a number of
|
||||||
/// actions, resting, and then doing another set.
|
/// actions, resting, and then doing another set.
|
||||||
#[allow(dead_code)]
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct SetRep {
|
pub struct SetRep {
|
||||||
/// I assume that a set/rep workout is only done once in a day.
|
/// I assume that a set/rep workout is only done once in a day.
|
||||||
date: NaiveDate,
|
pub date: NaiveDate,
|
||||||
|
/// The activity involved
|
||||||
|
pub activity: SetRepActivity,
|
||||||
/// Each set entry represents the number of times that the action was performed in a set. So, a
|
/// Each set entry represents the number of times that the action was performed in a set. So, a
|
||||||
/// pushup workout that involved five sets would have five entries. Each entry would be x
|
/// pushup workout that involved five sets would have five entries. Each entry would be x
|
||||||
/// number of pushups. A viable workout would be something like [6, 6, 4, 4, 5].
|
/// number of pushups. A viable workout would be something like [6, 6, 4, 4, 5].
|
||||||
sets: Vec<u32>,
|
pub sets: Vec<u32>,
|
||||||
comments: Option<String>,
|
pub comments: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Recordable for SetRep {
|
||||||
|
fn timestamp(&self) -> Timestamp {
|
||||||
|
Timestamp::Date(self.date)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tags(&self) -> Vec<String> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The number of steps one takes in a single day.
|
/// The number of steps one takes in a single day.
|
||||||
|
@ -120,11 +141,45 @@ impl Recordable for Weight {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub enum DurationWorkoutActivity {
|
||||||
|
MartialArts,
|
||||||
|
Yoga,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const DURATION_WORKOUT_ACTIVITIES: [DurationWorkoutActivity; 2] = [
|
||||||
|
DurationWorkoutActivity::MartialArts,
|
||||||
|
DurationWorkoutActivity::Yoga,
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Generic workouts for which only duration really matters. This is for things
|
||||||
|
/// such as Martial Arts or Yoga, which are activities done for an amount of
|
||||||
|
/// time, but with no other details.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct DurationWorkout {
|
||||||
|
pub datetime: DateTime<FixedOffset>,
|
||||||
|
pub activity: DurationWorkoutActivity,
|
||||||
|
pub duration: Option<si::Second<f64>>,
|
||||||
|
pub comments: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Recordable for DurationWorkout {
|
||||||
|
fn timestamp(&self) -> Timestamp {
|
||||||
|
Timestamp::DateTime(self.datetime)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tags(&self) -> Vec<String> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The unified data structure for all records that are part of the app.
|
/// The unified data structure for all records that are part of the app.
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
pub enum TraxRecord {
|
pub enum TraxRecord {
|
||||||
TimeDistance(TimeDistance),
|
DurationWorkout(DurationWorkout),
|
||||||
|
SetRep(SetRep),
|
||||||
Steps(Steps),
|
Steps(Steps),
|
||||||
|
TimeDistance(TimeDistance),
|
||||||
Weight(Weight),
|
Weight(Weight),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,8 +219,10 @@ impl Recordable for TraxRecord {
|
||||||
fn timestamp(&self) -> Timestamp {
|
fn timestamp(&self) -> Timestamp {
|
||||||
match self {
|
match self {
|
||||||
TraxRecord::TimeDistance(rec) => Timestamp::DateTime(rec.datetime),
|
TraxRecord::TimeDistance(rec) => Timestamp::DateTime(rec.datetime),
|
||||||
|
TraxRecord::SetRep(rec) => rec.timestamp(),
|
||||||
TraxRecord::Steps(rec) => rec.timestamp(),
|
TraxRecord::Steps(rec) => rec.timestamp(),
|
||||||
TraxRecord::Weight(rec) => rec.timestamp(),
|
TraxRecord::Weight(rec) => rec.timestamp(),
|
||||||
|
TraxRecord::DurationWorkout(rec) => rec.timestamp(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue