hualinxin8615 commited on
Commit
55c78cd
·
verified ·
1 Parent(s): b6d1abd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +115 -171
app.py CHANGED
@@ -21,6 +21,8 @@ ACTIVITY_CACHE_FILE = "space_activity_cache.json"
21
  CACHE_UPDATE_INTERVAL = 600 # 10分钟 = 600秒
22
  CACHE_LOCK = threading.Lock()
23
 
 
 
24
  class SpaceActivityCache:
25
  def __init__(self, repo_dir):
26
  self.repo_dir = repo_dir
@@ -29,187 +31,89 @@ class SpaceActivityCache:
29
  self._last_update = None
30
  self._update_thread = None
31
  self._stop_event = threading.Event()
 
32
 
33
- # 启动时立即更新一次缓存
34
- self._update_cache()
35
 
36
  # 启动后台更新线程
37
  self.start_background_updates()
38
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  def start_background_updates(self):
40
  """启动后台缓存更新线程"""
41
  if self._update_thread and self._update_thread.is_alive():
42
- return # 已经在运行
43
 
44
  self._stop_event.clear()
45
  self._update_thread = threading.Thread(target=self._background_update_worker, daemon=True)
46
  self._update_thread.start()
47
  print("[SpaceActivityCache] Background update thread started")
48
 
49
- def stop_background_updates(self):
50
- """停止后台更新线程"""
51
- if self._update_thread:
52
- self._stop_event.set()
53
- print("[SpaceActivityCache] Background update thread stopping...")
54
-
55
  def _background_update_worker(self):
56
- """后台更新工作线程"""
 
 
 
 
 
 
 
 
 
 
 
57
  while not self._stop_event.is_set():
58
  try:
59
  # 等待指定间隔或停止信号
60
  if self._stop_event.wait(timeout=CACHE_UPDATE_INTERVAL):
61
- break # 收到停止信号
62
 
63
- # 执行缓存更新
64
  print("[SpaceActivityCache] Starting scheduled cache update...")
65
  self._update_cache()
66
  print("[SpaceActivityCache] Scheduled cache update completed")
67
 
68
  except Exception as e:
69
  print(f"[SpaceActivityCache] Error in background update: {e}")
70
- # 发生错误时等待较短时间后重试
71
- if not self._stop_event.wait(timeout=60): # 1分钟后重试
72
  continue
73
 
