Skip to content

Commit 14b01d8

Browse files
committed
Implement stale detection by comparing modified_gmt timestamps
When fetching metadata, compare the API's `modified_gmt` against cached posts in the database. Posts with different timestamps are marked as `Stale`, triggering a re-fetch on the next sync. Changes: - Add `select_modified_gmt_by_ids` to PostRepository for efficient batch lookup - Add `detect_and_mark_stale_posts` to PostService for staleness comparison - Call staleness detection in `fetch_and_store_metadata` after storing metadata
1 parent d7a49dd commit 14b01d8

File tree

2 files changed

+117
-1
lines changed

2 files changed

+117
-1
lines changed

wp_mobile/src/service/posts.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,11 @@ impl PostService {
199199
/// - If `is_first_page` is true, replaces existing metadata for `kv_key`
200200
/// - If `is_first_page` is false, appends to existing metadata
201201
///
202+
/// Additionally performs staleness detection:
203+
/// - For posts currently marked as `Cached`, compares the fetched `modified_gmt`
204+
/// against the cached value in the database
205+
/// - Posts with different `modified_gmt` are marked as `Stale`
206+
///
202207
/// Used by `MetadataFetcher` implementations to both fetch and store
203208
/// in one operation.
204209
///
@@ -229,9 +234,64 @@ impl PostService {
229234
self.metadata_store.append(kv_key, result.metadata.clone());
230235
}
231236

237+
// Detect stale posts by comparing modified_gmt
238+
self.detect_and_mark_stale_posts(&result.metadata);
239+
232240
Ok(result)
233241
}
234242

243+
/// Compare fetched metadata against cached posts and mark stale ones.
244+
///
245+
/// For each post that is currently `Cached`, compares the fetched `modified_gmt`
246+
/// against the database value. If they differ, the post is marked as `Stale`.
247+
fn detect_and_mark_stale_posts(&self, metadata: &[EntityMetadata]) {
248+
// Get IDs of posts that are currently Cached (candidates for staleness check)
249+
let cached_ids: Vec<i64> = metadata
250+
.iter()
251+
.filter(|m| {
252+
matches!(
253+
self.state_store_with_edit_context.get(m.id),
254+
EntityState::Cached
255+
)
256+
})
257+
.map(|m| m.id)
258+
.collect();
259+
260+
if cached_ids.is_empty() {
261+
return;
262+
}
263+
264+
// Query database for cached modified_gmt values
265+
let cached_timestamps = self
266+
.cache
267+
.execute(|conn| {
268+
let repo = PostRepository::<EditContext>::new();
269+
repo.select_modified_gmt_by_ids(conn, &self.db_site, &cached_ids)
270+
})
271+
.unwrap_or_default();
272+
273+
// Compare and mark stale
274+
let mut stale_count = 0;
275+
for m in metadata.iter().filter(|m| cached_ids.contains(&m.id)) {
276+
if let Some(fetched_modified) = &m.modified_gmt {
277+
if let Some(cached_modified) = cached_timestamps.get(&m.id) {
278+
if fetched_modified != cached_modified {
279+
self.state_store_with_edit_context
280+
.set(m.id, EntityState::Stale);
281+
stale_count += 1;
282+
}
283+
}
284+
}
285+
}
286+
287+
if stale_count > 0 {
288+
println!(
289+
"[PostService] Detected {} stale post(s) via modified_gmt comparison",
290+
stale_count
291+
);
292+
}
293+
}
294+
235295
/// Fetch full post data for specific post IDs and save to cache.
236296
///
237297
/// This is used for selective sync - fetching only the posts that are

wp_mobile_cache/src/repository/posts.rs

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,15 @@ use crate::{
2020
term_relationships::DbTermRelationship,
2121
};
2222
use rusqlite::{OptionalExtension, Row};
23-
use std::{marker::PhantomData, sync::Arc};
23+
use std::{collections::HashMap, marker::PhantomData, sync::Arc};
2424
use wp_api::{
2525
posts::{
2626
AnyPostWithEditContext, AnyPostWithEmbedContext, AnyPostWithViewContext,
2727
PostContentWithEditContext, PostContentWithViewContext, PostGuidWithEditContext,
2828
PostGuidWithViewContext, PostId, PostTitleWithEditContext, PostTitleWithEmbedContext,
2929
PostTitleWithViewContext, SparsePostExcerpt,
3030
},
31+
prelude::WpGmtDateTime,
3132
taxonomies::TaxonomyType,
3233
terms::TermId,
3334
};
@@ -307,6 +308,61 @@ impl<C: PostContext> PostRepository<C> {
307308
}))
308309
}
309310

311+
/// Select `modified_gmt` timestamps for multiple posts by their WordPress post IDs.
312+
///
313+
/// This is a lightweight query used for staleness detection - it only fetches
314+
/// the `id` and `modified_gmt` columns without loading the full post data.
315+
///
316+
/// Returns a HashMap mapping post IDs to their cached `modified_gmt` timestamps.
317+
/// Posts not found in the cache are simply omitted from the result.
318+
///
319+
/// # Arguments
320+
/// * `executor` - Database connection or transaction
321+
/// * `site` - The site to query posts for
322+
/// * `post_ids` - WordPress post IDs to look up
323+
///
324+
/// # Returns
325+
/// HashMap where keys are post IDs and values are their `modified_gmt` timestamps.
326+
pub fn select_modified_gmt_by_ids(
327+
&self,
328+
executor: &impl QueryExecutor,
329+
site: &DbSite,
330+
post_ids: &[i64],
331+
) -> Result<HashMap<i64, WpGmtDateTime>, SqliteDbError> {
332+
if post_ids.is_empty() {
333+
return Ok(HashMap::new());
334+
}
335+
336+
let ids_str = post_ids
337+
.iter()
338+
.map(|id| id.to_string())
339+
.collect::<Vec<_>>()
340+
.join(", ");
341+
342+
let sql = format!(
343+
"SELECT id, modified_gmt FROM {} WHERE db_site_id = ? AND id IN ({})",
344+
Self::table_name(),
345+
ids_str
346+
);
347+
348+
let mut stmt = executor.prepare(&sql)?;
349+
let rows = stmt.query_map([site.row_id], |row| {
350+
let id: i64 = row.get(0)?;
351+
let modified_gmt_str: String = row.get(1)?;
352+
Ok((id, modified_gmt_str))
353+
})?;
354+
355+
let mut result = HashMap::new();
356+
for row_result in rows {
357+
let (id, modified_gmt_str) = row_result.map_err(SqliteDbError::from)?;
358+
if let Ok(modified_gmt) = modified_gmt_str.parse::<WpGmtDateTime>() {
359+
result.insert(id, modified_gmt);
360+
}
361+
}
362+
363+
Ok(result)
364+
}
365+
310366
/// Delete a post by its EntityId for a given site.
311367
///
312368
/// Returns the number of rows deleted (0 or 1).

0 commit comments

Comments
 (0)