WeddingPlatform.Web 1.0.0.0
지도, 갤러리, 방명록, 계좌 안내와 배경음악을 링크 하나에 담는 무료 모바일 청첩장 서비스입니다.
로딩중...
검색중...
일치하는것 없음
CsvGuestbookStorage.cs
이 파일의 문서화 페이지로 가기
1using System.Globalization;
2using System.IO;
3using System.Text;
5
7
17{
26 private readonly ITenantStore _tenants;
35 private static readonly SemaphoreSlim _gate = new(1, 1);
36
53 public CsvGuestbookStorage(ITenantStore tenants) => _tenants = tenants;
54
87 public async Task<IReadOnlyList<GuestbookEntry>> LoadAsync(string slug, CancellationToken ct = default)
88 {
89 var path = CsvPath(slug);
90 await _gate.WaitAsync(ct).ConfigureAwait(false);
91 try
92 {
93 var result = new List<GuestbookEntry>();
94 if (!File.Exists(path)) return result;
95
96 using var sr = new StreamReader(path, Encoding.UTF8);
97 bool first = true;
98 string? line;
99 while ((line = await sr.ReadLineAsync().ConfigureAwait(false)) != null)
100 {
101 if (string.IsNullOrWhiteSpace(line)) continue;
102 if (first && line.TrimStart().StartsWith("Name,")) { first = false; continue; }
103 first = false;
104
105 var f = ParseCsvLine(line);
106 if (f.Count < 4) continue;
107 if (!DateTime.TryParseExact(f[3], "yyyy-MM-dd HH:mm:ss",
108 CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out var when))
109 when = DateTime.Now;
110
111 result.Add(new GuestbookEntry { Name = f[0], Contact = f[1], Message = f[2], CreatedLocal = when });
112 }
113 return result;
114 }
115 finally { _gate.Release(); }
116 }
117
158 public async Task SaveAsync(string slug, IReadOnlyList<GuestbookEntry> entries, CancellationToken ct = default)
159 {
160 var path = CsvPath(slug);
161 Directory.CreateDirectory(Path.GetDirectoryName(path)!);
162 var tmp = path + ".tmp";
163
164 await _gate.WaitAsync(ct).ConfigureAwait(false);
165 try
166 {
167 using (var sw = new StreamWriter(tmp, false, new UTF8Encoding(false)))
168 {
169 await sw.WriteLineAsync("Name,Contact,Message,CreatedLocal").ConfigureAwait(false);
170 foreach (var e in entries)
171 {
172 ct.ThrowIfCancellationRequested();
173 await sw.WriteLineAsync(string.Join(",",
174 Esc(e.Name), Esc(e.Contact), Esc(e.Message),
175 Esc(e.CreatedLocal.ToString("yyyy-MM-dd HH:mm:ss")))).ConfigureAwait(false);
176 }
177 }
178 File.Copy(tmp, path, overwrite: true);
179 File.Delete(tmp);
180 }
181 finally { _gate.Release(); }
182 }
183
208 private string CsvPath(string slug) =>
209 Path.Combine(_tenants.GetTenantDataPath(slug), "guestbook.csv");
210
235 private static string Esc(string v)
236 {
237 if (v.Contains(',') || v.Contains('"') || v.Contains('\n'))
238 return $"\"{v.Replace("\"", "\"\"")}\"";
239 return v;
240 }
241
266 private static List<string> ParseCsvLine(string line)
267 {
268 var list = new List<string>();
269 var sb = new StringBuilder();
270 bool inQ = false;
271 for (int i = 0; i < line.Length; i++)
272 {
273 var ch = line[i];
274 if (inQ) { if (ch == '"') { if (i + 1 < line.Length && line[i + 1] == '"') { sb.Append('"'); i++; } else inQ = false; } else sb.Append(ch); }
275 else { if (ch == ',') { list.Add(sb.ToString()); sb.Clear(); } else if (ch == '"') inQ = true; else sb.Append(ch); }
276 }
277 list.Add(sb.ToString());
278 return list;
279 }
280}
async Task SaveAsync(string slug, IReadOnlyList< GuestbookEntry > entries, CancellationToken ct=default)
async Task< IReadOnlyList< GuestbookEntry > > LoadAsync(string slug, CancellationToken ct=default)
static List< string > ParseCsvLine(string line)