74
- def _update_cache(self):
75
- """更新缓存数据"""
76
- try:
77
- print("[SpaceActivityCache] Updating activity cache...")
78
- start_time = time.time()
79
-
80
- # 获取所有可用空间
81
- available_spaces = self._get_available_spaces()
82
-
83
- new_cache_data = {
84
- 'last_updated': datetime.now().isoformat(),
85
- 'update_interval_seconds': CACHE_UPDATE_INTERVAL,
86
- 'spaces': {}
87
- }
88
-
89
- # 为每个空间读取最后活动时间
90
- for space_name in available_spaces:
91
- csv_file = os.path.join(self.repo_dir, f"{space_name}_query_log.csv")
92
- last_activity, status = self._get_last_activity_from_file(csv_file)
93
-
94
- new_cache_data['spaces'][space_name] = {
95
- 'last_activity': last_activity.isoformat() if last_activity != datetime.min else None,
96
- 'status': status,
97
- 'cache_update_time': datetime.now().isoformat()
98
- }
99
-
100
- print(f"[SpaceActivityCache] {space_name}: {status}")
101
-
102
- # 线程安全地更新缓存
103
- with CACHE_LOCK:
104
- self._cache_data = new_cache_data
105
- self._last_update = datetime.now()
106
-
107
- # 保存到文件
108
- self._save_cache_to_file()
109
-
110
- elapsed_time = time.time() - start_time
111
- print(f"[SpaceActivityCache] Cache updated in {elapsed_time:.2f}s, {len(available_spaces)} spaces processed")
112
-
113
- except Exception as e:
114
- print(f"[SpaceActivityCache] Error updating cache: {e}")
115
- import traceback
116
- print(f"[SpaceActivityCache] Traceback: {traceback.format_exc()}")
117
-
118
- def _get_available_spaces(self):
119
- """获取可用空间列表"""
120
- available_spaces = set()
121
- try:
122
- for filename in os.listdir(self.repo_dir):
123
- if filename.endswith('_query_log.csv') and '_Student_' in filename:
124
- space_name = filename.replace('_query_log.csv', '')
125
- available_spaces.add(space_name)
126
- return sorted(list(available_spaces))
127
- except Exception as e:
128
- print(f"[SpaceActivityCache] Error getting available spaces: {e}")
129
- return []
130
-
131
- def _get_last_activity_from_file(self, csv_file_path):
132
- """从文件读取最后活动时间(使用原来的方法)"""
133
- try:
134
- if not os.path.exists(csv_file_path):
135
- return datetime.min, "file_not_found"
136
-
137
- # 检查文件大小
138
- file_size = os.path.getsize(csv_file_path)
139
- if file_size <= 100:
140
- return datetime.min, "empty_or_header_only"
141
-
142
- # 使用CSV reader读取最后一行
143
- with open(csv_file_path, 'r', encoding='utf-8') as f:
144
- csv_reader = csv.reader(f)
145
-
146
- # 跳过header
147
- try:
148
- header = next(csv_reader)
149
- except StopIteration:
150
- return datetime.min, "empty_file"
151
-
152
- # 读取所有行,获取最后一行
153
- rows = []
154
- try:
155
- for row in csv_reader:
156
- if row: # 跳过空行
157
- rows.append(row)
158
- except Exception as csv_error:
159
- print(f"[SpaceActivityCache] CSV parsing error for {csv_file_path}: {csv_error}")
160
- return datetime.min, "csv_parse_error"
161
-
162
- if not rows:
163
- return datetime.min, "no_data_rows"
164
-
165
- # 解析最后一行的时间戳
166
- last_row = rows[-1]
167
- if len(last_row) >= 3:
168
- timestamp_str = last_row[2].strip() # timestamp column
169
- try:
170
- parsed_time = datetime.strptime(timestamp_str, '%Y-%m-%d %H:%M:%S')
171
- return parsed_time, f"active_last_at_{parsed_time.strftime('%Y-%m-%d_%H:%M:%S')}"
172
- except ValueError as ve:
173
- print(f"[SpaceActivityCache] Date parsing error for '{timestamp_str}': {ve}")
174
- return datetime.min, "date_parse_error"
175
- else:
176
- return datetime.min, "invalid_row_format"
177
-
178
- except Exception as e:
179
- print(f"[SpaceActivityCache] Error reading {csv_file_path}: {e}")
180
- return datetime.min, "read_error"
181
-
182
- def _save_cache_to_file(self):
183
- """保存缓存到文件"""
184
- try:
185
- with open(self.cache_file_path, 'w', encoding='utf-8') as f:
186
- json.dump(self._cache_data, f, indent=2, ensure_ascii=False)
187
- print(f"[SpaceActivityCache] Cache saved to {self.cache_file_path}")
188
- except Exception as e:
189
- print(f"[SpaceActivityCache] Error saving cache to file: {e}")
190
-
191
- def _load_cache_from_file(self):
192
- """从文件加载缓存"""
193
- try:
194
- if os.path.exists(self.cache_file_path):
195
- with open(self.cache_file_path, 'r', encoding='utf-8') as f:
196
- data = json.load(f)
197
- print(f"[SpaceActivityCache] Cache loaded from file")
198
- return data
199
- except Exception as e:
200
- print(f"[SpaceActivityCache] Error loading cache from file: {e}")
201
- return {}
202
-
203
  def get_space_activity(self, space_name):
204
- """获取指定空间的活动信息"""
205
  with CACHE_LOCK:
 
 
 
 
 
 
 
 
 
 
206
  if not self._cache_data or 'spaces' not in self._cache_data:
207
- # 缓存为空,尝试从文件加载
208
- self._cache_data = self._load_cache_from_file()
209
- if not self._cache_data:
210
- # 文件也没有,立即更新一次
211
- print("[SpaceActivityCache] No cache available, updating immediately...")
212
- self._update_cache()
213
 
214
  spaces_data = self._cache_data.get('spaces', {})
215
  space_info = spaces_data.get(space_name, {})
@@ -231,17 +135,25 @@ class SpaceActivityCache:
231
  return last_activity, status
232
 
233
  def get_all_spaces_activity(self):
234
- """获取所有空间的活动信息"""
235
  with CACHE_LOCK:
236
- if not self._cache_data or 'spaces' not in self._cache_data:
237
- self._cache_data = self._load_cache_from_file()
238
- if not self._cache_data:
239
- print("[SpaceActivityCache] No cache available, updating immediately...")
240
- self._update_cache()
 
 
 
241
 
242
  result = {}
243
  spaces_data = self._cache_data.get('spaces', {})
244
 
 
 
 
 
 
245
  for space_name, space_info in spaces_data.items():
246
  last_activity_str = space_info.get('last_activity')
247
  if last_activity_str:
