support multiple archives

This commit is contained in:
Radek Davidek 2026-01-28 14:43:42 +01:00
parent 26d2f339fe
commit 686e3ac121
4 changed files with 128 additions and 160 deletions

View File

@ -15,7 +15,7 @@ import java.io.InputStreamReader;
*/ */
public class MainApp { public class MainApp {
public static final String APP_VERSION = "1.0.0"; public static final String APP_VERSION = "1.0.1";
public enum OS { public enum OS {
WINDOWS, LINUX, MACOS, UNKNOWN WINDOWS, LINUX, MACOS, UNKNOWN

View File

@ -790,9 +790,14 @@ public class FileOperations {
throw new IOException("Entry not found in archive: " + entryPath); throw new IOException("Entry not found in archive: " + entryPath);
} }
private static boolean isArchiveFile(File f) { public static boolean isArchiveFile(File f) {
if (f == null) return false; if (f == null) return false;
String n = f.getName().toLowerCase(); return isArchiveFile(f.getName());
}
public static boolean isArchiveFile(String filename) {
if (filename == null) return false;
String n = filename.toLowerCase();
return n.endsWith(".war") || n.endsWith(".zip") || n.endsWith(".jar") || n.endsWith(".tar") || n.endsWith(".tar.gz") || n.endsWith(".tgz") || n.endsWith(".7z") || n.endsWith(".rar"); return n.endsWith(".war") || n.endsWith(".zip") || n.endsWith(".jar") || n.endsWith(".tar") || n.endsWith(".tar.gz") || n.endsWith(".tgz") || n.endsWith(".7z") || n.endsWith(".rar");
} }
@ -989,6 +994,110 @@ public class FileOperations {
} }
} }
/**
* Extract an archive into a target directory
*/
public static void extractArchive(File archiveFile, File targetDirectory, ProgressCallback callback) throws Exception {
if (!targetDirectory.exists()) {
Files.createDirectories(targetDirectory.toPath());
}
String name = archiveFile.getName().toLowerCase();
if (name.endsWith(".zip") || name.endsWith(".jar") || name.endsWith(".war")) {
unzip(archiveFile, targetDirectory, callback);
} else if (name.endsWith(".tar.gz") || name.endsWith(".tgz")) {
extractTarGz(archiveFile, targetDirectory, callback);
} else if (name.endsWith(".tar")) {
extractTar(archiveFile, targetDirectory, callback);
} else if (name.endsWith(".7z")) {
extractSevenZ(archiveFile, targetDirectory, callback);
} else if (name.endsWith(".rar")) {
extractRar(archiveFile, targetDirectory, callback);
} else {
throw new IOException("Unsupported archive format: " + archiveFile.getName());
}
}
private static void extractTarGz(File archive, File targetDir, ProgressCallback callback) throws IOException {
try (InputStream fis = Files.newInputStream(archive.toPath());
InputStream gzis = new org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream(fis);
org.apache.commons.compress.archivers.tar.TarArchiveInputStream tais = new org.apache.commons.compress.archivers.tar.TarArchiveInputStream(gzis)) {
extractTarInternal(tais, targetDir, callback);
}
}
private static void extractTar(File archive, File targetDir, ProgressCallback callback) throws IOException {
try (InputStream fis = Files.newInputStream(archive.toPath());
org.apache.commons.compress.archivers.tar.TarArchiveInputStream tais = new org.apache.commons.compress.archivers.tar.TarArchiveInputStream(fis)) {
extractTarInternal(tais, targetDir, callback);
}
}
private static void extractTarInternal(org.apache.commons.compress.archivers.tar.TarArchiveInputStream tais, File targetDir, ProgressCallback callback) throws IOException {
org.apache.commons.compress.archivers.tar.TarArchiveEntry entry;
long current = 0;
while ((entry = tais.getNextTarEntry()) != null) {
current++;
if (callback != null) callback.onProgress(current, -1, entry.getName());
File newFile = new File(targetDir, entry.getName());
if (entry.isDirectory()) {
newFile.mkdirs();
} else {
newFile.getParentFile().mkdirs();
Files.copy(tais, newFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
}
}
}
private static void extractSevenZ(File archive, File targetDir, ProgressCallback callback) throws IOException {
try (org.apache.commons.compress.archivers.sevenz.SevenZFile sevenZFile = new org.apache.commons.compress.archivers.sevenz.SevenZFile(archive)) {
org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry entry;
long current = 0;
while ((entry = sevenZFile.getNextEntry()) != null) {
current++;
if (callback != null) callback.onProgress(current, -1, entry.getName());
File newFile = new File(targetDir, entry.getName());
if (entry.isDirectory()) {
newFile.mkdirs();
} else {
newFile.getParentFile().mkdirs();
try (OutputStream os = Files.newOutputStream(newFile.toPath())) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = sevenZFile.read(buffer)) != -1) {
os.write(buffer, 0, bytesRead);
}
}
}
}
}
}
private static void extractRar(File archiveFile, File targetDir, ProgressCallback callback) throws Exception {
try (com.github.junrar.Archive archive = new com.github.junrar.Archive(archiveFile)) {
com.github.junrar.rarfile.FileHeader fh = archive.nextFileHeader();
long current = 0;
while (fh != null) {
current++;
String entryName = fh.getFileName().replace('\\', '/');
if (callback != null) callback.onProgress(current, -1, entryName);
File newFile = new File(targetDir, entryName);
if (fh.isDirectory()) {
newFile.mkdirs();
} else {
newFile.getParentFile().mkdirs();
try (OutputStream os = Files.newOutputStream(newFile.toPath())) {
archive.extractFile(fh, os);
}
}
fh = archive.nextFileHeader();
}
}
}
/** /**
* Unzip a zip file into a target directory * Unzip a zip file into a target directory
*/ */

