-
Notifications
You must be signed in to change notification settings - Fork 1
/
DataGetter.cs
109 lines (89 loc) · 3.76 KB
/
DataGetter.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
using System;
using System.Linq;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.IO.Compression;
using System.Collections.Generic;
namespace SongDetailsCache {
static class DataGetter {
public static readonly IReadOnlyDictionary<string, string> dataSources = new Dictionary<string, string>() {
{ "Direct", "https://raw.githubusercontent.com/kinsi55/BeatSaberScrappedData/master/songDetails2_v3.gz" },
// Caches stuff for 12 hours as backup
{ "JSDelivr", "https://cdn.jsdelivr.net/gh/kinsi55/BeatSaberScrappedData/songDetails2_v3.gz" }
};
private static HttpClient client = null;
public static string basePath = Path.Combine(Environment.CurrentDirectory, "UserData");
public static string cachePath => Path.Combine(basePath, "SongDetailsCache.proto");
public static string cachePathEtag(string source) => Path.Combine(basePath, $"SongDetailsCache.proto.{source}.etag");
public class DownloadedDatabase {
public string source;
public string etag;
public MemoryStream stream;
}
public static async Task<DownloadedDatabase> UpdateAndReadDatabase(string dataSourceName = "Direct") {
if(client == null) {
client = new HttpClient(new HttpClientHandler() {
AutomaticDecompression = DecompressionMethods.None,
AllowAutoRedirect = false
});
client.DefaultRequestHeaders.ConnectionClose = true;
// For some reason this can throw some times?!
try {
client.Timeout = TimeSpan.FromSeconds(20);
} catch { }
}
dataSourceName = dataSources.Keys.FirstOrDefault(x => x == dataSourceName) ?? dataSources.Keys.First();
var dataSource = dataSources[dataSourceName];
using(var req = new HttpRequestMessage(HttpMethod.Get, dataSource)) {
try {
if(File.Exists(cachePathEtag(dataSourceName)))
req.Headers.Add("If-None-Match", File.ReadAllText(cachePathEtag(dataSourceName)));
} catch { }
using(var resp = await client.SendAsync(req, HttpCompletionOption.ResponseHeadersRead)) {
if(resp.StatusCode == HttpStatusCode.NotModified)
return null;
if(resp.StatusCode != HttpStatusCode.OK)
throw new Exception($"Got unexpected HTTP response: {resp.StatusCode} {resp.ReasonPhrase}");
using(var stream = await resp.Content.ReadAsStreamAsync()) {
var fs = new MemoryStream();
//using(var decompressor = new BrotliStream(stream, CompressionMode.Decompress))
// await decompressor.CopyToAsync(fs);
//stream.DecompressFromBrotli(fs);
using(var decompressed = new GZipStream(stream, CompressionMode.Decompress))
await decompressed.CopyToAsync(fs);
//Returning the file handle so we can end the HTTP request
fs.Position = 0;
return new DownloadedDatabase() {
source = dataSourceName,
etag = resp.Headers.ETag.Tag,
stream = fs
};
}
}
}
}
public static async Task WriteCachedDatabase(DownloadedDatabase db) {
//Using create here so that a possibly existing file (And handles to it) are kept intact
using(var fs = new FileStream(cachePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read | FileShare.Delete, 8192, true)) {
fs.Position = 0;
db.stream.Position = 0;
await db.stream.CopyToAsync(fs);
fs.SetLength(db.stream.Length);
}
File.WriteAllText(cachePathEtag(db.source), db.etag);
}
public static Stream ReadCachedDatabase() {
if(!File.Exists(cachePath))
return null;
return new FileStream(cachePath, FileMode.Open, FileAccess.Read, FileShare.Delete | FileShare.Read);
}
public static bool HasCachedData(int maximumAgeHours = 12) {
if(!File.Exists(cachePath))
return false;
FileInfo fInfo = new FileInfo(cachePath);
return fInfo.LastWriteTime > DateTime.Now - TimeSpan.FromHours(maximumAgeHours);
}
}
}