mirror of
				https://github.com/9ParsonsB/Pulsar.git
				synced 2025-11-03 23:36:42 -05:00 
			
		
		
		
	JournalService setup work in progress
This commit is contained in:
		@@ -278,7 +278,7 @@ using Travel;
 | 
			
		||||
[JsonDerivedType(typeof(ShipyardFile))]
 | 
			
		||||
[JsonDerivedType(typeof(Status))]
 | 
			
		||||
[JsonDerivedType(typeof(JournalBase))]
 | 
			
		||||
public interface IJournal
 | 
			
		||||
public class JournalBase
 | 
			
		||||
{
 | 
			
		||||
    [JsonPropertyName("timestamp")]
 | 
			
		||||
    public DateTimeOffset Timestamp { get; init; }
 | 
			
		||||
@@ -289,10 +289,3 @@ public interface IJournal
 | 
			
		||||
    [JsonExtensionData]
 | 
			
		||||
    public Dictionary<string, object> AdditionalProperties { get; init; }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public abstract class JournalBase : IJournal
 | 
			
		||||
{
 | 
			
		||||
    public DateTimeOffset Timestamp { get; init; }
 | 
			
		||||
    public string Event { get; init; }
 | 
			
		||||
    public Dictionary<string, object> AdditionalProperties { get; init; }
 | 
			
		||||
}
 | 
			
		||||
@@ -54,7 +54,7 @@ public interface IObservatoryWorker : IObservatoryPlugin
 | 
			
		||||
    /// <param name="journal"><para>Elite Dangerous journal event, deserialized into a .NET object.</para>
 | 
			
		||||
    /// <para>Unhandled json values within a journal entry type will be contained in member property:<br/>Dictionary<string, object> AdditionalProperties.</para>
 | 
			
		||||
    /// <para>Unhandled journal event types will be type JournalBase with all values contained in AdditionalProperties.</para></param>
 | 
			
		||||
    public void JournalEvent<TJournal>(TJournal journal) where TJournal : IJournal;
 | 
			
		||||
    public void JournalEvent<TJournal>(TJournal journal) where TJournal : JournalBase;
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Method called when status.json content is updated.<br/>
 | 
			
		||||
 
 | 
			
		||||
@@ -6,45 +6,45 @@ using Observatory.Framework.Files.Journal;
 | 
			
		||||
public class EventsHub : Hub<IEventsHub>
 | 
			
		||||
{
 | 
			
		||||
    public async Task StatusUpdated(Observatory.Framework.Files.Status status) => await Clients.All.StatusUpdated(status);
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public async Task OutfittingUpdated(OutfittingFile outfitting) => await Clients.All.OutfittingUpdated(outfitting);
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public async Task ShipyardUpdated(ShipyardFile shipyard) => await Clients.All.ShipyardUpdated(shipyard);
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public async Task NavRouteUpdated(NavRouteFile navRoute) => await Clients.All.NavRouteUpdated(navRoute);
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public async Task MarketUpdated(MarketFile market) => await Clients.All.MarketUpdated(market);
 | 
			
		||||
    
 | 
			
		||||
    public async Task JournalUpdated(IReadOnlyCollection<IJournal> journals) => await Clients.All.JournalUpdated(journals);
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public async Task JournalUpdated(IReadOnlyCollection<JournalBase> journals) => await Clients.All.JournalUpdated(journals);
 | 
			
		||||
 | 
			
		||||
    public async Task ModuleInfoUpdated(ModuleInfoFile moduleInfo) => await Clients.All.ModuleInfoUpdated(moduleInfo);
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public async Task FleetCarrierUpdated(FCMaterialsFile fleetCarrier) => await Clients.All.FleetCarrierUpdated(fleetCarrier);
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public async Task CargoUpdated(CargoFile cargo) => await Clients.All.CargoUpdated(cargo);
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public async Task BackpackUpdated(BackpackFile backpack) => await Clients.All.BackpackUpdated(backpack);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public interface IEventsHub
 | 
			
		||||
{
 | 
			
		||||
    Task StatusUpdated(Observatory.Framework.Files.Status status);
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    Task OutfittingUpdated(OutfittingFile outfitting);
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    Task ShipyardUpdated(ShipyardFile shipyard);
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    Task NavRouteUpdated(NavRouteFile navRoute);
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    Task MarketUpdated(MarketFile market);
 | 
			
		||||
    
 | 
			
		||||
    Task JournalUpdated(IReadOnlyCollection<IJournal> journals);
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    Task JournalUpdated(IReadOnlyCollection<JournalBase> journals);
 | 
			
		||||
 | 
			
		||||
    Task ModuleInfoUpdated(ModuleInfoFile moduleInfo);
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    Task FleetCarrierUpdated(FCMaterialsFile fleetCarrier);
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    Task CargoUpdated(CargoFile cargo);
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    Task BackpackUpdated(BackpackFile backpack);
 | 
			
		||||
}
 | 
			
		||||
@@ -8,7 +8,7 @@ namespace Pulsar.Features;
 | 
			
		||||
public interface IJournalHandler : IFileHandler
 | 
			
		||||
{
 | 
			
		||||
    string FileName { get; }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public bool ValidateFile(string filePath);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -17,42 +17,6 @@ public interface IJournalHandler : IFileHandler
 | 
			
		||||
/// Only used for Controllers
 | 
			
		||||
/// </summary>
 | 
			
		||||
public interface IJournalHandler<T> : IJournalHandler
 | 
			
		||||
    where T: IJournal
 | 
			
		||||
{
 | 
			
		||||
    Task<T> Get();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public abstract class JournalHandlerBase<T>(ILogger logger) : IJournalHandler<T>
 | 
			
		||||
    where T: IJournal
 | 
			
		||||
{
 | 
			
		||||
    public abstract string FileName { get; }
 | 
			
		||||
 | 
			
		||||
    public abstract Task HandleFile(string filePath);
 | 
			
		||||
 | 
			
		||||
    public bool ValidateFile(string filePath)
 | 
			
		||||
    {
 | 
			
		||||
        if (!File.Exists(filePath))
 | 
			
		||||
        {
 | 
			
		||||
            logger.LogWarning("Journal file {JournalFile} does not exist", filePath);
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var fileInfo = new FileInfo(filePath);
 | 
			
		||||
 | 
			
		||||
        if (!string.Equals(fileInfo.Name, FileName, StringComparison.InvariantCultureIgnoreCase))
 | 
			
		||||
        {
 | 
			
		||||
            logger.LogWarning("Journal file {JournalFile} is not a {NameOfCurrentHandler} file", filePath, nameof(T));
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (fileInfo.Length == 0)
 | 
			
		||||
        {
 | 
			
		||||
            logger.LogWarning("Journal file {JournalFile} is empty", filePath);
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public abstract Task<T> Get();
 | 
			
		||||
}
 | 
			
		||||
@@ -2,7 +2,11 @@ namespace Pulsar.Features.Journal;
 | 
			
		||||
 | 
			
		||||
[ApiController]
 | 
			
		||||
[Route("api/[controller]")]
 | 
			
		||||
public class JournalController : ControllerBase
 | 
			
		||||
public class JournalController(IJournalService journalService) : ControllerBase
 | 
			
		||||
{
 | 
			
		||||
    
 | 
			
		||||
    [HttpGet]
 | 
			
		||||
    public async Task<IActionResult> Get()
 | 
			
		||||
    {
 | 
			
		||||
        return Ok(await journalService.Get());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										89
									
								
								Pulsar/Features/Journal/JournalService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								Pulsar/Features/Journal/JournalService.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,89 @@
 | 
			
		||||
namespace Pulsar.Features.Journal;
 | 
			
		||||
 | 
			
		||||
using Observatory.Framework.Files;
 | 
			
		||||
using Observatory.Framework.Files.Journal;
 | 
			
		||||
 | 
			
		||||
public interface IJournalService : IJournalHandler<List<JournalBase>>;
 | 
			
		||||
 | 
			
		||||
public class JournalService
 | 
			
		||||
(
 | 
			
		||||
    ILogger<JournalService> logger,
 | 
			
		||||
    IOptions<PulsarConfiguration> options,
 | 
			
		||||
    IEventHubContext hub
 | 
			
		||||
) : IJournalService
 | 
			
		||||
{
 | 
			
		||||
    public string FileName => "Journal.2024-03-16T152419.01.log"; // FileHandlerService.JournalLogFileName;
 | 
			
		||||
 | 
			
		||||
    public bool ValidateFile(string filePath)
 | 
			
		||||
    {
 | 
			
		||||
        if (!File.Exists(filePath))
 | 
			
		||||
        {
 | 
			
		||||
            logger.LogWarning("Journal file {JournalFile} does not exist", filePath);
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var fileInfo = new FileInfo(filePath);
 | 
			
		||||
 | 
			
		||||
        if (!string.Equals(fileInfo.Name, FileName, StringComparison.InvariantCultureIgnoreCase))
 | 
			
		||||
        {
 | 
			
		||||
            logger.LogWarning("Journal file {name} is not valid");
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (fileInfo.Length == 0)
 | 
			
		||||
        {
 | 
			
		||||
            logger.LogWarning("Journal file {name} is empty", filePath);
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task HandleFile(string filePath)
 | 
			
		||||
    {
 | 
			
		||||
        if (!ValidateFile(filePath))
 | 
			
		||||
        {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var file = File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
 | 
			
		||||
        var moduleInfo = await JsonSerializer.DeserializeAsync<List<JournalBase>>(file);
 | 
			
		||||
 | 
			
		||||
        if (moduleInfo == null)
 | 
			
		||||
        {
 | 
			
		||||
            logger.LogWarning("Failed to deserialize status file {FilePath}", filePath);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // await hub.Clients.All.ModuleInfoUpdated(moduleInfo);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<List<JournalBase>> Get()
 | 
			
		||||
    {
 | 
			
		||||
        var dataFileName = Path.Combine(options.Value.JournalDirectory, FileName);
 | 
			
		||||
 | 
			
		||||
        if (!ValidateFile(dataFileName))
 | 
			
		||||
        {
 | 
			
		||||
            return [];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Seems each entry is a new line. Not sure if this can be relied on?
 | 
			
		||||
        var logs = File.ReadAllLines(dataFileName);
 | 
			
		||||
 | 
			
		||||
        var journals = new List<JournalBase>();
 | 
			
		||||
        foreach (var log in logs)
 | 
			
		||||
        {
 | 
			
		||||
            // var info = JournalReader.ObservatoryDeserializer<JournalBase>(log);
 | 
			
		||||
            var info = JsonSerializer.Deserialize<JournalBase>(log);
 | 
			
		||||
            if (info != null)
 | 
			
		||||
            {
 | 
			
		||||
                journals.Add(info);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (journals.Count > 0) return journals;
 | 
			
		||||
 | 
			
		||||
        logger.LogWarning("Failed to deserialize module info file {file}", dataFileName);
 | 
			
		||||
        return [];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -7,6 +7,6 @@ public class ModulesInfoController(IModulesInfoService modulesInfo) : Controller
 | 
			
		||||
    [HttpGet]
 | 
			
		||||
    public async Task<IActionResult> Get()
 | 
			
		||||
    {
 | 
			
		||||
        return Ok(modulesInfo.Get());
 | 
			
		||||
        return Ok(await modulesInfo.Get());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -7,12 +7,36 @@ public interface IModulesInfoService : IJournalHandler<ModuleInfoFile>;
 | 
			
		||||
public class ModulesInfoService(
 | 
			
		||||
    ILogger<ModulesInfoService> logger,
 | 
			
		||||
    IOptions<PulsarConfiguration> options,
 | 
			
		||||
    IEventHubContext hub)
 | 
			
		||||
    : JournalHandlerBase<ModuleInfoFile>(logger), IModulesInfoService
 | 
			
		||||
    IEventHubContext hub) : IModulesInfoService
 | 
			
		||||
{
 | 
			
		||||
    public override string FileName => FileHandlerService.ModulesInfoFileName;
 | 
			
		||||
    public string FileName => FileHandlerService.ModulesInfoFileName;
 | 
			
		||||
 | 
			
		||||
    public override async Task HandleFile(string filePath)
 | 
			
		||||
    public bool ValidateFile(string filePath)
 | 
			
		||||
    {
 | 
			
		||||
        if (!File.Exists(filePath))
 | 
			
		||||
        {
 | 
			
		||||
            logger.LogWarning("Journal file {JournalFile} does not exist", filePath);
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var fileInfo = new FileInfo(filePath);
 | 
			
		||||
 | 
			
		||||
        if (!string.Equals(fileInfo.Name, FileName, StringComparison.InvariantCultureIgnoreCase))
 | 
			
		||||
        {
 | 
			
		||||
            logger.LogWarning("Journal file {name} is not valid");
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (fileInfo.Length == 0)
 | 
			
		||||
        {
 | 
			
		||||
            logger.LogWarning("Journal file {name} is empty", filePath);
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task HandleFile(string filePath)
 | 
			
		||||
    {
 | 
			
		||||
        if (!ValidateFile(filePath))
 | 
			
		||||
        {
 | 
			
		||||
@@ -31,7 +55,7 @@ public class ModulesInfoService(
 | 
			
		||||
        await hub.Clients.All.ModuleInfoUpdated(moduleInfo);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public override async Task<ModuleInfoFile> Get()
 | 
			
		||||
    public async Task<ModuleInfoFile> Get()
 | 
			
		||||
    {
 | 
			
		||||
        var moduleInfoFile = Path.Combine(options.Value.JournalDirectory, FileName);
 | 
			
		||||
 | 
			
		||||
@@ -47,4 +71,4 @@ public class ModulesInfoService(
 | 
			
		||||
        logger.LogWarning("Failed to deserialize module info file {ModuleInfoFile}", moduleInfoFile);
 | 
			
		||||
        return new ModuleInfoFile();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,16 +5,41 @@ using Observatory.Framework.Files.Journal.Odyssey;
 | 
			
		||||
public interface IShipLockerService : IJournalHandler<ShipLockerMaterials>;
 | 
			
		||||
 | 
			
		||||
public class ShipLockerService(ILogger<ShipLockerService> logger)
 | 
			
		||||
    : JournalHandlerBase<ShipLockerMaterials>(logger), IShipLockerService
 | 
			
		||||
    : IShipLockerService
 | 
			
		||||
{
 | 
			
		||||
    public override string FileName => FileHandlerService.ShipLockerFileName;
 | 
			
		||||
    public string FileName => FileHandlerService.ShipLockerFileName;
 | 
			
		||||
 | 
			
		||||
    public override Task<ShipLockerMaterials> Get()
 | 
			
		||||
    public bool ValidateFile(string filePath)
 | 
			
		||||
    {
 | 
			
		||||
        if (!File.Exists(filePath))
 | 
			
		||||
        {
 | 
			
		||||
            logger.LogWarning("Journal file {JournalFile} does not exist", filePath);
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var fileInfo = new FileInfo(filePath);
 | 
			
		||||
 | 
			
		||||
        if (!string.Equals(fileInfo.Name, FileName, StringComparison.InvariantCultureIgnoreCase))
 | 
			
		||||
        {
 | 
			
		||||
            logger.LogWarning("Journal file {name} is not valid");
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (fileInfo.Length == 0)
 | 
			
		||||
        {
 | 
			
		||||
            logger.LogWarning("Journal file {name} is empty", filePath);
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Task<ShipLockerMaterials> Get()
 | 
			
		||||
    {
 | 
			
		||||
        throw new NotImplementedException();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public override Task HandleFile(string filePath)
 | 
			
		||||
    public Task HandleFile(string filePath)
 | 
			
		||||
    {
 | 
			
		||||
        throw new NotImplementedException();
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -4,12 +4,41 @@ using Observatory.Framework.Files;
 | 
			
		||||
 | 
			
		||||
public interface IStatusService : IJournalHandler<Status>;
 | 
			
		||||
 | 
			
		||||
public class StatusService(ILogger<StatusService> logger, IOptions<PulsarConfiguration> options, IEventHubContext hub)
 | 
			
		||||
    : JournalHandlerBase<Status>(logger), IStatusService
 | 
			
		||||
public class StatusService
 | 
			
		||||
(
 | 
			
		||||
    ILogger<StatusService> logger,
 | 
			
		||||
    IOptions<PulsarConfiguration> options,
 | 
			
		||||
    IEventHubContext hub
 | 
			
		||||
) : IStatusService
 | 
			
		||||
{
 | 
			
		||||
    public override string FileName => FileHandlerService.StatusFileName;
 | 
			
		||||
    public string FileName => FileHandlerService.StatusFileName;
 | 
			
		||||
 | 
			
		||||
    public override async Task HandleFile(string filePath)
 | 
			
		||||
    public bool ValidateFile(string filePath)
 | 
			
		||||
    {
 | 
			
		||||
        if (!File.Exists(filePath))
 | 
			
		||||
        {
 | 
			
		||||
            logger.LogWarning("Journal file {JournalFile} does not exist", filePath);
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var fileInfo = new FileInfo(filePath);
 | 
			
		||||
 | 
			
		||||
        if (!string.Equals(fileInfo.Name, FileName, StringComparison.InvariantCultureIgnoreCase))
 | 
			
		||||
        {
 | 
			
		||||
            logger.LogWarning("Journal file {name} is not valid");
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (fileInfo.Length == 0)
 | 
			
		||||
        {
 | 
			
		||||
            logger.LogWarning("Journal file {name} is empty", filePath);
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task HandleFile(string filePath)
 | 
			
		||||
    {
 | 
			
		||||
        if (!ValidateFile(filePath))
 | 
			
		||||
        {
 | 
			
		||||
@@ -28,7 +57,7 @@ public class StatusService(ILogger<StatusService> logger, IOptions<PulsarConfigu
 | 
			
		||||
        await hub.Clients.All.StatusUpdated(status);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public override async Task<Status> Get()
 | 
			
		||||
    public async Task<Status> Get()
 | 
			
		||||
    {
 | 
			
		||||
        var statusFile = Path.Combine(options.Value.JournalDirectory, FileName);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis;
 | 
			
		||||
using Lamar;
 | 
			
		||||
using Pulsar.Features;
 | 
			
		||||
using Pulsar.Features.ModulesInfo;
 | 
			
		||||
using Pulsar.Features.Journal;
 | 
			
		||||
 | 
			
		||||
namespace Pulsar;
 | 
			
		||||
 | 
			
		||||
@@ -13,5 +14,6 @@ public class PulsarServiceRegistry : ServiceRegistry
 | 
			
		||||
        For<IFileHandlerService>().Use<FileHandlerService>();
 | 
			
		||||
        For<IStatusService>().Use<StatusService>();
 | 
			
		||||
        For<IModulesInfoService>().Use<ModulesInfoService>();
 | 
			
		||||
        For<IJournalService>().Use<JournalService>();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -5,7 +5,7 @@ namespace Pulsar.Utils;
 | 
			
		||||
 | 
			
		||||
public static class JournalReader
 | 
			
		||||
{
 | 
			
		||||
    public static TJournal ObservatoryDeserializer<TJournal>(string json) where TJournal : IJournal
 | 
			
		||||
    public static TJournal ObservatoryDeserializer<TJournal>(string json) where TJournal : JournalBase
 | 
			
		||||
    {
 | 
			
		||||
        TJournal deserialized;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user