Skip to content

Commit 998d574

Browse files
committed
updated cache system
1 parent 310a8ed commit 998d574

File tree

2 files changed

+125
-9
lines changed

2 files changed

+125
-9
lines changed

src/gitfetch/cache.py

Lines changed: 81 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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
''', (

src/gitfetch/cli.py

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,14 +95,51 @@ def main() -> int:
9595
if args.no_cache:
9696
user_data = fetcher.fetch_user_data(username)
9797
stats = fetcher.fetch_user_stats(username, user_data)
98+
cache_manager.cache_user_data(username, user_data, stats)
9899
else:
100+
# Try to get fresh cache first
99101
user_data = cache_manager.get_cached_user_data(username)
100102
stats = cache_manager.get_cached_stats(username)
101103

102104
if user_data is None or stats is None:
103-
user_data = fetcher.fetch_user_data(username)
104-
stats = fetcher.fetch_user_stats(username, user_data)
105-
cache_manager.cache_user_data(username, user_data, stats)
105+
# No fresh cache, try to get stale cache for immediate display
106+
stale_user_data = cache_manager.get_stale_cached_user_data(
107+
username
108+
)
109+
stale_stats = cache_manager.get_stale_cached_stats(username)
110+
111+
if stale_user_data is not None and stale_stats is not None:
112+
# Display stale cache immediately
113+
formatter.display(username, stale_user_data, stale_stats)
114+
print("\n🔄 Refreshing data in background...",
115+
file=sys.stderr)
116+
117+
# Refresh cache in background (don't wait for it)
118+
import threading
119+
120+
def refresh_cache():
121+
try:
122+
fresh_user_data = fetcher.fetch_user_data(username)
123+
fresh_stats = fetcher.fetch_user_stats(
124+
username, fresh_user_data
125+
)
126+
cache_manager.cache_user_data(
127+
username, fresh_user_data, fresh_stats
128+
)
129+
except Exception:
130+
pass # Silently fail background refresh
131+
132+
thread = threading.Thread(
133+
target=refresh_cache, daemon=True
134+
)
135+
thread.start()
136+
return 0
137+
else:
138+
# No cache at all, fetch fresh data
139+
user_data = fetcher.fetch_user_data(username)
140+
stats = fetcher.fetch_user_stats(username, user_data)
141+
cache_manager.cache_user_data(username, user_data, stats)
142+
# else: fresh cache available, proceed to display
106143

107144
# Display the results
108145
formatter.display(username, user_data, stats)
@@ -126,7 +163,8 @@ def _prompt_username() -> Optional[str]:
126163

127164
def _initialize_gitfetch(config_manager: ConfigManager) -> bool:
128165
"""
129-
Initialize gitfetch by creating config directory and asking for default username.
166+
Initialize gitfetch by creating config directory and asking for
167+
default username.
130168
131169
Args:
132170
config_manager: ConfigManager instance
@@ -137,7 +175,8 @@ def _initialize_gitfetch(config_manager: ConfigManager) -> bool:
137175
try:
138176
# Prompt for default username
139177
print("Please enter your default GitHub username.")
140-
print("(You can override this later by passing a username as an argument)")
178+
print("(You can override this later by passing a username as "
179+
"an argument)")
141180
username = input("\nGitHub username: ").strip()
142181

143182
if not username:

0 commit comments

Comments
 (0)