@@ -15,12 +15,12 @@ class CacheManager:
1515 CACHE_DIR = Path .home () / ".config" / "gitfetch"
1616 DB_FILE = CACHE_DIR / "cache.db"
1717
18- def __init__ (self , cache_expiry_hours : int = 24 ):
18+ def __init__ (self , cache_expiry_hours : int = 6 ):
1919 """
2020 Initialize the cache manager.
2121
2222 Args:
23- cache_expiry_hours: Hours before cache expires (default: 24 )
23+ cache_expiry_hours: Hours before cache expires (default: 6 )
2424 """
2525 self .cache_expiry_hours = cache_expiry_hours
2626 self ._ensure_cache_dir ()
@@ -75,7 +75,9 @@ def get_cached_stats(self, username: str) -> Optional[Dict[str, Any]]:
7575 entry = self .get_cached_entry (username )
7676 return entry [1 ] if entry else None
7777
78- def get_cached_entry (self , username : str ) -> Optional [tuple [Dict [str , Any ], Dict [str , Any ]]]:
78+ def get_cached_entry (self , username : str ) -> Optional [
79+ tuple [Dict [str , Any ], Dict [str , Any ]]
80+ ]:
7981 """
8082 Retrieve both user data and stats if available and not expired.
8183
@@ -109,6 +111,81 @@ def get_cached_entry(self, username: str) -> Optional[tuple[Dict[str, Any], Dict
109111 except (sqlite3 .Error , json .JSONDecodeError , ValueError ):
110112 return None
111113
114+ def get_stale_cached_entry (self , username : str ) -> Optional [
115+ tuple [Dict [str , Any ], Dict [str , Any ], datetime ]
116+ ]:
117+ """
118+ Retrieve cached data even if expired (for background refresh).
119+
120+ Args:
121+ username: GitHub username
122+
123+ Returns:
124+ Tuple of (user_data, stats, cached_at) or None if not available
125+ """
126+ try :
127+ conn = sqlite3 .connect (self .DB_FILE )
128+ cursor = conn .cursor ()
129+ cursor .execute (
130+ 'SELECT user_data, stats_data, cached_at FROM users '
131+ 'WHERE username = ?' ,
132+ (username ,)
133+ )
134+ row = cursor .fetchone ()
135+ conn .close ()
136+
137+ if not row :
138+ return None
139+
140+ cached_at = datetime .fromisoformat (row [2 ])
141+ user_data = json .loads (row [0 ])
142+ stats_data = json .loads (row [1 ])
143+ return (user_data , stats_data , cached_at )
144+ except (sqlite3 .Error , json .JSONDecodeError , ValueError ):
145+ return None
146+ """
147+ Retrieve cached user data even if expired.
148+
149+ Args:
150+ username: GitHub username
151+
152+ Returns:
153+ Cached user data or None if not available
154+ """
155+ entry = self .get_stale_cached_entry (username )
156+ return entry [0 ] if entry else None
157+
158+ def get_stale_cached_stats (self , username : str ) -> Optional [
159+ Dict [str , Any ]
160+ ]:
161+ """
162+ Retrieve cached statistics even if expired.
163+
164+ Args:
165+ username: GitHub username
166+
167+ Returns:
168+ Cached stats or None if not available
169+ """
170+ entry = self .get_stale_cached_entry (username )
171+ return entry [1 ] if entry else None
172+
173+ def is_cache_stale (self , username : str ) -> bool :
174+ """
175+ Check if cached data exists but is stale (expired).
176+
177+ Args:
178+ username: GitHub username
179+
180+ Returns:
181+ True if cache exists but is stale, False otherwise
182+ """
183+ entry = self .get_stale_cached_entry (username )
184+ if not entry :
185+ return False
186+ _ , _ , cached_at = entry
187+ return self ._is_cache_expired (cached_at )
188+
112189 def cache_user_data (self , username : str , user_data : Dict [str , Any ],
113190 stats : Dict [str , Any ]) -> None :
114191 """
@@ -123,7 +200,7 @@ def cache_user_data(self, username: str, user_data: Dict[str, Any],
123200 conn = sqlite3 .connect (self .DB_FILE )
124201 cursor = conn .cursor ()
125202 cursor .execute ('''
126- INSERT OR REPLACE INTO users
203+ INSERT OR REPLACE INTO users
127204 (username, user_data, stats_data, cached_at)
128205 VALUES (?, ?, ?, ?)
129206 ''' , (
0 commit comments