From 7d05bd1928f488736a34cb04131c2c85f5097a41 Mon Sep 17 00:00:00 2001 From: CodeLtDave Date: Mon, 23 Oct 2023 14:54:23 +0200 Subject: [PATCH] Proposal: Opening/Closing Mechanism for Zip Files The Eclipse IDE has no built in functionality to open Zip Files and read or manipulate their content. Because of this, other operations like searching inside of Zip Files or comparing two Zip Files were also not possible. This pull request introduces a mechanism for handling Zip Files within the Eclipse workspace, enhancing the functionality to read and write Zip files. The primary goal is to provide a seamless experience for developers working with zip archives directly within Eclipse. Zip files must be opened manually within the workspace by using the new command "Open Zip File" in the menu when right clicking the zip file. It is also possible to open nested zip files. Zip Files are opened by replacing the file in the workspace with a linked folder that reads and writes the Zip File in the file system. By closing the Zip FIle, the linked folder will be deleted and the file can be seen in the workspace again. Please note that only ZIP Archives are supported in this current implementation. Other archive types can be added in future improvements. Also linked Zip Files can not be opened with this implementation because the Zip File must be local. An additional PR for the repository **eclipse.platform.ui** that grants access to the open/close mechanism for zip files over UI can be found in the following: https: //github.com/eclipse-platform/eclipse.platform/pull/1413 Co-Authored-By: David <42917573+CodeLtDave@users.noreply.github.com> --- .../META-INF/MANIFEST.MF | 2 +- .../org.eclipse.core.filesystem/plugin.xml | 7 + .../eclipse/core/filesystem/ZipFileUtil.java | 114 ++++ .../internal/filesystem/zip/ZipFileStore.java | 559 ++++++++++++++++++ .../filesystem/zip/ZipFileSystem.java | 21 + .../META-INF/MANIFEST.MF | 8 +- .../org.eclipse.core.resources/plugin.xml | 6 + .../propertytester/ZipFilePropertyTester.java | 73 +++ .../core/internal/resources/ResourceTree.java | 80 ++- .../org/eclipse/core/resources/IResource.java | 9 + .../core/resources/ZipFileTransformer.java | 113 ++++ .../plugin.xml | 77 +-- .../internal/filesystem/zip/ZipFileStore.java | 245 -------- .../zip/ZipFileSystemContributor.java | 82 --- .../filesystem/CollapseZipHandler.java | 79 --- .../examples/filesystem/ExpandZipHandler.java | 66 --- .../META-INF/MANIFEST.MF | 2 + .../resources/ZipFileSystem/BasicText.zip | Bin 0 -> 126 bytes .../resources/ZipFileSystem/BasicTextNew.zip | Bin 0 -> 126 bytes .../resources/ZipFileSystem/DeepNested.zip | Bin 0 -> 1268 bytes .../resources/ZipFileSystem/Empty.zip | Bin 0 -> 22 bytes .../resources/ZipFileSystem/Fake.zip | 1 + .../ZipFileSystem/NestedZipFileParent.zip | Bin 0 -> 1478 bytes .../ZipFileSystem/PasswordProtected.zip | Bin 0 -> 173 bytes .../filesystem/zip/AllZipFileSystemTests.java | 26 + .../core/tests/filesystem/zip/CloseTest.java | 52 ++ .../core/tests/filesystem/zip/CopyTest.java | 211 +++++++ .../core/tests/filesystem/zip/CreateTest.java | 64 ++ .../core/tests/filesystem/zip/DeleteTest.java | 92 +++ .../core/tests/filesystem/zip/MoveTest.java | 298 ++++++++++ .../core/tests/filesystem/zip/OpenTest.java | 162 +++++ .../core/tests/filesystem/zip/RenameTest.java | 73 +++ .../core/tests/filesystem/zip/SetupTest.java | 55 ++ .../zip/ZipFileSystemTestSetup.java | 159 +++++ .../filesystem/zip/ZipFileSystemTestUtil.java | 142 +++++ .../resources/AutomatedResourceTests.java | 1 + 36 files changed, 2308 insertions(+), 571 deletions(-) create mode 100644 resources/bundles/org.eclipse.core.filesystem/src/org/eclipse/core/filesystem/ZipFileUtil.java create mode 100644 resources/bundles/org.eclipse.core.filesystem/src/org/eclipse/core/internal/filesystem/zip/ZipFileStore.java rename resources/{examples/org.eclipse.ui.examples.filesystem => bundles/org.eclipse.core.filesystem}/src/org/eclipse/core/internal/filesystem/zip/ZipFileSystem.java (79%) create mode 100644 resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/propertytester/ZipFilePropertyTester.java create mode 100644 resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/ZipFileTransformer.java delete mode 100644 resources/examples/org.eclipse.ui.examples.filesystem/src/org/eclipse/core/internal/filesystem/zip/ZipFileStore.java delete mode 100644 resources/examples/org.eclipse.ui.examples.filesystem/src/org/eclipse/core/internal/filesystem/zip/ZipFileSystemContributor.java delete mode 100644 resources/examples/org.eclipse.ui.examples.filesystem/src/org/eclipse/ui/examples/filesystem/CollapseZipHandler.java delete mode 100644 resources/examples/org.eclipse.ui.examples.filesystem/src/org/eclipse/ui/examples/filesystem/ExpandZipHandler.java create mode 100644 resources/tests/org.eclipse.core.tests.resources/resources/ZipFileSystem/BasicText.zip create mode 100644 resources/tests/org.eclipse.core.tests.resources/resources/ZipFileSystem/BasicTextNew.zip create mode 100644 resources/tests/org.eclipse.core.tests.resources/resources/ZipFileSystem/DeepNested.zip create mode 100644 resources/tests/org.eclipse.core.tests.resources/resources/ZipFileSystem/Empty.zip create mode 100644 resources/tests/org.eclipse.core.tests.resources/resources/ZipFileSystem/Fake.zip create mode 100644 resources/tests/org.eclipse.core.tests.resources/resources/ZipFileSystem/NestedZipFileParent.zip create mode 100644 resources/tests/org.eclipse.core.tests.resources/resources/ZipFileSystem/PasswordProtected.zip create mode 100644 resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/filesystem/zip/AllZipFileSystemTests.java create mode 100644 resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/filesystem/zip/CloseTest.java create mode 100644 resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/filesystem/zip/CopyTest.java create mode 100644 resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/filesystem/zip/CreateTest.java create mode 100644 resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/filesystem/zip/DeleteTest.java create mode 100644 resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/filesystem/zip/MoveTest.java create mode 100644 resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/filesystem/zip/OpenTest.java create mode 100644 resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/filesystem/zip/RenameTest.java create mode 100644 resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/filesystem/zip/SetupTest.java create mode 100644 resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/filesystem/zip/ZipFileSystemTestSetup.java create mode 100644 resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/filesystem/zip/ZipFileSystemTestUtil.java diff --git a/resources/bundles/org.eclipse.core.filesystem/META-INF/MANIFEST.MF b/resources/bundles/org.eclipse.core.filesystem/META-INF/MANIFEST.MF index c317ba0769e..f62a84662c2 100644 --- a/resources/bundles/org.eclipse.core.filesystem/META-INF/MANIFEST.MF +++ b/resources/bundles/org.eclipse.core.filesystem/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.core.filesystem; singleton:=true -Bundle-Version: 1.10.400.qualifier +Bundle-Version: 1.11.0.qualifier Bundle-Localization: plugin Require-Bundle: org.eclipse.equinox.common;bundle-version="[3.18.0,4.0.0)", org.eclipse.equinox.registry;bundle-version="[3.2.0,4.0.0)", diff --git a/resources/bundles/org.eclipse.core.filesystem/plugin.xml b/resources/bundles/org.eclipse.core.filesystem/plugin.xml index a91ee65ed6d..de665c388f7 100644 --- a/resources/bundles/org.eclipse.core.filesystem/plugin.xml +++ b/resources/bundles/org.eclipse.core.filesystem/plugin.xml @@ -16,4 +16,11 @@ + + + + + diff --git a/resources/bundles/org.eclipse.core.filesystem/src/org/eclipse/core/filesystem/ZipFileUtil.java b/resources/bundles/org.eclipse.core.filesystem/src/org/eclipse/core/filesystem/ZipFileUtil.java new file mode 100644 index 00000000000..6cb045a6aac --- /dev/null +++ b/resources/bundles/org.eclipse.core.filesystem/src/org/eclipse/core/filesystem/ZipFileUtil.java @@ -0,0 +1,114 @@ +/******************************************************************************* + * Copyright (c) 2024 Vector Informatik GmbH and others. + * + * This program and the accompanying materials are made available under the terms of the Eclipse + * Public License 2.0 which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: Vector Informatik GmbH - initial API and implementation + *******************************************************************************/ + +package org.eclipse.core.filesystem; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.HashSet; +import java.util.Set; +import org.eclipse.core.internal.filesystem.zip.ZipFileStore; +import org.eclipse.core.runtime.CoreException; + +/** + * Utility class to determine if a file is an archive based on file header information. + * This class checks for known file signatures to identify if a given file is a ZIP archive + * or a format based on ZIP, such as EPUB, JAR, ODF, and OOXML. + * + * @since 1.11 + */ +public class ZipFileUtil { + + private static final Set ARCHIVE_FILE_SIGNATURES = new HashSet<>(); + + static { + // Initializes known archive file signatures from Wikipedia's list of file signatures + // (https://en.wikipedia.org/wiki/List_of_file_signatures) + ARCHIVE_FILE_SIGNATURES.add(0x504B0304); // Standard ZIP file + ARCHIVE_FILE_SIGNATURES.add(0x504B0506); // Empty archive + ARCHIVE_FILE_SIGNATURES.add(0x504B0708); // Spanned archive + } + + /** + * Determines if the given {@link IFileStore} represents an open ZIP file. + * This can be used to check if operations on a ZIP file should be allowed or handled differently. + * + * @param store The file store to check. + * @return true if the store is an instance of {@link ZipFileStore}, false otherwise. + */ + public static boolean isInsideOpenZipFile(IFileStore store) { + return store instanceof ZipFileStore; + } + + public static boolean isInsideOpenZipFile(URI locationURI) { + IFileStore store; + try { + store = EFS.getStore(locationURI); + } catch (CoreException e) { + return false; + } + return isInsideOpenZipFile(store); + } + + //TODO Implement this method + public static boolean isOpenZipFile(IFileStore store) { + if (isInsideOpenZipFile(store)) { + ZipFileStore zipStore = (ZipFileStore) store; + return zipStore.getPath().isEmpty(); //if path is empty its the root + } + return false; + } + + public static boolean isOpenZipFile(URI locationURI) { + IFileStore store; + try { + store = EFS.getStore(locationURI); + } catch (CoreException e) { + return false; + } + return isOpenZipFile(store); + } + + public static boolean isNested(URI fileURI) { + if (fileURI.getScheme().contains("zip")) { //$NON-NLS-1$ + return true; + } + return false; + } + + /** + * Checks if the provided {@link InputStream} represents a ZIP archive + * by reading its first four bytes and comparing them against known ZIP file signatures. + * This method throws {@link IOException} if the file signature does not match any known ZIP archive signatures. + * + * @param fis The {@link InputStream} of the file to check. + * @throws IOException If the file signature does not match known ZIP archive signatures + * or an I/O error occurs during reading from the stream. + */ + public static void checkFileForZipHeader(InputStream fis) throws IOException { + byte[] bytes = new byte[4]; + if (fis.read(bytes) == bytes.length) { + ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN); + int header = buffer.getInt(); + + if (!ARCHIVE_FILE_SIGNATURES.contains(header)) { + throw new IOException("Invalid archive file signature."); // Throws IOException if header is not recognized //$NON-NLS-1$ + } + } else { + // Handle the case where not enough bytes are read + throw new IOException("Could not read enough data to check ZIP file header."); //$NON-NLS-1$ + } + } +} diff --git a/resources/bundles/org.eclipse.core.filesystem/src/org/eclipse/core/internal/filesystem/zip/ZipFileStore.java b/resources/bundles/org.eclipse.core.filesystem/src/org/eclipse/core/internal/filesystem/zip/ZipFileStore.java new file mode 100644 index 00000000000..78c0696019e --- /dev/null +++ b/resources/bundles/org.eclipse.core.filesystem/src/org/eclipse/core/internal/filesystem/zip/ZipFileStore.java @@ -0,0 +1,559 @@ +/******************************************************************************* + * Copyright (c) 2022 IBM Corporation and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which accompanies this distribution, + * and is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.core.internal.filesystem.zip; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystemAlreadyExistsException; +import java.nio.file.FileSystems; +import java.nio.file.FileVisitOption; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileTime; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import org.eclipse.core.filesystem.EFS; +import org.eclipse.core.filesystem.IFileInfo; +import org.eclipse.core.filesystem.IFileStore; +import org.eclipse.core.filesystem.provider.FileInfo; +import org.eclipse.core.filesystem.provider.FileStore; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.osgi.framework.FrameworkUtil; + +/** + * File store implementation representing a file or directory inside a zip file. + * @since 1.11 + */ +public class ZipFileStore extends FileStore { + /** + * The path of this store within the zip file. + */ + private final IPath path; + + /** + * The file store that represents the actual zip file. + */ + private final IFileStore rootStore; + + /** + * Creates a new zip file store. + */ + public ZipFileStore(IFileStore rootStore, IPath path) { + this.rootStore = rootStore; + this.path = path.makeRelative(); + } + + private ZipEntry[] childEntries(IProgressMonitor monitor) throws CoreException { + List entryList = new ArrayList<>(); + String myName = path.toString(); + + try (FileSystem zipFs = openZipFileSystem()) { + Path zipRoot = zipFs.getPath(myName); + Files.walkFileTree(zipRoot, EnumSet.noneOf(FileVisitOption.class), 1, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { + String entryName = zipRoot.relativize(file).toString(); + if (!Files.isDirectory(file)) { + // For files, read attributes and create ZipEntry + ZipEntry zipEntry = new ZipEntry(entryName); + zipEntry.setSize(attrs.size()); + zipEntry.setTime(attrs.lastModifiedTime().toMillis()); + // Compressed size is not directly available; method is set based on ZIP standard + zipEntry.setMethod(ZipEntry.DEFLATED); + entryList.add(zipEntry); + } else { + // For directories, simply add them with a trailing slash + entryList.add(new ZipEntry(entryName + "/")); //$NON-NLS-1$ + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { + // Include directories only if they are not the root directory + if (!dir.equals(zipRoot)) { + String dirName = zipRoot.relativize(dir).toString() + "/"; //$NON-NLS-1$ + entryList.add(new ZipEntry(dirName)); + } + return FileVisitResult.CONTINUE; + } + }); + } catch (IOException | URISyntaxException e) { + throw new CoreException(new Status(IStatus.ERROR, getPluginId(), "Error reading ZIP file", e)); //$NON-NLS-1$ + } + + return entryList.toArray(new ZipEntry[0]); + } + + @Override + public IFileInfo[] childInfos(int options, IProgressMonitor monitor) throws CoreException { + ZipEntry[] entries = childEntries(monitor); + int entryCount = entries.length; + IFileInfo[] infos = new IFileInfo[entryCount]; + for (int i = 0; i < entryCount; i++) { + infos[i] = convertZipEntryToFileInfo(entries[i]); + } + return infos; + } + + @Override + public String[] childNames(int options, IProgressMonitor monitor) throws CoreException { + ZipEntry[] entries = childEntries(monitor); + int entryCount = entries.length; + String[] names = new String[entryCount]; + for (int i = 0; i < entryCount; i++) { + names[i] = computeName(entries[i]); + } + return names; + } + + /** + * Computes the simple file name for a given zip entry. + */ + private String computeName(ZipEntry entry) { + String name = entry.getName(); + // removes "/" at the end + if (name.endsWith("/")) { //$NON-NLS-1$ + name = name.substring(0, name.length() - 1); + } + // creates last segment after last / + int lastIndex = name.lastIndexOf('/'); + + if (lastIndex != -1) { + return name.substring(lastIndex + 1); + } + + return name; // Falls kein '/' gefunden wurde + } + + private IFileInfo convertToIFileInfo(Path zipEntryPath, BasicFileAttributes attrs) { + Path namePath = zipEntryPath.getFileName(); + String name = namePath != null ? namePath.toString() : ""; //$NON-NLS-1$ + FileInfo info = new FileInfo(name); + info.setExists(true); + info.setDirectory(attrs.isDirectory()); + info.setLastModified(attrs.lastModifiedTime().toMillis()); + info.setLength(attrs.size()); + return info; + } + + /** + * Creates a file info object corresponding to a given zip entry + * + * @param entry the zip entry + * @return The file info for a zip entry + */ + private IFileInfo convertZipEntryToFileInfo(ZipEntry entry) { + FileInfo info = new FileInfo(computeName(entry)); + if (entry.isDirectory()) { + info.setLastModified(EFS.NONE); + } else { + info.setLastModified(entry.getTime()); + } + + info.setExists(true); + info.setDirectory(entry.isDirectory()); + info.setLength(entry.getSize()); + return info; + } + + @Override + protected void copyDirectory(IFileInfo sourceInfo, IFileStore destination, int options, IProgressMonitor monitor) throws CoreException { + if (!(destination instanceof ZipFileStore)) { + super.copyDirectory(sourceInfo, destination, options, monitor); + return; + } + + if (!sourceInfo.isDirectory()) { + throw new CoreException(new Status(IStatus.ERROR, getPluginId(), "Source is not a directory")); //$NON-NLS-1$ + } + + try (FileSystem zipFs = openZipFileSystem()) { + Path sourceDir = zipFs.getPath(this.path.toString()); + FileSystem destFs = ((ZipFileStore) destination).openZipFileSystem(); + Path destDir = destFs.getPath(((ZipFileStore) destination).path.toString()); + + // Use Files.walk to iterate over each entry in the directory + Files.walk(sourceDir).forEach(sourcePath -> { + try { + Path destPath = destDir.resolve(sourceDir.relativize(sourcePath)); + if (Files.isDirectory(sourcePath)) { + Files.createDirectories(destPath); + } else { + Files.copy(sourcePath, destPath, StandardCopyOption.REPLACE_EXISTING); + } + } catch (IOException e) { + throw new RuntimeException("Error copying directory contents", e); //$NON-NLS-1$ + } + }); + } catch (IOException | URISyntaxException e) { + throw new CoreException(new Status(IStatus.ERROR, getPluginId(), "Error copying directory within ZIP", e)); //$NON-NLS-1$ + } + } + + @Override + protected void copyFile(IFileInfo sourceInfo, IFileStore destination, int options, IProgressMonitor monitor) throws CoreException { + if (!(destination instanceof ZipFileStore)) { + super.copyFile(sourceInfo, destination, options, monitor); + return; + } + + if (sourceInfo.isDirectory()) { + throw new CoreException(new Status(IStatus.ERROR, getPluginId(), "Source is a directory, not a file")); //$NON-NLS-1$ + } + + try (FileSystem zipFs = openZipFileSystem()) { + Path sourcePath = zipFs.getPath(this.path.toString()); + FileSystem destFs = ((ZipFileStore) destination).openZipFileSystem(); + Path destPath = destFs.getPath(((ZipFileStore) destination).path.toString()); + + // Copy the file with REPLACE_EXISTING option to overwrite if it already exists + Files.copy(sourcePath, destPath, StandardCopyOption.REPLACE_EXISTING); + } catch (IOException | URISyntaxException e) { + throw new CoreException(new Status(IStatus.ERROR, getPluginId(), "Error copying file within ZIP", e)); //$NON-NLS-1$ + } + } + + @Override + public void delete(int options, IProgressMonitor monitor) throws CoreException { + Path toDelete = null; + try (FileSystem zipFs = openZipFileSystem()) { + toDelete = zipFs.getPath(path.toString()); + if (Files.exists(toDelete)) { + deleteRecursive(toDelete); + } + } catch (IOException | URISyntaxException e) { + throw new CoreException(new Status(IStatus.ERROR, "org.eclipse.core.filesystem.zip", "Error deleting file from zip: " + toDelete, e)); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + + private void deleteRecursive(Path pathToDelete) throws IOException { + if (Files.isDirectory(pathToDelete)) { + // Use try-with-resources to close the directory stream automatically + try (Stream entries = Files.walk(pathToDelete)) { + // We need to sort it in reverse order so directories come after their contents + List sortedPaths = entries.sorted(Comparator.reverseOrder()).collect(Collectors.toList()); + for (Path entry : sortedPaths) { + Files.delete(entry); + } + } + } else { + Files.delete(pathToDelete); + } + } + + @Override + public IFileInfo fetchInfo(int options, IProgressMonitor monitor) throws CoreException { + try (FileSystem zipFs = openZipFileSystem()) { + Path zipEntryPath = zipFs.getPath(path.toString()); + if (Files.exists(zipEntryPath)) { + BasicFileAttributes attrs = Files.readAttributes(zipEntryPath, BasicFileAttributes.class); + return convertToIFileInfo(zipEntryPath, attrs); + } + } catch (IOException | URISyntaxException e) { + throw new CoreException(new Status(IStatus.ERROR, getPluginId(), "Error accessing ZIP file", e)); //$NON-NLS-1$ + } + + // Correctly set up FileInfo before returning + FileInfo notFoundInfo = new FileInfo(path.lastSegment()); + notFoundInfo.setExists(false); + return notFoundInfo; + } + + /** + * Finds the zip entry with the given name in this zip file. Returns the + * entry and leaves the input stream open positioned at the beginning of the + * bytes of that entry. Returns null if the entry could not be found. + */ + private ZipEntry findEntry(String name, ZipInputStream in) throws IOException { + ZipEntry current; + while ((current = in.getNextEntry()) != null) { + if (current.getName().equals(name)) { + return current; + } + } + return null; + } + + @Override + public IFileStore getChild(String name) { + return new ZipFileStore(rootStore, path.append(name)); + } + + @Override + public String getName() { + String name = path.lastSegment(); + return name == null ? "" : name; //$NON-NLS-1$ + } + + @Override + public IFileStore getParent() { + if (path.segmentCount() > 0) { + return new ZipFileStore(rootStore, path.removeLastSegments(1)); + } + // the root entry has no parent + return null; + } + + private String getPluginId() { + return FrameworkUtil.getBundle(this.getClass()).getSymbolicName(); + } + + public IPath getPath() { + return path; + } + + private boolean isNested() { + return this.rootStore instanceof ZipFileStore; + } + + @Override + public IFileStore mkdir(int options, IProgressMonitor monitor) throws CoreException { + URI zipUri; + try { + zipUri = new URI("jar:" + rootStore.toURI().toString() + "!/"); //$NON-NLS-1$ //$NON-NLS-2$ + } catch (URISyntaxException e) { + throw new CoreException(new Status(IStatus.ERROR, "org.eclipse.core.filesystem.zip", "Invalid ZIP file URI", e)); //$NON-NLS-1$ //$NON-NLS-2$ + } + + Map env = new HashMap<>(); + env.put("create", "false"); //$NON-NLS-1$ //$NON-NLS-2$ + + // Assuming the directory to create is represented by 'this.path' + try (FileSystem zipFs = FileSystems.newFileSystem(zipUri, env)) { + Path dirInZipPath = zipFs.getPath(this.path.toString()); + if (Files.notExists(dirInZipPath)) { + Files.createDirectories(dirInZipPath); + + // To ensure the directory is actually added to the ZIP, we + // might need to add a temporary file + // in this directory. This is a workaround and should be used + // with caution. + Path tempFileInDir = dirInZipPath.resolve(".keep"); //$NON-NLS-1$ + Files.createFile(tempFileInDir); + + // Immediately delete the temporary file after creation to just + // keep the directory + Files.delete(tempFileInDir); + } + } catch (IOException e) { + throw new CoreException(new Status(IStatus.ERROR, "org.eclipse.core.filesystem.zip", "Error creating directory in ZIP file", e)); //$NON-NLS-1$ //$NON-NLS-2$ + } + + // Return a file store representing the newly created directory. + return new ZipFileStore(rootStore, this.path); + } + + @Override + public void move(IFileStore destination, int options, IProgressMonitor monitor) throws CoreException { + if (!(destination instanceof ZipFileStore)) { + super.move(destination, options, monitor); + return; + } + ZipFileStore destZipFileStore = (ZipFileStore) destination; + + try (FileSystem srcFs = openZipFileSystem(); FileSystem destFs = destZipFileStore.openZipFileSystem()) { + Path srcPath = srcFs.getPath(this.path.toString()); + Path destPath = destFs.getPath(destZipFileStore.path.toString()); + + if (destPath.getParent() != null) { + Files.createDirectories(destPath.getParent()); + } + + if (Files.isDirectory(srcPath)) { + moveDirectory(srcPath, destPath, srcFs, destFs); + } else { + Files.move(srcPath, destPath, StandardCopyOption.REPLACE_EXISTING); + } + } catch (IOException | URISyntaxException e) { + throw new CoreException(new Status(IStatus.ERROR, "org.eclipse.core.filesystem.zip", "Error moving entry within ZIP", e)); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + + private void moveDirectory(Path srcPath, Path destPath, FileSystem srcFs, FileSystem destFs) throws IOException { + // Ensure the destination directory structure is ready + if (destPath.getParent() != null) { + Files.createDirectories(destPath.getParent()); + } + + // Recursively move the contents + Files.walk(srcPath).forEach(source -> { + try { + Path destination = destPath.resolve(srcPath.relativize(source)); + if (Files.isDirectory(source)) { + if (!Files.exists(destination)) { + Files.createDirectories(destination); + } + } else { + Files.move(source, destination, StandardCopyOption.REPLACE_EXISTING); + } + } catch (IOException e) { + throw new RuntimeException("Failed to move files", e); //$NON-NLS-1$ + } + }); + + // Delete the source directory after moving its contents + Files.walk(srcPath).sorted(Comparator.reverseOrder()).forEach(pathToMove -> { + try { + Files.delete(pathToMove); + } catch (IOException e) { + throw new RuntimeException("Failed to delete original files after move", e); //$NON-NLS-1$ + } + }); + } + + @Override + public InputStream openInputStream(int options, IProgressMonitor monitor) throws CoreException { + try { + ZipInputStream in = new ZipInputStream(rootStore.openInputStream(EFS.NONE, monitor)); + ZipEntry entry = findEntry(path.toString(), in); + if (entry == null) { + throw new CoreException(Status.error("File not found: " + rootStore.toString())); //$NON-NLS-1$ + } + if (entry.isDirectory()) { + throw new CoreException(Status.error("Resource is not a file: " + rootStore.toString())); //$NON-NLS-1$ + } + return in; + } catch (IOException e) { + throw new CoreException(Status.error("Could not read file: " + rootStore.toString(), e)); //$NON-NLS-1$ + } + } + + @Override + public OutputStream openOutputStream(int options, IProgressMonitor monitor) { + // Creating a ByteArrayOutputStream to capture the data written to the + // OutputStream + ByteArrayOutputStream baos = new ByteArrayOutputStream() { + @Override + public void close() throws IOException { + try (FileSystem zipFs = openZipFileSystem()) { + Path entryPath = zipFs.getPath(path.toString()); + // Ensure parent directories exist + Path parentPath = entryPath.getParent(); + if (parentPath != null) { + Files.createDirectories(parentPath); + } + // Write the ByteArrayOutputStream's data to the entry + // in the ZIP file + Files.write(entryPath, this.toByteArray(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + + } catch (Exception e) { + throw new IOException("Failed to integrate data into ZIP file", e); //$NON-NLS-1$ + } + } + }; + + return baos; + } + + private FileSystem openZipFileSystem() throws URISyntaxException, IOException { + Map env = new HashMap<>(); + env.put("create", "false"); //$NON-NLS-1$ //$NON-NLS-2$ + URI nioURI = toNioURI(); + Path innerArchivePath = null; + + if (isNested()) { + ZipFileStore outerZipFileStore = (ZipFileStore) this.rootStore; + FileSystem outerFs = outerZipFileStore.openZipFileSystem(); + innerArchivePath = outerFs.getPath(outerZipFileStore.path.toString()); + nioURI = innerArchivePath.toUri(); + } + + try { + if (innerArchivePath != null) { + return FileSystems.newFileSystem(innerArchivePath, env); + } + return FileSystems.newFileSystem(nioURI, env); + } catch (FileSystemAlreadyExistsException e) { + return FileSystems.getFileSystem(nioURI); + } + } + + @Override + public void putInfo(IFileInfo info, int options, IProgressMonitor monitor) throws CoreException { + if (monitor != null) { + monitor.beginTask("Updating Zip Entry Information", 1); //$NON-NLS-1$ + } + try (FileSystem zipFs = openZipFileSystem()) { + Path filePath = zipFs.getPath(path.toString()); + // Check options for what information is requested to be updated + if ((options & EFS.SET_ATTRIBUTES) != 0) { + boolean isHidden = info.getAttribute(EFS.ATTRIBUTE_HIDDEN); + boolean isArchive = info.getAttribute(EFS.ATTRIBUTE_ARCHIVE); + + if (ZipFileSystem.getOS().startsWith("Windows")) { //$NON-NLS-1$ + Files.setAttribute(filePath, "dos:hidden", isHidden); //$NON-NLS-1$ + Files.setAttribute(filePath, "dos:archive", isArchive); //$NON-NLS-1$ + } + } + if ((options & EFS.SET_LAST_MODIFIED) != 0) { + FileTime lastModified = FileTime.fromMillis(info.getLastModified()); + Files.setLastModifiedTime(filePath, lastModified); + } + + } catch (Exception e) { + throw new CoreException(new Status(IStatus.ERROR, getPluginId(), "Error updating ZIP file entry information", e)); //$NON-NLS-1$ + } finally { + if (monitor != null) { + monitor.done(); + } + } + } + + @Override + public URI toURI() { + String scheme = ZipFileSystem.SCHEME_ZIP; + String pathString = path.makeAbsolute().toString(); + URI rootStoreURI = rootStore.toURI(); + String rootStoreScheme = rootStoreURI.getScheme(); + String rootStorePath = rootStoreURI.getPath(); + String rootStoreQuery = rootStoreScheme + ":" + rootStorePath; //$NON-NLS-1$ + try { + return new URI(scheme, null, pathString, rootStoreQuery, null); + } catch (URISyntaxException e) { + // should not happen + throw new RuntimeException(e); + } + } + + private URI toNioURI() throws URISyntaxException { + String nioScheme = "jar:"; //$NON-NLS-1$ + String rootPath = rootStore.toURI().toString(); + + String suffix = "!/"; //$NON-NLS-1$ + String ret = nioScheme + rootPath + suffix; + return new URI(ret); + } + +} diff --git a/resources/examples/org.eclipse.ui.examples.filesystem/src/org/eclipse/core/internal/filesystem/zip/ZipFileSystem.java b/resources/bundles/org.eclipse.core.filesystem/src/org/eclipse/core/internal/filesystem/zip/ZipFileSystem.java similarity index 79% rename from resources/examples/org.eclipse.ui.examples.filesystem/src/org/eclipse/core/internal/filesystem/zip/ZipFileSystem.java rename to resources/bundles/org.eclipse.core.filesystem/src/org/eclipse/core/internal/filesystem/zip/ZipFileSystem.java index 386fdda01ef..b030d13ddc7 100644 --- a/resources/examples/org.eclipse.ui.examples.filesystem/src/org/eclipse/core/internal/filesystem/zip/ZipFileSystem.java +++ b/resources/bundles/org.eclipse.core.filesystem/src/org/eclipse/core/internal/filesystem/zip/ZipFileSystem.java @@ -21,6 +21,9 @@ import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; +/** + * @since 1.11 + */ public class ZipFileSystem extends FileSystem { /** * Scheme constant (value "zip") indicating the zip file system scheme. @@ -41,4 +44,22 @@ public IFileStore getStore(URI uri) { } return EFS.getNullFileSystem().getStore(uri); } + + /** + * Returns the current OS. This is equivalent to Platform.getOS(), but + * is tolerant of the platform runtime not being present. + */ + static String getOS() { + return System.getProperty("osgi.os", ""); //$NON-NLS-1$ //$NON-NLS-2$ + } + + @Override + public boolean canDelete() { + return true; + } + + @Override + public boolean canWrite() { + return true; + } } diff --git a/resources/bundles/org.eclipse.core.resources/META-INF/MANIFEST.MF b/resources/bundles/org.eclipse.core.resources/META-INF/MANIFEST.MF index 9a9a0bb5d06..4f245439b2a 100644 --- a/resources/bundles/org.eclipse.core.resources/META-INF/MANIFEST.MF +++ b/resources/bundles/org.eclipse.core.resources/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.core.resources; singleton:=true -Bundle-Version: 3.20.300.qualifier +Bundle-Version: 3.21.0.qualifier Bundle-Activator: org.eclipse.core.resources.ResourcesPlugin Bundle-Vendor: %providerName Bundle-Localization: plugin @@ -35,3 +35,9 @@ Service-Component: OSGI-INF/ResourceChangeListenerRegistrar.xml, Bundle-RequiredExecutionEnvironment: JavaSE-17 Automatic-Module-Name: org.eclipse.core.resources Import-Package: org.eclipse.osgi.service.datalocation;version="[1.3.0,2.0.0)" +Service-Component: OSGI-INF/org.eclipse.core.internal.resources.CheckMissingNaturesListener.xml +Bundle-ActivationPolicy: lazy +Service-Component: OSGI-INF/org.eclipse.core.internal.resources.CheckMissingNaturesListener.xml +Bundle-ActivationPolicy: lazy +Service-Component: OSGI-INF/org.eclipse.core.internal.resources.CheckMissingNaturesListener.xml +Bundle-ActivationPolicy: lazy diff --git a/resources/bundles/org.eclipse.core.resources/plugin.xml b/resources/bundles/org.eclipse.core.resources/plugin.xml index 74b386a109c..9cdeba8cce4 100644 --- a/resources/bundles/org.eclipse.core.resources/plugin.xml +++ b/resources/bundles/org.eclipse.core.resources/plugin.xml @@ -203,6 +203,12 @@ class="org.eclipse.core.internal.propertytester.ResourceMappingPropertyTester" properties="projectPersistentProperty" id="org.eclipse.core.resources.mappingPropertyTester"/> + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - entries = new HashMap<>(); - String myName = path.toString(); - try (ZipInputStream in = new ZipInputStream(rootStore.openInputStream(EFS.NONE, monitor))) { - ZipEntry current; - while ((current = in.getNextEntry()) != null) { - final String currentPath = current.getName(); - if (isParent(myName, currentPath)) { - entries.put(currentPath, current); - } else if (isAncestor(myName, currentPath)) { - int myNameLength = myName.length() + 1; - int nameEnd = currentPath.indexOf('/', myNameLength); - String dirName = nameEnd == -1 ? currentPath : currentPath.substring(0, nameEnd + 1); - if (!entries.containsKey(dirName)) - entries.put(dirName, new ZipEntry(dirName)); - } - } - } catch (IOException e) { - throw new CoreException(Status.error("Could not read file: " + rootStore.toString(), e)); - } - return entries.values().toArray(new ZipEntry[entries.size()]); - } - - @Override - public IFileInfo[] childInfos(int options, IProgressMonitor monitor) throws CoreException { - ZipEntry[] entries = childEntries(monitor); - int entryCount = entries.length; - IFileInfo[] infos = new IFileInfo[entryCount]; - for (int i = 0; i < entryCount; i++) { - infos[i] = convertZipEntryToFileInfo(entries[i]); - } - return infos; - } - - @Override - public String[] childNames(int options, IProgressMonitor monitor) throws CoreException { - ZipEntry[] entries = childEntries(monitor); - int entryCount = entries.length; - String[] names = new String[entryCount]; - for (int i = 0; i < entryCount; i++) { - names[i] = computeName(entries[i]); - } - return names; - } - - /** - * Computes the simple file name for a given zip entry. - */ - private String computeName(ZipEntry entry) { - //the entry name is a relative path, with an optional trailing separator - //We need to strip off the trailing slash, and then take everything after the - //last separator as the name - String name = entry.getName(); - int end = name.length() - 1; - if (name.charAt(end) == '/') { - end--; - } - return name.substring(name.lastIndexOf('/', end) + 1, end + 1); - } - - /** - * Creates a file info object corresponding to a given zip entry - * - * @param entry the zip entry - * @return The file info for a zip entry - */ - private IFileInfo convertZipEntryToFileInfo(ZipEntry entry) { - FileInfo info = new FileInfo(computeName(entry)); - info.setLastModified(entry.getTime()); - info.setExists(true); - info.setDirectory(entry.isDirectory()); - info.setLength(entry.getSize()); - return info; - } - - @Override - public IFileInfo fetchInfo(int options, IProgressMonitor monitor) throws CoreException { - try (ZipInputStream in = new ZipInputStream(rootStore.openInputStream(EFS.NONE, monitor))) { - String myPath = path.toString(); - ZipEntry current; - while ((current = in.getNextEntry()) != null) { - String currentPath = current.getName(); - if (myPath.equals(currentPath)) { - return convertZipEntryToFileInfo(current); - } - //directories don't always have their own entry, but it is implied by the existence of a child - if (isAncestor(myPath, currentPath)) { - return createDirectoryInfo(getName()); - } - } - } catch (IOException e) { - throw new CoreException(Status.error("Could not read file: " + rootStore.toString(), e)); - } - //does not exist - return new FileInfo(getName()); - } - - /** - * @return A directory info for this file store - */ - private IFileInfo createDirectoryInfo(String name) { - FileInfo result = new FileInfo(name); - result.setExists(true); - result.setDirectory(true); - return result; - } - - /** - * Finds the zip entry with the given name in this zip file. Returns the - * entry and leaves the input stream open positioned at the beginning of - * the bytes of that entry. Returns null if the entry could not be found. - */ - private ZipEntry findEntry(String name, ZipInputStream in) throws IOException { - ZipEntry current; - while ((current = in.getNextEntry()) != null) { - if (current.getName().equals(name)) { - return current; - } - } - return null; - } - - @Override - public IFileStore getChild(String name) { - return new ZipFileStore(rootStore, path.append(name)); - } - - @Override - public String getName() { - String name = path.lastSegment(); - return name == null ? "" : name; //$NON-NLS-1$ - } - - @Override - public IFileStore getParent() { - if (path.segmentCount() > 0) { - return new ZipFileStore(rootStore, path.removeLastSegments(1)); - } - //the root entry has no parent - return null; - } - - /** - * Returns whether ancestor is a parent of child. - * @param ancestor the potential ancestor - * @param child the potential child - * @return true or false - */ - private boolean isAncestor(String ancestor, String child) { - //children will start with myName and have no child path - int ancestorLength = ancestor.length(); - if (ancestorLength == 0) { - return true; - } - return child.startsWith(ancestor) && child.length() > ancestorLength && child.charAt(ancestorLength) == '/'; - } - - /** - * Returns whether parent is the immediate parent of child. - * @param parent the potential parent - * @param child the potential child - * @return true or false - */ - private boolean isParent(String parent, String child) { - //children will start with myName and have no child path - int chop = parent.length() + 1; - return child.startsWith(parent) && child.length() > chop && child.substring(chop).indexOf('/') == -1; - } - - @Override - public InputStream openInputStream(int options, IProgressMonitor monitor) throws CoreException { - try (ZipInputStream in = new ZipInputStream(rootStore.openInputStream(EFS.NONE, monitor))) { - ZipEntry entry = findEntry(path.toString(), in); - if (entry == null) { - throw new CoreException(Status.error("File not found: " + rootStore.toString())); - } - if (entry.isDirectory()) { - throw new CoreException(Status.error("Resource is not a file: " + rootStore.toString())); - } - return in; - } catch (IOException e) { - throw new CoreException(Status.error("Could not read file: " + rootStore.toString(), e)); - } - } - - @Override - public URI toURI() { - try { - return new URI(ZipFileSystem.SCHEME_ZIP, null, path.makeAbsolute().toString(), rootStore.toURI().toString(), null); - } catch (URISyntaxException e) { - //should not happen - throw new RuntimeException(e); - } - } -} diff --git a/resources/examples/org.eclipse.ui.examples.filesystem/src/org/eclipse/core/internal/filesystem/zip/ZipFileSystemContributor.java b/resources/examples/org.eclipse.ui.examples.filesystem/src/org/eclipse/core/internal/filesystem/zip/ZipFileSystemContributor.java deleted file mode 100644 index d8516a8b60a..00000000000 --- a/resources/examples/org.eclipse.ui.examples.filesystem/src/org/eclipse/core/internal/filesystem/zip/ZipFileSystemContributor.java +++ /dev/null @@ -1,82 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2022 IBM Corporation and others. - * - * This program and the accompanying materials - * are made available under the terms of the Eclipse Public License 2.0 - * which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * IBM Corporation - initial API and implementation - *******************************************************************************/ - -package org.eclipse.core.internal.filesystem.zip; - -import java.io.File; -import java.net.URI; -import java.net.URISyntaxException; - -import org.eclipse.swt.widgets.FileDialog; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.ui.ide.fileSystem.FileSystemContributor; - -/** - * ZipFileSystemContributor is the zip example of a file system contributor. - */ -public class ZipFileSystemContributor extends FileSystemContributor { - - public ZipFileSystemContributor() { - super(); - } - - @Override - public URI getURI(String pathString) { - try { - if (pathString.startsWith(ZipFileSystem.SCHEME_ZIP)) - return new URI(pathString); - } catch (URISyntaxException e1) { - return null; - } - if (File.separatorChar != '/') - pathString = pathString.replace(File.separatorChar, '/'); - final int length = pathString.length(); - StringBuffer pathBuf = new StringBuffer(length + 1); - pathBuf.append("file:"); //$NON-NLS-1$ - // There must be a leading slash in a hierarchical URI - if (length > 0 && (pathString.charAt(0) != '/')) - pathBuf.append('/'); - // additional double-slash for UNC paths to distinguish from host - // separator - if (pathString.startsWith("//")) //$NON-NLS-1$ - pathBuf.append('/').append('/'); - pathBuf.append(pathString); - try { - //scheme, host, path, query, fragment - return new URI(ZipFileSystem.SCHEME_ZIP, null, "/", pathBuf.toString(), null); //$NON-NLS-1$ - } catch (URISyntaxException e) { - return null; - } - } - - /* (non-Javadoc) - * @see org.eclipse.ui.ide.fileSystem.FileSystemContributor#browseFileSystem(java.lang.String, org.eclipse.swt.widgets.Shell) - */ - @Override - public URI browseFileSystem(String initialPath, Shell shell) { - - FileDialog dialog = new FileDialog(shell); - - if (initialPath.length() > 0) - dialog.setFilterPath(initialPath); - - dialog.setFilterExtensions(new String[] {"*.zip"});//$NON-NLS-1$ - - String selectedFile = dialog.open(); - if (selectedFile == null) - return null; - return getURI(selectedFile); - } - -} diff --git a/resources/examples/org.eclipse.ui.examples.filesystem/src/org/eclipse/ui/examples/filesystem/CollapseZipHandler.java b/resources/examples/org.eclipse.ui.examples.filesystem/src/org/eclipse/ui/examples/filesystem/CollapseZipHandler.java deleted file mode 100644 index 9b75ba8bf0d..00000000000 --- a/resources/examples/org.eclipse.ui.examples.filesystem/src/org/eclipse/ui/examples/filesystem/CollapseZipHandler.java +++ /dev/null @@ -1,79 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2022 IBM Corporation and others. - * - * This program and the accompanying materials - * are made available under the terms of the Eclipse Public License 2.0 - * which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * IBM Corporation - initial API and implementation - * Patrick Ziegler - Migration from a JFace Action to a Command Handler, - * in order to be used with the 'org.eclipse.ui.menus' - * extension point. - *******************************************************************************/ -package org.eclipse.ui.examples.filesystem; - -import java.net.URI; -import org.eclipse.core.commands.AbstractHandler; -import org.eclipse.core.commands.ExecutionEvent; -import org.eclipse.core.commands.ExecutionException; -import org.eclipse.core.filesystem.EFS; -import org.eclipse.core.filesystem.IFileStore; -import org.eclipse.core.filesystem.URIUtil; -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IFolder; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.runtime.IPath; -import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.ui.handlers.HandlerUtil; - -public class CollapseZipHandler extends AbstractHandler { - - private void collapseZip(IFolder folder, Shell shell) { - try { - URI zipURI = new URI(folder.getLocationURI().getQuery()); - //check if the zip file is physically stored below the folder in the workspace - IFileStore parentStore = EFS.getStore(folder.getParent().getLocationURI()); - URI childURI = parentStore.getChild(folder.getName()).toURI(); - if (URIUtil.equals(zipURI, childURI)) { - //the zip file is in the workspace so just delete the link - // and refresh the parent to create the resource - folder.delete(IResource.NONE, null); - folder.getParent().refreshLocal(IResource.DEPTH_INFINITE, null); - } else { - //otherwise the zip file must be a linked resource - IFile file = folder.getParent().getFile(IPath.fromOSString(folder.getName())); - file.createLink(zipURI, IResource.REPLACE, null); - } - } catch (Exception e) { - MessageDialog.openError(shell, "Error", "Error opening zip file"); - e.printStackTrace(); - } - } - - @Override - public Object execute(ExecutionEvent event) throws ExecutionException { - Shell shell = HandlerUtil.getActiveShell(event); - ISelection selection = HandlerUtil.getCurrentSelection(event); - - if (!(selection instanceof IStructuredSelection)) { - return null; - } - - Object element = ((IStructuredSelection) selection).getFirstElement(); - - if (!(element instanceof IFolder)) { - return null; - } - - collapseZip((IFolder) element, shell); - return null; - } - -} diff --git a/resources/examples/org.eclipse.ui.examples.filesystem/src/org/eclipse/ui/examples/filesystem/ExpandZipHandler.java b/resources/examples/org.eclipse.ui.examples.filesystem/src/org/eclipse/ui/examples/filesystem/ExpandZipHandler.java deleted file mode 100644 index 7fde7131080..00000000000 --- a/resources/examples/org.eclipse.ui.examples.filesystem/src/org/eclipse/ui/examples/filesystem/ExpandZipHandler.java +++ /dev/null @@ -1,66 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2022 IBM Corporation and others. - * - * This program and the accompanying materials - * are made available under the terms of the Eclipse Public License 2.0 - * which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * IBM Corporation - initial API and implementation - * Patrick Ziegler - Migration from a JFace Action to a Command Handler, - * in order to be used with the 'org.eclipse.ui.menus' - * extension point. - *******************************************************************************/ -package org.eclipse.ui.examples.filesystem; - -import java.net.URI; -import org.eclipse.core.commands.AbstractHandler; -import org.eclipse.core.commands.ExecutionEvent; -import org.eclipse.core.commands.ExecutionException; -import org.eclipse.core.internal.filesystem.zip.ZipFileSystem; -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IFolder; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.runtime.IPath; -import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.ui.handlers.HandlerUtil; - -public class ExpandZipHandler extends AbstractHandler { - - private void expandZip(IFile file, Shell shell) { - try { - URI zipURI = new URI(ZipFileSystem.SCHEME_ZIP, null, "/", file.getLocationURI().toString(), null); - IFolder link = file.getParent().getFolder(IPath.fromOSString(file.getName())); - link.createLink(zipURI, IResource.REPLACE, null); - } catch (Exception e) { - MessageDialog.openError(shell, "Error", "Error opening zip file"); - e.printStackTrace(); - } - } - - @Override - public Object execute(ExecutionEvent event) throws ExecutionException { - Shell shell = HandlerUtil.getActiveShell(event); - ISelection selection = HandlerUtil.getCurrentSelection(event); - - if (!(selection instanceof IStructuredSelection)) { - return null; - } - - Object element = ((IStructuredSelection) selection).getFirstElement(); - - if (!(element instanceof IFile)) { - return null; - } - - expandZip((IFile) element, shell); - return null; - } - -} diff --git a/resources/tests/org.eclipse.core.tests.resources/META-INF/MANIFEST.MF b/resources/tests/org.eclipse.core.tests.resources/META-INF/MANIFEST.MF index 89934875b60..8ff40f6c1bb 100644 --- a/resources/tests/org.eclipse.core.tests.resources/META-INF/MANIFEST.MF +++ b/resources/tests/org.eclipse.core.tests.resources/META-INF/MANIFEST.MF @@ -38,6 +38,8 @@ Import-Package: org.assertj.core.api, org.junit.jupiter.api, org.junit.jupiter.api.extension, org.junit.jupiter.api.io, + org.junit.jupiter.params;version="[5.10.0,6.0.0]", + org.junit.jupiter.params.provider;version="[5.10.0,6.0.0]", org.junit.platform.suite.api, org.mockito Bundle-ActivationPolicy: lazy diff --git a/resources/tests/org.eclipse.core.tests.resources/resources/ZipFileSystem/BasicText.zip b/resources/tests/org.eclipse.core.tests.resources/resources/ZipFileSystem/BasicText.zip new file mode 100644 index 0000000000000000000000000000000000000000..b33117fa4057a19f7b86474b2e667ccddc7593c1 GIT binary patch literal 126 zcmWIWW@Zs#0D;x3Ln0Q-Xv**a*&xgT#388_C3+bqjd4n_9T$u{?90o)3pD4XSG#5hC$abw{<^RBtf!B#*;XnHXf&M@ykUNdIjHP*98y3ahl%5|15J_ zebNBslrJa4TAeujfGvH9w6WL?Xh-GZ*VgLA!bgSxhRVi_RySsTb}_K1d~9j;V{5h7 z0!U?KP^%+@BTxTI_BYy>vmh2KC#zaLS$)r;g0^L4TdOPCo@roR=C(KGjl!cMIVGIm K1!LN{j9CL5weZaV literal 0 HcmV?d00001 diff --git a/resources/tests/org.eclipse.core.tests.resources/resources/ZipFileSystem/Empty.zip b/resources/tests/org.eclipse.core.tests.resources/resources/ZipFileSystem/Empty.zip new file mode 100644 index 0000000000000000000000000000000000000000..15cb0ecb3e219d1701294bfdf0fe3f5cb5d208e7 GIT binary patch literal 22 NcmWIWW@Tf*000g10H*)| literal 0 HcmV?d00001 diff --git a/resources/tests/org.eclipse.core.tests.resources/resources/ZipFileSystem/Fake.zip b/resources/tests/org.eclipse.core.tests.resources/resources/ZipFileSystem/Fake.zip new file mode 100644 index 00000000000..945948d8236 --- /dev/null +++ b/resources/tests/org.eclipse.core.tests.resources/resources/ZipFileSystem/Fake.zip @@ -0,0 +1 @@ +THIS IS NOT A ZIP! \ No newline at end of file diff --git a/resources/tests/org.eclipse.core.tests.resources/resources/ZipFileSystem/NestedZipFileParent.zip b/resources/tests/org.eclipse.core.tests.resources/resources/ZipFileSystem/NestedZipFileParent.zip new file mode 100644 index 0000000000000000000000000000000000000000..6af194f1ce744b7ce672bbca26462f0d25c6f54a GIT binary patch literal 1478 zcmWIWW@Zs#0D z*GD#-Z*|>F37}dK769T9pdOd}?9$xSyb`^ViV{aBXBSsDcU~^f*&{i8Cey7!E)OzyTMKRa}~6s1GEJ^aH#ZnYb80VhjwJzE=T@BM9`c z;9^h!OEQ8&Yjb&m_pO_ZA0q6o-rG1APUEvdkOA2SH=tR_Hh{y4x>od}Spg11VyzHi zfTS#JAqox#aC*4{WFm(zD;r3T1qc%u85sV66(Z7#LFTfE#WI>QJV0>}HUnaWZXnAT z6i+4~!W2Z9fd~r_VF@A(4fI1&E5PZ}BQ+-{Um-ldC?`b`VSYj$s`(QAP#{hGug zipFileNames() { + return Stream.of(ZipFileSystemTestSetup.ZIP_FILE_VIRTUAL_FOLDER_NAME); + } + + static void ensureExists(IResource resource) throws CoreException, IOException { + switch (resource.getType()) { + case IResource.FILE: { + IFileStore fileStore = EFS.getStore(resource.getLocationURI()); + ensureExistsInFileSystem(fileStore); + ensureExistsInWorkspace((IFile) resource); + break; + } + case IResource.FOLDER: { + IFileStore fileStore = EFS.getStore(resource.getLocationURI()); + ensureExistsInFileSystem(fileStore); + ensureExistsInWorkspace((IFolder) resource); + break; + } + default: + throw new IllegalArgumentException("Unexpected value: " + resource.getType()); + } + } + + static void ensureDoesNotExist(IResource resource) throws CoreException, IOException { + switch (resource.getType()) { + case IResource.FILE: { + IFileStore fileStore = EFS.getStore(resource.getLocationURI()); + ensureDoesNotExistInFileSystem(fileStore); + ensureDoesNotExistInWorkspace((IFile) resource); + break; + } + case IResource.FOLDER: { + IFileStore fileStore = EFS.getStore(resource.getLocationURI()); + ensureDoesNotExistInFileSystem(fileStore); + ensureDoesNotExistInWorkspace((IFolder) resource); + break; + } + default: + throw new IllegalArgumentException("Unexpected value: " + resource.getType()); + } + } + + static void assertTextFileContent(IFile textFile, String expectedContent) throws IOException, CoreException { + try (InputStreamReader isr = new InputStreamReader(textFile.getContents()); + BufferedReader reader = new BufferedReader(isr)) { + String content = reader.readLine(); // Assuming the file has a single line with "Hello World!" + Assert.assertEquals("The content of " + textFile.getName() + " should be '" + expectedContent + "'", + expectedContent, content); + } + } + + private static void ensureDoesNotExistInFileSystem(IFileStore store) throws CoreException { + assertTrue("store was not properly deleted: " + store, !store.fetchInfo().exists()); + } + + private static void ensureExistsInFileSystem(IFileStore store) throws CoreException, IOException { + final IFileInfo info = store.fetchInfo(); + assertTrue("file info for store does not exist: " + store, info.exists()); + } + + private static void ensureDoesNotExistInWorkspace(IFile file) throws CoreException { + assertTrue("file was not properly deleted: " + file, !file.exists()); + } + + private static void ensureDoesNotExistInWorkspace(IFolder folder) throws CoreException { + assertTrue("folder was not properly deleted: " + folder, !folder.exists()); + } + + private static void ensureExistsInWorkspace(IFile file) throws CoreException, IOException { + assertTrue("file does not exist in workspace: " + file, file.exists()); + } + + private static void ensureExistsInWorkspace(IFolder folder) throws CoreException, IOException { + assertTrue("folder does not exist in workspace: " + folder, folder.exists()); + } + + static IProgressMonitor getMonitor() { + return new FussyProgressMonitor(); + } + + static void openZipFile(IFile file) throws URISyntaxException, CoreException, IOException { + ZipFileTransformer.openZipFile(file, new NullProgressMonitor(), false); + } + + static void openZipFileBackground(IFile file) throws URISyntaxException, CoreException, IOException { + ZipFileTransformer.openZipFile(file, new NullProgressMonitor(), true); + } + + static void closeZipFile(IFolder folder) throws Exception { + ZipFileTransformer.closeZipFile(folder); + } + + static void printContents(IContainer container, String indent) throws CoreException { + IResource[] members = container.members(); + for (IResource member : members) { + if (member instanceof IFile) { + System.out.println(indent + "File: " + member.getName()); + } else if (member instanceof IContainer) { // This can be IFolder or IProject + System.out.println(indent + "Folder: " + member.getName()); + printContents((IContainer) member, indent + " "); // Recursively print contents + } + } + } + +} diff --git a/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/AutomatedResourceTests.java b/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/AutomatedResourceTests.java index 38d3f7545fe..3c899db1ab2 100644 --- a/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/AutomatedResourceTests.java +++ b/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/AutomatedResourceTests.java @@ -23,6 +23,7 @@ @Suite @SelectClasses({ // org.eclipse.core.tests.filesystem.AllFileSystemTests.class, // + org.eclipse.core.tests.filesystem.zip.AllZipFileSystemTests.class, // org.eclipse.core.tests.internal.alias.AllAliasTests.class, // org.eclipse.core.tests.internal.builders.AllBuilderTests.class, // org.eclipse.core.tests.internal.dtree.AllDtreeTests.class, //