Skip to content

Commit 40c58b1

Browse files
Add legacy folder.view2/view3 compatibility migration
1 parent afc4e23 commit 40c58b1

8 files changed

Lines changed: 182 additions & 19 deletions

File tree

192 KB
Binary file not shown.

folderview.plus.plg

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,20 @@
66
<!ENTITY launch "Settings/FolderViewPlus">
77
<!ENTITY plugdir "/usr/local/emhttp/plugins/&name;">
88
<!ENTITY pluginURL "https://raw.githubusercontent.com/&github;/main/folderview.plus.plg">
9-
<!ENTITY version "2026.03.06.5">
10-
<!ENTITY md5 "b610c5da34656583f446a7868612784c">
9+
<!ENTITY version "2026.03.06.6">
10+
<!ENTITY md5 "94dfbc5312d0459896a9ef0b8787545d">
1111
]>
1212

1313
<PLUGIN name="&name;" author="&author;" version="&version;" launch="&launch;" pluginURL="&pluginURL;" icon="folder-open-o" support="https://github.com/alexphillips-dev/FolderView-Plus/issues" min="7.0.0">
1414
<CHANGES>
1515

16+
###2026.03.06.6
17+
- Add compatibility migration for legacy Folder View installs:
18+
- auto-import folder data from `/boot/config/plugins/folder.view3` and `/boot/config/plugins/folder.view2` when `folderview.plus` data is empty/missing,
19+
- migrate legacy type prefs (`*.prefs.json`) when current prefs are still defaults.
20+
- Add legacy Docker label compatibility so folder assignment by label works with `folder.view3`, `folder.view2`, and `folder.view` labels in addition to `folderview.plus`.
21+
- Load custom override scripts/styles from legacy config directories so older customizations continue to apply after upgrade.
22+
1623
###2026.03.06.5
1724
- Restore diagnostics export mode popup so users can choose `Full` or `Sanitized` export at click time.
1825
- Start diagnostics file download immediately based on the selected popup action.
@@ -164,4 +171,3 @@
164171
</FILE>
165172

166173
</PLUGIN>
167-
Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
<?php
22
require_once("/usr/local/emhttp/plugins/folderview.plus/server/lib.php");
3-
$scripts = dirToArrayOfFiles(pathToMultiDimArray('/boot/config/plugins/folderview.plus/scripts'), "/\..*{$type}.*\.js$/", "/.*\.disabled$/");
4-
foreach ($scripts as $script) {
5-
echo "<script src=\"";
6-
autov($script['path']);
7-
echo "\"></script>";
3+
$seen = [];
4+
foreach (getCustomOverrideDirs('scripts') as $scriptsDir) {
5+
$scripts = dirToArrayOfFiles(pathToMultiDimArray($scriptsDir), "/\..*{$type}.*\.js$/", "/.*\.disabled$/");
6+
foreach ($scripts as $script) {
7+
if (!is_array($script) || empty($script['path']) || isset($seen[$script['path']])) {
8+
continue;
9+
}
10+
$seen[$script['path']] = true;
11+
echo "<script src=\"";
12+
autov($script['path']);
13+
echo "\"></script>";
14+
}
815
}
9-
?>
16+
?>

src/folderview.plus/usr/local/emhttp/plugins/folderview.plus/scripts/dashboard.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@ const utils = window.FolderViewPlusUtils || {
2929
};
3030
}
3131
};
32+
const FOLDER_LABEL_KEYS = ['folderview.plus', 'folder.view3', 'folder.view2', 'folder.view'];
33+
const getFolderLabelValue = (labels) => {
34+
const source = labels && typeof labels === 'object' ? labels : {};
35+
for (const key of FOLDER_LABEL_KEYS) {
36+
if (typeof source[key] === 'string' && source[key].trim() !== '') {
37+
return source[key].trim();
38+
}
39+
}
40+
return '';
41+
};
3242

