//! The file cache: a machine-local shortcut so `status` and `save` can skip //! files whose size, mtime and ctime are unchanged. It caches the working //! tree, never history, and is safe to drop at any time. module storage pub struct CacheRow { pub: path string size i64 mtime_ns i64 ctime_ns i64 hash string } pub fn (s &Store) load_cache() !map[string]CacheRow { mut rows := map[string]CacheRow{} mut stmt := s.db.prepare('SELECT path, size, mtime_ns, ctime_ns, hash FROM file_cache')! defer { stmt.finalize() } for stmt.step()! { row := CacheRow{ path: stmt.text(0) size: stmt.int(1) mtime_ns: stmt.int(2) ctime_ns: stmt.int(3) hash: stmt.text(4) } rows[row.path] = row } return rows } // save_cache_rows writes a walk's rows in one transaction, without waiting for // another writer: the cache only saves time, so while another command holds // the lock the rows are simply not written. pub fn (s &Store) save_cache_rows(rows []CacheRow) ! { if rows.len == 0 { return } s.db.exec('PRAGMA busy_timeout = 0')! defer { s.db.exec('PRAGMA busy_timeout = ${busy_timeout_ms}') or {} } s.db.exec('BEGIN IMMEDIATE')! for row in rows { s.save_cache(row) or { s.rollback() return err } } s.commit()! } fn (s &Store) save_cache(row CacheRow) ! { mut stmt := s.db.prepare('INSERT INTO file_cache (path, size, mtime_ns, ctime_ns, hash) VALUES (?, ?, ?, ?, ?) ON CONFLICT(path) DO UPDATE SET size = excluded.size, mtime_ns = excluded.mtime_ns, ctime_ns = excluded.ctime_ns, hash = excluded.hash')! defer { stmt.finalize() } stmt.bind_text(1, row.path)! stmt.bind_int(2, row.size)! stmt.bind_int(3, row.mtime_ns)! stmt.bind_int(4, row.ctime_ns)! stmt.bind_text(5, row.hash)! stmt.step()! } pub fn (s &Store) drop_cache() ! { s.db.exec('DELETE FROM file_cache')! } pub fn (s &Store) cache_count() !i64 { mut stmt := s.db.prepare('SELECT COUNT(*) FROM file_cache')! defer { stmt.finalize() } if !stmt.step()! { return 0 } return stmt.int(0) }