| 172 | } |
| 173 | |
| 174 | func (c *Cache) flushBatch(batch []*Location) { |
| 175 | if len(batch) == 0 { |
| 176 | return |
| 177 | } |
| 178 | |
| 179 | c.writeMu.Lock() |
| 180 | defer c.writeMu.Unlock() |
| 181 | |
| 182 | start := time.Now() |
| 183 | defer func() { |
| 184 | log.Infof("flushed batch of %d locations in %v", len(batch), time.Since(start)) |
| 185 | }() |
| 186 | |
| 187 | // Begin transaction (godb style) |
| 188 | if err := c.godbWrite.Begin(); err != nil { |
| 189 | log.Warnln("batch writer: failed to begin transaction:", err) |
| 190 | return |
| 191 | } |
| 192 | |
| 193 | defer func() { |
| 194 | if r := recover(); r != nil { |
| 195 | c.godbWrite.Rollback() |
| 196 | panic(r) |
| 197 | } |
| 198 | }() |
| 199 | |
| 200 | stmt, err := c.godbWrite.CurrentDB().Prepare(` |
| 201 | INSERT INTO Location (name, displayName, lat, lon, timezone) |
| 202 | VALUES (?, ?, ?, ?, ?) |
| 203 | ON CONFLICT(name) DO UPDATE SET |
| 204 | displayName = excluded.displayName, |
| 205 | lat = excluded.lat, |
| 206 | lon = excluded.lon, |
| 207 | timezone = excluded.timezone |
| 208 | `) |
| 209 | if err != nil { |
| 210 | log.Warnln("batch writer: prepare failed:", err) |
| 211 | c.godbWrite.Rollback() |
| 212 | return |
| 213 | } |
| 214 | defer stmt.Close() |
| 215 | |
| 216 | for _, loc := range batch { |
| 217 | if _, err = stmt.Exec(loc.Name, loc.Fullname, loc.Lat, loc.Lon, loc.Timezone); err != nil { |
| 218 | log.Warnf("batch writer: failed to write %s: %v", loc.Name, err) |
| 219 | } |
| 220 | } |
| 221 | |
| 222 | if err := c.godbWrite.Commit(); err != nil { |
| 223 | log.Warnln("batch writer: commit failed:", err) |
| 224 | c.godbWrite.Rollback() // safety |
| 225 | } |
| 226 | } |
| 227 | |
| 228 | // Resolve returns location data — first from cache (LRU → persistent), then from external search. |
| 229 | func (c *Cache) Resolve(location string) (*Location, error) { |