Discord.Addons icon indicating copy to clipboard operation
Discord.Addons copied to clipboard

Question about the LiteDB integration

Open MostHated opened this issue 3 years ago • 3 comments

------- Edit this questions is worked out, but the last comment is an issue that arose after this one.

Hey there, I know it has been some time since you updated the addon, but I was using LiteDB prior to finding your permission addon, so I was interested in trying it out. I figured that since there wasn't an example and when I opened the project the LiteDB project wasn't added to the solution that it might not have been fully completed, but I figured I would give it a go and see how it went. I was able to get everything working pretty well, but only pretty well. I keep running into issues of the DB being locked anytime I try to access something similar to the following (seemingly anywhere or any time that I try to do it):

IConfigStore<LiteDbConfig> configStore;

using var config = configStore.Load();
var guild =  config.Guilds.FirstOrDefault();

That was what I was last attempting before I hopped on Github. Which yielded this rather lengthy exception.

Details
---> System.IO.IOException: The process cannot access the file 'E:\GitHub\instance-id\UASVerifier\UASVerifier.Console\bin\Debug\netcoreapp3.1\LiteDB.db' because it is being used by another process.
   at System.IO.FileStream.ValidateFileHandle(SafeFileHandle fileHandle)
   at System.IO.FileStream.CreateFileOpenHandle(FileMode mode, FileShare share, FileOptions options)
   at System.IO.FileStream.OpenHandle(FileMode mode, FileShare share, FileOptions options)
   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options)
   at LiteDB.Engine.FileStreamFactory.GetStream(Boolean canWrite, Boolean sequencial)
   at LiteDB.Engine.StreamPool.<>c__DisplayClass3_0.<.ctor>b__0()
   at System.Lazy`1.ViaFactory(LazyThreadSafetyMode mode)
   at System.Lazy`1.ExecutionAndPublication(LazyHelper executionAndPublication, Boolean useDefaultConstructor)
   at System.Lazy`1.CreateValue()
   at System.Lazy`1.get_Value()
   at LiteDB.Engine.StreamPool.get_Writer()
   at LiteDB.Engine.DiskService..ctor(EngineSettings settings, Int32[] memorySegmentSizes)
   at LiteDB.Engine.LiteEngine..ctor(EngineSettings settings)
   at LiteDB.ConnectionString.CreateEngine()
   at LiteDB.LiteDatabase..ctor(ConnectionString connectionString, BsonMapper mapper)
   at LiteDB.LiteRepository..ctor(ConnectionString connectionString, BsonMapper mapper)
   at instance.id.UASVerifier.Core.LiteConfigBase`3..ctor(ConnectionString connectionString, BsonMapper mapper) in E:\GitHub\instance-id\UASVerifier\UASVerifier.Core\Services\DataStorage\Provider\LiteDb\LiteConfigBase.cs:line 21
   at instance.id.UASVerifier.Core.LiteDbConfig..ctor(ConnectionString connectionString, BsonMapper mapper) in E:\GitHub\instance-id\UASVerifier\UASVerifier.Core\Services\DataStorage\Provider\LiteDb\LiteDBConfig.cs:line 29

I was trying my best to go by the examples from the Entity Framework for the things that at least seemed to match up, but there are obviously some differences, of course.

LiteDB, instead of having a standard Enumeration FirstOrDefault() and various other extension methods, has an interface in which things like FirstOrDefault() accept no parameters, so while examples in other projects I was looking at that use this library and the Entity Framework provider are able to simply do the following:

var guild =  config.Guilds.FirstOrDefault(g => g.GuildId == Context.Guild.Id);

I am guessing that it is not quite the same here, so I was hoping that if you had some time you might be able to shed a bit of light on the proper way to go about achieving a similar result?

I can't help but feel that either I am missing a step, or perhaps that going by the Entity Framework examples for things like that simply aren't going to work for this. I like the overall setup and architecture of the LiteDB provider and would like to use it, but I have been banging my head against a wall all evening, lol.

If it helps any, here is the file in particular in which I was working with:

CommandHandler
public class CommandHandler
{
    private readonly DiscordSocketClient client;
    private readonly CmdService cmdService;
    private readonly InitConfig discord;

    private readonly IServiceProvider services;
    private readonly IConfigStore<LiteDbConfig> configStore;

    public CommandHandler(DiscordSocketClient socketClient, CmdService commandService, IServiceProvider serviceProvider, IConfigStore<LiteDbConfig> config, InitConfig dg)
    {
        client = socketClient;
        cmdService = commandService;
        services = serviceProvider;
        configStore = config;
        discord = dg;
    }

    public async Task InitAsync()
    {
        await cmdService.AddModulesAsync(Assembly.GetExecutingAssembly(), services);

        client.MessageReceived += OnMsgReceived;
        cmdService.CommandExecuted += OnCmdExec;
    }

    /// <summary>
    /// Message received handler
    /// </summary>
    /// <param name="msg">The received message</param>
    /// <returns></returns>
    private async Task OnMsgReceived(SocketMessage msg)
    {
        if (!(msg is SocketUserMessage message)) return;
        if (message.Author.Id == client.CurrentUser.Id || message.Author.IsBot) return;

        using var config = configStore.Load();
        var guild =  config.Guilds.FirstOrDefault(); // -- Here is where the trouble lies
        // var guild =  config.Guilds.FirstOrDefault(g => g.GuildId == Context.Guild.Id);

        if (!message.Prefixed(client, out var argPos,  guild.CommandPrefix))
            return;

        var context = new SocketCommandContext(client, message);
        var result = await cmdService.ExecuteAsync(context, argPos, services);
    }
}

Then here was where I did the setup:

BotClient
public class BotClient
{
    public static char commandPrefix = '!';

    private readonly DiscordSocketClient client;
    private readonly CmdService commands;
    private readonly Initialization init;
    private readonly InitConfig discord;
    private IServiceProvider services;
    private IConfigStore<LiteDbConfig> configStore { get; }

    public static IConfiguration config { get; } = new ConfigurationBuilder()
        .AddTomlFile("config.toml".LocateConfig(), false)
        .Build();

    public BotClient(CmdService commands = null, DiscordSocketClient client = null)
    {
        discord = config.GetSection("discord").Get<InitConfig>();

        this.client = client ?? new DiscordSocketClient(new DiscordSocketConfig
        {
            LogLevel = discord.SystemLoglevel,
            AlwaysDownloadUsers = discord.alwaysDownloadUsers,
            MessageCacheSize = discord.MessageCacheSize
        });

        this.commands = commands ?? new CmdService(new CmdServiceConfig
        {
            CaseSensitiveCommands = discord.caseSensitiveCommands,
            LogLevel = discord.UserLogLevel
        });

        var dbPath = string.IsNullOrWhiteSpace(discord.databasePath) ? "LiteDB.db" : discord.databasePath;

        configStore = new LiteConfigStore<LiteDbConfig, MyConfigGuild, MyConfigChannel, MyConfigUser>(
            new ConnectionString(dbPath), this.commands, logger: LoggingUtils.Log);
    }

    public async Task InitializeAsync()
    {
        services = ConfigureServices();
        ((LiteConfigStore<LiteDbConfig, MyConfigGuild, MyConfigChannel, MyConfigUser>) configStore).Services = services;

        client.Ready += OnReadyAsync;
        client.Log += LoggingUtils.Log;
        commands.Log += LoggingUtils.Log;

        await Task.Delay(10).ContinueWith(t => client.LoginAsync(TokenType.Bot, discord.serverToken));
        await client.StartAsync();

        await services.GetRequiredService<CommandHandler>().InitAsync();
        await Task.Delay(-1);
    }

    private async Task OnReadyAsync()
    {
        await client.SetGameAsync(discord.initialStatus);
        await services.GetRequiredService<Initialization>().Init();
    }

    private ServiceProvider ConfigureServices()
    {
        return new ServiceCollection()
            .AddSingleton(client)
            .AddSingleton(commands)
            .AddSingleton(configStore)
            .AddSingleton(discord)
            .AddSingleton<CommandHandler>()
            .AddSingleton<PageHandler>()
            .AddSingleton<CmdPermissionService>()
            .AddSingleton<Initialization>()
            .AddSingleton(new PermissionsService(configStore, commands, client, discord, LoggingUtils.Log))
            .BuildServiceProvider();
    }
}

Then this was my "config" file, not sure if it helps any, but I figured, what the heck, why not?

LiteDbConfig
namespace instance.id.UASVerifier.Core
{
    public class MyConfigUser : ConfigUser
    {
        public string Nickname { get; set; }
    }

    public class MyConfigChannel : ConfigChannel<MyConfigUser>
    {
        public string Topic { get; set; }
    }

    public class MyConfigGuild : ConfigGuild<MyConfigChannel, MyConfigUser>
    {
        public char CommandPrefix { get; set; } = BotClient.commandPrefix;
    }

    public class LiteDbConfig : LiteConfigBase<MyConfigGuild, MyConfigChannel, MyConfigUser>
    {
        public LiteDbConfig(ConnectionString connectionString, BsonMapper mapper) : base(connectionString, mapper) { }
        internal string GetLoginToken() => throw new NotImplementedException();

        // internal ulong GetGuildId() => Guilds.SingleOrDefault().GuildId; // -- Some sort of issue?
    }
}

Hopefully, I am just doing something incorrectly somewhere, lol. If you are able to lend a hand, I would greatly appreciate it. If not, I completely understand, so no worries. Thanks, -MH

MostHated avatar Nov 28 '20 02:11 MostHated