refactor directory handling

Signed-off-by: Jimmy Tanagra <jcode@tanagra.id.au>
pull/4718/head
Jimmy Tanagra 2025-05-12 11:11:37 +10:00 committed by Kai Kreuzer
parent d8c07d5601
commit ff2cafa690
6 changed files with 108 additions and 53 deletions

View File

@ -47,6 +47,9 @@ public class UpgradeTool {
private static final String OPT_LOG = "log";
private static final String OPT_FORCE = "force";
private static final String ENV_USERDATA = "OPENHAB_USERDATA";
private static final String ENV_CONF = "OPENHAB_CONF";
private static final List<Upgrader> UPGRADERS = List.of( //
new ItemUnitToMetadataUpgrader(), //
new JSProfileUpgrader(), //
@ -105,26 +108,15 @@ public class UpgradeTool {
System.exit(0);
}
String userdataDir = commandLine.hasOption(OPT_USERDATA_DIR) ? commandLine.getOptionValue(OPT_USERDATA_DIR)
: System.getenv("OPENHAB_USERDATA");
if (command == null && (userdataDir == null || userdataDir.isBlank())) {
println("Please either set the environment variable ${OPENHAB_USERDATA} or provide a directory through the --userdata option.");
System.exit(0);
return;
} else if (userdataDir != null) {
logger.info("Using userdataDir: {}", userdataDir);
Path upgradeJsonDatabasePath = Path.of(userdataDir, "jsondb", "org.openhab.core.tools.UpgradeTool");
upgradeRecords = new JsonStorage<>(upgradeJsonDatabasePath.toFile(), null, 5, 0, 0, List.of());
}
Path userdataPath = getPath("userdata", commandLine, OPT_USERDATA_DIR, ENV_USERDATA);
Path confPath = getPath("conf", commandLine, OPT_CONF_DIR, ENV_CONF);
String confDir = commandLine.hasOption(OPT_CONF_DIR) ? commandLine.getOptionValue(OPT_CONF_DIR)
: System.getenv("OPENHAB_CONF");
if (confDir == null || confDir.isBlank()) {
println("Please either set the environment variable ${OPENHAB_CONF} or provide a directory through the --conf option.");
System.exit(0);
return;
if (userdataPath != null) {
Path upgradeJsonDatabasePath = userdataPath
.resolve(Path.of("jsondb", "org.openhab.core.tools.UpgradeTool"));
upgradeRecords = new JsonStorage<>(upgradeJsonDatabasePath.toFile(), null, 5, 0, 0, List.of());
} else {
logger.info("Using confDir: {}", confDir);
logger.warn("Upgrade records storage is not initialized.");
}
UPGRADERS.forEach(upgrader -> {
@ -139,7 +131,7 @@ public class UpgradeTool {
}
try {
logger.info("Executing {}: {}", upgraderName, upgrader.getDescription());
if (upgrader.execute(userdataDir, confDir)) {
if (upgrader.execute(userdataPath, confPath)) {
updateUpgradeRecord(upgraderName);
}
} catch (Exception e) {
@ -154,6 +146,40 @@ public class UpgradeTool {
System.exit(0);
}
/**
* Returns the path to the given directory, either from the command line or from the environment variable.
* If neither is set, it defaults to a relative subdirectory of the given pathName ('./userdata' or './conf').
*
* @param pathName the name of the directory (e.g., "userdata" or "conf").
* @param commandLine a CommandLine instance.
* @param option the command line option for the directory (e.g., "userdata" or "conf").
* @param env the environment variable name for the directory (e.g., "OPENHAB_USERDATA" or "OPENHAB_CONF").
* @return the absolute path to the directory, or null if it does not exist.
*/
private static @Nullable Path getPath(String pathName, CommandLine commandLine, String option, String env) {
Path path = Path.of(pathName);
String optionValue = commandLine.getOptionValue(option);
String envValue = System.getenv(env);
if (optionValue != null && !optionValue.isBlank()) {
path = Path.of(optionValue);
} else if (envValue != null && !envValue.isBlank()) {
path = Path.of(envValue);
}
path = path.toAbsolutePath();
if (path.toFile().isDirectory()) {
return path;
} else {
logger.warn(
"The '{}' directory '{}' does not exist. Some tasks may fail. To set it, either set the environment variable ${{}} or provide a directory through the --{} option.",
pathName, path, env, option);
return null;
}
}
private static @Nullable String lastExecuted(String upgrader) {
JsonStorage<UpgradeRecord> records = upgradeRecords;
if (records != null) {
@ -161,8 +187,6 @@ public class UpgradeTool {
if (upgradeRecord != null) {
return upgradeRecord.executionDate;
}
} else {
logger.error("Upgrade records storage is not initialized.");
}
return null;
}
@ -172,8 +196,6 @@ public class UpgradeTool {
if (records != null) {
records.put(upgrader, new UpgradeRecord(ZonedDateTime.now()));
records.flush();
} else {
logger.error("Upgrade records storage is not initialized.");
}
}

View File

@ -12,11 +12,14 @@
*/
package org.openhab.core.tools;
import java.nio.file.Path;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link Upgrader} provides an interface for upgrading openHAB configuration files.
*
*
* Implementing class MUST provide a no-argument constructor.
*
* @author Jimmy Tanagra - Initial contribution
@ -30,11 +33,11 @@ public interface Upgrader {
/**
* Executes the upgrade process.
*
* @param userdataDir the OPENHAB_USERDATA directory for the upgrade,
* @param userdataPath the OPENHAB_USERDATA directory for the upgrade,
* or a custom path given by the user as --userdata argument
* @param confDir the OPENHAB_CONF directory for the upgrade,
* @param confPath the OPENHAB_CONF directory for the upgrade,
* or a custom path given by the user as --conf argument
* @return true if the upgrade was successful, false otherwise
*/
boolean execute(String userdataDir, String confDir);
boolean execute(@Nullable Path userdataPath, @Nullable Path confPath);
}

View File

@ -24,6 +24,7 @@ import java.util.Objects;
import javax.measure.Unit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.items.ManagedItemProvider;
import org.openhab.core.items.Metadata;
import org.openhab.core.items.MetadataKey;
@ -57,13 +58,19 @@ public class ItemUnitToMetadataUpgrader implements Upgrader {
}
@Override
public boolean execute(String userdataDir, String confDir) {
public boolean execute(@Nullable Path userdataPath, @Nullable Path confPath) {
if (userdataPath == null) {
logger.error("{} skipped: no userdata directory found.", getName());
return false;
}
userdataPath = userdataPath.resolve("jsondb");
boolean noLink;
Path itemJsonDatabasePath = Path.of(userdataDir, "jsondb", "org.openhab.core.items.Item.json");
Path metadataJsonDatabasePath = Path.of(userdataDir, "jsondb", "org.openhab.core.items.Metadata.json");
Path linkJsonDatabasePath = Path.of(userdataDir, "jsondb", "org.openhab.core.thing.link.ItemChannelLink.json");
Path thingJsonDatabasePath = Path.of(userdataDir, "jsondb", "org.openhab.core.thing.Thing.json");
Path itemJsonDatabasePath = userdataPath.resolve("org.openhab.core.items.Item.json");
Path metadataJsonDatabasePath = userdataPath.resolve("org.openhab.core.items.Metadata.json");
Path linkJsonDatabasePath = userdataPath.resolve("org.openhab.core.thing.link.ItemChannelLink.json");
Path thingJsonDatabasePath = userdataPath.resolve("org.openhab.core.thing.Thing.json");
logger.info("Copying item unit from state description to metadata in database '{}'", itemJsonDatabasePath);
if (!Files.isReadable(itemJsonDatabasePath)) {

View File

@ -18,6 +18,7 @@ import java.util.List;
import java.util.Objects;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.storage.json.internal.JsonStorage;
import org.openhab.core.thing.internal.link.ItemChannelLinkConfigDescriptionProvider;
@ -47,8 +48,15 @@ public class JSProfileUpgrader implements Upgrader {
}
@Override
public boolean execute(String userdataDir, String confDir) {
Path linkJsonDatabasePath = Path.of(userdataDir, "jsondb", "org.openhab.core.thing.link.ItemChannelLink.json");
public boolean execute(@Nullable Path userdataPath, @Nullable Path confPath) {
if (userdataPath == null) {
logger.error("{} skipped: no userdata directory found.", getName());
return false;
}
userdataPath = userdataPath.resolve("jsondb");
Path linkJsonDatabasePath = userdataPath.resolve("org.openhab.core.thing.link.ItemChannelLink.json");
logger.info("Upgrading JS profile configuration in database '{}'", linkJsonDatabasePath);
if (!Files.isWritable(linkJsonDatabasePath)) {

View File

@ -18,6 +18,7 @@ import java.util.List;
import java.util.Objects;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.storage.json.internal.JsonStorage;
import org.openhab.core.thing.internal.link.ItemChannelLinkConfigDescriptionProvider;
@ -31,7 +32,7 @@ import org.slf4j.LoggerFactory;
* for the separation of {@code toHandlerScript} into
* {@code commandFromItemScript} and {@code stateFromItemScript}.
* See <a href="https://github.com/openhab/openhab-core/pull/4058">openhab/openhab-core#4058</a>.
*
*
* @author Florian Hotze - Initial contribution
* @author Jimmy Tanagra - Refactored into a separate class
*/
@ -50,8 +51,14 @@ public class ScriptProfileUpgrader implements Upgrader {
}
@Override
public boolean execute(String userdataDir, String confDir) {
Path linkJsonDatabasePath = Path.of(userdataDir, "jsondb", "org.openhab.core.thing.link.ItemChannelLink.json");
public boolean execute(@Nullable Path userdataPath, @Nullable Path confPath) {
if (userdataPath == null) {
logger.error("{} skipped: no userdata directory found.", getName());
return false;
}
Path linkJsonDatabasePath = userdataPath
.resolve(Path.of("jsondb", "org.openhab.core.thing.link.ItemChannelLink.json"));
logger.info("Upgrading script profile configuration in database '{}'", linkJsonDatabasePath);
if (!Files.isWritable(linkJsonDatabasePath)) {

View File

@ -20,6 +20,7 @@ import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.tools.Upgrader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -37,16 +38,16 @@ import com.fasterxml.jackson.dataformat.yaml.YAMLParser;
/**
* The {@link YamlConfigurationV1TagsUpgrader} upgrades YAML Tags Configuration from List to Map.
*
*
* Convert list to map format for tags in V1 configuration files.
*
*
* Input file criteria:
* - Search only in CONF/tags/, or in the given directory, and its subdirectories
* - Contains a version key with value 1
* - it must contain a tags key that is a list
* - The tags list must contain a uid key
* - If the above criteria are not met, the file will not be modified
*
*
* Output file will
* - Retain `version: 1`
* - convert tags list to a map with uid as key and the rest as map
@ -54,9 +55,9 @@ import com.fasterxml.jackson.dataformat.yaml.YAMLParser;
* - other keys will be unchanged
* - A backup of the original file will be created with the extension `.yaml.org`
* - If an .org file already exists, append a number to the end, e.g. `.org.1`
*
*
* @since 5.0.0
*
*
* @author Jimmy Tanagra - Initial contribution
*/
@NonNullByDefault
@ -96,16 +97,22 @@ public class YamlConfigurationV1TagsUpgrader implements Upgrader {
}
@Override
public boolean execute(String userdataDir, String confDir) {
String confEnv = System.getenv("OPENHAB_CONF");
// If confDir is set to OPENHAB_CONF, look inside /tags/ subdirectory
// otherwise use the given confDir as is
if (confEnv != null && !confEnv.isBlank() && confEnv.equals(confDir)) {
confDir = Path.of(confEnv, "tags").toString();
public boolean execute(@Nullable Path userdataPath, @Nullable Path confPath) {
if (confPath == null) {
logger.error("{} skipped: no conf directory found.", getName());
return false;
}
Path configPath = Path.of(confDir).toAbsolutePath();
logger.info("Upgrading YAML tags configurations in '{}'", configPath);
String confEnv = System.getenv("OPENHAB_CONF");
// If confPath is set to OPENHAB_CONF, look inside /tags/ subdirectory
// otherwise use the given confPath as is
if (confEnv != null && !confEnv.isBlank() && Path.of(confEnv).toAbsolutePath().equals(confPath)) {
confPath = confPath.resolve("tags");
}
logger.info("Upgrading YAML tags configurations in '{}'", confPath);
Path configPath = confPath; // make configPath "effectively final" inside the lambda below
try {
Files.walkFileTree(configPath, new SimpleFileVisitor<>() {
@Override
@ -115,7 +122,6 @@ public class YamlConfigurationV1TagsUpgrader implements Upgrader {
Path relativePath = configPath.relativize(file);
String modelName = relativePath.toString();
if (!relativePath.startsWith("automation") && modelName.endsWith(".yaml")) {
logger.info("Checking {}", file);
convertTagsListToMap(file);
}
}
@ -142,15 +148,17 @@ public class YamlConfigurationV1TagsUpgrader implements Upgrader {
JsonNode versionNode = fileContent.get(VERSION);
if (versionNode == null || !versionNode.canConvertToInt() || versionNode.asInt() != 1) {
logger.debug("{} skipped: it doesn't contain a version key", filePath);
return;
}
JsonNode tagsNode = fileContent.get("tags");
if (tagsNode == null || !tagsNode.isArray()) {
logger.debug("{} skipped: it doesn't contain a 'tags' array.", filePath);
return;
}
logger.info("Found v1 yaml file with tags list {}", filePath);
logger.debug("{} found containing v1 yaml file with a 'tags' array", filePath);
fileContent.properties().forEach(entry -> {
String key = entry.getKey();
JsonNode node = entry.getValue();
@ -187,7 +195,7 @@ public class YamlConfigurationV1TagsUpgrader implements Upgrader {
try {
Files.move(filePath, backupPath);
Files.writeString(filePath, content);
logger.info("Converted {} to map format, and the original file saved as {}", filePath, backupPath);
logger.info("{} converted to map format, and the original file saved as {}", filePath, backupPath);
} catch (IOException e) {
logger.error("Failed to save YAML file {}: {}", filePath, e.getMessage());
}