using Microsoft.Extensions.Logging; using LocationTrackerApp.Models; using LocationTrackerApp.Data; namespace LocationTrackerApp.Services; /// /// Service for tracking user location and storing data in SQLite database /// public class LocationService : ILocationService, IDisposable { private readonly LocationDbContext _dbContext; private readonly ILogger _logger; private CancellationTokenSource? _cancellationTokenSource; private bool _isTracking; private string? _currentSessionId; private LocationData? _lastKnownLocation; /// /// Event fired when a new location is received /// public event EventHandler? LocationChanged; /// /// Event fired when location tracking status changes /// public event EventHandler? TrackingStatusChanged; /// /// Gets the current location tracking status /// public bool IsTracking => _isTracking; /// /// Gets the current session ID /// public string? CurrentSessionId => _currentSessionId; /// /// Initializes a new instance of LocationService /// /// Database context for storing location data /// Logger for recording service events public LocationService(LocationDbContext dbContext, ILogger logger) { _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } /// /// Starts location tracking /// /// Optional session ID for grouping location data /// Task representing the async operation public async Task StartTrackingAsync(string? sessionId = null) { try { if (_isTracking) { _logger.LogWarning("Location tracking is already active"); return; } // Check permissions first if (!await HasLocationPermissionsAsync()) { var granted = await RequestLocationPermissionsAsync(); if (!granted) { _logger.LogError("Location permissions not granted"); throw new UnauthorizedAccessException("Location permissions are required for tracking"); } } _currentSessionId = sessionId ?? Guid.NewGuid().ToString(); _cancellationTokenSource = new CancellationTokenSource(); _logger.LogInformation("Starting location tracking with session ID: {SessionId}", _currentSessionId); // Start location tracking var request = new GeolocationRequest { DesiredAccuracy = GeolocationAccuracy.Best, Timeout = TimeSpan.FromSeconds(10) }; // Start continuous location updates _ = Task.Run(async () => await TrackLocationContinuouslyAsync(_cancellationTokenSource.Token)); _isTracking = true; OnTrackingStatusChanged(true); _logger.LogInformation("Location tracking started successfully"); } catch (Exception ex) { _logger.LogError(ex, "Failed to start location tracking"); throw; } } /// /// Stops location tracking /// /// Task representing the async operation public async Task StopTrackingAsync() { try { if (!_isTracking) { _logger.LogWarning("Location tracking is not active"); return; } _logger.LogInformation("Stopping location tracking"); _cancellationTokenSource?.Cancel(); _cancellationTokenSource?.Dispose(); _cancellationTokenSource = null; _isTracking = false; _currentSessionId = null; OnTrackingStatusChanged(false); _logger.LogInformation("Location tracking stopped successfully"); } catch (Exception ex) { _logger.LogError(ex, "Failed to stop location tracking"); throw; } } /// /// Gets the current location /// /// Current location data or null if not available public async Task GetCurrentLocationAsync() { try { if (!await HasLocationPermissionsAsync()) { _logger.LogWarning("Location permissions not available"); return null; } var request = new GeolocationRequest { DesiredAccuracy = GeolocationAccuracy.Best, Timeout = TimeSpan.FromSeconds(10) }; var location = await Geolocation.GetLocationAsync(request); if (location == null) { _logger.LogWarning("Unable to get current location"); return null; } var locationData = new LocationData { Latitude = location.Latitude, Longitude = location.Longitude, Accuracy = location.Accuracy ?? 0, Altitude = location.Altitude, Speed = location.Speed, Timestamp = DateTime.UtcNow, SessionId = _currentSessionId }; _lastKnownLocation = locationData; return locationData; } catch (Exception ex) { _logger.LogError(ex, "Failed to get current location"); return null; } } /// /// Requests location permissions from the user /// /// True if permissions are granted, false otherwise public async Task RequestLocationPermissionsAsync() { try { var status = await Permissions.RequestAsync(); var granted = status == PermissionStatus.Granted; _logger.LogInformation("Location permission request result: {Status}", status); return granted; } catch (Exception ex) { _logger.LogError(ex, "Failed to request location permissions"); return false; } } /// /// Checks if location permissions are granted /// /// True if permissions are granted, false otherwise public async Task HasLocationPermissionsAsync() { try { var status = await Permissions.CheckStatusAsync(); return status == PermissionStatus.Granted; } catch (Exception ex) { _logger.LogError(ex, "Failed to check location permissions"); return false; } } /// /// Continuously tracks location updates /// /// Cancellation token for stopping the tracking private async Task TrackLocationContinuouslyAsync(CancellationToken cancellationToken) { try { while (!cancellationToken.IsCancellationRequested && _isTracking) { var locationData = await GetCurrentLocationAsync(); if (locationData != null) { // Save to database await SaveLocationDataAsync(locationData); // Notify subscribers OnLocationChanged(locationData); } // Wait before next update (adjust interval as needed) await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken); } } catch (OperationCanceledException) { _logger.LogInformation("Location tracking cancelled"); } catch (Exception ex) { _logger.LogError(ex, "Error in continuous location tracking"); } } /// /// Saves location data to the database /// /// Location data to save private async Task SaveLocationDataAsync(LocationData locationData) { try { _dbContext.LocationData.Add(locationData); await _dbContext.SaveChangesAsync(); _logger.LogDebug("Location data saved: Lat={Latitude}, Lng={Longitude}, Time={Timestamp}", locationData.Latitude, locationData.Longitude, locationData.Timestamp); } catch (Exception ex) { _logger.LogError(ex, "Failed to save location data to database"); } } /// /// Raises the LocationChanged event /// /// The location data that changed protected virtual void OnLocationChanged(LocationData locationData) { LocationChanged?.Invoke(this, locationData); } /// /// Raises the TrackingStatusChanged event /// /// Current tracking status protected virtual void OnTrackingStatusChanged(bool isTracking) { TrackingStatusChanged?.Invoke(this, isTracking); } /// /// Disposes the service and stops tracking /// public void Dispose() { try { if (_isTracking) { StopTrackingAsync().Wait(); } } catch (Exception ex) { _logger.LogError(ex, "Error disposing LocationService"); } finally { _cancellationTokenSource?.Dispose(); } } }