diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/LocalResourceController.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/LocalResourceController.java index 0c59fb308d..071c467b11 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/LocalResourceController.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/LocalResourceController.java @@ -22,6 +22,7 @@ import io.cloudbeaver.model.rm.RMUtils; import io.cloudbeaver.service.security.SMUtils; import io.cloudbeaver.service.sql.WebSQLConstants; import io.cloudbeaver.utils.WebAppUtils; +import io.cloudbeaver.utils.file.UniversalFileVisitor; import org.eclipse.core.runtime.IPath; import org.jkiss.code.NotNull; import org.jkiss.code.Nullable; @@ -45,6 +46,7 @@ import org.jkiss.dbeaver.runtime.DBWorkbench; import org.jkiss.utils.ArrayUtils; import org.jkiss.utils.CommonUtils; import org.jkiss.utils.IOUtils; +import org.jkiss.utils.Pair; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -57,6 +59,8 @@ import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; +import static org.jkiss.utils.StringUtils.normalizeResourcePath; + /** * Resource manager API */ @@ -471,26 +475,57 @@ public class LocalResourceController implements RMController { @NotNull String oldResourcePath, @NotNull String newResourcePath ) throws DBException { - Path oldTargetPath = getTargetPath(projectId, oldResourcePath); + var normalizedOldResourcePath = normalizeResourcePath(oldResourcePath); + var normalizedNewResourcePath = normalizeResourcePath(newResourcePath); + if (log.isDebugEnabled()) { + log.debug("Moving resource from '" + normalizedOldResourcePath + "' to '" + normalizedNewResourcePath + "'"); + } + Path oldTargetPath = getTargetPath(projectId, normalizedOldResourcePath); List rmOldResourcePath = makeResourcePath(projectId, oldTargetPath, false); if (!Files.exists(oldTargetPath)) { throw new DBException("Resource '" + oldTargetPath + "' doesn't exists"); } - Path newTargetPath = getTargetPath(projectId, newResourcePath); + Path newTargetPath = getTargetPath(projectId, normalizedNewResourcePath); validateResourcePath(newTargetPath.toString()); try { Files.move(oldTargetPath, newTargetPath); } catch (IOException e) { - throw new DBException("Error moving resource '" + oldResourcePath + "'", e); + throw new DBException("Error moving resource '" + normalizedOldResourcePath + "'", e); + } + + log.debug("Moving resource properties"); + try { + movePropertiesRecursive(projectId, newTargetPath, normalizedOldResourcePath, normalizedNewResourcePath); + } catch (IOException | DBException e) { + throw new DBException("Unable to move resource properties", e); } - // Move properties - getProjectMetadata(projectId, false).moveResourceProperties(oldResourcePath, newResourcePath); fireRmResourceDeleteEvent(projectId, rmOldResourcePath); - fireRmResourceAddEvent(projectId, newResourcePath); + fireRmResourceAddEvent(projectId, normalizedNewResourcePath); return DEFAULT_CHANGE_ID; } + private void movePropertiesRecursive( + @NotNull String projectId, + @NotNull Path rootResourcePath, + @NotNull String oldRootPropertiesPath, + @NotNull String newRootPropertiesPath + ) throws IOException, DBException { + var project = getProjectMetadata(projectId, false); + var projectPath = getProjectPath(projectId); + var propertiesPathsList = new ArrayList>(); + Files.walkFileTree(rootResourcePath, (UniversalFileVisitor) (path, attrs) -> { + var newResourcePropertiesPath = normalizeResourcePath(projectPath.relativize(path.toAbsolutePath()).toString()); + var oldResourcePropertiesPath = newResourcePropertiesPath.replace(newRootPropertiesPath, oldRootPropertiesPath); + propertiesPathsList.add(new Pair<>(oldResourcePropertiesPath, newResourcePropertiesPath)); + return FileVisitResult.CONTINUE; + }); + if (log.isDebugEnabled()) { + log.debug("Move resources properties:\n" + propertiesPathsList); + } + project.moveResourcePropertiesBatch(propertiesPathsList); + } + @Override public void deleteResource(@NotNull String projectId, @NotNull String resourcePath, boolean recursive) throws DBException { validateResourcePath(resourcePath); diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/utils/file/UniversalFileVisitor.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/utils/file/UniversalFileVisitor.java new file mode 100644 index 0000000000..1416c0cb4a --- /dev/null +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/utils/file/UniversalFileVisitor.java @@ -0,0 +1,55 @@ +/* + * DBeaver - Universal Database Manager + * Copyright (C) 2010-2023 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.cloudbeaver.utils.file; + +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.FileVisitor; +import java.nio.file.attribute.BasicFileAttributes; + +/** + * A simple file visitor with default behavior to visit all files and folders and perform the same operation on them and to re-throw I/O errors. + */ +@FunctionalInterface +public interface UniversalFileVisitor extends FileVisitor { + + FileVisitResult dirOrFileOperation(T dirOrFile, BasicFileAttributes attrs) throws IOException; + + @Override + default FileVisitResult preVisitDirectory(T dir, BasicFileAttributes attrs) throws IOException { + dirOrFileOperation(dir, attrs); + return FileVisitResult.CONTINUE; + } + + @Override + default FileVisitResult visitFile(T file, BasicFileAttributes attrs) throws IOException { + dirOrFileOperation(file, attrs); + return FileVisitResult.CONTINUE; + } + + @Override + default FileVisitResult visitFileFailed(T file, IOException exc) throws IOException { + throw exc; + } + + @Override + default FileVisitResult postVisitDirectory(T dir, IOException exc) throws IOException { + if (exc != null) + throw exc; + return FileVisitResult.CONTINUE; + } +}