Skip to content

Commit 32dfba6

Browse files
author
FolderView Plus Test
committed
Fix custom icon endpoint locking on Unraid flash storage
1 parent cb1975e commit 32dfba6

10 files changed

Lines changed: 34 additions & 8 deletions

archive/folderview.plus-2026.04.11.02.txz.sha256

Lines changed: 0 additions & 1 deletion
This file was deleted.

archive/folderview.plus-2026.04.11.03.txz.sha256

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
9e78dfac913144b03e796da49d79b81e0168bb768b3a51eb022607c88f027050 folderview.plus-2026.04.14.14.txz
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3543a28255e17209ae7525d232318aa05508a49d23011b510193abc0b574c051 folderview.plus-2026.04.14.15.txz

docs/releases/2026.04.14.14.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
###2026.04.14.14
2+
- Fix: Custom icon manager requests now keep their lock file under `/tmp` instead of the flash-backed icon storage path.
3+
- Fix: Custom icon list, stats, and usage reads now use shared locks so the manager can load reliably on Unraid.

folderview.plus.plg

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,18 @@
66
<!ENTITY launch "Settings/FolderViewPlus">
77
<!ENTITY plugdir "/usr/local/emhttp/plugins/&name;">
88
<!ENTITY pluginURL "https://raw.githubusercontent.com/&github;/dev/folderview.plus.plg">
9-
<!ENTITY version "2026.04.14.13">
10-
<!ENTITY md5 "07376d047fc962390c36ae7661cd5085">
9+
<!ENTITY version "2026.04.14.15">
10+
<!ENTITY md5 "fe4f555eb370a953b773daedc4949ad6">
1111
]>
1212

1313
<PLUGIN name="&name;" author="&author;" version="&version;" launch="&launch;" pluginURL="&pluginURL;" icon="folder-icon.png" support="https://forums.unraid.net/topic/197631-plugin-folderview-plus/" min="7.0.0">
1414
<CHANGES>
1515

16+
###2026.04.14.15
17+
- UX: Icon picker, bundled icon packs, and custom icon management.
18+
- Docs: Project documentation and support guidance.
19+
20+
1621
###2026.04.14.13
1722
- UX: Folder editor flows, previews, and bootstrap behavior.
1823
- UX: Icon picker, bundled icon packs, and custom icon management.

src/folderview.plus/usr/local/emhttp/plugins/folderview.plus/server/upload_custom_icon.php

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,15 @@ function customIconDirectoryHealth(): array {
114114
}
115115

116116
function customIconLockPath(): string {
117-
return customIconDirPath() . '/.upload.lock';
117+
$tmpRoot = function_exists('sys_get_temp_dir') ? trim((string)sys_get_temp_dir()) : '/tmp';
118+
if ($tmpRoot === '') {
119+
$tmpRoot = '/tmp';
120+
}
121+
$normalized = rtrim(str_replace('\\', '/', $tmpRoot), '/');
122+
if ($normalized === '') {
123+
$normalized = '/tmp';
124+
}
125+
return $normalized . '/folderview.plus-custom-icons.lock';
118126
}
119127

120128
function withCustomIconLock(bool $exclusive, callable $callback) {
@@ -1326,7 +1334,7 @@ function handleCustomIconUploadAction(): array {
13261334
}
13271335

13281336
function handleCustomIconListAction(): array {
1329-
return withCustomIconLock(true, static function (): array {
1337+
return withCustomIconLock(false, static function (): array {
13301338
$customDir = ensureCustomIconDirExists(false);
13311339
$search = trim((string)($_REQUEST['query'] ?? ''));
13321340
$sort = trim((string)($_REQUEST['sort'] ?? 'newest'));
@@ -1341,7 +1349,7 @@ function handleCustomIconListAction(): array {
13411349
}
13421350

13431351
function handleCustomIconStatsAction(): array {
1344-
return withCustomIconLock(true, static function (): array {
1352+
return withCustomIconLock(false, static function (): array {
13451353
$customDir = ensureCustomIconDirExists(false);
13461354
syncCustomIconMetadataIndex($customDir);
13471355
$usageMap = customIconUsageMap();
@@ -1441,7 +1449,7 @@ function handleCustomIconRenameAction(): array {
14411449
}
14421450

14431451
function handleCustomIconUsageAction(): array {
1444-
return withCustomIconLock(true, static function (): array {
1452+
return withCustomIconLock(false, static function (): array {
14451453
$name = normalizeCustomIconFileNameInput((string)($_REQUEST['name'] ?? ''));
14461454
$usageMap = customIconUsageMap();
14471455
$refs = is_array($usageMap[$name] ?? null) ? array_values($usageMap[$name]) : [];

tests/icon-endpoints.test.mjs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,13 +134,23 @@ test('upload endpoint stores metadata and supports dedupe or replace flows', ()
134134
test('upload endpoint includes self-heal directory checks and fix hints', () => {
135135
assert.match(uploadPhp, /function customIconDirectoryHealth\s*\(/);
136136
assert.match(uploadPhp, /function customIconRepairHintCommand\s*\(/);
137+
assert.match(uploadPhp, /function customIconLockPath\(\): string/);
138+
assert.match(uploadPhp, /sys_get_temp_dir/);
139+
assert.match(uploadPhp, /return \$normalized \. '\/folderview\.plus-custom-icons\.lock';/);
137140
assert.match(uploadPhp, /'runtimePath'\s*=>\s*\$runtimePath/);
138141
assert.match(uploadPhp, /'publicMode'\s*=>\s*\(string\)\(\$health\['publicMode'\] \?\? 'missing'\)/);
139142
assert.match(uploadPhp, /'migratedFileCount'\s*=>\s*max\(0,\s*\(int\)\(\$health\['migratedFileCount'\] \?\? 0\)\)/);
140143
assert.match(uploadPhp, /fvplusEnsureCustomIconStorageReady\(false\)/);
141144
assert.match(uploadPhp, /repairHint/);
142145
});
143146

147+
test('upload endpoint uses shared locks for read-only custom icon actions and tmp-backed lock storage', () => {
148+
assert.match(uploadPhp, /function handleCustomIconListAction\(\): array \{[\s\S]*?withCustomIconLock\(false,/);
149+
assert.match(uploadPhp, /function handleCustomIconStatsAction\(\): array \{[\s\S]*?withCustomIconLock\(false,/);
150+
assert.match(uploadPhp, /function handleCustomIconUsageAction\(\): array \{[\s\S]*?withCustomIconLock\(false,/);
151+
assert.match(uploadPhp, /function handleCustomIconUploadAction\(\): array \{[\s\S]*?withCustomIconLock\(true,/);
152+
});
153+
144154
test('upload and third-party endpoints share the same icon extension allowlist', () => {
145155
const thirdPartyExt = parsePhpStringArray(thirdPartyPhp, 'FVPLUS_THIRD_PARTY_ICON_EXTENSIONS');
146156
const uploadExt = parsePhpStringArray(uploadPhp, 'FVPLUS_CUSTOM_ICON_EXTENSIONS');

0 commit comments

Comments
 (0)