Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions fit_file_faker/app_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,42 @@ def validate_path(self, path: Path) -> bool:
return path.exists() and path.is_dir()


class OnelapDetector(AppDetector):
"""Onelap (顽鹿运动) directory detector."""

def get_display_name(self) -> str:
"""Get human-readable app name."""
return "Onelap (顽鹿运动)"

def get_short_name(self) -> str:
"""Get short app name for compact display."""
return "Onelap"

def get_default_path(self) -> Path | None:
"""Detect Onelap FIT files directory.

Onelap stores FIT files in specific locations:
- macOS: ~/Documents/Onelap/Activity/
- Windows: ~/Documents/Onelap/Activity/

Note: The actual path might vary slightly depending on version.
"""
base = Path.home() / "Documents" / "Onelap" / "Activity"
if base.exists():
return base

# Fallback for older versions or different locales
alternate = Path.home() / "Documents" / "顽鹿运动" / "Activity"
if alternate.exists():
return alternate

return None

def validate_path(self, path: Path) -> bool:
"""Check if path looks like Onelap directory."""
return path.exists() and path.is_dir()


class CustomDetector(AppDetector):
"""Custom/manual path specification detector."""

Expand Down Expand Up @@ -301,6 +337,7 @@ def validate_path(self, path: Path) -> bool:
AppType.TP_VIRTUAL: TPVDetector,
AppType.ZWIFT: ZwiftDetector,
AppType.MYWHOOSH: MyWhooshDetector,
AppType.ONELAP: OnelapDetector,
AppType.CUSTOM: CustomDetector,
}

Expand Down
2 changes: 2 additions & 0 deletions fit_file_faker/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,7 @@ class AppType(Enum):
TP_VIRTUAL = "tp_virtual"
ZWIFT = "zwift"
MYWHOOSH = "mywhoosh"
ONELAP = "onelap"
CUSTOM = "custom"


Expand Down Expand Up @@ -1673,6 +1674,7 @@ def create_profile_wizard(self) -> Profile | None:
questionary.Choice("TrainingPeaks Virtual", AppType.TP_VIRTUAL),
questionary.Choice("Zwift", AppType.ZWIFT),
questionary.Choice("MyWhoosh", AppType.MYWHOOSH),
questionary.Choice("Onelap (顽鹿运动)", AppType.ONELAP),
Comment thread
jat255 marked this conversation as resolved.
Outdated
questionary.Choice("Custom (manual path)", AppType.CUSTOM),
]

Expand Down
22 changes: 13 additions & 9 deletions fit_file_faker/fit_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,7 @@ def rewrite_file_id_message(
Note:
The product_name field is intentionally not copied as Garmin devices
typically don't set this field. Only files from supported manufacturers
(`DEVELOPMENT`, `ZWIFT`, `WAHOO_FITNESS`, `PEAKSWARE`, `HAMMERHEAD`, `COROS`,
`MYWHOOSH`) are modified; others are returned unchanged.
`MYWHOOSH` (`331`), and `ONELAP` (`307`).
Comment thread
jat255 marked this conversation as resolved.
Outdated
"""
dt = datetime.fromtimestamp(m.time_created / 1000.0) # type: ignore
_logger.info(f'Activity timestamp is "{dt.isoformat()}"')
Expand Down Expand Up @@ -260,10 +259,8 @@ def _should_modify_manufacturer(self, manufacturer: int | None) -> bool:
True if the manufacturer is from a supported platform and should
be modified, False otherwise.

Note:
Supported manufacturers include: `DEVELOPMENT` (TrainingPeaks Virtual),
`ZWIFT`, `WAHOO_FITNESS`, `PEAKSWARE`, `HAMMERHEAD`, `COROS`, and
`MYWHOOSH` (`331`).
`ZWIFT`, `WAHOO_FITNESS`, `PEAKSWARE`, `HAMMERHEAD`, `COROS`, `MYWHOOSH` (`331`),
and `ONELAP` (`307`).
Comment thread
jat255 marked this conversation as resolved.
"""
if manufacturer is None:
return False
Expand All @@ -275,6 +272,7 @@ def _should_modify_manufacturer(self, manufacturer: int | None) -> bool:
Manufacturer.HAMMERHEAD.value,
Manufacturer.COROS.value,
331, # MYWHOOSH is unknown to fit_tools
307, # ONELAP
Comment thread
jat255 marked this conversation as resolved.
Outdated
]

def _should_modify_device_info(self, manufacturer: int | None) -> bool:
Expand Down Expand Up @@ -306,6 +304,7 @@ def _should_modify_device_info(self, manufacturer: int | None) -> bool:
Manufacturer.HAMMERHEAD.value,
Manufacturer.COROS.value,
331, # MYWHOOSH is unknown to fit_tools
307, # ONELAP
]

def strip_unknown_fields(self, fit_file: FitFile) -> None:
Expand Down Expand Up @@ -492,6 +491,11 @@ def edit_fit(
# Skip any existing file creator message
continue

# Software message (35) - skip to remove original software info
Comment thread
jat255 marked this conversation as resolved.
Outdated
if message.global_id == 35:
_logger.debug(f"Skipping Software message at record {i}")
continue

# Change device info messages
if message.global_id == DeviceInfoMessage.ID:
if isinstance(message, DeviceInfoMessage):
Expand Down Expand Up @@ -522,11 +526,11 @@ def edit_fit(
target_device = GarminProduct.EDGE_830.value

# have not seen this set explicitly in testing, but probable good to set regardless
if message.garmin_product: # pragma: no cover
if message.garmin_product is not None: # pragma: no cover
message.garmin_product = target_device
if message.product:
if message.product is not None:
message.product = target_device # type: ignore
if message.manufacturer:
if message.manufacturer is not None:
message.manufacturer = target_manufacturer
message.product_name = ""
self.print_message(f" New Record: {i}", message)
Expand Down