Skip to content

Commit fd0b4d8

Browse files
committed
feature(social_card): integrate with Material Social cache mechanism
1 parent 3df7d6a commit fd0b4d8

File tree

2 files changed

+127
-40
lines changed

2 files changed

+127
-40
lines changed

mkdocs_rss_plugin/integrations/theme_material_social_plugin.py

Lines changed: 110 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
# ##################################
66

77
# standard library
8+
import json
9+
from hashlib import md5
810
from pathlib import Path
911

1012
# 3rd party
@@ -39,6 +41,7 @@ class IntegrationMaterialSocialCards:
3941
IS_SOCIAL_PLUGIN_CARDS_ENABLED: bool = True
4042
IS_THEME_MATERIAL: bool = False
4143
IS_INSIDERS: bool = False
44+
CARDS_MANIFEST: dict | None = None
4245

4346
def __init__(self, mkdocs_config: MkDocsConfig, switch_force: bool = True) -> None:
4447
"""Integration instanciation.
@@ -62,7 +65,6 @@ def __init__(self, mkdocs_config: MkDocsConfig, switch_force: bool = True) -> No
6265
self.IS_SOCIAL_PLUGIN_CARDS_ENABLED,
6366
]
6467
)
65-
self.IS_INSIDERS = self.is_mkdocs_theme_material_insiders()
6668

6769
# except if the end-user wants to disable it
6870
if switch_force is False:
@@ -82,6 +84,12 @@ def __init__(self, mkdocs_config: MkDocsConfig, switch_force: bool = True) -> No
8284
self.social_cards_cache_dir = self.get_social_cards_cache_dir(
8385
mkdocs_config=mkdocs_config
8486
)
87+
if self.is_mkdocs_theme_material_insiders():
88+
self.load_cache_cards_manifest()
89+
90+
# store some attributes used to compute social card hash
91+
self.site_name = mkdocs_config.site_name
92+
self.site_description = mkdocs_config.site_description or ""
8593

8694
def is_mkdocs_theme_material(self, mkdocs_config: MkDocsConfig) -> bool:
8795
"""Check if the theme set in mkdocs.yml is material or not.
@@ -107,9 +115,11 @@ def is_mkdocs_theme_material_insiders(self) -> bool | None:
107115

108116
if material_version is not None and "insiders" in material_version:
109117
logger.debug("Material theme edition INSIDERS")
118+
self.IS_INSIDERS = True
110119
return True
111120
else:
112121
logger.debug("Material theme edition COMMUNITY")
122+
self.IS_INSIDERS = False
113123
return False
114124

115125
def is_social_plugin_enabled_mkdocs(self, mkdocs_config: MkDocsConfig) -> bool:
@@ -126,17 +136,17 @@ def is_social_plugin_enabled_mkdocs(self, mkdocs_config: MkDocsConfig) -> bool:
126136
return False
127137

128138
if not mkdocs_config.plugins.get("material/social"):
129-
logger.debug("Social plugin not listed in configuration.")
139+
logger.debug("Material Social plugin not listed in configuration.")
130140
return False
131141

132142
social_plugin_cfg = mkdocs_config.plugins.get("material/social")
133143

134144
if not social_plugin_cfg.config.enabled:
135-
logger.debug("Social plugin is installed but disabled.")
145+
logger.debug("Material Social plugin is installed but disabled.")
136146
self.IS_SOCIAL_PLUGIN_ENABLED = False
137147
return False
138148

139-
logger.debug("Social plugin is enabled in Mkdocs configuration.")
149+
logger.debug("Material Social plugin is enabled in Mkdocs configuration.")
140150
self.IS_SOCIAL_PLUGIN_CARDS_ENABLED = True
141151
return True
142152