@@ -299,8 +211,9 @@ def init_activity_cache(repo_dir):
299
  print("[init_activity_cache] Activity cache initialized")
300
  return activity_cache
301
 
 
302
  def analyze_space_activity_cached(available_spaces, repo_dir):
303
- """使用缓存的空间活动分析"""
304
  global activity_cache
305
 
306
  # 确保缓存已初始化
@@ -314,14 +227,18 @@ def analyze_space_activity_cached(available_spaces, repo_dir):
314
  cache_info = activity_cache.get_cache_info()
315
  print(f"[analyze_space_activity_cached] Using cache (age: {cache_info['age_minutes']:.1f} min, fresh: {cache_info['is_fresh']})")
316
 
317
- # 如果缓存太旧,可以选择强制更新(可选)
318
- if not cache_info['is_fresh']:
319
- print("[analyze_space_activity_cached] Cache is stale, but using it anyway for speed")
320
- # 可以选择在后台触发更新:activity_cache.force_update()
321
-
322
  # 获取所有空间的缓存活动数据
323
  all_spaces_activity = activity_cache.get_all_spaces_activity()
324
 
 
 
 
 
 
 
 
 
 
325
  # Read allocation records to filter out recently allocated spaces
326
  active_allocations = read_allocation_records(repo_dir)
327
 
@@ -335,14 +252,17 @@ def analyze_space_activity_cached(available_spaces, repo_dir):
335
  last_activity = cached_info['last_activity']
336
  cached_status = cached_info['status']
337
  else:
338
- # 缓存中没有这个空间,可能是新空间
339
  last_activity = datetime.min
340
  cached_status = "not_in_cache"
341
 
342
  # Calculate idle time in minutes
343
- if last_activity == datetime.min or 'empty' in cached_status or 'not_found' in cached_status:
344
- idle_minutes = float('inf') # Never used
345
- status = "Never used"
 
 
 
346
  last_activity_str = "Never"
347
  else:
348
  idle_minutes = (current_time - last_activity).total_seconds() / 60
@@ -386,8 +306,8 @@ class LocalAllocationTracker:
386
  def __init__(self):
387
  self._recent_allocations = {} # {space_name: {'student_id': str, 'timestamp': datetime}}
388
  self._lock = threading.Lock()
389
- self._cleanup_interval = 60 # 清理间隔(秒)
390
- self._allocation_ttl = 30 # 本地分配记录的生存时间(秒)
391
 
392
  # 启动后台清理线程
393
  self._start_cleanup_thread()
@@ -712,10 +632,28 @@ def simple_push_allocation_record(repo, space_name, student_id):
712
 
713
 
714
 
715
- def create_status_display(space_activity):
716
- """Create formatted status display for all spaces with proper line breaks"""
 
 
 
 
 
 
717
  status_display = "📊 **Current Space Status (sorted by availability):**<br><br>"
718
 
 
 
 
 
 
 
 
 
 
 
 
 
719
  # 显示本地分配记录摘要
720
  local_summary = local_tracker.get_recent_allocations_summary()
721
  if local_summary:
@@ -727,7 +665,13 @@ def create_status_display(space_activity):
727
  for i, space in enumerate(space_activity, 1):
728
  status_display += f"{i}. **{space['space_name']}**<br>"
729
  status_display += f"&nbsp;&nbsp;&nbsp;• Status: {space['status']}<br>"
730
- status_display += f"&nbsp;&nbsp;&nbsp;• Last activity: {space['last_activity_str']}<br><br>"
 
 
 
 
 
 
731
 
732
  return status_display
733
 
 
21
  CACHE_UPDATE_INTERVAL = 600 # 10分钟 = 600秒
22
  CACHE_LOCK = threading.Lock()
23
 
24
+ # 修改 SpaceActivityCache 类的相关方法
25
+
26
  class SpaceActivityCache:
27
  def __init__(self, repo_dir):
28
  self.repo_dir = repo_dir
 
31
  self._last_update = None
32
  self._update_thread = None
33
  self._stop_event = threading.Event()
34
+ self._is_initialized = False
35
 
36
+ # 启动时先尝试从文件加载缓存,而不是立即更新
37
+ self._load_cache_from_file_on_init()
38
 
39
  # 启动后台更新线程
40
  self.start_background_updates()
41
 
