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();
}
}
}