service cleanups, containing logic directly now

This commit is contained in:
choco
2025-10-20 14:00:54 +02:00
parent cac94374d9
commit 0cb71e5444
10 changed files with 1274 additions and 88 deletions

1086
CONTRIBUTING.md Normal file

File diff suppressed because it is too large Load Diff

111
DEVELOPMENT.md Normal file
View File

@@ -0,0 +1,111 @@
# Development Setup for macOS
This document explains how to set up the Lightless Sync development environment on macOS.
## Problem: "Cannot resolve symbol 'Dalamud'"
When developing Dalamud plugins on macOS, you may encounter the error:
```
Cannot resolve symbol 'Dalamud'
Dalamud.NET.Sdk: Dalamud installation not found at /Users/.../Library/Application Support/XIV on Mac/dalamud/Hooks/dev/
```
This happens because the Dalamud.NET.Sdk expects to find Dalamud assemblies at a specific path, but they don't exist if you don't have XIV on Mac or Dalamud installed.
## Solution
### Automated Setup (Recommended)
Run the setup script to download the required Dalamud assemblies:
```bash
./setup-dalamud.sh
```
This script will:
1. Create a development directory at `~/.dalamud/dev`
2. Download the latest Dalamud assemblies from the official distribution
3. Extract them to the development directory
### Manual Setup
If you prefer to set up manually:
1. **Create the Dalamud directory:**
```bash
mkdir -p ~/.dalamud/dev
```
2. **Download Dalamud assemblies:**
```bash
curl -L -o /tmp/dalamud.zip https://goatcorp.github.io/dalamud-distrib/latest.zip
unzip /tmp/dalamud.zip -d ~/.dalamud/dev
```
3. **Set the DALAMUD_HOME environment variable (optional):**
```bash
export DALAMUD_HOME="$HOME/.dalamud/dev"
```
## How It Works
The project includes a `Directory.Build.props` file that automatically configures the `DALAMUD_HOME` path to use `~/.dalamud/dev` if it exists. This overrides the default XIV on Mac path.
The Dalamud.NET.Sdk will then use this path to find the required assemblies for compilation and IntelliSense.
## Building the Project
After setup, you can build the project normally:
```bash
dotnet restore
dotnet build
```
## IDE Configuration
### JetBrains Rider / IntelliJ IDEA
After running the setup script, you may need to:
1. Invalidate caches and restart: **File → Invalidate Caches → Invalidate and Restart**
2. Reload the solution: **Right-click on solution → Reload All Projects**
The IDE should now resolve all Dalamud symbols correctly.
## Troubleshooting
### Build still fails with "Dalamud installation not found"
1. Verify the assemblies were downloaded:
```bash
ls -la ~/.dalamud/dev/Dalamud.dll
```
2. Check that `Directory.Build.props` exists in the project root
3. Try cleaning and rebuilding:
```bash
dotnet clean
dotnet build
```
### IDE still shows "Cannot resolve symbol 'Dalamud'"
1. Ensure the build succeeds first (run `dotnet build`)
2. Restart your IDE
3. Try invalidating caches (Rider/IntelliJ)
4. Check that the project references are loaded correctly
## Files Modified
- `Directory.Build.props` - Configures DALAMUD_HOME path
- `LightlessSync/LightlessSync.csproj` - Removed duplicate DalamudPackager reference
- `PenumbraAPI/Penumbra.Api.csproj` - Added DalamudLibPath configuration
- `setup-dalamud.sh` - Setup script to download Dalamud assemblies
## Additional Notes
- The Dalamud assemblies are only needed for development/compilation
- You don't need a running FFXIV or XIV on Mac installation to develop plugins
- The assemblies are downloaded from the official Dalamud distribution
- Updates to Dalamud may require re-running the setup script

View File

@@ -269,8 +269,7 @@ public sealed class Plugin : IDalamudPlugin
s.GetRequiredService<WindowSystem>(), s.GetServices<WindowMediatorSubscriberBase>(),
s.GetRequiredService<UiFactory>(),
s.GetRequiredService<FileDialogManager>(),
s.GetRequiredService<LightlessMediator>(),
s.GetRequiredService<NotificationService>()));
s.GetRequiredService<LightlessMediator>()));
collection.AddScoped((s) => new CommandManagerService(commandManager, s.GetRequiredService<PerformanceCollectorService>(),
s.GetRequiredService<ServerConfigurationManager>(), s.GetRequiredService<CacheMonitor>(), s.GetRequiredService<ApiController>(),
s.GetRequiredService<LightlessMediator>(), s.GetRequiredService<LightlessConfigService>()));

View File

