1 | #include "serverbrowser_ping_cache.h" |
2 | |
3 | #include <base/system.h> |
4 | #include <engine/console.h> |
5 | #include <engine/sqlite.h> |
6 | |
7 | #include <sqlite3.h> |
8 | |
9 | #include <unordered_map> |
10 | #include <vector> |
11 | |
12 | class CServerBrowserPingCache : public IServerBrowserPingCache |
13 | { |
14 | public: |
15 | class CEntry |
16 | { |
17 | public: |
18 | NETADDR m_Addr; |
19 | int m_Ping; |
20 | }; |
21 | |
22 | CServerBrowserPingCache(IConsole *pConsole, IStorage *pStorage); |
23 | ~CServerBrowserPingCache() override = default; |
24 | |
25 | void Load() override; |
26 | |
27 | int NumEntries() const override; |
28 | void CachePing(const NETADDR &Addr, int Ping) override; |
29 | int GetPing(const NETADDR *pAddrs, int NumAddrs) const override; |
30 | |
31 | private: |
32 | IConsole *m_pConsole; |
33 | |
34 | CSqlite m_pDisk; |
35 | CSqliteStmt m_pLoadStmt; |
36 | CSqliteStmt m_pStoreStmt; |
37 | |
38 | std::unordered_map<NETADDR, int> m_Entries; |
39 | }; |
40 | |
41 | CServerBrowserPingCache::CServerBrowserPingCache(IConsole *pConsole, IStorage *pStorage) : |
42 | m_pConsole(pConsole) |
43 | { |
44 | m_pDisk = SqliteOpen(pConsole, pStorage, pPath: "ddnet-cache.sqlite3" ); |
45 | if(!m_pDisk) |
46 | { |
47 | pConsole->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "serverbrowse_ping_cache" , pStr: "failed to open ddnet-cache.sqlite3" ); |
48 | return; |
49 | } |
50 | sqlite3 *pSqlite = m_pDisk.get(); |
51 | static const char TABLE[] = "CREATE TABLE IF NOT EXISTS server_pings (ip_address TEXT PRIMARY KEY NOT NULL, ping INTEGER NOT NULL, utc_timestamp TEXT NOT NULL)" ; |
52 | if(SQLITE_HANDLE_ERROR(sqlite3_exec(pSqlite, TABLE, nullptr, nullptr, nullptr))) |
53 | { |
54 | m_pDisk = nullptr; |
55 | pConsole->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "serverbrowse_ping_cache" , pStr: "failed to create server_pings table" ); |
56 | return; |
57 | } |
58 | m_pLoadStmt = SqlitePrepare(pConsole, pSqlite, pStatement: "SELECT ip_address, ping FROM server_pings" ); |
59 | m_pStoreStmt = SqlitePrepare(pConsole, pSqlite, pStatement: "INSERT OR REPLACE INTO server_pings (ip_address, ping, utc_timestamp) VALUES (?, ?, datetime('now'))" ); |
60 | } |
61 | |
62 | void CServerBrowserPingCache::Load() |
63 | { |
64 | if(m_pDisk) |
65 | { |
66 | std::vector<CEntry> vNewEntries; |
67 | |
68 | sqlite3 *pSqlite = m_pDisk.get(); |
69 | IConsole *pConsole = m_pConsole; |
70 | bool Error = false; |
71 | bool WarnedForBadAddress = false; |
72 | Error = Error || !m_pLoadStmt; |
73 | while(!Error) |
74 | { |
75 | int StepResult = SQLITE_HANDLE_ERROR(sqlite3_step(m_pLoadStmt.get())); |
76 | if(StepResult == SQLITE_DONE) |
77 | { |
78 | break; |
79 | } |
80 | else if(StepResult == SQLITE_ROW) |
81 | { |
82 | const char *pIpAddress = (const char *)sqlite3_column_text(m_pLoadStmt.get(), iCol: 0); |
83 | int Ping = sqlite3_column_int(m_pLoadStmt.get(), iCol: 1); |
84 | NETADDR Addr; |
85 | if(net_addr_from_str(addr: &Addr, string: pIpAddress)) |
86 | { |
87 | if(!WarnedForBadAddress) |
88 | { |
89 | char aBuf[64]; |
90 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "invalid address: %s" , pIpAddress); |
91 | pConsole->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "serverbrowse_ping_cache" , pStr: aBuf); |
92 | WarnedForBadAddress = true; |
93 | } |
94 | continue; |
95 | } |
96 | vNewEntries.push_back(x: CEntry{.m_Addr: Addr, .m_Ping: Ping}); |
97 | } |
98 | else |
99 | { |
100 | Error = true; |
101 | } |
102 | } |
103 | if(Error) |
104 | { |
105 | pConsole->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "serverbrowse_ping_cache" , pStr: "failed to load ping cache" ); |
106 | return; |
107 | } |
108 | for(const auto &Entry : vNewEntries) |
109 | { |
110 | m_Entries[Entry.m_Addr] = Entry.m_Ping; |
111 | } |
112 | } |
113 | } |
114 | |
115 | int CServerBrowserPingCache::NumEntries() const |
116 | { |
117 | return m_Entries.size(); |
118 | } |
119 | |
120 | void CServerBrowserPingCache::CachePing(const NETADDR &Addr, int Ping) |
121 | { |
122 | NETADDR AddrWithoutPort = Addr; |
123 | AddrWithoutPort.port = 0; |
124 | m_Entries[AddrWithoutPort] = Ping; |
125 | if(m_pDisk) |
126 | { |
127 | sqlite3 *pSqlite = m_pDisk.get(); |
128 | IConsole *pConsole = m_pConsole; |
129 | char aAddr[NETADDR_MAXSTRSIZE]; |
130 | net_addr_str(addr: &AddrWithoutPort, string: aAddr, max_length: sizeof(aAddr), add_port: false); |
131 | |
132 | bool Error = false; |
133 | Error = Error || !m_pStoreStmt; |
134 | Error = Error || SQLITE_HANDLE_ERROR(sqlite3_reset(m_pStoreStmt.get())) != SQLITE_OK; |
135 | Error = Error || SQLITE_HANDLE_ERROR(sqlite3_bind_text(m_pStoreStmt.get(), 1, aAddr, -1, SQLITE_STATIC)) != SQLITE_OK; |
136 | Error = Error || SQLITE_HANDLE_ERROR(sqlite3_bind_int(m_pStoreStmt.get(), 2, Ping)) != SQLITE_OK; |
137 | Error = Error || SQLITE_HANDLE_ERROR(sqlite3_step(m_pStoreStmt.get())) != SQLITE_DONE; |
138 | if(Error) |
139 | { |
140 | pConsole->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "serverbrowse_ping_cache" , pStr: "failed to store ping" ); |
141 | } |
142 | } |
143 | } |
144 | |
145 | int CServerBrowserPingCache::GetPing(const NETADDR *pAddrs, int NumAddrs) const |
146 | { |
147 | int Ping = -1; |
148 | for(int i = 0; i < NumAddrs; i++) |
149 | { |
150 | NETADDR Addr = pAddrs[i]; |
151 | Addr.port = 0; |
152 | auto Entry = m_Entries.find(x: Addr); |
153 | if(Entry == m_Entries.end()) |
154 | { |
155 | continue; |
156 | } |
157 | if(Ping == -1 || Entry->second < Ping) |
158 | { |
159 | Ping = Entry->second; |
160 | } |
161 | } |
162 | return Ping; |
163 | } |
164 | |
165 | IServerBrowserPingCache *CreateServerBrowserPingCache(IConsole *pConsole, IStorage *pStorage) |
166 | { |
167 | return new CServerBrowserPingCache(pConsole, pStorage); |
168 | } |
169 | |