-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathmodels.py
More file actions
297 lines (241 loc) · 9.08 KB
/
models.py
File metadata and controls
297 lines (241 loc) · 9.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
"""
models.py
Defines data models and configuration structures for the LinuxReport project.
"""
# =============================================================================
# STANDARD LIBRARY IMPORTS
# =============================================================================
from abc import ABC, abstractmethod
from dataclasses import dataclass
from flask_login import UserMixin
import datetime
import os
import sys
# =============================================================================
# THIRD-PARTY IMPORTS
# =============================================================================
import diskcache
# =============================================================================
# LOGGING CONFIGURATION
# =============================================================================
# Import the global logger instance from dedicated logging module
from Logging import g_logger
# =============================================================================
# LOCAL IMPORTS
# =============================================================================
from app_config import get_admin_password
import FeedHistory
# =============================================================================
# DATA MODELS AND CONFIGURATION CLASSES
# =============================================================================
@dataclass
class SiteConfig:
"""
Configuration for a site.
"""
ALL_URLS: dict = None
USER_AGENT: str = None
SITE_URLS: list = None #This is the order of the URLs in the display (left to right), most active at top
URL_IMAGES: str = None
FAVICON: str = None
LOGO_URL: str = None
WEB_DESCRIPTION: str = None
WEB_TITLE: str = None
REPORT_PROMPT: str = None
PATH: str = None
SCHEDULE: list = None
DEFAULT_THEME: str = "silver" # Default fallback
CUSTOM_FETCH_CONFIG: dict = None
class RssInfo:
"""
Represents information about an RSS feed.
"""
def __init__(self, logo_url, logo_alt, site_url):
self.logo_url = logo_url
self.logo_alt = logo_alt
self.site_url = site_url
class RssFeed:
"""
Represents an RSS feed with entries and optional top articles.
This class encapsulates RSS feed data and provides methods for
managing feed entries and top article tracking.
"""
def __init__(self, entries, top_articles=None):
"""
Initialize an RSS feed with entries and optional top articles.
Args:
entries (list): List of RSS feed entries
top_articles (Optional[list]): List of top articles to track
"""
self.entries = entries
self.top_articles = top_articles if top_articles else []
self.__post_init__()
def __post_init__(self):
"""Ensure top_articles attribute is properly initialized."""
if not hasattr(self, 'top_articles'):
object.__setattr__(self, 'top_articles', [])
def __setstate__(self, state):
"""
Restore state and reinitialize attributes during unpickling.
Args:
state (dict): State dictionary from pickle
"""
object.__setattr__(self, '__dict__', state)
self.__post_init__()
class DiskCacheWrapper:
"""
Wrapper for diskcache to manage caching operations with additional functionality.
This wrapper provides a consistent interface for disk-based caching operations
and adds custom methods for feed management and expiration checking.
"""
def __init__(self, cache_dir):
"""
Initialize the cache wrapper with a directory.
Args:
cache_dir (str): Directory path for cache storage
"""
self.cache = diskcache.Cache(cache_dir, disk_min_file_size=10000000)
def get(self, key):
"""
Retrieve a value from the cache.
Args:
key (str): Cache key to retrieve
Returns:
Any: Cached value or None if not found
"""
return self.cache.get(key)
def put(self, key, value, timeout=None):
"""
Store a value in the cache with optional expiration.
Args:
key (str): Cache key
value (Any): Value to store
timeout (Optional[int]): Expiration time in seconds
"""
self.cache.set(key, value, expire=timeout)
def delete(self, key):
"""
Remove a key from the cache.
Args:
key (str): Cache key to delete
"""
self.cache.delete(key)
def has(self, key):
"""
Check if a key exists in the cache.
Args:
key (str): Cache key to check
Returns:
bool: True if key exists, False otherwise
"""
return key in self.cache
def has_feed_expired(self, url, last_fetch=None, history=None):
"""
Check if a feed has expired based on the last fetch time.
Args:
url (str): The URL of the feed to check
last_fetch (Optional[datetime.datetime]): Pre-fetched last_fetch timestamp
to avoid duplicate calls
history (Optional[FeedHistory.FeedHistory]): FeedHistory instance to use for expiration checking.
If None, uses the global history instance from shared.py
Returns:
bool: True if the feed has expired, False otherwise
"""
if last_fetch is None:
last_fetch = self.get_last_fetch(url)
if last_fetch is None:
return True
# Use provided history instance or the global one from shared
if history is None:
# Import here to avoid circular imports
import shared
history = shared.history
return history.has_expired(url, last_fetch)
def get_all_last_fetches(self, urls):
"""
Get last fetch times for multiple URLs in a single operation.
Args:
urls (List[str]): List of URLs to check
Returns:
Dict[str, Optional[datetime.datetime]]: Dictionary mapping URLs to their last fetch times
"""
all_fetches = self.get('all_last_fetches') or {}
return {url: all_fetches.get(url) for url in urls}
def get_last_fetch(self, url):
"""
Get the last fetch time for a URL from the shared disk cache.
Args:
url (str): URL to get last fetch time for
Returns:
Optional[datetime.datetime]: Last fetch timestamp or None if not found
"""
all_fetches = self.get('all_last_fetches') or {}
if url in all_fetches:
return all_fetches[url]
return None
def set_last_fetch(self, url, timestamp, timeout=None):
"""
Set the last fetch time for a URL in the shared disk cache.
Args:
url (str): URL to set last fetch time for
timestamp (Any): Timestamp to store
timeout (Optional[int]): Cache expiration time
"""
all_fetches = self.get('all_last_fetches') or {}
all_fetches[url] = timestamp
self.put('all_last_fetches', all_fetches, timeout)
def clear_last_fetch(self, url):
"""
Clear the last fetch time for a URL in the shared disk cache.
Args:
url (str): URL to clear last fetch time for
"""
self.set_last_fetch(url, None)
class User(UserMixin):
"""
Simple user model for Flask-Login that works with config.yaml.
"""
def __init__(self, user_id):
self.id = user_id
self.is_admin = True
@staticmethod
def get(user_id):
if user_id == 'admin':
return User('admin')
return None
@staticmethod
def authenticate(username, password):
if username == 'admin':
correct_password = get_admin_password()
if password == correct_password:
return User('admin')
return None
# =============================================================================
# ABSTRACT BASE CLASSES
# =============================================================================
class LockBase(ABC):
"""An abstract base class defining the interface for a lock."""
@abstractmethod
def acquire(self, timeout_seconds=60, wait=False):
"""Acquires the lock, optionally waiting for it to become available."""
pass
@abstractmethod
def release(self) -> bool:
"""Releases the lock."""
pass
@abstractmethod
def __enter__(self):
"""Enters the context manager, acquiring the lock."""
pass
@abstractmethod
def __exit__(self, exc_type, exc_val, exc_tb):
"""Exits the context manager, releasing the lock."""
pass
@abstractmethod
def locked(self):
"""Checks if the lock is currently held."""
pass
@abstractmethod
def renew(self, timeout_seconds):
"""Renews the lock with a new timeout."""
pass