@@ -157,19 +167,21 @@ def is_social_plugin_and_cards_enabled_mkdocs(
157167
social_plugin_cfg = mkdocs_config.plugins.get("material/social")
158168

159169
if not social_plugin_cfg.config.cards:
160-
logger.debug("Social plugin is installed, present but cards are disabled.")
170+
logger.debug(
171+
"Material Social plugin is installed, present but cards are disabled."
172+
)
161173
self.IS_SOCIAL_PLUGIN_CARDS_ENABLED = False
162174
return False
163175

164-
logger.debug("Social cards are enabled in Mkdocs configuration.")
176+
logger.debug("Material Social cards are enabled in Mkdocs configuration.")
165177
self.IS_SOCIAL_PLUGIN_CARDS_ENABLED = True
166178
return True
167179

168180
def is_social_plugin_enabled_page(
169181
self, mkdocs_page: Page, fallback_value: bool = True
170182
) -> bool:
171183
"""Check if the social plugin is enabled or disabled for a specific page. Plugin
172-
has to enabled in Mkdocs configuration before.
184+
has to be enabled in Mkdocs configuration before.
173185
174186
Args:
175187
mkdocs_page (Page): Mkdocs page object.
@@ -183,7 +195,31 @@ def is_social_plugin_enabled_page(
183195
"cards", fallback_value
184196
)
185197

186-
def get_social_cards_build_dir(self, mkdocs_config: MkDocsConfig) -> str:
198+
def load_cache_cards_manifest(self) -> dict | None:
199+
"""Load social cards manifest if the file exists.
200+
201+
Returns:
202+
dict | None: manifest as dict or None if the file does not exist
203+
"""
204+
cache_cards_manifest = Path(self.social_cards_cache_dir).joinpath(
205+
"manifest.json"
206+
)
207+
if not cache_cards_manifest.is_file():
208+
logger.debug(
209+
"Material Social Cards cache manifest file not found: "
210+
f"{cache_cards_manifest}"
211+
)
212+
return None
213+
214+
with cache_cards_manifest.open(mode="r", encoding="UTF-8") as manifest:
215+
self.CARDS_MANIFEST = json.load(manifest)
216+
logger.debug(
217+
f"Material Social Cards cache manifest loaded from {cache_cards_manifest}"
218+
)
219+
220+
return self.CARDS_MANIFEST
221+
222+
def get_social_cards_build_dir(self, mkdocs_config: MkDocsConfig) -> Path:
187223
"""Get Social Cards folder within Mkdocs site_dir.
188224
See: https://squidfunk.github.io/mkdocs-material/plugins/social/#config.cards_dir
189225
@@ -196,13 +232,13 @@ def get_social_cards_build_dir(self, mkdocs_config: MkDocsConfig) -> str:
196232
social_plugin_cfg = mkdocs_config.plugins.get("material/social")
197233

198234
logger.debug(
199-
"Social cards folder in Mkdocs build directory: "
235+
"Material Social cards folder in Mkdocs build directory: "
200236
f"{social_plugin_cfg.config.cards_dir}."
201237
)
202238

203-
return social_plugin_cfg.config.cards_dir
239+
return Path(social_plugin_cfg.config.cards_dir).resolve()
204240

205-
def get_social_cards_cache_dir(self, mkdocs_config: MkDocsConfig) -> str:
241+
def get_social_cards_cache_dir(self, mkdocs_config: MkDocsConfig) -> Path:
206242
"""Get Social Cards folder within Mkdocs site_dir.
207243
See: https://squidfunk.github.io/mkdocs-material/plugins/social/#config.cards_dir
208244
@@ -213,16 +249,17 @@ def get_social_cards_cache_dir(self, mkdocs_config: MkDocsConfig) -> str:
213249
str: True if the theme material and the plugin social cards is enabled.
214250
"""
215251
social_plugin_cfg = mkdocs_config.plugins.get("material/social")
252+
self.social_cards_cache_dir = Path(social_plugin_cfg.config.cache_dir).resolve()
216253

217254
logger.debug(
218-
"Social cards cache folder: " f"{social_plugin_cfg.config.cache_dir}."
255+
"Material Social cards cache folder: " f"{self.social_cards_cache_dir}."
219256
)
220257

221-
return social_plugin_cfg.config.cache_dir
258+
return self.social_cards_cache_dir
222259

223260
def get_social_card_build_path_for_page(
224261
self, mkdocs_page: Page, mkdocs_site_dir: str | None = None
225-
) -> Path:
262+
) -> Path | None:
226263
"""Get social card path in Mkdocs build dir for a specific page.
227264
228265
Args:
@@ -231,31 +268,80 @@ def get_social_card_build_path_for_page(
231268
'class.mkdocs_site_build_dir' is used. is Defaults to None.
232269
233270
Returns:
234-
str: path to the image once published
271+
Path: path to the image once published
235272
"""
236273
if mkdocs_site_dir is None and self.mkdocs_site_build_dir:
237274
mkdocs_site_dir = self.mkdocs_site_build_dir
238275

