feat: add database
This commit is contained in:
parent
ada2003ac8
commit
1e50cfe32d
43
Command.cs
43
Command.cs
|
@ -67,15 +67,44 @@ internal class Command : IDisposable {
|
||||||
}
|
}
|
||||||
|
|
||||||
string hash;
|
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);
|
await stream.WriteAsync(imageData);
|
||||||
hash = Convert.ToHexString(stream.ComputeHash().AsSpan());
|
hash = Convert.ToHexString(stream.ComputeHash().AsSpan());
|
||||||
}
|
}
|
||||||
|
|
||||||
meta.Blake3Hash = hash;
|
var saved = new SavedMetadata {
|
||||||
var json = JsonConvert.SerializeObject(meta, Formatting.Indented);
|
Path = path,
|
||||||
// TODO: save this info to database
|
Blake3Hash = hash,
|
||||||
Plugin.Log.Info(json);
|
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()
|
var message = new SeStringBuilder()
|
||||||
.AddText("Screenshot saved. [")
|
.AddText("Screenshot saved. [")
|
||||||
|
@ -114,7 +143,7 @@ internal class Command : IDisposable {
|
||||||
return (stream.ToArray(), ext);
|
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);
|
Directory.CreateDirectory(this.Plugin.Config.SaveDirectory);
|
||||||
|
|
||||||
var fileName = this.Plugin.Config.SaveFileNameTemplate.Render(meta).ReplaceLineEndings(" ");
|
var fileName = this.Plugin.Config.SaveFileNameTemplate.Render(meta).ReplaceLineEndings(" ");
|
||||||
|
@ -125,6 +154,6 @@ internal class Command : IDisposable {
|
||||||
var parent = Path.Join(path, "..");
|
var parent = Path.Join(path, "..");
|
||||||
Directory.CreateDirectory(parent);
|
Directory.CreateDirectory(parent);
|
||||||
|
|
||||||
return new FileStream(path, FileMode.Create, FileAccess.Write);
|
return (new FileStream(path, FileMode.Create, FileAccess.Write), path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -36,6 +36,7 @@ public class Plugin : IDalamudPlugin {
|
||||||
internal IObjectTable ObjectTable { get; init; }
|
internal IObjectTable ObjectTable { get; init; }
|
||||||
|
|
||||||
internal Configuration Config { get; }
|
internal Configuration Config { get; }
|
||||||
|
internal Database Database { get; }
|
||||||
internal LinkHandlers LinkHandlers { get; }
|
internal LinkHandlers LinkHandlers { get; }
|
||||||
internal PluginUi Ui { get; }
|
internal PluginUi Ui { get; }
|
||||||
|
|
||||||
|
@ -43,6 +44,7 @@ public class Plugin : IDalamudPlugin {
|
||||||
|
|
||||||
public Plugin() {
|
public Plugin() {
|
||||||
this.Config = this.Interface!.GetPluginConfig() as Configuration ?? new Configuration();
|
this.Config = this.Interface!.GetPluginConfig() as Configuration ?? new Configuration();
|
||||||
|
this.Database = new Database(this);
|
||||||
this.LinkHandlers = new LinkHandlers(this);
|
this.LinkHandlers = new LinkHandlers(this);
|
||||||
this.Ui = new PluginUi(this);
|
this.Ui = new PluginUi(this);
|
||||||
this.Command = new Command(this);
|
this.Command = new Command(this);
|
||||||
|
@ -52,9 +54,14 @@ public class Plugin : IDalamudPlugin {
|
||||||
this.Command.Dispose();
|
this.Command.Dispose();
|
||||||
this.Ui.Dispose();
|
this.Ui.Dispose();
|
||||||
this.LinkHandlers.Dispose();
|
this.LinkHandlers.Dispose();
|
||||||
|
this.Database.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void SaveConfig() {
|
internal void SaveConfig() {
|
||||||
this.Interface.SavePluginConfig(this.Config);
|
this.Interface.SavePluginConfig(this.Config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void Main() {
|
||||||
|
using var db = new Database("/home/anna/code/Screenie");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,12 +56,15 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<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="DalamudPackager" Version="2.1.12"/>
|
||||||
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.49-beta">
|
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.49-beta">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</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="System.Drawing.Common" Version="8.0.2"/>
|
||||||
<PackageReference Include="WebP_Net" Version="1.1.1"/>
|
<PackageReference Include="WebP_Net" Version="1.1.1"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
@ -13,8 +13,14 @@ using Map = Lumina.Excel.GeneratedSheets.Map;
|
||||||
namespace Screenie;
|
namespace Screenie;
|
||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public class ScreenshotMetadata {
|
public class SavedMetadata {
|
||||||
public required string Blake3Hash;
|
public required string Blake3Hash;
|
||||||
|
public required string Path;
|
||||||
|
public required ScreenshotMetadata Metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public class ScreenshotMetadata {
|
||||||
public required Character? ActiveCharacter;
|
public required Character? ActiveCharacter;
|
||||||
public required string? Location;
|
public required string? Location;
|
||||||
public required string? LocationSub;
|
public required string? LocationSub;
|
||||||
|
@ -105,7 +111,6 @@ public class ScreenshotMetadata {
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
return new ScreenshotMetadata {
|
return new ScreenshotMetadata {
|
||||||
Blake3Hash = "",
|
|
||||||
ActiveCharacter = active,
|
ActiveCharacter = active,
|
||||||
Location = map?.PlaceName.Value?.Name.ToDalamudString().TextValue.WhitespaceToNull()
|
Location = map?.PlaceName.Value?.Name.ToDalamudString().TextValue.WhitespaceToNull()
|
||||||
?? territory?.PlaceName.Value?.Name.ToDalamudString().TextValue.WhitespaceToNull(),
|
?? territory?.PlaceName.Value?.Name.ToDalamudString().TextValue.WhitespaceToNull(),
|
||||||
|
|
|
@ -95,7 +95,15 @@ internal class PluginUi : IDisposable {
|
||||||
|
|
||||||
ImGui.TextUnformatted("Filename format");
|
ImGui.TextUnformatted("Filename format");
|
||||||
ImGui.SetNextItemWidth(-1);
|
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;
|
var template = this.Plugin.Config.SaveFileNameTemplate;
|
||||||
if (template.HasErrors) {
|
if (template.HasErrors) {
|
||||||
|
@ -142,14 +150,18 @@ internal class PluginUi : IDisposable {
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
|
|
||||||
if (ImGuiComponents.IconButton(FontAwesomeIcon.Folder)) {
|
if (ImGuiComponents.IconButton(FontAwesomeIcon.Folder)) {
|
||||||
this.FileDialogManager.OpenFolderDialog("Choose screenshots folder", (b, s) => {
|
this.FileDialogManager.OpenFolderDialog(
|
||||||
if (!b) {
|
"Choose screenshots folder",
|
||||||
return;
|
(b, s) => {
|
||||||
}
|
if (!b) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.Plugin.Config.SaveDirectory = s;
|
this.Plugin.Config.SaveDirectory = s;
|
||||||
this.Plugin.SaveConfig();
|
this.Plugin.SaveConfig();
|
||||||
});
|
},
|
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return changed;
|
return changed;
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"sdk": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"rollForward": "latestMajor",
|
||||||
|
"allowPrerelease": true
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,12 +27,33 @@
|
||||||
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
|
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Penumbra.Api": {
|
||||||
|
"type": "Direct",
|
||||||
|
"requested": "[1.0.14, )",
|
||||||
|
"resolved": "1.0.14",
|
||||||
|
"contentHash": "KqUuD4d30uhczvfH2YtcbwBxGnovBQAIudZ99eRxK0NeHwnQ3vdKe2BO2gZ2ZzT3JV6lu0NvosXXqqcQ0ZtqLw=="
|
||||||
|
},
|
||||||
"Scriban": {
|
"Scriban": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[5.9.1, )",
|
"requested": "[5.9.1, )",
|
||||||
"resolved": "5.9.1",
|
"resolved": "5.9.1",
|
||||||
"contentHash": "Er0jZCXrHXtk+nnzmHVEnmz1pjfU+VL3GppO0UjtCMoZ0Se1plyPe1OLb6gM7ToSRA7nu/QIcdRFr29x8w8rQQ=="
|
"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": {
|
"System.Drawing.Common": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[8.0.2, )",
|
"requested": "[8.0.2, )",
|
||||||
|
@ -74,6 +95,11 @@
|
||||||
"Microsoft.Windows.SDK.Win32Metadata": "55.0.45-preview"
|
"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": {
|
"System.Memory": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "4.5.5",
|
"resolved": "4.5.5",
|
||||||
|
|
Loading…
Reference in New Issue