TH1/Unity/Assets/Scripts/TH1_Logic/Questionnaire/QuestionnaireAnswerStore.cs

286 lines
9.1 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using TH1_Logic.Tools;
using UnityEngine;
namespace TH1_Logic.Questionnaire
{
[Serializable]
public class QuestionnaireAnswerArchive
{
public string Schema = "th1.questionnaire-answer-archive.v2";
public int SchemaVersion = 2;
public List<QuestionnaireAnswerSheet> Submissions = new List<QuestionnaireAnswerSheet>();
[Obsolete("Use Submissions. Kept for reading v1 local archives.")]
public List<QuestionnaireAnswerSheet> Sheets = new List<QuestionnaireAnswerSheet>();
}
[Serializable]
public class QuestionnaireAnswerSheet
{
public string ResponseId;
public string QuestionnaireId;
public int QuestionnaireRevision = 1;
public long SubmittedAtUnix;
public string SubmittedAtUtc;
public string LastUploadObjectKey;
public string LastUploadedAtUtc;
public string LastUploadError;
public string ClientVersion;
public QuestionnaireDefinitionSnapshot DefinitionSnapshot;
public List<QuestionnaireAnswer> Answers = new List<QuestionnaireAnswer>();
}
[Serializable]
public class QuestionnaireAnswer
{
public string QuestionId;
public QuestionnaireQuestionType QuestionType;
public List<string> SelectedOptionIds = new List<string>();
public string OpenText;
}
[Serializable]
public class QuestionnaireDefinitionSnapshot
{
public string QuestionnaireId;
public int Revision;
public string Title;
public string Description;
public List<QuestionnaireQuestionSnapshot> Questions = new List<QuestionnaireQuestionSnapshot>();
}
[Serializable]
public class QuestionnaireQuestionSnapshot
{
public string QuestionId;
public QuestionnaireQuestionType QuestionType;
public string Title;
public string Hint;
public bool Required;
public List<QuestionnaireOptionSnapshot> Options = new List<QuestionnaireOptionSnapshot>();
}
[Serializable]
public class QuestionnaireOptionSnapshot
{
public string OptionId;
public string Text;
}
public class QuestionnaireAnswerStore
{
public static readonly QuestionnaireAnswerStore Instance = new QuestionnaireAnswerStore();
private const string FileName = "questionnaire_answers.json";
private QuestionnaireAnswerArchive _archive;
public bool HasSubmitted(string questionnaireId)
{
return GetAnswerSheet(questionnaireId) != null;
}
public QuestionnaireAnswerSheet GetAnswerSheet(string questionnaireId)
{
return GetLatestAnswerSheet(questionnaireId);
}
public QuestionnaireAnswerSheet GetLatestAnswerSheet(string questionnaireId)
{
if (string.IsNullOrEmpty(questionnaireId)) return null;
EnsureLoaded();
QuestionnaireAnswerSheet latest = null;
foreach (var sheet in _archive.Submissions)
{
if (sheet == null || sheet.QuestionnaireId != questionnaireId) continue;
if (latest == null || sheet.SubmittedAtUnix > latest.SubmittedAtUnix)
{
latest = sheet;
}
}
return latest;
}
public List<QuestionnaireAnswerSheet> GetAnswerSheets(string questionnaireId)
{
EnsureLoaded();
var result = new List<QuestionnaireAnswerSheet>();
if (string.IsNullOrEmpty(questionnaireId)) return result;
foreach (var sheet in _archive.Submissions)
{
if (sheet != null && sheet.QuestionnaireId == questionnaireId)
{
result.Add(sheet);
}
}
result.Sort((left, right) => right.SubmittedAtUnix.CompareTo(left.SubmittedAtUnix));
return result;
}
public IReadOnlyList<QuestionnaireAnswerSheet> GetAllAnswerSheets()
{
EnsureLoaded();
return _archive.Submissions;
}
public bool SaveAnswerSheet(QuestionnaireAnswerSheet sheet)
{
if (sheet == null || string.IsNullOrEmpty(sheet.QuestionnaireId)) return false;
EnsureLoaded();
var now = DateTimeOffset.UtcNow;
sheet.SubmittedAtUnix = now.ToUnixTimeSeconds();
sheet.SubmittedAtUtc = now.UtcDateTime.ToString("o");
if (string.IsNullOrEmpty(sheet.ResponseId))
{
sheet.ResponseId = Guid.NewGuid().ToString("N");
}
var index = _archive.Submissions.FindIndex(item => item != null && item.ResponseId == sheet.ResponseId);
if (index >= 0)
{
_archive.Submissions[index] = sheet;
}
else
{
_archive.Submissions.Add(sheet);
}
return SaveArchive();
}
public bool SaveUploadResult(string questionnaireId, bool uploaded, string objectKey, string error)
{
var sheet = GetLatestAnswerSheet(questionnaireId);
return sheet != null && SaveUploadResultByResponseId(sheet.ResponseId, uploaded, objectKey, error);
}
public bool SaveUploadResultByResponseId(string responseId, bool uploaded, string objectKey, string error)
{
if (string.IsNullOrEmpty(responseId)) return false;
EnsureLoaded();
QuestionnaireAnswerSheet sheet = null;
foreach (var item in _archive.Submissions)
{
if (item == null || item.ResponseId != responseId) continue;
sheet = item;
break;
}
if (sheet == null) return false;
if (uploaded)
{
sheet.LastUploadObjectKey = objectKey ?? string.Empty;
sheet.LastUploadedAtUtc = DateTime.UtcNow.ToString("o");
sheet.LastUploadError = string.Empty;
}
else
{
sheet.LastUploadError = error ?? string.Empty;
}
return SaveArchive();
}
private void EnsureLoaded()
{
if (_archive != null) return;
_archive = new QuestionnaireAnswerArchive();
var path = GetArchivePath();
if (!File.Exists(path)) return;
try
{
var json = File.ReadAllText(path, Encoding.UTF8);
var loaded = JsonUtility.FromJson<QuestionnaireAnswerArchive>(json);
if (loaded != null)
{
_archive = loaded;
NormalizeArchive();
}
}
catch (Exception e)
{
Debug.LogError($"[QuestionnaireAnswerStore] Failed to read {path}: {e.Message}");
}
}
private void NormalizeArchive()
{
_archive.Schema = "th1.questionnaire-answer-archive.v2";
_archive.SchemaVersion = 2;
if (_archive.Submissions == null)
{
_archive.Submissions = new List<QuestionnaireAnswerSheet>();
}
#pragma warning disable CS0618
if ((_archive.Submissions.Count == 0) && _archive.Sheets != null && _archive.Sheets.Count > 0)
{
_archive.Submissions.AddRange(_archive.Sheets);
}
_archive.Sheets = new List<QuestionnaireAnswerSheet>();
#pragma warning restore CS0618
foreach (var sheet in _archive.Submissions)
{
if (sheet == null) continue;
if (string.IsNullOrEmpty(sheet.ResponseId))
{
sheet.ResponseId = Guid.NewGuid().ToString("N");
}
if (sheet.QuestionnaireRevision <= 0)
{
sheet.QuestionnaireRevision = 1;
}
if (sheet.Answers == null)
{
sheet.Answers = new List<QuestionnaireAnswer>();
}
foreach (var answer in sheet.Answers)
{
if (answer != null && answer.SelectedOptionIds == null)
{
answer.SelectedOptionIds = new List<string>();
}
}
}
}
private bool SaveArchive()
{
try
{
NormalizeArchive();
var json = JsonUtility.ToJson(_archive, true);
var bytes = Encoding.UTF8.GetBytes(json);
return FileTools.SafeWriteFile(GetArchivePath(), bytes);
}
catch (Exception e)
{
Debug.LogError($"[QuestionnaireAnswerStore] Failed to save answers: {e.Message}");
return false;
}
}
private static string GetArchivePath()
{
return Path.Combine(Application.persistentDataPath, "../Config", FileName);
}
}
}