public static MemoryMappedBasedRetrievedSecrets ReadExposedSecrets(OperationContext context, string?fileName = null, int pollingIntervalInSeconds = 10)
        {
            string memoryMappedFileName = fileName ?? SecretsFileName;

            var secrets = context.PerformOperation(
                Tracer,
                () =>
            {
                var content = MemoryMappedFileHelper.ReadContent(memoryMappedFileName);
                return(RetrievedSecretsSerializer.Deserialize(content));
            },
                extraStartMessage: $"Obtaining secrets from '{memoryMappedFileName}'",
                messageFactory: _ => $"Obtained secrets from '{memoryMappedFileName}'").ThrowIfFailure();

            TimeSpan pollingInterval = TimeSpan.FromSeconds(pollingIntervalInSeconds);
            Timer?   timer           = null;

            var result = new MemoryMappedBasedRetrievedSecrets(secrets.Secrets, () => timer?.Dispose(), memoryMappedFileName);

            timer = new Timer(
                _ =>
            {
                result.RefreshSecrets(context);

                try
                {
                    timer?.Change(pollingInterval, Timeout.InfiniteTimeSpan);
                } catch (ObjectDisposedException) { }
            },
                state: null,
                dueTime: pollingInterval,
                period: Timeout.InfiniteTimeSpan);

            return(result);
        }
            public void RefreshSecrets(OperationContext context)
            {
                var newSecrets = context.PerformOperation(
                    Tracer,
                    () =>
                {
                    var content = MemoryMappedFileHelper.ReadContent(MemoryMappedFileName);
                    return(RetrievedSecretsSerializer.Deserialize(content));
                },
                    extraStartMessage: $"Refreshing secrets from '{MemoryMappedFileName}'",
                    messageFactory: _ => $"Refreshing secrets from '{MemoryMappedFileName}'");

                // Now we need to update the secrets originally read from file.
                if (newSecrets.Succeeded)
                {
                    context.PerformOperation(
                        Tracer,
                        () =>
                    {
                        UpdateSecrets(this, newSecrets.Value);
                        return(BoolResult.Success);
                    }).IgnoreFailure();     // the error was already traced.
                }
            }
        public static IDisposable Expose(OperationContext context, RetrievedSecrets secrets, string?fileName = null)
        {
            string memoryMappedFileName = fileName ?? SecretsFileName;

            var memoryMappedFile = context.PerformOperation(
                Tracer,
                () =>
            {
                var serializedSecrets = RetrievedSecretsSerializer.Serialize(secrets);
                return(Result.Success(MemoryMappedFileHelper.CreateMemoryMappedFileWithContent(memoryMappedFileName, serializedSecrets)));
            },
                extraStartMessage: $"Exposing secrets to '{memoryMappedFileName}'",
                messageFactory: _ => $"Exposed secrets to '{memoryMappedFileName}'"
                ).ThrowIfFailure();

            var disposeActions = new List <Action>();

            disposeActions.Add(
                () =>
            {
                Tracer.Debug(context, $"Closing memory mapped file '{memoryMappedFileName}'");
                memoryMappedFile.Dispose();
            });

            disposeActions.AddRange(trackSecretsLifetime());

            return(new InterProcessSecretsCommunicator(disposeActions));

            List <Action> trackSecretsLifetime()
            {
                var actions = new List <Action>();

                foreach (var(_, secret) in secrets.Secrets)
                {
                    if (secret is UpdatingSasToken updating)
                    {
                        updating.TokenUpdated += tokenUpdated;

                        actions.Add(() => updating.TokenUpdated -= tokenUpdated);
                    }
                }

                if (actions.Count != 0)
                {
                    // It means that we have at least one 'updatable' token.
                    actions.Insert(
                        0,
                        () =>
                    {
                        Tracer.Debug(context, "Unsubscribing from secret updates.");
                    });
                }

                return(actions);
            }

            void tokenUpdated(object?sender, SasToken e)
            {
                var newSerializedSecrets = RetrievedSecretsSerializer.Serialize(secrets);

                context.PerformOperation(
                    Tracer,
                    () =>
                {
                    MemoryMappedFileHelper.UpdateContent(memoryMappedFileName, newSerializedSecrets);
                    return(BoolResult.Success);
                },
                    extraStartMessage: $"Updating secrets in '{memoryMappedFileName}'",
                    messageFactory: _ => $"Updated secrets in '{memoryMappedFileName}'",
                    caller: "tokenUpdated")
                .IgnoreFailure();     // We traced the results
            }
        }