Skip to content

Commit cf21f18

Browse files
committed
Add configurable minimum photo threshold
1 parent 4506f5a commit cf21f18

File tree

7 files changed

+70
-13
lines changed

7 files changed

+70
-13
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ This code was generatde by GPT5.1-Codex.
1414
**Microsoft Graph API** - Secure authentication using Azure AD
1515
**Real-time Updates** - Background refresh with coordinator pattern
1616
**Recent Folder History** - Configurable limit keeps the last N folders out of rotation
17+
**Folder Quality Filter** - Enforce a minimum number of photos before a folder enters the rotation
1718
**Rich Sensors** - Multiple sensors with photo URLs as attributes
1819
**Services** - Manual refresh and folder selection services
1920
**Configuration UI** - Easy setup through Home Assistant UI
@@ -76,6 +77,7 @@ Restart Home Assistant to load the new integration.
7677
- **Library Name**: Document library name (default: `Documents`)
7778
- **Base Folder Path**: Path to photo folders (e.g., `/Photos`)
7879
- **Recent Folder History Size**: Number of previously selected folders to avoid when refreshing (default: `30`, set to `0` to disable)
80+
- **Minimum Photos Per Folder**: Only include folders that contain at least this many photos (default: `5`)
7981

8082
## How It Works
8183

custom_components/sharepoint_photos/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@
1717
from .const import (
1818
CONF_BASE_FOLDER_PATH,
1919
CONF_FOLDER_HISTORY_SIZE,
20+
CONF_MIN_PHOTO_COUNT,
2021
CONF_LIBRARY_NAME,
2122
DEFAULT_BASE_FOLDER_PATH,
2223
DEFAULT_FOLDER_HISTORY_SIZE,
24+
DEFAULT_MIN_PHOTO_COUNT,
2325
DEFAULT_LIBRARY_NAME,
2426
DOMAIN,
2527
)
@@ -170,6 +172,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
170172
CONF_FOLDER_HISTORY_SIZE,
171173
entry.data.get(CONF_FOLDER_HISTORY_SIZE, DEFAULT_FOLDER_HISTORY_SIZE),
172174
)
175+
min_photos_per_folder = entry.options.get(
176+
CONF_MIN_PHOTO_COUNT,
177+
entry.data.get(CONF_MIN_PHOTO_COUNT, DEFAULT_MIN_PHOTO_COUNT),
178+
)
173179

174180
client = SharePointPhotosApiClient(
175181
hass=hass,
@@ -180,6 +186,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
180186
library_name=library_name,
181187
base_folder_path=base_folder_path,
182188
recent_history_size=recent_history_size,
189+
min_photos_per_folder=min_photos_per_folder,
183190
)
184191

185192
coordinator = SharePointPhotosDataUpdateCoordinator(hass, client=client, entry_id=entry.entry_id)