@@ -108,7 +108,9 @@ public record OpenCharaDataHubWithFilterMessage(UserData UserData) : MessageBase
public record EnableBroadcastMessage(string HashedCid, bool Enabled) : MessageBase;
public record BroadcastStatusChangedMessage(bool Enabled, TimeSpan? Ttl) : MessageBase;
public record SyncshellBroadcastsUpdatedMessage : MessageBase;
public record PairRequestReceivedMessage(string HashedCid, string Message) : MessageBase;
public record PairRequestsUpdatedMessage : MessageBase;
public record PairDownloadStatusMessage(List<(string PlayerName, float Progress, string Status)> DownloadStatus, int QueueWaiting) : MessageBase;
public record VisibilityChange : MessageBase;
#pragma warning restore S2094
#pragma warning restore MA0048 // File name must match type name

View File

@@ -45,7 +45,9 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
public Task StartAsync(CancellationToken cancellationToken)
{
Mediator.Subscribe<NotificationMessage>(this, HandleNotificationMessage);
Mediator.Subscribe<PairRequestReceivedMessage>(this, HandlePairRequestReceived);
Mediator.Subscribe<PairRequestsUpdatedMessage>(this, HandlePairRequestsUpdated);
Mediator.Subscribe<PairDownloadStatusMessage>(this, HandlePairDownloadStatus);
Mediator.Subscribe<PerformanceNotificationMessage>(this, HandlePerformanceNotification);
return Task.CompletedTask;
}
@@ -293,33 +295,8 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
return actions;
}
public void ShowPairDownloadNotification(List<(string playerName, float progress, string status)> downloadStatus,
int queueWaiting = 0)
{
var userDownloads = downloadStatus.Where(x => x.playerName != "Pair Queue").ToList();
var totalProgress = userDownloads.Count > 0 ? userDownloads.Average(x => x.progress) : 0f;
var message = BuildPairDownloadMessage(userDownloads, queueWaiting);
var notification = new LightlessNotification
{
Id = "pair_download_progress",
Title = "Downloading Pair Data",
Message = message,
Type = NotificationType.Download,
Duration = TimeSpan.FromSeconds(_configService.Current.DownloadNotificationDurationSeconds),
ShowProgress = true,
Progress = totalProgress
};
Mediator.Publish(new LightlessNotificationMessage(notification));
if (AreAllDownloadsCompleted(userDownloads))
{
DismissPairDownloadNotification();
}
}
private string BuildPairDownloadMessage(List<(string playerName, float progress, string status)> userDownloads,
private string BuildPairDownloadMessage(List<(string PlayerName, float Progress, string Status)> userDownloads,
int queueWaiting)
{
var messageParts = new List<string>();
@@ -331,7 +308,7 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
if (userDownloads.Count > 0)
{
var completedCount = userDownloads.Count(x => x.progress >= 1.0f);
var completedCount = userDownloads.Count(x => x.Progress >= 1.0f);
messageParts.Add($"Progress: {completedCount}/{userDownloads.Count} completed");
}
@@ -344,29 +321,29 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
return string.Join("\n", messageParts);
}
private string BuildActiveDownloadLines(List<(string playerName, float progress, string status)> userDownloads)
private string BuildActiveDownloadLines(List<(string PlayerName, float Progress, string Status)> userDownloads)
{
var activeDownloads = userDownloads
.Where(x => x.progress < 1.0f)
.Where(x => x.Progress < 1.0f)
.Take(_configService.Current.MaxConcurrentPairApplications);
if (!activeDownloads.Any()) return string.Empty;
return string.Join("\n", activeDownloads.Select(x => $"• {x.playerName}: {FormatDownloadStatus(x)}"));
return string.Join("\n", activeDownloads.Select(x => $"• {x.PlayerName}: {FormatDownloadStatus(x)}"));
}
private string FormatDownloadStatus((string playerName, float progress, string status) download) =>
download.status switch
private string FormatDownloadStatus((string PlayerName, float Progress, string Status) download) =>
download.Status switch
{
"downloading" => $"{download.progress:P0}",
"downloading" => $"{download.Progress:P0}",
"decompressing" => "decompressing",
"queued" => "queued",
"waiting" => "waiting for slot",
_ => download.status
_ => download.Status
};
private bool AreAllDownloadsCompleted(List<(string playerName, float progress, string status)> userDownloads) =>
userDownloads.Any() && userDownloads.All(x => x.progress >= 1.0f);
private bool AreAllDownloadsCompleted(List<(string PlayerName, float Progress, string Status)> userDownloads) =>
userDownloads.Any() && userDownloads.All(x => x.Progress >= 1.0f);
public void DismissPairDownloadNotification() =>
Mediator.Publish(new LightlessNotificationDismissMessage("pair_download_progress"));
@@ -581,12 +558,25 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
_chatGui.Print(se.BuiltString);
}
private void HandlePairRequestReceived(PairRequestReceivedMessage msg)
{
var request = _pairRequestService.RegisterIncomingRequest(msg.HashedCid, msg.Message);
var senderName = string.IsNullOrEmpty(request.DisplayName) ? "Unknown User" : request.DisplayName;
_shownPairRequestNotifications.Add(request.HashedCid);
ShowPairRequestNotification(
senderName,
request.HashedCid,
onAccept: () => _pairRequestService.AcceptPairRequest(request.HashedCid, senderName),
onDecline: () => _pairRequestService.DeclinePairRequest(request.HashedCid));
}
private void HandlePairRequestsUpdated(PairRequestsUpdatedMessage _)
{
var activeRequests = _pairRequestService.GetActiveRequests();
var activeRequestIds = activeRequests.Select(r => r.HashedCid).ToHashSet();
// Dismiss notifications for requests that are no longer active
// Dismiss notifications for requests that are no longer active (expired)
var notificationsToRemove = _shownPairRequestNotifications
.Where(hashedCid => !activeRequestIds.Contains(hashedCid))
.ToList();
@@ -597,17 +587,30 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
Mediator.Publish(new LightlessNotificationDismissMessage(notificationId));
_shownPairRequestNotifications.Remove(hashedCid);
}
}
// Show/update notifications for all active requests
foreach (var request in activeRequests)
private void HandlePairDownloadStatus(PairDownloadStatusMessage msg)
{
var userDownloads = msg.DownloadStatus.Where(x => x.PlayerName != "Pair Queue").ToList();
var totalProgress = userDownloads.Count > 0 ? userDownloads.Average(x => x.Progress) : 0f;
var message = BuildPairDownloadMessage(userDownloads, msg.QueueWaiting);
var notification = new LightlessNotification
{
_shownPairRequestNotifications.Add(request.HashedCid);
ShowPairRequestNotification(
request.DisplayName,
request.HashedCid,
() => _pairRequestService.AcceptPairRequest(request.HashedCid, request.DisplayName),
() => _pairRequestService.DeclinePairRequest(request.HashedCid)
);
Id = "pair_download_progress",
Title = "Downloading Pair Data",
Message = message,
Type = NotificationType.Download,
Duration = TimeSpan.FromSeconds(_configService.Current.DownloadNotificationDurationSeconds),
ShowProgress = true,
Progress = totalProgress
};
Mediator.Publish(new LightlessNotificationMessage(notification));
if (AreAllDownloadsCompleted(userDownloads))
{
Mediator.Publish(new LightlessNotificationDismissMessage("pair_download_progress"));
}
}

