Initial commit to new repository
This commit is contained in:
368
.decompiledproj/AxCopilot/Services/ChatStorageService.cs
Normal file
368
.decompiledproj/AxCopilot/Services/ChatStorageService.cs
Normal file
@@ -0,0 +1,368 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using AxCopilot.Models;
|
||||
|
||||
namespace AxCopilot.Services;
|
||||
|
||||
public class ChatStorageService
|
||||
{
|
||||
private static readonly string ConversationsDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "AxCopilot", "conversations");
|
||||
|
||||
private static readonly JsonSerializerOptions JsonOpts = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = false,
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
|
||||
private static readonly ReaderWriterLockSlim Lock = new ReaderWriterLockSlim();
|
||||
|
||||
private List<ChatConversation>? _metaCache;
|
||||
|
||||
private bool _metaDirty = true;
|
||||
|
||||
public ChatStorageService()
|
||||
{
|
||||
Directory.CreateDirectory(ConversationsDir);
|
||||
}
|
||||
|
||||
public void Save(ChatConversation conversation)
|
||||
{
|
||||
conversation.UpdatedAt = DateTime.Now;
|
||||
if (string.IsNullOrEmpty(conversation.Preview) && conversation.Messages.Count > 0)
|
||||
{
|
||||
ChatMessage chatMessage = conversation.Messages.FirstOrDefault((ChatMessage m) => m.Role == "user");
|
||||
if (chatMessage != null)
|
||||
{
|
||||
conversation.Preview = ((chatMessage.Content.Length > 100) ? chatMessage.Content.Substring(0, 100) : chatMessage.Content);
|
||||
}
|
||||
}
|
||||
string content = JsonSerializer.Serialize(conversation, JsonOpts);
|
||||
string filePath = GetFilePath(conversation.Id);
|
||||
string text = filePath + ".tmp";
|
||||
Lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
CryptoService.EncryptToFile(text, content);
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
File.Delete(filePath);
|
||||
}
|
||||
File.Move(text, filePath);
|
||||
UpdateMetaCache(conversation);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(text))
|
||||
{
|
||||
File.Delete(text);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
LogService.Warn("대화 저장 실패 (" + conversation.Id + "): " + ex.Message);
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
public ChatConversation? Load(string id)
|
||||
{
|
||||
string filePath = GetFilePath(id);
|
||||
Lock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
string json = CryptoService.DecryptFromFile(filePath);
|
||||
return JsonSerializer.Deserialize<ChatConversation>(json, JsonOpts);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Warn("대화 로드 실패 (" + id + "): " + ex.Message);
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Lock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
public void InvalidateMetaCache()
|
||||
{
|
||||
_metaDirty = true;
|
||||
}
|
||||
|
||||
public void UpdateMetaCache(ChatConversation conv)
|
||||
{
|
||||
if (_metaCache != null)
|
||||
{
|
||||
int num = _metaCache.FindIndex((ChatConversation c) => c.Id == conv.Id);
|
||||
ChatConversation chatConversation = new ChatConversation
|
||||
{
|
||||
Id = conv.Id,
|
||||
Title = conv.Title,
|
||||
CreatedAt = conv.CreatedAt,
|
||||
UpdatedAt = conv.UpdatedAt,
|
||||
Pinned = conv.Pinned,
|
||||
Category = conv.Category,
|
||||
Tab = conv.Tab,
|
||||
SystemCommand = conv.SystemCommand,
|
||||
WorkFolder = conv.WorkFolder,
|
||||
Preview = conv.Preview,
|
||||
Messages = new List<ChatMessage>(),
|
||||
ExecutionEvents = (conv.ExecutionEvents?.ToList() ?? new List<ChatExecutionEvent>())
|
||||
};
|
||||
if (num >= 0)
|
||||
{
|
||||
_metaCache[num] = chatConversation;
|
||||
}
|
||||
else
|
||||
{
|
||||
_metaCache.Add(chatConversation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveFromMetaCache(string id)
|
||||
{
|
||||
_metaCache?.RemoveAll((ChatConversation c) => c.Id == id);
|
||||
}
|
||||
|
||||
public List<ChatConversation> LoadAllMeta()
|
||||
{
|
||||
if (!_metaDirty && _metaCache != null)
|
||||
{
|
||||
return (from c in _metaCache
|
||||
orderby c.Pinned descending, c.UpdatedAt descending
|
||||
select c).ToList();
|
||||
}
|
||||
List<ChatConversation> list = new List<ChatConversation>();
|
||||
if (!Directory.Exists(ConversationsDir))
|
||||
{
|
||||
_metaCache = list;
|
||||
_metaDirty = false;
|
||||
return list;
|
||||
}
|
||||
Lock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
string[] files = Directory.GetFiles(ConversationsDir, "*.axchat");
|
||||
foreach (string text in files)
|
||||
{
|
||||
try
|
||||
{
|
||||
string json = CryptoService.DecryptFromFile(text);
|
||||
ChatConversation chatConversation = JsonSerializer.Deserialize<ChatConversation>(json, JsonOpts);
|
||||
if (chatConversation != null)
|
||||
{
|
||||
ChatConversation item = new ChatConversation
|
||||
{
|
||||
Id = chatConversation.Id,
|
||||
Title = chatConversation.Title,
|
||||
CreatedAt = chatConversation.CreatedAt,
|
||||
UpdatedAt = chatConversation.UpdatedAt,
|
||||
Pinned = chatConversation.Pinned,
|
||||
Category = chatConversation.Category,
|
||||
Tab = chatConversation.Tab,
|
||||
SystemCommand = chatConversation.SystemCommand,
|
||||
WorkFolder = chatConversation.WorkFolder,
|
||||
Preview = chatConversation.Preview,
|
||||
Messages = new List<ChatMessage>(),
|
||||
ExecutionEvents = (chatConversation.ExecutionEvents?.ToList() ?? new List<ChatExecutionEvent>())
|
||||
};
|
||||
list.Add(item);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Warn("대화 메타 로드 실패 (" + Path.GetFileName(text) + "): " + ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Lock.ExitReadLock();
|
||||
}
|
||||
_metaCache = list;
|
||||
_metaDirty = false;
|
||||
return (from c in list
|
||||
orderby c.Pinned descending, c.UpdatedAt descending
|
||||
select c).ToList();
|
||||
}
|
||||
|
||||
public void Delete(string id)
|
||||
{
|
||||
string filePath = GetFilePath(id);
|
||||
Lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
File.Delete(filePath);
|
||||
}
|
||||
RemoveFromMetaCache(id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Warn("대화 삭제 실패 (" + id + "): " + ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
public int DeleteAll()
|
||||
{
|
||||
if (!Directory.Exists(ConversationsDir))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
Lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
string[] files = Directory.GetFiles(ConversationsDir, "*.axchat");
|
||||
int num = 0;
|
||||
string[] array = files;
|
||||
foreach (string path in array)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(path);
|
||||
num++;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Warn("파일 삭제 실패 (" + Path.GetFileName(path) + "): " + ex.Message);
|
||||
}
|
||||
}
|
||||
InvalidateMetaCache();
|
||||
return num;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
public int PurgeExpired(int retentionDays)
|
||||
{
|
||||
if (retentionDays <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
DateTime dateTime = DateTime.Now.AddDays(-retentionDays);
|
||||
int num = 0;
|
||||
Lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
string[] files = Directory.GetFiles(ConversationsDir, "*.axchat");
|
||||
foreach (string text in files)
|
||||
{
|
||||
try
|
||||
{
|
||||
string json = CryptoService.DecryptFromFile(text);
|
||||
ChatConversation chatConversation = JsonSerializer.Deserialize<ChatConversation>(json, JsonOpts);
|
||||
if (chatConversation != null && !chatConversation.Pinned && chatConversation.UpdatedAt < dateTime)
|
||||
{
|
||||
File.Delete(text);
|
||||
RemoveFromMetaCache(chatConversation.Id);
|
||||
num++;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Warn("만료 대화 정리 실패 (" + Path.GetFileName(text) + "): " + ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Lock.ExitWriteLock();
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
public int PurgeForDiskSpace(double threshold = 0.98)
|
||||
{
|
||||
try
|
||||
{
|
||||
DriveInfo driveInfo = new DriveInfo(Path.GetPathRoot(ConversationsDir) ?? "C");
|
||||
if (driveInfo.TotalSize <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
double num = 1.0 - (double)driveInfo.AvailableFreeSpace / (double)driveInfo.TotalSize;
|
||||
if (num < threshold)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
LogService.Info($"드라이브 사용률 {num:P1} — 대화 정리 시작 (임계값: {threshold:P0})");
|
||||
var list = (from f in Directory.GetFiles(ConversationsDir, "*.axchat")
|
||||
select new
|
||||
{
|
||||
Path = f,
|
||||
LastWrite = File.GetLastWriteTime(f)
|
||||
} into f
|
||||
orderby f.LastWrite
|
||||
select f).ToList();
|
||||
int num2 = 0;
|
||||
Lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
foreach (var item in list)
|
||||
{
|
||||
double num3 = 1.0 - (double)driveInfo.AvailableFreeSpace / (double)driveInfo.TotalSize;
|
||||
if (num3 < threshold - 0.02)
|
||||
{
|
||||
break;
|
||||
}
|
||||
try
|
||||
{
|
||||
string json = CryptoService.DecryptFromFile(item.Path);
|
||||
ChatConversation chatConversation = JsonSerializer.Deserialize<ChatConversation>(json, JsonOpts);
|
||||
if (chatConversation == null || !chatConversation.Pinned)
|
||||
{
|
||||
File.Delete(item.Path);
|
||||
num2++;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Lock.ExitWriteLock();
|
||||
}
|
||||
if (num2 > 0)
|
||||
{
|
||||
LogService.Info($"드라이브 용량 부족으로 대화 {num2}개 삭제 완료");
|
||||
}
|
||||
return num2;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Warn("디스크 용량 확인 실패: " + ex.Message);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetFilePath(string id)
|
||||
{
|
||||
return Path.Combine(ConversationsDir, id + ".axchat");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user