custom_components/sharepoint_photos/api.py

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
CONF_CLIENT_SECRET,
1919
CONF_TENANT_ID,
2020
DEFAULT_FOLDER_HISTORY_SIZE,
21+
DEFAULT_MIN_PHOTO_COUNT,
2122
GRAPH_API_BASE,
2223
IMAGE_EXTENSIONS,
2324
SCOPE,
@@ -39,6 +40,7 @@ def __init__(
3940
library_name: str = "Documents",
4041
base_folder_path: str = "/Photos",
4142
recent_history_size: int = DEFAULT_FOLDER_HISTORY_SIZE,
43+
min_photos_per_folder: int = DEFAULT_MIN_PHOTO_COUNT,
4244
) -> None:
4345
"""Initialize the SharePoint client."""
4446
self.hass = hass
@@ -49,6 +51,7 @@ def __init__(
4951
self.library_name = library_name
5052
self.base_folder_path = base_folder_path
5153
self._recent_history_size = max(0, recent_history_size or 0)
54+
self._min_photos_per_folder = max(1, min_photos_per_folder or DEFAULT_MIN_PHOTO_COUNT)
5255
self._recent_folder_paths = (
5356
deque(maxlen=self._recent_history_size) if self._recent_history_size > 0 else None
5457
)
@@ -401,8 +404,8 @@ async def _scan_folders_recursive(self, drive_id: str, folder_path: str, folders
401404

402405
status, data = await self._make_authenticated_request(url)
403406
if status == 200:
404-
# Check if current folder has photos
405-
has_photos = False
407+
# Count how many photos exist in this folder
408+
photo_count = 0
406409
subfolders = []
407410

408411
items = data.get("value", [])
@@ -416,16 +419,28 @@ async def _scan_folders_recursive(self, drive_id: str, folder_path: str, folders
416419
# It's a file, check if it's an image
417420
file_name = item.get("name", "").lower()
418421
if any(file_name.endswith(ext) for ext in IMAGE_EXTENSIONS):
419-
has_photos = True
422+
photo_count += 1
420423

421-
# If current folder has photos, add it to the list
422-
if has_photos:
424+
# If current folder meets the minimum threshold, add it to the list
425+
if photo_count >= self._min_photos_per_folder:
423426
folders.append({
424427
"name": self._build_display_folder_name(folder_path),
425428
"path": folder_path,
426429
"full_path": folder_path,
430+
"photo_count": photo_count,
427431
})
428-
_LOGGER.debug("Added photo folder: %s", folder_path)
432+
_LOGGER.debug(
433+
"Added photo folder: %s (%d photos)",
434+
folder_path,
435+
photo_count,
436+
)
437+
else:
438+
_LOGGER.debug(
439+
"Skipping folder %s: only %d photos (minimum %d)",
440+
folder_path,
441+
photo_count,
442+
self._min_photos_per_folder,
443+
)
429444

430445
# Recursively scan subfolders
431446
_LOGGER.debug("Scanning %d subfolders in %s", len(subfolders), folder_path)
@@ -517,12 +532,23 @@ async def get_random_photo_folder(self) -> Optional[Dict[str, Any]]:
517532
"""Get a random photo folder with its images - this always selects a NEW folder."""
518533
folders = await self.get_photo_folders()
519534
if not folders:
535+
_LOGGER.warning(
536+
"No folders meet the minimum photo requirement (%d)",
537+
self._min_photos_per_folder,
538+
)
520539
return None
521540

522541
# Select a random folder while avoiding recently used ones when possible
523542
candidate_folders = self._filter_recent_folders(folders)
543+
if not candidate_folders:
544+
_LOGGER.warning("No folders available after recent-history filtering")
545+
return None
524546
selected_folder = random.choice(candidate_folders)
525-
_LOGGER.info("Selected random folder: %s", selected_folder["path"])
547+
_LOGGER.info(
548+
"Selected random folder: %s (%d photos)",
549+
selected_folder["path"],
550+
selected_folder.get("photo_count", -1),
551+
)
526552

527553
# Get photos from the selected folder
528554
photos = await self.get_folder_photos(selected_folder["path"])

custom_components/sharepoint_photos/config_flow.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@
1515
CONF_CLIENT_ID,
1616
CONF_CLIENT_SECRET,
1717
CONF_FOLDER_HISTORY_SIZE,
18+
CONF_MIN_PHOTO_COUNT,
1819
CONF_LIBRARY_NAME,
1920
CONF_SITE_URL,
2021
CONF_TENANT_ID,
2122
DEFAULT_BASE_FOLDER_PATH,
2223
DEFAULT_FOLDER_HISTORY_SIZE,
24+
DEFAULT_MIN_PHOTO_COUNT,
2325
DEFAULT_LIBRARY_NAME,
2426
DOMAIN,
2527
ERROR_AUTH_FAILED,
@@ -58,6 +60,7 @@ async def async_step_user(
5860
library_name=user_input.get(CONF_LIBRARY_NAME, DEFAULT_LIBRARY_NAME),
5961
base_folder_path=user_input.get(CONF_BASE_FOLDER_PATH, DEFAULT_BASE_FOLDER_PATH),
6062
recent_history_size=user_input.get(CONF_FOLDER_HISTORY_SIZE, DEFAULT_FOLDER_HISTORY_SIZE),
63+
min_photos_per_folder=user_input.get(CONF_MIN_PHOTO_COUNT, DEFAULT_MIN_PHOTO_COUNT),
6164
)
6265

6366
try:
@@ -94,6 +97,10 @@ async def async_step_user(
9497
CONF_FOLDER_HISTORY_SIZE,
9598
default=DEFAULT_FOLDER_HISTORY_SIZE
9699
): vol.All(vol.Coerce(int), vol.Range(min=0, max=200)),
100+
vol.Optional(
101+
CONF_MIN_PHOTO_COUNT,
102+
default=DEFAULT_MIN_PHOTO_COUNT
103+
): vol.All(vol.Coerce(int), vol.Range(min=1, max=500)),
97104
}),
98105
errors=errors,
99106
)
@@ -145,5 +152,12 @@ async def async_step_init(
145152
self.config_entry.data.get(CONF_FOLDER_HISTORY_SIZE, DEFAULT_FOLDER_HISTORY_SIZE)
146153
),
147154
): vol.All(vol.Coerce(int), vol.Range(min=0, max=200)),
155+
vol.Optional(
156+
CONF_MIN_PHOTO_COUNT,
157+
default=self.config_entry.options.get(
158+
CONF_MIN_PHOTO_COUNT,
159+
self.config_entry.data.get(CONF_MIN_PHOTO_COUNT, DEFAULT_MIN_PHOTO_COUNT)
160+
),
161+
): vol.All(vol.Coerce(int), vol.Range(min=1, max=500)),
148162
}),
149163
)