View File

@@ -19,7 +19,12 @@ public sealed class PairRequestService : DisposableMediatorSubscriberBase
private static readonly TimeSpan Expiration = TimeSpan.FromMinutes(5);
public PairRequestService(ILogger<PairRequestService> logger, LightlessMediator mediator, DalamudUtilService dalamudUtil, PairManager pairManager, Lazy<WebAPI.ApiController> apiController)
public PairRequestService(
ILogger<PairRequestService> logger,
LightlessMediator mediator,
DalamudUtilService dalamudUtil,
PairManager pairManager,
Lazy<WebAPI.ApiController> apiController)
: base(logger, mediator)
{
_dalamudUtil = dalamudUtil;

View File

@@ -23,8 +23,7 @@ public sealed class UiService : DisposableMediatorSubscriberBase
LightlessConfigService lightlessConfigService, WindowSystem windowSystem,
IEnumerable<WindowMediatorSubscriberBase> windows,
UiFactory uiFactory, FileDialogManager fileDialogManager,
LightlessMediator lightlessMediator,
NotificationService notificationService) : base(logger, lightlessMediator)
LightlessMediator lightlessMediator) : base(logger, lightlessMediator)
{
_logger = logger;
_logger.LogTrace("Creating {type}", GetType().Name);

View File

@@ -22,13 +22,12 @@ public class DownloadUi : WindowMediatorSubscriberBase
private readonly UiSharedService _uiShared;
private readonly PairProcessingLimiter _pairProcessingLimiter;
private readonly ConcurrentDictionary<GameObjectHandler, bool> _uploadingPlayers = new();
private readonly NotificationService _notificationService;
private bool _notificationDismissed = true;
private int _lastDownloadStateHash = 0;
public DownloadUi(ILogger<DownloadUi> logger, DalamudUtilService dalamudUtilService, LightlessConfigService configService,
PairProcessingLimiter pairProcessingLimiter, FileUploadManager fileTransferManager, LightlessMediator mediator, UiSharedService uiShared,
PerformanceCollectorService performanceCollectorService, NotificationService notificationService)
PerformanceCollectorService performanceCollectorService)
: base(logger, mediator, "Lightless Sync Downloads", performanceCollectorService)
{
_dalamudUtilService = dalamudUtilService;
@@ -36,7 +35,6 @@ public class DownloadUi : WindowMediatorSubscriberBase
_pairProcessingLimiter = pairProcessingLimiter;
_fileTransferManager = fileTransferManager;
_uiShared = uiShared;
_notificationService = notificationService;
SizeConstraints = new WindowSizeConstraints()
{
@@ -359,7 +357,7 @@ public class DownloadUi : WindowMediatorSubscriberBase
_lastDownloadStateHash = currentHash;
if (downloadStatus.Count > 0 || queueWaiting > 0)
{
_notificationService.ShowPairDownloadNotification(downloadStatus, queueWaiting);
Mediator.Publish(new PairDownloadStatusMessage(downloadStatus, queueWaiting));
}
}
}

