Class: Pikuri::Thunderbird::Gloda
- Inherits:
-
Object
- Object
- Pikuri::Thunderbird::Gloda
- Defined in:
- lib/pikuri/thunderbird/gloda.rb,
lib/pikuri/thunderbird/gloda/mail.rb,
lib/pikuri/thunderbird/gloda/contacts.rb
Overview
Provides queryable access to Thunderbird's Gloda index (+global-messages-db.sqlite+) — a pre-decoded corpus: Gloda has already MIME-decoded, HTML-stripped, and split every message into body / subject / attachment-names / author / recipients, and keeps it fresh for free. This is the service: it snapshots the locked live file, builds an ephemeral FTS5 index into the copy, tracks freshness, and hands out a current connection — but runs no queries itself. Two views do the querying:
gloda = Gloda.new(gloda_path: profile.gloda_path, cache_dir: dir)
gloda.available? # is the Gloda file present?
Gloda::Mail.new(gloda: gloda) # mail search + read
Gloda::Contacts.new(gloda: gloda) # name → address + novelty
gloda.close # closes the snapshot connection
A view queries through #with_fresh_db; it never caches the connection
(a rebuild reopens it). pikuri never touches the locked live file — it
works against a DatabaseSnapshot copy and builds its own FTS5 index into that
copy (Gloda's own messagesText is an fts3 vtable behind the custom
mozporter tokenizer, which the sqlite3 gem can't open).
This service is specific to Gloda's schema — the FTS5 index it builds is
over Gloda's decoded columns (+c0body+ … c4recipients), so it is not a
generic SQLite handle and does not serve the calendar DBs (Calendar has
its own DatabaseSnapshot + backend).
Freshness & consistency (one signal)
Each #with_fresh_db first compares the live Gloda change counter against the generation the current copy+index were built from; if it moved, a fresh DatabaseSnapshot is taken and the FTS5 index rebuilt (~1 s). Between changes it's a 4-byte read and a no-op. The counter bumps on any Gloda write — including read/star/tag flips during triage — so a rebuild can fire mid-conversation (desirable: the views then see new mail). See DatabaseSnapshot.
Holds no external state beyond its own snapshot + connection, but #close is required for orderly teardown.
Sharing
P_shared_locked, and sharing beats one-per-agent: the snapshot copy and
the ~1 s FTS5 rebuild are then paid once for the machine, not once each.
Three contracts come with that:
- The lock is held across the caller's block. Releasing it at the
yieldwould hand out exactly the connection a rebuild then closes, so a rebuild parks a second agent for ~1 s — the cost it would have paid alone anyway; every other query is local SQLite. - A view may therefore never nest #with_fresh_db inside another (the
guard is a
Mutex, not aMonitor). - #close needs no refcount: it drops the fd, and the next #with_fresh_db reopens the same copy. A co-owner pays a rebuild, never a failure.
The cache_dir is what must stay unshared — two instances over one
snapshot path cp -f under each other's open fd.
Defined Under Namespace
Constant Summary collapse
- LOGGER =
Pikuri.logger_for('Thunderbird::Gloda')
Instance Method Summary collapse
-
#available? ⇒ Boolean
Whether the Gloda index file exists (the mail / contact tools' registration gate).
-
#close ⇒ void
Close the snapshot connection (the snapshot dir itself is finalizer-reaped).
- #initialize(gloda_path:, cache_dir:) ⇒ Gloda constructor
-
#with_fresh_db {|SQLite3::Database| ... } ⇒ Object
Yield the snapshot connection, guaranteed current — a stale index is rebuilt first.
Constructor Details
#initialize(gloda_path:, cache_dir:) ⇒ Gloda
70 71 72 73 74 75 76 |
# File 'lib/pikuri/thunderbird/gloda.rb', line 70 def initialize(gloda_path:, cache_dir:) @source = gloda_path @snapshot = DatabaseSnapshot.new(source: gloda_path, dir: cache_dir) @db = nil @indexed_generation = nil @lock = Mutex.new end |
Instance Method Details
#available? ⇒ Boolean
Returns whether the Gloda index file exists (the mail / contact tools' registration gate).
80 |
# File 'lib/pikuri/thunderbird/gloda.rb', line 80 def available? = File.file?(@source) |
#close ⇒ void
This method returns an undefined value.
Close the snapshot connection (the snapshot dir itself is finalizer-reaped). Idempotent, and safe while another agent shares this instance — the next #with_fresh_db reopens.
106 107 108 |
# File 'lib/pikuri/thunderbird/gloda.rb', line 106 def close @lock.synchronize { close! } end |
#with_fresh_db {|SQLite3::Database| ... } ⇒ Object
Yield the snapshot connection, guaranteed current — a stale index is rebuilt first. The sole seam the Mail / Contacts views query through; they never cache the returned connection, because a rebuild reopens it.
gloda.with_fresh_db { |db| db.execute('SELECT count(*) FROM messages') }
The lock spans the block, so the yielded connection cannot be swapped
mid-query and the block must not re-enter (see == Sharing).
94 95 96 97 98 99 |
# File 'lib/pikuri/thunderbird/gloda.rb', line 94 def with_fresh_db @lock.synchronize do ensure_fresh yield @db end end |