using DysonNetwork.Shared.Models; using Microsoft.EntityFrameworkCore; namespace DysonNetwork.Drive.Index; public class FileIndexService(AppDatabase db) { /// /// Creates a new file index entry /// /// The parent folder path with a trailing slash /// The file ID /// The account ID /// The created file index public async Task CreateAsync(string path, string fileId, Guid accountId) { // Ensure a path has a trailing slash and is query-safe var normalizedPath = NormalizePath(path); // Check if a file with the same name already exists in the same path for this account var existingFileIndex = await db.FileIndexes .FirstOrDefaultAsync(fi => fi.AccountId == accountId && fi.Path == normalizedPath && fi.FileId == fileId); if (existingFileIndex != null) { throw new InvalidOperationException( $"A file with ID '{fileId}' already exists in path '{normalizedPath}' for account '{accountId}'"); } var fileIndex = new SnCloudFileIndex { Path = normalizedPath, FileId = fileId, AccountId = accountId }; db.FileIndexes.Add(fileIndex); await db.SaveChangesAsync(); return fileIndex; } /// /// Updates an existing file index entry by removing the old one and creating a new one /// /// The file index ID /// The new parent folder path with trailing slash /// The updated file index public async Task UpdateAsync(Guid id, string newPath) { var fileIndex = await db.FileIndexes.FindAsync(id); if (fileIndex == null) return null; // Since properties are init-only, we need to remove the old index and create a new one db.FileIndexes.Remove(fileIndex); var newFileIndex = new SnCloudFileIndex { Path = NormalizePath(newPath), FileId = fileIndex.FileId, AccountId = fileIndex.AccountId }; db.FileIndexes.Add(newFileIndex); await db.SaveChangesAsync(); return newFileIndex; } /// /// Removes a file index entry by ID /// /// The file index ID /// True if the index was found and removed, false otherwise public async Task RemoveAsync(Guid id) { var fileIndex = await db.FileIndexes.FindAsync(id); if (fileIndex == null) return false; db.FileIndexes.Remove(fileIndex); await db.SaveChangesAsync(); return true; } /// /// Removes file index entries by file ID /// /// The file ID /// The number of indexes removed public async Task RemoveByFileIdAsync(string fileId) { var indexes = await db.FileIndexes .Where(fi => fi.FileId == fileId) .ToListAsync(); if (indexes.Count == 0) return 0; db.FileIndexes.RemoveRange(indexes); await db.SaveChangesAsync(); return indexes.Count; } /// /// Removes file index entries by account ID and path /// /// The account ID /// The parent folder path /// The number of indexes removed public async Task RemoveByPathAsync(Guid accountId, string path) { var normalizedPath = NormalizePath(path); var indexes = await db.FileIndexes .Where(fi => fi.AccountId == accountId && fi.Path == normalizedPath) .ToListAsync(); if (!indexes.Any()) return 0; db.FileIndexes.RemoveRange(indexes); await db.SaveChangesAsync(); return indexes.Count; } /// /// Gets file indexes by account ID and path /// /// The account ID /// The parent folder path /// List of file indexes public async Task> GetByPathAsync(Guid accountId, string path) { var normalizedPath = NormalizePath(path); return await db.FileIndexes .Where(fi => fi.AccountId == accountId && fi.Path == normalizedPath) .Include(fi => fi.File) .ToListAsync(); } /// /// Gets file indexes by file ID /// /// The file ID /// List of file indexes public async Task> GetByFileIdAsync(string fileId) { return await db.FileIndexes .Where(fi => fi.FileId == fileId) .Include(fi => fi.File) .ToListAsync(); } /// /// Gets all file indexes for an account /// /// The account ID /// List of file indexes public async Task> GetByAccountIdAsync(Guid accountId) { return await db.FileIndexes .Where(fi => fi.AccountId == accountId) .Include(fi => fi.File) .ToListAsync(); } /// /// Normalizes the path to ensure it has a trailing slash and is query-safe /// /// The original path /// The normalized path public static string NormalizePath(string path) { if (string.IsNullOrEmpty(path)) return "/"; // Ensure the path starts with a slash if (!path.StartsWith('/')) path = "/" + path; // Ensure the path ends with a slash (unless it's just the root) if (path != "/" && !path.EndsWith('/')) path += "/"; // Make path query-safe by removing problematic characters // This is a basic implementation - you might want to add more robust validation path = path.Replace("%", "").Replace("'", "").Replace("\"", ""); return path; } }