42
+ def _load_cache_from_file_on_init(self):
43
+ """初始化时从文件加载缓存"""
44
+ try:
45
+ if os.path.exists(self.cache_file_path):
46
+ with open(self.cache_file_path, 'r', encoding='utf-8') as f:
47
+ self._cache_data = json.load(f)
48
+ if 'last_updated' in self._cache_data:
49
+ self._last_update = datetime.fromisoformat(self._cache_data['last_updated'])
50
+ self._is_initialized = True
51
+ print(f"[SpaceActivityCache] Cache loaded from file on init: {len(self._cache_data.get('spaces', {}))} spaces")
52
+ else:
53
+ print("[SpaceActivityCache] Invalid cache file format")
54
+ else:
55
+ print("[SpaceActivityCache] No cache file found on init")
56
+ except Exception as e:
57
+ print(f"[SpaceActivityCache] Error loading cache on init: {e}")
58
+
59
+ # 如果没有有效缓存,标记为需要初始更新
60
+ if not self._is_initialized:
61
+ print("[SpaceActivityCache] No valid cache found, will update on first background cycle")
62
+
63
  def start_background_updates(self):
64
  """启动后台缓存更新线程"""
65
  if self._update_thread and self._update_thread.is_alive():
66
+ return
67
 
68
  self._stop_event.clear()
69
  self._update_thread = threading.Thread(target=self._background_update_worker, daemon=True)
70
  self._update_thread.start()
71
  print("[SpaceActivityCache] Background update thread started")
72
 
 
 
 
 
 
 
73
  def _background_update_worker(self):
74
+ """后台更新工作线程 - 修改启动逻辑"""
75
+ # 如果没有初始化,等待30秒后进行第一次更新
76
+ if not self._is_initialized:
77
+ print("[SpaceActivityCache] Waiting 30 seconds before first cache update...")
78
+ if self._stop_event.wait(timeout=30):
79
+ return # 收到停止信号
80
+
81
+ print("[SpaceActivityCache] Performing initial cache update...")
82
+ self._update_cache()
83
+ self._is_initialized = True
84
+
85
+ # 正常的定期更新循环
86
  while not self._stop_event.is_set():
87
  try:
88
  # 等待指定间隔或停止信号
89
  if self._stop_event.wait(timeout=CACHE_UPDATE_INTERVAL):
90
+ break
91
 
 
92
  print("[SpaceActivityCache] Starting scheduled cache update...")
93
  self._update_cache()
94
  print("[SpaceActivityCache] Scheduled cache update completed")
95
 
96
  except Exception as e:
97
  print(f"[SpaceActivityCache] Error in background update: {e}")
98
+ if not self._stop_event.wait(timeout=60):
 
99
  continue
100
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  def get_space_activity(self, space_name):
102
+ """获取指定空间的活动信息 - 不触发立即更新"""
103
  with CACHE_LOCK:
104
+ # 如果缓存为空但已经有缓存文件,尝试加载
105
+ if not self._cache_data and os.path.exists(self.cache_file_path):
106
+ try:
107
+ with open(self.cache_file_path, 'r', encoding='utf-8') as f:
108
+ self._cache_data = json.load(f)
109
+ print("[SpaceActivityCache] Cache loaded from file in get_space_activity")
110
+ except Exception as e:
111
+ print(f"[SpaceActivityCache] Error loading cache in get_space_activity: {e}")
112
+
113
+ # 如果仍然没有缓存数据,返回默认值而不是触发更新
114
  if not self._cache_data or 'spaces' not in self._cache_data:
115
+ print(f"[SpaceActivityCache] No cache data available for {space_name}, returning defaults")
116
+ return datetime.min, "no_cache_available"
 
 
 
 
117
 
118
  spaces_data = self._cache_data.get('spaces', {})
119
  space_info = spaces_data.get(space_name, {})
 
135
  return last_activity, status
136
 
137
  def get_all_spaces_activity(self):
138
+ """获取所有空间的活动信息 - 不触发立即更新"""
139
  with CACHE_LOCK:
140
+ # 如果缓存为空但已经有缓存文件,尝试加载
141
+ if not self._cache_data and os.path.exists(self.cache_file_path):
142
+ try:
143
+ with open(self.cache_file_path, 'r', encoding='utf-8') as f:
144
+ self._cache_data = json.load(f)
145
+ print("[SpaceActivityCache] Cache loaded from file in get_all_spaces_activity")
146
+ except Exception as e:
147
+ print(f"[SpaceActivityCache] Error loading cache in get_all_spaces_activity: {e}")
148
 
149
  result = {}
150
  spaces_data = self._cache_data.get('spaces', {})
151
 
152
+ # 如果没有缓存数据,返回空字典而不是触发更新
153
+ if not spaces_data:
154
+ print("[SpaceActivityCache] No cached spaces data available, returning empty dict")
155
+ return result
156
+
157
  for space_name, space_info in spaces_data.items():