custom_components/sharepoint_photos/const.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@
1212
CONF_BASE_FOLDER_PATH = "base_folder_path"
1313
CONF_REFRESH_INTERVAL = "refresh_interval"
1414
CONF_FOLDER_HISTORY_SIZE = "folder_history_size"
15+
CONF_MIN_PHOTO_COUNT = "min_photo_count"
1516

1617
# Default values
1718
DEFAULT_LIBRARY_NAME = "Freigegebene Dokumente" # German SharePoint default
1819
DEFAULT_BASE_FOLDER_PATH = "/General/Fotos"
1920
DEFAULT_REFRESH_INTERVAL = 6 # hours
2021
DEFAULT_FOLDER_HISTORY_SIZE = 30
22+
DEFAULT_MIN_PHOTO_COUNT = 5
2123

2224
# Microsoft Graph API
2325
GRAPH_API_BASE = "https://graph.microsoft.com/v1.0"

custom_components/sharepoint_photos/strings.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
"site_url": "SharePoint Site URL",
1212
"library_name": "Document Library Name",
1313
"base_folder_path": "Base Folder Path",
14-
"folder_history_size": "Recent Folder History Size"
14+
"folder_history_size": "Recent Folder History Size",
15+
"min_photo_count": "Minimum Photos Per Folder"
1516
},
1617
"data_description": {
1718
"tenant_id": "Your Azure Active Directory tenant ID (GUID)",
@@ -20,7 +21,8 @@
2021
"site_url": "Full URL to your SharePoint site (e.g., https://yourdomain.sharepoint.com/sites/photos)",
2122
"library_name": "Name of the document library containing your photos. Common values: 'Documents' (English), 'Freigegebene Dokumente' (German), 'Shared Documents'",
2223
"base_folder_path": "Path to the folder containing your photo folders (e.g., /Photos, /General/Fotos)",
23-
"folder_history_size": "Number of recently used folders to exclude when picking a new random folder (set to 0 to disable)"
24+
"folder_history_size": "Number of recently used folders to exclude when picking a new random folder (set to 0 to disable)",
25+
"min_photo_count": "Only include folders that contain at least this many photos (default 5)"
2426
}
2527
}
2628
},
@@ -45,7 +47,8 @@
4547
"data": {
4648
"library_name": "Document Library Name",
4749
"base_folder_path": "Base Folder Path",
48-
"folder_history_size": "Recent Folder History Size"
50+
"folder_history_size": "Recent Folder History Size",
51+
"min_photo_count": "Minimum Photos Per Folder"
4952
}
5053
}
5154
}

custom_components/sharepoint_photos/translations/en.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
"site_url": "SharePoint Site URL",
1212
"library_name": "Document Library Name",
1313
"base_folder_path": "Base Folder Path",
14-
"folder_history_size": "Recent Folder History Size"
14+
"folder_history_size": "Recent Folder History Size",
15+
"min_photo_count": "Minimum Photos Per Folder"
1516
},
1617
"data_description": {
1718
"tenant_id": "Your Azure Active Directory tenant ID (GUID)",
@@ -20,7 +21,8 @@
2021
"site_url": "Full URL to your SharePoint site (e.g., https://yourdomain.sharepoint.com/sites/photos)",
2122
"library_name": "Name of the document library containing your photos. Common values: 'Documents' (English), 'Freigegebene Dokumente' (German), 'Shared Documents'",
2223
"base_folder_path": "Path to the folder containing your photo folders (e.g., /Photos, /General/Fotos)",
23-
"folder_history_size": "Number of recently used folders to exclude when picking a new random folder (set to 0 to disable)"
24+
"folder_history_size": "Number of recently used folders to exclude when picking a new random folder (set to 0 to disable)",
25+
"min_photo_count": "Only include folders that contain at least this many photos (default 5)"
2426
}
2527
}
2628
},
@@ -45,7 +47,8 @@
4547
"data": {
4648
"library_name": "Document Library Name",
4749
"base_folder_path": "Base Folder Path",
48-
"folder_history_size": "Recent Folder History Size"
50+
"folder_history_size": "Recent Folder History Size",
51+
"min_photo_count": "Minimum Photos Per Folder"
4952
}
5053
}
5154
}

0 commit comments

Comments
 (0)