feat: add database

This commit is contained in:
Anna 2024-02-18 10:16:12 -05:00
parent ada2003ac8
commit 1e50cfe32d
Signed by: anna
GPG Key ID: D0943384CD9F87D1
8 changed files with 210 additions and 19 deletions

View File

@ -67,15 +67,44 @@ internal class Command : IDisposable {
}
string hash;
await using (var stream = new Blake3Stream(this.OpenFile(ext, meta))) {
var (inner, path) = this.OpenFile(ext, meta);
await using (var stream = new Blake3Stream(inner)) {
await stream.WriteAsync(imageData);
hash = Convert.ToHexString(stream.ComputeHash().AsSpan());
}
meta.Blake3Hash = hash;
var json = JsonConvert.SerializeObject(meta, Formatting.Indented);
// TODO: save this info to database
Plugin.Log.Info(json);
var saved = new SavedMetadata {
Path = path,
Blake3Hash = hash,
Metadata = meta,
};
this.Plugin.Database.Execute(
"""
insert into screenshots
(hash, path, active_character, location, location_sub, area, area_sub, territory_type, world, world_id, captured_at_local, captured_at_utc, eorzea_time, weather, ward, plot, visible_characters)
values ($hash, $path, jsonb($active_character), $location, $location_sub, $area, $area_sub, $territory_type, $world, $world_id, $captured_at_local, $captured_at_utc, $eorzea_time, $weather, $ward, $plot, jsonb($visible_characters))
""",
new Dictionary<string, object?> {
["$hash"] = saved.Blake3Hash,
["$path"] = saved.Path,
["$active_character"] = JsonConvert.SerializeObject(saved.Metadata.ActiveCharacter),
["$location"] = saved.Metadata.Location,
["$location_sub"] = saved.Metadata.LocationSub,
["$area"] = saved.Metadata.Area,
["$area_sub"] = saved.Metadata.AreaSub,
["$territory_type"] = saved.Metadata.TerritoryType,
["$world"] = saved.Metadata.World,
["$world_id"] = saved.Metadata.WorldId,
["$captured_at_local"] = saved.Metadata.CapturedAtLocal,
["$captured_at_utc"] = saved.Metadata.CapturedAtUtc,
["$eorzea_time"] = saved.Metadata.EorzeaTime,
["$weather"] = saved.Metadata.Weather,
["$ward"] = saved.Metadata.Ward,
["$plot"] = saved.Metadata.Plot,
["$visible_characters"] = JsonConvert.SerializeObject(saved.Metadata.VisibleCharacters),
}
);
var message = new SeStringBuilder()
.AddText("Screenshot saved. [")
@ -114,7 +143,7 @@ internal class Command : IDisposable {
return (stream.ToArray(), ext);
}
private FileStream OpenFile(string ext, ScreenshotMetadata meta) {
private (FileStream, string) OpenFile(string ext, ScreenshotMetadata meta) {
Directory.CreateDirectory(this.Plugin.Config.SaveDirectory);
var fileName = this.Plugin.Config.SaveFileNameTemplate.Render(meta).ReplaceLineEndings(" ");
@ -125,6 +154,6 @@ internal class Command : IDisposable {
var parent = Path.Join(path, "..");
Directory.CreateDirectory(parent);
return new FileStream(path, FileMode.Create, FileAccess.Write);
return (new FileStream(path, FileMode.Create, FileAccess.Write), path);
}
}

102
Database.cs Normal file
View File

@ -0,0 +1,102 @@
using System.Data.SQLite;
namespace Screenie;
internal class Database : IDisposable {
private Plugin Plugin { get; }
private SQLiteConnection Connection { get; }
internal Database(Plugin plugin) {
this.Plugin = plugin;
var dir = this.Plugin.Interface.GetPluginConfigDirectory();
Directory.CreateDirectory(dir);
var dbPath = Path.Join(dir, "database.sqlite");
this.Connection = new SQLiteConnection($"Data Source={dbPath}");
this.Connection.Open();
this.RunMigrations();
}
internal Database(string dir) {
Directory.CreateDirectory(dir);
var dbPath = Path.Join(dir, "database.sqlite");
this.Connection = new SQLiteConnection($"Data Source={dbPath}");
this.Connection.Open();
this.RunMigrations();
}
public void Dispose() {
this.Connection.Close();
this.Connection.Dispose();
}
private void RunMigrations() {
using var t = this.Connection.BeginTransaction();
this.Execute("create table if not exists _migrations (id int not null primary key, query text not null)");
// get latest migration
long max;
{
using var reader = this.Query("select coalesce(max(id), 0) from _migrations");
if (!reader.Read()) {
throw new Exception("could not get max migration");
}
max = reader.GetFieldValue<long>(0);
}
if (max < 1) {
const string migrationOne = """
create table screenshots (
hash text not null primary key,
path text not null,
active_character jsonb not null,
location text,
location_sub text,
area text,
area_sub text,
territory_type int not null,
world text,
world_id int not null,
captured_at_local timestamp,
captured_at_utc timestamp,
eorzea_time text not null,
weather text,
ward int,
plot int,
visible_characters jsonb not null
)
""";
this.Execute(migrationOne);
this.Execute("insert into _migrations (id, query) values (1, $query)", new Dictionary<string, object?> {
["$query"] = migrationOne,
});
}
t.Commit();
}
private SQLiteCommand GetCommand(string query, Dictionary<string, object?>? parameters = null) {
var command = this.Connection.CreateCommand();
command.CommandText = query;
if (parameters != null) {
foreach (var (key, value) in parameters) {
command.Parameters.AddWithValue(key, value);
}
}
return command;
}
internal SQLiteDataReader Query(string query, Dictionary<string, object?>? parameters = null) {
using var command = this.GetCommand(query, parameters);
return command.ExecuteReader();
}
internal int Execute(string query, Dictionary<string, object?>? parameters = null) {
using var command = this.GetCommand(query, parameters);
return command.ExecuteNonQuery();
}
}

View File

@ -36,6 +36,7 @@ public class Plugin : IDalamudPlugin {
internal IObjectTable ObjectTable { get; init; }
internal Configuration Config { get; }
internal Database Database { get; }
internal LinkHandlers LinkHandlers { get; }
internal PluginUi Ui { get; }
@ -43,6 +44,7 @@ public class Plugin : IDalamudPlugin {
public Plugin() {
this.Config = this.Interface!.GetPluginConfig() as Configuration ?? new Configuration();
this.Database = new Database(this);
this.LinkHandlers = new LinkHandlers(this);
this.Ui = new PluginUi(this);
this.Command = new Command(this);
@ -52,9 +54,14 @@ public class Plugin : IDalamudPlugin {
this.Command.Dispose();
this.Ui.Dispose();
this.LinkHandlers.Dispose();
this.Database.Dispose();
}
internal void SaveConfig() {
this.Interface.SavePluginConfig(this.Config);
}
public static void Main() {
using var db = new Database("/home/anna/code/Screenie");
}
}

View File

@ -56,12 +56,15 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Blake3" Version="1.0.0" />
<PackageReference Include="Blake3" Version="1.0.0"/>
<PackageReference Include="DalamudPackager" Version="2.1.12"/>
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.49-beta">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Scriban" Version="5.9.1" />
<PackageReference Include="Penumbra.Api" Version="1.0.14"/>
<PackageReference Include="Scriban" Version="5.9.1"/>
<PackageReference Include="SQLite" Version="3.13.0"/>
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.118"/>
<PackageReference Include="System.Drawing.Common" Version="8.0.2"/>
<PackageReference Include="WebP_Net" Version="1.1.1"/>
</ItemGroup>

View File

@ -13,8 +13,14 @@ using Map = Lumina.Excel.GeneratedSheets.Map;
namespace Screenie;
[Serializable]
public class ScreenshotMetadata {
public class SavedMetadata {
public required string Blake3Hash;
public required string Path;
public required ScreenshotMetadata Metadata;
}
[Serializable]
public class ScreenshotMetadata {
public required Character? ActiveCharacter;
public required string? Location;
public required string? LocationSub;
@ -105,7 +111,6 @@ public class ScreenshotMetadata {
.ToArray();
return new ScreenshotMetadata {
Blake3Hash = "",
ActiveCharacter = active,
Location = map?.PlaceName.Value?.Name.ToDalamudString().TextValue.WhitespaceToNull()
?? territory?.PlaceName.Value?.Name.ToDalamudString().TextValue.WhitespaceToNull(),

View File

@ -95,7 +95,15 @@ internal class PluginUi : IDisposable {
ImGui.TextUnformatted("Filename format");
ImGui.SetNextItemWidth(-1);
anyChanged |= ImGui.InputTextMultiline("##filename-format", ref this.Plugin.Config.SaveFileNameFormat, 1024, new Vector2(-1, 150));
ImGui.PushFont(UiBuilder.MonoFont);
using (new OnDispose(ImGui.PopFont)) {
anyChanged |= ImGui.InputTextMultiline(
"##filename-format",
ref this.Plugin.Config.SaveFileNameFormat,
2048,
new Vector2(-1, 150)
);
}
var template = this.Plugin.Config.SaveFileNameTemplate;
if (template.HasErrors) {
@ -142,14 +150,18 @@ internal class PluginUi : IDisposable {
ImGui.SameLine();
if (ImGuiComponents.IconButton(FontAwesomeIcon.Folder)) {
this.FileDialogManager.OpenFolderDialog("Choose screenshots folder", (b, s) => {
if (!b) {
return;
}
this.FileDialogManager.OpenFolderDialog(
"Choose screenshots folder",
(b, s) => {
if (!b) {
return;
}
this.Plugin.Config.SaveDirectory = s;
this.Plugin.SaveConfig();
});
this.Plugin.Config.SaveDirectory = s;
this.Plugin.SaveConfig();
},
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)
);
}
return changed;

7
global.json Normal file
View File

@ -0,0 +1,7 @@
{
"sdk": {
"version": "8.0.0",
"rollForward": "latestMajor",
"allowPrerelease": true
}
}

View File

@ -27,12 +27,33 @@
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
}
},
"Penumbra.Api": {
"type": "Direct",
"requested": "[1.0.14, )",
"resolved": "1.0.14",
"contentHash": "KqUuD4d30uhczvfH2YtcbwBxGnovBQAIudZ99eRxK0NeHwnQ3vdKe2BO2gZ2ZzT3JV6lu0NvosXXqqcQ0ZtqLw=="
},
"Scriban": {
"type": "Direct",
"requested": "[5.9.1, )",
"resolved": "5.9.1",
"contentHash": "Er0jZCXrHXtk+nnzmHVEnmz1pjfU+VL3GppO0UjtCMoZ0Se1plyPe1OLb6gM7ToSRA7nu/QIcdRFr29x8w8rQQ=="
},
"SQLite": {
"type": "Direct",
"requested": "[3.13.0, )",
"resolved": "3.13.0",
"contentHash": "MJfRiz2p6aMVOxrxGMdVzhpzI0oxTgZSwC8eVuOpV8L7yYaFUu8q/OFYwv9P0/aZ/pdEu24O6gma6wZJMTun9A=="
},
"System.Data.SQLite.Core": {
"type": "Direct",
"requested": "[1.0.118, )",
"resolved": "1.0.118",
"contentHash": "2V1PsfBeqWlZxF/VtB8lQKPfDBayCU8zD5Xc3Mq7cILOa2ZqpPDSwMP0fTfk1gtGSStSk//DxKiGy6zwCQs7Uw==",
"dependencies": {
"Stub.System.Data.SQLite.Core.NetStandard": "[1.0.118]"
}
},
"System.Drawing.Common": {
"type": "Direct",
"requested": "[8.0.2, )",
@ -74,6 +95,11 @@
"Microsoft.Windows.SDK.Win32Metadata": "55.0.45-preview"
}
},
"Stub.System.Data.SQLite.Core.NetStandard": {
"type": "Transitive",
"resolved": "1.0.118",
"contentHash": "4TS8IZvDj0ud6utxfXI6zv9Ditk4U9Kt9KqLyAIQGcU3GXp5oGBHgGZifq+APcqRCayuN/MSE8t9ZZmygtU28A=="
},
"System.Memory": {
"type": "Transitive",
"resolved": "4.5.5",