1010from logfmter import Logfmter
1111from pydantic_settings import BaseSettings
1212
13+ from apitool import StaticFilesCache
1314from beetsstatistics import AlbumSort , BeetsStatistics , DBNotFoundError , DBQueryError
1415
1516log_format = "%(asctime)s [%(levelname)-7s] [%(name)-12s] %(name)s - %(message)s"
@@ -51,7 +52,10 @@ async def get_beets_statistics():
5152
5253settings = Settings ()
5354app = FastAPI ()
54- app .mount ("/static" , StaticFiles (directory = "static" ), name = "static" )
55+ static_files_with_cache = StaticFilesCache (
56+ directory = "static" , cachecontrol = "public, max-age: 86400"
57+ )
58+ app .mount ("/static" , static_files_with_cache , name = "static" )
5559
5660templates = Jinja2Templates (directory = "templates" )
5761templates .env .filters ["quote_plus" ] = lambda u : quote_plus (u )
@@ -83,7 +87,7 @@ async def get_general_stats(
8387 raise HTTPException (
8488 status_code = 500 , detail = "Could not query general statistics: {}" .format (e )
8589 )
86- return templates .TemplateResponse (
90+ response = templates .TemplateResponse (
8791 request = request ,
8892 name = "index.html" ,
8993 context = {
@@ -99,6 +103,8 @@ async def get_general_stats(
99103 "recently_added_albums" : recently_added_albums ,
100104 },
101105 )
106+ _inject_cache_headers (response .headers )
107+ return response
102108
103109
104110@app .get ("/albums" , response_class = HTMLResponse )
@@ -108,9 +114,11 @@ async def get_album_stats(
108114 sort_by : AlbumSort = AlbumSort .ARTIST ,
109115):
110116 albums = beets_statistics .get_albums_from_db (sort_by = sort_by )
111- return templates .TemplateResponse (
117+ response = templates .TemplateResponse (
112118 request = request , name = "albums.html" , context = {"albums" : albums }
113119 )
120+ _inject_cache_headers (response .headers )
121+ return response
114122
115123
116124@app .get ("/genres" , response_class = HTMLResponse )
@@ -124,11 +132,13 @@ async def get_genre_count(
124132 for genre in genres [0 ]:
125133 genre_list .append (genre ["genre" ])
126134 count_list .append (genre ["count" ])
127- return templates .TemplateResponse (
135+ response = templates .TemplateResponse (
128136 request = request ,
129137 name = "genres.html" ,
130138 context = {"genres" : genres , "genre_list" : genre_list , "count_list" : count_list },
131139 )
140+ _inject_cache_headers (response .headers )
141+ return response
132142
133143
134144@app .get ("/artists" , response_class = HTMLResponse )
@@ -143,7 +153,7 @@ async def get_artist_stats(
143153 artist_list .append (artist ["artist" ])
144154 count_list .append (artist ["track_count" ])
145155 count = beets_statistics .get_track_count ()
146- return templates .TemplateResponse (
156+ response = templates .TemplateResponse (
147157 request = request ,
148158 name = "artists.html" ,
149159 context = {
@@ -153,6 +163,8 @@ async def get_artist_stats(
153163 "count_list" : count_list ,
154164 },
155165 )
166+ _inject_cache_headers (response .headers )
167+ return response
156168
157169
158170@app .get ("/decades" , response_class = HTMLResponse )
@@ -161,11 +173,13 @@ async def get_track_decades(
161173 beets_statistics : Annotated [BeetsStatistics , Depends (get_beets_statistics )],
162174):
163175 decades = beets_statistics .get_track_decades ()
164- return templates .TemplateResponse (
176+ response = templates .TemplateResponse (
165177 request = request ,
166178 name = "decades.html" ,
167179 context = {"decades" : decades },
168180 )
181+ _inject_cache_headers (response .headers )
182+ return response
169183
170184
171185@app .get ("/quality" , response_class = HTMLResponse )
@@ -175,11 +189,14 @@ async def get_track_quality(
175189):
176190 bitrates = beets_statistics .get_track_quality ()
177191 worst_quality_tracks = beets_statistics .get_worst_quality_tracks ()
178- return templates .TemplateResponse (
192+
193+ response = templates .TemplateResponse (
179194 request = request ,
180195 name = "quality.html" ,
181196 context = {"bitrates" : bitrates , "worst_quality_tracks" : worst_quality_tracks },
182197 )
198+ _inject_cache_headers (response .headers )
199+ return response
183200
184201
185202@app .get ("/genre-decade-heatmap" , response_class = HTMLResponse )
@@ -222,7 +239,7 @@ async def get_genre_decade_heatmap(
222239 sorted_genre = dict (sorted (heatmap [genre ].items ()))
223240 heatmap [genre ] = sorted_genre
224241
225- return templates .TemplateResponse (
242+ response = templates .TemplateResponse (
226243 request = request ,
227244 name = "genre-decade-heatmap.html" ,
228245 context = {
@@ -231,6 +248,8 @@ async def get_genre_decade_heatmap(
231248 "genre_list" : genre_list ,
232249 },
233250 )
251+ _inject_cache_headers (response .headers )
252+ return response
234253
235254
236255@app .get ("/cover/{album_id}" , response_class = FileResponse )
@@ -241,7 +260,21 @@ async def get_album_cover(
241260 album_cover_path = beets_statistics .get_album_cover_path (album_id )
242261 if album_cover_path is None :
243262 album_cover_path = "static/blank.png"
244- return album_cover_path
263+
264+ response = FileResponse (album_cover_path )
265+ _inject_cache_headers_for_images (response .headers )
266+ return response
267+
268+
269+ def _inject_cache_headers_for_images (headers ):
270+ headers ["Cache-Control" ] = "public, max-age: 86400"
271+ headers ["Vary" ] = "Accept-Encoding"
272+
273+
274+ def _inject_cache_headers (headers ):
275+ headers ["Cache-Control" ] = (
276+ "public, s-maxage=30, stale-while-revalidate=30, stale-if-error=300"
277+ )
245278
246279
247280@app .get ("/added-timeline" , response_class = HTMLResponse )
@@ -250,11 +283,13 @@ async def get_added_timeline(
250283 beets_statistics : Annotated [BeetsStatistics , Depends (get_beets_statistics )],
251284):
252285 timeline = beets_statistics .get_added_timeline ()
253- return templates .TemplateResponse (
286+ response = templates .TemplateResponse (
254287 request = request ,
255288 name = "added-timeline.html" ,
256289 context = {"timeline" : timeline },
257290 )
291+ _inject_cache_headers (response .headers )
292+ return response
258293
259294
260295@app .get ("/duplicates" , response_class = HTMLResponse )
@@ -264,9 +299,11 @@ async def get_duplicates(
264299):
265300 duplicates = beets_statistics .get_duplicates ()
266301
267- return templates .TemplateResponse (
302+ response = templates .TemplateResponse (
268303 request = request , name = "duplicates.html" , context = {"duplicates" : duplicates }
269304 )
305+ _inject_cache_headers (response .headers )
306+ return response
270307
271308
272309@app .get ("/not-in-mb" , response_class = HTMLResponse )
@@ -277,8 +314,10 @@ async def get_not_in_mb(
277314 items_not_in_mb = beets_statistics .get_items_not_in_mb ()
278315 albums_not_in_mb = beets_statistics .get_albums_not_in_mb ()
279316
280- return templates .TemplateResponse (
317+ response = templates .TemplateResponse (
281318 request = request ,
282319 name = "not-in-mb.html" ,
283320 context = {"tracks" : items_not_in_mb , "albums" : albums_not_in_mb },
284321 )
322+ _inject_cache_headers (response .headers )
323+ return response
0 commit comments