This repository has been archived on 2026-05-26. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
SimPas2-Windows/Managers/QaManager.cs
2026-01-21 03:07:35 +09:00

216 lines
7.7 KiB
C#

using Microsoft.Data.Sqlite;
using System.Security.Cryptography;
using System.Text;
namespace SimPas2_Windows.Managers
{
public class QaManager
{
private readonly string mConnectionString;
private readonly byte[] mEncryptionKey;
private readonly string mCulture;
private string mJpLang;
public QaManager(string databasePath, byte[] encryptionKey, string culture)
{
mConnectionString = $"Data Source={databasePath}";
mEncryptionKey = encryptionKey ?? throw new ArgumentNullException(nameof(encryptionKey));
mCulture = culture;
mJpLang = "ja-JP";
}
private bool AlreadyExists(string website, string question, int? excludeId = null)
{
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
{
conn.Open();
SqliteCommand com = conn.CreateCommand();
com.CommandText = @"
SELECT COUNT(*) FROM Qa
WHERE UPPER(Website) = UPPER(@website) AND UPPER(Question) = UPPER(@question)";
com.Parameters.AddWithValue("@website", website);
com.Parameters.AddWithValue("@question", question);
if (excludeId.HasValue)
{
com.CommandText += " AND Id != @excludeId";
com.Parameters.AddWithValue("@excludeId", excludeId.Value);
}
return Convert.ToInt32(com.ExecuteScalar()) > 0;
}
}
public void AddQa(string website, string question, string answer, string note)
{
if (string.IsNullOrWhiteSpace(website) || string.IsNullOrWhiteSpace(question) || string.IsNullOrWhiteSpace(answer))
{
string err = mCulture == mJpLang
? "ウェブサイト、質問及び、回答を御入力下さい。"
: "Please fill in the website, question, and answer.";
throw new ArgumentException(err);
}
string encryptedAnswer = EncryptAnswer(answer);
if (AlreadyExists(website, question))
{
string err = mCulture == mJpLang
? $"ウェブサイト及び質問「{website}/{question}」向け秘密質問は既に存在します。"
: $"An secret question with the website and question for '{website}/{question}' already exists.";
throw new ArgumentException(err);
}
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
{
conn.Open();
SqliteCommand com = conn.CreateCommand();
com.CommandText = @"
INSERT INTO Qa (Website, Question, Answer, Note)
VALUES ($website, $question, $answer, $note)";
com.Parameters.AddWithValue("$website", website);
com.Parameters.AddWithValue("$question", question);
com.Parameters.AddWithValue("$answer", encryptedAnswer);
com.Parameters.AddWithValue("$note", string.IsNullOrEmpty(note) ? DBNull.Value : note);
com.ExecuteNonQuery();
}
}
public bool EditQa(int id, string website, string question, string answer, string note)
{
if (string.IsNullOrWhiteSpace(website) || string.IsNullOrWhiteSpace(question) || string.IsNullOrWhiteSpace(answer))
{
string err = mCulture == mJpLang
? "ウェブサイト、質問及び、回答を御入力下さい。"
: "Please fill in the website, question, and answer.";
throw new ArgumentException(err);
}
string encryptedAnswer = EncryptAnswer(answer);
if (AlreadyExists(website, question, id))
{
string err = mCulture == mJpLang
? $"ウェブサイト及び質問「{website}/{question}」向け秘密質問は既に存在します。"
: $"An secret question with the website and question for '{website}/{question}' already exists.";
throw new ArgumentException(err);
}
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
{
conn.Open();
SqliteCommand com = conn.CreateCommand();
com.CommandText = @"
UPDATE Qa
SET Website = $website, Question = $question, Answer = $answer, Note = $note
WHERE Id = $id";
com.Parameters.AddWithValue("$id", id);
com.Parameters.AddWithValue("$website", website);
com.Parameters.AddWithValue("$question", question);
com.Parameters.AddWithValue("$answer", encryptedAnswer);
com.Parameters.AddWithValue("$note", string.IsNullOrEmpty(note) ? DBNull.Value : note);
return com.ExecuteNonQuery() > 0;
}
}
public bool DeleteQa(int id)
{
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
{
conn.Open();
SqliteCommand com = conn.CreateCommand();
com.CommandText = "DELETE FROM Qa WHERE Id = $id";
com.Parameters.AddWithValue("$id", id);
return com.ExecuteNonQuery() > 0;
}
}
public List<(int Id, string Website, string Question, string Answer, string Note)> GetAll(string keyword = "")
{
var qas = new List<(int, string, string, string, string)>();
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
{
conn.Open();
SqliteCommand com = conn.CreateCommand();
if (string.IsNullOrWhiteSpace(keyword))
{
com.CommandText = @"
SELECT Id, Website, Question, Answer, Note FROM Qa
ORDER BY Website ASC";
}
else
{
com.CommandText = @"
SELECT Id, Website, Question, Answer, Note FROM Qa
WHERE Website LIKE @keyword OR Question LIKE @keyword
ORDER BY Website ASC";
com.Parameters.AddWithValue("@keyword", $"%{keyword}%");
}
using (SqliteDataReader reader = com.ExecuteReader())
{
while (reader.Read())
{
string encryptedAnswer = reader.GetString(3);
string decryptedAnswer = DecryptAnswer(encryptedAnswer);
qas.Add((
reader.GetInt32(0),
reader.GetString(1),
reader.GetString(2),
decryptedAnswer,
reader.IsDBNull(4) ? string.Empty : reader.GetString(4)
));
}
}
}
return qas;
}
private string EncryptAnswer(string answer)
{
using (Aes aes = Aes.Create())
{
aes.Key = mEncryptionKey;
aes.GenerateIV();
byte[] iv = aes.IV;
using (ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, iv))
{
byte[] plainBytes = Encoding.UTF8.GetBytes(answer);
byte[] encryptedBytes = encryptor.TransformFinalBlock(plainBytes, 0, plainBytes.Length);
byte[] result = new byte[iv.Length + encryptedBytes.Length];
Buffer.BlockCopy(iv, 0, result, 0, iv.Length);
Buffer.BlockCopy(encryptedBytes, 0, result, iv.Length, encryptedBytes.Length);
return Convert.ToBase64String(result);
}
}
}
private string DecryptAnswer(string encryptedAnswer)
{
byte[] combined = Convert.FromBase64String(encryptedAnswer);
byte[] iv = new byte[16];
byte[] encryptedBytes = new byte[combined.Length - iv.Length];
Buffer.BlockCopy(combined, 0, iv, 0, iv.Length);
Buffer.BlockCopy(combined, iv.Length, encryptedBytes, 0, encryptedBytes.Length);
using (Aes aes = Aes.Create())
{
aes.Key = mEncryptionKey;
aes.IV = iv;
using (ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV))
{
byte[] decryptedBytes = decryptor.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length);
return Encoding.UTF8.GetString(decryptedBytes);
}
}
}
}
}