158
  last_activity_str = space_info.get('last_activity')
159
  if last_activity_str:
 
211
  print("[init_activity_cache] Activity cache initialized")
212
  return activity_cache
213
 
214
+ # 修改 analyze_space_activity_cached 函数
215
  def analyze_space_activity_cached(available_spaces, repo_dir):
216
+ """使用缓存的空间活动分析 - 改进版本"""
217
  global activity_cache
218
 
219
  # 确保缓存已初始化
 
227
  cache_info = activity_cache.get_cache_info()
228
  print(f"[analyze_space_activity_cached] Using cache (age: {cache_info['age_minutes']:.1f} min, fresh: {cache_info['is_fresh']})")
229
 
 
 
 
 
 
230
  # 获取所有空间的缓存活动数据
231
  all_spaces_activity = activity_cache.get_all_spaces_activity()
232
 
233
+ # 如果缓存完全为空,为所有空间提供默认值
234
+ if not all_spaces_activity:
235
+ print("[analyze_space_activity_cached] No cached data available, using default values for all spaces")
236
+ for space_name in available_spaces:
237
+ all_spaces_activity[space_name] = {
238
+ 'last_activity': datetime.min,
239
+ 'status': 'no_cache_data'
240
+ }
241
+
242
  # Read allocation records to filter out recently allocated spaces
243
  active_allocations = read_allocation_records(repo_dir)
244
 
 
252
  last_activity = cached_info['last_activity']
253
  cached_status = cached_info['status']
254
  else:
255
+ # 缓存中没有这个空间,使用默认值
256
  last_activity = datetime.min
257
  cached_status = "not_in_cache"
258
 
259
  # Calculate idle time in minutes
260
+ if last_activity == datetime.min or 'empty' in cached_status or 'not_found' in cached_status or 'no_cache' in cached_status:
261
+ idle_minutes = float('inf') # Never used or no data
262
+ if 'no_cache' in cached_status:
263
+ status = "No cache data available"
264
+ else:
265
+ status = "Never used"
266
  last_activity_str = "Never"
267
  else:
268
  idle_minutes = (current_time - last_activity).total_seconds() / 60
 
306
  def __init__(self):
307
  self._recent_allocations = {} # {space_name: {'student_id': str, 'timestamp': datetime}}
308
  self._lock = threading.Lock()
309
+ self._cleanup_interval = 600 # 清理间隔(秒)
310
+ self._allocation_ttl = 60 # 本地分配记录的生存时间(秒)
311
 
312
  # 启动后台清理线程
313
  self._start_cleanup_thread()
 
632
 
633
 
634
 
635
+ # 修改状态显示函数,处理无缓存数据的情况
636
+ def create_status_display_with_cache_info(space_activity):
637
+ """创建包含缓存信息的状态显示 - 改进版本"""
638
+ global activity_cache
639
+
640
+ # 获取缓存状态
641
+ cache_info = activity_cache.get_cache_info() if activity_cache else None
642
+
643
  status_display = "📊 **Current Space Status (sorted by availability):**<br><br>"
644
 
645
+ # 显示缓存信息
646
+ if cache_info:
647
+ if cache_info['is_fresh']:
648
+ cache_status = f"✅ Fresh (updated {cache_info['age_minutes']:.1f} min ago)"
649
+ elif cache_info['last_updated']:
650
+ cache_status = f"⚠️ Stale (updated {cache_info['age_minutes']:.1f} min ago)"
651
+ else:
652
+ cache_status = f"🔄 No cache data (background update in progress)"
653
+
654
+ status_display += f"🔄 **Cache Status:** {cache_status}<br>"
655
+ status_display += f"📋 **Cached Spaces:** {cache_info['spaces_count']}<br><br>"
656
+
657
  # 显示本地分配记录摘要
658
  local_summary = local_tracker.get_recent_allocations_summary()
659
  if local_summary:
 
665
  for i, space in enumerate(space_activity, 1):
666
  status_display += f"{i}. **{space['space_name']}**<br>"
667
  status_display += f"&nbsp;&nbsp;&nbsp;• Status: {space['status']}<br>"
668
+ status_display += f"&nbsp;&nbsp;&nbsp;• Last activity: {space['last_activity_str']}<br>"
669
+
670
+ # 显示缓存状态
671
+ if 'cached_status' in space:
672
+ status_display += f"&nbsp;&nbsp;&nbsp;• Cache: {space['cached_status']}<br>"
673
+
674
+ status_display += "<br>"
675
 
676
  return status_display
677