View File

@@ -63,7 +63,6 @@ public class SettingsUi : WindowMediatorSubscriberBase
private readonly IProgress<(int, int, FileCacheEntity)> _validationProgress;
private readonly NameplateService _nameplateService;
private readonly NameplateHandler _nameplateHandler;
private readonly NotificationService _lightlessNotificationService;
private (int, int, FileCacheEntity) _currentProgress;
private bool _deleteAccountPopupModalShown = false;
private bool _deleteFilesPopupModalShown = false;
@@ -107,8 +106,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
IpcManager ipcManager, CacheMonitor cacheMonitor,
DalamudUtilService dalamudUtilService, HttpClient httpClient,
NameplateService nameplateService,
NameplateHandler nameplateHandler,
NotificationService lightlessNotificationService) : base(logger, mediator, "Lightless Sync Settings",
NameplateHandler nameplateHandler) : base(logger, mediator, "Lightless Sync Settings",
performanceCollector)
{
_configService = configService;
@@ -130,7 +128,6 @@ public class SettingsUi : WindowMediatorSubscriberBase
_uiShared = uiShared;
_nameplateService = nameplateService;
_nameplateHandler = nameplateHandler;
_lightlessNotificationService = lightlessNotificationService;
AllowClickthrough = false;
AllowPinning = true;
_validationProgress = new Progress<(int, int, FileCacheEntity)>(v => _currentProgress = v);
@@ -3616,20 +3613,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
{
if (ImGui.Button($"{FontAwesomeIcon.Play.ToIconString()}##test_pair", new Vector2(availableWidth, 0)))
{
_lightlessNotificationService.ShowPairRequestNotification(
"Test User",
"test-uid-123",
() =>
{
Mediator.Publish(new NotificationMessage("Accepted", "You accepted the test pair request.",
NotificationType.Info));
},
() =>
{
Mediator.Publish(new NotificationMessage("Declined", "You declined the test pair request.",
NotificationType.Info));
}
);
Mediator.Publish(new PairRequestReceivedMessage("test-uid-123", "Test User wants to pair with you."));
}
}
UiSharedService.AttachToolTip("Test pair request notification");
@@ -3652,15 +3636,14 @@ public class SettingsUi : WindowMediatorSubscriberBase
{
if (ImGui.Button($"{FontAwesomeIcon.Play.ToIconString()}##test_download", new Vector2(availableWidth, 0)))
{
_lightlessNotificationService.ShowPairDownloadNotification(
new List<(string playerName, float progress, string status)>
{
Mediator.Publish(new PairDownloadStatusMessage(
[
("Player One", 0.35f, "downloading"),
("Player Two", 0.75f, "downloading"),
("Player Three", 1.0f, "downloading")
},
queueWaiting: 2
);
],
2
));
}
}
UiSharedService.AttachToolTip("Test download progress notification");

View File

@@ -107,17 +107,17 @@ public partial class ApiController
}
public Task Client_ReceiveBroadcastPairRequest(UserPairNotificationDto dto)
{
if (dto == null)
Logger.LogDebug("Client_ReceiveBroadcastPairRequest: {dto}", dto);
if (dto is null)
{
return Task.CompletedTask;
}
var request = _pairRequestService.RegisterIncomingRequest(dto.myHashedCid, dto.message ?? string.Empty);
var senderName = string.IsNullOrEmpty(request.DisplayName) ? "Unknown User" : request.DisplayName;
_lightlessNotificationService.ShowPairRequestNotification(
senderName,
request.HashedCid,
onAccept: () => _pairRequestService.AcceptPairRequest(request.HashedCid, senderName),
onDecline: () => _pairRequestService.DeclinePairRequest(request.HashedCid));
ExecuteSafely(() =>
{
Mediator.Publish(new PairRequestReceivedMessage(dto.myHashedCid, dto.message ?? string.Empty));
});
return Task.CompletedTask;
}