239-
return Path(
276+
expected_built_card_path = Path(
240277
f"{mkdocs_site_dir}/{self.social_cards_assets_dir}/"
241278
f"{Path(mkdocs_page.file.src_uri).with_suffix('.png')}"
242279
)
243280

244-
def get_social_card_cache_path_for_page(self, mkdocs_page: Page) -> Path:
281+
if expected_built_card_path.is_file():
282+
logger.debug(
283+
f"Social card file found in cache folder: {expected_built_card_path}"
284+
)
285+
return expected_built_card_path
286+
else:
287+
logger.debug(f"Not found: {expected_built_card_path}")
288+
return None
289+
290+
def get_social_card_cache_path_for_page(self, mkdocs_page: Page) -> Path | None:
245291
"""Get social card path in social plugin cache folder for a specific page.
246292
293+
Note:
294+
As we write this code (June 2024), the cache mechanism in Insiders edition
295+
has stores images directly with the corresponding Page's path and name and
296+
keep a correspondance matrix with hashes in a manifest.json;
297+
the cache mechanism in Community edition uses the hash as file names without
298+
any exposed matching criteria.
299+
247300
Args:
248301
mkdocs_page (Page): Mkdocs page object.
249-
social_plugin_cache_dir (Optional[str], optional): Mkdocs build site dir. If None, the
250-
'class.mkdocs_site_build_dir' is used. is Defaults to None.
251302
252303
Returns:
253-
str: path to the image once published
304+
Path: path to the image in local cache folder if it exists
254305
"""
255-
return Path(
256-
f"{self.social_cards_assets_dir}/"
257-
f"{Path(mkdocs_page.file.src_uri).with_suffix('.png')}"
258-
)
306+
if self.IS_INSIDERS:
307+
expected_cached_card_path = self.social_cards_cache_dir.joinpath(
308+
f"assets/images/social/{Path(mkdocs_page.file.src_uri).with_suffix('.png')}"
309+
)
310+
if expected_cached_card_path.is_file():
311+
logger.debug(
312+
f"Social card file found in cache folder: {expected_cached_card_path}"
313+
)
314+
return expected_cached_card_path
315+
else:
316+
logger.debug(f"Not found: {expected_cached_card_path}")
317+
318+
else:
319+
if "description" in mkdocs_page.meta:
320+
description = mkdocs_page.meta["description"]
321+
else:
322+
description = self.site_description
323+
324+
page_hash = md5(
325+
"".join(
326+
[
327+
self.site_name,
328+
str(mkdocs_page.meta.get("title", mkdocs_page.title)),
329+
description,
330+
]
331+
).encode("utf-8")
332+
)
333+
expected_cached_card_path = self.social_cards_cache_dir.joinpath(
334+
f"{page_hash.hexdigest()}.png"
335+
)
336+
337+
if expected_cached_card_path.is_file():
338+
logger.debug(
339+
f"Social card file found in cache folder: {expected_cached_card_path}"
340+
)
341+
return expected_cached_card_path
342+
else:
343+
logger.debug(f"Not found: {expected_cached_card_path}")
344+
return None
259345

260346
def get_social_card_url_for_page(
261347
self,

mkdocs_rss_plugin/util.py

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -534,40 +534,41 @@ def get_image(self, in_page: Page, base_url: str) -> tuple[str, str, int] | None
534534
f"{in_page.file.src_uri}"
535535
)
536536
elif (
537-
self.social_cards.IS_ENABLED
537+
isinstance(self.social_cards, IntegrationMaterialSocialCards)
538+
and self.social_cards.IS_ENABLED
538539
and self.social_cards.IS_SOCIAL_PLUGIN_CARDS_ENABLED
539540
and self.social_cards.is_social_plugin_enabled_page(
540541
mkdocs_page=in_page,
541-
fallback_value=self.social_cards,
542+
fallback_value=self.social_cards.IS_SOCIAL_PLUGIN_CARDS_ENABLED,
542543
)
543544
):
544-
img_local_path = self.social_cards.get_social_card_build_path_for_page(
545-
mkdocs_page=in_page
546-
)
545+
547546
img_url = self.social_cards.get_social_card_url_for_page(
548547
mkdocs_page=in_page
549548
)
550-
logger.debug(
551-
f"Image found ({img_url}) from social cards for page: "
552-
f"{in_page.file.src_uri}. Using local image to get mime and length: "
553-
f"{img_local_path}"
554-
)
555-
556-
if img_local_path.is_file():
557-
logger.debug("Local image already exists. Using it to get its length.")
558-
img_length = img_local_path.stat().st_size
549+
if img_local_cache_path := self.social_cards.get_social_card_cache_path_for_page(
550+
mkdocs_page=in_page
551+
):
552+
img_length = img_local_cache_path.stat().st_size
553+
img_type = guess_type(url=img_local_cache_path, strict=False)[0]
554+
elif img_local_build_path := self.social_cards.get_social_card_build_path_for_page(
555+
mkdocs_page=in_page
556+
):
557+
img_length = img_local_build_path.stat().st_size
558+
img_type = guess_type(url=img_local_build_path, strict=False)[0]
559559
else:
560560
logger.debug(
561-
f"Social card: {img_local_path} still not exists. Trying to "
561+
"Social card still not exists locally. Trying to "
562562
f"retrieve length from remote image: {img_url}. "
563563
"Note that would work only if the social card image has been "
564564
"already published before the build."
565565
)
566566
img_length = self.get_remote_image_length(image_url=img_url)
567+
img_type = guess_type(url=img_url, strict=False)[0]
567568

568569
return (
569570
img_url,
570-
guess_type(url=img_local_path, strict=False)[0],
571+
img_type,
571572
img_length,
572573
)
573574

0 commit comments

Comments
 (0)