3343
/**
3444
* Handles the creation of all folders
@@ -306,7 +316,7 @@ const createFolderDocker = (folder, id, position, order, containersInfo, folders
306316

307317
folder.containers = folder.containers.concat(order.filter(el => {
308318
const labels = containersInfo[el]?.Labels || {};
309-
return labels['folderview.plus'] === folder.name;
319+
return getFolderLabelValue(labels) === folder.name;
310320
}));
311321

312322
folder.containers = folder.containers.concat(utils.getAutoRuleMatches({

src/folderview.plus/usr/local/emhttp/plugins/folderview.plus/scripts/docker.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,16 @@ const utils = window.FolderViewPlusUtils || {
3535
};
3636
}
3737
};
38+
const FOLDER_LABEL_KEYS = ['folderview.plus', 'folder.view3', 'folder.view2', 'folder.view'];
39+
const getFolderLabelValue = (labels) => {
40+
const source = labels && typeof labels === 'object' ? labels : {};
41+
for (const key of FOLDER_LABEL_KEYS) {
42+
if (typeof source[key] === 'string' && source[key].trim() !== '') {
43+
return source[key].trim();
44+
}
45+
}
46+
return '';
47+
};
3848

3949
if (FOLDER_VIEW_DEBUG_MODE) {
4050
console.log('[FV3_DEBUG] docker.js loaded. FOLDER_VIEW_DEBUG_MODE is ON.');
@@ -281,7 +291,7 @@ const createFolder = (folder, id, positionInMainOrder, liveOrderArray, container
281291

282292
const labelMatches = orderSnapshotAtFolderStart.filter(el => {
283293
const labels = containersInfo[el]?.Labels || {};
284-
return labels['folderview.plus'] === folder.name && !combinedContainers.includes(el);
294+
return getFolderLabelValue(labels) === folder.name && !combinedContainers.includes(el);
285295
});
286296
labelMatches.forEach(match => combinedContainers.push(match));
287297

src/folderview.plus/usr/local/emhttp/plugins/folderview.plus/scripts/folder.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,17 @@ const DEFAULT_FOLDER_STATUS_COLORS = {
1313
paused: '#b8860b',
1414
stopped: '#ff4d4d'
1515
};
16+
const FOLDER_LABEL_KEYS = ['folderview.plus', 'folder.view3', 'folder.view2', 'folder.view'];
17+
18+
const getFolderLabelValue = (labels) => {
19+
const source = labels && typeof labels === 'object' ? labels : {};
20+
for (const key of FOLDER_LABEL_KEYS) {
21+
if (typeof source[key] === 'string' && source[key].trim() !== '') {
22+
return source[key].trim();
23+
}
24+
}
25+
return '';
26+
};
1627

1728
const rgbToHex = (rgb) => {
1829
rgb = rgb.slice(4, -1).split(', ');
@@ -56,10 +67,11 @@ resetStatusColorDefaults();
5667
let typeFilter;
5768
if (type === 'docker') {
5869
typeFilter = (e) => {
70+
const labels = e?.info?.Config?.Labels || {};
5971
return {
6072
'Name': e.info.Name,
61-
'Icon': e.info.Config.Labels['net.unraid.docker.icon'],
62-
'Label': e.info.Config.Labels['folderview.plus']
73+
'Icon': labels['net.unraid.docker.icon'],
74+
'Label': getFolderLabelValue(labels)
6375
}
6476
};
6577
} else if (type === 'vm') {

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

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@ function fv3_get_tailscale_fqdn_from_container(string $containerName): ?string {
8181
'paused' => '#b8860b',
8282
'stopped' => '#ff4d4d'
8383
];
84+
const FVPLUS_LEGACY_CONFIG_DIRS = [
85+
'/boot/config/plugins/folder.view3',
86+
'/boot/config/plugins/folder.view2',
87+
'/boot/config/plugins/folder.view'
88+
];
8489

8590
function ensureType(string $type): string {
8691
if (!in_array($type, FVPLUS_ALLOWED_TYPES, true)) {
@@ -99,13 +104,118 @@ function readInstalledVersion(): string {
99104
return $version === '' ? '0.0.0' : $version;
100105
}
101106

107+
function getLegacyConfigDirCandidates(): array {
108+
$candidates = [];
109+
foreach (FVPLUS_LEGACY_CONFIG_DIRS as $dir) {
110+
if (is_dir($dir)) {
111+
$candidates[] = $dir;
112+
}
113+
}
114+
return $candidates;
115+
}
116+
117+
function readJsonObjectFile(string $path): ?array {
118+
if (!file_exists($path)) {
119+
return null;
120+
}
121+
$decoded = @json_decode((string)@file_get_contents($path), true);
122+
return is_array($decoded) ? $decoded : null;
123+
}
124+
125+
function getLegacyMigrationMarkerPath(string $type, string $kind): string {
126+
global $configDir;
127+
$safeType = ensureType($type);
128+
$safeKind = $kind === 'prefs' ? 'prefs' : 'folders';
129+
return "$configDir/.legacy-migrated-$safeType-$safeKind";
130+
}
131+
132+
function hasLegacyMigrationMarker(string $type, string $kind): bool {
133+
return file_exists(getLegacyMigrationMarkerPath($type, $kind));
134+
}
135+
136+
function markLegacyMigrationComplete(string $type, string $kind): void {
137+
global $configDir;
138+
if (!is_dir($configDir)) {
139+
@mkdir($configDir, 0770, true);
140+
}
141+
@file_put_contents(getLegacyMigrationMarkerPath($type, $kind), gmdate('c'));
142+
}
143+
144+
function migrateLegacyTypeDataIfNeeded(string $type, string $kind): void {
145+
$type = ensureType($type);
146+
$safeKind = $kind === 'prefs' ? 'prefs' : 'folders';
147+
if (hasLegacyMigrationMarker($type, $safeKind)) {
148+
return;
149+
}
150+
151+
$targetPath = $safeKind === 'prefs' ? getTypePrefsPath($type) : getFolderFilePath($type);
152+
$targetData = readJsonObjectFile($targetPath);
153+
154+
// Keep existing non-empty data/prefs untouched.
155+
if ($safeKind === 'prefs' && is_array($targetData)) {
156+
if (normalizeTypePrefs($targetData) !== defaultTypePrefs()) {
157+
return;
158+
}
159+
}
160+
if ($safeKind === 'folders' && is_array($targetData) && count($targetData) > 0) {
161+
return;
162+
}
163+
164+
$legacyName = $safeKind === 'prefs' ? "$type.prefs.json" : "$type.json";
165+
foreach (getLegacyConfigDirCandidates() as $legacyDir) {
166+
$legacyPath = "$legacyDir/$legacyName";
167+
$legacyData = readJsonObjectFile($legacyPath);
168+
if (!is_array($legacyData)) {
169+
continue;
170+
}
171+
172+
if ($safeKind === 'prefs') {
173+
$legacyData = normalizeTypePrefs($legacyData);
174+
if ($legacyData === defaultTypePrefs()) {
175+
continue;
176+
}
177+
} elseif (count($legacyData) === 0) {
178+
continue;
179+
}
180+
181+
$parent = dirname($targetPath);
182+
if (!is_dir($parent)) {
183+
@mkdir($parent, 0770, true);
184+
}
185+
@file_put_contents($targetPath, json_encode($legacyData, JSON_UNESCAPED_SLASHES));
186+
markLegacyMigrationComplete($type, $safeKind);
187+
return;
188+
}
189+
}
190+
191+
function getCustomOverrideDirs(string $kind): array {
192+
global $configDir;
193+
$safeKind = $kind === 'styles' ? 'styles' : 'scripts';
194+
$dirs = [];
195+
196+
$currentDir = "$configDir/$safeKind";
197+
if (is_dir($currentDir)) {
198+
$dirs[] = $currentDir;
199+
}
200+
201+
foreach (getLegacyConfigDirCandidates() as $legacyDir) {
202+
$path = "$legacyDir/$safeKind";
203+
if (is_dir($path)) {
204+
$dirs[] = $path;
205+
}
206+
}
207+
208+
return array_values(array_unique($dirs));
209+
}
210+
102211
function getFolderFilePath(string $type): string {
103212
global $configDir;
104213
return "$configDir/$type.json";
105214
}
106215

107216
function readRawFolderMap(string $type): array {
108217
$type = ensureType($type);
218+
migrateLegacyTypeDataIfNeeded($type, 'folders');
109219
$path = getFolderFilePath($type);
110220
if (!file_exists($path)) {
111221
createFile($type);
@@ -200,6 +310,7 @@ function normalizeTypePrefs(array $prefs): array {
200310

201311
function readTypePrefs(string $type): array {
202312
$type = ensureType($type);
313+
migrateLegacyTypeDataIfNeeded($type, 'prefs');
203314
$path = getTypePrefsPath($type);
204315
$parent = dirname($path);
205316
if (!is_dir($parent)) {
Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
<?php
22
require_once("/usr/local/emhttp/plugins/folderview.plus/server/lib.php");
3-
$styles = dirToArrayOfFiles(pathToMultiDimArray('/boot/config/plugins/folderview.plus/styles'), "/\..*{$type}.*\.css$/", "/.*\.disabled$/");
4-
foreach ($styles as $style) {
5-
echo "<link rel=\"stylesheet\" href=\"";
6-
autov($style['path']);
7-
echo "\">";
3+
$seen = [];
4+
foreach (getCustomOverrideDirs('styles') as $stylesDir) {
5+
$styles = dirToArrayOfFiles(pathToMultiDimArray($stylesDir), "/\..*{$type}.*\.css$/", "/.*\.disabled$/");
6+
foreach ($styles as $style) {
7+
if (!is_array($style) || empty($style['path']) || isset($seen[$style['path']])) {
8+
continue;
9+
}
10+
$seen[$style['path']] = true;
11+
echo "<link rel=\"stylesheet\" href=\"";
12+
autov($style['path']);
13+
echo "\">";
14+
}
815
}
9-
?>
16+
?>

0 commit comments

Comments
 (0)