View File

@ -26,20 +26,8 @@ import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.archivers.sevenz.SevenZFile;
import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
import com.github.junrar.Archive;
import com.github.junrar.rarfile.FileHeader;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.PosixFileAttributes; import java.nio.file.attribute.PosixFileAttributes;
import java.nio.file.attribute.PosixFilePermissions; import java.nio.file.attribute.PosixFilePermissions;
@ -755,7 +743,7 @@ public class FilePanelTab extends JPanel {
int row = fileTable.getSelectedRow(); int row = fileTable.getSelectedRow();
if (row >= 0) { if (row >= 0) {
FileItem item = (viewMode == ViewMode.BRIEF) ? tableModel.getItemFromBriefLayout(row, briefCurrentColumn) : tableModel.getItem(row); FileItem item = (viewMode == ViewMode.BRIEF) ? tableModel.getItemFromBriefLayout(row, briefCurrentColumn) : tableModel.getItem(row);
if (item != null && (item.isDirectory() || isArchiveFile(item.getFile()))) { if (item != null && (item.isDirectory() || FileOperations.isArchiveFile(item.getFile()))) {
openSelectedItem(); openSelectedItem();
} }
} }
@ -1241,30 +1229,11 @@ public class FilePanelTab extends JPanel {
} }
} }
private boolean isArchiveFile(File f) {
if (f == null) return false;
String n = f.getName().toLowerCase();
return n.endsWith(".war") || n.endsWith(".zip") || n.endsWith(".jar") || n.endsWith(".tar") || n.endsWith(".tar.gz") || n.endsWith(".tgz") || n.endsWith(".7z") || n.endsWith(".rar");
}
private Path extractArchiveToTemp(File archive) { private Path extractArchiveToTemp(File archive) {
if (archive == null || !archive.isFile()) return null; if (archive == null || !archive.isFile()) return null;
String name = archive.getName().toLowerCase();
try { try {
Path tempDir = Files.createTempDirectory("kfmanager-archive-"); Path tempDir = Files.createTempDirectory("kfmanager-archive-");
FileOperations.extractArchive(archive, tempDir.toFile(), null);
if (name.endsWith(".zip") || name.endsWith(".jar") || name.endsWith(".war")) {
extractZip(archive, tempDir);
} else if (name.endsWith(".tar.gz") || name.endsWith(".tgz")) {
extractTarGz(archive, tempDir);
} else if (name.endsWith(".tar")) {
extractTar(archive, tempDir);
} else if (name.endsWith(".7z")) {
extractSevenZ(archive, tempDir);
} else if (name.endsWith(".rar")) {
extractRar(archive, tempDir);
}
return tempDir; return tempDir;
} catch (Exception ex) { } catch (Exception ex) {
// extraction failed; attempt best-effort cleanup // extraction failed; attempt best-effort cleanup
@ -1275,116 +1244,6 @@ public class FilePanelTab extends JPanel {
} }
} }
private void extractZip(File archive, Path tempDir) throws IOException {
try (ZipInputStream zis = new ZipInputStream(Files.newInputStream(archive.toPath()))) {
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
String entryName = entry.getName();
Path resolved = tempDir.resolve(entryName).normalize();
if (!resolved.startsWith(tempDir)) {
zis.closeEntry();
continue;
}
if (entry.isDirectory() || entryName.endsWith("/")) {
Files.createDirectories(resolved);
} else {
Path parent = resolved.getParent();
if (parent != null) Files.createDirectories(parent);
Files.copy(zis, resolved, StandardCopyOption.REPLACE_EXISTING);
}
zis.closeEntry();
}
}
}
private void extractSevenZ(File archive, Path tempDir) throws IOException {
try (SevenZFile sevenZFile = new SevenZFile(archive)) {
SevenZArchiveEntry entry;
while ((entry = sevenZFile.getNextEntry()) != null) {
String entryName = entry.getName();
Path resolved = tempDir.resolve(entryName).normalize();
if (!resolved.startsWith(tempDir)) {
continue;
}
if (entry.isDirectory()) {
Files.createDirectories(resolved);
} else {
Path parent = resolved.getParent();
if (parent != null) Files.createDirectories(parent);
try (OutputStream os = Files.newOutputStream(resolved)) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = sevenZFile.read(buffer)) != -1) {
os.write(buffer, 0, bytesRead);
}
}
}
}
}
}
private void extractRar(File archiveFile, Path tempDir) throws Exception {
try (Archive archive = new Archive(archiveFile)) {
FileHeader fh = archive.nextFileHeader();
while (fh != null) {
String entryName = fh.getFileName().replace('\\', '/');
Path resolved = tempDir.resolve(entryName).normalize();
if (!resolved.startsWith(tempDir)) {
fh = archive.nextFileHeader();
continue;
}
if (fh.isDirectory()) {
Files.createDirectories(resolved);
} else {
Path parent = resolved.getParent();
if (parent != null) Files.createDirectories(parent);
try (OutputStream os = Files.newOutputStream(resolved)) {
archive.extractFile(fh, os);
}
}
fh = archive.nextFileHeader();
}
}
}
private void extractTarGz(File archive, Path tempDir) throws IOException {
try (InputStream fis = Files.newInputStream(archive.toPath());
InputStream gzis = new GzipCompressorInputStream(fis);
TarArchiveInputStream tais = new TarArchiveInputStream(gzis)) {
extractTarInternal(tais, tempDir);
}
}
private void extractTar(File archive, Path tempDir) throws IOException {
try (InputStream fis = Files.newInputStream(archive.toPath());
TarArchiveInputStream tais = new TarArchiveInputStream(fis)) {
extractTarInternal(tais, tempDir);
}
}
private void extractTarInternal(TarArchiveInputStream tais, Path tempDir) throws IOException {
TarArchiveEntry entry;
while ((entry = tais.getNextTarEntry()) != null) {
String entryName = entry.getName();
Path resolved = tempDir.resolve(entryName).normalize();
if (!resolved.startsWith(tempDir)) {
continue;
}
if (entry.isDirectory()) {
Files.createDirectories(resolved);
} else {
Path parent = resolved.getParent();
if (parent != null) Files.createDirectories(parent);
Files.copy(tais, resolved, StandardCopyOption.REPLACE_EXISTING);
}
}
}
private void deleteTempDirRecursively(Path dir) { private void deleteTempDirRecursively(Path dir) {
if (dir == null) return; if (dir == null) return;
try { try {
@ -1412,7 +1271,7 @@ public class FilePanelTab extends JPanel {
if (item.getName().equals("..")) { if (item.getName().equals("..")) {
navigateUp(); navigateUp();
} else if (isArchiveFile(item.getFile())) { } else if (FileOperations.isArchiveFile(item.getFile())) {
Path temp = extractArchiveToTemp(item.getFile()); Path temp = extractArchiveToTemp(item.getFile());
if (temp != null) { if (temp != null) {
// Delete any previous temp dir if different // Delete any previous temp dir if different
@ -1551,7 +1410,7 @@ public class FilePanelTab extends JPanel {
if (item.getName().equals("..")) { if (item.getName().equals("..")) {
navigateUp(); navigateUp();
} else if (isArchiveFile(item.getFile())) { } else if (FileOperations.isArchiveFile(item.getFile())) {
Path temp = extractArchiveToTemp(item.getFile()); Path temp = extractArchiveToTemp(item.getFile());
if (temp != null) { if (temp != null) {
try { try {

View File

@ -1837,24 +1837,24 @@ public class MainWindow extends JFrame {
} }
/** /**
* Unzip selected zip file * Extract selected archive
*/ */
private void unzipFiles() { private void unzipFiles() {
List<FileItem> selectedItems = activePanel.getSelectedItems(); List<FileItem> selectedItems = activePanel.getSelectedItems();
if (selectedItems.isEmpty()) { if (selectedItems.isEmpty()) {
JOptionPane.showMessageDialog(this, JOptionPane.showMessageDialog(this,
"No files selected", "No files selected",
"Unzip", "Extract",
JOptionPane.INFORMATION_MESSAGE); JOptionPane.INFORMATION_MESSAGE);
requestFocusInActivePanel(); requestFocusInActivePanel();
return; return;
} }
File zipFile = selectedItems.get(0).getFile(); File archiveFile = selectedItems.get(0).getFile();
if (!zipFile.getName().toLowerCase().endsWith(".zip")) { if (!FileOperations.isArchiveFile(archiveFile)) {
JOptionPane.showMessageDialog(this, JOptionPane.showMessageDialog(this,
"Selected file is not a ZIP archive", "Selected file is not a supported archive",
"Unzip", "Extract",
JOptionPane.ERROR_MESSAGE); JOptionPane.ERROR_MESSAGE);
requestFocusInActivePanel(); requestFocusInActivePanel();
return; return;
@ -1865,18 +1865,18 @@ public class MainWindow extends JFrame {
final FilePanel sourcePanel = activePanel; final FilePanel sourcePanel = activePanel;
int result = showConfirmWithBackground( int result = showConfirmWithBackground(
String.format("Unzip %s to:\n%s", zipFile.getName(), targetDir.getAbsolutePath()), String.format("Extract %s to:\n%s", archiveFile.getName(), targetDir.getAbsolutePath()),
"Unzip"); "Extract archive");
if (result == 0 || result == 1) { if (result == 0 || result == 1) {
boolean background = (result == 1); boolean background = (result == 1);
if (background) { if (background) {
addOperationToQueue("Unzip", String.format("Unzip %s to %s", zipFile.getName(), targetDir.getName()), addOperationToQueue("Extract", String.format("Extract %s to %s", archiveFile.getName(), targetDir.getName()),
(cb) -> FileOperations.unzip(zipFile, targetDir, cb), () -> sourcePanel.unselectAll(), targetPanel); (cb) -> FileOperations.extractArchive(archiveFile, targetDir, cb), () -> sourcePanel.unselectAll(), targetPanel);
} else { } else {
performFileOperation((callback) -> { performFileOperation((callback) -> {
FileOperations.unzip(zipFile, targetDir, callback); FileOperations.extractArchive(archiveFile, targetDir, callback);
}, "Unzipped into " + targetDir.getName(), false, true, () -> sourcePanel.unselectAll(), targetPanel); }, "Extracted into " + targetDir.getName(), false, true, () -> sourcePanel.unselectAll(), targetPanel);
} }
} else { } else {
if (activePanel != null && activePanel.getFileTable() != null) { if (activePanel != null && activePanel.getFileTable() != null) {