diff --git a/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/api/traversal/TreeNode.java b/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/api/traversal/TreeNode.java index 7afcc5254027..a249293250a3 100644 --- a/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/api/traversal/TreeNode.java +++ b/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/api/traversal/TreeNode.java @@ -1,9 +1,18 @@ package com.dotcms.api.traversal; +import static com.dotcms.common.AssetsUtils.isMarkedForDelete; +import static com.dotcms.common.AssetsUtils.isMarkedForPush; + +import com.dotcms.model.asset.AbstractAssetSync.PushType; +import com.dotcms.model.asset.AssetSync; import com.dotcms.model.asset.AssetView; +import com.dotcms.model.asset.FolderSync; import com.dotcms.model.asset.FolderView; + import java.util.ArrayList; import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -16,7 +25,7 @@ */ public class TreeNode { - private FolderView folder; + private FolderView folder; private final List children; private List assets; @@ -51,9 +60,15 @@ public TreeNode(final FolderView folder, final boolean ignoreAssets) { * @param mark the delete mark */ public void markForDelete(boolean mark) { + final Optional syncData = this.folder.sync(); + final FolderSync sync = syncData.map( + folderSync -> FolderSync.builder().from(folderSync) + .markedForDelete(mark).build() + ).orElseGet( + () -> FolderSync.builder().markedForDelete(mark).build() + ); this.folder = FolderView.builder().from(this.folder) - .markForDelete(mark) - //Here only for compatibility with the actual code will be removed later + .sync(sync) .build(); } @@ -128,63 +143,72 @@ public void assets(final List assets) { * parameter. */ public TreeNode cloneAndFilterAssets(final boolean live, final String language, - final boolean showEmptyFolders, - final boolean filterForPushChanges) { + final boolean showEmptyFolders, + final boolean filterForPushChanges) { - TreeNode newNode = new TreeNode(this.folder, true); + final TreeNode newNode = new TreeNode(this.folder, true); + boolean includeAssets = includeAssets(); // Clone and filter assets based on the status and language - boolean includeAssets = includeAssets(); if (includeAssets && this.assets != null) { List filteredAssets = this.assets.stream() - .filter(asset -> { - - if (live) { - return asset.live() && asset.lang().equalsIgnoreCase(language); - } - - return asset.working() && asset.lang().equalsIgnoreCase(language); - }) + .filter(filterAssetsPredicate(live, language)) .collect(Collectors.toList()); newNode.assets(filteredAssets); } - // Clone children without assets - for (TreeNode child : this.children) { - - // If we have an explicit rule to exclude this folder, we skip it - if (child.folder().explicitGlobExclude()) { - continue; + // Clone children without assets and apply filtering conditions + for (final TreeNode child : this.children) { + if (childShouldBeIncluded(child, showEmptyFolders, filterForPushChanges)) { + final TreeNode clonedChild = child.cloneAndFilterAssets(live, language, showEmptyFolders, filterForPushChanges); + newNode.addChild(clonedChild); } + } - TreeNode clonedChild = child.cloneAndFilterAssets(live, language, showEmptyFolders, filterForPushChanges); - - if (filterForPushChanges) { - - if (showEmptyFolders - || !clonedChild.assets.isEmpty() - || (child.folder().markForPush().isPresent() || child.folder().markForDelete().isPresent()) - || hasAssetsWithChangesInSubtree(clonedChild) - || hasAFolderWithChangesInSubtree(clonedChild)) { - - if (clonedChild.folder.implicitGlobInclude() || hasIncludeInSubtree(clonedChild)) { - newNode.addChild(clonedChild); - } - } - } else { + return newNode; + } - if (showEmptyFolders - || !clonedChild.assets.isEmpty() - || hasAssetsInSubtree(clonedChild)) { - if (clonedChild.folder.implicitGlobInclude() || hasIncludeInSubtree(clonedChild)) { - newNode.addChild(clonedChild); - } - } - } + /** + * Clones the current TreeNode and filters its assets based on the provided status and language. + * @param child + * @param showEmptyFolders + * @param filterForPushChanges + * @return + */ + private boolean childShouldBeIncluded(TreeNode child, boolean showEmptyFolders, boolean filterForPushChanges) { + if (child.folder().explicitGlobExclude()) { + return false; } + if (filterForPushChanges) { + return showEmptyFolders + || !child.assets.isEmpty() + || isMarkedForPush(child.folder()) + || isMarkedForDelete(child.folder()) + || hasAssetsWithChangesInSubtree(child) + || hasFolderWithChangesInSubtree(child) + || (child.folder.implicitGlobInclude() || hasIncludeInSubtree(child)); + } else { + return showEmptyFolders + || !child.assets.isEmpty() + || hasAssetsInSubtree(child) + || (child.folder.implicitGlobInclude() || hasIncludeInSubtree(child)); + } + } - return newNode; + /** + * Status and language filter predicate. + * @param live + * @param language + * @return + */ + private static Predicate filterAssetsPredicate(boolean live, String language) { + return asset -> { + if (live) { + return asset.live() && asset.lang().equalsIgnoreCase(language); + } + return asset.working() && asset.lang().equalsIgnoreCase(language); + }; } /** @@ -193,10 +217,10 @@ public TreeNode cloneAndFilterAssets(final boolean live, final String language, * @param showEmptyFolders A boolean indicating whether to include empty folders * @return a TreeNodeInfo object containing the collected statuses and languages */ - public TreeNodeInfo collectUniqueStatusesAndLanguages(final boolean showEmptyFolders) { + public TreeNodeInfo collectUniqueStatusAndLanguage(final boolean showEmptyFolders) { TreeNodeInfo nodeInfo = new TreeNodeInfo(); - collectUniqueStatusesAndLanguagesHelper(nodeInfo, showEmptyFolders); + internalCollectUniqueStatusAndLanguage(nodeInfo, showEmptyFolders); return nodeInfo; } @@ -206,43 +230,55 @@ public TreeNodeInfo collectUniqueStatusesAndLanguages(final boolean showEmptyFol * @param collectEmptyFoldersInfo A boolean indicating whether to include empty folders * @param nodeInfo A TreeNodeInfo object containing the collected statuses and languages */ - private void collectUniqueStatusesAndLanguagesHelper(TreeNodeInfo nodeInfo, final boolean collectEmptyFoldersInfo) { - + private void internalCollectUniqueStatusAndLanguage(TreeNodeInfo nodeInfo, final boolean collectEmptyFoldersInfo) { boolean includeAssets = includeAssets(); if (includeAssets && assets() != null) { - for (AssetView asset : assets()) { - - if (asset.live()) { - nodeInfo.addLiveLanguage(asset.lang()); - nodeInfo.incrementAssetsCount(); - } - if (asset.working()) { - nodeInfo.addWorkingLanguage(asset.lang()); - nodeInfo.incrementAssetsCount(); - } - - nodeInfo.addLanguage(asset.lang()); - } + assetsLangAndStatusInfo(nodeInfo, assets()); } + if(null != children()) { + childrenLangAndStatusInfo(nodeInfo, children(), collectEmptyFoldersInfo); + } + } - for (TreeNode child : children()) { - - // If we have an explicit rule to exclude this folder, we skip it - if (child.folder().explicitGlobExclude()) { - continue; + /** + * Collects unique statuses and languages from the current node's assets. + * @param nodeInfo + * @param children + * @param collectEmptyFoldersInfo + */ + private void childrenLangAndStatusInfo(final TreeNodeInfo nodeInfo, final List children, final boolean collectEmptyFoldersInfo) { + for (TreeNode child : children) { + if (shouldIncludeChild(child, collectEmptyFoldersInfo)) { + child.internalCollectUniqueStatusAndLanguage(nodeInfo, collectEmptyFoldersInfo); + nodeInfo.incrementFoldersCount(); } + } + } - if (collectEmptyFoldersInfo - || !child.assets().isEmpty() - || hasAssetsInSubtree(child)) { + // Helper method to determine if a child should be included + private boolean shouldIncludeChild(TreeNode child, boolean collectEmptyFoldersInfo) { + if (child.folder().explicitGlobExclude()) { + return false; + } - if (child.folder().implicitGlobInclude() || hasIncludeInSubtree(child)) { + if (collectEmptyFoldersInfo || !child.assets().isEmpty() || hasAssetsInSubtree(child)) { + return child.folder().implicitGlobInclude() || hasIncludeInSubtree(child); + } - child.collectUniqueStatusesAndLanguagesHelper(nodeInfo, collectEmptyFoldersInfo); + return false; + } - nodeInfo.incrementFoldersCount(); - } + private void assetsLangAndStatusInfo(final TreeNodeInfo nodeInfo, final List assets) { + for (AssetView asset : assets) { + if (asset.live()) { + nodeInfo.addLiveLanguage(asset.lang()); + nodeInfo.incrementAssetsCount(); + } + if (asset.working()) { + nodeInfo.addWorkingLanguage(asset.lang()); + nodeInfo.incrementAssetsCount(); } + nodeInfo.addLanguage(asset.lang()); } } @@ -251,10 +287,10 @@ private void collectUniqueStatusesAndLanguagesHelper(TreeNodeInfo nodeInfo, fina * * @return A TreeNodePushInfo object containing the collected push information. */ - public TreeNodePushInfo collectTreeNodePushInfo() { + public TreeNodePushInfo collectPushInfo() { var nodeInfo = new TreeNodePushInfo(); - collectTreeNodePushInfoHelper(nodeInfo); + internalCollectPushInfo(nodeInfo); return nodeInfo; } @@ -263,64 +299,57 @@ public TreeNodePushInfo collectTreeNodePushInfo() { * * @param nodeInfo A TreeNodePushInfo object containing the collected push information. */ - private void collectTreeNodePushInfoHelper(TreeNodePushInfo nodeInfo) { + private void internalCollectPushInfo(TreeNodePushInfo nodeInfo) { - boolean includeAssets = includeAssets(); - if (includeAssets && assets() != null) { - for (AssetView asset : assets()) { - - if (asset.markForPush().isPresent()) { - if (asset.markForPush().get()) { - - nodeInfo.incrementAssetsToPushCount(); - - if (asset.pushTypeNew().isPresent()) { - if (asset.pushTypeNew().get()) { - nodeInfo.incrementAssetsNewCount(); - } - } - if (asset.pushTypeModified().isPresent()) { - if (asset.pushTypeModified().get()) { - nodeInfo.incrementAssetsModifiedCount(); - } - } - } - } - - if (asset.markForDelete().isPresent()) { - if (asset.markForDelete().get()) { - nodeInfo.incrementAssetsToDeleteCount(); - } - } - } + if (includeAssets() && assets() != null) { + assetsPushInfo(nodeInfo, assets()); } + if(null != children()){ + childrenPushInfo(nodeInfo, children()); + } + } - for (TreeNode child : children()) { - + private void childrenPushInfo(TreeNodePushInfo nodeInfo, List children) { + for (TreeNode child : children) { // If we have an explicit rule to exclude this folder, we skip it if (child.folder().explicitGlobExclude()) { continue; } - if (child.folder().implicitGlobInclude() || hasIncludeInSubtree(child)) { - - child.collectTreeNodePushInfoHelper(nodeInfo); - - if (child.folder().markForPush().isPresent()) { - if (child.folder().markForPush().get()) { - nodeInfo.incrementFoldersToPushCount(); - } + child.internalCollectPushInfo(nodeInfo); + if(isMarkedForPush(child.folder())){ + nodeInfo.incrementFoldersToPushCount(); + } else if(isMarkedForDelete(child.folder())){ + nodeInfo.incrementFoldersToDeleteCount(); } + } + } + } - if (child.folder().markForDelete().isPresent()) { - if (child.folder().markForDelete().get()) { - nodeInfo.incrementFoldersToDeleteCount(); - } + private void assetsPushInfo(final TreeNodePushInfo nodeInfo, List assets) { + for (AssetView asset : assets) { + final Optional optional = asset.sync(); + if(optional.isEmpty()) { + continue; + } + final AssetSync meta = optional.get(); + if(meta.markedForPush()){ + nodeInfo.incrementAssetsToPushCount(); + final PushType pushType = meta.pushType(); + if(pushType == PushType.NEW) { + nodeInfo.incrementAssetsNewCount(); + } + if(pushType == PushType.MODIFIED) { + nodeInfo.incrementAssetsModifiedCount(); } + } else if(meta.markedForDelete()){ + nodeInfo.incrementAssetsToDeleteCount(); } } } + + /** * Determines whether the assets should be included for the current TreeNode based on its folder's * include and exclude rules. @@ -371,11 +400,9 @@ private boolean hasAssetsInSubtree(final TreeNode node) { */ private boolean hasAssetsWithChangesInSubtree(final TreeNode node) { - if (!node.assets().isEmpty()) { - for (var asset : node.assets()) { - if (asset.markForPush().isPresent() || asset.markForDelete().isPresent()) { - return true; - } + for (var asset : node.assets()) { + if (isMarkedForPush(asset) || isMarkedForDelete(asset)) { + return true; } } @@ -384,6 +411,7 @@ private boolean hasAssetsWithChangesInSubtree(final TreeNode node) { anyMatch(this::hasAssetsWithChangesInSubtree); } + /** * Recursively checks if the given node or any of its children contains a folder marked for push or delete. * @@ -391,11 +419,11 @@ private boolean hasAssetsWithChangesInSubtree(final TreeNode node) { * @return {@code true} if the node or any of its children contains a folder marked for push or * delete, {@code false} otherwise. */ - private boolean hasAFolderWithChangesInSubtree(final TreeNode node) { + private boolean hasFolderWithChangesInSubtree(final TreeNode node) { if (!node.children().isEmpty()) { for (var child : node.children()) { - if (child.folder().markForPush().isPresent() || child.folder().markForDelete().isPresent()) { + if (isMarkedForPush(child.folder()) || isMarkedForDelete(child.folder())) { return true; } } @@ -403,7 +431,7 @@ private boolean hasAFolderWithChangesInSubtree(final TreeNode node) { return node.children(). stream(). - anyMatch(this::hasAFolderWithChangesInSubtree); + anyMatch(this::hasFolderWithChangesInSubtree); } /** diff --git a/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/common/AbstractLocalPathStructure.java b/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/common/AbstractLocalPathStructure.java new file mode 100644 index 000000000000..f98fb32e0082 --- /dev/null +++ b/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/common/AbstractLocalPathStructure.java @@ -0,0 +1,48 @@ +package com.dotcms.common; + +import com.dotcms.model.annotation.ValueType; +import java.io.File; +import java.nio.file.Path; +import javax.annotation.Nullable; +import org.immutables.value.Value; +import org.immutables.value.Value.Default; +import org.immutables.value.Value.Derived; + +/** + * Represents the structure of a local file or directory path within the workspace. + */ +@ValueType +@Value.Immutable +public interface AbstractLocalPathStructure { + boolean isDirectory(); + String status(); + String language(); + String site(); + @Nullable + String fileName(); + String folderPath(); + Path filePath(); + @Default + default boolean languageExists() {return false;} + + @Derived + default String folderName() { + + final int nameCount = filePath().getNameCount(); + + String folderName = File.separator; + + if (nameCount > 1) { + folderName = filePath().subpath(nameCount - 1, nameCount).toString(); + } else if (nameCount == 1) { + folderName = filePath().subpath(0, nameCount).toString(); + } + + if (folderName.equalsIgnoreCase(this.site())) { + folderName = File.separator; + } + + return folderName; + } + +} diff --git a/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/common/AbstractRemotePathStructure.java b/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/common/AbstractRemotePathStructure.java new file mode 100644 index 000000000000..42a5fe679757 --- /dev/null +++ b/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/common/AbstractRemotePathStructure.java @@ -0,0 +1,68 @@ +package com.dotcms.common; + +import com.dotcms.model.annotation.ValueType; +import java.nio.file.Path; +import javax.annotation.Nullable; +import org.immutables.value.Value; +import org.immutables.value.Value.Derived; + +/** + * Represents the site, folder path and file name components of the parsed remote path. + */ +@ValueType +@Value.Immutable +public interface AbstractRemotePathStructure { + + /** + * The site component of the parsed path. + */ + String site(); + + /** + * The folder path component of the parsed path. + */ + Path folderPath(); + + /** + * The file name component of the parsed path. + */ + @Nullable + String fileName(); + + @Derived + default String folderName() { + + int nameCount = folderPath().getNameCount(); + + String folderName = "/"; + + if (nameCount > 1) { + folderName = folderPath().subpath(nameCount - 1, nameCount).toString(); + } else if (nameCount == 1) { + folderName = folderPath().subpath(0, nameCount).toString(); + } + + return folderName; + } + + + class Builder extends RemotePathStructure.Builder { + public Builder folder(Path path) { + super.fileName(null); + super.folderPath(path); + return this; + } + + public Builder asset(Path path) { + super.fileName(path.getFileName().toString()); + super.folderPath(path.getParent()); + return this; + } + + } + + static Builder builder() { + return new Builder(); + } + +} diff --git a/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/common/AssetsUtils.java b/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/common/AssetsUtils.java index 7d38ce66810d..fafe60394cdc 100644 --- a/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/common/AssetsUtils.java +++ b/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/common/AssetsUtils.java @@ -1,8 +1,12 @@ package com.dotcms.common; import static com.dotcms.common.LocationUtils.encodePath; - -import com.dotcms.model.config.Workspace; +import static com.dotcms.model.config.Workspace.FILES_NAMESPACE; +import com.dotcms.model.asset.AbstractAssetSync.PushType; +import com.dotcms.model.asset.AssetSync; +import com.dotcms.model.asset.AssetView; +import com.dotcms.model.asset.FolderSync; +import com.dotcms.model.asset.FolderView; import com.google.common.base.Strings; import java.io.File; import java.net.URI; @@ -12,6 +16,7 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; +import java.util.Optional; public class AssetsUtils { @@ -148,8 +153,20 @@ public static RemotePathStructure parseRemotePath(String remotePathToParse) { throw new IllegalArgumentException(error, e); } + final AbstractRemotePathStructure.Builder builder = AbstractRemotePathStructure.builder(); + // Represents the site and folder path components of the parsed path. - return new RemotePathStructure(site, dotCMSPath, isFolder); + if(isFolder){ + return builder + .site(site) + .folder(dotCMSPath) + .build(); + } else { + return builder + .site(site) + .asset(dotCMSPath) + .build(); + } } /** @@ -176,13 +193,13 @@ public static List parseRootPaths(File workspace, File source) { // Check if we are inside the workspace but also inside the files folder if (sourceCount > workspaceCount + 1) { if (!source.getAbsolutePath().startsWith( - workspace.getAbsolutePath() + File.separator + Workspace.FILES_NAMESPACE + File.separator + workspace.getAbsolutePath() + File.separator + FILES_NAMESPACE + File.separator )) { throw new IllegalArgumentException("Invalid source path. Source path must be inside the files folder or " + "at the root of the workspace"); } } else if (sourceCount == workspaceCount + 1) { - if (!source.getName().equals(Workspace.FILES_NAMESPACE)) { + if (!source.getName().equals(FILES_NAMESPACE)) { throw new IllegalArgumentException("Invalid source path. Source path must be inside the files folder or " + "at the root of the workspace"); } @@ -224,7 +241,7 @@ public static List parseRootPaths(File workspace, File source) { * @return a list of root paths */ private static List fromRootFolder(File source) { - return fromFilesFolder(new File(source, Workspace.FILES_NAMESPACE)); + return fromFilesFolder(new File(source, FILES_NAMESPACE)); } /** @@ -332,9 +349,9 @@ public static LocalPathStructure parseLocalPath(File workspace, File source) { var workspacePath = workspace.toPath(); var isDirectory = source.isDirectory(); - var pathStructureBuilder = new LocalPathStructure.Builder(); - pathStructureBuilder.withFilePath(sourcePath); - pathStructureBuilder.withIsDirectory(source.isDirectory()); + var pathStructureBuilder = LocalPathStructure.builder(); + pathStructureBuilder.filePath(sourcePath); + pathStructureBuilder.isDirectory(source.isDirectory()); var workspaceCount = workspacePath.getNameCount(); var sourceCount = sourcePath.getNameCount(); @@ -349,33 +366,33 @@ public static LocalPathStructure parseLocalPath(File workspace, File source) { // Finding the files section var sourcePart = sourcePath.getName(workspaceCount); - if (sourcePart.getFileName().toString().equals(Workspace.FILES_NAMESPACE)) { + if (sourcePart.getFileName().toString().equals(FILES_NAMESPACE)) { // Finding the status section var statusPart = sourcePath.getName(++workspaceCount); if (!Strings.isNullOrEmpty(statusPart.getFileName().toString())) { statusToBoolean(statusPart.getFileName().toString()); - pathStructureBuilder.withStatus(statusPart.getFileName().toString()); + pathStructureBuilder.status(statusPart.getFileName().toString()); // Finding the language section var languagePart = sourcePath.getName(++workspaceCount); if (!Strings.isNullOrEmpty(languagePart.getFileName().toString())) { - pathStructureBuilder.withLanguage(languagePart.getFileName().toString()); + pathStructureBuilder.language(languagePart.getFileName().toString()); // Finding the site section var site = sourcePath.getName(++workspaceCount); if (!Strings.isNullOrEmpty(site.getFileName().toString())) { - pathStructureBuilder.withSite(site.getFileName().toString()); + pathStructureBuilder.site(site.getFileName().toString()); var folderPath = File.separator; // Now calculate the folder path for (var i = workspaceCount + 1; i < sourcePath.getNameCount(); i++) { if (!isDirectory && i == sourcePath.getNameCount() - 1) { - pathStructureBuilder.withFileName(source.getName()); + pathStructureBuilder.fileName(source.getName()); continue; } @@ -383,7 +400,7 @@ public static LocalPathStructure parseLocalPath(File workspace, File source) { concat(sourcePath.getName(i).getFileName().toString()). concat(File.separator); } - pathStructureBuilder.withFolderPath(folderPath); + pathStructureBuilder.folderPath(folderPath); } } } @@ -392,218 +409,69 @@ public static LocalPathStructure parseLocalPath(File workspace, File source) { return pathStructureBuilder.build(); } + /** - * Represents the site, folder path and file name components of the parsed remote path. + * Checks if the given folder hs any sync metadata indicating that it must be removed + * @param folder + * @return */ - public static class RemotePathStructure { - - /** - * The site component of the parsed path. - */ - private final String site; - - /** - * The folder path component of the parsed path. - */ - private final Path folderPath; - - /** - * The file name component of the parsed path. - */ - private final String fileName; - - /** - * Constructs an RemotePathStructure object with the given site and folder path. - * - * @param site the site component - * @param path the folder path component - */ - public RemotePathStructure(String site, Path path, boolean isFolder) { - - this.site = site; - - if (isFolder) { - this.folderPath = path; - this.fileName = null; - } else { - this.folderPath = path.getParent(); - this.fileName = path.getFileName().toString(); - } - } - - public String site() { - return site; - } - - public Path folderPath() { - return folderPath; - } - - public String fileName() { - return fileName; - } - - public String folderName() { - - int nameCount = folderPath.getNameCount(); - - String folderName = "/"; - - if (nameCount > 1) { - folderName = folderPath.subpath(nameCount - 1, nameCount).toString(); - } else if (nameCount == 1) { - folderName = folderPath.subpath(0, nameCount).toString(); - } - - return folderName; - } - + public static boolean isMarkedForDelete(FolderView folder) { + return folder.sync().map(FolderSync::markedForDelete).orElse(false); } /** - * Represents the structure of a local file or directory path within the workspace. + * Checks if the given folder hs any sync metadata indicating that it must be pushed + * @param folder + * @return */ - public static class LocalPathStructure { - - private boolean isDirectory; - private String status; - private String language; - private String site; - private String fileName; - private String folderPath; - private Path filePath; - private boolean languageExists; - - private LocalPathStructure(Builder builder) { - this.isDirectory = builder.isDirectory; - this.status = builder.status; - this.language = builder.language; - this.site = builder.site; - this.fileName = builder.fileName; - this.folderPath = builder.folderPath; - this.filePath = builder.filePath; - this.languageExists = true; - } - - public boolean isDirectory() { - return isDirectory; - } - - public String status() { - return status; - } - - public String language() { - return language; - } - - public String site() { - return site; - } - - public String fileName() { - return fileName; - } - - public String folderPath() { - return folderPath; - } - - public Path filePath() { - return filePath; - } - - public String folderName() { - - int nameCount = filePath().getNameCount(); - - String folderName = File.separator; - - if (nameCount > 1) { - folderName = filePath().subpath(nameCount - 1, nameCount).toString(); - } else if (nameCount == 1) { - folderName = filePath().subpath(0, nameCount).toString(); - } - - if (folderName.equalsIgnoreCase(this.site())) { - folderName = File.separator; - } - - return folderName; - } + public static boolean isMarkedForPush(FolderView folder) { + return folder.sync().map(FolderSync::markedForPush).orElse(false); + } - public void setLanguageExists(boolean languageExists) { - this.languageExists = languageExists; - } + /** + * Checks if the given asset hs any sync metadata indicating that it must be removed + * @param asset + * @return + */ + public static boolean isMarkedForDelete(AssetView asset) { + return asset.sync().map(AssetSync::markedForDelete).orElse(false); + } - public boolean languageExists() { - return this.languageExists; - } + /** + * Checks if the given asset hs any sync metadata indicating that it must be pushed + * @param asset + * @return + */ + public static boolean isMarkedForPush(AssetView asset) { + return asset.sync().map(AssetSync::markedForPush).orElse(false); + } - @Override - public String toString() { - return "LocalPathStructure{" + - "status='" + status + '\'' + - ", language='" + language + '\'' + - ", site='" + site + '\'' + - ", fileName='" + fileName + '\'' + - ", folderPath='" + folderPath + '\'' + - ", filePath='" + filePath + '\'' + - ", isDirectory='" + isDirectory + '\'' + - '}'; + /** + * Checks if the given asset hs any sync metadata indicating that it must be pushed as existing but modified content + * @param asset + * @return + */ + public static boolean isPushModified(AssetView asset) { + final Optional optional = asset.sync(); + if (optional.isPresent()) { + final AssetSync sync = optional.get(); + return sync.markedForPush() && sync.pushType() == PushType.MODIFIED; } + return false; + } - public static class Builder { - private boolean isDirectory; - private String status; - private String language; - private String site; - private String fileName; - private String folderPath; - private Path filePath; - - public Builder() { - } - - public Builder withIsDirectory(boolean isDirectory) { - this.isDirectory = isDirectory; - return this; - } - - public Builder withStatus(String status) { - this.status = status; - return this; - } - - public Builder withLanguage(String language) { - this.language = language; - return this; - } - - public Builder withSite(String site) { - this.site = site; - return this; - } - - public Builder withFileName(String fileName) { - this.fileName = fileName; - return this; - } - - public Builder withFolderPath(String folderPath) { - this.folderPath = folderPath; - return this; - } - - public Builder withFilePath(Path filePath) { - this.filePath = filePath; - return this; - } - - public LocalPathStructure build() { - return new LocalPathStructure(this); - } + /** + * Checks if the given asset hs any sync metadata indicating that it must be pushed as new content + * @param asset + * @return + */ + public static boolean isPushNew(AssetView asset) { + final Optional optional = asset.sync(); + if (optional.isPresent()) { + final AssetSync sync = optional.get(); + return sync.markedForPush() && sync.pushType() == PushType.NEW; } + return false; } } diff --git a/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/model/asset/AbstractAssetSync.java b/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/model/asset/AbstractAssetSync.java new file mode 100644 index 000000000000..7127f27f5cba --- /dev/null +++ b/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/model/asset/AbstractAssetSync.java @@ -0,0 +1,25 @@ +package com.dotcms.model.asset; + +import com.dotcms.model.annotation.ValueType; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.immutables.value.Value; + +@ValueType +@Value.Immutable +@JsonDeserialize(as = AssetSync.class) +public interface AbstractAssetSync { + + @Value.Default + default boolean markedForPush(){return false;} + + @Value.Default + default boolean markedForDelete(){return false;} + + @Value.Default + default PushType pushType() {return PushType.UNKNOWN;} + + enum PushType { + NEW, MODIFIED, UNKNOWN + } + +} diff --git a/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/model/asset/AbstractAssetView.java b/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/model/asset/AbstractAssetView.java index 53907b511ddf..87fa996e9d10 100644 --- a/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/model/asset/AbstractAssetView.java +++ b/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/model/asset/AbstractAssetView.java @@ -38,12 +38,7 @@ public interface AbstractAssetView { Map metadata(); - Optional markForPush(); - - Optional pushTypeNew(); - - Optional pushTypeModified(); - - Optional markForDelete(); + @Auxiliary + Optional sync(); } diff --git a/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/model/asset/AbstractFolderSync.java b/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/model/asset/AbstractFolderSync.java new file mode 100644 index 000000000000..d57caa7b10c9 --- /dev/null +++ b/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/model/asset/AbstractFolderSync.java @@ -0,0 +1,26 @@ +package com.dotcms.model.asset; + +import com.dotcms.model.annotation.ValueType; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.immutables.value.Value; +import org.immutables.value.Value.Default; + +@ValueType +@Value.Immutable +@JsonDeserialize(as = FolderSync.class) +public interface AbstractFolderSync { + + String UNKNOWN = "unknown"; + + @Default + default boolean markedForPush(){return false;} + + @Default + default boolean markedForDelete(){return false;} + + @Default + default String localStatus(){return UNKNOWN;} + + @Default + default String localLanguage(){return UNKNOWN;} +} diff --git a/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/model/asset/AbstractFolderView.java b/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/model/asset/AbstractFolderView.java index bce9ecfba59c..ab21a90d9dba 100644 --- a/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/model/asset/AbstractFolderView.java +++ b/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/model/asset/AbstractFolderView.java @@ -9,6 +9,7 @@ import java.util.Optional; import javax.annotation.Nullable; import org.immutables.value.Value; +import org.immutables.value.Value.Auxiliary; @ValueType @Value.Immutable @@ -74,8 +75,6 @@ default boolean implicitGlobInclude() { @Nullable List subFolders(); - Optional markForPush(); - - Optional markForDelete(); - + @Auxiliary + Optional sync(); } diff --git a/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/files/PullFile.java b/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/files/PullFile.java index e649df961dca..e8fc08b0a241 100644 --- a/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/files/PullFile.java +++ b/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/files/PullFile.java @@ -58,7 +58,7 @@ public void pull(OutputOptionMixin output, final AssetVersionsView assetInfo, TreeNode tree = new TreeNode(folder); // Collect important information about the tree - final var treeNodeInfo = tree.collectUniqueStatusesAndLanguages(false); + final var treeNodeInfo = tree.collectUniqueStatusAndLanguage(false); output.info(String.format("\rStarting pull process for: " + "@|bold,green [%s]|@ Assets in " + diff --git a/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/files/PullFolder.java b/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/files/PullFolder.java index 6f0bf5ca1618..0ddfb3e59139 100644 --- a/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/files/PullFolder.java +++ b/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/files/PullFolder.java @@ -43,7 +43,7 @@ public void pull(OutputOptionMixin output, final TreeNode tree, final File desti } // Collect important information about the tree - final var treeNodeInfo = tree.collectUniqueStatusesAndLanguages(generateEmptyFolders); + final var treeNodeInfo = tree.collectUniqueStatusAndLanguage(generateEmptyFolders); output.info(String.format("\rStarting pull process for: " + "@|bold,green [%s]|@ Assets in " + diff --git a/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/files/PushService.java b/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/files/PushService.java index 1a1b2a1abbc5..53bfbc57db41 100644 --- a/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/files/PushService.java +++ b/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/files/PushService.java @@ -1,12 +1,11 @@ package com.dotcms.api.client.files; -import com.dotcms.api.client.files.traversal.AbstractTraverseResult; import com.dotcms.api.client.files.traversal.TraverseResult; import com.dotcms.api.traversal.TreeNode; import com.dotcms.api.traversal.TreeNodePushInfo; import com.dotcms.cli.common.OutputOptionMixin; import com.dotcms.common.AssetsUtils; - +import com.dotcms.common.LocalPathStructure; import java.io.File; import java.util.List; @@ -48,7 +47,7 @@ List traverseLocalFolders( * @throws RuntimeException if an error occurs during the push process */ void processTreeNodes(OutputOptionMixin output, String workspace, - AssetsUtils.LocalPathStructure localPathStructure, TreeNode treeNode, + LocalPathStructure localPathStructure, TreeNode treeNode, TreeNodePushInfo treeNodePushInfo, final boolean failFast, final int maxRetryAttempts); } diff --git a/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/files/PushServiceImpl.java b/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/files/PushServiceImpl.java index f5b294976753..3b53a07a4be2 100644 --- a/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/files/PushServiceImpl.java +++ b/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/files/PushServiceImpl.java @@ -4,31 +4,29 @@ import com.dotcms.api.client.files.traversal.LocalTraversalService; import com.dotcms.api.client.files.traversal.RemoteTraversalService; -import com.dotcms.api.client.files.traversal.AbstractTraverseResult; +import com.dotcms.api.client.files.traversal.TraverseParams; import com.dotcms.api.client.files.traversal.TraverseResult; import com.dotcms.api.client.files.traversal.exception.TraversalTaskException; -import com.dotcms.api.client.files.traversal.TraverseParams; import com.dotcms.api.client.push.exception.PushException; import com.dotcms.api.traversal.TreeNode; import com.dotcms.api.traversal.TreeNodePushInfo; import com.dotcms.cli.common.ConsoleProgressBar; import com.dotcms.cli.common.OutputOptionMixin; import com.dotcms.common.AssetsUtils; -import com.dotcms.common.AssetsUtils.LocalPathStructure; +import com.dotcms.common.LocalPathStructure; import io.quarkus.arc.DefaultBean; +import java.io.File; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; -import org.jboss.logging.Logger; - import javax.enterprise.context.Dependent; import javax.enterprise.context.control.ActivateRequestContext; import javax.inject.Inject; -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; +import org.jboss.logging.Logger; @DefaultBean @Dependent @@ -183,7 +181,7 @@ List findAllNodesWithTheSamePath(Map> in * @return */ boolean isAllFoldersMarkedForDelete(List nodes) { - return nodes.stream().map(TreeNode::folder).allMatch(folderView -> folderView.markForDelete().isPresent() && folderView.markForDelete().get()); + return nodes.stream().map(TreeNode::folder).allMatch(folderView -> folderView.sync().isPresent() && folderView.sync().get().markedForDelete()); } /** diff --git a/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/files/traversal/AbstractTraverseResult.java b/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/files/traversal/AbstractTraverseResult.java index 56c94504907f..13b19f55347d 100644 --- a/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/files/traversal/AbstractTraverseResult.java +++ b/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/files/traversal/AbstractTraverseResult.java @@ -1,7 +1,7 @@ package com.dotcms.api.client.files.traversal; import com.dotcms.api.traversal.TreeNode; -import com.dotcms.common.AssetsUtils.LocalPathStructure; +import com.dotcms.common.LocalPathStructure; import com.dotcms.model.annotation.ValueType; import java.util.List; import org.immutables.value.Value; diff --git a/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/files/traversal/LocalTraversalServiceImpl.java b/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/files/traversal/LocalTraversalServiceImpl.java index 7f6af153a862..072894fd70ae 100644 --- a/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/files/traversal/LocalTraversalServiceImpl.java +++ b/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/files/traversal/LocalTraversalServiceImpl.java @@ -9,6 +9,7 @@ import com.dotcms.api.traversal.TreeNode; import com.dotcms.cli.common.ConsoleProgressBar; import com.dotcms.common.AssetsUtils; +import com.dotcms.common.LocalPathStructure; import io.quarkus.arc.DefaultBean; import java.io.File; import java.nio.file.Paths; @@ -55,7 +56,7 @@ public TraverseResult traverseLocalFolder(final TraverseParams params) { logger.debug(String.format("Traversing file system folder: %s - in workspace: %s", source, workspace.getAbsolutePath())); - final var localPath = parseLocalPath(workspace, new File(source)); + var localPath = parseLocalPath(workspace, new File(source)); // Initial check to see if the site exist var siteExists = true; @@ -71,11 +72,10 @@ public TraverseResult traverseLocalFolder(final TraverseParams params) { // Checking if the language exist try { - localPath.setLanguageExists(true); retriever.retrieveLanguage(localPath.language()); + localPath = LocalPathStructure.builder().from(localPath).languageExists(true).build(); } catch (NotFoundException e) { - - localPath.setLanguageExists(false); + localPath = LocalPathStructure.builder().from(localPath).languageExists(false).build(); // Language doesn't exist on remote server logger.debug(String.format("Language [%s] doesn't exist on remote server.", localPath.language())); diff --git a/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/files/traversal/RemoteTraversalService.java b/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/files/traversal/RemoteTraversalService.java index 531562ac0d5a..daf4842f7ce7 100644 --- a/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/files/traversal/RemoteTraversalService.java +++ b/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/files/traversal/RemoteTraversalService.java @@ -2,11 +2,10 @@ import com.dotcms.api.traversal.TreeNode; import com.dotcms.cli.common.ConsoleProgressBar; -import com.dotcms.common.AssetsUtils; -import org.apache.commons.lang3.tuple.Pair; - +import com.dotcms.common.LocalPathStructure; import java.util.List; import java.util.Set; +import org.apache.commons.lang3.tuple.Pair; /** * Service for traversing a dotCMS remote location and building a hierarchical tree representation of @@ -54,7 +53,7 @@ Pair, TreeNode> traverseRemoteFolder( * @param progressBar the console progress bar to track and display the push progress * @return A list of exceptions encountered during the push process. */ - List pushTreeNode(String workspace, AssetsUtils.LocalPathStructure localPathStructure, TreeNode treeNode, + List pushTreeNode(String workspace, LocalPathStructure localPathStructure, TreeNode treeNode, final boolean failFast, final boolean isRetry, ConsoleProgressBar progressBar); } diff --git a/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/files/traversal/RemoteTraversalServiceImpl.java b/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/files/traversal/RemoteTraversalServiceImpl.java index 822f8acf5d30..876a30ea3921 100644 --- a/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/files/traversal/RemoteTraversalServiceImpl.java +++ b/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/files/traversal/RemoteTraversalServiceImpl.java @@ -8,6 +8,7 @@ import com.dotcms.api.traversal.TreeNode; import com.dotcms.cli.common.ConsoleProgressBar; import com.dotcms.common.AssetsUtils; +import com.dotcms.common.LocalPathStructure; import com.dotcms.model.asset.FolderView; import io.quarkus.arc.DefaultBean; import org.apache.commons.lang3.tuple.Pair; @@ -119,7 +120,7 @@ public Pair, TreeNode> traverseRemoteFolder( * @param progressBar the console progress bar to track and display the push progress * @return A list of exceptions encountered during the push process. */ - public List pushTreeNode(final String workspace, final AssetsUtils.LocalPathStructure localPathStructure, + public List pushTreeNode(final String workspace, final LocalPathStructure localPathStructure, final TreeNode treeNode, final boolean failFast, final boolean isRetry, ConsoleProgressBar progressBar) { diff --git a/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/files/traversal/task/LocalFolderTraversalTask.java b/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/files/traversal/task/LocalFolderTraversalTask.java index 6cd0eb924a39..613a3959ca28 100644 --- a/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/files/traversal/task/LocalFolderTraversalTask.java +++ b/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/files/traversal/task/LocalFolderTraversalTask.java @@ -9,10 +9,13 @@ import com.dotcms.api.client.files.traversal.exception.TraversalTaskException; import com.dotcms.api.traversal.TreeNode; import com.dotcms.cli.common.HiddenFileFilter; -import com.dotcms.common.AssetsUtils; -import com.dotcms.common.AssetsUtils.LocalPathStructure; +import com.dotcms.common.LocalPathStructure; +import com.dotcms.model.asset.AbstractAssetSync.PushType; +import com.dotcms.model.asset.AssetSync; import com.dotcms.model.asset.AssetVersionsView; import com.dotcms.model.asset.AssetView; +import com.dotcms.model.asset.FolderSync; +import com.dotcms.model.asset.FolderSync.Builder; import com.dotcms.model.asset.FolderView; import com.dotcms.security.Utils; import com.google.common.base.Strings; @@ -21,6 +24,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; +import java.util.Optional; import java.util.concurrent.RecursiveTask; import javax.ws.rs.NotFoundException; import org.apache.commons.lang3.tuple.Pair; @@ -116,7 +120,7 @@ protected Pair, TreeNode> compute() { * @return The TreeNode containing the synchronization information for the folder or file */ private TreeNode gatherSyncInformation(File workspaceFile, File folderOrFile, - AssetsUtils.LocalPathStructure localPathStructure) { + LocalPathStructure localPathStructure) { var live = statusToBoolean(localPathStructure.status()); var lang = localPathStructure.language(); @@ -185,10 +189,13 @@ private TreeNode gatherSyncInformation(File workspaceFile, File folderOrFile, "- New [%b] - Modified [%b].", localPathStructure.filePath(), live, lang, pushInfo.isNew(), pushInfo.isModified())); + var asset = assetViewFromFile(localPathStructure); - asset.markForPush(true). - pushTypeNew(pushInfo.isNew()). - pushTypeModified(pushInfo.isModified()); + final AssetSync syncData = AssetSync.builder(). + markedForPush(true). + pushType(pushInfo.pushType()). + build(); + asset.sync(syncData); assetVersions.addVersions( asset.build() ); @@ -242,11 +249,14 @@ private void checkAssetsToPush(File workspaceFile, boolean live, String lang, "- New [%b] - Modified [%b].", file.toPath(), live, lang, pushInfo.isNew(), pushInfo.isModified())); + final AssetSync syncData = AssetSync.builder() + .markedForPush(true) + .pushType(pushInfo.pushType()) + .build(); + assetVersionsBuilder.addVersions( assetViewFromFile(workspaceFile, file). - markForPush(true). - pushTypeNew(pushInfo.isNew()). - pushTypeModified(pushInfo.isModified()). + sync(syncData). build() ); } else { @@ -270,15 +280,17 @@ private void checkFolderToPush(FolderView.Builder folder, File[] folderFiles) { if (remoteFolder == null) { + boolean markForPush = false; if (params.ignoreEmptyFolders()) { if (folderFiles != null && folderFiles.length > 0) { // Does not exist on remote server, so we need to push it - folder.markForPush(true); + markForPush = true; } } else { // Does not exist on remote server, so we need to push it - folder.markForPush(true); + markForPush = true; } + folder.sync(FolderSync.builder().markedForPush(markForPush).build()); } } @@ -312,9 +324,16 @@ private void checkAssetsToRemove(boolean live, String lang, String.format("Marking file [%s] - live [%b] - lang [%s] for delete.", version.name(), live, lang)); - var copy = version.withMarkForDelete(true); - copy = copy.withLive(live); - copy = copy.withWorking(!live); + final Optional existingSyncData = version.sync(); + + final AssetSync.Builder builder = AssetSync.builder(); + existingSyncData.ifPresent(builder::from); + builder.markedForDelete(true); + + final AssetSync syncData = builder.build(); + var copy = version.withSync(syncData) + .withLive(live) + .withWorking(!live); assetVersions.addVersions(copy); } @@ -376,7 +395,10 @@ private void checkFoldersToRemove(boolean live, String lang, // Folder exist on remote server, but not locally, so we need to remove it logger.debug(String.format("Marking folder [%s] for delete.", subFolder.path())); if (params.removeFolders()) { - subFolder = subFolder.withMarkForDelete(true); + final Optional existingSyncData = subFolder.sync(); + final Builder builder = FolderSync.builder(); + existingSyncData.ifPresent(builder::from); + subFolder = subFolder.withSync(builder.markedForDelete(true).build()); } folder.addSubFolders(subFolder); } @@ -463,7 +485,7 @@ private AssetVersionsView retrieveAsset(LocalPathStructure localPathStructure) { * @param localPathStructure the local path structure * @return The FolderView representing the retrieved folder data, or null if it doesn't exist */ - private FolderView retrieveFolder(AssetsUtils.LocalPathStructure localPathStructure) { + private FolderView retrieveFolder(LocalPathStructure localPathStructure) { return retrieveFolder(localPathStructure.site(), localPathStructure.folderPath()); } @@ -615,7 +637,7 @@ private boolean matchByStatusAndLang(boolean live, String lang, AssetView versio * @param localPathStructure the local path structure * @return The FolderView.Builder representing the folder view */ - private FolderView.Builder folderViewFromFile(AssetsUtils.LocalPathStructure localPathStructure) { + private FolderView.Builder folderViewFromFile(LocalPathStructure localPathStructure) { return FolderView.builder() @@ -647,7 +669,7 @@ private AssetView.Builder assetViewFromFile(File workspaceFile, File file) { * @param localPathStructure the local path structure * @return The AssetView.Builder representing the asset view */ - private AssetView.Builder assetViewFromFile(AssetsUtils.LocalPathStructure localPathStructure) { + private AssetView.Builder assetViewFromFile(LocalPathStructure localPathStructure) { var metadata = new HashMap(); metadata.put(PATH_META_KEY.key(), localPathStructure.folderPath()); @@ -712,6 +734,14 @@ public boolean isModified() { return isModified; } + PushType pushType() { + PushType pushType = isNew() ? PushType.NEW : PushType.UNKNOWN; + if(pushType == PushType.UNKNOWN){ + pushType = isModified() ? PushType.MODIFIED : PushType.UNKNOWN; + } + return pushType; + } + } } \ No newline at end of file diff --git a/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/files/traversal/task/PushTreeNodeTask.java b/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/files/traversal/task/PushTreeNodeTask.java index e4613551e80a..c4a218096d66 100644 --- a/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/files/traversal/task/PushTreeNodeTask.java +++ b/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/files/traversal/task/PushTreeNodeTask.java @@ -1,21 +1,23 @@ package com.dotcms.api.client.files.traversal.task; +import static com.dotcms.common.AssetsUtils.isMarkedForDelete; +import static com.dotcms.common.AssetsUtils.isMarkedForPush; + import com.dotcms.api.client.files.traversal.data.Pusher; import com.dotcms.api.client.files.traversal.exception.SiteCreationException; import com.dotcms.api.client.files.traversal.exception.TraversalTaskException; import com.dotcms.api.traversal.TreeNode; import com.dotcms.cli.common.ConsoleProgressBar; -import com.dotcms.common.AssetsUtils; +import com.dotcms.common.LocalPathStructure; import com.dotcms.model.asset.AssetView; import com.dotcms.model.asset.FolderView; -import org.jboss.logging.Logger; - -import javax.ws.rs.NotFoundException; -import javax.ws.rs.WebApplicationException; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.concurrent.RecursiveTask; +import javax.ws.rs.NotFoundException; +import javax.ws.rs.WebApplicationException; +import org.jboss.logging.Logger; /** * Represents a task that pushes the contents of a tree node to a remote server. @@ -26,7 +28,7 @@ public class PushTreeNodeTask extends RecursiveTask> { private final String workspacePath; - private final AssetsUtils.LocalPathStructure localPathStructure; + private final LocalPathStructure localPathStructure; private final TreeNode rootNode; @@ -51,7 +53,7 @@ public class PushTreeNodeTask extends RecursiveTask> { * @param progressBar the console progress bar to track and display the push progress */ public PushTreeNodeTask(String workspacePath, - AssetsUtils.LocalPathStructure localPathStructure, + LocalPathStructure localPathStructure, TreeNode rootNode, final boolean failFast, final boolean isRetry, @@ -142,76 +144,76 @@ protected List compute() { */ private void processFolder(final FolderView folder) { - if (folder.markForDelete().isPresent() || folder.markForPush().isPresent()) { + if (isMarkedForDelete(folder)) {// Delete + doDeleteFolder(folder); + } else if (isMarkedForPush(folder)) {// Push + doPushFolder(folder); + } - if (folder.markForDelete().isPresent() && folder.markForDelete().get()) {// Delete + } - try { + private void doPushFolder(FolderView folder) { + var isSite = Objects.equals(folder.path(), "/") && Objects.equals(folder.name(), "/"); + try { + if (isSite) { + // And we need to create the non-existing site + pusher.pushSite(folder.host(), this.localPathStructure.status()); + logger.debug(String.format("Site [%s] created", folder.host())); + } else { - pusher.deleteFolder(folder.host(), folder.path()); - logger.debug(String.format("Folder [%s] deleted", folder.path())); - } catch (Exception e) { + // Creating the non-existing folder + pusher.createFolder(folder.host(), folder.path()); + logger.debug(String.format("Folder [%s] created", folder.path())); + } + } catch (Exception e) { + var message = String.format("Error creating %s [%s]", + isSite ? "site" : "folder", + isSite ? folder.host() : folder.path()); + logger.debug(message, e); + if (isSite) { + + // Using the exception to check if the site already exist + var alreadyExist = checkIfSiteAlreadyExist(e); + + // If we are trying to create a site that already exist we could ignore the error on retries + if (!this.isRetry || !alreadyExist) { + logger.error(message, e); + throw new SiteCreationException(message, e); + } + } else { + // Using the exception to check if the folder already exist + var alreadyExist = checkIfAssetOrFolderAlreadyExist(e); - // If we are trying to delete a folder that does not exist anymore we could ignore the error on retries - if (!this.isRetry || !(e instanceof NotFoundException)) { + // If we are trying to create a folder that already exist we could ignore the error on retries + if (!this.isRetry || !alreadyExist) { + logger.error(message, e); + throw new TraversalTaskException(message, e); + } + } + } finally { + // Folder processed, updating the progress bar + this.progressBar.incrementStep(); + } + } - var message = String.format("Error deleting folder [%s]", folder.path()); - logger.error(message, e); - throw new TraversalTaskException(message, e); - } + private void doDeleteFolder(FolderView folder) { + try { - } finally { - // Folder processed, updating the progress bar - this.progressBar.incrementStep(); - } - } else if (folder.markForPush().isPresent() && folder.markForPush().get()) {// Push - - var isSite = Objects.equals(folder.path(), "/") && Objects.equals(folder.name(), "/"); - - try { - - if (isSite) { - - // And we need to create the non-existing site - pusher.pushSite(folder.host(), this.localPathStructure.status()); - logger.debug(String.format("Site [%s] created", folder.host())); - } else { - - // Creating the non-existing folder - pusher.createFolder(folder.host(), folder.path()); - logger.debug(String.format("Folder [%s] created", folder.path())); - } - } catch (Exception e) { - var message = String.format("Error creating %s [%s]", - isSite ? "site" : "folder", - isSite ? folder.host() : folder.path()); - logger.debug(message, e); - if (isSite) { - - // Using the exception to check if the site already exist - var alreadyExist = checkIfSiteAlreadyExist(e); - - // If we are trying to create a site that already exist we could ignore the error on retries - if (!this.isRetry || !alreadyExist) { - logger.error(message, e); - throw new SiteCreationException(message, e); - } - } else { - - // Using the exception to check if the folder already exist - var alreadyExist = checkIfAssetOrFolderAlreadyExist(e); - - // If we are trying to create a folder that already exist we could ignore the error on retries - if (!this.isRetry || !alreadyExist) { - logger.error(message, e); - throw new TraversalTaskException(message, e); - } - } - } finally { - // Folder processed, updating the progress bar - this.progressBar.incrementStep(); - } + pusher.deleteFolder(folder.host(), folder.path()); + logger.debug(String.format("Folder [%s] deleted", folder.path())); + } catch (Exception e) { + + // If we are trying to delete a folder that does not exist anymore we could ignore the error on retries + if (!this.isRetry || !(e instanceof NotFoundException)) { + + var message = String.format("Error deleting folder [%s]", folder.path()); + logger.error(message, e); + throw new TraversalTaskException(message, e); } + + } finally { + // Folder processed, updating the progress bar + this.progressBar.incrementStep(); } } @@ -222,64 +224,63 @@ private void processFolder(final FolderView folder) { * @param asset the asset to process */ private void processAsset(final FolderView folder, final AssetView asset) { + if (isMarkedForDelete(asset)) { + doDeleteAsset(folder, asset); + } else if (isMarkedForPush(asset)) { + doPushAsset(folder, asset); + } + } - if (asset.markForDelete().isPresent() || asset.markForPush().isPresent()) { - - if (asset.markForDelete().isPresent() && asset.markForDelete().get()) { - - try { - - // Check if we already deleted the folder - if ((folder.markForDelete().isPresent() && folder.markForDelete().get())) { - - // Folder already deleted, we don't need to delete the asset - logger.debug(String.format("Folder [%s] already deleted, ignoring deletion of [%s] asset", - folder.path(), asset.name())); - } else { - - pusher.archive(folder.host(), folder.path(), asset.name()); - logger.debug(String.format("Asset [%s] deleted", asset.name())); - } - } catch (Exception e) { - - // If we are trying to delete an asset that does not exist anymore we could ignore the error on retries - if (!this.isRetry || !(e instanceof NotFoundException)) { + private void doPushAsset(FolderView folder, AssetView asset) { + try { - var message = String.format("Error deleting asset [%s%s]", folder.path(), asset.name()); - logger.error(message, e); - throw new TraversalTaskException(message, e); - } + pusher.push(this.workspacePath, this.localPathStructure.status(), localPathStructure.language(), + localPathStructure.site(), folder.path(), asset.name()); + logger.debug(String.format("Asset [%s%s] pushed", folder.path(), asset.name())); + } catch (Exception e) { - } finally { - // Asset processed, updating the progress bar - this.progressBar.incrementStep(); - } + // Using the exception to check if the asset already exist + var alreadyExist = checkIfAssetOrFolderAlreadyExist(e); - } else if (asset.markForPush().isPresent() && asset.markForPush().get()) { + // If we are trying to push an asset that already exist we could ignore the error on retries + if (!this.isRetry || !alreadyExist) { + var message = String.format("Error pushing asset [%s%s]", folder.path(), asset.name()); + logger.error(message, e); + throw new TraversalTaskException(message, e); + } - try { + } finally { + // Asset processed, updating the progress bar + this.progressBar.incrementStep(); + } + } - pusher.push(this.workspacePath, this.localPathStructure.status(), localPathStructure.language(), - localPathStructure.site(), folder.path(), asset.name()); - logger.debug(String.format("Asset [%s%s] pushed", folder.path(), asset.name())); - } catch (Exception e) { + private void doDeleteAsset(FolderView folder, AssetView asset) { + try { - // Using the exception to check if the asset already exist - var alreadyExist = checkIfAssetOrFolderAlreadyExist(e); + // Check if we already deleted the folder + if (isMarkedForDelete(folder)) { + // Folder already deleted, we don't need to delete the asset + logger.debug(String.format("Folder [%s] already deleted, ignoring deletion of [%s] asset", + folder.path(), asset.name())); + } else { - // If we are trying to push an asset that already exist we could ignore the error on retries - if (!this.isRetry || !alreadyExist) { - var message = String.format("Error pushing asset [%s%s]", folder.path(), asset.name()); - logger.error(message, e); - throw new TraversalTaskException(message, e); - } + pusher.archive(folder.host(), folder.path(), asset.name()); + logger.debug(String.format("Asset [%s] deleted", asset.name())); + } + } catch (Exception e) { - } finally { - // Asset processed, updating the progress bar - this.progressBar.incrementStep(); - } + // If we are trying to delete an asset that does not exist anymore we could ignore the error on retries + if (!this.isRetry || !(e instanceof NotFoundException)) { + var message = String.format("Error deleting asset [%s%s]", folder.path(), asset.name()); + logger.error(message, e); + throw new TraversalTaskException(message, e); } + + } finally { + // Asset processed, updating the progress bar + this.progressBar.incrementStep(); } } diff --git a/tools/dotcms-cli/cli/src/main/java/com/dotcms/cli/command/PushCommand.java b/tools/dotcms-cli/cli/src/main/java/com/dotcms/cli/command/PushCommand.java index 9f79f8ea458a..6f2daf77df1e 100644 --- a/tools/dotcms-cli/cli/src/main/java/com/dotcms/cli/command/PushCommand.java +++ b/tools/dotcms-cli/cli/src/main/java/com/dotcms/cli/command/PushCommand.java @@ -50,20 +50,13 @@ public class PushCommand implements Callable, DotCommand { @Inject protected WorkspaceManager workspaceManager; + @Inject + Instance pushCommands; - /** - * The Resolved DotPush command instances - * But this also allows for mocking push commands - * @return the resolved push commands or mocked instances - */ - Iterable pushCommands(){ - return CDI.current().select(DotPush.class); - } @Override public Integer call() throws Exception { // Find the instances of all push subcommands - Iterable pushCommands = pushCommands(); // Checking for unmatched arguments output.throwIfUnmatchedArguments(spec.commandLine()); diff --git a/tools/dotcms-cli/cli/src/main/java/com/dotcms/cli/command/files/FilesPush.java b/tools/dotcms-cli/cli/src/main/java/com/dotcms/cli/command/files/FilesPush.java index 3b1a5e6b4176..f47738bb5048 100644 --- a/tools/dotcms-cli/cli/src/main/java/com/dotcms/cli/command/files/FilesPush.java +++ b/tools/dotcms-cli/cli/src/main/java/com/dotcms/cli/command/files/FilesPush.java @@ -5,7 +5,6 @@ import static com.dotcms.cli.command.files.TreePrinter.COLOR_NEW; import com.dotcms.api.client.files.PushService; -import com.dotcms.api.client.files.traversal.AbstractTraverseResult; import com.dotcms.api.client.files.traversal.TraverseResult; import com.dotcms.api.traversal.TreeNode; import com.dotcms.api.traversal.TreeNodePushInfo; @@ -15,7 +14,7 @@ import com.dotcms.cli.common.OutputOptionMixin; import com.dotcms.cli.common.PushMixin; import com.dotcms.common.AssetsUtils; -import com.dotcms.common.AssetsUtils.LocalPathStructure; +import com.dotcms.common.LocalPathStructure; import java.util.List; import java.util.Optional; import java.util.concurrent.Callable; @@ -116,7 +115,7 @@ public Integer call() throws Exception { header(count++, localPaths, outputBuilder); - var treeNodePushInfo = treeNode.collectTreeNodePushInfo(); + var treeNodePushInfo = treeNode.collectPushInfo(); if (treeNodePushInfo.hasChanges()) { diff --git a/tools/dotcms-cli/cli/src/main/java/com/dotcms/cli/command/files/TreePrinter.java b/tools/dotcms-cli/cli/src/main/java/com/dotcms/cli/command/files/TreePrinter.java index cfb41d5b01b9..6e0aa1c7c254 100644 --- a/tools/dotcms-cli/cli/src/main/java/com/dotcms/cli/command/files/TreePrinter.java +++ b/tools/dotcms-cli/cli/src/main/java/com/dotcms/cli/command/files/TreePrinter.java @@ -1,5 +1,9 @@ package com.dotcms.cli.command.files; +import static com.dotcms.common.AssetsUtils.isMarkedForDelete; +import static com.dotcms.common.AssetsUtils.isMarkedForPush; +import static com.dotcms.common.AssetsUtils.isPushNew; + import com.dotcms.api.traversal.TreeNode; import com.dotcms.cli.common.FilesUtils; import com.dotcms.common.AssetsUtils; @@ -79,7 +83,7 @@ public void filteredFormat(StringBuilder sb, final List languages) { // Collect the list of unique statuses and languages - final var treeNodeInfo = rootNode.collectUniqueStatusesAndLanguages(showEmptyFolders); + final var treeNodeInfo = rootNode.collectUniqueStatusAndLanguage(showEmptyFolders); final var uniqueLiveLanguages = treeNodeInfo.liveLanguages(); final var uniqueWorkingLanguages = treeNodeInfo.workingLanguages(); @@ -151,12 +155,11 @@ public void formatByStatus(StringBuilder sb, boolean isLive, List sorted append('\n'); var siteFormat = SITE_REGULAR_FORMAT; - if (rootNode.folder().markForPush().isPresent()) { - if (rootNode.folder().markForPush().get()) { - siteFormat = SITE_PUSH_FORMAT; - } + if (isMarkedForPush(rootNode.folder())) { + siteFormat = SITE_PUSH_FORMAT; } + // Add the domain and parent folder sb.append(" "). append((isLastLang ? " " : "│ ")). @@ -214,16 +217,16 @@ private void format(StringBuilder sb, String prefix, final TreeNode node, final String indent, boolean isLastSibling, boolean includeAssets) { var folderFormat = FOLDER_REGULAR_FORMAT; - if (node.folder().markForDelete().isPresent()) { - if (node.folder().markForDelete().get()) { - folderFormat = FOLDER_DELETE_FORMAT; - } - } else if (node.folder().markForPush().isPresent()) { - if (node.folder().markForPush().get()) { - folderFormat = FOLDER_PUSH_FORMAT; - } + + if (isMarkedForDelete(node.folder())) { + folderFormat = FOLDER_DELETE_FORMAT; } + if (isMarkedForPush(node.folder())) { + folderFormat = FOLDER_PUSH_FORMAT; + } + + String filePrefix; String nextIndent; if (!node.folder().name().equals("/")) { @@ -248,24 +251,19 @@ private void format(StringBuilder sb, String prefix, final TreeNode node, AssetView asset = node.assets().get(i); var assetFormat = ASSET_REGULAR_FORMAT; - if (asset.markForDelete().isPresent()) { - if (asset.markForDelete().get()) { + if (isMarkedForDelete(asset)) { assetFormat = ASSET_DELETE_FORMAT; } - } - if (asset.markForPush().isPresent()) { - if (asset.markForPush().get()) { + if (isMarkedForPush(asset)) { assetFormat = ASSET_PUSH_MODIFIED_FORMAT; - if (asset.pushTypeNew().isPresent()) { - if (asset.pushTypeNew().get()) { + if (isPushNew(asset)) { assetFormat = ASSET_PUSH_NEW_FORMAT; - } } } - } + boolean lastAsset = i == assetCount - 1 && node.children().isEmpty(); @@ -313,7 +311,7 @@ private String calculateRootParentPath(TreeNode rootNode) { replaceAll("/$", ""); } - if (!emptyFolderName) { + if (!emptyFolderName && !emptyFolderPath) { if (folderPath.endsWith(folderName)) { int folderIndex = folderPath.lastIndexOf(folderName); diff --git a/tools/dotcms-cli/cli/src/test/java/com/dotcms/api/client/files/PushServiceIT.java b/tools/dotcms-cli/cli/src/test/java/com/dotcms/api/client/files/PushServiceIT.java index 1963d67fa20b..338eb20588dd 100644 --- a/tools/dotcms-cli/cli/src/test/java/com/dotcms/api/client/files/PushServiceIT.java +++ b/tools/dotcms-cli/cli/src/test/java/com/dotcms/api/client/files/PushServiceIT.java @@ -116,7 +116,7 @@ void Test_Nothing_To_Push() throws IOException { Assertions.assertTrue( traversalResult.get(1).exceptions().isEmpty());// No errors should be found var treeNode = traversalResult.get(0).treeNode(); - var treeNodePushInfo = treeNode.collectTreeNodePushInfo(); + var treeNodePushInfo = treeNode.collectPushInfo(); // Should be nothing to push as we are pushing the same folder we pull Assertions.assertEquals(0, treeNodePushInfo.assetsToPushCount()); @@ -194,7 +194,7 @@ void Test_Push_New_Site() throws IOException { Assertions.assertTrue(traversalResult.get(1).exceptions().isEmpty());// No errors should be found var treeNode = traversalResult.get(0).treeNode(); - var treeNodePushInfo = treeNode.collectTreeNodePushInfo(); + var treeNodePushInfo = treeNode.collectPushInfo(); // Should be nothing to push as we are pushing the same folder we pull Assertions.assertEquals(5, treeNodePushInfo.assetsToPushCount()); @@ -333,7 +333,7 @@ void Test_Push_Modified_Data() throws IOException { Assertions.assertTrue(traversalResult.get(1).exceptions().isEmpty());// No errors should be found var treeNode = traversalResult.get(0).treeNode(); - var treeNodePushInfo = treeNode.collectTreeNodePushInfo(); + var treeNodePushInfo = treeNode.collectPushInfo(); // Should be nothing to push as we are pushing the same folder we pull Assertions.assertEquals(3, treeNodePushInfo.assetsToPushCount()); @@ -449,7 +449,7 @@ void Test_Delete_Folder() throws IOException { true, true, true, true); var treeNode1 = traversalResultLiveRemoved.get(0).treeNode(); - var treeNodePushInfo1 = treeNode1.collectTreeNodePushInfo(); + var treeNodePushInfo1 = treeNode1.collectPushInfo(); //This is zero because there is still another folder hanging under the "working" branch which needs to be removed Assertions.assertEquals(0, treeNodePushInfo1.foldersToDeleteCount()); @@ -461,7 +461,7 @@ void Test_Delete_Folder() throws IOException { true, true, true, true); var treeNode2 = traversalResultWorkingRemoved.get(0).treeNode(); - var treeNodePushInfo2 = treeNode2.collectTreeNodePushInfo(); + var treeNodePushInfo2 = treeNode2.collectPushInfo(); //Now we should expect this to be 1, because both folder are removed Assertions.assertEquals(1, treeNodePushInfo2.foldersToDeleteCount()); diff --git a/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/PushCommandIT.java b/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/PushCommandIT.java index 6f8e09768295..6566ab6cd978 100644 --- a/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/PushCommandIT.java +++ b/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/PushCommandIT.java @@ -178,7 +178,7 @@ void testAllPushCommandsAreCalled() throws Exception { when(pushCommands.iterator()). thenReturn(Arrays.asList(dotPush1, dotPush2, dotPush3).iterator()); - when(pushCommand.pushCommands()).thenReturn(pushCommands); + pushCommand.pushCommands = pushCommands; doReturn(commandLine). when(pushCommand).